@90soft/parse-server-kit 1.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +424 -0
  2. package/dist/decorators/cloudDecorator.d.ts +7 -0
  3. package/dist/decorators/cloudDecorator.d.ts.map +1 -0
  4. package/dist/decorators/cloudDecorator.js +77 -0
  5. package/dist/decorators/cloudDecorator.js.map +1 -0
  6. package/dist/decorators/cloudRegistry.d.ts +9 -0
  7. package/dist/decorators/cloudRegistry.d.ts.map +1 -0
  8. package/dist/decorators/cloudRegistry.js +26 -0
  9. package/dist/decorators/cloudRegistry.js.map +1 -0
  10. package/dist/decorators/cronDecorator.d.ts +6 -0
  11. package/dist/decorators/cronDecorator.d.ts.map +1 -0
  12. package/dist/decorators/cronDecorator.js +28 -0
  13. package/dist/decorators/cronDecorator.js.map +1 -0
  14. package/dist/decorators/cronRegistry.d.ts +13 -0
  15. package/dist/decorators/cronRegistry.d.ts.map +1 -0
  16. package/dist/decorators/cronRegistry.js +96 -0
  17. package/dist/decorators/cronRegistry.js.map +1 -0
  18. package/dist/decorators/routeDecorator.d.ts +35 -0
  19. package/dist/decorators/routeDecorator.d.ts.map +1 -0
  20. package/dist/decorators/routeDecorator.js +160 -0
  21. package/dist/decorators/routeDecorator.js.map +1 -0
  22. package/dist/decorators/triggerDecorator.d.ts +19 -0
  23. package/dist/decorators/triggerDecorator.d.ts.map +1 -0
  24. package/dist/decorators/triggerDecorator.js +45 -0
  25. package/dist/decorators/triggerDecorator.js.map +1 -0
  26. package/dist/decorators/triggerRegistry.d.ts +10 -0
  27. package/dist/decorators/triggerRegistry.d.ts.map +1 -0
  28. package/dist/decorators/triggerRegistry.js +87 -0
  29. package/dist/decorators/triggerRegistry.js.map +1 -0
  30. package/dist/decorators/types/cloudTypes.d.ts +32 -0
  31. package/dist/decorators/types/cloudTypes.d.ts.map +1 -0
  32. package/dist/decorators/types/cloudTypes.js +3 -0
  33. package/dist/decorators/types/cloudTypes.js.map +1 -0
  34. package/dist/decorators/types/common.d.ts +14 -0
  35. package/dist/decorators/types/common.d.ts.map +1 -0
  36. package/dist/decorators/types/common.js +3 -0
  37. package/dist/decorators/types/common.js.map +1 -0
  38. package/dist/decorators/types/cronTypes.d.ts +32 -0
  39. package/dist/decorators/types/cronTypes.d.ts.map +1 -0
  40. package/dist/decorators/types/cronTypes.js +21 -0
  41. package/dist/decorators/types/cronTypes.js.map +1 -0
  42. package/dist/decorators/types/fieldValidation.d.ts +36 -0
  43. package/dist/decorators/types/fieldValidation.d.ts.map +1 -0
  44. package/dist/decorators/types/fieldValidation.js +135 -0
  45. package/dist/decorators/types/fieldValidation.js.map +1 -0
  46. package/dist/decorators/types/triggerTypes.d.ts +14 -0
  47. package/dist/decorators/types/triggerTypes.d.ts.map +1 -0
  48. package/dist/decorators/types/triggerTypes.js +3 -0
  49. package/dist/decorators/types/triggerTypes.js.map +1 -0
  50. package/dist/index.d.ts +25 -4
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +78 -6
  53. package/dist/index.js.map +1 -1
  54. package/dist/middleware/middleware.d.ts +41 -0
  55. package/dist/middleware/middleware.d.ts.map +1 -0
  56. package/dist/middleware/middleware.js +154 -0
  57. package/dist/middleware/middleware.js.map +1 -0
  58. package/dist/swagger/index.d.ts +9 -0
  59. package/dist/swagger/index.d.ts.map +1 -0
  60. package/dist/swagger/index.js +37 -0
  61. package/dist/swagger/index.js.map +1 -0
  62. package/dist/swagger/swaggerRegistry.d.ts +79 -0
  63. package/dist/swagger/swaggerRegistry.d.ts.map +1 -0
  64. package/dist/swagger/swaggerRegistry.js +115 -0
  65. package/dist/swagger/swaggerRegistry.js.map +1 -0
  66. package/dist/swagger/swaggerSpec.d.ts +69 -0
  67. package/dist/swagger/swaggerSpec.d.ts.map +1 -0
  68. package/dist/swagger/swaggerSpec.js +174 -0
  69. package/dist/swagger/swaggerSpec.js.map +1 -0
  70. package/dist/utils/ACL.d.ts +19 -0
  71. package/dist/utils/ACL.d.ts.map +1 -0
  72. package/dist/utils/ACL.js +35 -0
  73. package/dist/utils/ACL.js.map +1 -0
  74. package/dist/utils/constants.d.ts +13 -0
  75. package/dist/utils/constants.d.ts.map +1 -0
  76. package/dist/utils/constants.js +19 -0
  77. package/dist/utils/constants.js.map +1 -0
  78. package/dist/utils/dynamicImport.d.ts +2 -0
  79. package/dist/utils/dynamicImport.d.ts.map +1 -0
  80. package/dist/utils/dynamicImport.js +21 -0
  81. package/dist/utils/dynamicImport.js.map +1 -0
  82. package/dist/utils/helper.d.ts +15 -0
  83. package/dist/utils/helper.d.ts.map +1 -0
  84. package/dist/utils/helper.js +93 -0
  85. package/dist/utils/helper.js.map +1 -0
  86. package/package.json +21 -5
