@terreno/api 0.13.3 → 0.14.1

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 +136 -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 +30 -17
  15. package/dist/betterAuthSetup.test.js +1 -0
  16. package/dist/config.d.ts +48 -0
  17. package/dist/config.js +257 -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 +111 -12
  31. package/dist/errors.test.js +16 -1
  32. package/dist/example.js +19 -7
  33. package/dist/expressServer.d.ts +10 -9
  34. package/dist/expressServer.js +62 -53
  35. package/dist/expressServer.test.js +165 -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 +724 -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 +3066 -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 +384 -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 +94 -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 +59 -22
  115. package/src/config.test.ts +255 -0
  116. package/src/config.ts +216 -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 +118 -38
  125. package/src/example.ts +49 -21
  126. package/src/express.d.ts +18 -1
  127. package/src/expressServer.test.ts +147 -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 +572 -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 +2465 -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 +321 -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/errors.js CHANGED
@@ -58,11 +58,27 @@ var __values = (this && this.__values) || function(o) {
58
58
  };
59
59
  throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
60
60
  };
61
+ var __read = (this && this.__read) || function (o, n) {
62
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
63
+ if (!m) return o;
64
+ var i = m.call(o), r, ar = [], e;
65
+ try {
66
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
67
+ }
68
+ catch (error) { e = { error: error }; }
69
+ finally {
70
+ try {
71
+ if (r && !r.done && (m = i["return"])) m.call(i);
72
+ }
73
+ finally { if (e) throw e.error; }
74
+ }
75
+ return ar;
76
+ };
61
77
  Object.defineProperty(exports, "__esModule", { value: true });
62
- exports.apiErrorMiddleware = exports.apiUnauthorizedMiddleware = exports.getAPIErrorBody = exports.getDisableExternalErrorTracking = exports.isAPIError = exports.errorsPlugin = exports.APIError = void 0;
78
+ exports.apiFallthroughErrorMiddleware = exports.apiErrorMiddleware = exports.mongooseErrorToAPIError = exports.apiUnauthorizedMiddleware = exports.getAPIErrorBody = exports.getDisableExternalErrorTracking = exports.errorStack = exports.errorMessage = exports.isAPIError = exports.errorsPlugin = exports.APIError = void 0;
63
79
  // https://jsonapi.org/format/#errors
64
80
  var Sentry = __importStar(require("@sentry/bun"));
65
- var mongoose_1 = require("mongoose");
81
+ var mongoose_1 = __importStar(require("mongoose"));
66
82
  var logger_1 = require("./logger");
