@terreno/api 0.13.3 → 0.14.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.
Files changed (172) hide show
  1. package/dist/__tests__/versionCheckPlugin.test.js +53 -3
  2. package/dist/api.arrayOperations.test.js +1 -0
  3. package/dist/api.d.ts +15 -4
  4. package/dist/api.errors.test.js +1 -0
  5. package/dist/api.hooks.test.js +1 -0
  6. package/dist/api.js +153 -104
  7. package/dist/api.query.test.js +1 -0
  8. package/dist/api.test.js +174 -0
  9. package/dist/auth.d.ts +10 -5
  10. package/dist/auth.js +163 -90
  11. package/dist/auth.test.js +159 -0
  12. package/dist/betterAuthApp.test.js +1 -0
  13. package/dist/betterAuthSetup.d.ts +5 -6
  14. package/dist/betterAuthSetup.js +17 -14
  15. package/dist/betterAuthSetup.test.js +1 -0
  16. package/dist/config.d.ts +48 -0
  17. package/dist/config.js +248 -0
  18. package/dist/config.test.d.ts +1 -0
  19. package/dist/config.test.js +328 -0
  20. package/dist/configuration.test.js +1 -0
  21. package/dist/configurationApp.d.ts +1 -1
  22. package/dist/configurationApp.js +17 -13
  23. package/dist/configurationPlugin.test.js +1 -0
  24. package/dist/consentApp.test.js +1 -0
  25. package/dist/envConfigurationPlugin.d.ts +2 -0
  26. package/dist/envConfigurationPlugin.js +173 -0
  27. package/dist/envConfigurationPlugin.test.d.ts +1 -0
  28. package/dist/envConfigurationPlugin.test.js +322 -0
  29. package/dist/errors.d.ts +18 -7
  30. package/dist/errors.js +106 -10
  31. package/dist/errors.test.js +16 -1
  32. package/dist/example.js +16 -7
  33. package/dist/expressServer.d.ts +10 -9
  34. package/dist/expressServer.js +62 -53
  35. package/dist/expressServer.test.js +53 -2
  36. package/dist/githubAuth.d.ts +2 -1
  37. package/dist/githubAuth.js +41 -26
  38. package/dist/githubAuth.test.js +1 -0
  39. package/dist/index.d.ts +4 -0
  40. package/dist/index.js +4 -0
  41. package/dist/logger.d.ts +1 -1
  42. package/dist/logger.js +42 -20
  43. package/dist/models/versionConfig.d.ts +2 -0
  44. package/dist/models/versionConfig.js +8 -0
  45. package/dist/notifiers/googleChatNotifier.js +14 -16
  46. package/dist/notifiers/googleChatNotifier.test.js +1 -0
  47. package/dist/notifiers/slackNotifier.js +16 -14
  48. package/dist/notifiers/slackNotifier.test.js +41 -3
  49. package/dist/notifiers/zoomNotifier.js +7 -10
  50. package/dist/notifiers/zoomNotifier.test.js +1 -0
  51. package/dist/openApi.d.ts +1 -1
  52. package/dist/openApi.test.js +1 -0
  53. package/dist/openApiBuilder.d.ts +39 -6
  54. package/dist/openApiBuilder.js +1 -31
  55. package/dist/openApiBuilder.test.js +1 -0
  56. package/dist/openApiValidator.js +1 -0
  57. package/dist/openApiValidator.test.js +1 -0
  58. package/dist/permissions.d.ts +4 -4
  59. package/dist/permissions.js +67 -65
  60. package/dist/permissions.middleware.test.js +1 -0
  61. package/dist/permissions.test.js +1 -0
  62. package/dist/plugins.d.ts +5 -5
  63. package/dist/plugins.js +18 -9
  64. package/dist/plugins.test.js +1 -1
  65. package/dist/populate.d.ts +15 -8
  66. package/dist/populate.js +23 -24
  67. package/dist/populate.test.js +1 -0
  68. package/dist/realtime/changeStreamWatcher.d.ts +73 -0
  69. package/dist/realtime/changeStreamWatcher.js +720 -0
  70. package/dist/realtime/index.d.ts +6 -0
  71. package/dist/realtime/index.js +27 -0
  72. package/dist/realtime/queryMatcher.d.ts +14 -0
  73. package/dist/realtime/queryMatcher.js +250 -0
  74. package/dist/realtime/queryStore.d.ts +37 -0
  75. package/dist/realtime/queryStore.js +195 -0
  76. package/dist/realtime/realtime.test.d.ts +10 -0
  77. package/dist/realtime/realtime.test.js +2158 -0
  78. package/dist/realtime/realtimeApp.d.ts +93 -0
  79. package/dist/realtime/realtimeApp.js +560 -0
  80. package/dist/realtime/registry.d.ts +40 -0
  81. package/dist/realtime/registry.js +38 -0
  82. package/dist/realtime/socketUser.d.ts +10 -0
  83. package/dist/realtime/socketUser.js +17 -0
  84. package/dist/realtime/types.d.ts +100 -0
  85. package/dist/realtime/types.js +2 -0
  86. package/dist/requestContext.d.ts +37 -0
  87. package/dist/requestContext.js +344 -0
  88. package/dist/requestContext.test.d.ts +1 -0
  89. package/dist/requestContext.test.js +241 -0
  90. package/dist/terrenoApp.d.ts +8 -0
  91. package/dist/terrenoApp.js +50 -13
  92. package/dist/terrenoApp.test.js +194 -21
  93. package/dist/terrenoPlugin.d.ts +11 -0
  94. package/dist/tests/bunSetup.js +1 -0
  95. package/dist/tests.js +1 -1
  96. package/dist/transformers.d.ts +2 -2
  97. package/dist/transformers.js +5 -3
  98. package/dist/transformers.test.js +90 -0
  99. package/dist/types/consentResponse.d.ts +6 -3
  100. package/dist/versionCheckPlugin.d.ts +2 -0
  101. package/dist/versionCheckPlugin.js +18 -12
  102. package/package.json +4 -2
  103. package/src/__tests__/versionCheckPlugin.test.ts +37 -3
  104. package/src/api.arrayOperations.test.ts +1 -0
  105. package/src/api.errors.test.ts +1 -0
  106. package/src/api.hooks.test.ts +1 -0
  107. package/src/api.query.test.ts +1 -0
  108. package/src/api.test.ts +132 -0
  109. package/src/api.ts +199 -84
  110. package/src/auth.test.ts +160 -0
  111. package/src/auth.ts +120 -50
  112. package/src/betterAuthApp.test.ts +1 -0
  113. package/src/betterAuthSetup.test.ts +1 -0
  114. package/src/betterAuthSetup.ts +46 -19
  115. package/src/config.test.ts +255 -0
  116. package/src/config.ts +206 -0
  117. package/src/configuration.test.ts +1 -0
  118. package/src/configurationApp.ts +59 -24
  119. package/src/configurationPlugin.test.ts +1 -0
  120. package/src/consentApp.test.ts +1 -0
  121. package/src/envConfigurationPlugin.test.ts +143 -0
  122. package/src/envConfigurationPlugin.ts +100 -0
  123. package/src/errors.test.ts +19 -1
  124. package/src/errors.ts +94 -20
  125. package/src/example.ts +46 -21
  126. package/src/express.d.ts +18 -1
  127. package/src/expressServer.test.ts +50 -2
  128. package/src/expressServer.ts +80 -50
  129. package/src/githubAuth.test.ts +1 -0
  130. package/src/githubAuth.ts +59 -38
  131. package/src/index.ts +4 -0
  132. package/src/logger.ts +47 -17
  133. package/src/models/versionConfig.ts +13 -2
  134. package/src/notifiers/googleChatNotifier.test.ts +1 -0
  135. package/src/notifiers/googleChatNotifier.ts +7 -9
  136. package/src/notifiers/slackNotifier.test.ts +29 -3
  137. package/src/notifiers/slackNotifier.ts +9 -7
  138. package/src/notifiers/zoomNotifier.test.ts +1 -0
  139. package/src/notifiers/zoomNotifier.ts +8 -11
  140. package/src/openApi.test.ts +1 -0
  141. package/src/openApi.ts +4 -4
  142. package/src/openApiBuilder.test.ts +1 -0
  143. package/src/openApiBuilder.ts +14 -11
  144. package/src/openApiValidator.test.ts +1 -0
  145. package/src/openApiValidator.ts +3 -2
  146. package/src/permissions.middleware.test.ts +1 -0
  147. package/src/permissions.test.ts +1 -0
  148. package/src/permissions.ts +30 -25
  149. package/src/plugins.test.ts +1 -1
  150. package/src/plugins.ts +21 -14
  151. package/src/populate.test.ts +1 -0
  152. package/src/populate.ts +44 -36
  153. package/src/realtime/changeStreamWatcher.ts +568 -0
  154. package/src/realtime/index.ts +34 -0
  155. package/src/realtime/queryMatcher.ts +179 -0
  156. package/src/realtime/queryStore.ts +132 -0
  157. package/src/realtime/realtime.test.ts +1755 -0
  158. package/src/realtime/realtimeApp.ts +478 -0
  159. package/src/realtime/registry.ts +64 -0
  160. package/src/realtime/socketUser.ts +25 -0
  161. package/src/realtime/types.ts +112 -0
  162. package/src/requestContext.test.ts +196 -0
  163. package/src/requestContext.ts +368 -0
  164. package/src/terrenoApp.test.ts +137 -11
  165. package/src/terrenoApp.ts +64 -17
  166. package/src/terrenoPlugin.ts +12 -0
  167. package/src/tests/bunSetup.ts +1 -0
  168. package/src/tests.ts +7 -2
  169. package/src/transformers.test.ts +70 -2
  170. package/src/transformers.ts +15 -7
  171. package/src/types/consentResponse.ts +8 -10
  172. package/src/versionCheckPlugin.ts +15 -7
