npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@90soft/parse-server-kit

v2.4.0

Published

Complete decorator-driven development kit for Parse Server — models, cloud functions, triggers, cron, routing, ACL, Swagger, and more.

Readme

@90soft/parse-server-kit

Decorator-driven development kit for Parse Server. Write models and cloud functions with TypeScript decorators — no boilerplate.

Install

npm install @90soft/parse-server-kit

Peer dependencies: parse, reflect-metadata, express Optional: node-cron (for @Cron), swagger-ui-express (for Swagger)


Quick Start

1. Define a Model

import { ParseClass, ParseField, BaseModel } from '@90soft/parse-server-kit';
import { UserRoles, roleKey } from '@90soft/parse-server-kit';

@ParseClass('Product', {
  clp: {
    find:   { [roleKey(UserRoles.ADMIN)]: true, [roleKey(UserRoles.EMPLOYEE)]: true },
    get:    { [roleKey(UserRoles.ADMIN)]: true, [roleKey(UserRoles.EMPLOYEE)]: true },
    create: { [roleKey(UserRoles.ADMIN)]: true, [roleKey(UserRoles.EMPLOYEE)]: true },
    update: { [roleKey(UserRoles.ADMIN)]: true, [roleKey(UserRoles.EMPLOYEE)]: true },
    delete: { [roleKey(UserRoles.ADMIN)]: true },
    count:  { [roleKey(UserRoles.ADMIN)]: true, [roleKey(UserRoles.EMPLOYEE)]: true },
  },
})
export default class Product extends BaseModel {
  @ParseField({ type: 'String', required: true })
  name!: string;

  @ParseField({ type: 'Number', required: true, min: 0 })
  price!: number;

  @ParseField({ type: 'String', enum: ['active', 'draft', 'archived'] })
  status!: string;

  @ParseField({ type: 'Pointer', targetClass: 'Category', required: true })
  category!: any;

  @ParseField({ type: 'Boolean' })
  available!: boolean;

  @ParseField({ type: 'Date' })
  createdDate!: Date;
}

2. Write Cloud Functions

import { CloudFunction, Route, catchError, UserRoles } from '@90soft/parse-server-kit';
import Product from '../../models/Product';
import User from '../../models/User';

@Route(Product) // → /api/products/create, /api/products/list, etc.
class ProductFunctions {

  @CloudFunction({
    methods: ['POST'],
    validation: { requireUser: true, fields: { name: { required: true } } },
  })
  static async createProduct(req: Parse.Cloud.FunctionRequest) {
    const user = req.user! as User;
    const product = Product.fromParams(req.params);
    const [err, saved] = await catchError(
      product.save(null, { sessionToken: user.getSessionToken() })
    );
    if (err) throw err;
    return saved;
  }

  @CloudFunction({
    methods: ['GET'],
    validation: { requireUser: true, fields: { skip: { type: String }, limit: { type: String } } },
  })
  static async listProducts(req: Parse.Cloud.FunctionRequest) {
    const query = new Parse.Query(Product);
    query.include('category');
    const skip = Number(req.params.skip) || 0;
    const limit = Number(req.params.limit) || 10;
    query.skip(skip).limit(limit);

    if (req.params.search) {
      const search = req.params.search;
      query.matches('name', new RegExp(search, 'i'));
    }

    const [err, results] = await catchError(query.find({ sessionToken: req.user!.getSessionToken() }));
    if (err) throw err;

    const [countErr, count] = await catchError(query.count({ sessionToken: req.user!.getSessionToken() }));
    return { results, count: countErr ? 0 : count };
  }

  @CloudFunction({
    methods: ['POST'],
    validation: { requireUser: true, fields: { id: { required: true } } },
  })
  static async getProduct(req: Parse.Cloud.FunctionRequest) {
    const query = new Parse.Query(Product);
    query.include('category');
    const [err, product] = await catchError(query.get(req.params.id, { sessionToken: req.user!.getSessionToken() }));
    if (err) throw err;
    return product;
  }

  @CloudFunction({
    methods: ['POST'],
    validation: { requireUser: true, fields: { id: { required: true } } },
  })
  static async updateProduct(req: Parse.Cloud.FunctionRequest) {
    const user = req.user! as User;
    const product = Product.fromParams(req.params);
    const [err, saved] = await catchError(
      product.save(null, { sessionToken: user.getSessionToken() })
    );
    if (err) throw err;
    return saved;
  }

