@terreno/api 0.18.0 → 0.19.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/dist/api.test.js +18 -8
- package/dist/auth.d.ts +5 -5
- package/dist/auth.js +123 -131
- package/dist/expressServer.test.js +0 -1
- package/dist/openApi.d.ts +6 -6
- package/dist/openApi.js +21 -21
- package/dist/populate.test.js +23 -0
- package/dist/realtime/queryMatcher.js +0 -6
- package/dist/realtime/queryStore.js +3 -11
- package/dist/realtime/realtime.test.js +41 -34
- package/package.json +1 -1
- package/src/actions.openApi.test.ts +1 -1
- package/src/actions.ts +0 -1
- package/src/api.test.ts +10 -2
- package/src/auth.ts +19 -19
- package/src/expressServer.test.ts +0 -1
- package/src/openApi.ts +21 -21
- package/src/populate.test.ts +25 -0
- package/src/realtime/queryMatcher.ts +0 -6
- package/src/realtime/queryStore.ts +1 -10
- package/src/realtime/realtime.test.ts +24 -24
- package/src/realtime/realtimeApp.ts +0 -1
- package/src/realtime/registry.ts +0 -1
- package/src/realtime/types.ts +0 -4
|
@@ -35,7 +35,6 @@ var __read = (this && this.__read) || function (o, n) {
|
|
|
35
35
|
};
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
37
|
exports.matchesQuery = void 0;
|
|
38
|
-
// biome-ignore lint/suspicious/noExplicitAny: traversing arbitrary nested document fields by user-supplied dotted path
|
|
39
38
|
var getNestedValue = function (doc, path) {
|
|
40
39
|
var e_1, _a;
|
|
41
40
|
var parts = path.split(".");
|
|
@@ -58,7 +57,6 @@ var getNestedValue = function (doc, path) {
|
|
|
58
57
|
}
|
|
59
58
|
return current;
|
|
60
59
|
};
|
|
61
|
-
// biome-ignore lint/suspicious/noExplicitAny: value may be any document field type (string, number, ObjectId, etc.)
|
|
62
60
|
var normalize = function (value) {
|
|
63
61
|
var _a;
|
|
64
62
|
if (value === null || value === undefined) {
|
|
@@ -73,7 +71,6 @@ var normalize = function (value) {
|
|
|
73
71
|
}
|
|
74
72
|
return value;
|
|
75
73
|
};
|
|
76
|
-
// biome-ignore lint/suspicious/noExplicitAny: rawValue is an arbitrary document field, condition is an arbitrary user query operand
|
|
77
74
|
var matchesCondition = function (rawValue, condition) {
|
|
78
75
|
var e_2, _a;
|
|
79
76
|
var value = normalize(rawValue);
|
|
@@ -127,7 +124,6 @@ var matchesCondition = function (rawValue, condition) {
|
|
|
127
124
|
return false;
|
|
128
125
|
}
|
|
129
126
|
var inValues = operand.map(normalize);
|
|
130
|
-
// biome-ignore lint/suspicious/noExplicitAny: normalized value of arbitrary document field
|
|
131
127
|
if (!inValues.some(function (v) { return v === value || String(v) === String(value); })) {
|
|
132
128
|
return false;
|
|
133
129
|
}
|
|
@@ -138,7 +134,6 @@ var matchesCondition = function (rawValue, condition) {
|
|
|
138
134
|
return false;
|
|
139
135
|
}
|
|
140
136
|
var ninValues = operand.map(normalize);
|
|
141
|
-
// biome-ignore lint/suspicious/noExplicitAny: normalized value of arbitrary document field
|
|
142
137
|
if (ninValues.some(function (v) { return v === value || String(v) === String(value); })) {
|
|
143
138
|
return false;
|
|
144
139
|
}
|
|
@@ -179,7 +174,6 @@ var matchesCondition = function (rawValue, condition) {
|
|
|
179
174
|
* @param query - MongoDB-style query object
|
|
180
175
|
* @returns true if the document matches all query conditions
|
|
181
176
|
*/
|
|
182
|
-
// biome-ignore lint/suspicious/noExplicitAny: doc is arbitrary; query values are arbitrary user-supplied JSON
|
|
183
177
|
var matchesQuery = function (doc, query) {
|
|
184
178
|
var e_3, _a, e_4, _b, e_5, _c;
|
|
185
179
|
try {
|
|
@@ -40,12 +40,9 @@ exports.clearQueryStore = exports.getQuerySubscriptionsForCollection = exports.r
|
|
|
40
40
|
* Compute a deterministic queryId from collection and query on the server side.
|
|
41
41
|
* This prevents clients from hijacking other subscriptions by providing a colliding queryId.
|
|
42
42
|
*/
|
|
43
|
-
var computeQueryId = function (collection,
|
|
44
|
-
// biome-ignore lint/suspicious/noExplicitAny: MongoDB query filter values are arbitrary user-supplied JSON
|
|
45
|
-
query) {
|
|
43
|
+
var computeQueryId = function (collection, query) {
|
|
46
44
|
var e_1, _a;
|
|
47
45
|
var sortedKeys = Object.keys(query).sort();
|
|
48
|
-
// biome-ignore lint/suspicious/noExplicitAny: mirrors the input query value shape
|
|
49
46
|
var normalized = {};
|
|
50
47
|
try {
|
|
51
48
|
for (var sortedKeys_1 = __values(sortedKeys), sortedKeys_1_1 = sortedKeys_1.next(); !sortedKeys_1_1.done; sortedKeys_1_1 = sortedKeys_1.next()) {
|
|
@@ -71,9 +68,7 @@ var socketQueries = new Map();
|
|
|
71
68
|
* Register a query subscription for a socket.
|
|
72
69
|
* The socket joins the `query:{queryId}` room (handled by the caller).
|
|
73
70
|
*/
|
|
74
|
-
var addQuerySubscription = function (socketId, collection,
|
|
75
|
-
// biome-ignore lint/suspicious/noExplicitAny: MongoDB query filter values are arbitrary user-supplied JSON
|
|
76
|
-
query, queryId) {
|
|
71
|
+
var addQuerySubscription = function (socketId, collection, query, queryId) {
|
|
77
72
|
var _a;
|
|
78
73
|
querySubscriptions.set(queryId, { collection: collection, query: query, queryId: queryId });
|
|
79
74
|
if (!socketQueries.has(socketId)) {
|
|
@@ -161,11 +156,8 @@ exports.removeAllSocketQueries = removeAllSocketQueries;
|
|
|
161
156
|
* Get all unique query subscriptions for a given collection.
|
|
162
157
|
* Used by the change stream watcher to evaluate which query rooms to emit to.
|
|
163
158
|
*/
|
|
164
|
-
var getQuerySubscriptionsForCollection = function (collection
|
|
165
|
-
// biome-ignore lint/suspicious/noExplicitAny: MongoDB query filter values are arbitrary user-supplied JSON
|
|
166
|
-
) {
|
|
159
|
+
var getQuerySubscriptionsForCollection = function (collection) {
|
|
167
160
|
var e_5, _a;
|
|
168
|
-
// biome-ignore lint/suspicious/noExplicitAny: MongoDB query filter values are arbitrary user-supplied JSON
|
|
169
161
|
var result = [];
|
|
170
162
|
try {
|
|
171
163
|
for (var querySubscriptions_1 = __values(querySubscriptions), querySubscriptions_1_1 = querySubscriptions_1.next(); !querySubscriptions_1_1.done; querySubscriptions_1_1 = querySubscriptions_1.next()) {
|
|
@@ -2201,6 +2201,22 @@ var createMockSocket = function (decodedToken) {
|
|
|
2201
2201
|
},
|
|
2202
2202
|
};
|
|
2203
2203
|
};
|
|
2204
|
+
var invokeRegisteredChangeHandler = function (mockStream, event) { return __awaiter(void 0, void 0, void 0, function () {
|
|
2205
|
+
var changeHandler;
|
|
2206
|
+
return __generator(this, function (_a) {
|
|
2207
|
+
switch (_a.label) {
|
|
2208
|
+
case 0:
|
|
2209
|
+
changeHandler = mockStream.listeners.get("change");
|
|
2210
|
+
if (!changeHandler) {
|
|
2211
|
+
throw new Error("expected change handler");
|
|
2212
|
+
}
|
|
2213
|
+
return [4 /*yield*/, changeHandler(event)];
|
|
2214
|
+
case 1:
|
|
2215
|
+
_a.sent();
|
|
2216
|
+
return [2 /*return*/];
|
|
2217
|
+
}
|
|
2218
|
+
});
|
|
2219
|
+
}); };
|
|
2204
2220
|
var createMockIo = function () {
|
|
2205
2221
|
var rooms = new Map();
|
|
2206
2222
|
var sockets = new Map();
|
|
@@ -2272,7 +2288,7 @@ var createMockSocket = function (decodedToken) {
|
|
|
2272
2288
|
});
|
|
2273
2289
|
}); });
|
|
2274
2290
|
(0, bun_test_1.it)("handles change events for registered models", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2275
|
-
var mockStream, mockDb, startChangeStreamWatcher, io
|
|
2291
|
+
var mockStream, mockDb, startChangeStreamWatcher, io;
|
|
2276
2292
|
return __generator(this, function (_a) {
|
|
2277
2293
|
switch (_a.label) {
|
|
2278
2294
|
case 0:
|
|
@@ -2304,22 +2320,22 @@ var createMockSocket = function (decodedToken) {
|
|
|
2304
2320
|
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2305
2321
|
io = createMockIo();
|
|
2306
2322
|
startChangeStreamWatcher(io, {}, true);
|
|
2307
|
-
|
|
2308
|
-
(
|
|
2309
|
-
return [4 /*yield*/, changeHandler({
|
|
2323
|
+
// Trigger an insert change event
|
|
2324
|
+
return [4 /*yield*/, invokeRegisteredChangeHandler(mockStream, {
|
|
2310
2325
|
documentKey: { _id: "doc-1" },
|
|
2311
2326
|
fullDocument: { _id: "doc-1", name: "Test Todo" },
|
|
2312
2327
|
ns: { coll: "todos" },
|
|
2313
2328
|
operationType: "insert",
|
|
2314
2329
|
})];
|
|
2315
2330
|
case 2:
|
|
2331
|
+
// Trigger an insert change event
|
|
2316
2332
|
_a.sent();
|
|
2317
2333
|
return [2 /*return*/];
|
|
2318
2334
|
}
|
|
2319
2335
|
});
|
|
2320
2336
|
}); });
|
|
2321
2337
|
(0, bun_test_1.it)("skips events for unregistered collections", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2322
|
-
var mockStream, mockDb, startChangeStreamWatcher, io
|
|
2338
|
+
var mockStream, mockDb, startChangeStreamWatcher, io;
|
|
2323
2339
|
return __generator(this, function (_a) {
|
|
2324
2340
|
switch (_a.label) {
|
|
2325
2341
|
case 0:
|
|
@@ -2333,9 +2349,8 @@ var createMockSocket = function (decodedToken) {
|
|
|
2333
2349
|
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2334
2350
|
io = createMockIo();
|
|
2335
2351
|
startChangeStreamWatcher(io, {}, true);
|
|
2336
|
-
changeHandler = mockStream.listeners.get("change");
|
|
2337
2352
|
// Trigger for an unregistered collection — should not throw
|
|
2338
|
-
return [4 /*yield*/,
|
|
2353
|
+
return [4 /*yield*/, invokeRegisteredChangeHandler(mockStream, {
|
|
2339
2354
|
documentKey: { _id: "doc-1" },
|
|
2340
2355
|
fullDocument: { _id: "doc-1" },
|
|
2341
2356
|
ns: { coll: "unknown_collection" },
|
|
@@ -2349,7 +2364,7 @@ var createMockSocket = function (decodedToken) {
|
|
|
2349
2364
|
});
|
|
2350
2365
|
}); });
|
|
2351
2366
|
(0, bun_test_1.it)("skips events when method is not enabled for the model", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2352
|
-
var mockStream, mockDb, startChangeStreamWatcher, io
|
|
2367
|
+
var mockStream, mockDb, startChangeStreamWatcher, io;
|
|
2353
2368
|
return __generator(this, function (_a) {
|
|
2354
2369
|
switch (_a.label) {
|
|
2355
2370
|
case 0:
|
|
@@ -2381,9 +2396,8 @@ var createMockSocket = function (decodedToken) {
|
|
|
2381
2396
|
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2382
2397
|
io = createMockIo();
|
|
2383
2398
|
startChangeStreamWatcher(io, {}, true);
|
|
2384
|
-
changeHandler = mockStream.listeners.get("change");
|
|
2385
2399
|
// Update event should be skipped because "update" not in methods
|
|
2386
|
-
return [4 /*yield*/,
|
|
2400
|
+
return [4 /*yield*/, invokeRegisteredChangeHandler(mockStream, {
|
|
2387
2401
|
documentKey: { _id: "doc-1" },
|
|
2388
2402
|
fullDocument: { _id: "doc-1", name: "Updated" },
|
|
2389
2403
|
ns: { coll: "todos" },
|
|
@@ -2398,7 +2412,7 @@ var createMockSocket = function (decodedToken) {
|
|
|
2398
2412
|
});
|
|
2399
2413
|
}); });
|
|
2400
2414
|
(0, bun_test_1.it)("handles delete events for owner-strategy models", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2401
|
-
var mockStream, mockDb, startChangeStreamWatcher, io
|
|
2415
|
+
var mockStream, mockDb, startChangeStreamWatcher, io;
|
|
2402
2416
|
return __generator(this, function (_a) {
|
|
2403
2417
|
switch (_a.label) {
|
|
2404
2418
|
case 0:
|
|
@@ -2430,9 +2444,8 @@ var createMockSocket = function (decodedToken) {
|
|
|
2430
2444
|
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2431
2445
|
io = createMockIo();
|
|
2432
2446
|
startChangeStreamWatcher(io, {}, true);
|
|
2433
|
-
changeHandler = mockStream.listeners.get("change");
|
|
2434
2447
|
// Hard delete (no fullDocument)
|
|
2435
|
-
return [4 /*yield*/,
|
|
2448
|
+
return [4 /*yield*/, invokeRegisteredChangeHandler(mockStream, {
|
|
2436
2449
|
documentKey: { _id: "doc-1" },
|
|
2437
2450
|
ns: { coll: "todos" },
|
|
2438
2451
|
operationType: "delete",
|
|
@@ -2445,7 +2458,7 @@ var createMockSocket = function (decodedToken) {
|
|
|
2445
2458
|
});
|
|
2446
2459
|
}); });
|
|
2447
2460
|
(0, bun_test_1.it)("handles delete events for broadcast-strategy models", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2448
|
-
var mockStream, mockDb, startChangeStreamWatcher, io
|
|
2461
|
+
var mockStream, mockDb, startChangeStreamWatcher, io;
|
|
2449
2462
|
return __generator(this, function (_a) {
|
|
2450
2463
|
switch (_a.label) {
|
|
2451
2464
|
case 0:
|
|
@@ -2477,9 +2490,8 @@ var createMockSocket = function (decodedToken) {
|
|
|
2477
2490
|
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2478
2491
|
io = createMockIo();
|
|
2479
2492
|
startChangeStreamWatcher(io, {}, true);
|
|
2480
|
-
changeHandler = mockStream.listeners.get("change");
|
|
2481
2493
|
// Hard delete for broadcast strategy
|
|
2482
|
-
return [4 /*yield*/,
|
|
2494
|
+
return [4 /*yield*/, invokeRegisteredChangeHandler(mockStream, {
|
|
2483
2495
|
documentKey: { _id: "doc-1" },
|
|
2484
2496
|
ns: { coll: "broadcasts" },
|
|
2485
2497
|
operationType: "delete",
|
|
@@ -2492,7 +2504,7 @@ var createMockSocket = function (decodedToken) {
|
|
|
2492
2504
|
});
|
|
2493
2505
|
}); });
|
|
2494
2506
|
(0, bun_test_1.it)("includes updatedFields in event for update operations", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2495
|
-
var mockStream, mockDb, startChangeStreamWatcher, io
|
|
2507
|
+
var mockStream, mockDb, startChangeStreamWatcher, io;
|
|
2496
2508
|
return __generator(this, function (_a) {
|
|
2497
2509
|
switch (_a.label) {
|
|
2498
2510
|
case 0:
|
|
@@ -2524,8 +2536,7 @@ var createMockSocket = function (decodedToken) {
|
|
|
2524
2536
|
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2525
2537
|
io = createMockIo();
|
|
2526
2538
|
startChangeStreamWatcher(io, {}, true);
|
|
2527
|
-
|
|
2528
|
-
return [4 /*yield*/, changeHandler({
|
|
2539
|
+
return [4 /*yield*/, invokeRegisteredChangeHandler(mockStream, {
|
|
2529
2540
|
documentKey: { _id: "doc-1" },
|
|
2530
2541
|
fullDocument: { _id: "doc-1", name: "Updated", status: "done" },
|
|
2531
2542
|
ns: { coll: "todos" },
|
|
@@ -2563,7 +2574,7 @@ var createMockSocket = function (decodedToken) {
|
|
|
2563
2574
|
});
|
|
2564
2575
|
}); });
|
|
2565
2576
|
(0, bun_test_1.it)("respects ignoredOperations config", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2566
|
-
var mockStream, mockDb, startChangeStreamWatcher, io
|
|
2577
|
+
var mockStream, mockDb, startChangeStreamWatcher, io;
|
|
2567
2578
|
return __generator(this, function (_a) {
|
|
2568
2579
|
switch (_a.label) {
|
|
2569
2580
|
case 0:
|
|
@@ -2595,9 +2606,8 @@ var createMockSocket = function (decodedToken) {
|
|
|
2595
2606
|
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2596
2607
|
io = createMockIo();
|
|
2597
2608
|
startChangeStreamWatcher(io, { ignoredOperations: ["insert"] }, true);
|
|
2598
|
-
changeHandler = mockStream.listeners.get("change");
|
|
2599
2609
|
// This insert should be skipped because "insert" is ignored
|
|
2600
|
-
return [4 /*yield*/,
|
|
2610
|
+
return [4 /*yield*/, invokeRegisteredChangeHandler(mockStream, {
|
|
2601
2611
|
documentKey: { _id: "doc-1" },
|
|
2602
2612
|
fullDocument: { _id: "doc-1" },
|
|
2603
2613
|
ns: { coll: "todos" },
|
|
@@ -2611,7 +2621,7 @@ var createMockSocket = function (decodedToken) {
|
|
|
2611
2621
|
});
|
|
2612
2622
|
}); });
|
|
2613
2623
|
(0, bun_test_1.it)("skips events with no collectionName or docId", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2614
|
-
var mockStream, mockDb, startChangeStreamWatcher, io
|
|
2624
|
+
var mockStream, mockDb, startChangeStreamWatcher, io;
|
|
2615
2625
|
return __generator(this, function (_a) {
|
|
2616
2626
|
switch (_a.label) {
|
|
2617
2627
|
case 0:
|
|
@@ -2625,9 +2635,8 @@ var createMockSocket = function (decodedToken) {
|
|
|
2625
2635
|
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2626
2636
|
io = createMockIo();
|
|
2627
2637
|
startChangeStreamWatcher(io, {}, true);
|
|
2628
|
-
changeHandler = mockStream.listeners.get("change");
|
|
2629
2638
|
// Missing ns.coll
|
|
2630
|
-
return [4 /*yield*/,
|
|
2639
|
+
return [4 /*yield*/, invokeRegisteredChangeHandler(mockStream, {
|
|
2631
2640
|
documentKey: { _id: "doc-1" },
|
|
2632
2641
|
ns: {},
|
|
2633
2642
|
operationType: "insert",
|
|
@@ -2636,7 +2645,7 @@ var createMockSocket = function (decodedToken) {
|
|
|
2636
2645
|
// Missing ns.coll
|
|
2637
2646
|
_a.sent();
|
|
2638
2647
|
// Missing documentKey
|
|
2639
|
-
return [4 /*yield*/,
|
|
2648
|
+
return [4 /*yield*/, invokeRegisteredChangeHandler(mockStream, {
|
|
2640
2649
|
documentKey: {},
|
|
2641
2650
|
ns: { coll: "todos" },
|
|
2642
2651
|
operationType: "insert",
|
|
@@ -2649,7 +2658,7 @@ var createMockSocket = function (decodedToken) {
|
|
|
2649
2658
|
});
|
|
2650
2659
|
}); });
|
|
2651
2660
|
(0, bun_test_1.it)("skips non-CRUD operation types", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2652
|
-
var mockStream, mockDb, startChangeStreamWatcher, io
|
|
2661
|
+
var mockStream, mockDb, startChangeStreamWatcher, io;
|
|
2653
2662
|
return __generator(this, function (_a) {
|
|
2654
2663
|
switch (_a.label) {
|
|
2655
2664
|
case 0:
|
|
@@ -2663,9 +2672,8 @@ var createMockSocket = function (decodedToken) {
|
|
|
2663
2672
|
startChangeStreamWatcher = (_a.sent()).startChangeStreamWatcher;
|
|
2664
2673
|
io = createMockIo();
|
|
2665
2674
|
startChangeStreamWatcher(io, {}, true);
|
|
2666
|
-
changeHandler = mockStream.listeners.get("change");
|
|
2667
2675
|
// "drop" is not in our pipeline filter, should be skipped
|
|
2668
|
-
return [4 /*yield*/,
|
|
2676
|
+
return [4 /*yield*/, invokeRegisteredChangeHandler(mockStream, {
|
|
2669
2677
|
operationType: "drop",
|
|
2670
2678
|
})];
|
|
2671
2679
|
case 2:
|
|
@@ -2721,7 +2729,7 @@ var createMockSocket = function (decodedToken) {
|
|
|
2721
2729
|
});
|
|
2722
2730
|
}); });
|
|
2723
2731
|
(0, bun_test_1.it)("catches errors thrown in the change handler", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2724
|
-
var mockStream, mockDb, startChangeStreamWatcher, emissions, mockSocket, rooms, sockets, io
|
|
2732
|
+
var mockStream, mockDb, startChangeStreamWatcher, emissions, mockSocket, rooms, sockets, io;
|
|
2725
2733
|
return __generator(this, function (_a) {
|
|
2726
2734
|
switch (_a.label) {
|
|
2727
2735
|
case 0:
|
|
@@ -2776,9 +2784,8 @@ var createMockSocket = function (decodedToken) {
|
|
|
2776
2784
|
to: function () { return ({ emit: function () { } }); },
|
|
2777
2785
|
};
|
|
2778
2786
|
startChangeStreamWatcher(io, {}, true);
|
|
2779
|
-
changeHandler = mockStream.listeners.get("change");
|
|
2780
2787
|
// Should not throw even though permission check throws
|
|
2781
|
-
return [4 /*yield*/,
|
|
2788
|
+
return [4 /*yield*/, invokeRegisteredChangeHandler(mockStream, {
|
|
2782
2789
|
documentKey: { _id: "doc-1" },
|
|
2783
2790
|
fullDocument: { _id: "doc-1", name: "Test" },
|
|
2784
2791
|
ns: { coll: "todos" },
|
|
@@ -2862,7 +2869,7 @@ var createMockSocket = function (decodedToken) {
|
|
|
2862
2869
|
});
|
|
2863
2870
|
}); });
|
|
2864
2871
|
var makeServer = function () {
|
|
2865
|
-
var http = require("http");
|
|
2872
|
+
var http = require("node:http");
|
|
2866
2873
|
var server = http.createServer();
|
|
2867
2874
|
servers.push(server);
|
|
2868
2875
|
return server;
|
|
@@ -2925,7 +2932,7 @@ var createMockSocket = function (decodedToken) {
|
|
|
2925
2932
|
});
|
|
2926
2933
|
}); });
|
|
2927
2934
|
var makeServer = function () {
|
|
2928
|
-
var http = require("http");
|
|
2935
|
+
var http = require("node:http");
|
|
2929
2936
|
var server = http.createServer();
|
|
2930
2937
|
servers.push(server);
|
|
2931
2938
|
return server;
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@ import {addAuthRoutes, setupAuth} from "./auth";
|
|
|
7
7
|
import {setupServer} from "./expressServer";
|
|
8
8
|
import {Permissions} from "./permissions";
|
|
9
9
|
import {TerrenoApp} from "./terrenoApp";
|
|
10
|
-
import {
|
|
10
|
+
import {FoodModel, setupDb, UserModel} from "./tests";
|
|
11
11
|
import {z} from "./zodOpenApi";
|
|
12
12
|
|
|
13
13
|
const foodActionPermissions = {
|
package/src/actions.ts
CHANGED
|
@@ -9,7 +9,6 @@ import {loadDocOr404} from "./docLoader";
|
|
|
9
9
|
import {APIError} from "./errors";
|
|
10
10
|
import {defaultOpenApiErrorResponses} from "./openApi";
|
|
11
11
|
import {checkPermissions, type PermissionMethod} from "./permissions";
|
|
12
|
-
import {z} from "./zodOpenApi";
|
|
13
12
|
|
|
14
13
|
// At least two characters: leading letter plus one or more alphanumeric/_/- chars.
|
|
15
14
|
export const ACTION_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_-]+$/;
|
package/src/api.test.ts
CHANGED
|
@@ -2010,9 +2010,13 @@ describe("@terreno/api", () => {
|
|
|
2010
2010
|
});
|
|
2011
2011
|
|
|
2012
2012
|
it("returns 409 when precise conflict timestamp is older than doc.updated", async () => {
|
|
2013
|
+
const ifUnmodifiedSince = DateTime.fromISO("2025-06-15T12:00:01.000Z").toHTTP();
|
|
2014
|
+
if (ifUnmodifiedSince === null) {
|
|
2015
|
+
throw new Error("expected HTTP If-Unmodified-Since value");
|
|
2016
|
+
}
|
|
2013
2017
|
await agent
|
|
2014
2018
|
.patch(`/food/${spinach._id}`)
|
|
2015
|
-
.set("If-Unmodified-Since",
|
|
2019
|
+
.set("If-Unmodified-Since", ifUnmodifiedSince)
|
|
2016
2020
|
.set("X-Unmodified-Since-ISO", "2025-06-15T11:59:59.500Z")
|
|
2017
2021
|
.send({name: "Precise Stale"})
|
|
2018
2022
|
.expect(409);
|
|
@@ -2024,9 +2028,13 @@ describe("@terreno/api", () => {
|
|
|
2024
2028
|
{$unset: {updated: ""}}
|
|
2025
2029
|
);
|
|
2026
2030
|
|
|
2031
|
+
const ifUnmodifiedSince = DateTime.fromISO("2025-06-15T11:59:59.999Z").toHTTP();
|
|
2032
|
+
if (ifUnmodifiedSince === null) {
|
|
2033
|
+
throw new Error("expected HTTP If-Unmodified-Since value");
|
|
2034
|
+
}
|
|
2027
2035
|
const res = await agent
|
|
2028
2036
|
.patch(`/food/${spinach._id}`)
|
|
2029
|
-
.set("If-Unmodified-Since",
|
|
2037
|
+
.set("If-Unmodified-Since", ifUnmodifiedSince)
|
|
2030
2038
|
.send({name: "Created Fallback"})
|
|
2031
2039
|
.expect(409);
|
|
2032
2040
|
|
package/src/auth.ts
CHANGED
|
@@ -54,7 +54,7 @@ export interface GenerateTokensOptions {
|
|
|
54
54
|
sessionId?: string;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
export
|
|
57
|
+
export const authenticateMiddleware = (anonymous = false) => {
|
|
58
58
|
const strategies = ["jwt"];
|
|
59
59
|
if (anonymous) {
|
|
60
60
|
strategies.push("anonymous");
|
|
@@ -70,14 +70,14 @@ export function authenticateMiddleware(anonymous = false) {
|
|
|
70
70
|
}
|
|
71
71
|
return passportAuth(req, res, next);
|
|
72
72
|
};
|
|
73
|
-
}
|
|
73
|
+
};
|
|
74
74
|
|
|
75
|
-
export async
|
|
75
|
+
export const signupUser = async (
|
|
76
76
|
userModel: UserModel,
|
|
77
77
|
email: string,
|
|
78
78
|
password: string,
|
|
79
79
|
body?: Record<string, unknown>
|
|
80
|
-
) {
|
|
80
|
+
) => {
|
|
81
81
|
// Strip email and password from the body. They can cause mongoose to throw an error if strict is
|
|
82
82
|
// set.
|
|
83
83
|
const {email: _email, password: _password, ...bodyRest} = body ?? {};
|
|
@@ -100,7 +100,7 @@ export async function signupUser(
|
|
|
100
100
|
const message = errorMessage(error);
|
|
101
101
|
throw new APIError({title: message});
|
|
102
102
|
}
|
|
103
|
-
}
|
|
103
|
+
};
|
|
104
104
|
|
|
105
105
|
/**
|
|
106
106
|
* Generates both an access token (JWT) and a refresh token for a given user.
|
|
@@ -126,7 +126,7 @@ export const generateTokens = async (
|
|
|
126
126
|
) => {
|
|
127
127
|
const tokenSecretOrKey = process.env.TOKEN_SECRET;
|
|
128
128
|
if (!tokenSecretOrKey) {
|
|
129
|
-
throw new
|
|
129
|
+
throw new APIError({status: 500, title: "TOKEN_SECRET must be set in env."});
|
|
130
130
|
}
|
|
131
131
|
const tokenUser = user as {_id?: ObjectId | string} | null | undefined;
|
|
132
132
|
if (!tokenUser?._id) {
|
|
@@ -186,7 +186,7 @@ export const generateTokens = async (
|
|
|
186
186
|
};
|
|
187
187
|
|
|
188
188
|
// TODO allow customization
|
|
189
|
-
export
|
|
189
|
+
export const setupAuth = (app: express.Application, userModel: UserModel): void => {
|
|
190
190
|
passport.use(new AnonymousStrategy());
|
|
191
191
|
passport.use(userModel.createStrategy());
|
|
192
192
|
passport.use(
|
|
@@ -208,7 +208,7 @@ export function setupAuth(app: express.Application, userModel: UserModel) {
|
|
|
208
208
|
);
|
|
209
209
|
|
|
210
210
|
if (!userModel.createStrategy) {
|
|
211
|
-
throw new
|
|
211
|
+
throw new APIError({status: 500, title: "setupAuth userModel must have .createStrategy()"});
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
const customTokenExtractor: JwtFromRequestFunction = (req) => {
|
|
@@ -228,7 +228,7 @@ export function setupAuth(app: express.Application, userModel: UserModel) {
|
|
|
228
228
|
|
|
229
229
|
const secretOrKey = process.env.TOKEN_SECRET;
|
|
230
230
|
if (!secretOrKey) {
|
|
231
|
-
throw new
|
|
231
|
+
throw new APIError({status: 500, title: "TOKEN_SECRET must be set in env."});
|
|
232
232
|
}
|
|
233
233
|
const jwtOpts: StrategyOptions = {
|
|
234
234
|
issuer: process.env.TOKEN_ISSUER,
|
|
@@ -264,11 +264,11 @@ export function setupAuth(app: express.Application, userModel: UserModel) {
|
|
|
264
264
|
|
|
265
265
|
// Adds req.user to the request. This may wind up duplicating requests with passport,
|
|
266
266
|
// but passport doesn't give us req.user early enough.
|
|
267
|
-
async
|
|
267
|
+
const decodeJWTMiddleware = async (
|
|
268
268
|
req: express.Request,
|
|
269
269
|
res: express.Response,
|
|
270
270
|
next: express.NextFunction
|
|
271
|
-
) {
|
|
271
|
+
) => {
|
|
272
272
|
if (!process.env.TOKEN_SECRET) {
|
|
273
273
|
return next();
|
|
274
274
|
}
|
|
@@ -324,17 +324,17 @@ export function setupAuth(app: express.Application, userModel: UserModel) {
|
|
|
324
324
|
}
|
|
325
325
|
}
|
|
326
326
|
return next();
|
|
327
|
-
}
|
|
327
|
+
};
|
|
328
328
|
app.use(decodeJWTMiddleware);
|
|
329
329
|
// biome-ignore lint/suspicious/noExplicitAny: express 5 type for urlencoded doesn't match RequestHandler
|
|
330
330
|
app.use(express.urlencoded({extended: false}) as any);
|
|
331
|
-
}
|
|
331
|
+
};
|
|
332
332
|
|
|
333
|
-
export
|
|
333
|
+
export const addAuthRoutes = (
|
|
334
334
|
app: express.Application,
|
|
335
335
|
userModel: UserModel,
|
|
336
336
|
authOptions?: AuthOptions
|
|
337
|
-
): void {
|
|
337
|
+
): void => {
|
|
338
338
|
const router = express.Router();
|
|
339
339
|
router.post("/login", async (req, res, next) => {
|
|
340
340
|
passport.authenticate(
|
|
@@ -430,13 +430,13 @@ export function addAuthRoutes(
|
|
|
430
430
|
}
|
|
431
431
|
app.set("etag", false);
|
|
432
432
|
app.use("/auth", router);
|
|
433
|
-
}
|
|
433
|
+
};
|
|
434
434
|
|
|
435
|
-
export
|
|
435
|
+
export const addMeRoutes = (
|
|
436
436
|
app: express.Application,
|
|
437
437
|
userModel: UserModel,
|
|
438
438
|
_authOptions?: AuthOptions
|
|
439
|
-
): void {
|
|
439
|
+
): void => {
|
|
440
440
|
const router = express.Router();
|
|
441
441
|
router.get("/me", authenticateMiddleware(), async (req, res) => {
|
|
442
442
|
if (!req.user?.id) {
|
|
@@ -483,4 +483,4 @@ export function addMeRoutes(
|
|
|
483
483
|
app.set("etag", false);
|
|
484
484
|
app.use("/auth", router);
|
|
485
485
|
app.use(apiErrorMiddleware);
|
|
486
|
-
}
|
|
486
|
+
};
|
|
@@ -812,7 +812,6 @@ describe("expressServer", () => {
|
|
|
812
812
|
// Mock app.listen on the Express prototype to avoid opening a real port
|
|
813
813
|
const express = await import("express");
|
|
814
814
|
const originalListen = express.default.application.listen;
|
|
815
|
-
// biome-ignore lint/suspicious/noExplicitAny: mocking Express internals requires type escape
|
|
816
815
|
express.default.application.listen = mock(function (this: unknown, ...args: unknown[]) {
|
|
817
816
|
const cb = args.find((a: unknown) => typeof a === "function") as (() => void) | undefined;
|
|
818
817
|
if (cb) {
|
package/src/openApi.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type {ModelRouterOptions, OpenApiMiddleware} from "./api";
|
|
|
8
8
|
import {logger} from "./logger";
|
|
9
9
|
import {getOpenApiSpecForModel} from "./populate";
|
|
10
10
|
|
|
11
|
-
const noop = (_a, _b, next) => next();
|
|
11
|
+
const noop = (_a: unknown, _b: unknown, next: () => void) => next();
|
|
12
12
|
|
|
13
13
|
const m2sOptions = {
|
|
14
14
|
props: ["readOnly", "required", "enum", "default"],
|
|
@@ -44,7 +44,7 @@ export const defaultOpenApiErrorResponses = {
|
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
// We repeat this constantly, so we make it a component so we only have to define it once.
|
|
47
|
-
|
|
47
|
+
const createAPIErrorComponent = (openApi?: OpenApiMiddleware): void => {
|
|
48
48
|
// Create a schema component called APIError
|
|
49
49
|
openApi?.component("schemas", "APIError", {
|
|
50
50
|
properties: {
|
|
@@ -111,12 +111,12 @@ function createAPIErrorComponent(openApi?: OpenApiMiddleware) {
|
|
|
111
111
|
},
|
|
112
112
|
type: "object",
|
|
113
113
|
});
|
|
114
|
-
}
|
|
114
|
+
};
|
|
115
115
|
|
|
116
|
-
export
|
|
116
|
+
export const getOpenApiMiddleware = <T>(
|
|
117
117
|
model: Model<T>,
|
|
118
118
|
options: Partial<ModelRouterOptions<T>>
|
|
119
|
-
): express.RequestHandler {
|
|
119
|
+
): express.RequestHandler => {
|
|
120
120
|
createAPIErrorComponent(options.openApi);
|
|
121
121
|
if (!options.openApi?.path) {
|
|
122
122
|
// Just log this once rather than for each middleware.
|
|
@@ -158,12 +158,12 @@ export function getOpenApiMiddleware<T>(
|
|
|
158
158
|
options.openApiOverwrite?.get ?? {}
|
|
159
159
|
)
|
|
160
160
|
);
|
|
161
|
-
}
|
|
161
|
+
};
|
|
162
162
|
|
|
163
|
-
export
|
|
163
|
+
export const listOpenApiMiddleware = <T>(
|
|
164
164
|
model: Model<T>,
|
|
165
165
|
options: Partial<ModelRouterOptions<T>>
|
|
166
|
-
): express.RequestHandler {
|
|
166
|
+
): express.RequestHandler => {
|
|
167
167
|
if (!options.openApi?.path) {
|
|
168
168
|
return noop;
|
|
169
169
|
}
|
|
@@ -324,12 +324,12 @@ export function listOpenApiMiddleware<T>(
|
|
|
324
324
|
options.openApiOverwrite?.list ?? {}
|
|
325
325
|
)
|
|
326
326
|
);
|
|
327
|
-
}
|
|
327
|
+
};
|
|
328
328
|
|
|
329
|
-
export
|
|
329
|
+
export const createOpenApiMiddleware = <T>(
|
|
330
330
|
model: Model<T>,
|
|
331
331
|
options: Partial<ModelRouterOptions<T>>
|
|
332
|
-
): express.RequestHandler {
|
|
332
|
+
): express.RequestHandler => {
|
|
333
333
|
if (!options.openApi?.path) {
|
|
334
334
|
return noop;
|
|
335
335
|
}
|
|
@@ -376,12 +376,12 @@ export function createOpenApiMiddleware<T>(
|
|
|
376
376
|
options.openApiOverwrite?.create ?? {}
|
|
377
377
|
)
|
|
378
378
|
);
|
|
379
|
-
}
|
|
379
|
+
};
|
|
380
380
|
|
|
381
|
-
export
|
|
381
|
+
export const patchOpenApiMiddleware = <T>(
|
|
382
382
|
model: Model<T>,
|
|
383
383
|
options: Partial<ModelRouterOptions<T>>
|
|
384
|
-
): express.RequestHandler {
|
|
384
|
+
): express.RequestHandler => {
|
|
385
385
|
if (!options.openApi?.path) {
|
|
386
386
|
return noop;
|
|
387
387
|
}
|
|
@@ -428,12 +428,12 @@ export function patchOpenApiMiddleware<T>(
|
|
|
428
428
|
options.openApiOverwrite?.update ?? {}
|
|
429
429
|
)
|
|
430
430
|
);
|
|
431
|
-
}
|
|
431
|
+
};
|
|
432
432
|
|
|
433
|
-
export
|
|
433
|
+
export const deleteOpenApiMiddleware = <T>(
|
|
434
434
|
model: Model<T>,
|
|
435
435
|
options: Partial<ModelRouterOptions<T>>
|
|
436
|
-
): express.RequestHandler {
|
|
436
|
+
): express.RequestHandler => {
|
|
437
437
|
if (!options.openApi?.path) {
|
|
438
438
|
return noop;
|
|
439
439
|
}
|
|
@@ -456,16 +456,16 @@ export function deleteOpenApiMiddleware<T>(
|
|
|
456
456
|
options.openApiOverwrite?.delete ?? {}
|
|
457
457
|
)
|
|
458
458
|
);
|
|
459
|
-
}
|
|
459
|
+
};
|
|
460
460
|
|
|
461
461
|
// This is a generic OpenAPI wrapper for a read that returns any object described by `properties`.
|
|
462
462
|
// Useful for endpoints that don't directly map to a model.
|
|
463
|
-
export
|
|
463
|
+
export const readOpenApiMiddleware = <T>(
|
|
464
464
|
options: Partial<ModelRouterOptions<T>>,
|
|
465
465
|
properties: Record<string, unknown>,
|
|
466
466
|
required: string[],
|
|
467
467
|
queryParameters: Array<Record<string, unknown>>
|
|
468
|
-
): express.RequestHandler {
|
|
468
|
+
): express.RequestHandler => {
|
|
469
469
|
if (!options.openApi?.path) {
|
|
470
470
|
// Just log this once rather than for each middleware.
|
|
471
471
|
logger.debug(
|
|
@@ -502,4 +502,4 @@ export function readOpenApiMiddleware<T>(
|
|
|
502
502
|
options.openApiOverwrite?.get ?? {}
|
|
503
503
|
)
|
|
504
504
|
);
|
|
505
|
-
}
|
|
505
|
+
};
|