package/dist/auth.test.js CHANGED
@@ -99,22 +99,30 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
99
99
  return (mod && mod.__esModule) ? mod : { "default": mod };
100
100
  };
101
101
  Object.defineProperty(exports, "__esModule", { value: true });
102
+ // biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
102
103
  var bun_test_1 = require("bun:test");
103
104
  var supertest_1 = __importDefault(require("supertest"));
104
105
  var api_1 = require("./api");
105
106
  var auth_1 = require("./auth");
106
107
  var expressServer_1 = require("./expressServer");
107
108
  var permissions_1 = require("./permissions");
109
+ var requestContext_1 = require("./requestContext");
108
110
  var tests_1 = require("./tests");
109
111
  var transformers_1 = require("./transformers");
110
112
  var utils_1 = require("./utils");
113
+ var decodeTokenPayload = function (token) {
114
+ var encodedPayload = token.split(".")[1];
115
+ return JSON.parse(Buffer.from(encodedPayload, "base64url").toString("utf8"));
116
+ };
111
117
  (0, bun_test_1.describe)("auth tests", function () {
112
118
  var app;
113
119
  var admin;
120
+ var contextEvents;
114
121
  var notAdmin;
115
122
  var agent;
116
123
  (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
117
124
  function addRoutes(router) {
125
+ var _this = this;
118
126
  router.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, {
119
127
  allowAnonymous: true,
120
128
  permissions: {
@@ -141,6 +149,58 @@ var utils_1 = require("./utils");
141
149
  ownerWriteFields: ["name", "calories", "created"],
142
150
  }),
143
151
  }));
152
+ router.use("/context-food", (0, api_1.modelRouter)(tests_1.FoodModel, {
153
+ permissions: {
154
+ create: [permissions_1.Permissions.IsAuthenticated],
155
+ delete: [],
156
+ list: [],
157
+ read: [],
158
+ update: [],
159
+ },
160
+ postCreate: function (_value, req) { return __awaiter(_this, void 0, void 0, function () {
161
+ var _a, _b;
162
+ return __generator(this, function (_c) {
163
+ contextEvents.push({
164
+ currentSessionId: (_a = (0, requestContext_1.getCurrentRequestContext)()) === null || _a === void 0 ? void 0 : _a.sessionId,
165
+ requestId: req.requestId,
166
+ sessionId: req.sessionId,
167
+ stage: "postCreate",
168
+ userId: (_b = req.user) === null || _b === void 0 ? void 0 : _b.id,
169
+ });
170
+ return [2 /*return*/];
171
+ });
172
+ }); },
173
+ preCreate: function (body, req) {
174
+ var _a, _b, _c, _d;
175
+ contextEvents.push({
176
+ currentSessionId: (_a = (0, requestContext_1.getCurrentRequestContext)()) === null || _a === void 0 ? void 0 : _a.sessionId,
177
+ requestId: req.requestId,
178
+ sessionId: req.sessionId,
179
+ stage: "preCreate",
180
+ userId: (_b = req.user) === null || _b === void 0 ? void 0 : _b.id,
181
+ });
182
+ return __assign(__assign({}, body), { categories: [], eatenBy: [(_c = req.user) === null || _c === void 0 ? void 0 : _c._id], expiration: "2026-01-01", lastEatenWith: {}, likesIds: [], ownerId: (_d = req.user) === null || _d === void 0 ? void 0 : _d._id, source: { name: "context-test" }, tags: [] });
183
+ },
184
+ responseHandler: function (value, method, req) { return __awaiter(_this, void 0, void 0, function () {
185
+ var _a, _b, _c, _d, _e, _f, _g, _h;
186
+ return __generator(this, function (_j) {
187
+ contextEvents.push({
188
+ currentSessionId: (_a = (0, requestContext_1.getCurrentRequestContext)()) === null || _a === void 0 ? void 0 : _a.sessionId,
189
+ requestId: req.requestId,
190
+ sessionId: req.sessionId,
191
+ stage: "responseHandler:".concat(method),
192
+ userId: (_b = req.user) === null || _b === void 0 ? void 0 : _b.id,
193
+ });
194
+ return [2 /*return*/, {
195
+ id: String(value._id),
196
+ requestId: (_c = req.requestId) !== null && _c !== void 0 ? _c : null,
197
+ sessionContext: (_e = (_d = (0, requestContext_1.getCurrentRequestContext)()) === null || _d === void 0 ? void 0 : _d.sessionId) !== null && _e !== void 0 ? _e : null,
198
+ sessionId: (_f = req.sessionId) !== null && _f !== void 0 ? _f : null,
199
+ userId: (_h = (_g = req.user) === null || _g === void 0 ? void 0 : _g.id) !== null && _h !== void 0 ? _h : null,
200
+ }];
201
+ });
202
+ }); },
203
+ }));
144
204
  }