  @CloudFunction({
    methods: ['POST'],
    validation: { requireUser: true, fields: { id: { required: true } } },
  })
  static async deleteProduct(req: Parse.Cloud.FunctionRequest) {
    const query = new Parse.Query(Product);
    const [err, product] = await catchError(query.get(req.params.id, { useMasterKey: true }));
    if (err) throw err;
    const [delErr] = await catchError(product!.destroy({ useMasterKey: true }));
    if (delErr) throw delErr;
    return { success: true };
  }
}

3. Add Triggers

import { ParseClass, ParseField, BaseModel, BeforeSave, AfterDelete } from '@90soft/parse-server-kit';

@ParseClass('Product', { /* clp... */ })
export default class Product extends BaseModel {
  @ParseField({ type: 'String', required: true })
  name!: string;

  @BeforeSave()
  static async onBeforeSave(req: Parse.Cloud.BeforeSaveRequest<Product>) {
    // Validate or modify before saving
    if (!req.object.get('name')) {
      throw new Parse.Error(142, 'Name is required');
    }
  }

  @AfterDelete()
  static async onAfterDelete(req: Parse.Cloud.AfterDeleteRequest<Product>) {
    // Cleanup after deletion
    console.log(`Product ${req.object.id} deleted`);
  }
}

4. Add Cron Jobs

import { Cron, CronSchedule } from '@90soft/parse-server-kit';

class MyCronJobs {
  @Cron({ schedule: CronSchedule.DAILY_MIDNIGHT, description: 'Cleanup expired sessions' })
  static async cleanupSessions() {
    // job logic
  }

  @Cron({ schedule: '*/30 * * * *', description: 'Sync data every 30 minutes' })
  static async syncData() {
    // job logic
  }
}

5. Server Setup (app.ts)

import express from 'express';
import {
  CloudFunctionRegistry, TriggerRegistry, CronRegistry,
  importFiles, catchError, applyMongoValidators,
  validateEntityRoutes, restrictRoutes, removeResultMiddleware,
  conditionalJsonMiddleware, setupSwagger,
} from '@90soft/parse-server-kit';

const app = express();

// 1. Load models (must be before Parse Server init)
importFiles(join(__dirname, 'cloudCode/models'));

// 2. Init Parse Server
const parseServer = await initializeParseServer();

// 3. Middleware
app.use(removeResultMiddleware);
app.use(cors());
app.use(process.env.mountPath, validateEntityRoutes);
app.use(conditionalJsonMiddleware);
app.use(process.env.mountPath, restrictRoutes);

// 4. Mount Parse Server
app.use(process.env.mountPath, parseServer.app);

// 5. Initialize registries (after Parse Server mount)
CloudFunctionRegistry.initialize();  // also initializes RouteRegistry
TriggerRegistry.initialize();
CronRegistry.initialize();

// 6. Swagger docs
setupSwagger(app, { title: 'My API', version: '1.0.0' });

// 7. Start server
server.listen(1337);

API Reference

Decorators

| Decorator | Target | Description | |---|---|---| | @ParseClass(name, options) | Class | Registers a Parse model with CLP, ACL, indexes | | @ParseField(options) | Property | Defines a field with type, validation, index | | @CloudFunction(config) | Static method | Registers a cloud function with HTTP method, validation, roles | | @ProtectedCloudFunction(config) | Static method | Same as @CloudFunction but requires auth by default | | @Route(ModelOrString) | Class | Maps cloud functions to /api/{entity}/{action} routes | | @Cron(config) | Static method | Registers a cron job with schedule | | @BeforeSave(config?) | Static method | Before save trigger | | @AfterSave(config?) | Static method | After save trigger | | @BeforeDelete(config?) | Static method | Before delete trigger | | @AfterDelete(config?) | Static method | After delete trigger | | @BeforeFind(config?) | Static method | Before find trigger | | @AfterFind(config?) | Static method | After find trigger | | @BeforeLogin(config?) | Static method | Before login trigger | | @AfterLogin(config?) | Static method | After login trigger | | @AfterLogout(config?) | Static method | After logout trigger |

@ParseField Options

| Option | Type | Description | |---|---|---| | type | 'String' \| 'Number' \| 'Boolean' \| 'Date' \| 'Pointer' \| 'Array' \| ... | Field type | | required | boolean | Whether the field is required | | targetClass | string | Target class for Pointer/Relation | | index | boolean \| 1 \| -1 | Create an index | | unique | boolean | Create a unique index | | min / max | number | Number range validation | | minLength / maxLength | number | String length validation | | enum | string[] | Allowed values for String fields | | pattern | string | Regex pattern for String fields | | description | string | Swagger documentation |