67
83
  /**
68
84
  * APIError is a simple way to throw an error in an API route and control what is shown and the
@@ -81,9 +97,10 @@ var logger_1 = require("./logger");
81
97
  var APIError = /** @class */ (function (_super) {
82
98
  __extends(APIError, _super);
83
99
  function APIError(data) {
84
- var _a, _b;
100
+ var _this = this;
101
+ var errorStack = data.error instanceof Error && data.error.stack ? "\n".concat(data.error.stack) : "";
85
102
  // Include details in when the error is printed to the console or sent to Sentry.
86
- var _this = _super.call(this, "".concat(data.title).concat(data.detail ? ": ".concat(data.detail) : "").concat(data.error ? "\n".concat(data.error.stack) : "")) || this;
103
+ _this = _super.call(this, "".concat(data.title).concat(data.detail ? ": ".concat(data.detail) : "").concat(errorStack)) || this;
87
104
  _this.name = "APIError";
88
105
  var title = data.title, id = data.id, links = data.links, status = data.status, code = data.code, detail = data.detail, source = data.source, meta = data.meta, fields = data.fields, error = data.error;
89
106
  if (!status) {
@@ -106,7 +123,8 @@ var APIError = /** @class */ (function (_super) {
106
123
  _this.meta.fields = fields;
107
124
  }
108
125
  _this.error = error;
109
- var logMessage = "APIError(".concat(status, "): ").concat(title, " ").concat(detail ? detail : "").concat(((_a = data.error) === null || _a === void 0 ? void 0 : _a.stack) ? "\n".concat((_b = data.error) === null || _b === void 0 ? void 0 : _b.stack) : "");
126
+ var dataErrorStack = data.error instanceof Error && data.error.stack ? "\n".concat(data.error.stack) : "";
127
+ var logMessage = "APIError(".concat(status, "): ").concat(title, " ").concat(detail ? detail : "").concat(dataErrorStack);
110
128
  if (data.disableExternalErrorTracking) {
111
129
  logger_1.logger.warn(logMessage);
112
130
  }
@@ -132,7 +150,10 @@ var errorsPlugin = function (schema) {
132
150
  about: { description: "Link to documentation about this error", type: String },
133
151
  type: { description: "Link describing the error type", type: String },
134
152
  },
135
- meta: { description: "Non-standard meta information about the error", type: mongoose_1.Schema.Types.Mixed },
153
+ meta: {
154
+ description: "Non-standard meta information about the error",
155
+ type: mongoose_1.Schema.Types.Mixed,
156
+ },
136
157
  source: {
137
158
  header: { description: "HTTP header that caused the error", type: String },
138
159
  parameter: { description: "Query parameter that caused the error", type: String },
@@ -143,14 +164,30 @@ var errorsPlugin = function (schema) {
143
164
  },
144
165
  status: { description: "HTTP status code for this error", type: Number },
145
166
  title: { description: "Short summary of the error", required: true, type: String },
146
- });
167
+ }, { _id: false, strict: "throw" });
147
168
  schema.add({ apiErrors: errorSchema });
148
169
  };
149
170
  exports.errorsPlugin = errorsPlugin;
150
171
  var isAPIError = function (error) {
151
- return error.name === "APIError";
172
+ return error instanceof Error && error.name === "APIError";
152
173
  };
153
174
  exports.isAPIError = isAPIError;
175
+ /** Extract a human-readable message from an unknown error. */
176
+ var errorMessage = function (error) {
177
+ if (error instanceof Error) {
178
+ return error.message;
179
+ }
180
+ return String(error);
181
+ };
182
+ exports.errorMessage = errorMessage;
183
+ /** Extract a stack trace string from an unknown error. */
184
+ var errorStack = function (error) {
185
+ if (error instanceof Error && error.stack) {
186
+ return error.stack;
187
+ }
188
+ return String(error);
189
+ };
190
+ exports.errorStack = errorStack;
154
191
  /**
155
192
  * Safely extracts the disableExternalErrorTracking property from an error.
156
193
  * Works with both APIError instances and regular Error objects that may have
@@ -174,6 +211,7 @@ exports.getDisableExternalErrorTracking = getDisableExternalErrorTracking;
174
211
  var getAPIErrorBody = function (error) {
175
212
  var e_1, _a;
176
213
  var errorData = { status: error.status, title: error.title };
214
+ var indexable = error;
177
215
  try {
178
216
  for (var _b = __values([
179
217
  "id",
@@ -186,8 +224,8 @@ var getAPIErrorBody = function (error) {
186
224
  "disableExternalErrorTracking",
187
225
  ]), _c = _b.next(); !_c.done; _c = _b.next()) {
188
226
  var key = _c.value;
189
- if (error[key]) {
190
- errorData[key] = error[key];
227
+ if (indexable[key]) {
228
+ errorData[key] = indexable[key];
191
229
  }
192
230
  }
193
231
  }
@@ -211,15 +249,76 @@ var apiUnauthorizedMiddleware = function (err, _req, res, next) {
211
249
  }
212
250
  };
213
251
  exports.apiUnauthorizedMiddleware = apiUnauthorizedMiddleware;
252
+ /**
253
+ * Converts Mongoose validation/cast errors into client-friendly APIErrors.
254
+ */
255
+ var mongooseErrorToAPIError = function (err) {
256
+ var e_2, _a, _b;
257
+ var _c, _d;
258
+ if (err instanceof mongoose_1.default.Error.ValidationError) {
259
+ var fields = {};
260
+ try {
261
+ for (var _e = __values(Object.entries(err.errors)), _f = _e.next(); !_f.done; _f = _e.next()) {
262
+ var _g = __read(_f.value, 2), path = _g[0], subErr = _g[1];
263
+ fields[path] = subErr.message;
264
+ }
265
+ }
266
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
267
+ finally {
268
+ try {
269
+ if (_f && !_f.done && (_a = _e.return)) _a.call(_e);
270
+ }
271
+ finally { if (e_2) throw e_2.error; }
272
+ }
273
+ return new APIError({
274
+ detail: err.message,
275
+ disableExternalErrorTracking: true,
276
+ fields: fields,
277
+ status: 400,
278
+ title: "Validation failed",
279
+ });
280
+ }
281
+ if (err instanceof mongoose_1.default.Error.CastError) {
282
+ var path = (_c = err.path) !== null && _c !== void 0 ? _c : "field";
283
+ return new APIError({
284
+ detail: "Invalid value for ".concat(path),
285
+ disableExternalErrorTracking: true,
286
+ fields: (_b = {},
287
+ _b[path] = "Expected ".concat((_d = err.kind) !== null && _d !== void 0 ? _d : "a valid value", ", got ").concat(JSON.stringify(err.value)),
288
+ _b),
289
+ status: 400,
290
+ title: "Validation failed",
291
+ });
292
+ }
293
+ return null;
294
+ };
295
+ exports.mongooseErrorToAPIError = mongooseErrorToAPIError;
214
296
  var apiErrorMiddleware = function (err, _req, res, next) {
215
297
  if ((0, exports.isAPIError)(err)) {
216
298
  if (!err.disableExternalErrorTracking) {
217
299
  Sentry.captureException(err);
218
300
  }
219
301
  res.status(err.status).json((0, exports.getAPIErrorBody)(err)).send();
302
+ return;
220
303
  }
221
- else {
222
- next(err);
304
+ var mongooseError = (0, exports.mongooseErrorToAPIError)(err);
305
+ if (mongooseError) {
306
+ res.status(mongooseError.status).json((0, exports.getAPIErrorBody)(mongooseError)).send();
307
+ return;
223
308
  }
309
+ next(err);
224
310
  };
225
311
  exports.apiErrorMiddleware = apiErrorMiddleware;
312
+ /**
313
+ * Final Express error handler for unexpected errors. Always returns JSON so
314
+ * clients (e.g. RTK Query) can parse the response.
315
+ */
316
+ var apiFallthroughErrorMiddleware = function (err, _req, res, _next) {
317
+ logger_1.logger.error("Fallthrough error: ".concat(err).concat(err.stack ? "\n".concat(err.stack) : ""));
318
+ Sentry.captureException(err);
319
+ if (res.headersSent) {
320
+ return;
321
+ }
322
+ res.status(500).json({ status: 500, title: "Internal server error" }).send();
323
+ };
324
+ exports.apiFallthroughErrorMiddleware = apiFallthroughErrorMiddleware;
@@ -35,7 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  var bun_test_1 = require("bun:test");
37
37
  var Sentry = __importStar(require("@sentry/bun"));
38
- var mongoose_1 = require("mongoose");
38
+ var mongoose_1 = __importStar(require("mongoose"));
39
39
  var errors_1 = require("./errors");
40
40
  var buildResponse = function () {
41
41
  var res = {
@@ -277,4 +277,19 @@ var buildResponse = function () {
277
277
  (0, bun_test_1.expect)(next).toHaveBeenCalledWith(err);
278
278
  (0, bun_test_1.expect)(res.status).not.toHaveBeenCalled();
279
279
  });
280
+ (0, bun_test_1.it)("converts Mongoose CastError to a 400 APIError response", function () {
281
+ var err = new mongoose_1.default.Error.CastError("Number", "not-a-number", "general.maxUploadSizeMb");
282
+ (0, errors_1.apiErrorMiddleware)(err, req, res, next);
283
+ (0, bun_test_1.expect)(res.status).toHaveBeenCalledWith(400);
284
+ (0, bun_test_1.expect)(res.json).toHaveBeenCalledWith(bun_test_1.expect.objectContaining({
285
+ meta: bun_test_1.expect.objectContaining({
286
+ fields: bun_test_1.expect.objectContaining({
287
+ "general.maxUploadSizeMb": bun_test_1.expect.stringContaining("Expected Number"),
288
+ }),
289
+ }),
290
+ status: 400,
291
+ title: "Validation failed",
292
+ }));
293
+ (0, bun_test_1.expect)(next).not.toHaveBeenCalled();
294
+ });
280
295
  });
package/dist/example.js CHANGED
@@ -67,20 +67,32 @@ mongoose_1.default
67
67
  var userSchema = new mongoose_1.Schema({
68
68
  admin: { default: false, description: "Whether the user has admin privileges", type: Boolean },
69
69
  username: { description: "The user's username", type: String },
70
- });
70
+ }, { strict: "throw", toJSON: { virtuals: true }, toObject: { virtuals: true } });
71
+ // biome-ignore lint/suspicious/noExplicitAny: passport-local-mongoose's plugin type is incompatible with mongoose Schema generics
71
72
  userSchema.plugin(passport_local_mongoose_1.default, { usernameField: "email" });