145
205
  var _a;
146
206
  return __generator(this, function (_b) {
@@ -152,6 +212,7 @@ var utils_1 = require("./utils");
152
212
  return [4 /*yield*/, (0, tests_1.setupDb)()];
153
213
  case 1:
154
214
  _a = __read.apply(void 0, [_b.sent(), 2]), admin = _a[0], notAdmin = _a[1];
215
+ contextEvents = [];
155
216
  return [4 /*yield*/, Promise.all([
156
217
  tests_1.FoodModel.create({
157
218
  calories: 1,
@@ -346,6 +407,104 @@ var utils_1 = require("./utils");
346
407
  }
347
408
  });
348
409
  }); });
410
+ (0, bun_test_1.it)("passes request and session context through modelRouter hooks", function () { return __awaiter(void 0, void 0, void 0, function () {
411
+ var loginRes, loginTokenPayload, createRes, sessionId;
412
+ return __generator(this, function (_a) {
413
+ switch (_a.label) {
414
+ case 0: return [4 /*yield*/, agent
415
+ .post("/auth/login")
416
+ .send({ email: "admin@example.com", password: "securePassword" })
417
+ .expect(200)];
418
+ case 1:
419
+ loginRes = _a.sent();
420
+ loginTokenPayload = decodeTokenPayload(loginRes.body.data.token);
421
+ return [4 /*yield*/, agent
422
+ .post("/context-food")
423
+ .set("authorization", "Bearer ".concat(loginRes.body.data.token))
424
+ .set("X-Request-ID", "model-router-request-1")
425
+ .send({ calories: 10, name: "Context Apple" })
426
+ .expect(201)];
427
+ case 2:
428
+ createRes = _a.sent();
429
+ (0, bun_test_1.expect)(loginTokenPayload.sid).toBeDefined();
430
+ sessionId = loginTokenPayload.sid;
431
+ if (!sessionId) {
432
+ throw new Error("Expected login token to include a session id");
433
+ }
434
+ (0, bun_test_1.expect)(createRes.headers["x-request-id"]).toBe("model-router-request-1");
435
+ (0, bun_test_1.expect)(createRes.headers["x-session-id"]).toBe(sessionId);
436
+ (0, bun_test_1.expect)(createRes.body.data.requestId).toBe("model-router-request-1");
437
+ (0, bun_test_1.expect)(createRes.body.data.sessionId).toBe(sessionId);
438
+ (0, bun_test_1.expect)(createRes.body.data.sessionContext).toBe(sessionId);
439
+ (0, bun_test_1.expect)(createRes.body.data.userId).toBe(String(admin._id));
440
+ (0, bun_test_1.expect)(contextEvents).toEqual([
441
+ {
442
+ currentSessionId: sessionId,
443
+ requestId: "model-router-request-1",
444
+ sessionId: sessionId,
445
+ stage: "preCreate",
446
+ userId: String(admin._id),
447
+ },
448
+ {
449
+ currentSessionId: sessionId,
450
+ requestId: "model-router-request-1",
451
+ sessionId: sessionId,
452
+ stage: "postCreate",
453
+ userId: String(admin._id),
454
+ },
455
+ {
456
+ currentSessionId: sessionId,
457
+ requestId: "model-router-request-1",
458
+ sessionId: sessionId,
459
+ stage: "responseHandler:create",
460
+ userId: String(admin._id),
461
+ },
462
+ ]);
463
+ return [2 /*return*/];
464
+ }
465
+ });
466
+ }); });
467
+ (0, bun_test_1.it)("preserves JWT session id across refresh and request context", function () { return __awaiter(void 0, void 0, void 0, function () {
468
+ var loginRes, loginTokenPayload, loginRefreshPayload, loginSessionId, refreshRes, refreshedTokenPayload, refreshedRefreshPayload, foodRes;
469
+ return __generator(this, function (_a) {
470
+ switch (_a.label) {
471
+ case 0: return [4 /*yield*/, agent
472
+ .post("/auth/login")
473
+ .send({ email: "admin@example.com", password: "securePassword" })
474
+ .expect(200)];
475
+ case 1:
476
+ loginRes = _a.sent();
477
+ loginTokenPayload = decodeTokenPayload(loginRes.body.data.token);
478
+ loginRefreshPayload = decodeTokenPayload(loginRes.body.data.refreshToken);
479
+ (0, bun_test_1.expect)(loginTokenPayload.sid).toBeDefined();
480
+ loginSessionId = loginTokenPayload.sid;
481
+ if (!loginSessionId) {
482
+ throw new Error("Expected login token to include a session id");
483
+ }
484
+ (0, bun_test_1.expect)(loginRefreshPayload.sid).toBe(loginSessionId);
485
+ (0, bun_test_1.expect)(loginRes.headers["x-session-id"]).toBe(loginSessionId);
486
+ return [4 /*yield*/, agent
487
+ .post("/auth/refresh_token")
488
+ .send({ refreshToken: loginRes.body.data.refreshToken })
489
+ .expect(200)];
490
+ case 2:
491
+ refreshRes = _a.sent();
492
+ refreshedTokenPayload = decodeTokenPayload(refreshRes.body.data.token);
493
+ refreshedRefreshPayload = decodeTokenPayload(refreshRes.body.data.refreshToken);
494
+ (0, bun_test_1.expect)(refreshedTokenPayload.sid).toBe(loginSessionId);
495
+ (0, bun_test_1.expect)(refreshedRefreshPayload.sid).toBe(loginSessionId);
496
+ (0, bun_test_1.expect)(refreshRes.headers["x-session-id"]).toBe(loginSessionId);
497
+ return [4 /*yield*/, agent
498
+ .get("/food")
499
+ .set("authorization", "Bearer ".concat(refreshRes.body.data.token))
500
+ .expect(200)];
501
+ case 3:
502
+ foodRes = _a.sent();
503
+ (0, bun_test_1.expect)(foodRes.headers["x-session-id"]).toBe(loginSessionId);
504
+ return [2 /*return*/];
505
+ }
506
+ });
507
+ }); });
349
508
  (0, bun_test_1.it)("completes token login e2e", function () { return __awaiter(void 0, void 0, void 0, function () {
350
509
  var res, _a, userId, token, meRes, mePatchRes, getRes, food, updateRes;
351
510
  return __generator(this, function (_b) {
@@ -83,6 +83,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
83
83
  return (mod && mod.__esModule) ? mod : { "default": mod };
84
84
  };
85
85
  Object.defineProperty(exports, "__esModule", { value: true });
86
+ // biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
86
87
  var bun_test_1 = require("bun:test");
87
88
  var express_1 = __importDefault(require("express"));
88
89
  var mongodb_memory_server_1 = require("mongodb-memory-server");
@@ -15,9 +15,12 @@ export type BetterAuthInstance = ReturnType<typeof betterAuth>;
15
15
  /**
16
16
  * Options for creating a Better Auth instance.
17
17
  */
18
+ export interface MongoClientLike {
19
+ db: () => any;
20
+ }
18
21
  export interface CreateBetterAuthOptions {
19
22
  config: BetterAuthConfig;
20
- mongoClient: any;
23
+ mongoClient: MongoClientLike;
21
24
  userModel?: UserModel;
22
25
  }
23
26
  /**
@@ -29,10 +32,6 @@ export declare const createBetterAuth: (options: CreateBetterAuthOptions) => Bet
29
32
  * and populates req.user with the application User model.
30
33
  */
31
34
  export declare const createBetterAuthSessionMiddleware: (auth: BetterAuthInstance, userModel?: UserModel) => (req: Request, _res: Response, next: NextFunction) => Promise<void>;
32
- /**
33
- * Syncs a Better Auth user to the application User model.
34
- * Creates or updates the user as needed.
35
- */
36
35
  export declare const syncBetterAuthUser: (userModel: UserModel, betterAuthUser: BetterAuthUser, oauthProvider?: string) => Promise<any>;
37
36
  /**
38
37
  * Mounts Better Auth routes on the Express app.
@@ -41,7 +40,7 @@ export declare const mountBetterAuthRoutes: (app: Application, auth: BetterAuthI
41
40
  /**
42
41
  * Gets the MongoDB client from the mongoose connection.
43
42
  */
44
- export declare const getMongoClientFromMongoose: () => any;
43
+ export declare const getMongoClientFromMongoose: () => MongoClientLike;
45
44
  /**
46
45
  * Sets up Better Auth user sync hooks.
47
46
  * This ensures users created/updated in Better Auth are synced to the application User model.
@@ -63,6 +63,7 @@ var node_1 = require("better-auth/node");
63
63
  var mongoose_1 = __importDefault(require("mongoose"));
64
64
  var logger_1 = require("./logger");
65
65
  var plugins_1 = require("./plugins");
66
+ var requestContext_1 = require("./requestContext");
66
67
  /**
67
68
  * Creates a Better Auth instance with MongoDB adapter.
68
69
  */
@@ -123,7 +124,7 @@ exports.createBetterAuth = createBetterAuth;
123
124
  */
124
125
  var createBetterAuthSessionMiddleware = function (auth, userModel) {
125
126
  return function (req, _res, next) { return __awaiter(void 0, void 0, void 0, function () {
126
- var session, betterAuthUser, appUser, newUser, error_1;
127
+ var session, betterAuthUser, reqWithSession, appUser, newUser, error_1;
127
128
  return __generator(this, function (_a) {
128
129
  switch (_a.label) {
129
130
  case 0:
@@ -135,6 +136,7 @@ var createBetterAuthSessionMiddleware = function (auth, userModel) {
135
136
  session = _a.sent();
136
137
  if (!((session === null || session === void 0 ? void 0 : session.user) && (session === null || session === void 0 ? void 0 : session.session))) return [3 /*break*/, 7];
137
138
  betterAuthUser = session.user;
139
+ reqWithSession = req;
138
140
  if (!userModel) return [3 /*break*/, 6];
139
141
  return [4 /*yield*/, (0, plugins_1.findOneOrNoneFor)(userModel, {
140
142
  betterAuthId: betterAuthUser.id,
@@ -142,19 +144,21 @@ var createBetterAuthSessionMiddleware = function (auth, userModel) {
142
144
  case 2:
143
145
  appUser = _a.sent();
144
146
  if (!appUser) return [3 /*break*/, 3];
145
- req.user = appUser;
146
- req.betterAuthSession = session;
147
+ reqWithSession.user = appUser;
148
+ reqWithSession.betterAuthSession = session;
149
+ (0, requestContext_1.updateRequestContextFromRequest)(req);
147
150
  return [3 /*break*/, 5];
148
151
  case 3: return [4 /*yield*/, (0, exports.syncBetterAuthUser)(userModel, betterAuthUser)];
149
152
  case 4:
150
153
  newUser = _a.sent();
151
- req.user = newUser;
152
- req.betterAuthSession = session;
154
+ reqWithSession.user = newUser;
155
+ reqWithSession.betterAuthSession = session;
156
+ (0, requestContext_1.updateRequestContextFromRequest)(req);
153
157
  _a.label = 5;
154
158
  case 5: return [3 /*break*/, 7];
155
159
  case 6:
156
160
  // No user model - just attach the Better Auth user directly
157
- req.user = {
161
+ reqWithSession.user = {
158
162
  _id: betterAuthUser.id,
159
163
  admin: false,
160
164
  betterAuthId: betterAuthUser.id,
@@ -162,7 +166,8 @@ var createBetterAuthSessionMiddleware = function (auth, userModel) {
162
166
  id: betterAuthUser.id,
163
167
  name: betterAuthUser.name,
164
168
  };
165
- req.betterAuthSession = session;
169
+ reqWithSession.betterAuthSession = session;
170
+ (0, requestContext_1.updateRequestContextFromRequest)(req);
166
171
  _a.label = 7;
167
172
  case 7:
168
173
  next();
@@ -178,11 +183,9 @@ var createBetterAuthSessionMiddleware = function (auth, userModel) {
178
183
  }); };
179
184
  };
180
185
  exports.createBetterAuthSessionMiddleware = createBetterAuthSessionMiddleware;
181
- /**
182
- * Syncs a Better Auth user to the application User model.
183
- * Creates or updates the user as needed.
184
- */
185
- var syncBetterAuthUser = function (userModel, betterAuthUser, oauthProvider) { return __awaiter(void 0, void 0, void 0, function () {
186
+ var syncBetterAuthUser = function (userModel, betterAuthUser, oauthProvider
187
+ // biome-ignore lint/suspicious/noExplicitAny: return is a consumer-defined user document; tests inspect varied fields
188
+ ) { return __awaiter(void 0, void 0, void 0, function () {
186
189
  var existingUser, userByEmail, useAsId, newUser, error_2;
187
190
  return __generator(this, function (_a) {
188
191
  switch (_a.label) {
@@ -192,7 +195,7 @@ var syncBetterAuthUser = function (userModel, betterAuthUser, oauthProvider) { r
192
195
  betterAuthId: betterAuthUser.id,
193
196
  })];
194
197
  case 1:
195
- existingUser = _a.sent();
198
+ existingUser = (_a.sent());
196
199
  if (!existingUser) return [3 /*break*/, 3];
197
200
  // Update existing user if needed
198
201
  existingUser.email = betterAuthUser.email;
@@ -207,7 +210,7 @@ var syncBetterAuthUser = function (userModel, betterAuthUser, oauthProvider) { r
207
210
  email: betterAuthUser.email,
208
211
  })];
209
212
  case 4:
210
- userByEmail = _a.sent();
213
+ userByEmail = (_a.sent());
211
214
  if (!userByEmail) return [3 /*break*/, 6];
212
215
  // Link existing user to Better Auth
213
216
  userByEmail.betterAuthId = betterAuthUser.id;
@@ -83,6 +83,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
83
83
  return (mod && mod.__esModule) ? mod : { "default": mod };
84
84
  };
85
85
  Object.defineProperty(exports, "__esModule", { value: true });
86
+ // biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
86
87
  var bun_test_1 = require("bun:test");
87
88
  var express_1 = __importDefault(require("express"));
88
89
  var mongodb_memory_server_1 = require("mongodb-memory-server");
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Runtime configuration registry with a fixed resolution order:
3
+ *
4
+ * 1. In-process override (Config.setOverride) — highest, for tests/bootstrap
5
+ * 2. Cached env map — typically loaded from an admin-editable Mongoose document
6
+ * 3. process.env
7
+ * 4. Registered default
8
+ *
9
+ * Why a registry: every key migrating off raw `process.env` declares its type
10
+ * and default in one place. That keeps the admin UI honest (no surprise keys),
11
+ * gives synchronous access without scattered `?? "default"` literals at call
12
+ * sites, and lets tests assert behavior against a single source of truth.
13
+ *
14
+ * Why sync: hundreds of call sites read configuration during request handling
15
+ * and module init; an async API would force enormous refactors. Callers load
16
+ * the env map once via `Config.refresh()` after Mongo connects, then read
17
+ * synchronously from cache.
18
+ *
19
+ * The mechanism is agnostic to where the env map comes from. Apps wire up
20
+ * their backing store with `Config.setEnvLoader(fn)`. The optional
21
+ * `envConfigurationPlugin` provides a drop-in Mongoose schema integration.
22
+ */
23
+ export interface ConfigRegistration {
24
+ /** Default returned when neither override, cache, nor process.env supplies a value. */
25
+ default?: string;
26
+ /** Documentation surfaced in the admin UI. */
27
+ description?: string;
28
+ /** Marks the key as a secret so admin UI can mask the value. */
29
+ secret?: boolean;
30
+ }
31
+ export declare const Config: {
32
+ clearOverrides: () => void;
33
+ clearRegistryForTesting: () => void;
34
+ get: (key: string) => string | undefined;
35
+ getBoolean: (key: string) => boolean;
36
+ getDefault: (key: string) => string | undefined;
37
+ getJSON: <T = unknown>(key: string) => T | undefined;
38
+ getNumber: (key: string) => number | undefined;
39
+ getRegisteredKeys: () => string[];
40
+ getRegistration: (key: string) => ConfigRegistration | undefined;
41
+ isRegistered: (key: string) => boolean;
42
+ refresh: () => Promise<void>;
43
+ register: (key: string, registration?: ConfigRegistration) => void;
44
+ setCachedEnv: (env: Record<string, string> | null) => void;
45
+ setEnvLoader: (loader: (() => Promise<Record<string, string>>) | null) => void;
46
+ setOverride: (key: string, value: string | undefined) => void;
47
+ };
48
+ export type ConfigType = typeof Config;
package/dist/config.js ADDED
@@ -0,0 +1,248 @@
1
+ "use strict";
2
+ /**
3
+ * Runtime configuration registry with a fixed resolution order:
4
+ *
5
+ * 1. In-process override (Config.setOverride) — highest, for tests/bootstrap
6
+ * 2. Cached env map — typically loaded from an admin-editable Mongoose document
7
+ * 3. process.env
8
+ * 4. Registered default
9
+ *
10
+ * Why a registry: every key migrating off raw `process.env` declares its type
11
+ * and default in one place. That keeps the admin UI honest (no surprise keys),
12
+ * gives synchronous access without scattered `?? "default"` literals at call
13
+ * sites, and lets tests assert behavior against a single source of truth.
14
+ *
15
+ * Why sync: hundreds of call sites read configuration during request handling
16
+ * and module init; an async API would force enormous refactors. Callers load
17
+ * the env map once via `Config.refresh()` after Mongo connects, then read
18
+ * synchronously from cache.
19
+ *
20
+ * The mechanism is agnostic to where the env map comes from. Apps wire up
21
+ * their backing store with `Config.setEnvLoader(fn)`. The optional
22
+ * `envConfigurationPlugin` provides a drop-in Mongoose schema integration.
23
+ */
24
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
25
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
26
+ return new (P || (P = Promise))(function (resolve, reject) {
27
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
28
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
29
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
30
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
31
+ });
32
+ };
33
+ var __generator = (this && this.__generator) || function (thisArg, body) {
34
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
35
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
36
+ function verb(n) { return function (v) { return step([n, v]); }; }
37
+ function step(op) {
38
+ if (f) throw new TypeError("Generator is already executing.");
39
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
40
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
41
+ if (y = 0, t) op = [op[0] & 2, t.value];
42
+ switch (op[0]) {
43
+ case 0: case 1: t = op; break;
44
+ case 4: _.label++; return { value: op[1], done: false };
45
+ case 5: _.label++; y = op[1]; op = [0]; continue;
46
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
47
+ default:
48
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
49
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
50
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
51
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
52
+ if (t[2]) _.ops.pop();
53
+ _.trys.pop(); continue;
54
+ }
55
+ op = body.call(thisArg, _);
56
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
57
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
58
+ }
59
+ };
60
+ var __values = (this && this.__values) || function(o) {
61
+ var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
62
+ if (m) return m.call(o);
63
+ if (o && typeof o.length === "number") return {
64
+ next: function () {
65
+ if (o && i >= o.length) o = void 0;
66
+ return { value: o && o[i++], done: !o };
67
+ }
68
+ };
69
+ throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
70
+ };
71
+ Object.defineProperty(exports, "__esModule", { value: true });
72
+ exports.Config = void 0;
73
+ var overrides = new Map();
74
+ var cachedEnv = null;
75
+ var envLoader = null;
76
+ // Null-prototype object so lookups don't resolve inherited keys like
77
+ // `constructor` / `toString` as accidentally-registered entries.
78
+ var REGISTRY = Object.create(null);
79
+ /**
80
+ * Registers a configuration key, its default, and metadata. Re-registration
81
+ * of the same key throws so duplicates surface at boot.
82
+ */
83
+ var register = function (key, registration) {
84
+ if (registration === void 0) { registration = {}; }
85
+ if (REGISTRY[key]) {
86
+ throw new Error("Config key \"".concat(key, "\" registered more than once"));
87
+ }
88
+ REGISTRY[key] = registration;
89
+ };
90
+ /**
91
+ * Returns the configured string value for `key`, applying the resolution
92
+ * order documented at the top of this file. Returns `undefined` if no
93
+ * source supplies a value and no default was registered.
94
+ */
95
+ var getString = function (key) {
96
+ var _a;
97
+ if (overrides.has(key)) {
98
+ return overrides.get(key);
99
+ }
100
+ if (cachedEnv && key in cachedEnv) {
101
+ var v = cachedEnv[key];
102
+ if (v !== undefined && v !== "") {
103
+ return v;
104
+ }
105
+ }
106
+ // Guard against process.env keys like "toString" / "constructor" inheriting
107
+ // a function from Object.prototype. Only accept string values.
108
+ var fromProcess = process.env[key];
109
+ if (typeof fromProcess === "string" && fromProcess !== "") {
110
+ return fromProcess;
111
+ }
112
+ return (_a = REGISTRY[key]) === null || _a === void 0 ? void 0 : _a.default;
113
+ };
114
+ /**
115
+ * Returns the configured value as a number. Throws if a value is present but
116
+ * not finite — silent NaN propagation has bitten apps before.
117
+ */
118
+ var getNumber = function (key) {
119
+ var raw = getString(key);
120
+ if (raw === undefined) {
121
+ return undefined;
122
+ }
123
+ // Number() rejects partially-numeric strings like "5000ms" (returns NaN)
124
+ // whereas parseFloat would silently truncate to 5000.
125
+ var parsed = Number(raw);
126
+ if (!Number.isFinite(parsed)) {
127
+ throw new Error("Config key \"".concat(key, "\" is not a valid number: ").concat(JSON.stringify(raw)));
128
+ }
129
+ return parsed;
130
+ };
131
+ /**
132
+ * Returns true iff the string value equals "true" (case-insensitive). Mirrors
133
+ * the existing `process.env.X === "true"` idiom.
134
+ */
135
+ var getBoolean = function (key) {
136
+ var raw = getString(key);
137
+ return raw !== undefined && raw.toLowerCase() === "true";
138
+ };
139
+ /**
140
+ * Parses a JSON-encoded config value. Returns undefined if unset; throws on
141
+ * malformed JSON so misconfiguration fails loud at the call site rather than
142
+ * producing silent runtime errors later.
143
+ */
144
+ var getJSON = function (key) {
145
+ var raw = getString(key);
146
+ if (raw === undefined) {
147
+ return undefined;
148
+ }
149
+ try {
150
+ return JSON.parse(raw);
151
+ }
152
+ catch (error) {
153
+ throw new Error("Config key \"".concat(key, "\" is not valid JSON: ").concat(error.message));
154
+ }
155
+ };
156
+ /**
157
+ * Registers a loader that returns the env map (typically backed by an
158
+ * admin-editable Mongoose document). Called once at app startup before
159
+ * the first `Config.refresh()`.
160
+ */
161
+ var setEnvLoader = function (loader) {
162
+ envLoader = loader;
163
+ };
164
+ /**
165
+ * Reloads the in-memory cache by invoking the registered env loader. No-op
166
+ * (clears cache) if no loader has been registered.
167
+ */
168
+ var refresh = function () { return __awaiter(void 0, void 0, void 0, function () {
169
+ return __generator(this, function (_a) {
170
+ switch (_a.label) {
171
+ case 0:
172
+ if (!envLoader) {
173
+ cachedEnv = {};
174
+ return [2 /*return*/];
175
+ }
176
+ return [4 /*yield*/, envLoader()];
177
+ case 1:
178
+ cachedEnv = _a.sent();
179
+ return [2 /*return*/];
180
+ }
181
+ });
182
+ }); };
183
+ /** Replaces the cache directly. Intended for the envConfigurationPlugin and tests. */
184
+ var setCachedEnv = function (env) {
185
+ cachedEnv = env;
186
+ };
187
+ /**
188
+ * Sets an in-process override for `key`. Highest precedence — wins over
189
+ * the cached env map. Intended for tests and bootstrap helpers.
190
+ */
191
+ var setOverride = function (key, value) {
192
+ overrides.set(key, value);
193
+ };
194
+ /** Clears every override. Call from afterEach in tests. */
195
+ var clearOverrides = function () {
196
+ overrides.clear();
197
+ };
198
+ /** Returns the registered default (if any) for `key`. */
199
+ var getDefault = function (key) {
200
+ var _a;
201
+ return (_a = REGISTRY[key]) === null || _a === void 0 ? void 0 : _a.default;
202
+ };
203
+ /** Returns the registration metadata for `key`, including secret/description. */
204
+ var getRegistration = function (key) {
205
+ return REGISTRY[key];
206
+ };
207
+ /** Returns the registered keys, sorted. Used by the admin UI. */
208
+ var getRegisteredKeys = function () {
209
+ return Object.keys(REGISTRY).sort();
210
+ };
211
+ /** Returns true if `key` was registered. */
212
+ var isRegistered = function (key) {
213
+ return key in REGISTRY;
214
+ };
215
+ /** Removes every registered key. Intended for tests. */
216
+ var clearRegistryForTesting = function () {
217
+ var e_1, _a;
218
+ try {
219
+ for (var _b = __values(Object.keys(REGISTRY)), _c = _b.next(); !_c.done; _c = _b.next()) {
220
+ var key = _c.value;
221
+ delete REGISTRY[key];
222
+ }
223
+ }
224
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
225
+ finally {
226
+ try {
227
+ if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
228
+ }
229
+ finally { if (e_1) throw e_1.error; }
230
+ }
231
+ };
232
+ exports.Config = {
233
+ clearOverrides: clearOverrides,
234
+ clearRegistryForTesting: clearRegistryForTesting,
235
+ get: getString,
236
+ getBoolean: getBoolean,
237
+ getDefault: getDefault,
238
+ getJSON: getJSON,
239
+ getNumber: getNumber,
240
+ getRegisteredKeys: getRegisteredKeys,
241
+ getRegistration: getRegistration,
242
+ isRegistered: isRegistered,
243
+ refresh: refresh,
244
+ register: register,
245
+ setCachedEnv: setCachedEnv,
246
+ setEnvLoader: setEnvLoader,
247
+ setOverride: setOverride,
248
+ };
@@ -0,0 +1 @@
1
+ export {};