@90soft/parse-server-kit 2.5.3 → 2.8.0
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.
- package/README.md +613 -424
- package/dist/database/schema.d.ts +14 -0
- package/dist/database/schema.d.ts.map +1 -1
- package/dist/database/schema.js +2 -1
- package/dist/database/schema.js.map +1 -1
- package/dist/database/versionRegistry.d.ts +35 -0
- package/dist/database/versionRegistry.d.ts.map +1 -0
- package/dist/database/versionRegistry.js +153 -0
- package/dist/database/versionRegistry.js.map +1 -0
- package/dist/database/versionedMongoAdapter.d.ts +51 -0
- package/dist/database/versionedMongoAdapter.d.ts.map +1 -0
- package/dist/database/versionedMongoAdapter.js +131 -0
- package/dist/database/versionedMongoAdapter.js.map +1 -0
- package/dist/decorators/cloudRegistry.d.ts.map +1 -1
- package/dist/decorators/cloudRegistry.js +5 -0
- package/dist/decorators/cloudRegistry.js.map +1 -1
- package/dist/decorators/triggerDecorator.d.ts +8 -0
- package/dist/decorators/triggerDecorator.d.ts.map +1 -1
- package/dist/decorators/triggerDecorator.js +10 -1
- package/dist/decorators/triggerDecorator.js.map +1 -1
- package/dist/decorators/triggerRegistry.d.ts.map +1 -1
- package/dist/decorators/triggerRegistry.js +52 -8
- package/dist/decorators/triggerRegistry.js.map +1 -1
- package/dist/decorators/types/triggerTypes.d.ts +1 -1
- package/dist/decorators/types/triggerTypes.d.ts.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -2
- package/dist/index.js.map +1 -1
- package/dist/middleware/middleware.d.ts.map +1 -1
- package/dist/middleware/middleware.js +34 -1
- package/dist/middleware/middleware.js.map +1 -1
- package/dist/middleware/rateLimit.d.ts.map +1 -1
- package/dist/middleware/rateLimit.js +7 -1
- package/dist/middleware/rateLimit.js.map +1 -1
- package/dist/swagger/index.d.ts +5 -1
- package/dist/swagger/index.d.ts.map +1 -1
- package/dist/swagger/index.js +32 -8
- package/dist/swagger/index.js.map +1 -1
- package/dist/transactions/ambientSession.d.ts +39 -0
- package/dist/transactions/ambientSession.d.ts.map +1 -0
- package/dist/transactions/ambientSession.js +88 -0
- package/dist/transactions/ambientSession.js.map +1 -0
- package/dist/transactions/context.d.ts +86 -0
- package/dist/transactions/context.d.ts.map +1 -0
- package/dist/transactions/context.js +124 -0
- package/dist/transactions/context.js.map +1 -0
- package/dist/utils/dynamicImport.d.ts +22 -1
- package/dist/utils/dynamicImport.d.ts.map +1 -1
- package/dist/utils/dynamicImport.js +13 -4
- package/dist/utils/dynamicImport.js.map +1 -1
- package/dist/utils/helper.d.ts +10 -1
- package/dist/utils/helper.d.ts.map +1 -1
- package/dist/utils/helper.js +10 -1
- package/dist/utils/helper.js.map +1 -1
- package/dist/utils/imageAcl.d.ts +28 -0
- package/dist/utils/imageAcl.d.ts.map +1 -0
- package/dist/utils/imageAcl.js +74 -0
- package/dist/utils/imageAcl.js.map +1 -0
- package/package.json +75 -54
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ambientSession.js","sourceRoot":"","sources":["../../src/transactions/ambientSession.ts"],"names":[],"mappings":";;;AA8DA,gDA6BC;AA3FD,uCAAyC;AAEzC;;;;;;;;;;;;;;;GAeG;AACU,QAAA,gBAAgB,GAAqC;IAChE,SAAS,EAAE,CAAC;IACZ,UAAU,EAAE,CAAC;IACb,gBAAgB,EAAE,CAAC;IACnB,gBAAgB,EAAE,CAAC;IACnB,UAAU,EAAE,CAAC;IACb,QAAQ,EAAE,CAAC;IACX,SAAS,EAAE,CAAC;IACZ,UAAU,EAAE,CAAC;IACb,SAAS,EAAE,CAAC;IACZ,UAAU,EAAE,CAAC;IACb,IAAI,EAAE,CAAC;IACP,OAAO,EAAE,CAAC;IACV,SAAS,EAAE,CAAC;IACZ,cAAc,EAAE,CAAC;CAClB,CAAC;AAEF;;;;;;GAMG;AACU,QAAA,sBAAsB,GAAwB,IAAI,GAAG,CAAC;IACjE,SAAS;IACT,cAAc;IACd,QAAQ;IACR,YAAY;IACZ,eAAe;CAChB,CAAC,CAAC;AAIH;;;;;;;;;GASG;AACH,SAAgB,kBAAkB,CAA6B,GAAM;IACnE,OAAO,IAAI,KAAK,CAAC,GAAG,EAAE;QACpB,GAAG,CAAC,MAAM,EAAE,QAAQ;YAClB,MAAM,YAAY,GAAG,wBAAgB,CAAC,QAAkB,CAAC,CAAC;YAE1D,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;gBAC/B,MAAM,KAAK,GAAG,MAAM,CAAC,QAAkB,CAAC,CAAC;gBACzC,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;YAClE,CAAC;YAED,OAAO,CAAC,GAAG,IAAe,EAAE,EAAE;gBAC5B,MAAM,OAAO,GAAG,IAAA,wBAAc,GAAE,CAAC;gBACjC,IAAI,OAAO,EAAE,CAAC;oBACZ,MAAM,OAAO,GAAG;wBACd,GAAG,CAAE,IAAI,CAAC,YAAY,CAA6B,IAAI,EAAE,CAAC;qBAC3D,CAAC;oBACF,OAAO,CAAC,OAAO,KAAf,OAAO,CAAC,OAAO,GAAK,OAAO,EAAC;oBAC5B,IAAI,OAAO,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;wBAChC,kEAAkE;wBAClE,OAAO,OAAO,CAAC,cAAc,CAAC;oBAChC,CAAC;oBACD,IAAI,CAAC,YAAY,CAAC,GAAG,OAAO,CAAC;gBAC/B,CAAC;gBACD,OAAQ,MAAM,CAAC,QAAkB,CAAkC,CACjE,GAAG,IAAI,CACR,CAAC;YACJ,CAAC,CAAC;QACJ,CAAC;KACF,CAAM,CAAC;AACV,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transactions that follow the call, not the request.
|
|
3
|
+
*
|
|
4
|
+
* Parse Server has a transaction of its own, but it keeps the open session in a
|
|
5
|
+
* single field on the shared `DatabaseController`, and every write in the
|
|
6
|
+
* process passes that field to the adapter. One cloud function's transaction
|
|
7
|
+
* therefore swallows every *unrelated* request running at the same moment, and
|
|
8
|
+
* two of them collide outright.
|
|
9
|
+
*
|
|
10
|
+
* This one lives in an `AsyncLocalStorage` instead. The store follows the chain
|
|
11
|
+
* of awaits belonging to one call and nothing else, so two callers each get
|
|
12
|
+
* their own session and neither can see the other's. The database adapter reads
|
|
13
|
+
* it at the moment it talks to the driver — see `ambientSession.ts` — which is
|
|
14
|
+
* why a save inside the body joins without being told to.
|
|
15
|
+
*
|
|
16
|
+
* **This needs Parse Server's `directAccess`.** With it, a `save()` in cloud
|
|
17
|
+
* code is handled in the same async context; without it the SDK sends itself an
|
|
18
|
+
* HTTP request, which arrives in a fresh context with an empty store and writes
|
|
19
|
+
* outside the transaction. `directAccess` defaults to true and the config pins
|
|
20
|
+
* it, because that failure has no symptom.
|
|
21
|
+
*
|
|
22
|
+
* And it needs MongoDB to be a replica set. A standalone has no transactions
|
|
23
|
+
* and refuses to open one.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* One code for every "somebody else got there first".
|
|
27
|
+
*
|
|
28
|
+
* A lost optimistic lock and a transaction that lost its race are the same
|
|
29
|
+
* event to whoever is looking at the screen, so they read the same. Parse has
|
|
30
|
+
* no code of its own for a write conflict and every code it does have means
|
|
31
|
+
* something else.
|
|
32
|
+
*/
|
|
33
|
+
type ParseErrorCode = ConstructorParameters<typeof Parse.Error>[0];
|
|
34
|
+
export declare const CONFLICT: ParseErrorCode;
|
|
35
|
+
export declare const CONFLICT_MESSAGE = "Somebody else changed this while you were working on it. Open it again and redo your change.";
|
|
36
|
+
/**
|
|
37
|
+
* The four things this needs of a storage adapter.
|
|
38
|
+
*
|
|
39
|
+
* Deliberately structural and deliberately ignorant: the session is `unknown`
|
|
40
|
+
* because what a transaction *is* belongs to the adapter, and nothing here
|
|
41
|
+
* should be able to reach into one.
|
|
42
|
+
*/
|
|
43
|
+
export interface TransactionalAdapter {
|
|
44
|
+
connect(): Promise<unknown>;
|
|
45
|
+
createTransactionalSession(): Promise<unknown>;
|
|
46
|
+
commitTransactionalSession(session: unknown): Promise<unknown>;
|
|
47
|
+
abortTransactionalSession(session: unknown): Promise<unknown>;
|
|
48
|
+
}
|
|
49
|
+
/** Told once, when the adapter is built. */
|
|
50
|
+
export declare function useTransactionAdapter(value: TransactionalAdapter): void;
|
|
51
|
+
/** The session this call is running under, if it is running under one. */
|
|
52
|
+
export declare function currentSession(): unknown;
|
|
53
|
+
export declare function inTransaction(): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Run this, and either everything it wrote lands or none of it does.
|
|
56
|
+
*
|
|
57
|
+
* **Nested calls join.** A body already running inside a transaction simply
|
|
58
|
+
* runs — MongoDB has no nested transactions, and the useful meaning of an inner
|
|
59
|
+
* one is "take part in the outer". Committing is therefore the outermost
|
|
60
|
+
* caller's job alone.
|
|
61
|
+
*
|
|
62
|
+
* A body may be **re-run** if the transaction lost a race with another one, so
|
|
63
|
+
* it must be safe to repeat. Ours are: each reads the state it acts on at the
|
|
64
|
+
* start, and anything it wrote on the failed attempt was rolled back with it.
|
|
65
|
+
*/
|
|
66
|
+
export declare function withTransaction<T>(body: () => Promise<T>): Promise<T>;
|
|
67
|
+
/**
|
|
68
|
+
* Everything this cloud function writes lands together, or none of it does.
|
|
69
|
+
*
|
|
70
|
+
* **Order matters.** `@CloudFunction` reads the method off the descriptor when
|
|
71
|
+
* it is applied, and decorators apply bottom-up — so this must sit *below* it,
|
|
72
|
+
* or the registry keeps the unwrapped method and the transaction silently never
|
|
73
|
+
* opens:
|
|
74
|
+
*
|
|
75
|
+
* ```ts
|
|
76
|
+
* @CloudFunction({...}) // sees the wrapped method
|
|
77
|
+
* @Transactional() // wraps it
|
|
78
|
+
* async submitJob(req) {}
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* `withTransaction` is exported beside this for anything that is not a cloud
|
|
82
|
+
* function, or for wrapping part of one by hand.
|
|
83
|
+
*/
|
|
84
|
+
export declare function Transactional(): (_target: object, _propertyKey: string | symbol, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
85
|
+
export {};
|
|
86
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/transactions/context.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;;;;;;GAOG;AACH,KAAK,cAAc,GAAG,qBAAqB,CAAC,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAEnE,eAAO,MAAM,QAAQ,EAAW,cAAc,CAAC;AAE/C,eAAO,MAAM,gBAAgB,iGACmE,CAAC;AASjG;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5B,0BAA0B,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,0BAA0B,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/D,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC/D;AAKD,4CAA4C;AAC5C,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,oBAAoB,GAAG,IAAI,CAEvE;AAED,0EAA0E;AAC1E,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAmCD;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAkC3E;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,KAEzB,SAAS,MAAM,EACf,cAAc,MAAM,GAAG,MAAM,EAC7B,YAAY,kBAAkB,KAC7B,kBAAkB,CAWtB"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CONFLICT_MESSAGE = exports.CONFLICT = void 0;
|
|
4
|
+
exports.useTransactionAdapter = useTransactionAdapter;
|
|
5
|
+
exports.currentSession = currentSession;
|
|
6
|
+
exports.inTransaction = inTransaction;
|
|
7
|
+
exports.withTransaction = withTransaction;
|
|
8
|
+
exports.Transactional = Transactional;
|
|
9
|
+
const node_async_hooks_1 = require("node:async_hooks");
|
|
10
|
+
exports.CONFLICT = 5001;
|
|
11
|
+
exports.CONFLICT_MESSAGE = 'Somebody else changed this while you were working on it. Open it again and redo your change.';
|
|
12
|
+
const storage = new node_async_hooks_1.AsyncLocalStorage();
|
|
13
|
+
let adapter = null;
|
|
14
|
+
let warnedWithoutAdapter = false;
|
|
15
|
+
/** Told once, when the adapter is built. */
|
|
16
|
+
function useTransactionAdapter(value) {
|
|
17
|
+
adapter = value;
|
|
18
|
+
}
|
|
19
|
+
/** The session this call is running under, if it is running under one. */
|
|
20
|
+
function currentSession() {
|
|
21
|
+
return storage.getStore()?.session;
|
|
22
|
+
}
|
|
23
|
+
function inTransaction() {
|
|
24
|
+
return storage.getStore() !== undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A write that lost a race with another transaction and may simply be retried.
|
|
28
|
+
*
|
|
29
|
+
* MongoDB labels these itself, and a write inside a transaction reports code
|
|
30
|
+
* 251. Neither survives the trip up, though: the Mongo adapter classifies the
|
|
31
|
+
* error first and rethrows it as a bare `INTERNAL_SERVER_ERROR` with the message
|
|
32
|
+
* `Database error`, dropping the label and the code.
|
|
33
|
+
*
|
|
34
|
+
* So that message is the third thing checked. It reads like a guess and is not:
|
|
35
|
+
* Parse Server produces that exact string in exactly one place, and only for an
|
|
36
|
+
* error its own `isTransientError` has already classified — which is the same
|
|
37
|
+
* question being asked here.
|
|
38
|
+
*/
|
|
39
|
+
const PARSE_TRANSIENT_MESSAGE = 'Database error';
|
|
40
|
+
function isTransient(error) {
|
|
41
|
+
const failure = error;
|
|
42
|
+
if (failure?.hasErrorLabel?.('TransientTransactionError') === true)
|
|
43
|
+
return true;
|
|
44
|
+
if (failure?.code === 251)
|
|
45
|
+
return true;
|
|
46
|
+
return (failure?.code === Parse.Error.INTERNAL_SERVER_ERROR &&
|
|
47
|
+
failure?.message === PARSE_TRANSIENT_MESSAGE);
|
|
48
|
+
}
|
|
49
|
+
/** How many times a body may be re-run after losing a race. */
|
|
50
|
+
const MAX_ATTEMPTS = 3;
|
|
51
|
+
/**
|
|
52
|
+
* Run this, and either everything it wrote lands or none of it does.
|
|
53
|
+
*
|
|
54
|
+
* **Nested calls join.** A body already running inside a transaction simply
|
|
55
|
+
* runs — MongoDB has no nested transactions, and the useful meaning of an inner
|
|
56
|
+
* one is "take part in the outer". Committing is therefore the outermost
|
|
57
|
+
* caller's job alone.
|
|
58
|
+
*
|
|
59
|
+
* A body may be **re-run** if the transaction lost a race with another one, so
|
|
60
|
+
* it must be safe to repeat. Ours are: each reads the state it acts on at the
|
|
61
|
+
* start, and anything it wrote on the failed attempt was rolled back with it.
|
|
62
|
+
*/
|
|
63
|
+
async function withTransaction(body) {
|
|
64
|
+
// Already inside one — take part in it rather than opening a second.
|
|
65
|
+
if (inTransaction())
|
|
66
|
+
return body();
|
|
67
|
+
if (!adapter) {
|
|
68
|
+
if (!warnedWithoutAdapter) {
|
|
69
|
+
warnedWithoutAdapter = true;
|
|
70
|
+
console.warn('[Transactions] No storage adapter registered — running without one. ' +
|
|
71
|
+
'Writes will not be rolled back together.');
|
|
72
|
+
}
|
|
73
|
+
return body();
|
|
74
|
+
}
|
|
75
|
+
// `createTransactionalSession` reaches straight for the driver's client, so
|
|
76
|
+
// the very first transaction of the process has to wait for the connection.
|
|
77
|
+
await adapter.connect();
|
|
78
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt += 1) {
|
|
79
|
+
const session = await adapter.createTransactionalSession();
|
|
80
|
+
try {
|
|
81
|
+
const result = await storage.run({ session }, body);
|
|
82
|
+
await adapter.commitTransactionalSession(session);
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
await adapter.abortTransactionalSession(session).catch(() => undefined);
|
|
87
|
+
if (!isTransient(error))
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Out of attempts, still losing. The caller is not interested in which lock
|
|
92
|
+
// it was — only that somebody else is working on the same thing.
|
|
93
|
+
throw new Parse.Error(exports.CONFLICT, exports.CONFLICT_MESSAGE);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Everything this cloud function writes lands together, or none of it does.
|
|
97
|
+
*
|
|
98
|
+
* **Order matters.** `@CloudFunction` reads the method off the descriptor when
|
|
99
|
+
* it is applied, and decorators apply bottom-up — so this must sit *below* it,
|
|
100
|
+
* or the registry keeps the unwrapped method and the transaction silently never
|
|
101
|
+
* opens:
|
|
102
|
+
*
|
|
103
|
+
* ```ts
|
|
104
|
+
* @CloudFunction({...}) // sees the wrapped method
|
|
105
|
+
* @Transactional() // wraps it
|
|
106
|
+
* async submitJob(req) {}
|
|
107
|
+
* ```
|
|
108
|
+
*
|
|
109
|
+
* `withTransaction` is exported beside this for anything that is not a cloud
|
|
110
|
+
* function, or for wrapping part of one by hand.
|
|
111
|
+
*/
|
|
112
|
+
function Transactional() {
|
|
113
|
+
return (_target, _propertyKey, descriptor) => {
|
|
114
|
+
const body = descriptor.value;
|
|
115
|
+
if (typeof body !== 'function') {
|
|
116
|
+
throw new Error('@Transactional can only be applied to methods.');
|
|
117
|
+
}
|
|
118
|
+
descriptor.value = function (...args) {
|
|
119
|
+
return withTransaction(() => body.apply(this, args));
|
|
120
|
+
};
|
|
121
|
+
return descriptor;
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/transactions/context.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,2 +1,23 @@
|
|
|
1
|
-
export
|
|
1
|
+
export interface ImportFilesOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Which file extensions to load. Defaults to `['.js']` — the compiled output
|
|
4
|
+
* a production server runs.
|
|
5
|
+
*
|
|
6
|
+
* Pass `['.js', '.ts']` when running straight from source under ts-node or
|
|
7
|
+
* tsx. Without it nothing is imported at all: no models register, no cloud
|
|
8
|
+
* functions exist, and the server starts perfectly happily with an empty
|
|
9
|
+
* schema. It is not the kind of failure you want to debug from the symptoms.
|
|
10
|
+
*
|
|
11
|
+
* Point this at a source directory only. A directory holding *both* compiled
|
|
12
|
+
* and source copies would register every class twice.
|
|
13
|
+
*/
|
|
14
|
+
extensions?: string[];
|
|
15
|
+
/** Log each file as it is imported. Defaults to true, as it always has. */
|
|
16
|
+
verbose?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Recursively import every matching file in a directory, for the side effect of
|
|
20
|
+
* running its decorators.
|
|
21
|
+
*/
|
|
22
|
+
export declare const importFiles: (directoryPath: string, options?: ImportFilesOptions) => void;
|
|
2
23
|
//# sourceMappingURL=dynamicImport.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamicImport.d.ts","sourceRoot":"","sources":["../../src/utils/dynamicImport.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"dynamicImport.d.ts","sourceRoot":"","sources":["../../src/utils/dynamicImport.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,kBAAkB;IACjC;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;GAGG;AACH,eAAO,MAAM,WAAW,GACtB,eAAe,MAAM,EACrB,UAAS,kBAAuB,SAkBjC,CAAC"}
|
|
@@ -4,15 +4,24 @@ exports.importFiles = void 0;
|
|
|
4
4
|
const fs_1 = require("fs");
|
|
5
5
|
const path_1 = require("path");
|
|
6
6
|
const isDirectory = (path) => (0, fs_1.statSync)(path).isDirectory();
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Recursively import every matching file in a directory, for the side effect of
|
|
9
|
+
* running its decorators.
|
|
10
|
+
*/
|
|
11
|
+
const importFiles = (directoryPath, options = {}) => {
|
|
12
|
+
const { extensions = ['.js'], verbose = true } = options;
|
|
13
|
+
// `.d.ts` is a declaration, never a module to execute — importing one under
|
|
14
|
+
// ts-node is at best a no-op and at worst a crash.
|
|
15
|
+
const wanted = new Set(extensions.map(e => (e.startsWith('.') ? e : `.${e}`)));
|
|
8
16
|
const files = (0, fs_1.readdirSync)(directoryPath);
|
|
9
17
|
files.forEach(file => {
|
|
10
18
|
const filePath = (0, path_1.join)(directoryPath, file);
|
|
11
19
|
if (isDirectory(filePath)) {
|
|
12
|
-
(0, exports.importFiles)(filePath);
|
|
20
|
+
(0, exports.importFiles)(filePath, options);
|
|
13
21
|
}
|
|
14
|
-
else if ((0, path_1.extname)(filePath)
|
|
15
|
-
|
|
22
|
+
else if (wanted.has((0, path_1.extname)(filePath)) && !file.endsWith('.d.ts')) {
|
|
23
|
+
if (verbose)
|
|
24
|
+
console.log(filePath);
|
|
16
25
|
require(filePath);
|
|
17
26
|
}
|
|
18
27
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamicImport.js","sourceRoot":"","sources":["../../src/utils/dynamicImport.ts"],"names":[],"mappings":";;;AAAA,2BAAyC;AACzC,+BAAmC;AAEnC,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,IAAA,aAAQ,EAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"dynamicImport.js","sourceRoot":"","sources":["../../src/utils/dynamicImport.ts"],"names":[],"mappings":";;;AAAA,2BAAyC;AACzC,+BAAmC;AAEnC,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,IAAA,aAAQ,EAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;AAoBnE;;;GAGG;AACI,MAAM,WAAW,GAAG,CACzB,aAAqB,EACrB,UAA8B,EAAE,EAChC,EAAE;IACF,MAAM,EAAC,UAAU,GAAG,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,IAAI,EAAC,GAAG,OAAO,CAAC;IAEvD,4EAA4E;IAC5E,mDAAmD;IACnD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAE/E,MAAM,KAAK,GAAG,IAAA,gBAAW,EAAC,aAAa,CAAC,CAAC;IACzC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;QACnB,MAAM,QAAQ,GAAG,IAAA,WAAI,EAAC,aAAa,EAAE,IAAI,CAAC,CAAC;QAC3C,IAAI,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1B,IAAA,mBAAW,EAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACjC,CAAC;aAAM,IAAI,MAAM,CAAC,GAAG,CAAC,IAAA,cAAO,EAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YACpE,IAAI,OAAO;gBAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACnC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACpB,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AApBW,QAAA,WAAW,eAoBtB"}
|
package/dist/utils/helper.d.ts
CHANGED
|
@@ -9,7 +9,16 @@ export declare function catchError<T>(promise: Promise<T>): Promise<[undefined,
|
|
|
9
9
|
*/
|
|
10
10
|
export declare function getUserRoles(user: Parse.User): Promise<string[]>;
|
|
11
11
|
/**
|
|
12
|
-
* Get role names for multiple users
|
|
12
|
+
* Get role names for multiple users.
|
|
13
|
+
*
|
|
14
|
+
* Costs one query per *role*, not one per user — which is the half that
|
|
15
|
+
* matters, since the role list is a fixed property of the deployment (and
|
|
16
|
+
* capped at 100 here) while the user list is whatever the caller passed. A page
|
|
17
|
+
* of 500 users costs the same as a page of 5.
|
|
18
|
+
*
|
|
19
|
+
* It is not a single query: Parse relations cannot be filtered across roles in
|
|
20
|
+
* one go. If the role count ever grows past a few dozen, this is the place to
|
|
21
|
+
* look.
|
|
13
22
|
*/
|
|
14
23
|
export declare function getUsersRoles(users: Parse.User[]): Promise<Map<string, string[]>>;
|
|
15
24
|
//# sourceMappingURL=helper.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../../src/utils/helper.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,SAAO,UAMlD;AAED,wBAAgB,sBAAsB,CACpC,MAAM,SAAI,EACV,cAAc,UAAO,EACrB,cAAc,UAAO,UAiBtB;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,UAInD;AAED,wBAAgB,oBAAoB,CAAC,MAAM,SAAI,GAAG,MAAM,CAOvD;AAED,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,oBAE/B;AAED,wBAAsB,UAAU,CAAC,CAAC,EAChC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAClB,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAQnC;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAKtE;AAED
|
|
1
|
+
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../../src/utils/helper.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,SAAO,UAMlD;AAED,wBAAgB,sBAAsB,CACpC,MAAM,SAAI,EACV,cAAc,UAAO,EACrB,cAAc,UAAO,UAiBtB;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,UAInD;AAED,wBAAgB,oBAAoB,CAAC,MAAM,SAAI,GAAG,MAAM,CAOvD;AAED,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,oBAE/B;AAED,wBAAsB,UAAU,CAAC,CAAC,EAChC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAClB,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAQnC;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAKtE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAmBvF"}
|
package/dist/utils/helper.js
CHANGED
|
@@ -67,7 +67,16 @@ async function getUserRoles(user) {
|
|
|
67
67
|
return roles.map(r => r.get('name'));
|
|
68
68
|
}
|
|
69
69
|
/**
|
|
70
|
-
* Get role names for multiple users
|
|
70
|
+
* Get role names for multiple users.
|
|
71
|
+
*
|
|
72
|
+
* Costs one query per *role*, not one per user — which is the half that
|
|
73
|
+
* matters, since the role list is a fixed property of the deployment (and
|
|
74
|
+
* capped at 100 here) while the user list is whatever the caller passed. A page
|
|
75
|
+
* of 500 users costs the same as a page of 5.
|
|
76
|
+
*
|
|
77
|
+
* It is not a single query: Parse relations cannot be filtered across roles in
|
|
78
|
+
* one go. If the role count ever grows past a few dozen, this is the place to
|
|
79
|
+
* look.
|
|
71
80
|
*/
|
|
72
81
|
async function getUsersRoles(users) {
|
|
73
82
|
if (users.length === 0)
|
package/dist/utils/helper.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helper.js","sourceRoot":"","sources":["../../src/utils/helper.ts"],"names":[],"mappings":";;AAAA,kCAMC;AAED,wDAoBC;AAED,sDAIC;AAED,oDAOC;AAED,sBAEC;AAED,gCAUC;AAKD,oCAKC;
|
|
1
|
+
{"version":3,"file":"helper.js","sourceRoot":"","sources":["../../src/utils/helper.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Make a parent's nested image(s) FOLLOW the parent's per-record ACL, so an
|
|
3
|
+
* image is visible to exactly whoever can see its parent (an active record →
|
|
4
|
+
* Member/public-readable image; a hidden one → hidden image).
|
|
5
|
+
*
|
|
6
|
+
* `handleImageLogic` stamps a new image with the parent CLASS's static ACL
|
|
7
|
+
* template, and nothing re-stamps it on later transitions — so without this,
|
|
8
|
+
* catalogues render blank images and hidden parents leak readable images.
|
|
9
|
+
*
|
|
10
|
+
* Call `syncImageAcl(parent, [fields])` AFTER setting the parent's per-record ACL
|
|
11
|
+
* and BEFORE saving the parent. It copies the parent's ACL onto each nested image
|
|
12
|
+
* (single Pointer or Array-of-Pointer, e.g. `images`), dirtying it so the
|
|
13
|
+
* parent's save cascades the change:
|
|
14
|
+
* - a NEW image (no id) is created with the ACL;
|
|
15
|
+
* - an EXISTING image gets an ACL-ONLY update (no file rewrite / re-process).
|
|
16
|
+
*
|
|
17
|
+
* The parent ACL must already carry its FINAL read grants when this is called.
|
|
18
|
+
* For collections whose visibility is derived from a status in a `beforeSave`
|
|
19
|
+
* trigger (e.g. `publicRead = status === 'active'`), pass that `publicRead` into
|
|
20
|
+
* `implementACL` at the call site — the trigger runs AFTER this helper.
|
|
21
|
+
*
|
|
22
|
+
* @param acl Optional ACL source override, for callers that hold the images on a
|
|
23
|
+
* PARTIAL save object (where `parent.getACL()` isn't the live ACL).
|
|
24
|
+
*/
|
|
25
|
+
export declare function syncImageAcl(parent: Parse.Object, fields: string[], acl?: Parse.ACL): void;
|
|
26
|
+
/** Deep-copy a Parse.ACL (role, public `*`, and per-user grants). */
|
|
27
|
+
export declare function cloneAcl(acl: Parse.ACL): Parse.ACL;
|
|
28
|
+
//# sourceMappingURL=imageAcl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"imageAcl.d.ts","sourceRoot":"","sources":["../../src/utils/imageAcl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,KAAK,CAAC,MAAM,EACpB,MAAM,EAAE,MAAM,EAAE,EAChB,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,GACd,IAAI,CAeN;AAED,qEAAqE;AACrE,wBAAgB,QAAQ,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,CAqBlD"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.syncImageAcl = syncImageAcl;
|
|
4
|
+
exports.cloneAcl = cloneAcl;
|
|
5
|
+
/**
|
|
6
|
+
* Make a parent's nested image(s) FOLLOW the parent's per-record ACL, so an
|
|
7
|
+
* image is visible to exactly whoever can see its parent (an active record →
|
|
8
|
+
* Member/public-readable image; a hidden one → hidden image).
|
|
9
|
+
*
|
|
10
|
+
* `handleImageLogic` stamps a new image with the parent CLASS's static ACL
|
|
11
|
+
* template, and nothing re-stamps it on later transitions — so without this,
|
|
12
|
+
* catalogues render blank images and hidden parents leak readable images.
|
|
13
|
+
*
|
|
14
|
+
* Call `syncImageAcl(parent, [fields])` AFTER setting the parent's per-record ACL
|
|
15
|
+
* and BEFORE saving the parent. It copies the parent's ACL onto each nested image
|
|
16
|
+
* (single Pointer or Array-of-Pointer, e.g. `images`), dirtying it so the
|
|
17
|
+
* parent's save cascades the change:
|
|
18
|
+
* - a NEW image (no id) is created with the ACL;
|
|
19
|
+
* - an EXISTING image gets an ACL-ONLY update (no file rewrite / re-process).
|
|
20
|
+
*
|
|
21
|
+
* The parent ACL must already carry its FINAL read grants when this is called.
|
|
22
|
+
* For collections whose visibility is derived from a status in a `beforeSave`
|
|
23
|
+
* trigger (e.g. `publicRead = status === 'active'`), pass that `publicRead` into
|
|
24
|
+
* `implementACL` at the call site — the trigger runs AFTER this helper.
|
|
25
|
+
*
|
|
26
|
+
* @param acl Optional ACL source override, for callers that hold the images on a
|
|
27
|
+
* PARTIAL save object (where `parent.getACL()` isn't the live ACL).
|
|
28
|
+
*/
|
|
29
|
+
function syncImageAcl(parent, fields, acl) {
|
|
30
|
+
const parentAcl = acl ?? parent.getACL();
|
|
31
|
+
if (!parentAcl)
|
|
32
|
+
return;
|
|
33
|
+
for (const field of fields) {
|
|
34
|
+
const value = parent.get(field);
|
|
35
|
+
const images = Array.isArray(value)
|
|
36
|
+
? value
|
|
37
|
+
: value
|
|
38
|
+
? [value]
|
|
39
|
+
: [];
|
|
40
|
+
for (const img of images) {
|
|
41
|
+
if (img)
|
|
42
|
+
img.setACL(cloneAcl(parentAcl));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Deep-copy a Parse.ACL (role, public `*`, and per-user grants). */
|
|
47
|
+
function cloneAcl(acl) {
|
|
48
|
+
const json = acl.toJSON();
|
|
49
|
+
const clone = new Parse.ACL();
|
|
50
|
+
for (const id of Object.keys(json)) {
|
|
51
|
+
const perms = json[id];
|
|
52
|
+
if (id === '*') {
|
|
53
|
+
if (perms.read)
|
|
54
|
+
clone.setPublicReadAccess(true);
|
|
55
|
+
if (perms.write)
|
|
56
|
+
clone.setPublicWriteAccess(true);
|
|
57
|
+
}
|
|
58
|
+
else if (id.startsWith('role:')) {
|
|
59
|
+
const role = id.slice(5);
|
|
60
|
+
if (perms.read)
|
|
61
|
+
clone.setRoleReadAccess(role, true);
|
|
62
|
+
if (perms.write)
|
|
63
|
+
clone.setRoleWriteAccess(role, true);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
if (perms.read)
|
|
67
|
+
clone.setReadAccess(id, true);
|
|
68
|
+
if (perms.write)
|
|
69
|
+
clone.setWriteAccess(id, true);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return clone;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=imageAcl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"imageAcl.js","sourceRoot":"","sources":["../../src/utils/imageAcl.ts"],"names":[],"mappings":";;AAwBA,oCAmBC;AAGD,4BAqBC;AAnED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,SAAgB,YAAY,CAC1B,MAAoB,EACpB,MAAgB,EAChB,GAAe;IAEf,MAAM,SAAS,GAAG,GAAG,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;IACzC,IAAI,CAAC,SAAS;QAAE,OAAO;IAEvB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAChC,MAAM,MAAM,GAAmB,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YACjD,CAAC,CAAE,KAAwB;YAC3B,CAAC,CAAC,KAAK;gBACL,CAAC,CAAC,CAAC,KAAqB,CAAC;gBACzB,CAAC,CAAC,EAAE,CAAC;QACT,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;YACzB,IAAI,GAAG;gBAAE,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;AACH,CAAC;AAED,qEAAqE;AACrE,SAAgB,QAAQ,CAAC,GAAc;IACrC,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,EAGtB,CAAC;IACF,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,GAAG,EAAE,CAAC;IAC9B,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;QACvB,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,IAAI,KAAK,CAAC,IAAI;gBAAE,KAAK,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;YAChD,IAAI,KAAK,CAAC,KAAK;gBAAE,KAAK,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC;QACpD,CAAC;aAAM,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACzB,IAAI,KAAK,CAAC,IAAI;gBAAE,KAAK,CAAC,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACpD,IAAI,KAAK,CAAC,KAAK;gBAAE,KAAK,CAAC,kBAAkB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACxD,CAAC;aAAM,CAAC;YACN,IAAI,KAAK,CAAC,IAAI;gBAAE,KAAK,CAAC,aAAa,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;YAC9C,IAAI,KAAK,CAAC,KAAK;gBAAE,KAAK,CAAC,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,54 +1,75 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@90soft/parse-server-kit",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Complete decorator-driven development kit for Parse Server — models, cloud functions, triggers, cron, routing, ACL, Swagger, and more.",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"types": "dist/index.d.ts",
|
|
7
|
-
"files": [
|
|
8
|
-
"dist"
|
|
9
|
-
],
|
|
10
|
-
"scripts": {
|
|
11
|
-
"build": "tsc",
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"@types/
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@90soft/parse-server-kit",
|
|
3
|
+
"version": "2.8.0",
|
|
4
|
+
"description": "Complete decorator-driven development kit for Parse Server — models, cloud functions, triggers, cron, routing, ACL, Swagger, and more.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"test": "jest",
|
|
13
|
+
"test:integration": "NODE_OPTIONS=--experimental-vm-modules jest --config jest.integration.config.js",
|
|
14
|
+
"test:all": "npm test && npm run test:integration",
|
|
15
|
+
"prepublishOnly": "npm run build"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"parse-server",
|
|
19
|
+
"decorators",
|
|
20
|
+
"typescript",
|
|
21
|
+
"backend",
|
|
22
|
+
"cloud-functions",
|
|
23
|
+
"mongodb",
|
|
24
|
+
"acl",
|
|
25
|
+
"swagger",
|
|
26
|
+
"cron"
|
|
27
|
+
],
|
|
28
|
+
"author": "90Soft",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://git.90-soft.com/90_soft/parse-server-kit.git"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18.0.0"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@types/parse": ">=3.0.0",
|
|
39
|
+
"express": ">=4.0.0",
|
|
40
|
+
"node-cron": ">=3.0.0",
|
|
41
|
+
"parse": ">=5.0.0",
|
|
42
|
+
"parse-server": ">=8.3.0",
|
|
43
|
+
"reflect-metadata": ">=0.1.13",
|
|
44
|
+
"swagger-ui-express": ">=4.0.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependenciesMeta": {
|
|
47
|
+
"@types/parse": {
|
|
48
|
+
"optional": true
|
|
49
|
+
},
|
|
50
|
+
"node-cron": {
|
|
51
|
+
"optional": true
|
|
52
|
+
},
|
|
53
|
+
"swagger-ui-express": {
|
|
54
|
+
"optional": true
|
|
55
|
+
},
|
|
56
|
+
"parse-server": {
|
|
57
|
+
"optional": true
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@types/express": "^5.0.0",
|
|
62
|
+
"@types/jest": "^29.5.0",
|
|
63
|
+
"@types/node": "^20.0.0",
|
|
64
|
+
"express": "^5.0.0",
|
|
65
|
+
"jest": "^29.7.0",
|
|
66
|
+
"mongodb-memory-server": "^11.2.0",
|
|
67
|
+
"node-cron": "^4.0.0",
|
|
68
|
+
"parse": "^5.3.0",
|
|
69
|
+
"parse-server": "^9.10.0",
|
|
70
|
+
"reflect-metadata": "^0.2.2",
|
|
71
|
+
"swagger-ui-express": "^5.0.1",
|
|
72
|
+
"ts-jest": "^29.2.0",
|
|
73
|
+
"typescript": "^5.5.0"
|
|
74
|
+
}
|
|
75
|
+
}
|