72
73
  userSchema.plugin(plugins_1.createdUpdatedPlugin);
74
+ userSchema.plugin(plugins_1.isDeletedPlugin);
75
+ userSchema.plugin(plugins_1.findOneOrNone);
76
+ userSchema.plugin(plugins_1.findExactlyOne);
73
77
  userSchema.plugin(plugins_1.baseUserPlugin);
74
78
  var UserModel = (0, mongoose_1.model)("User", userSchema);
75
79
  var schema = new mongoose_1.Schema({
76
80
  calories: { description: "Number of calories in the food", type: Number },
77
81
  created: { description: "When this food was created", type: Date },
78
- hidden: { default: false, description: "Whether this food is hidden from listings", type: Boolean },
82
+ hidden: {
83
+ default: false,
84
+ description: "Whether this food is hidden from listings",
85
+ type: Boolean,
86
+ },
79
87
  name: { description: "The name of the food", type: String },
80
88
  ownerId: { description: "The user who owns this food entry", ref: "User", type: "ObjectId" },
81
- });
89
+ }, { strict: "throw", toJSON: { virtuals: true }, toObject: { virtuals: true } });
90
+ schema.plugin(plugins_1.createdUpdatedPlugin);
91
+ schema.plugin(plugins_1.isDeletedPlugin);
92
+ schema.plugin(plugins_1.findOneOrNone);
93
+ schema.plugin(plugins_1.findExactlyOne);
82
94
  var FoodModel = (0, mongoose_1.model)("Food", schema);
