@90soft/parse-server-kit 2.8.2 → 2.10.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.
- package/CHANGELOG.md +99 -0
- package/CLAUDE.md +698 -674
- package/README.md +27 -0
- package/dist/config.d.ts +68 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +35 -0
- package/dist/config.js.map +1 -0
- package/dist/database/indexes.d.ts.map +1 -1
- package/dist/database/indexes.js +14 -1
- package/dist/database/indexes.js.map +1 -1
- package/dist/decorators/cloudDecorator.d.ts.map +1 -1
- package/dist/decorators/cloudDecorator.js +58 -15
- package/dist/decorators/cloudDecorator.js.map +1 -1
- package/dist/decorators/cloudRegistry.d.ts +13 -0
- package/dist/decorators/cloudRegistry.d.ts.map +1 -1
- package/dist/decorators/cloudRegistry.js +23 -1
- package/dist/decorators/cloudRegistry.js.map +1 -1
- package/dist/decorators/parseDecorators.d.ts +1 -1
- package/dist/decorators/parseDecorators.d.ts.map +1 -1
- package/dist/decorators/parseDecorators.js +131 -4
- package/dist/decorators/parseDecorators.js.map +1 -1
- package/dist/decorators/triggerDecorator.d.ts +14 -0
- package/dist/decorators/triggerDecorator.d.ts.map +1 -1
- package/dist/decorators/triggerDecorator.js +31 -2
- package/dist/decorators/triggerDecorator.js.map +1 -1
- package/dist/decorators/triggerRegistry.d.ts +8 -0
- package/dist/decorators/triggerRegistry.d.ts.map +1 -1
- package/dist/decorators/triggerRegistry.js +18 -0
- package/dist/decorators/triggerRegistry.js.map +1 -1
- package/dist/decorators/types/cloudTypes.d.ts +22 -0
- package/dist/decorators/types/cloudTypes.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware/middleware.d.ts.map +1 -1
- package/dist/middleware/middleware.js +66 -5
- package/dist/middleware/middleware.js.map +1 -1
- package/dist/models/BaseModel.d.ts.map +1 -1
- package/dist/models/BaseModel.js +5 -2
- package/dist/models/BaseModel.js.map +1 -1
- package/dist/swagger/index.d.ts.map +1 -1
- package/dist/swagger/index.js +17 -4
- package/dist/swagger/index.js.map +1 -1
- package/dist/swagger/swaggerRegistry.d.ts +11 -0
- package/dist/swagger/swaggerRegistry.d.ts.map +1 -1
- package/dist/swagger/swaggerRegistry.js +18 -1
- package/dist/swagger/swaggerRegistry.js.map +1 -1
- package/dist/utils/dynamicImport.d.ts.map +1 -1
- package/dist/utils/dynamicImport.js +33 -11
- package/dist/utils/dynamicImport.js.map +1 -1
- package/dist/utils/helper.d.ts +5 -1
- package/dist/utils/helper.d.ts.map +1 -1
- package/dist/utils/helper.js +25 -2
- package/dist/utils/helper.js.map +1 -1
- package/dist/utils/roleCache.d.ts +60 -0
- package/dist/utils/roleCache.d.ts.map +1 -0
- package/dist/utils/roleCache.js +112 -0
- package/dist/utils/roleCache.js.map +1 -0
- package/package.json +3 -2
|
@@ -9,6 +9,7 @@ exports.extractMasterKey = extractMasterKey;
|
|
|
9
9
|
exports.restrictRoutes = restrictRoutes;
|
|
10
10
|
exports.conditionalJsonMiddleware = conditionalJsonMiddleware;
|
|
11
11
|
exports.removeResultMiddleware = removeResultMiddleware;
|
|
12
|
+
const node_crypto_1 = require("node:crypto");
|
|
12
13
|
const express_1 = __importDefault(require("express"));
|
|
13
14
|
const cloudRegistry_1 = require("../decorators/cloudRegistry");
|
|
14
15
|
const routeDecorator_1 = require("../decorators/routeDecorator");
|
|
@@ -22,7 +23,12 @@ function validateFunctionRoutes(req, res, next) {
|
|
|
22
23
|
if (req._entityRouteHandled) {
|
|
23
24
|
return next();
|
|
24
25
|
}
|
|
25
|
-
|
|
26
|
+
// Tolerate a leading "functions" segment, so this works whether it is
|
|
27
|
+
// mounted at the app root (paths arrive as /functions/myFn) or under
|
|
28
|
+
// {mountPath}/functions (arriving as /myFn). Reading segment [1] blindly
|
|
29
|
+
// meant every lookup 404'd in the first case.
|
|
30
|
+
const segments = req.path.split('/').filter(Boolean);
|
|
31
|
+
const functionName = segments[0] === 'functions' ? segments[1] : segments[0];
|
|
26
32
|
const metadata = cloudRegistry_1.CloudFunctionRegistry.getFunction(functionName);
|
|
27
33
|
if (!metadata) {
|
|
28
34
|
return res.status(404).json({ message: 'Function not found' });
|
|
@@ -116,7 +122,13 @@ function extractMasterKey(req, res, buf, encoding) {
|
|
|
116
122
|
// deliberately so this stays a performance change only. Malformed JSON
|
|
117
123
|
// without a marker now reaches body-parser instead, which answers 400
|
|
118
124
|
// cleanly — so this path is merely reached far less often than before.
|
|
119
|
-
|
|
125
|
+
/*
|
|
126
|
+
* Throw only. This used to also send a 400, which left Express reporting
|
|
127
|
+
* "Cannot set headers after they are sent" on top of the real problem.
|
|
128
|
+
* body-parser catches the throw and answers 400 itself, so the caller gets
|
|
129
|
+
* one clean response and the log shows one cause.
|
|
130
|
+
*/
|
|
131
|
+
throw new Error('Error parsing JSON body');
|
|
120
132
|
}
|
|
121
133
|
}
|
|
122
134
|
/**
|
|
@@ -134,13 +146,33 @@ function extractMasterKey(req, res, buf, encoding) {
|
|
|
134
146
|
* An unset `masterKey` env var never matches, so a misconfigured server fails
|
|
135
147
|
* closed rather than letting every request through as master.
|
|
136
148
|
*/
|
|
149
|
+
/**
|
|
150
|
+
* Compare two secrets without leaking their contents through timing.
|
|
151
|
+
*
|
|
152
|
+
* `===` on strings stops at the first differing character, so how long it takes
|
|
153
|
+
* to fail is a function of how much of the key was right. Given enough samples
|
|
154
|
+
* that is enough to recover a key one character at a time.
|
|
155
|
+
*
|
|
156
|
+
* `timingSafeEqual` needs equal-length buffers, so the length is checked first
|
|
157
|
+
* — that does leak the key's length, which is not worth defending and cannot be
|
|
158
|
+
* avoided here.
|
|
159
|
+
*/
|
|
160
|
+
function secretsMatch(provided, expected) {
|
|
161
|
+
if (typeof provided !== 'string')
|
|
162
|
+
return false;
|
|
163
|
+
const a = Buffer.from(provided, 'utf8');
|
|
164
|
+
const b = Buffer.from(expected, 'utf8');
|
|
165
|
+
if (a.length !== b.length)
|
|
166
|
+
return false;
|
|
167
|
+
return (0, node_crypto_1.timingSafeEqual)(a, b);
|
|
168
|
+
}
|
|
137
169
|
function hasMasterKey(req) {
|
|
138
170
|
const expected = process.env.masterKey;
|
|
139
171
|
if (!expected)
|
|
140
172
|
return false;
|
|
141
|
-
if (req.headers?.['x-parse-master-key']
|
|
173
|
+
if (secretsMatch(req.headers?.['x-parse-master-key'], expected))
|
|
142
174
|
return true;
|
|
143
|
-
if (req['x-master-key']
|
|
175
|
+
if (secretsMatch(req['x-master-key'], expected)) {
|
|
144
176
|
if (!warnedBodyMasterKey) {
|
|
145
177
|
warnedBodyMasterKey = true;
|
|
146
178
|
console.warn('[Security] Master key received in the request body. This is deprecated ' +
|
|
@@ -172,7 +204,36 @@ function restrictRoutes(req, res, next) {
|
|
|
172
204
|
return next();
|
|
173
205
|
}
|
|
174
206
|
// Allow /functions for internal use (rewritten by validateEntityRoutes)
|
|
175
|
-
|
|
207
|
+
/*
|
|
208
|
+
* `/functions/{name}` — where validateEntityRoutes rewrites entity routes to,
|
|
209
|
+
* and also reachable directly.
|
|
210
|
+
*
|
|
211
|
+
* The RATE LIMIT used to be skipped on that direct path, because it is
|
|
212
|
+
* applied in validateEntityRoutes, which returns early for anything that is
|
|
213
|
+
* not a registered entity prefix. So `rateLimit: {max: 2}` could be called
|
|
214
|
+
* any number of times just by asking for `/functions/thatName` instead.
|
|
215
|
+
*
|
|
216
|
+
* The declared `methods` deliberately do NOT follow. `/functions` is Parse's
|
|
217
|
+
* own protocol endpoint, and every SDK's `Parse.Cloud.run` POSTs to it —
|
|
218
|
+
* always. `methods` describes the REST facade in FRONT of that, so enforcing
|
|
219
|
+
* it here would 405 every GET-declared function called through Cloud.run.
|
|
220
|
+
*
|
|
221
|
+
* A request the entity middleware already rewrote carries
|
|
222
|
+
* `_entityRouteHandled` and has been counted once; counting again would
|
|
223
|
+
* charge the limiter twice for one call.
|
|
224
|
+
*
|
|
225
|
+
* Exact segment match, so `/functionsX` is not treated as a function route.
|
|
226
|
+
*/
|
|
227
|
+
if (req.path === '/functions' || req.path.startsWith('/functions/')) {
|
|
228
|
+
if (req._entityRouteHandled)
|
|
229
|
+
return next();
|
|
230
|
+
const fnName = req.path.slice('/functions/'.length).split('/')[0];
|
|
231
|
+
const fnMeta = fnName ? cloudRegistry_1.CloudFunctionRegistry.getFunction(fnName) : undefined;
|
|
232
|
+
if (fnMeta?.config.rateLimit) {
|
|
233
|
+
const allowed = (0, rateLimit_1.checkRateLimit)(req, res, fnName, fnMeta.config.rateLimit);
|
|
234
|
+
if (!allowed)
|
|
235
|
+
return; // 429 already sent
|
|
236
|
+
}
|
|
176
237
|
return next();
|
|
177
238
|
}
|
|
178
239
|
return res.status(403).json({ message: 'Route not allowed' });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/middleware/middleware.ts"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/middleware/middleware.ts"],"names":[],"mappings":";;;;;AAWA,wDA4BC;AAeD,oDAmDC;AAmBD,4CA2BC;AAkED,wCAsDC;AAqBD,8DAUC;AAMD,wDAWC;AA/TD,6CAA4C;AAC5C,sDAAiE;AACjE,+DAAkE;AAClE,iEAA2D;AAE3D,2CAA2C;AAE3C;;;GAGG;AACH,SAAgB,sBAAsB,CACpC,GAAY,EACZ,GAAa,EACb,IAAkB;IAElB,qDAAqD;IACrD,IAAK,GAAW,CAAC,mBAAmB,EAAE,CAAC;QACrC,OAAO,IAAI,EAAE,CAAC;IAChB,CAAC;IAED,sEAAsE;IACtE,qEAAqE;IACrE,yEAAyE;IACzE,8CAA8C;IAC9C,MAAM,QAAQ,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACrD,MAAM,YAAY,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC7E,MAAM,QAAQ,GAAG,qCAAqB,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;IAEjE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAC,OAAO,EAAE,oBAAoB,EAAC,CAAC,CAAC;IAC/D,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAoB,CAAC,EAAE,CAAC;QAChE,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAC,OAAO,EAAE,oBAAoB,EAAC,CAAC,CAAC;IAC/D,CAAC;IAED,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC;IACpB,OAAO,IAAI,EAAE,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAgB,oBAAoB,CAClC,GAAY,EACZ,GAAa,EACb,IAAkB;IAElB,MAAM,SAAS,GAAG,GAAG,CAAC,IAAI,CAAC;IAE3B,0DAA0D;IAC1D,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO,IAAI,EAAE,CAAC;IAChB,CAAC;IAED,6DAA6D;IAC7D,IAAI,CAAC,8BAAa,CAAC,kBAAkB,CAAC,SAAS,CAAC,EAAE,CAAC;QACjD,OAAO,IAAI,EAAE,CAAC;IAChB,CAAC;IAED,MAAM,YAAY,GAAG,8BAAa,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEtD,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAC,OAAO,EAAE,iBAAiB,EAAC,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,QAAQ,GAAG,qCAAqB,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;IACjE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAC,OAAO,EAAE,oBAAoB,EAAC,CAAC,CAAC;IAC/D,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAoB,CAAC,EAAE,CAAC;QAChE,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAC,OAAO,EAAE,oBAAoB,EAAC,CAAC,CAAC;IAC/D,CAAC;IAED,sEAAsE;IACtE,IAAI,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,IAAA,0BAAc,EAAC,GAAG,EAAE,GAAG,EAAE,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAClF,IAAI,CAAC,OAAO;YAAE,OAAO,CAAC,mBAAmB;IAC3C,CAAC;IAED,sEAAsE;IACtE,yCAAyC;IACzC,MAAM,WAAW,GAAG,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAClD,GAAG,CAAC,GAAG,GAAG,cAAc,YAAY,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,GAAG,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IAE9E,yEAAyE;IACzE,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3E,GAAG,CAAC,IAAI,GAAG,EAAC,GAAG,GAAG,CAAC,IAAI,EAAE,GAAG,GAAG,CAAC,KAAK,EAAC,CAAC;IACzC,CAAC;IAED,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC;IACnB,GAAW,CAAC,mBAAmB,GAAG,IAAI,CAAC;IACxC,OAAO,IAAI,EAAE,CAAC;AAChB,CAAC;AAED,0DAA0D;AAC1D,MAAM,kBAAkB,GAAG,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;AAEvD;;;;;;;;;;;;;GAaG;AACH,SAAgB,gBAAgB,CAC9B,GAAQ,EACR,GAAa,EACb,GAAW,EACX,QAAwB;IAExB,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAAE,OAAO;IAErE,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;QAChD,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YAChD,GAAG,CAAC,cAAc,CAAC,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,UAAU,CAAC;QAC1D,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,yEAAyE;QACzE,qEAAqE;QACrE,uEAAuE;QACvE,sEAAsE;QACtE,uEAAuE;QACvE;;;;;WAKG;QACH,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH;;;;;;;;;;GAUG;AACH,SAAS,YAAY,CAAC,QAAiB,EAAE,QAAgB;IACvD,IAAI,OAAO,QAAQ,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAE/C,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACxC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACxC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAExC,OAAO,IAAA,6BAAe,EAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/B,CAAC;AAED,SAAS,YAAY,CAAC,GAAQ;IAC5B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;IACvC,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC;IAE5B,IAAI,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,oBAAoB,CAAC,EAAE,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IAE7E,IAAI,YAAY,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC;QAChD,IAAI,CAAC,mBAAmB,EAAE,CAAC;YACzB,mBAAmB,GAAG,IAAI,CAAC;YAC3B,OAAO,CAAC,IAAI,CACV,yEAAyE;gBACvE,0EAA0E;gBAC1E,mEAAmE,CACtE,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,IAAI,mBAAmB,GAAG,KAAK,CAAC;AAEhC;;;;GAIG;AACH,SAAgB,cAAc,CAAC,GAAQ,EAAE,GAAa,EAAE,IAAkB;IACxE,MAAM,YAAY,GAAG,CAAC,SAAS,EAAE,aAAa,EAAE,QAAQ,CAAC,CAAC;IAE1D,uCAAuC;IACvC,IAAI,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC;QACtB,OAAO,IAAI,EAAE,CAAC;IAChB,CAAC;IAED,sBAAsB;IACtB,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;QAC3D,OAAO,IAAI,EAAE,CAAC;IAChB,CAAC;IAED,yCAAyC;IACzC,IAAI,8BAAa,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/C,OAAO,IAAI,EAAE,CAAC;IAChB,CAAC;IAED,wEAAwE;IACxE;;;;;;;;;;;;;;;;;;;OAmBG;IACH,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,IAAI,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QACpE,IAAK,GAAW,CAAC,mBAAmB;YAAE,OAAO,IAAI,EAAE,CAAC;QAEpD,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAClE,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,qCAAqB,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAE9E,IAAI,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC;YAC7B,MAAM,OAAO,GAAG,IAAA,0BAAc,EAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAC1E,IAAI,CAAC,OAAO;gBAAE,OAAO,CAAC,mBAAmB;QAC3C,CAAC;QAED,OAAO,IAAI,EAAE,CAAC;IAChB,CAAC;IAED,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAC,OAAO,EAAE,mBAAmB,EAAC,CAAC,CAAC;AAC9D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,GAAG,iBAAO,CAAC,IAAI,CAAC;IAC9B,KAAK,EAAE,MAAM;IACb,IAAI,EAAE,CAAC,YAAY,CAAC;IACpB,MAAM,EAAE,gBAAgB;CACzB,CAAC,CAAC;AAEH;;;;;GAKG;AACH,SAAgB,yBAAyB,CACvC,GAAY,EACZ,GAAa,EACb,IAAkB;IAElB,IAAI,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,QAAQ,CAAC,EAAE,CAAC;QAC1D,OAAO,IAAI,EAAE,CAAC;IAChB,CAAC;IAED,OAAO,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;AACpC,CAAC;AAED;;;GAGG;AACH,SAAgB,sBAAsB,CAAC,GAAQ,EAAE,GAAQ,EAAE,IAAS;IAClE,MAAM,YAAY,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAExC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAS,EAAE,EAAE;QACvB,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;YACzD,OAAO,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC;QACD,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC,CAAC;IAEF,IAAI,EAAE,CAAC;AACT,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseModel.d.ts","sourceRoot":"","sources":["../../src/models/BaseModel.ts"],"names":[],"mappings":"AAAA,QAAA,MAAM,WAAW,EAA8B,KAAK,SAAS,CAAC,EAAE,MAAM,KAAK,KAAK,CAAC,MAAM,CAAC;AAExF,qBAAa,SAAU,SAAQ,WAAW;IACxC;;;;;;OAMG;IACH,SAAS,CAAC,MAAM,CAAC,wBAAwB,EAAE,MAAM,EAAE,CAAmB;IAEtE,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,OAAO,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC;IAMhF,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,OAAO,SAAS,EAC1C,IAAI,EAAE,CAAC,EACP,MAAM,EAAE,GAAG,GACV,YAAY,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"BaseModel.d.ts","sourceRoot":"","sources":["../../src/models/BaseModel.ts"],"names":[],"mappings":"AAAA,QAAA,MAAM,WAAW,EAA8B,KAAK,SAAS,CAAC,EAAE,MAAM,KAAK,KAAK,CAAC,MAAM,CAAC;AAExF,qBAAa,SAAU,SAAQ,WAAW;IACxC;;;;;;OAMG;IACH,SAAS,CAAC,MAAM,CAAC,wBAAwB,EAAE,MAAM,EAAE,CAAmB;IAEtE,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,OAAO,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC;IAMhF,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,OAAO,SAAS,EAC1C,IAAI,EAAE,CAAC,EACP,MAAM,EAAE,GAAG,GACV,YAAY,CAAC,CAAC,CAAC;CAkFnB"}
|
package/dist/models/BaseModel.js
CHANGED
|
@@ -9,8 +9,11 @@ class BaseModel extends ParseObject {
|
|
|
9
9
|
return obj;
|
|
10
10
|
}
|
|
11
11
|
static fromParams(params) {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
// A copy, so this never writes `className` into the caller's `req.params`.
|
|
13
|
+
// Mutating the request object meant a second call — a retried transaction,
|
|
14
|
+
// say — saw an input the first call had already altered.
|
|
15
|
+
const source = { ...params, className: new this().className };
|
|
16
|
+
const raw = Parse.Object.fromJSON(source, true);
|
|
14
17
|
Object.setPrototypeOf(raw, this.prototype);
|
|
15
18
|
const obj = raw;
|
|
16
19
|
if (params.id)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseModel.js","sourceRoot":"","sources":["../../src/models/BaseModel.ts"],"names":[],"mappings":";;;AAAA,MAAM,WAAW,GAAG,KAAK,CAAC,MAA6D,CAAC;AAExF,MAAa,SAAU,SAAQ,WAAW;IAUxC,MAAM,CAAC,OAAO,CAAsC,EAAU;QAC5D,MAAM,GAAG,GAAG,IAAI,IAAI,EAAqB,CAAC;QAC1C,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC;QACZ,OAAO,GAAG,CAAC;IACb,CAAC;IAED,MAAM,CAAC,UAAU,CAEf,MAAW;QAEX,MAAM,
|
|
1
|
+
{"version":3,"file":"BaseModel.js","sourceRoot":"","sources":["../../src/models/BaseModel.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/swagger/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,OAAO,EAAC,MAAM,SAAS,CAAC;AAChC,OAAO,EAAsB,aAAa,EAAC,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/swagger/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,OAAO,EAAC,MAAM,SAAS,CAAC;AAChC,OAAO,EAAsB,aAAa,EAAC,MAAM,eAAe,CAAC;AAGjE,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAC,mBAAmB,EAAE,cAAc,EAAC,MAAM,eAAe,CAAC;AAqBlE;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,aAAa,EACrB,IAAI,GAAE,MAAoB,QA8C3B"}
|
package/dist/swagger/index.js
CHANGED
|
@@ -3,8 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.getSwaggerJson = exports.generateSwaggerSpec = exports.SwaggerRegistry = void 0;
|
|
4
4
|
exports.setupSwagger = setupSwagger;
|
|
5
5
|
const swaggerSpec_1 = require("./swaggerSpec");
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const swaggerRegistry_1 = require("./swaggerRegistry");
|
|
7
|
+
var swaggerRegistry_2 = require("./swaggerRegistry");
|
|
8
|
+
Object.defineProperty(exports, "SwaggerRegistry", { enumerable: true, get: function () { return swaggerRegistry_2.SwaggerRegistry; } });
|
|
8
9
|
var swaggerSpec_2 = require("./swaggerSpec");
|
|
9
10
|
Object.defineProperty(exports, "generateSwaggerSpec", { enumerable: true, get: function () { return swaggerSpec_2.generateSwaggerSpec; } });
|
|
10
11
|
Object.defineProperty(exports, "getSwaggerJson", { enumerable: true, get: function () { return swaggerSpec_2.getSwaggerJson; } });
|
|
@@ -35,8 +36,20 @@ function loadSwaggerUi() {
|
|
|
35
36
|
* browser page, not the document.
|
|
36
37
|
*/
|
|
37
38
|
function setupSwagger(app, config, path = '/api-docs') {
|
|
38
|
-
// Generate
|
|
39
|
-
|
|
39
|
+
// Generate once, then reuse until something registers.
|
|
40
|
+
//
|
|
41
|
+
// The spec was rebuilt from every model and every endpoint on each hit of
|
|
42
|
+
// `{path}/json` — walking the whole registry to produce a document that had
|
|
43
|
+
// not changed since boot. Keyed on the registry's revision rather than a flag,
|
|
44
|
+
// so late registration still invalidates it and nothing has to remember to.
|
|
45
|
+
let cached = null;
|
|
46
|
+
const getSpec = () => {
|
|
47
|
+
const revision = swaggerRegistry_1.SwaggerRegistry.getRevision();
|
|
48
|
+
if (!cached || cached.revision !== revision) {
|
|
49
|
+
cached = { revision, spec: (0, swaggerSpec_1.generateSwaggerSpec)(config) };
|
|
50
|
+
}
|
|
51
|
+
return cached.spec;
|
|
52
|
+
};
|
|
40
53
|
// Serve Swagger JSON BEFORE the UI middleware (so it doesn't get caught)
|
|
41
54
|
app.get(`${path}/json`, (req, res) => {
|
|
42
55
|
res.json(getSpec());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/swagger/index.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/swagger/index.ts"],"names":[],"mappings":";;;AAiCA,oCAiDC;AAjFD,+CAAiE;AACjE,uDAAkD;AAElD,qDAAkD;AAA1C,kHAAA,eAAe,OAAA;AACvB,6CAAkE;AAA1D,kHAAA,mBAAmB,OAAA;AAAE,6GAAA,cAAc,OAAA;AAE3C;;;;;;;;;GASG;AACH,SAAS,aAAa;IACpB,IAAI,CAAC;QACH,8DAA8D;QAC9D,OAAO,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,YAAY,CAC1B,GAAY,EACZ,MAAqB,EACrB,OAAe,WAAW;IAE1B,uDAAuD;IACvD,EAAE;IACF,0EAA0E;IAC1E,4EAA4E;IAC5E,+EAA+E;IAC/E,4EAA4E;IAC5E,IAAI,MAAM,GAA4C,IAAI,CAAC;IAC3D,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,MAAM,QAAQ,GAAG,iCAAe,CAAC,WAAW,EAAE,CAAC;QAC/C,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAC5C,MAAM,GAAG,EAAC,QAAQ,EAAE,IAAI,EAAE,IAAA,iCAAmB,EAAC,MAAM,CAAC,EAAC,CAAC;QACzD,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC,CAAC;IAEF,yEAAyE;IACzE,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACnC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IACtB,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,GAAG,CAAC,mCAAmC,IAAI,OAAO,CAAC,CAAC;IAE5D,MAAM,SAAS,GAAG,aAAa,EAAE,CAAC;IAClC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CACV,oEAAoE,IAAI,cAAc;YACpF,mCAAmC,CACtC,CAAC;QACF,OAAO;IACT,CAAC;IAED,mBAAmB;IACnB,GAAG,CAAC,GAAG,CACL,IAAI,EACJ,SAAS,CAAC,KAAK,EACf,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE;QACpB,cAAc,EAAE;YACd,GAAG,EAAE,GAAG,IAAI,OAAO;YACnB,oBAAoB,EAAE,IAAI;SAC3B;QACD,eAAe,EAAE,MAAM,CAAC,KAAK,IAAI,mBAAmB;KACrD,CAAC,CACH,CAAC;IAEF,OAAO,CAAC,GAAG,CAAC,4CAA4C,IAAI,EAAE,CAAC,CAAC;AAClE,CAAC"}
|
|
@@ -43,6 +43,17 @@ export interface SwaggerFunctionSchema {
|
|
|
43
43
|
export declare class SwaggerRegistry {
|
|
44
44
|
private static models;
|
|
45
45
|
private static functions;
|
|
46
|
+
/**
|
|
47
|
+
* Bumped on every change to what the spec would contain.
|
|
48
|
+
*
|
|
49
|
+
* Lets `setupSwagger` cache the generated document and still be correct: it
|
|
50
|
+
* compares revisions rather than trusting that registration stopped at boot.
|
|
51
|
+
* Anything registered later invalidates the cache on its own, with no call to
|
|
52
|
+
* remember.
|
|
53
|
+
*/
|
|
54
|
+
private static revision;
|
|
55
|
+
/** The current content revision. Changes whenever the spec would change. */
|
|
56
|
+
static getRevision(): number;
|
|
46
57
|
/**
|
|
47
58
|
* Register a model for Swagger documentation
|
|
48
59
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"swaggerRegistry.d.ts","sourceRoot":"","sources":["../../src/swagger/swaggerRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,gCAAgC,CAAC;AAEhE,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;IAClD,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE;QACZ,QAAQ,EAAE,OAAO,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;KACnD,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;CACnE;AAED;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAC,MAAM,CAA8C;IACnE,OAAO,CAAC,MAAM,CAAC,SAAS,CAAiD;IAEzE;;OAEG;IACH,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,kBAAkB;
|
|
1
|
+
{"version":3,"file":"swaggerRegistry.d.ts","sourceRoot":"","sources":["../../src/swagger/swaggerRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,gCAAgC,CAAC;AAEhE,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;IAClD,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE;QACZ,QAAQ,EAAE,OAAO,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;KACnD,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;CACnE;AAED;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAC,MAAM,CAA8C;IACnE,OAAO,CAAC,MAAM,CAAC,SAAS,CAAiD;IAEzE;;;;;;;OAOG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAK;IAE5B,4EAA4E;IAC5E,MAAM,CAAC,WAAW,IAAI,MAAM;IAI5B;;OAEG;IACH,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,kBAAkB;IAM/C;;OAEG;IACH,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,qBAAqB;IAMrD;;;;OAIG;IACH,MAAM,CAAC,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAQjD;;;;OAIG;IACH,MAAM,CAAC,yBAAyB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW;IAiDlE;;OAEG;IACH,MAAM,CAAC,SAAS;IAIhB;;OAEG;IACH,MAAM,CAAC,YAAY;IAInB;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM;IAI5B;;OAEG;IACH,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM;IAI/B;;;OAGG;IACH,MAAM,CAAC,kBAAkB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAWjE;;OAEG;IACH,MAAM,CAAC,kBAAkB,CACvB,SAAS,EAAE,MAAM,EACjB,WAAW,CAAC,EAAE,MAAM,GACnB,qBAAqB;IAiCxB;;OAEG;IACH,MAAM,CAAC,uBAAuB,CAAC,IAAI,EAAE,GAAG,GAAG,qBAAqB;CAQjE"}
|
|
@@ -6,11 +6,16 @@ require("reflect-metadata");
|
|
|
6
6
|
* Registry for collecting Swagger/OpenAPI documentation metadata
|
|
7
7
|
*/
|
|
8
8
|
class SwaggerRegistry {
|
|
9
|
+
/** The current content revision. Changes whenever the spec would change. */
|
|
10
|
+
static getRevision() {
|
|
11
|
+
return this.revision;
|
|
12
|
+
}
|
|
9
13
|
/**
|
|
10
14
|
* Register a model for Swagger documentation
|
|
11
15
|
*/
|
|
12
16
|
static registerModel(schema) {
|
|
13
17
|
this.models.set(schema.className, schema);
|
|
18
|
+
this.revision += 1;
|
|
14
19
|
console.log(`[Swagger] Registered model: ${schema.className}`);
|
|
15
20
|
}
|
|
16
21
|
/**
|
|
@@ -18,6 +23,7 @@ class SwaggerRegistry {
|
|
|
18
23
|
*/
|
|
19
24
|
static registerFunction(schema) {
|
|
20
25
|
this.functions.set(schema.name, schema);
|
|
26
|
+
this.revision += 1;
|
|
21
27
|
console.log(`[Swagger] Registered function: ${schema.name}`);
|
|
22
28
|
}
|
|
23
29
|
/**
|
|
@@ -27,8 +33,10 @@ class SwaggerRegistry {
|
|
|
27
33
|
*/
|
|
28
34
|
static setFunctionPath(name, path) {
|
|
29
35
|
const fn = this.functions.get(name);
|
|
30
|
-
if (fn)
|
|
36
|
+
if (fn) {
|
|
31
37
|
fn.path = path;
|
|
38
|
+
this.revision += 1;
|
|
39
|
+
}
|
|
32
40
|
}
|
|
33
41
|
/**
|
|
34
42
|
* Build + register a cloud function's Swagger schema straight from its
|
|
@@ -166,4 +174,13 @@ class SwaggerRegistry {
|
|
|
166
174
|
exports.SwaggerRegistry = SwaggerRegistry;
|
|
167
175
|
SwaggerRegistry.models = new Map();
|
|
168
176
|
SwaggerRegistry.functions = new Map();
|
|
177
|
+
/**
|
|
178
|
+
* Bumped on every change to what the spec would contain.
|
|
179
|
+
*
|
|
180
|
+
* Lets `setupSwagger` cache the generated document and still be correct: it
|
|
181
|
+
* compares revisions rather than trusting that registration stopped at boot.
|
|
182
|
+
* Anything registered later invalidates the cache on its own, with no call to
|
|
183
|
+
* remember.
|
|
184
|
+
*/
|
|
185
|
+
SwaggerRegistry.revision = 0;
|
|
169
186
|
//# sourceMappingURL=swaggerRegistry.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"swaggerRegistry.js","sourceRoot":"","sources":["../../src/swagger/swaggerRegistry.ts"],"names":[],"mappings":";;;AAAA,4BAA0B;AAqC1B;;GAEG;AACH,MAAa,eAAe;
|
|
1
|
+
{"version":3,"file":"swaggerRegistry.js","sourceRoot":"","sources":["../../src/swagger/swaggerRegistry.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,SA8CjC,CAAC"}
|
|
@@ -13,18 +13,40 @@ const importFiles = (directoryPath, options = {}) => {
|
|
|
13
13
|
// `.d.ts` is a declaration, never a module to execute — importing one under
|
|
14
14
|
// ts-node is at best a no-op and at worst a crash.
|
|
15
15
|
const wanted = new Set(extensions.map(e => (e.startsWith('.') ? e : `.${e}`)));
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
const counts = { imported: 0, passedOver: new Set() };
|
|
17
|
+
const walk = (dir) => {
|
|
18
|
+
for (const file of (0, fs_1.readdirSync)(dir)) {
|
|
19
|
+
const filePath = (0, path_1.join)(dir, file);
|
|
20
|
+
if (isDirectory(filePath)) {
|
|
21
|
+
walk(filePath);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const ext = (0, path_1.extname)(filePath);
|
|
25
|
+
if (wanted.has(ext) && !file.endsWith('.d.ts')) {
|
|
26
|
+
if (verbose)
|
|
27
|
+
console.log(filePath);
|
|
28
|
+
require(filePath);
|
|
29
|
+
counts.imported += 1;
|
|
30
|
+
}
|
|
31
|
+
else if (ext && !file.endsWith('.d.ts')) {
|
|
32
|
+
counts.passedOver.add(ext);
|
|
33
|
+
}
|
|
21
34
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
};
|
|
36
|
+
walk(directoryPath);
|
|
37
|
+
// Importing nothing is the single easiest way to end up with a server that
|
|
38
|
+
// starts perfectly and has no models, no schema and no routes. It looks like
|
|
39
|
+
// the library did nothing, so say what actually happened.
|
|
40
|
+
if (counts.imported === 0 && counts.passedOver.size > 0) {
|
|
41
|
+
const found = [...counts.passedOver].sort().join(', ');
|
|
42
|
+
const asked = [...wanted].sort().join(', ');
|
|
43
|
+
console.warn(`[importFiles] Imported 0 files from ${directoryPath}. ` +
|
|
44
|
+
`Looking for ${asked}, but the only files there are ${found}. ` +
|
|
45
|
+
(counts.passedOver.has('.ts')
|
|
46
|
+
? "Running from TypeScript sources? Pass {extensions: ['.js', '.ts']}."
|
|
47
|
+
: 'Check the path and the `extensions` option.') +
|
|
48
|
+
' Nothing has been registered: no models, no cloud functions, no routes.');
|
|
49
|
+
}
|
|
28
50
|
};
|
|
29
51
|
exports.importFiles = importFiles;
|
|
30
52
|
//# sourceMappingURL=dynamicImport.js.map
|
|
@@ -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;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,
|
|
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,MAAM,GAAG,EAAC,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,IAAI,GAAG,EAAU,EAAC,CAAC;IAE5D,MAAM,IAAI,GAAG,CAAC,GAAW,EAAQ,EAAE;QACjC,KAAK,MAAM,IAAI,IAAI,IAAA,gBAAW,EAAC,GAAG,CAAC,EAAE,CAAC;YACpC,MAAM,QAAQ,GAAG,IAAA,WAAI,EAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACjC,IAAI,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC1B,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACf,SAAS;YACX,CAAC;YAED,MAAM,GAAG,GAAG,IAAA,cAAO,EAAC,QAAQ,CAAC,CAAC;YAC9B,IAAI,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC/C,IAAI,OAAO;oBAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;gBACnC,OAAO,CAAC,QAAQ,CAAC,CAAC;gBAClB,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;YACvB,CAAC;iBAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC1C,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,IAAI,CAAC,aAAa,CAAC,CAAC;IAEpB,2EAA2E;IAC3E,6EAA6E;IAC7E,0DAA0D;IAC1D,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QACxD,MAAM,KAAK,GAAG,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvD,MAAM,KAAK,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5C,OAAO,CAAC,IAAI,CACV,uCAAuC,aAAa,IAAI;YACtD,eAAe,KAAK,kCAAkC,KAAK,IAAI;YAC/D,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC;gBAC3B,CAAC,CAAC,qEAAqE;gBACvE,CAAC,CAAC,6CAA6C,CAAC;YAClD,yEAAyE,CAC5E,CAAC;IACJ,CAAC;AACH,CAAC,CAAC;AAhDW,QAAA,WAAW,eAgDtB"}
|
package/dist/utils/helper.d.ts
CHANGED
|
@@ -5,7 +5,11 @@ export declare function generateRandomString(length?: number): string;
|
|
|
5
5
|
export declare function sleep(ms: number): Promise<unknown>;
|
|
6
6
|
export declare function catchError<T>(promise: Promise<T>): Promise<[undefined, T] | [Error]>;
|
|
7
7
|
/**
|
|
8
|
-
* Get role names for a single user
|
|
8
|
+
* Get role names for a single user.
|
|
9
|
+
*
|
|
10
|
+
* Served from the role cache when one is configured; otherwise this is the same
|
|
11
|
+
* query it has always been. See `configureRoleCache` — the cache is off unless
|
|
12
|
+
* a deployment turns it on.
|
|
9
13
|
*/
|
|
10
14
|
export declare function getUserRoles(user: Parse.User): Promise<string[]>;
|
|
11
15
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../../src/utils/helper.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../../src/utils/helper.ts"],"names":[],"mappings":"AAEA,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;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAWtE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAgCvF"}
|
package/dist/utils/helper.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.sleep = sleep;
|
|
|
8
8
|
exports.catchError = catchError;
|
|
9
9
|
exports.getUserRoles = getUserRoles;
|
|
10
10
|
exports.getUsersRoles = getUsersRoles;
|
|
11
|
+
const roleCache_1 = require("./roleCache");
|
|
11
12
|
function formatCount(num, locale = 'en') {
|
|
12
13
|
return new Intl.NumberFormat(locale, {
|
|
13
14
|
notation: 'compact',
|
|
@@ -58,13 +59,22 @@ async function catchError(promise) {
|
|
|
58
59
|
});
|
|
59
60
|
}
|
|
60
61
|
/**
|
|
61
|
-
* Get role names for a single user
|
|
62
|
+
* Get role names for a single user.
|
|
63
|
+
*
|
|
64
|
+
* Served from the role cache when one is configured; otherwise this is the same
|
|
65
|
+
* query it has always been. See `configureRoleCache` — the cache is off unless
|
|
66
|
+
* a deployment turns it on.
|
|
62
67
|
*/
|
|
63
68
|
async function getUserRoles(user) {
|
|
69
|
+
const cached = (0, roleCache_1.readCachedRoles)(user.id);
|
|
70
|
+
if (cached)
|
|
71
|
+
return cached;
|
|
64
72
|
const roleQuery = new Parse.Query('_Role');
|
|
65
73
|
roleQuery.equalTo('users', user);
|
|
66
74
|
const roles = await roleQuery.find({ useMasterKey: true });
|
|
67
|
-
|
|
75
|
+
const names = roles.map(r => r.get('name'));
|
|
76
|
+
(0, roleCache_1.writeCachedRoles)(user.id, names);
|
|
77
|
+
return names;
|
|
68
78
|
}
|
|
69
79
|
/**
|
|
70
80
|
* Get role names for multiple users.
|
|
@@ -81,6 +91,17 @@ async function getUserRoles(user) {
|
|
|
81
91
|
async function getUsersRoles(users) {
|
|
82
92
|
if (users.length === 0)
|
|
83
93
|
return new Map();
|
|
94
|
+
// Every user already cached means the whole batch — and its one-query-per-role
|
|
95
|
+
// cost — can be skipped outright.
|
|
96
|
+
const fromCache = new Map();
|
|
97
|
+
for (const user of users) {
|
|
98
|
+
const cached = (0, roleCache_1.readCachedRoles)(user.id);
|
|
99
|
+
if (!cached)
|
|
100
|
+
break;
|
|
101
|
+
fromCache.set(user.id, cached);
|
|
102
|
+
}
|
|
103
|
+
if (fromCache.size === users.length)
|
|
104
|
+
return fromCache;
|
|
84
105
|
const allRoles = await new Parse.Query('_Role').limit(100).find({ useMasterKey: true });
|
|
85
106
|
const userRolesMap = new Map();
|
|
86
107
|
for (const user of users) {
|
|
@@ -97,6 +118,8 @@ async function getUsersRoles(users) {
|
|
|
97
118
|
userRolesMap.set(u.id, existing);
|
|
98
119
|
}
|
|
99
120
|
}
|
|
121
|
+
for (const [userId, names] of userRolesMap)
|
|
122
|
+
(0, roleCache_1.writeCachedRoles)(userId, names);
|
|
100
123
|
return userRolesMap;
|
|
101
124
|
}
|
|
102
125
|
//# sourceMappingURL=helper.js.map
|
package/dist/utils/helper.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helper.js","sourceRoot":"","sources":["../../src/utils/helper.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"helper.js","sourceRoot":"","sources":["../../src/utils/helper.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Role membership, remembered for a while.
|
|
3
|
+
*
|
|
4
|
+
* `@CloudFunction({requireRoles})` asks the database who a user is on every
|
|
5
|
+
* single call, and so does every `getUserRoles()`. That is a full round-trip —
|
|
6
|
+
* milliseconds, not microseconds — to answer a question whose answer almost
|
|
7
|
+
* never changes.
|
|
8
|
+
*
|
|
9
|
+
* **Off by default, and deliberately so.** Caching membership means a revoked
|
|
10
|
+
* role keeps working until the entry expires. That is a security trade, and it
|
|
11
|
+
* belongs to whoever runs the deployment, not to this library. Nothing here
|
|
12
|
+
* takes effect until `configureRoleCache()` is called.
|
|
13
|
+
*
|
|
14
|
+
* When it is on, `invalidateRoles(userId)` is the tool that keeps the window
|
|
15
|
+
* from mattering: call it wherever you grant or revoke, and the TTL becomes a
|
|
16
|
+
* backstop for changes made outside your code rather than the primary control.
|
|
17
|
+
*
|
|
18
|
+
* There is no timer. Entries expire when read and the map is capped on write,
|
|
19
|
+
* so importing this module costs nothing and holds nothing open.
|
|
20
|
+
*/
|
|
21
|
+
export interface RoleCacheOptions {
|
|
22
|
+
/** How long a user's role list may be reused, in milliseconds. */
|
|
23
|
+
ttlMs: number;
|
|
24
|
+
/**
|
|
25
|
+
* Most users to remember at once. Oldest entries are dropped past this.
|
|
26
|
+
* Default 10000 — a bound against memory growth, not a tuning knob.
|
|
27
|
+
*/
|
|
28
|
+
maxUsers?: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Turn the cache on, change its settings, or turn it off with `false`.
|
|
32
|
+
*
|
|
33
|
+
* Switching off clears whatever was held, so a deployment can disable it and be
|
|
34
|
+
* certain nothing stale survives.
|
|
35
|
+
*/
|
|
36
|
+
export declare function configureRoleCache(config: RoleCacheOptions | false): void;
|
|
37
|
+
export declare function roleCacheEnabled(): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Forget a user's roles, or everyone's.
|
|
40
|
+
*
|
|
41
|
+
* Call this wherever a role is granted or revoked. Safe to call when the cache
|
|
42
|
+
* is off — it simply has nothing to do.
|
|
43
|
+
*/
|
|
44
|
+
export declare function invalidateRoles(userId?: string): void;
|
|
45
|
+
/** For tests and for reporting. */
|
|
46
|
+
export declare function roleCacheStats(): {
|
|
47
|
+
enabled: boolean;
|
|
48
|
+
size: number;
|
|
49
|
+
ttlMs: number | null;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* The cached roles for this user, or nothing if there is no live entry.
|
|
53
|
+
*
|
|
54
|
+
* `ttlOverrideMs` of 0 means "do not use the cache for this call" — how a
|
|
55
|
+
* single cloud function opts out of a global policy.
|
|
56
|
+
*/
|
|
57
|
+
export declare function readCachedRoles(userId: string, ttlOverrideMs?: number): string[] | undefined;
|
|
58
|
+
/** Remember this user's roles. A no-op while the cache is off. */
|
|
59
|
+
export declare function writeCachedRoles(userId: string, roles: string[], ttlOverrideMs?: number): void;
|
|
60
|
+
//# sourceMappingURL=roleCache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"roleCache.d.ts","sourceRoot":"","sources":["../../src/utils/roleCache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAOH,MAAM,WAAW,gBAAgB;IAC/B,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAOD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,gBAAgB,GAAG,KAAK,GAAG,IAAI,CAkBzE;AAED,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAGrD;AAED,mCAAmC;AACnC,wBAAgB,cAAc,IAAI;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,CAMvF;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,aAAa,CAAC,EAAE,MAAM,GACrB,MAAM,EAAE,GAAG,SAAS,CActB;AAED,kEAAkE;AAClE,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EAAE,EACf,aAAa,CAAC,EAAE,MAAM,GACrB,IAAI,CAaN"}
|