package/README.md ADDED
@@ -0,0 +1,424 @@
1
+ # @90soft/parse-server-kit
2
+
3
+ Decorator-driven development kit for Parse Server. Write models and cloud functions with TypeScript decorators — no boilerplate.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @90soft/parse-server-kit
9
+ ```
10
+
11
+ Peer dependencies: `parse`, `reflect-metadata`, `express`
12
+ Optional: `node-cron` (for @Cron), `swagger-ui-express` (for Swagger)
13
+
14
+ ---
15
+
16
+ ## Quick Start
17
+
18
+ ### 1. Define a Model
19
+
20
+ ```ts
21
+ import { ParseClass, ParseField, BaseModel } from '@90soft/parse-server-kit';
22
+ import { UserRoles, roleKey } from '@90soft/parse-server-kit';
23
+
24
+ @ParseClass('Product', {
25
+ clp: {
26
+ find: { [roleKey(UserRoles.ADMIN)]: true, [roleKey(UserRoles.EMPLOYEE)]: true },
27
+ get: { [roleKey(UserRoles.ADMIN)]: true, [roleKey(UserRoles.EMPLOYEE)]: true },
28
+ create: { [roleKey(UserRoles.ADMIN)]: true, [roleKey(UserRoles.EMPLOYEE)]: true },
29
+ update: { [roleKey(UserRoles.ADMIN)]: true, [roleKey(UserRoles.EMPLOYEE)]: true },
30
+ delete: { [roleKey(UserRoles.ADMIN)]: true },
31
+ count: { [roleKey(UserRoles.ADMIN)]: true, [roleKey(UserRoles.EMPLOYEE)]: true },
32
+ },
33
+ })
34
+ export default class Product extends BaseModel {
35
+ @ParseField({ type: 'String', required: true })
36
+ name!: string;
37
+
38
+ @ParseField({ type: 'Number', required: true, min: 0 })
39
+ price!: number;
40
+
41
+ @ParseField({ type: 'String', enum: ['active', 'draft', 'archived'] })
42
+ status!: string;
43
+
44
+ @ParseField({ type: 'Pointer', targetClass: 'Category', required: true })
45
+ category!: any;
46
+
47
+ @ParseField({ type: 'Boolean' })
48
+ available!: boolean;
49
+
50
+ @ParseField({ type: 'Date' })
51
+ createdDate!: Date;
52
+ }
53
+ ```
54
+
55
+ ### 2. Write Cloud Functions
56
+
57
+ ```ts
58
+ import { CloudFunction, Route, catchError, UserRoles } from '@90soft/parse-server-kit';
59
+ import Product from '../../models/Product';
60
+ import User from '../../models/User';
61
+
62
+ @Route(Product) // → /api/products/create, /api/products/list, etc.
63
+ class ProductFunctions {
64
+
65
+ @CloudFunction({
66
+ methods: ['POST'],
67
+ validation: { requireUser: true, fields: { name: { required: true } } },
68
+ })
69
+ static async createProduct(req: Parse.Cloud.FunctionRequest) {
70
+ const user = req.user! as User;
71
+ const product = Product.fromParams(req.params);
72
+ const [err, saved] = await catchError(
73
+ product.save(null, { sessionToken: user.getSessionToken() })
74
+ );
75
+ if (err) throw err;
76
+ return saved;
77
+ }
78
+
79
+ @CloudFunction({
80
+ methods: ['GET'],
81
+ validation: { requireUser: true, fields: { skip: { type: String }, limit: { type: String } } },
82
+ })
83
+ static async listProducts(req: Parse.Cloud.FunctionRequest) {
84
+ const query = new Parse.Query(Product);
85
+ query.include('category');
86
+ const skip = Number(req.params.skip) || 0;
87
+ const limit = Number(req.params.limit) || 10;
88
+ query.skip(skip).limit(limit);
89
+
90
+ if (req.params.search) {
91
+ const search = req.params.search;
92
+ query.matches('name', new RegExp(search, 'i'));
93
+ }
94
+
95
+ const [err, results] = await catchError(query.find({ sessionToken: req.user!.getSessionToken() }));
96
+ if (err) throw err;
97
+
98
+ const [countErr, count] = await catchError(query.count({ sessionToken: req.user!.getSessionToken() }));
99
+ return { results, count: countErr ? 0 : count };
100
+ }
101
+
102
+ @CloudFunction({
103
+ methods: ['POST'],
104
+ validation: { requireUser: true, fields: { id: { required: true } } },
105
+ })
106
+ static async getProduct(req: Parse.Cloud.FunctionRequest) {
107
+ const query = new Parse.Query(Product);
108
+ query.include('category');
109
+ const [err, product] = await catchError(query.get(req.params.id, { sessionToken: req.user!.getSessionToken() }));
110
+ if (err) throw err;
111
+ return product;
112
+ }
113
+
114
+ @CloudFunction({
115
+ methods: ['POST'],
116
+ validation: { requireUser: true, fields: { id: { required: true } } },
117
+ })
118
+ static async updateProduct(req: Parse.Cloud.FunctionRequest) {
119
+ const user = req.user! as User;
120
+ const product = Product.fromParams(req.params);
121
+ const [err, saved] = await catchError(
122
+ product.save(null, { sessionToken: user.getSessionToken() })
123
+ );
124
+ if (err) throw err;
125
+ return saved;
126
+ }
127
+
128
+ @CloudFunction({
129
+ methods: ['POST'],
130
+ validation: { requireUser: true, fields: { id: { required: true } } },
131
+ })
132
+ static async deleteProduct(req: Parse.Cloud.FunctionRequest) {
133
+ const query = new Parse.Query(Product);
134
+ const [err, product] = await catchError(query.get(req.params.id, { useMasterKey: true }));
135
+ if (err) throw err;
136
+ const [delErr] = await catchError(product!.destroy({ useMasterKey: true }));
137
+ if (delErr) throw delErr;
138
+ return { success: true };
139
+ }
140
+ }
141
+ ```
142
+
143
+ ### 3. Add Triggers
144
+
145
+ ```ts
146
+ import { ParseClass, ParseField, BaseModel, BeforeSave, AfterDelete } from '@90soft/parse-server-kit';
147
+
148
+ @ParseClass('Product', { /* clp... */ })
149
+ export default class Product extends BaseModel {
150
+ @ParseField({ type: 'String', required: true })
151
+ name!: string;
152
+
153
+ @BeforeSave()
154
+ static async onBeforeSave(req: Parse.Cloud.BeforeSaveRequest<Product>) {
155
+ // Validate or modify before saving
156
+ if (!req.object.get('name')) {
157
+ throw new Parse.Error(142, 'Name is required');
158
+ }
159
+ }
160
+
161
+ @AfterDelete()
162
+ static async onAfterDelete(req: Parse.Cloud.AfterDeleteRequest<Product>) {
163
+ // Cleanup after deletion
164
+ console.log(`Product ${req.object.id} deleted`);
165
+ }
166
+ }
167
+ ```
168
+
169
+ ### 4. Add Cron Jobs
170
+
171
+ ```ts
172
+ import { Cron, CronSchedule } from '@90soft/parse-server-kit';
173
+
174
+ class MyCronJobs {
175
+ @Cron({ schedule: CronSchedule.DAILY_MIDNIGHT, description: 'Cleanup expired sessions' })
176
+ static async cleanupSessions() {
177
+ // job logic
178
+ }
179
+
180
+ @Cron({ schedule: '*/30 * * * *', description: 'Sync data every 30 minutes' })
181
+ static async syncData() {
182
+ // job logic
183
+ }
184
+ }
185
+ ```
186
+
187
+ ### 5. Server Setup (app.ts)
188
+
189
+ ```ts
190
+ import express from 'express';
191
+ import {
192
+ CloudFunctionRegistry, TriggerRegistry, CronRegistry,
193
+ importFiles, catchError, applyMongoValidators,
194
+ validateEntityRoutes, restrictRoutes, removeResultMiddleware,
195
+ conditionalJsonMiddleware, setupSwagger,
196
+ } from '@90soft/parse-server-kit';
197
+
198
+ const app = express();
199
+
200
+ // 1. Load models (must be before Parse Server init)
201
+ importFiles(join(__dirname, 'cloudCode/models'));
202
+
203
+ // 2. Init Parse Server
204
+ const parseServer = await initializeParseServer();
205
+
206
+ // 3. Middleware
207
+ app.use(removeResultMiddleware);
208
+ app.use(cors());
209
+ app.use(process.env.mountPath, validateEntityRoutes);
210
+ app.use(conditionalJsonMiddleware);
211
+ app.use(process.env.mountPath, restrictRoutes);
212
+
213
+ // 4. Mount Parse Server
214
+ app.use(process.env.mountPath, parseServer.app);
215
+
216
+ // 5. Initialize registries (after Parse Server mount)
217
+ CloudFunctionRegistry.initialize(); // also initializes RouteRegistry
218
+ TriggerRegistry.initialize();
219
+ CronRegistry.initialize();
220
+
221
+ // 6. Swagger docs
222
+ setupSwagger(app, { title: 'My API', version: '1.0.0' });
223
+
224
+ // 7. Start server
225
+ server.listen(1337);
226
+ ```
227
+
228
+ ---
229
+
230
+ ## API Reference
231
+
232
+ ### Decorators
233
+
234
+ | Decorator | Target | Description |
235
+ |---|---|---|
236
+ | `@ParseClass(name, options)` | Class | Registers a Parse model with CLP, ACL, indexes |
237
+ | `@ParseField(options)` | Property | Defines a field with type, validation, index |
238
+ | `@CloudFunction(config)` | Static method | Registers a cloud function with HTTP method, validation, roles |
239
+ | `@ProtectedCloudFunction(config)` | Static method | Same as @CloudFunction but requires auth by default |
240
+ | `@Route(ModelOrString)` | Class | Maps cloud functions to `/api/{entity}/{action}` routes |
241
+ | `@Cron(config)` | Static method | Registers a cron job with schedule |
242
+ | `@BeforeSave(config?)` | Static method | Before save trigger |
243
+ | `@AfterSave(config?)` | Static method | After save trigger |
244
+ | `@BeforeDelete(config?)` | Static method | Before delete trigger |
245
+ | `@AfterDelete(config?)` | Static method | After delete trigger |
246
+ | `@BeforeFind(config?)` | Static method | Before find trigger |
247
+ | `@AfterFind(config?)` | Static method | After find trigger |
248
+ | `@BeforeLogin(config?)` | Static method | Before login trigger |
249
+ | `@AfterLogin(config?)` | Static method | After login trigger |
250
+ | `@AfterLogout(config?)` | Static method | After logout trigger |
251
+
252
+ ### @ParseField Options
253
+
254
+ | Option | Type | Description |
255
+ |---|---|---|
256
+ | `type` | `'String' \| 'Number' \| 'Boolean' \| 'Date' \| 'Pointer' \| 'Array' \| ...` | Field type |
257
+ | `required` | `boolean` | Whether the field is required |
258
+ | `targetClass` | `string` | Target class for Pointer/Relation |
259
+ | `index` | `boolean \| 1 \| -1` | Create an index |
260
+ | `unique` | `boolean` | Create a unique index |
261
+ | `min` / `max` | `number` | Number range validation |
262
+ | `minLength` / `maxLength` | `number` | String length validation |
263
+ | `enum` | `string[]` | Allowed values for String fields |
264
+ | `pattern` | `string` | Regex pattern for String fields |
265
+ | `description` | `string` | Swagger documentation |
266
+
267
+ ### @Route
268
+
269
+ ```ts
270
+ // Import model class — auto-generates route from className
271
+ @Route(Product) // → /api/products/*
272
+
273
+ // Custom string — full control over route prefix
274
+ @Route('menu-items') // → /api/menu-items/*
275
+ ```
276
+
277
+ Routes are automatically mapped:
278
+ ```
279
+ createProduct → POST /api/products/create
280
+ getProduct → POST /api/products/get
281
+ listProducts → GET /api/products/list
282
+ updateProduct → POST /api/products/update
283
+ deleteProduct → POST /api/products/delete
284
+ ```
285
+
286
+ ### BaseModel
287
+
288
+ ```ts
289
+ // Create from request params (auto-maps fields, handles pointers)
290
+ const product = Product.fromParams(req.params);
291
+
292
+ // Create a pointer reference by ID
293
+ const category = Category.pointer('abc123');
294
+
295
+ // Override excluded pointer classes (default: ['IMG', 'File'])
296
+ class MyModel extends BaseModel {
297
+ protected static EXCLUDED_POINTER_CLASSES = ['IMG', 'File', 'CustomFile'];
298
+ }
299
+ ```
300
+
301
+ ### ACL
302
+
303
+ ```ts
304
+ import { implementACL, UserRoles } from '@90soft/parse-server-kit';
305
+
306
+ obj.setACL(implementACL({
307
+ roleRules: [
308
+ { role: UserRoles.ADMIN, read: true, write: true },
309
+ { role: UserRoles.EMPLOYEE, read: true },
310
+ ],
311
+ owner: [
312
+ { user: userId, read: true, write: true },
313
+ ],
314
+ }));
315
+ ```
316
+
317
+ ### Utilities
318
+
319
+ ```ts
320
+ import {
321
+ catchError, // Wraps promises: const [err, data] = await catchError(promise)
322
+ getUserRoles, // Get role names for a user
323
+ getUsersRoles, // Batch get roles for multiple users
324
+ importFiles, // Auto-import all .js files in a directory
325
+ generateRandomPassword,
326
+ generateRandomString,
327
+ sleep,
328
+ formatCount,
329
+ } from '@90soft/parse-server-kit';
330
+ ```
331
+
332
+ ### Middleware
333
+
334
+ ```ts
335
+ import {
336
+ validateEntityRoutes, // Maps /api/{entity}/{action} → cloud functions
337
+ restrictRoutes, // Blocks /classes, /schemas, etc.
338
+ removeResultMiddleware, // Unwraps Parse {result: ...} wrapper
339
+ conditionalJsonMiddleware, // JSON parsing with master key extraction
340
+ validateFunctionRoutes, // Legacy /functions/* validation
341
+ } from '@90soft/parse-server-kit';
342
+ ```
343
+
344
+ ### Swagger
345
+
346
+ ```ts
347
+ import { setupSwagger, SwaggerRegistry } from '@90soft/parse-server-kit';
348
+
349
+ // Auto-generates API docs from @ParseClass and @CloudFunction metadata
350
+ setupSwagger(app, {
351
+ title: 'My API',
352
+ version: '1.0.0',
353
+ description: 'Auto-generated docs',
354
+ });
355
+ // Access at: http://localhost:1337/api-docs
356
+ ```
357
+
358
+ ### Hooks (for integrations)
359
+
360
+ ```ts
361
+ import { onClassRegistered, onFieldRegistered, onFunctionRegistered } from '@90soft/parse-server-kit';
362
+
363
+ // Runs when @ParseClass is applied
364
+ onClassRegistered((className, constructor, fields, options) => {
365
+ // Register with Swagger, setup triggers, etc.
366
+ });
367
+
368
+ // Runs when @CloudFunction is applied
369
+ onFunctionRegistered((name, config, target) => {
370
+ // Register with Swagger docs
371
+ });
372
+ ```
373
+
374
+ ### Constants
375
+
376
+ ```ts
377
+ import { UserRoles, roleKey, MAX_QUERY_LIMIT, CronSchedule } from '@90soft/parse-server-kit';
378
+
379
+ UserRoles.ADMIN // 'SuperAdmin'
380
+ UserRoles.EMPLOYEE // 'Employee'
381
+ roleKey(UserRoles.ADMIN) // 'role:SuperAdmin'
382
+ MAX_QUERY_LIMIT // 10000
383
+
384
+ CronSchedule.EVERY_HOUR // '0 * * * *'
385
+ CronSchedule.DAILY_MIDNIGHT // '0 0 * * *'
386
+ CronSchedule.WEEKLY_MONDAY // '0 0 * * 1'
387
+ ```
388
+
389
+ ### Validation
390
+
391
+ ```ts
392
+ import { validateObject, validateOrThrow, applyMongoValidators } from '@90soft/parse-server-kit';
393
+
394
+ // Validate against @ParseField constraints (min, max, enum, pattern, etc.)
395
+ const result = validateObject(parseObject);
396
+ // { valid: false, errors: [{ field: 'price', message: 'must be at least 0' }] }
397
+
398
+ // Throw Parse.Error if invalid (use in BeforeSave triggers)
399
+ validateOrThrow(parseObject);
400
+
401
+ // Apply MongoDB schema validators from decorator metadata
402
+ await applyMongoValidators(parseServerInstance);
403
+ ```
404
+
405
+ ---
406
+
407
+ ## File Structure Convention
408
+
409
+ ```
410
+ backend/src/cloudCode/
411
+ ├── models/
412
+ │ └── Product.ts ← @ParseClass + @ParseField + triggers
413
+ ├── modules/
414
+ │ └── Product/
415
+ │ └── functions.ts ← @Route + @CloudFunction
416
+ ├── cron.ts ← @Cron jobs
417
+ ├── main.ts ← importFiles for models + modules
418
+ └── decorator/
419
+ └── setupHooks.ts ← connects package hooks to Swagger + Triggers
420
+ ```
421
+
422
+ ## License
423
+
424
+ MIT
@@ -0,0 +1,7 @@
1
+ import { RouteConfig } from './types/cloudTypes';
2
+ type OnFunctionRegistered = (name: string, config: RouteConfig, target: any) => void;
3
+ export declare function onFunctionRegistered(hook: OnFunctionRegistered): void;
4
+ export declare function CloudFunction(config: RouteConfig): (target: any, propertyKey: string, descriptor?: PropertyDescriptor | undefined) => PropertyDescriptor;
5
+ export declare function ProtectedCloudFunction(config?: Partial<RouteConfig>): (target: any, propertyKey: string, descriptor?: PropertyDescriptor | undefined) => PropertyDescriptor;
6
+ export {};
7
+ //# sourceMappingURL=cloudDecorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudDecorator.d.ts","sourceRoot":"","sources":["../../src/decorators/cloudDecorator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAKtE,KAAK,oBAAoB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;AAErF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,oBAAoB,QAE9D;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,WAAW,IAE7C,QAAQ,GAAG,EACX,aAAa,MAAM,EACnB,aAAa,kBAAkB,GAAG,SAAS,wBAkC9C;AAoCD,wBAAgB,sBAAsB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,YAxE5D,GAAG,eACE,MAAM,eACN,kBAAkB,GAAG,SAAS,wBA4E9C"}
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.onFunctionRegistered = onFunctionRegistered;
4
+ exports.CloudFunction = CloudFunction;
5
+ exports.ProtectedCloudFunction = ProtectedCloudFunction;
6
+ const helper_1 = require("../utils/helper");
7
+ const cloudRegistry_1 = require("./cloudRegistry");
8
+ const functionHooks = [];
9
+ function onFunctionRegistered(hook) {
10
+ functionHooks.push(hook);
11
+ }
12
+ function CloudFunction(config) {
13
+ return function (target, propertyKey, descriptor) {
14
+ if (!descriptor) {
15
+ descriptor = Object.getOwnPropertyDescriptor(target, propertyKey);
16
+ }
17
+ if (!descriptor) {
18
+ throw new Error(`Descriptor not found for property ${propertyKey}`);
19
+ }
20
+ const originalMethod = descriptor.value;
21
+ // Wrap with role checking
22
+ descriptor.value = async function (...args) {
23
+ const request = args[0];
24
+ if (config.requireRoles) {
25
+ await checkUserRoles(request, config);
26
+ }
27
+ return originalMethod.apply(this, args);
28
+ };
29
+ const metadata = {
30
+ name: propertyKey,
31
+ config,
32
+ handler: descriptor.value,
33
+ };
34
+ cloudRegistry_1.CloudFunctionRegistry.register(metadata);
35
+ // Notify hooks (Swagger, etc.)
36
+ for (const hook of functionHooks) {
37
+ hook(propertyKey, config, target);
38
+ }
39
+ return descriptor;
40
+ };
41
+ }
42
+ async function checkUserRoles(request, config) {
43
+ const user = request.user;
44
+ if (!user) {
45
+ throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Authentication required');
46
+ }
47
+ if (!config.requireRoles || config.requireRoles.length === 0)
48
+ return;
49
+ const roleQuery = new Parse.Query('_Role');
50
+ roleQuery.containedIn('name', config.requireRoles);
51
+ roleQuery.equalTo('users', user);
52
+ const [error, userRoles] = await (0, helper_1.catchError)(roleQuery.find({ useMasterKey: true }));
53
+ if (error) {
54
+ throw new Parse.Error(Parse.Error.OTHER_CAUSE, 'Authorization check failed');
55
+ }
56
+ let hasPermission = false;
57
+ if (config.requireAllRoles) {
58
+ const userRoleNames = userRoles.map(role => role.get('name'));
59
+ hasPermission = config.requireRoles.every(role => userRoleNames.includes(role));
60
+ }
61
+ else {
62
+ hasPermission = userRoles.length > 0;
63
+ }
64
+ if (!hasPermission) {
65
+ const msg = config.customErrorMessage ||
66
+ `Access denied. Required ${config.requireAllRoles ? 'all' : 'one of'} these roles: ${config.requireRoles.join(', ')}`;
67
+ throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, msg);
68
+ }
69
+ }
70
+ function ProtectedCloudFunction(config = {}) {
71
+ return CloudFunction({
72
+ methods: ['POST'],
73
+ validation: { requireUser: true },
74
+ ...config,
75
+ });
76
+ }
77
+ //# sourceMappingURL=cloudDecorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudDecorator.js","sourceRoot":"","sources":["../../src/decorators/cloudDecorator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { CloudFunctionMetadata } from './types/cloudTypes';
2
+ export declare class CloudFunctionRegistry {
3
+ private static functions;
4
+ static register(metadata: CloudFunctionMetadata): void;
5
+ static getFunctions(): CloudFunctionMetadata[];
6
+ static getFunction(name: string): CloudFunctionMetadata | undefined;
7
+ static initialize(): void;
8
+ }
9
+ //# sourceMappingURL=cloudRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudRegistry.d.ts","sourceRoot":"","sources":["../../src/decorators/cloudRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,qBAAqB,EAAC,MAAM,oBAAoB,CAAC;AAGzD,qBAAa,qBAAqB;IAChC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAiD;IAEzE,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,qBAAqB;IAI/C,MAAM,CAAC,YAAY;IAInB,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM;IAI/B,MAAM,CAAC,UAAU;CASlB"}
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CloudFunctionRegistry = void 0;
4
+ const routeDecorator_1 = require("./routeDecorator");
5
+ class CloudFunctionRegistry {
6
+ static register(metadata) {
7
+ this.functions.set(metadata.name, metadata);
8
+ }
9
+ static getFunctions() {
10
+ return Array.from(this.functions.values());
11
+ }
12
+ static getFunction(name) {
13
+ return this.functions.get(name);
14
+ }
15
+ static initialize() {
16
+ this.functions.forEach(metadata => {
17
+ Parse.Cloud.define(metadata.name, metadata.handler, metadata.config.validation);
18
+ console.log(`Registered cloud function: ${metadata.name}`);
19
+ });
20
+ // Build entity-based route map after all functions are registered
21
+ routeDecorator_1.RouteRegistry.initialize();
22
+ }
23
+ }
24
+ exports.CloudFunctionRegistry = CloudFunctionRegistry;
25
+ CloudFunctionRegistry.functions = new Map();
26
+ //# sourceMappingURL=cloudRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudRegistry.js","sourceRoot":"","sources":["../../src/decorators/cloudRegistry.ts"],"names":[],"mappings":";;;AACA,qDAA+C;AAE/C,MAAa,qBAAqB;IAGhC,MAAM,CAAC,QAAQ,CAAC,QAA+B;QAC7C,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,CAAC,YAAY;QACjB,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,CAAC,WAAW,CAAC,IAAY;QAC7B,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,MAAM,CAAC,UAAU;QACf,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE;YAChC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YAChF,OAAO,CAAC,GAAG,CAAC,8BAA8B,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;QAC7D,CAAC,CAAC,CAAC;QAEH,kEAAkE;QAClE,8BAAa,CAAC,UAAU,EAAE,CAAC;IAC7B,CAAC;;AAvBH,sDAwBC;AAvBgB,+BAAS,GAAuC,IAAI,GAAG,EAAE,CAAC"}
@@ -0,0 +1,6 @@
1
+ import 'reflect-metadata';
2
+ import { CronConfig } from './types/cronTypes';
3
+ export declare function Cron(config: CronConfig): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
4
+ export { CronSchedule } from './types/cronTypes';
5
+ export { CronRegistry } from './cronRegistry';
6
+ //# sourceMappingURL=cronDecorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cronDecorator.d.ts","sourceRoot":"","sources":["../../src/decorators/cronDecorator.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAC,UAAU,EAAC,MAAM,mBAAmB,CAAC;AAG7C,wBAAgB,IAAI,CAAC,MAAM,EAAE,UAAU,IACpB,QAAQ,GAAG,EAAE,aAAa,MAAM,EAAE,YAAY,kBAAkB,wBAiBlF;AAED,OAAO,EAAC,YAAY,EAAC,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CronRegistry = exports.CronSchedule = void 0;
4
+ exports.Cron = Cron;
5
+ require("reflect-metadata");
6
+ const cronRegistry_1 = require("./cronRegistry");
7
+ function Cron(config) {
8
+ return function (target, propertyKey, descriptor) {
9
+ const handler = descriptor.value;
10
+ if (typeof handler !== 'function') {
11
+ throw new Error(`@Cron decorator can only be applied to methods.`);
12
+ }
13
+ cronRegistry_1.CronRegistry.register({
14
+ name: propertyKey,
15
+ schedule: config.schedule,
16
+ handler: handler,
17
+ description: config.description,
18
+ enabled: config.enabled ?? true,
19
+ timezone: config.timezone ?? 'UTC',
20
+ });
21
+ return descriptor;
22
+ };
23
+ }
24
+ var cronTypes_1 = require("./types/cronTypes");
25
+ Object.defineProperty(exports, "CronSchedule", { enumerable: true, get: function () { return cronTypes_1.CronSchedule; } });
26
+ var cronRegistry_2 = require("./cronRegistry");
27
+ Object.defineProperty(exports, "CronRegistry", { enumerable: true, get: function () { return cronRegistry_2.CronRegistry; } });
28
+ //# sourceMappingURL=cronDecorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cronDecorator.js","sourceRoot":"","sources":["../../src/decorators/cronDecorator.ts"],"names":[],"mappings":";;;AAIA,oBAkBC;AAtBD,4BAA0B;AAE1B,iDAA4C;AAE5C,SAAgB,IAAI,CAAC,MAAkB;IACrC,OAAO,UAAU,MAAW,EAAE,WAAmB,EAAE,UAA8B;QAC/E,MAAM,OAAO,GAAG,UAAU,CAAC,KAAK,CAAC;QACjC,IAAI,OAAO,OAAO,KAAK,UAAU,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;QACrE,CAAC;QAED,2BAAY,CAAC,QAAQ,CAAC;YACpB,IAAI,EAAE,WAAW;YACjB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,OAAO,EAAE,OAAO;YAChB,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,IAAI;YAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,KAAK;SACnC,CAAC,CAAC;QAEH,OAAO,UAAU,CAAC;IACpB,CAAC,CAAC;AACJ,CAAC;AAED,+CAA+C;AAAvC,yGAAA,YAAY,OAAA;AACpB,+CAA4C;AAApC,4GAAA,YAAY,OAAA"}
@@ -0,0 +1,13 @@
1
+ import { CronJobMetadata } from './types/cronTypes';
2
+ export declare class CronRegistry {
3
+ private static jobs;
4
+ static register(metadata: CronJobMetadata): void;
5
+ static getJobs(): CronJobMetadata[];
6
+ static getJob(name: string): CronJobMetadata | undefined;
7
+ static initialize(): void;
8
+ static stopJob(name: string): boolean;
9
+ static startJob(name: string): boolean;
10
+ static stopAll(): void;
11
+ static runNow(name: string): Promise<void>;
12
+ }
13
+ //# sourceMappingURL=cronRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cronRegistry.d.ts","sourceRoot":"","sources":["../../src/decorators/cronRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAC;AAUlD,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,IAAI,CAAyC;IAE5D,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI;IAOhD,MAAM,CAAC,OAAO,IAAI,eAAe,EAAE;IAInC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAIxD,MAAM,CAAC,UAAU,IAAI,IAAI;IA2CzB,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAMrC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAMtC,MAAM,CAAC,OAAO,IAAI,IAAI;WAKT,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAMjD"}