@terreno/api 0.20.1 → 0.21.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/.ai/guidelines/core.md +71 -0
- package/.ai/skills/mongoose-schema-safety/SKILL.md +143 -0
- package/README.md +54 -1
- package/dist/__tests__/versionCheckPlugin.test.js +29 -7
- package/dist/actions.openApi.test.js +13 -11
- package/dist/api.js +98 -11
- package/dist/api.query.test.js +31 -1
- package/dist/api.test.js +211 -0
- package/dist/auth.test.js +10 -10
- package/dist/betterAuth.d.ts +1 -1
- package/dist/consentApp.test.js +1 -0
- package/dist/example.js +4 -4
- package/dist/expressServer.d.ts +0 -22
- package/dist/expressServer.js +1 -125
- package/dist/expressServer.test.js +90 -91
- package/dist/githubAuth.test.js +22 -22
- package/dist/logger.d.ts +154 -0
- package/dist/logger.js +445 -26
- package/dist/logger.test.js +435 -0
- package/dist/middleware.d.ts +7 -0
- package/dist/middleware.js +58 -1
- package/dist/middleware.test.js +159 -0
- package/dist/openApi.test.js +10 -17
- package/dist/openApiBuilder.test.js +18 -10
- package/dist/realtime/changeStreamWatcher.d.ts +4 -4
- package/dist/realtime/changeStreamWatcher.js +2 -4
- package/dist/realtime/queryMatcher.d.ts +1 -1
- package/dist/realtime/queryMatcher.js +39 -14
- package/dist/realtime/types.d.ts +3 -3
- package/dist/requestContext.d.ts +61 -0
- package/dist/requestContext.js +74 -0
- package/dist/secretProviders.test.js +335 -0
- package/dist/terrenoApp.d.ts +27 -15
- package/dist/terrenoApp.js +24 -14
- package/dist/terrenoApp.test.js +52 -0
- package/dist/tests/bunSetup.js +61 -7
- package/dist/tests.js +27 -4
- package/package.json +1 -1
- package/src/__tests__/versionCheckPlugin.test.ts +43 -15
- package/src/actions.openApi.test.ts +12 -10
- package/src/api.query.test.ts +24 -1
- package/src/api.test.ts +169 -0
- package/src/api.ts +71 -0
- package/src/auth.test.ts +10 -10
- package/src/betterAuth.ts +1 -1
- package/src/consentApp.test.ts +1 -0
- package/src/example.ts +4 -4
- package/src/expressServer.test.ts +82 -85
- package/src/expressServer.ts +1 -213
- package/src/githubAuth.test.ts +22 -22
- package/src/logger.test.ts +466 -1
- package/src/logger.ts +477 -14
- package/src/middleware.test.ts +74 -2
- package/src/middleware.ts +57 -0
- package/src/openApi.test.ts +10 -17
- package/src/openApiBuilder.test.ts +18 -10
- package/src/realtime/changeStreamWatcher.ts +15 -10
- package/src/realtime/queryMatcher.ts +54 -27
- package/src/realtime/types.ts +4 -4
- package/src/requestContext.ts +86 -0
- package/src/secretProviders.test.ts +219 -1
- package/src/terrenoApp.test.ts +38 -0
- package/src/terrenoApp.ts +37 -15
- package/src/tests/bunSetup.ts +16 -3
- package/src/tests.ts +17 -4
package/dist/api.js
CHANGED
|
@@ -173,9 +173,95 @@ exports.addPopulateToQuery = addPopulateToQuery;
|
|
|
173
173
|
var PAGINATION_QUERY_PARAMS = ["limit", "page", "sort"];
|
|
174
174
|
// Add support for more complex queries.
|
|
175
175
|
var COMPLEX_QUERY_PARAMS = ["$and", "$or"];
|
|
176
|
+
/**
|
|
177
|
+
* Parses a date-range query bound from an ISO-8601 string using Luxon, throwing a 400
|
|
178
|
+
* APIError when the value cannot be parsed. Centralizes date-string parsing so the repo's
|
|
179
|
+
* Luxon convention is honored for admin changelist date-range filters.
|
|
180
|
+
*/
|
|
181
|
+
var parseDateRangeBound = function (rawValue, queryKey) {
|
|
182
|
+
var parsed = luxon_1.DateTime.fromISO(String(rawValue), { zone: "utc" });
|
|
183
|
+
if (!parsed.isValid) {
|
|
184
|
+
throw new errors_1.APIError({
|
|
185
|
+
status: 400,
|
|
186
|
+
title: "Invalid date for query parameter ".concat(queryKey),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
return parsed.toJSDate();
|
|
190
|
+
};
|
|
191
|
+
/**
|
|
192
|
+
* Collapses `field_gte` / `field_lte` query pairs into `{ field: { $gte, $lte } }` for Date paths,
|
|
193
|
+
* so admin changelist date-range filters map to valid Mongoose range queries.
|
|
194
|
+
*/
|
|
195
|
+
var mergeDateRangeQueryParams = function (model, query) {
|
|
196
|
+
var e_2, _a, e_3, _b;
|
|
197
|
+
var schema = model.schema;
|
|
198
|
+
var result = __assign({}, query);
|
|
199
|
+
var dateRangeBases = new Set();
|
|
200
|
+
try {
|
|
201
|
+
for (var _c = __values(Object.keys(result)), _d = _c.next(); !_d.done; _d = _c.next()) {
|
|
202
|
+
var key = _d.value;
|
|
203
|
+
var match = /^(.+)_(gte|lte)$/.exec(key);
|
|
204
|
+
if (!match) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
var baseField = match[1];
|
|
208
|
+
var path = schema.path(baseField);
|
|
209
|
+
if (!path || path.instance !== "Date") {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
dateRangeBases.add(baseField);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
216
|
+
finally {
|
|
217
|
+
try {
|
|
218
|
+
if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
|
|
219
|
+
}
|
|
220
|
+
finally { if (e_2) throw e_2.error; }
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
for (var dateRangeBases_1 = __values(dateRangeBases), dateRangeBases_1_1 = dateRangeBases_1.next(); !dateRangeBases_1_1.done; dateRangeBases_1_1 = dateRangeBases_1.next()) {
|
|
224
|
+
var baseField = dateRangeBases_1_1.value;
|
|
225
|
+
var gteKey = "".concat(baseField, "_gte");
|
|
226
|
+
var lteKey = "".concat(baseField, "_lte");
|
|
227
|
+
var gteRaw = result[gteKey];
|
|
228
|
+
var lteRaw = result[lteKey];
|
|
229
|
+
var bounds = {};
|
|
230
|
+
if (gteRaw !== undefined && gteRaw !== null && String(gteRaw).trim() !== "") {
|
|
231
|
+
bounds.$gte = parseDateRangeBound(gteRaw, gteKey);
|
|
232
|
+
}
|
|
233
|
+
if (lteRaw !== undefined && lteRaw !== null && String(lteRaw).trim() !== "") {
|
|
234
|
+
bounds.$lte = parseDateRangeBound(lteRaw, lteKey);
|
|
235
|
+
}
|
|
236
|
+
if (Object.keys(bounds).length === 0) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
delete result[gteKey];
|
|
240
|
+
delete result[lteKey];
|
|
241
|
+
var direct = result[baseField];
|
|
242
|
+
if (direct !== undefined && direct !== null && typeof direct !== "object") {
|
|
243
|
+
delete result[baseField];
|
|
244
|
+
}
|
|
245
|
+
if (typeof direct === "object" && direct !== null && !Array.isArray(direct)) {
|
|
246
|
+
result[baseField] = __assign(__assign({}, direct), bounds);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
result[baseField] = bounds;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch (e_3_1) { e_3 = { error: e_3_1 }; }
|
|
254
|
+
finally {
|
|
255
|
+
try {
|
|
256
|
+
if (dateRangeBases_1_1 && !dateRangeBases_1_1.done && (_b = dateRangeBases_1.return)) _b.call(dateRangeBases_1);
|
|
257
|
+
}
|
|
258
|
+
finally { if (e_3) throw e_3.error; }
|
|
259
|
+
}
|
|
260
|
+
return result;
|
|
261
|
+
};
|
|
176
262
|
// Ensures query params are allowed. Also checks nested query params when using $and/$or.
|
|
177
263
|
var checkQueryParamAllowed = function (queryParam, queryParamValue, queryFields) {
|
|
178
|
-
var
|
|
264
|
+
var e_4, _a, e_5, _b;
|
|
179
265
|
if (queryFields === void 0) { queryFields = []; }
|
|
180
266
|
// Cast for iteration through complex query values
|
|
181
267
|
var complexValue = queryParamValue;
|
|
@@ -187,26 +273,26 @@ var checkQueryParamAllowed = function (queryParam, queryParamValue, queryFields)
|
|
|
187
273
|
for (var complexValue_1 = __values(complexValue), complexValue_1_1 = complexValue_1.next(); !complexValue_1_1.done; complexValue_1_1 = complexValue_1.next()) {
|
|
188
274
|
var subQuery = complexValue_1_1.value;
|
|
189
275
|
try {
|
|
190
|
-
for (var _c = (
|
|
276
|
+
for (var _c = (e_5 = void 0, __values(Object.keys(subQuery))), _d = _c.next(); !_d.done; _d = _c.next()) {
|
|
191
277
|
var subKey = _d.value;
|
|
192
278
|
checkQueryParamAllowed(subKey, subQuery[subKey], queryFields);
|
|
193
279
|
}
|
|
194
280
|
}
|
|
195
|
-
catch (
|
|
281
|
+
catch (e_5_1) { e_5 = { error: e_5_1 }; }
|
|
196
282
|
finally {
|
|
197
283
|
try {
|
|
198
284
|
if (_d && !_d.done && (_b = _c.return)) _b.call(_c);
|
|
199
285
|
}
|
|
200
|
-
finally { if (
|
|
286
|
+
finally { if (e_5) throw e_5.error; }
|
|
201
287
|
}
|
|
202
288
|
}
|
|
203
289
|
}
|
|
204
|
-
catch (
|
|
290
|
+
catch (e_4_1) { e_4 = { error: e_4_1 }; }
|
|
205
291
|
finally {
|
|
206
292
|
try {
|
|
207
293
|
if (complexValue_1_1 && !complexValue_1_1.done && (_a = complexValue_1.return)) _a.call(complexValue_1);
|
|
208
294
|
}
|
|
209
|
-
finally { if (
|
|
295
|
+
finally { if (e_4) throw e_4.error; }
|
|
210
296
|
}
|
|
211
297
|
return;
|
|
212
298
|
}
|
|
@@ -489,7 +575,7 @@ function _buildModelRouter(model, options) {
|
|
|
489
575
|
queryValidation,
|
|
490
576
|
], (0, exports.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
|
|
491
577
|
var query, _a, _b, queryParam, _c, _d, queryParam, queryFilter, error_6, limit, builtQuery, total, populatedQuery, data, error_7, serialized, error_8, more, msg;
|
|
492
|
-
var
|
|
578
|
+
var e_6, _e, e_7, _f;
|
|
493
579
|
var _g, _h, _j, _k, _l, _m;
|
|
494
580
|
return __generator(this, function (_o) {
|
|
495
581
|
switch (_o.label) {
|
|
@@ -501,12 +587,12 @@ function _buildModelRouter(model, options) {
|
|
|
501
587
|
query[queryParam] = (_h = options.defaultQueryParams) === null || _h === void 0 ? void 0 : _h[queryParam];
|
|
502
588
|
}
|
|
503
589
|
}
|
|
504
|
-
catch (
|
|
590
|
+
catch (e_6_1) { e_6 = { error: e_6_1 }; }
|
|
505
591
|
finally {
|
|
506
592
|
try {
|
|
507
593
|
if (_b && !_b.done && (_e = _a.return)) _e.call(_a);
|
|
508
594
|
}
|
|
509
|
-
finally { if (
|
|
595
|
+
finally { if (e_6) throw e_6.error; }
|
|
510
596
|
}
|
|
511
597
|
try {
|
|
512
598
|
for (_c = __values(Object.keys(req.query)), _d = _c.next(); !_d.done; _d = _c.next()) {
|
|
@@ -527,13 +613,14 @@ function _buildModelRouter(model, options) {
|
|
|
527
613
|
}
|
|
528
614
|
}
|
|
529
615
|
}
|
|
530
|
-
catch (
|
|
616
|
+
catch (e_7_1) { e_7 = { error: e_7_1 }; }
|
|
531
617
|
finally {
|
|
532
618
|
try {
|
|
533
619
|
if (_d && !_d.done && (_f = _c.return)) _f.call(_c);
|
|
534
620
|
}
|
|
535
|
-
finally { if (
|
|
621
|
+
finally { if (e_7) throw e_7.error; }
|
|
536
622
|
}
|
|
623
|
+
query = mergeDateRangeQueryParams(model, query);
|
|
537
624
|
// Special operators. NOTE: these request Mongo Atlas.
|
|
538
625
|
if (req.query.$search) {
|
|
539
626
|
(_j = mongoose_1.default.connection.db) === null || _j === void 0 ? void 0 : _j.collection(model.collection.collectionName);
|
package/dist/api.query.test.js
CHANGED
|
@@ -184,7 +184,17 @@ var tests_1 = require("./tests");
|
|
|
184
184
|
update: [permissions_1.Permissions.IsOwner],
|
|
185
185
|
},
|
|
186
186
|
populatePaths: [{ path: "ownerId" }],
|
|
187
|
-
queryFields: [
|
|
187
|
+
queryFields: [
|
|
188
|
+
"hidden",
|
|
189
|
+
"name",
|
|
190
|
+
"calories",
|
|
191
|
+
"created",
|
|
192
|
+
"created_gte",
|
|
193
|
+
"created_lte",
|
|
194
|
+
"source.name",
|
|
195
|
+
"tags",
|
|
196
|
+
"eatenBy",
|
|
197
|
+
],
|
|
188
198
|
sort: { created: "descending" },
|
|
189
199
|
}));
|
|
190
200
|
server = (0, supertest_1.default)(app);
|
|
@@ -344,6 +354,26 @@ var tests_1 = require("./tests");
|
|
|
344
354
|
}
|
|
345
355
|
});
|
|
346
356
|
}); });
|
|
357
|
+
(0, bun_test_1.it)("list applies created_gte and created_lte as a Date range", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
358
|
+
var res, names;
|
|
359
|
+
return __generator(this, function (_a) {
|
|
360
|
+
switch (_a.label) {
|
|
361
|
+
case 0: return [4 /*yield*/, agent
|
|
362
|
+
.get("/food")
|
|
363
|
+
.query({
|
|
364
|
+
created_gte: "2021-12-03T00:00:05.000Z",
|
|
365
|
+
created_lte: "2021-12-03T00:00:25.000Z",
|
|
366
|
+
limit: 10,
|
|
367
|
+
})
|
|
368
|
+
.expect(200)];
|
|
369
|
+
case 1:
|
|
370
|
+
res = _a.sent();
|
|
371
|
+
names = res.body.data.map(function (d) { return d.name; }).sort();
|
|
372
|
+
(0, bun_test_1.expect)(names).toEqual(["Pizza", "Spinach"]);
|
|
373
|
+
return [2 /*return*/];
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
}); });
|
|
347
377
|
(0, bun_test_1.it)("list query params not in list", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
348
378
|
var res;
|
|
349
379
|
return __generator(this, function (_a) {
|
package/dist/api.test.js
CHANGED
|
@@ -2588,5 +2588,216 @@ var transformers_1 = require("./transformers");
|
|
|
2588
2588
|
}
|
|
2589
2589
|
});
|
|
2590
2590
|
}); });
|
|
2591
|
+
(0, bun_test_1.it)("returns 400 when X-Unmodified-Since-ISO header is an invalid date", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2592
|
+
var res;
|
|
2593
|
+
return __generator(this, function (_a) {
|
|
2594
|
+
switch (_a.label) {
|
|
2595
|
+
case 0: return [4 /*yield*/, agent
|
|
2596
|
+
.patch("/food/".concat(spinach._id))
|
|
2597
|
+
.set("X-Unmodified-Since-ISO", "not-a-date")
|
|
2598
|
+
.send({ name: "Bad Date" })
|
|
2599
|
+
.expect(400)];
|
|
2600
|
+
case 1:
|
|
2601
|
+
res = _a.sent();
|
|
2602
|
+
(0, bun_test_1.expect)(res.body.title).toBe("Invalid conflict-detection timestamp");
|
|
2603
|
+
(0, bun_test_1.expect)(res.body.detail).toContain("X-Unmodified-Since-ISO");
|
|
2604
|
+
return [2 /*return*/];
|
|
2605
|
+
}
|
|
2606
|
+
});
|
|
2607
|
+
}); });
|
|
2608
|
+
(0, bun_test_1.it)("returns 400 when If-Unmodified-Since header is an invalid HTTP date", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2609
|
+
var res;
|
|
2610
|
+
return __generator(this, function (_a) {
|
|
2611
|
+
switch (_a.label) {
|
|
2612
|
+
case 0: return [4 /*yield*/, agent
|
|
2613
|
+
.patch("/food/".concat(spinach._id))
|
|
2614
|
+
.set("If-Unmodified-Since", "not-a-valid-http-date")
|
|
2615
|
+
.send({ name: "Bad HTTP Date" })
|
|
2616
|
+
.expect(400)];
|
|
2617
|
+
case 1:
|
|
2618
|
+
res = _a.sent();
|
|
2619
|
+
(0, bun_test_1.expect)(res.body.title).toBe("Invalid conflict-detection timestamp");
|
|
2620
|
+
(0, bun_test_1.expect)(res.body.detail).toContain("If-Unmodified-Since");
|
|
2621
|
+
return [2 /*return*/];
|
|
2622
|
+
}
|
|
2623
|
+
});
|
|
2624
|
+
}); });
|
|
2625
|
+
(0, bun_test_1.it)("returns 400 when _updatedAt body field is an invalid date", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2626
|
+
var res;
|
|
2627
|
+
return __generator(this, function (_a) {
|
|
2628
|
+
switch (_a.label) {
|
|
2629
|
+
case 0: return [4 /*yield*/, agent
|
|
2630
|
+
.patch("/food/".concat(spinach._id))
|
|
2631
|
+
.send({ _updatedAt: "garbage-date", name: "Bad Body Date" })
|
|
2632
|
+
.expect(400)];
|
|
2633
|
+
case 1:
|
|
2634
|
+
res = _a.sent();
|
|
2635
|
+
(0, bun_test_1.expect)(res.body.title).toBe("Invalid conflict-detection timestamp");
|
|
2636
|
+
(0, bun_test_1.expect)(res.body.detail).toContain("_updatedAt");
|
|
2637
|
+
return [2 /*return*/];
|
|
2638
|
+
}
|
|
2639
|
+
});
|
|
2640
|
+
}); });
|
|
2641
|
+
(0, bun_test_1.it)("handles doc timestamp stored as ISO string", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2642
|
+
var res;
|
|
2643
|
+
return __generator(this, function (_a) {
|
|
2644
|
+
switch (_a.label) {
|
|
2645
|
+
case 0: return [4 /*yield*/, tests_1.FoodModel.collection.updateOne({ _id: spinach._id }, { $set: { updated: "2025-06-15T12:00:00.000Z" } })];
|
|
2646
|
+
case 1:
|
|
2647
|
+
_a.sent();
|
|
2648
|
+
return [4 /*yield*/, agent
|
|
2649
|
+
.patch("/food/".concat(spinach._id))
|
|
2650
|
+
.set("If-Unmodified-Since", luxon_1.DateTime.fromISO("2025-06-15T11:00:00.000Z").toHTTP())
|
|
2651
|
+
.send({ name: "String Timestamp" })
|
|
2652
|
+
.expect(409)];
|
|
2653
|
+
case 2:
|
|
2654
|
+
res = _a.sent();
|
|
2655
|
+
(0, bun_test_1.expect)(res.body.error).toBe("Conflict");
|
|
2656
|
+
return [2 /*return*/];
|
|
2657
|
+
}
|
|
2658
|
+
});
|
|
2659
|
+
}); });
|
|
2660
|
+
});
|
|
2661
|
+
(0, bun_test_1.describe)("three-arg modelRouter registration", function () {
|
|
2662
|
+
(0, bun_test_1.it)("returns a ModelRouterRegistration with path and realtime", function () {
|
|
2663
|
+
var registration = (0, api_1.modelRouter)("/food", tests_1.FoodModel, {
|
|
2664
|
+
permissions: {
|
|
2665
|
+
create: [permissions_1.Permissions.IsAny],
|
|
2666
|
+
delete: [permissions_1.Permissions.IsAny],
|
|
2667
|
+
list: [permissions_1.Permissions.IsAny],
|
|
2668
|
+
read: [permissions_1.Permissions.IsAny],
|
|
2669
|
+
update: [permissions_1.Permissions.IsAny],
|
|
2670
|
+
},
|
|
2671
|
+
realtime: { methods: ["create", "update", "delete"], roomStrategy: "model" },
|
|
2672
|
+
});
|
|
2673
|
+
(0, bun_test_1.expect)(registration).toHaveProperty("__type", "modelRouter");
|
|
2674
|
+
(0, bun_test_1.expect)(registration).toHaveProperty("path", "/food");
|
|
2675
|
+
(0, bun_test_1.expect)(registration).toHaveProperty("router");
|
|
2676
|
+
(0, bun_test_1.expect)(registration).toHaveProperty("_buildWithOpenApi");
|
|
2677
|
+
});
|
|
2678
|
+
(0, bun_test_1.it)("logs a warning when realtime config is used without the path form", function () {
|
|
2679
|
+
var router = (0, api_1.modelRouter)(tests_1.FoodModel, {
|
|
2680
|
+
permissions: {
|
|
2681
|
+
create: [permissions_1.Permissions.IsAny],
|
|
2682
|
+
delete: [permissions_1.Permissions.IsAny],
|
|
2683
|
+
list: [permissions_1.Permissions.IsAny],
|
|
2684
|
+
read: [permissions_1.Permissions.IsAny],
|
|
2685
|
+
update: [permissions_1.Permissions.IsAny],
|
|
2686
|
+
},
|
|
2687
|
+
realtime: { methods: ["create", "update", "delete"], roomStrategy: "model" },
|
|
2688
|
+
});
|
|
2689
|
+
// Returns a plain Router, not a registration, because no path was given
|
|
2690
|
+
(0, bun_test_1.expect)(router).toBeDefined();
|
|
2691
|
+
(0, bun_test_1.expect)(router).not.toHaveProperty("__type");
|
|
2692
|
+
});
|
|
2693
|
+
});
|
|
2694
|
+
(0, bun_test_1.describe)("create transform error wrapping", function () {
|
|
2695
|
+
var admin;
|
|
2696
|
+
var agent;
|
|
2697
|
+
(0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2698
|
+
var _a;
|
|
2699
|
+
return __generator(this, function (_b) {
|
|
2700
|
+
switch (_b.label) {
|
|
2701
|
+
case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
|
|
2702
|
+
case 1:
|
|
2703
|
+
_a = __read.apply(void 0, [_b.sent(), 1]), admin = _a[0];
|
|
2704
|
+
app = (0, tests_1.getBaseServer)();
|
|
2705
|
+
(0, auth_1.setupAuth)(app, tests_1.UserModel);
|
|
2706
|
+
(0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
|
|
2707
|
+
app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
|
|
2708
|
+
permissions: {
|
|
2709
|
+
create: [permissions_1.Permissions.IsAny],
|
|
2710
|
+
delete: [permissions_1.Permissions.IsAny],
|
|
2711
|
+
list: [permissions_1.Permissions.IsAny],
|
|
2712
|
+
read: [permissions_1.Permissions.IsAny],
|
|
2713
|
+
update: [permissions_1.Permissions.IsAny],
|
|
2714
|
+
},
|
|
2715
|
+
transformer: {
|
|
2716
|
+
transform: function () {
|
|
2717
|
+
throw new Error("generic transform error");
|
|
2718
|
+
},
|
|
2719
|
+
},
|
|
2720
|
+
}));
|
|
2721
|
+
server = (0, supertest_1.default)(app);
|
|
2722
|
+
return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
|
|
2723
|
+
case 2:
|
|
2724
|
+
agent = _b.sent();
|
|
2725
|
+
return [2 /*return*/];
|
|
2726
|
+
}
|
|
2727
|
+
});
|
|
2728
|
+
}); });
|
|
2729
|
+
(0, bun_test_1.it)("wraps non-APIError transform errors in a 400 APIError on create", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2730
|
+
var res;
|
|
2731
|
+
return __generator(this, function (_a) {
|
|
2732
|
+
switch (_a.label) {
|
|
2733
|
+
case 0: return [4 /*yield*/, agent
|
|
2734
|
+
.post("/food")
|
|
2735
|
+
.send({ calories: 10, name: "New Food", ownerId: admin._id })
|
|
2736
|
+
.expect(400)];
|
|
2737
|
+
case 1:
|
|
2738
|
+
res = _a.sent();
|
|
2739
|
+
(0, bun_test_1.expect)(res.body.title).toContain("generic transform error");
|
|
2740
|
+
return [2 /*return*/];
|
|
2741
|
+
}
|
|
2742
|
+
});
|
|
2743
|
+
}); });
|
|
2744
|
+
});
|
|
2745
|
+
(0, bun_test_1.describe)("update transform error wrapping", function () {
|
|
2746
|
+
var admin;
|
|
2747
|
+
var agent;
|
|
2748
|
+
var spinach;
|
|
2749
|
+
(0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2750
|
+
var _a;
|
|
2751
|
+
return __generator(this, function (_b) {
|
|
2752
|
+
switch (_b.label) {
|
|
2753
|
+
case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
|
|
2754
|
+
case 1:
|
|
2755
|
+
_a = __read.apply(void 0, [_b.sent(), 1]), admin = _a[0];
|
|
2756
|
+
return [4 /*yield*/, tests_1.FoodModel.create({
|
|
2757
|
+
calories: 10,
|
|
2758
|
+
created: new Date(),
|
|
2759
|
+
hidden: false,
|
|
2760
|
+
name: "Spinach",
|
|
2761
|
+
ownerId: admin._id,
|
|
2762
|
+
})];
|
|
2763
|
+
case 2:
|
|
2764
|
+
spinach = _b.sent();
|
|
2765
|
+
app = (0, tests_1.getBaseServer)();
|
|
2766
|
+
(0, auth_1.setupAuth)(app, tests_1.UserModel);
|
|
2767
|
+
(0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
|
|
2768
|
+
app.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
|
|
2769
|
+
permissions: {
|
|
2770
|
+
create: [permissions_1.Permissions.IsAny],
|
|
2771
|
+
delete: [permissions_1.Permissions.IsAny],
|
|
2772
|
+
list: [permissions_1.Permissions.IsAny],
|
|
2773
|
+
read: [permissions_1.Permissions.IsAny],
|
|
2774
|
+
update: [permissions_1.Permissions.IsAny],
|
|
2775
|
+
},
|
|
2776
|
+
transformer: {
|
|
2777
|
+
transform: function () {
|
|
2778
|
+
throw new Error("update transform error");
|
|
2779
|
+
},
|
|
2780
|
+
},
|
|
2781
|
+
}));
|
|
2782
|
+
server = (0, supertest_1.default)(app);
|
|
2783
|
+
return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
|
|
2784
|
+
case 3:
|
|
2785
|
+
agent = _b.sent();
|
|
2786
|
+
return [2 /*return*/];
|
|
2787
|
+
}
|
|
2788
|
+
});
|
|
2789
|
+
}); });
|
|
2790
|
+
(0, bun_test_1.it)("wraps non-APIError transform errors in a 403 APIError on update", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2791
|
+
var res;
|
|
2792
|
+
return __generator(this, function (_a) {
|
|
2793
|
+
switch (_a.label) {
|
|
2794
|
+
case 0: return [4 /*yield*/, agent.patch("/food/".concat(spinach._id)).send({ name: "Updated" }).expect(403)];
|
|
2795
|
+
case 1:
|
|
2796
|
+
res = _a.sent();
|
|
2797
|
+
(0, bun_test_1.expect)(res.body.title).toContain("update transform error");
|
|
2798
|
+
return [2 /*return*/];
|
|
2799
|
+
}
|
|
2800
|
+
});
|
|
2801
|
+
}); });
|
|
2591
2802
|
});
|
|
2592
2803
|
});
|
package/dist/auth.test.js
CHANGED
|
@@ -104,9 +104,9 @@ var bun_test_1 = require("bun:test");
|
|
|
104
104
|
var supertest_1 = __importDefault(require("supertest"));
|
|
105
105
|
var api_1 = require("./api");
|
|
106
106
|
var auth_1 = require("./auth");
|
|
107
|
-
var expressServer_1 = require("./expressServer");
|
|
108
107
|
var permissions_1 = require("./permissions");
|
|
109
108
|
var requestContext_1 = require("./requestContext");
|
|
109
|
+
var terrenoApp_1 = require("./terrenoApp");
|
|
110
110
|
var tests_1 = require("./tests");
|
|
111
111
|
var transformers_1 = require("./transformers");
|
|
112
112
|
var utils_1 = require("./utils");
|
|
@@ -236,11 +236,11 @@ var decodeTokenPayload = function (token) {
|
|
|
236
236
|
])];
|
|
237
237
|
case 2:
|
|
238
238
|
_b.sent();
|
|
239
|
-
app =
|
|
240
|
-
|
|
239
|
+
app = new terrenoApp_1.TerrenoApp({
|
|
240
|
+
configureApp: addRoutes,
|
|
241
241
|
skipListen: true,
|
|
242
242
|
userModel: tests_1.UserModel,
|
|
243
|
-
});
|
|
243
|
+
}).build();
|
|
244
244
|
agent = supertest_1.default.agent(app);
|
|
245
245
|
return [2 /*return*/];
|
|
246
246
|
}
|
|
@@ -1158,11 +1158,11 @@ var decodeTokenPayload = function (token) {
|
|
|
1158
1158
|
return [4 /*yield*/, (0, tests_1.setupDb)()];
|
|
1159
1159
|
case 1:
|
|
1160
1160
|
_a.sent();
|
|
1161
|
-
app =
|
|
1162
|
-
|
|
1161
|
+
app = new terrenoApp_1.TerrenoApp({
|
|
1162
|
+
configureApp: function () { },
|
|
1163
1163
|
skipListen: true,
|
|
1164
1164
|
userModel: tests_1.UserModel,
|
|
1165
|
-
});
|
|
1165
|
+
}).build();
|
|
1166
1166
|
agent = supertest_1.default.agent(app);
|
|
1167
1167
|
return [2 /*return*/];
|
|
1168
1168
|
}
|
|
@@ -1268,11 +1268,11 @@ var decodeTokenPayload = function (token) {
|
|
|
1268
1268
|
return [4 /*yield*/, (0, tests_1.setupDb)()];
|
|
1269
1269
|
case 1:
|
|
1270
1270
|
_a.sent();
|
|
1271
|
-
app =
|
|
1272
|
-
|
|
1271
|
+
app = new terrenoApp_1.TerrenoApp({
|
|
1272
|
+
configureApp: function () { },
|
|
1273
1273
|
skipListen: true,
|
|
1274
1274
|
userModel: tests_1.UserModel,
|
|
1275
|
-
});
|
|
1275
|
+
}).build();
|
|
1276
1276
|
agent = supertest_1.default.agent(app);
|
|
1277
1277
|
return [2 /*return*/];
|
|
1278
1278
|
}
|
package/dist/betterAuth.d.ts
CHANGED
|
@@ -53,7 +53,7 @@ export interface BetterAuthConfig {
|
|
|
53
53
|
baseURL?: string;
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
|
-
* Auth provider selection for
|
|
56
|
+
* Auth provider selection for TerrenoApp.
|
|
57
57
|
* - "jwt": Traditional JWT/Passport authentication (default)
|
|
58
58
|
* - "better-auth": Better Auth with OAuth support
|
|
59
59
|
*/
|
package/dist/consentApp.test.js
CHANGED
|
@@ -118,6 +118,7 @@ var buildApp = function (consentAppOptions) {
|
|
|
118
118
|
case 1:
|
|
119
119
|
res = _a.sent();
|
|
120
120
|
(0, bun_test_1.expect)(res.body.data).toHaveLength(0);
|
|
121
|
+
(0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
|
|
121
122
|
return [2 /*return*/];
|
|
122
123
|
}
|
|
123
124
|
});
|
package/dist/example.js
CHANGED
|
@@ -52,10 +52,10 @@ var mongoose_1 = __importStar(require("mongoose"));
|
|
|
52
52
|
var passport_local_mongoose_1 = __importDefault(require("passport-local-mongoose"));
|
|
53
53
|
var api_1 = require("./api");
|
|
54
54
|
var auth_1 = require("./auth");
|
|
55
|
-
var expressServer_1 = require("./expressServer");
|
|
56
55
|
var logger_1 = require("./logger");
|
|
57
56
|
var permissions_1 = require("./permissions");
|
|
58
57
|
var plugins_1 = require("./plugins");
|
|
58
|
+
var terrenoApp_1 = require("./terrenoApp");
|
|
59
59
|
mongoose_1.default
|
|
60
60
|
.connect("mongodb://localhost:27017/example")
|
|
61
61
|
.then(function () {
|
|
@@ -119,12 +119,12 @@ var getBaseServer = function () {
|
|
|
119
119
|
update: [permissions_1.Permissions.IsOwner],
|
|
120
120
|
}, queryFields: ["name", "calories", "created", "ownerId", "hidden"] })));
|
|
121
121
|
};
|
|
122
|
-
return
|
|
123
|
-
|
|
122
|
+
return new terrenoApp_1.TerrenoApp({
|
|
123
|
+
configureApp: addRoutes,
|
|
124
124
|
loggingOptions: {
|
|
125
125
|
level: "debug",
|
|
126
126
|
},
|
|
127
127
|
userModel: UserModel,
|
|
128
|
-
});
|
|
128
|
+
}).build();
|
|
129
129
|
};
|
|
130
130
|
getBaseServer();
|
package/dist/expressServer.d.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import * as Sentry from "@sentry/bun";
|
|
2
1
|
import express, { type Router } from "express";
|
|
3
2
|
import type jwt from "jsonwebtoken";
|
|
4
3
|
import type { ModelRouterOptions } from "./api";
|
|
5
|
-
import { type UserModel as UserMongooseModel } from "./auth";
|
|
6
|
-
import { type GitHubAuthOptions } from "./githubAuth";
|
|
7
|
-
import { type LoggingOptions } from "./logger";
|
|
8
4
|
export declare const setupEnvironment: () => void;
|
|
9
5
|
export type AddRoutes = (router: Router, options?: Partial<ModelRouterOptions<unknown>>) => void;
|
|
10
6
|
export declare const logRequests: (req: any, res: any, next: express.NextFunction) => void;
|
|
@@ -15,24 +11,6 @@ export interface AuthOptions {
|
|
|
15
11
|
generateTokenExpiration?: (user: any) => number | jwt.SignOptions["expiresIn"];
|
|
16
12
|
generateRefreshTokenExpiration?: (user: any) => number | jwt.SignOptions["expiresIn"];
|
|
17
13
|
}
|
|
18
|
-
export interface SetupServerOptions {
|
|
19
|
-
userModel: UserMongooseModel;
|
|
20
|
-
addRoutes: AddRoutes;
|
|
21
|
-
loggingOptions?: LoggingOptions;
|
|
22
|
-
logRequests?: boolean;
|
|
23
|
-
authOptions?: AuthOptions;
|
|
24
|
-
/**
|
|
25
|
-
* GitHub OAuth configuration. When provided, enables GitHub authentication.
|
|
26
|
-
* Requires the user schema to have GitHub fields (use githubUserPlugin).
|
|
27
|
-
*/
|
|
28
|
-
githubAuth?: GitHubAuthOptions;
|
|
29
|
-
skipListen?: boolean;
|
|
30
|
-
corsOrigin?: string | boolean | RegExp | Array<boolean | string | RegExp> | ((requestOrigin: string | undefined, callback: (err: Error | null, origin?: boolean | string | RegExp | Array<boolean | string | RegExp>) => void) => void);
|
|
31
|
-
addMiddleware?: AddRoutes;
|
|
32
|
-
ignoreTraces?: string[];
|
|
33
|
-
sentryOptions?: Sentry.BunOptions;
|
|
34
|
-
}
|
|
35
|
-
export declare const setupServer: (options: SetupServerOptions) => express.Application;
|
|
36
14
|
export declare const cronjob: (name: string, schedule: "hourly" | "minutely" | string, callback: () => void) => void;
|
|
37
15
|
export interface WrapScriptOptions {
|
|
38
16
|
onFinish?: (result?: unknown) => void | Promise<void>;
|