@Route

// Import model class — auto-generates route from className
@Route(Product)  // → /api/products/*

// Custom string — full control over route prefix
@Route('menu-items')  // → /api/menu-items/*

Routes are automatically mapped:

createProduct  → POST /api/products/create
getProduct     → POST /api/products/get
listProducts   → GET  /api/products/list
updateProduct  → POST /api/products/update
deleteProduct  → POST /api/products/delete

BaseModel

// Create from request params (auto-maps fields, handles pointers)
const product = Product.fromParams(req.params);

// Create a pointer reference by ID
const category = Category.pointer('abc123');

// Override excluded pointer classes (default: ['IMG', 'File'])
class MyModel extends BaseModel {
  protected static EXCLUDED_POINTER_CLASSES = ['IMG', 'File', 'CustomFile'];
}

ACL

import { implementACL, UserRoles } from '@90soft/parse-server-kit';

obj.setACL(implementACL({
  roleRules: [
    { role: UserRoles.ADMIN, read: true, write: true },
    { role: UserRoles.EMPLOYEE, read: true },
  ],
  owner: [
    { user: userId, read: true, write: true },
  ],
}));

Utilities

import {
  catchError,          // Wraps promises: const [err, data] = await catchError(promise)
  getUserRoles,        // Get role names for a user
  getUsersRoles,       // Batch get roles for multiple users
  importFiles,         // Auto-import all .js files in a directory
  generateRandomPassword,
  generateRandomString,
  sleep,
  formatCount,
} from '@90soft/parse-server-kit';

Middleware

import {
  validateEntityRoutes,       // Maps /api/{entity}/{action} → cloud functions
  restrictRoutes,             // Blocks /classes, /schemas, etc.
  removeResultMiddleware,     // Unwraps Parse {result: ...} wrapper
  conditionalJsonMiddleware,  // JSON parsing with master key extraction
  validateFunctionRoutes,     // Legacy /functions/* validation
} from '@90soft/parse-server-kit';

Swagger

import { setupSwagger, SwaggerRegistry } from '@90soft/parse-server-kit';

// Auto-generates API docs from @ParseClass and @CloudFunction metadata
setupSwagger(app, {
  title: 'My API',
  version: '1.0.0',
  description: 'Auto-generated docs',
});
// Access at: http://localhost:1337/api-docs

Hooks (for integrations)

import { onClassRegistered, onFieldRegistered, onFunctionRegistered } from '@90soft/parse-server-kit';

// Runs when @ParseClass is applied
onClassRegistered((className, constructor, fields, options) => {
  // Register with Swagger, setup triggers, etc.
});

// Runs when @CloudFunction is applied
onFunctionRegistered((name, config, target) => {
  // Register with Swagger docs
});

Constants

import { UserRoles, roleKey, MAX_QUERY_LIMIT, CronSchedule } from '@90soft/parse-server-kit';

UserRoles.ADMIN      // 'SuperAdmin'
UserRoles.EMPLOYEE   // 'Employee'
roleKey(UserRoles.ADMIN)  // 'role:SuperAdmin'
MAX_QUERY_LIMIT      // 10000

CronSchedule.EVERY_HOUR      // '0 * * * *'
CronSchedule.DAILY_MIDNIGHT  // '0 0 * * *'
CronSchedule.WEEKLY_MONDAY   // '0 0 * * 1'

Validation

import { validateObject, validateOrThrow, applyMongoValidators } from '@90soft/parse-server-kit';

// Validate against @ParseField constraints (min, max, enum, pattern, etc.)
const result = validateObject(parseObject);
// { valid: false, errors: [{ field: 'price', message: 'must be at least 0' }] }

// Throw Parse.Error if invalid (use in BeforeSave triggers)
validateOrThrow(parseObject);

// Apply MongoDB schema validators from decorator metadata
await applyMongoValidators(parseServerInstance);

File Structure Convention

backend/src/cloudCode/
  ├── models/
  │   └── Product.ts              ← @ParseClass + @ParseField + triggers
  ├── modules/
  │   └── Product/
  │       └── functions.ts        ← @Route + @CloudFunction
  ├── cron.ts                     ← @Cron jobs
  ├── main.ts                     ← importFiles for models + modules
  └── decorator/
      └── setupHooks.ts           ← connects package hooks to Swagger + Triggers

License

MIT