83
- function getBaseServer() {
95
+ var getBaseServer = function () {
84
96
  var app = (0, express_1.default)();
85
97
  app.use(function (req, res, next) {
86
98
  res.header("Access-Control-Allow-Origin", "*");
@@ -96,7 +108,7 @@ function getBaseServer() {
96
108
  app.use(express_1.default.json());
97
109
  (0, auth_1.setupAuth)(app, UserModel);
98
110
  (0, auth_1.addAuthRoutes)(app, UserModel);
99
- function addRoutes(router, options) {
111
+ var addRoutes = function (router, options) {
100
112
  router.use("/food", (0, api_1.modelRouter)(FoodModel, __assign(__assign({}, options), { openApiOverwrite: {
101
113
  get: { responses: { 200: { description: "Get all the food" } } },
102
114
  }, permissions: {
@@ -106,7 +118,7 @@ function getBaseServer() {
106
118
  read: [permissions_1.Permissions.IsAny],
107
119
  update: [permissions_1.Permissions.IsOwner],
108
120
  }, queryFields: ["name", "calories", "created", "ownerId", "hidden"] })));
109
- }
121
+ };
110
122
  return (0, expressServer_1.setupServer)({
111
123
  addRoutes: addRoutes,
112
124
  loggingOptions: {
@@ -114,5 +126,5 @@ function getBaseServer() {
114
126
  },
115
127
  userModel: UserModel,
116
128
  });
117
- }
129
+ };
118
130
  getBaseServer();
@@ -5,13 +5,13 @@ import type { ModelRouterOptions } from "./api";
5
5
  import { type UserModel as UserMongooseModel } from "./auth";
6
6
  import { type GitHubAuthOptions } from "./githubAuth";
7
7
  import { type LoggingOptions } from "./logger";
8
- export declare function setupEnvironment(): void;
8
+ export declare const setupEnvironment: () => void;
9
9
  export type AddRoutes = (router: Router, options?: Partial<ModelRouterOptions<unknown>>) => void;
10
- export declare function logRequests(req: any, res: any, next: any): void;
11
- export declare function createRouter(rootPath: string, addRoutes: AddRoutes, middleware?: any[]): any[];
12
- export declare function createRouterWithAuth(rootPath: string, addRoutes: (router: Router) => void, middleware?: any[]): any[];
10
+ export declare const logRequests: (req: any, res: any, next: express.NextFunction) => void;
11
+ export declare const createRouter: (rootPath: string, addRoutes: AddRoutes, middleware?: express.RequestHandler[]) => Array<string | express.RequestHandler | Router>;
12
+ export declare const createRouterWithAuth: (rootPath: string, addRoutes: (router: Router) => void, middleware?: express.RequestHandler[]) => Array<string | express.RequestHandler | Router>;
13
13
  export interface AuthOptions {
14
- generateJWTPayload?: (user: any) => Record<string, any>;
14
+ generateJWTPayload?: (user: any) => Record<string, unknown>;
15
15
  generateTokenExpiration?: (user: any) => number | jwt.SignOptions["expiresIn"];
16
16
  generateRefreshTokenExpiration?: (user: any) => number | jwt.SignOptions["expiresIn"];
17
17
  }
@@ -19,6 +19,7 @@ export interface SetupServerOptions {
19
19
  userModel: UserMongooseModel;
20
20
  addRoutes: AddRoutes;
21
21
  loggingOptions?: LoggingOptions;
22
+ logRequests?: boolean;
22
23
  authOptions?: AuthOptions;
23
24
  /**
24
25
  * GitHub OAuth configuration. When provided, enables GitHub authentication.
@@ -31,11 +32,11 @@ export interface SetupServerOptions {
31
32
  ignoreTraces?: string[];
32
33
  sentryOptions?: Sentry.BunOptions;
33
34
  }
34
- export declare function setupServer(options: SetupServerOptions): express.Application;
35
- export declare function cronjob(name: string, schedule: "hourly" | "minutely" | string, callback: () => void): void;
35
+ export declare const setupServer: (options: SetupServerOptions) => express.Application;
36
+ export declare const cronjob: (name: string, schedule: "hourly" | "minutely" | string, callback: () => void) => void;
36
37
  export interface WrapScriptOptions {
37
- onFinish?: (result?: any) => void | Promise<void>;
38
+ onFinish?: (result?: unknown) => void | Promise<void>;
38
39
  terminateTimeout?: number;
39
40
  slackChannel?: string;
40
41
  }
41
- export declare function wrapScript(func: () => Promise<any>, options?: WrapScriptOptions): Promise<void>;
42
+ export declare const wrapScript: (func: () => Promise<unknown>, options?: WrapScriptOptions) => Promise<void>;
@@ -97,13 +97,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
97
97
  return (mod && mod.__esModule) ? mod : { "default": mod };
98
98
  };
99
99
  Object.defineProperty(exports, "__esModule", { value: true });
100
- exports.setupEnvironment = setupEnvironment;
101
- exports.logRequests = logRequests;
102
- exports.createRouter = createRouter;
103
- exports.createRouterWithAuth = createRouterWithAuth;
104
- exports.setupServer = setupServer;
105
- exports.cronjob = cronjob;
106
- exports.wrapScript = wrapScript;
100
+ exports.wrapScript = exports.cronjob = exports.setupServer = exports.createRouterWithAuth = exports.createRouter = exports.logRequests = exports.setupEnvironment = void 0;
107
101
  var Sentry = __importStar(require("@sentry/bun"));
108
102
  var cors_1 = __importDefault(require("cors"));
109
103
  var cron_1 = __importDefault(require("cron"));
@@ -119,11 +113,12 @@ var logger_1 = require("./logger");
119
113
  var notifiers_1 = require("./notifiers");
120
114
  var openApiCompat_1 = require("./openApiCompat");
121
115
  var openApiEtag_1 = require("./openApiEtag");
116
+ var requestContext_1 = require("./requestContext");
122
117
  var index_1 = __importDefault(require("./vendor/wesleytodd-openapi/index"));
123
118
  var SLOW_READ_MAX = 200;
124
119
  var SLOW_WRITE_MAX = 500;
125
120
  var IS_JEST = process.env.JEST_WORKER_ID !== undefined;
126
- function setupEnvironment() {
121
+ var setupEnvironment = function () {
127
122
  if (!process.env.TOKEN_ISSUER) {
128
123
  throw new Error("TOKEN_ISSUER must be set in env.");
129
124
  }
@@ -142,12 +137,14 @@ function setupEnvironment() {
142
137
  if (!process.env.REFRESH_TOKEN_EXPIRES_IN && !IS_JEST) {
143
138
  logger_1.logger.warn("REFRESH_TOKEN_EXPIRES_IN not set so using default.");
144
139
  }
145
- }
140
+ };
141
+ exports.setupEnvironment = setupEnvironment;
142
+ // biome-ignore lint/suspicious/noExplicitAny: also called from tests with mock request/response objects
146
143
  var logRequestsFinished = function (req, res, startTime) {
147
- var _a, _b, _c;
148
- var options = ((_a = res.locals.loggingOptions) !== null && _a !== void 0 ? _a : {});
149
- var slowReadMs = (_b = options.logSlowRequestsReadMs) !== null && _b !== void 0 ? _b : SLOW_READ_MAX;
150
- var slowWriteMs = (_c = options.logSlowRequestsWriteMs) !== null && _c !== void 0 ? _c : SLOW_WRITE_MAX;
144
+ var _a, _b, _c, _d;
145
+ var options = ((_b = (_a = res.locals) === null || _a === void 0 ? void 0 : _a.loggingOptions) !== null && _b !== void 0 ? _b : {});
146
+ var slowReadMs = (_c = options.logSlowRequestsReadMs) !== null && _c !== void 0 ? _c : SLOW_READ_MAX;
147
+ var slowWriteMs = (_d = options.logSlowRequestsWriteMs) !== null && _d !== void 0 ? _d : SLOW_WRITE_MAX;
151
148
  var diff = process.hrtime.bigint() - startTime;
152
149
  var diffInMs = Number(diff) / 1000000;
153
150
  var pathName = "unknown";
@@ -180,7 +177,8 @@ var logRequestsFinished = function (req, res, startTime) {
180
177
  }
181
178
  }
182
179
  };
183
- function logRequests(req, res, next) {
180
+ // biome-ignore lint/suspicious/noExplicitAny: also called from tests with mock request/response objects
181
+ var logRequests = function (req, res, next) {
184
182
  var _a, _b, _c, _d;
185
183
  var startTime = process.hrtime.bigint();
186
184
  var userString = "";
@@ -210,35 +208,38 @@ function logRequests(req, res, next) {
210
208
  }
211
209
  (0, on_finished_1.default)(res, function () { return logRequestsFinished(req, res, startTime); });
212
210
  next();
213
- }
214
- function createRouter(rootPath, addRoutes, middleware) {
211
+ };
212
+ exports.logRequests = logRequests;
213
+ var createRouter = function (rootPath, addRoutes, middleware) {
215
214
  if (middleware === void 0) { middleware = []; }
216
- function routePathMiddleware(req, _res, next) {
215
+ var routePathMiddleware = function (req, _res, next) {
217
216
  if (!req.routeMount) {
218
217
  req.routeMount = [];
219
218
  }
220
219
  req.routeMount.push(rootPath);
221
220
  next();
222
- }
221
+ };
223
222
  var router = express_1.default.Router();
224
223
  router.use(routePathMiddleware);
225
224
  addRoutes(router);
226
225
  return __spreadArray(__spreadArray([rootPath], __read(middleware), false), [router], false);
227
- }
228
- function createRouterWithAuth(rootPath, addRoutes, middleware) {
226
+ };
227
+ exports.createRouter = createRouter;
228
+ var createRouterWithAuth = function (rootPath, addRoutes, middleware) {
229
229
  if (middleware === void 0) { middleware = []; }
230
- return createRouter(rootPath, addRoutes, __spreadArray([
230
+ return (0, exports.createRouter)(rootPath, addRoutes, __spreadArray([
231
231
  passport_1.default.authenticate("firebase-jwt", { session: false })
232
232
  ], __read(middleware), false));
233
- }
234
- function initializeRoutes(UserModel, addRoutes, options) {
233
+ };
234
+ exports.createRouterWithAuth = createRouterWithAuth;
235
+ var initializeRoutes = function (UserModel, addRoutes, options) {
235
236
  var _a;
236
237
  if (options === void 0) { options = {}; }
237
238
  var app = (0, express_1.default)();
238
239
  // Record mount paths on layers for Express 5 → OpenAPI compat
239
240
  (0, openApiCompat_1.patchAppUse)(app);
240
- // TODO: Log a warning when we hit the array limit.
241
241
  app.set("query parser", function (str) { var _a; return qs_1.default.parse(str, { arrayLimit: (_a = options.arrayLimit) !== null && _a !== void 0 ? _a : 200 }); });
242
+ app.use(requestContext_1.requestContextMiddleware);
242
243
  app.use((0, cors_1.default)({
243
244
  origin: (_a = options.corsOrigin) !== null && _a !== void 0 ? _a : "*",
244
245
  }));
@@ -249,8 +250,12 @@ function initializeRoutes(UserModel, addRoutes, options) {
249
250
  // Add login/signup/refresh_token before the JWT/auth middlewares
250
251
  (0, auth_1.addAuthRoutes)(app, UserModel, options === null || options === void 0 ? void 0 : options.authOptions);
251
252
  (0, auth_1.setupAuth)(app, UserModel);
253
+ app.use(function (req, res, next) {
254
+ (0, requestContext_1.updateRequestContextFromRequest)(req, res);
255
+ next();
256
+ });
252
257
  if (options.logRequests !== false) {
253
- app.use(logRequests);
258
+ app.use(exports.logRequests);
254
259
  }
255
260
  // Store the logging options on the request so we can access them later.
256
261
  app.use(function (_req, res, next) {
@@ -259,17 +264,21 @@ function initializeRoutes(UserModel, addRoutes, options) {
259
264
  });
260
265
  // Add Sentry scopes for session, transaction, and userId if any are set
261
266
  app.use(function (req, _res, next) {
262
- var _a;
267
+ var _a, _b;
268
+ var context = (0, requestContext_1.getCurrentRequestContext)();
263
269
  var transactionId = req.header("X-Transaction-ID");
264
- var sessionId = req.header("X-Session-ID");
270
+ var sessionId = (_a = context === null || context === void 0 ? void 0 : context.sessionId) !== null && _a !== void 0 ? _a : req.header("X-Session-ID");
271
+ if (context === null || context === void 0 ? void 0 : context.requestId) {
272
+ Sentry.getCurrentScope().setTag("request_id", context.requestId);
273
+ }
265
274
  if (transactionId) {
266
275
  Sentry.getCurrentScope().setTag("transaction_id", transactionId);
267
276
  }
268
277
  if (sessionId) {
269
278
  Sentry.getCurrentScope().setTag("session_id", sessionId);
270
279
  }
271
- if ((_a = req.user) === null || _a === void 0 ? void 0 : _a._id) {
272
- Sentry.getCurrentScope().setTag("user", req.user._id);
280
+ if ((_b = req.user) === null || _b === void 0 ? void 0 : _b._id) {
281
+ Sentry.getCurrentScope().setTag("user", String(req.user._id));
273
282
  }
274
283
  next();
275
284
  });
@@ -299,16 +308,10 @@ function initializeRoutes(UserModel, addRoutes, options) {
299
308
  // Catch any thrown APIErrors and return them in an OpenAPI compatible format
300
309
  app.use(errors_1.apiUnauthorizedMiddleware);
301
310
  app.use(errors_1.apiErrorMiddleware);
302
- app.use(function onError(err, _req, res, _next) {
303
- logger_1.logger.error("Fallthrough error: ".concat(err).concat((err === null || err === void 0 ? void 0 : err.stack) ? "\n".concat(err.stack) : "", "}"));
304
- Sentry.captureException(err);
305
- res.statusCode = 500;
306
- res.end("".concat(res.sentry, "\n"));
307
- });
311
+ app.use(errors_1.apiFallthroughErrorMiddleware);
308
312
  return app;
309
- }
310
- // Sets up the routes and returns the app.
311
- function setupServer(options) {
313
+ };
314
+ var setupServer = function (options) {
312
315
  var UserModel = options.userModel;
313
316
  var addRoutes = options.addRoutes;
314
317
  (0, logger_1.setupLogging)(options.loggingOptions);
@@ -319,10 +322,13 @@ function setupServer(options) {
319
322
  authOptions: options.authOptions,
320
323
  corsOrigin: options.corsOrigin,
321
324
  githubAuth: options.githubAuth,
325
+ loggingOptions: options.loggingOptions,
326
+ logRequests: options.logRequests,
322
327
  });
323
328
  }
324
329
  catch (error) {
325
- logger_1.logger.error("Error initializing routes: ".concat(error.stack));
330
+ var stack = error instanceof Error && error.stack ? error.stack : String(error);
331
+ logger_1.logger.error("Error initializing routes: ".concat(stack));
326
332
  throw error;
327
333
  }
328
334
  if (!options.skipListen) {
@@ -333,14 +339,15 @@ function setupServer(options) {
333
339
  });
334
340
  }
335
341
  catch (error) {
336
- logger_1.logger.error("Error trying to start HTTP server: ".concat(error, "\n").concat(error.stack));
342
+ var stack = error instanceof Error ? error.stack : String(error);
343
+ logger_1.logger.error("Error trying to start HTTP server: ".concat(error, "\n").concat(stack));
337
344
  process.exit(1);
338
345
  }
339
346
  }
340
347
  return app;
341
- }
342
- // Convenience method to execute cronjobs with an always-running server.
343
- function cronjob(name, schedule, callback) {
348
+ };
349
+ exports.setupServer = setupServer;
350
+ var cronjob = function (name, schedule, callback) {
344
351
  var cronSchedule = schedule === "hourly" ? "0 * * * *" : schedule === "minutely" ? "* * * * *" : schedule;
345
352
  logger_1.logger.info("Adding cronjob ".concat(name, ", running at: ").concat(cronSchedule));
346
353
  try {
@@ -349,13 +356,15 @@ function cronjob(name, schedule, callback) {
349
356
  catch (error) {
350
357
  throw new Error("Failed to create cronjob: ".concat(error));
351
358
  }
352
- }
353
- // Wrap up a script with some helpers, such as catching errors, reporting them to sentry, notifying
354
- // Slack of runs, etc. Also supports timeouts.
355
- function wrapScript(func_1) {
356
- return __awaiter(this, arguments, void 0, function (func, options) {
359
+ };
360
+ exports.cronjob = cronjob;
361
+ var wrapScript = function (func_1) {
362
+ var args_1 = [];
363
+ for (var _i = 1; _i < arguments.length; _i++) {
364
+ args_1[_i - 1] = arguments[_i];
365
+ }
366
+ return __awaiter(void 0, __spreadArray([func_1], __read(args_1), false), void 0, function (func, options) {
357
367
  var name, warnTime_1, closeTime, result, error_1;
358
- var _this = this;
359
368
  var _a, _b, _c;
360
369
  if (options === void 0) { options = {}; }
361
370
  return __generator(this, function (_d) {
@@ -371,7 +380,7 @@ function wrapScript(func_1) {
371
380
  if (options.terminateTimeout !== 0) {
372
381
  warnTime_1 = (((_b = options.terminateTimeout) !== null && _b !== void 0 ? _b : 300) / 2) * 1000;
373
382
  closeTime = ((_c = options.terminateTimeout) !== null && _c !== void 0 ? _c : 300) * 1000;
374
- setTimeout(function () { return __awaiter(_this, void 0, void 0, function () {
383
+ setTimeout(function () { return __awaiter(void 0, void 0, void 0, function () {
375
384
  var msg;
376
385
  return __generator(this, function (_a) {
377
386
  switch (_a.label) {
@@ -385,7 +394,7 @@ function wrapScript(func_1) {
385
394
  }
386
395
  });
387
396
  }); }, warnTime_1);
388
- setTimeout(function () { return __awaiter(_this, void 0, void 0, function () {
397
+ setTimeout(function () { return __awaiter(void 0, void 0, void 0, function () {
389
398
  var msg;
390
399
  return __generator(this, function (_a) {
391
400
  switch (_a.label) {
@@ -432,10 +441,10 @@ function wrapScript(func_1) {
432
441
  case 9: return [4 /*yield*/, (0, notifiers_1.sendToSlack)("Success running script ".concat(name, ": ").concat(result))];
433
442
  case 10:
434
443
  _d.sent();
435
- // Unclear why we have to exit here to prevent the script for continuing to run.
436
444
  process.exit(0);
437
445
  return [2 /*return*/];
438
446
  }
439
447
  });
440
448
  });
441
- }
449
+ };
450
+ exports.wrapScript = wrapScript;