@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.
@@ -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, changeHandler;
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
- changeHandler = mockStream.listeners.get("change");
2308
- (0, bun_test_1.expect)(changeHandler).toBeDefined();
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, changeHandler;
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*/, changeHandler({
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, changeHandler;
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*/, changeHandler({
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, changeHandler;
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*/, changeHandler({
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, changeHandler;
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*/, changeHandler({
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, changeHandler;
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
- changeHandler = mockStream.listeners.get("change");
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, changeHandler;
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*/, changeHandler({
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, changeHandler;
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*/, changeHandler({
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*/, changeHandler({
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, changeHandler;
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*/, changeHandler({
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, changeHandler;
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*/, changeHandler({
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
@@ -109,5 +109,5 @@
109
109
  "updateSnapshot": "bun test --update-snapshots"
110
110
  },
111
111
  "types": "dist/index.d.ts",
112
- "version": "0.18.0"
112
+ "version": "0.19.0"
113
113
  }
@@ -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 {authAsUser, FoodModel, setupDb, UserModel} from "./tests";
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", DateTime.fromISO("2025-06-15T12:00:01.000Z").toHTTP()!)
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", DateTime.fromISO("2025-06-15T11:59:59.999Z").toHTTP()!)
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 function authenticateMiddleware(anonymous = false) {
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 function signupUser(
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 Error("TOKEN_SECRET must be set in env.");
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 function setupAuth(app: express.Application, userModel: UserModel) {
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 Error("setupAuth userModel must have .createStrategy()");
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 Error("TOKEN_SECRET must be set in env.");
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 function decodeJWTMiddleware(
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 function addAuthRoutes(
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 function addMeRoutes(
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
- function createAPIErrorComponent(openApi?: OpenApiMiddleware) {
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 function getOpenApiMiddleware<T>(
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 function listOpenApiMiddleware<T>(
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 function createOpenApiMiddleware<T>(
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 function patchOpenApiMiddleware<T>(
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 function deleteOpenApiMiddleware<T>(
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 function readOpenApiMiddleware<T>(
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
+ };