@terreno/api 0.0.11-beta.1 → 0.0.11
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/bunfig.toml +2 -3
- package/dist/api.d.ts +14 -3
- package/dist/api.js +68 -44
- package/dist/api.test.js +166 -2051
- package/dist/permissions.d.ts +1 -1
- package/dist/permissions.js +25 -17
- package/dist/utils.test.js +7 -169
- package/package.json +1 -2
- package/src/api.test.ts +142 -1736
- package/src/api.ts +61 -19
- package/src/permissions.ts +14 -4
- package/src/utils.test.ts +9 -189
- package/CLAUDE.md +0 -107
package/bunfig.toml
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
[test]
|
|
2
2
|
preload = ["./src/tests/bunSetup.ts"]
|
|
3
3
|
root = "./src"
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
coverageThreshold = { line = 80, function = 80 }
|
|
4
|
+
coverageExclude = ["dist/**"]
|
|
5
|
+
# Note: No coverageThreshold set - tests will not fail on low coverage
|
|
7
6
|
|
package/dist/api.d.ts
CHANGED
|
@@ -181,6 +181,16 @@ export interface ModelRouterOptions<T> {
|
|
|
181
181
|
* keys. Throw an APIError to return a 400 with an error message.
|
|
182
182
|
*/
|
|
183
183
|
responseHandler?: (value: (Document<any, any, any> & T) | (Document<any, any, any> & T)[], method: "list" | "create" | "read" | "update" | "delete", request: express.Request, options: ModelRouterOptions<T>) => Promise<JSONValue>;
|
|
184
|
+
/**
|
|
185
|
+
* The discriminatorKey that you passed when creating the Mongoose models. Defaults to __t. See:
|
|
186
|
+
* https://mongoosejs.com/docs/discriminators.html If this key is provided,
|
|
187
|
+
* you must provide the same key as part of the top level of the body when making performing
|
|
188
|
+
* update or delete operations on this model.
|
|
189
|
+
* \{discriminatorKey: "__t"\}
|
|
190
|
+
*
|
|
191
|
+
* PATCH \{__t: "SuperUser", name: "Foo"\} // __t is required or there will be a 404 error.
|
|
192
|
+
*/
|
|
193
|
+
discriminatorKey?: string;
|
|
184
194
|
/**
|
|
185
195
|
* The OpenAPI generator for this server. This is used to generate the OpenAPI documentation.
|
|
186
196
|
*/
|
|
@@ -204,13 +214,14 @@ export interface ModelRouterOptions<T> {
|
|
|
204
214
|
*/
|
|
205
215
|
openApiExtraModelProperties?: any;
|
|
206
216
|
}
|
|
217
|
+
export declare function getModel(baseModel: Model<any>, body?: any, options?: ModelRouterOptions<any>): mongoose.Model<any, {}, {}, {}, any, any, any>;
|
|
207
218
|
/**
|
|
208
|
-
* Create a set of CRUD routes given a Mongoose model and configuration options.
|
|
219
|
+
* Create a set of CRUD routes given a Mongoose model $baseModel and configuration options.
|
|
209
220
|
*
|
|
210
|
-
* @param
|
|
221
|
+
* @param baseModel A Mongoose Model
|
|
211
222
|
* @param options Options for configuring the REST API, such as permissions, transformers, and hooks.
|
|
212
223
|
*/
|
|
213
|
-
export declare function modelRouter<T>(
|
|
224
|
+
export declare function modelRouter<T>(baseModel: Model<T>, options: ModelRouterOptions<T>): express.Router;
|
|
214
225
|
export declare const asyncHandler: (fn: any) => (req: Request, res: Response, next: NextFunction) => Promise<any>;
|
|
215
226
|
export declare const gooseRestRouter: typeof modelRouter;
|
|
216
227
|
export type GooseRESTOptions<T> = ModelRouterOptions<T>;
|
package/dist/api.js
CHANGED
|
@@ -121,6 +121,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
121
121
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
122
122
|
exports.gooseRestRouter = exports.asyncHandler = void 0;
|
|
123
123
|
exports.addPopulateToQuery = addPopulateToQuery;
|
|
124
|
+
exports.getModel = getModel;
|
|
124
125
|
exports.modelRouter = modelRouter;
|
|
125
126
|
/**
|
|
126
127
|
* This is the doc comment for api.ts
|
|
@@ -167,6 +168,21 @@ function addPopulateToQuery(builtQuery, populatePaths) {
|
|
|
167
168
|
var PAGINATION_QUERY_PARAMS = ["limit", "page", "sort"];
|
|
168
169
|
// Add support for more complex queries.
|
|
169
170
|
var COMPLEX_QUERY_PARAMS = ["$and", "$or"];
|
|
171
|
+
// A function to decide which model to use. If no discriminators are provided,
|
|
172
|
+
// just returns the base model. If
|
|
173
|
+
function getModel(baseModel, body, options) {
|
|
174
|
+
var _a, _b;
|
|
175
|
+
var discriminatorKey = (_a = options === null || options === void 0 ? void 0 : options.discriminatorKey) !== null && _a !== void 0 ? _a : "__t";
|
|
176
|
+
var modelName = body === null || body === void 0 ? void 0 : body[discriminatorKey];
|
|
177
|
+
if (!modelName) {
|
|
178
|
+
return baseModel;
|
|
179
|
+
}
|
|
180
|
+
var model = (_b = baseModel.discriminators) === null || _b === void 0 ? void 0 : _b[modelName];
|
|
181
|
+
if (!model) {
|
|
182
|
+
throw new Error("Could not find discriminator model for key ".concat(modelName, ", baseModel: ").concat(baseModel));
|
|
183
|
+
}
|
|
184
|
+
return model;
|
|
185
|
+
}
|
|
170
186
|
// Ensures query params are allowed. Also checks nested query params when using $and/$or.
|
|
171
187
|
function checkQueryParamAllowed(queryParam, queryParamValue, queryFields) {
|
|
172
188
|
var e_2, _a, e_3, _b;
|
|
@@ -228,12 +244,12 @@ function checkQueryParamAllowed(queryParam, queryParamValue, queryFields) {
|
|
|
228
244
|
// return result;
|
|
229
245
|
// }
|
|
230
246
|
/**
|
|
231
|
-
* Create a set of CRUD routes given a Mongoose model and configuration options.
|
|
247
|
+
* Create a set of CRUD routes given a Mongoose model $baseModel and configuration options.
|
|
232
248
|
*
|
|
233
|
-
* @param
|
|
249
|
+
* @param baseModel A Mongoose Model
|
|
234
250
|
* @param options Options for configuring the REST API, such as permissions, transformers, and hooks.
|
|
235
251
|
*/
|
|
236
|
-
function modelRouter(
|
|
252
|
+
function modelRouter(baseModel, options) {
|
|
237
253
|
var _this = this;
|
|
238
254
|
var _a;
|
|
239
255
|
var router = express_1.default.Router();
|
|
@@ -244,13 +260,15 @@ function modelRouter(model, options) {
|
|
|
244
260
|
var responseHandler = (_a = options.responseHandler) !== null && _a !== void 0 ? _a : transformers_1.defaultResponseHandler;
|
|
245
261
|
router.post("/", [
|
|
246
262
|
(0, auth_1.authenticateMiddleware)(options.allowAnonymous),
|
|
247
|
-
(0, openApi_1.createOpenApiMiddleware)(
|
|
248
|
-
(0, permissions_1.permissionMiddleware)(
|
|
263
|
+
(0, openApi_1.createOpenApiMiddleware)(baseModel, options),
|
|
264
|
+
(0, permissions_1.permissionMiddleware)(baseModel, options),
|
|
249
265
|
], (0, exports.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
|
|
250
|
-
var body, error_1, data, error_2, populateQuery, error_3, error_4, serialized, error_5;
|
|
251
|
-
|
|
252
|
-
|
|
266
|
+
var model, body, error_1, data, error_2, populateQuery, error_3, error_4, serialized, error_5;
|
|
267
|
+
var _a;
|
|
268
|
+
return __generator(this, function (_b) {
|
|
269
|
+
switch (_b.label) {
|
|
253
270
|
case 0:
|
|
271
|
+
model = getModel(baseModel, (_a = req.body) === null || _a === void 0 ? void 0 : _a.__t, options);
|
|
254
272
|
try {
|
|
255
273
|
body = (0, transformers_1.transform)(options, req.body, "create", req.user);
|
|
256
274
|
}
|
|
@@ -263,15 +281,15 @@ function modelRouter(model, options) {
|
|
|
263
281
|
});
|
|
264
282
|
}
|
|
265
283
|
if (!options.preCreate) return [3 /*break*/, 5];
|
|
266
|
-
|
|
284
|
+
_b.label = 1;
|
|
267
285
|
case 1:
|
|
268
|
-
|
|
286
|
+
_b.trys.push([1, 3, , 4]);
|
|
269
287
|
return [4 /*yield*/, options.preCreate(body, req)];
|
|
270
288
|
case 2:
|
|
271
|
-
body =
|
|
289
|
+
body = _b.sent();
|
|
272
290
|
return [3 /*break*/, 4];
|
|
273
291
|
case 3:
|
|
274
|
-
error_1 =
|
|
292
|
+
error_1 = _b.sent();
|
|
275
293
|
if ((0, errors_1.isAPIError)(error_1)) {
|
|
276
294
|
throw error_1;
|
|
277
295
|
}
|
|
@@ -296,7 +314,7 @@ function modelRouter(model, options) {
|
|
|
296
314
|
title: "Create not allowed",
|
|
297
315
|
});
|
|
298
316
|
}
|
|
299
|
-
|
|
317
|
+
_b.label = 5;
|
|
300
318
|
case 5:
|
|
301
319
|
if (body === undefined) {
|
|
302
320
|
throw new errors_1.APIError({
|
|
@@ -305,15 +323,15 @@ function modelRouter(model, options) {
|
|
|
305
323
|
title: "Invalid request body",
|
|
306
324
|
});
|
|
307
325
|
}
|
|
308
|
-
|
|
326
|
+
_b.label = 6;
|
|
309
327
|
case 6:
|
|
310
|
-
|
|
328
|
+
_b.trys.push([6, 8, , 9]);
|
|
311
329
|
return [4 /*yield*/, model.create(body)];
|
|
312
330
|
case 7:
|
|
313
|
-
data =
|
|
331
|
+
data = _b.sent();
|
|
314
332
|
return [3 /*break*/, 9];
|
|
315
333
|
case 8:
|
|
316
|
-
error_2 =
|
|
334
|
+
error_2 = _b.sent();
|
|
317
335
|
throw new errors_1.APIError({
|
|
318
336
|
disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_2),
|
|
319
337
|
error: error_2,
|
|
@@ -322,17 +340,17 @@ function modelRouter(model, options) {
|
|
|
322
340
|
});
|
|
323
341
|
case 9:
|
|
324
342
|
if (!options.populatePaths) return [3 /*break*/, 13];
|
|
325
|
-
|
|
343
|
+
_b.label = 10;
|
|
326
344
|
case 10:
|
|
327
|
-
|
|
345
|
+
_b.trys.push([10, 12, , 13]);
|
|
328
346
|
populateQuery = model.findById(data._id);
|
|
329
347
|
populateQuery = addPopulateToQuery(populateQuery, options.populatePaths);
|
|
330
348
|
return [4 /*yield*/, populateQuery.exec()];
|
|
331
349
|
case 11:
|
|
332
|
-
data =
|
|
350
|
+
data = _b.sent();
|
|
333
351
|
return [3 /*break*/, 13];
|
|
334
352
|
case 12:
|
|
335
|
-
error_3 =
|
|
353
|
+
error_3 = _b.sent();
|
|
336
354
|
throw new errors_1.APIError({
|
|
337
355
|
disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_3),
|
|
338
356
|
error: error_3,
|
|
@@ -341,15 +359,15 @@ function modelRouter(model, options) {
|
|
|
341
359
|
});
|
|
342
360
|
case 13:
|
|
343
361
|
if (!options.postCreate) return [3 /*break*/, 17];
|
|
344
|
-
|
|
362
|
+
_b.label = 14;
|
|
345
363
|
case 14:
|
|
346
|
-
|
|
364
|
+
_b.trys.push([14, 16, , 17]);
|
|
347
365
|
return [4 /*yield*/, options.postCreate(data, req)];
|
|
348
366
|
case 15:
|
|
349
|
-
|
|
367
|
+
_b.sent();
|
|
350
368
|
return [3 /*break*/, 17];
|
|
351
369
|
case 16:
|
|
352
|
-
error_4 =
|
|
370
|
+
error_4 = _b.sent();
|
|
353
371
|
throw new errors_1.APIError({
|
|
354
372
|
disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_4),
|
|
355
373
|
error: error_4,
|
|
@@ -357,13 +375,13 @@ function modelRouter(model, options) {
|
|
|
357
375
|
title: "postCreate hook error: ".concat(error_4.message),
|
|
358
376
|
});
|
|
359
377
|
case 17:
|
|
360
|
-
|
|
378
|
+
_b.trys.push([17, 19, , 20]);
|
|
361
379
|
return [4 /*yield*/, responseHandler(data, "create", req, options)];
|
|
362
380
|
case 18:
|
|
363
|
-
serialized =
|
|
381
|
+
serialized = _b.sent();
|
|
364
382
|
return [2 /*return*/, res.status(201).json({ data: serialized })];
|
|
365
383
|
case 19:
|
|
366
|
-
error_5 =
|
|
384
|
+
error_5 = _b.sent();
|
|
367
385
|
throw new errors_1.APIError({
|
|
368
386
|
disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_5),
|
|
369
387
|
error: error_5,
|
|
@@ -376,15 +394,16 @@ function modelRouter(model, options) {
|
|
|
376
394
|
// TODO add rate limit
|
|
377
395
|
router.get("/", [
|
|
378
396
|
(0, auth_1.authenticateMiddleware)(options.allowAnonymous),
|
|
379
|
-
(0, permissions_1.permissionMiddleware)(
|
|
380
|
-
(0, openApi_1.listOpenApiMiddleware)(
|
|
397
|
+
(0, permissions_1.permissionMiddleware)(baseModel, options),
|
|
398
|
+
(0, openApi_1.listOpenApiMiddleware)(baseModel, options),
|
|
381
399
|
], (0, exports.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
|
|
382
|
-
var query, _a, _b, queryParam, _c, _d, queryParam, queryFilter, error_6, limit, builtQuery, total, populatedQuery, data, error_7, serialized, error_8, more, msg;
|
|
400
|
+
var model, query, _a, _b, queryParam, _c, _d, queryParam, queryFilter, error_6, limit, builtQuery, total, populatedQuery, data, error_7, serialized, error_8, more, msg;
|
|
383
401
|
var e_4, _e, e_5, _f;
|
|
384
402
|
var _g, _h, _j, _k, _l, _m;
|
|
385
403
|
return __generator(this, function (_o) {
|
|
386
404
|
switch (_o.label) {
|
|
387
405
|
case 0:
|
|
406
|
+
model = baseModel;
|
|
388
407
|
query = {};
|
|
389
408
|
try {
|
|
390
409
|
for (_a = __values(Object.keys((_g = options.defaultQueryParams) !== null && _g !== void 0 ? _g : [])), _b = _a.next(); !_b.done; _b = _a.next()) {
|
|
@@ -556,8 +575,8 @@ function modelRouter(model, options) {
|
|
|
556
575
|
}); }));
|
|
557
576
|
router.get("/:id", [
|
|
558
577
|
(0, auth_1.authenticateMiddleware)(options.allowAnonymous),
|
|
559
|
-
(0, openApi_1.getOpenApiMiddleware)(
|
|
560
|
-
(0, permissions_1.permissionMiddleware)(
|
|
578
|
+
(0, openApi_1.getOpenApiMiddleware)(baseModel, options),
|
|
579
|
+
(0, permissions_1.permissionMiddleware)(baseModel, options),
|
|
561
580
|
], (0, exports.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
|
|
562
581
|
var data, serialized, error_9;
|
|
563
582
|
return __generator(this, function (_a) {
|
|
@@ -592,14 +611,15 @@ function modelRouter(model, options) {
|
|
|
592
611
|
}); }));
|
|
593
612
|
router.patch("/:id", [
|
|
594
613
|
(0, auth_1.authenticateMiddleware)(options.allowAnonymous),
|
|
595
|
-
(0, openApi_1.patchOpenApiMiddleware)(
|
|
596
|
-
(0, permissions_1.permissionMiddleware)(
|
|
614
|
+
(0, openApi_1.patchOpenApiMiddleware)(baseModel, options),
|
|
615
|
+
(0, permissions_1.permissionMiddleware)(baseModel, options),
|
|
597
616
|
], (0, exports.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
|
|
598
|
-
var doc, body, error_10, prevDoc, error_11, populateQuery, error_12, serialized, error_13;
|
|
617
|
+
var model, doc, body, error_10, prevDoc, error_11, populateQuery, error_12, serialized, error_13;
|
|
599
618
|
var _a;
|
|
600
619
|
return __generator(this, function (_b) {
|
|
601
620
|
switch (_b.label) {
|
|
602
621
|
case 0:
|
|
622
|
+
model = getModel(baseModel, req.body, options);
|
|
603
623
|
doc = req.obj;
|
|
604
624
|
try {
|
|
605
625
|
body = (0, transformers_1.transform)(options, req.body, "update", req.user);
|
|
@@ -713,13 +733,14 @@ function modelRouter(model, options) {
|
|
|
713
733
|
}); }));
|
|
714
734
|
router.delete("/:id", [
|
|
715
735
|
(0, auth_1.authenticateMiddleware)(options.allowAnonymous),
|
|
716
|
-
(0, openApi_1.deleteOpenApiMiddleware)(
|
|
717
|
-
(0, permissions_1.permissionMiddleware)(
|
|
736
|
+
(0, openApi_1.deleteOpenApiMiddleware)(baseModel, options),
|
|
737
|
+
(0, permissions_1.permissionMiddleware)(baseModel, options),
|
|
718
738
|
], (0, exports.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
|
|
719
|
-
var doc, body, error_14, error_15, error_16;
|
|
739
|
+
var model, doc, body, error_14, error_15, error_16;
|
|
720
740
|
return __generator(this, function (_a) {
|
|
721
741
|
switch (_a.label) {
|
|
722
742
|
case 0:
|
|
743
|
+
model = getModel(baseModel, req.body, options);
|
|
723
744
|
doc = req.obj;
|
|
724
745
|
if (!options.preDelete) return [3 /*break*/, 5];
|
|
725
746
|
body = void 0;
|
|
@@ -802,14 +823,15 @@ function modelRouter(model, options) {
|
|
|
802
823
|
}); }));
|
|
803
824
|
function arrayOperation(req, res, operation) {
|
|
804
825
|
return __awaiter(this, void 0, void 0, function () {
|
|
805
|
-
var doc, prevDoc, field, array, index, body, error_17, error_18, error_19;
|
|
826
|
+
var model, doc, prevDoc, field, array, index, body, error_17, error_18, error_19;
|
|
806
827
|
var _a;
|
|
807
828
|
var _b, _c;
|
|
808
829
|
return __generator(this, function (_d) {
|
|
809
830
|
switch (_d.label) {
|
|
810
|
-
case 0:
|
|
831
|
+
case 0:
|
|
832
|
+
model = getModel(baseModel, req.body, options);
|
|
833
|
+
return [4 /*yield*/, (0, permissions_1.checkPermissions)("update", options.permissions.update, req.user)];
|
|
811
834
|
case 1:
|
|
812
|
-
// TODO Combine array operations and .patch(), as they are very similar.
|
|
813
835
|
if (!(_d.sent())) {
|
|
814
836
|
throw new errors_1.APIError({
|
|
815
837
|
status: 405,
|
|
@@ -820,7 +842,9 @@ function modelRouter(model, options) {
|
|
|
820
842
|
case 2:
|
|
821
843
|
doc = _d.sent();
|
|
822
844
|
prevDoc = (0, cloneDeep_1.default)(doc);
|
|
823
|
-
|
|
845
|
+
// We fail here because we might fetch the document without the __t but we'd be missing all the
|
|
846
|
+
// hooks.
|
|
847
|
+
if (!doc || (doc.__t && !req.body.__t)) {
|
|
824
848
|
throw new errors_1.APIError({
|
|
825
849
|
status: 404,
|
|
826
850
|
title: "Could not find document to PATCH: ".concat(req.params.id),
|
|
@@ -983,7 +1007,7 @@ function modelRouter(model, options) {
|
|
|
983
1007
|
});
|
|
984
1008
|
}
|
|
985
1009
|
// Set up routes for managing array fields. Check if there any array fields to add this for.
|
|
986
|
-
if (Object.values(
|
|
1010
|
+
if (Object.values(baseModel.schema.paths).find(function (config) { return config.instance === "Array"; })) {
|
|
987
1011
|
router.post("/:id/:field", (0, auth_1.authenticateMiddleware)(options.allowAnonymous), (0, exports.asyncHandler)(arrayPost));
|
|
988
1012
|
router.patch("/:id/:field/:itemId", (0, auth_1.authenticateMiddleware)(options.allowAnonymous), (0, exports.asyncHandler)(arrayPatch));
|
|
989
1013
|
router.delete("/:id/:field/:itemId", (0, auth_1.authenticateMiddleware)(options.allowAnonymous), (0, exports.asyncHandler)(arrayDelete));
|