@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/api.js CHANGED
@@ -129,6 +129,7 @@ exports.modelRouter = modelRouter;
129
129
  var Sentry = __importStar(require("@sentry/bun"));
130
130
  var express_1 = __importDefault(require("express"));
131
131
  var cloneDeep_1 = __importDefault(require("lodash/cloneDeep"));
132
+ var luxon_1 = require("luxon");
132
133
  var mongoose_1 = __importDefault(require("mongoose"));
133
134
  var auth_1 = require("./auth");
134
135
  var errors_1 = require("./errors");
@@ -136,9 +137,12 @@ var logger_1 = require("./logger");
136
137
  var openApi_1 = require("./openApi");
137
138
  var openApiValidator_1 = require("./openApiValidator");
138
139
  var permissions_1 = require("./permissions");
140
+ var registry_1 = require("./realtime/registry");
139
141
  var transformers_1 = require("./transformers");
140
142
  var utils_1 = require("./utils");
141
- var addPopulateToQuery = function (builtQuery, populatePaths) {
143
+ var addPopulateToQuery = function (
144
+ // biome-ignore lint/suspicious/noExplicitAny: mongoose Query type parameters vary widely across populated/unpopulated documents — caller passes concrete types
145
+ builtQuery, populatePaths) {
142
146
  var e_1, _a;
143
147
  var paths = populatePaths !== null && populatePaths !== void 0 ? populatePaths : [];
144
148
  var query = builtQuery;
@@ -172,13 +176,15 @@ var COMPLEX_QUERY_PARAMS = ["$and", "$or"];
172
176
  var checkQueryParamAllowed = function (queryParam, queryParamValue, queryFields) {
173
177
  var e_2, _a, e_3, _b;
174
178
  if (queryFields === void 0) { queryFields = []; }
179
+ // Cast for iteration through complex query values
180
+ var complexValue = queryParamValue;
175
181
  // Check the values of each of the complex query params. We don't support recursive queries here,
176
182
  // just one level of and/or
177
183
  if (COMPLEX_QUERY_PARAMS.includes(queryParam)) {
178
184
  try {
179
185
  // Complex query of the form `$and: [{key1: value1}, {key2: value2}]`
180
- for (var queryParamValue_1 = __values(queryParamValue), queryParamValue_1_1 = queryParamValue_1.next(); !queryParamValue_1_1.done; queryParamValue_1_1 = queryParamValue_1.next()) {
181
- var subQuery = queryParamValue_1_1.value;
186
+ for (var complexValue_1 = __values(complexValue), complexValue_1_1 = complexValue_1.next(); !complexValue_1_1.done; complexValue_1_1 = complexValue_1.next()) {
187
+ var subQuery = complexValue_1_1.value;
182
188
  try {
183
189
  for (var _c = (e_3 = void 0, __values(Object.keys(subQuery))), _d = _c.next(); !_d.done; _d = _c.next()) {
184
190
  var subKey = _d.value;
@@ -197,7 +203,7 @@ var checkQueryParamAllowed = function (queryParam, queryParamValue, queryFields)
197
203
  catch (e_2_1) { e_2 = { error: e_2_1 }; }
198
204
  finally {
199
205
  try {
200
- if (queryParamValue_1_1 && !queryParamValue_1_1.done && (_a = queryParamValue_1.return)) _a.call(queryParamValue_1);
206
+ if (complexValue_1_1 && !complexValue_1_1.done && (_a = complexValue_1.return)) _a.call(complexValue_1);
201
207
  }
202
208
  finally { if (e_2) throw e_2.error; }
203
209
  }
@@ -299,13 +305,29 @@ function modelRouter(pathOrModel, modelOrOptions, maybeOptions) {
299
305
  }
300
306
  var router = _buildModelRouter(model, options);
301
307
  if (path !== undefined) {
308
+ // Register for real-time sync if configured
309
+ if (options.realtime) {
310
+ (0, registry_1.registerRealtime)({
311
+ collectionName: model.collection.collectionName,
312
+ config: options.realtime,
313
+ modelName: model.modelName,
314
+ options: options,
315
+ routePath: path,
316
+ });
317
+ }
302
318
  return {
303
319
  __type: "modelRouter",
304
- _buildWithOpenApi: function (openApi) { return _buildModelRouter(model, __assign(__assign({}, options), { openApi: openApi })); },
320
+ _buildWithOpenApi: function (openApi) {
321
+ return _buildModelRouter(model, __assign(__assign({}, options), { openApi: openApi }));
322
+ },
305
323
  path: path,
306
324
  router: router,
307
325
  };
308
326
  }
327
+ if (options.realtime) {
328
+ logger_1.logger.warn("modelRouter for ".concat(model.modelName, " has realtime config but was called without a path. ") +
329
+ "Realtime sync only works with the three-argument form: modelRouter('/path', Model, options)");
330
+ }
309
331
  return router;
310
332
  }
311
333
  function _buildModelRouter(model, options) {
@@ -342,7 +364,7 @@ function _buildModelRouter(model, options) {
342
364
  disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error),
343
365
  error: error,
344
366
  status: 400,
345
- title: error.message,
367
+ title: (0, errors_1.errorMessage)(error),
346
368
  });
347
369
  }
348
370
  if (!options.preCreate) return [3 /*break*/, 5];
@@ -362,7 +384,7 @@ function _buildModelRouter(model, options) {
362
384
  disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_1),
363
385
  error: error_1,
364
386
  status: 400,
365
- title: "preCreate hook error: ".concat(error_1.message),
387
+ title: "preCreate hook error: ".concat((0, errors_1.errorMessage)(error_1)),
366
388
  });
367
389
  case 4:
368
390
  if (body === undefined) {
@@ -393,7 +415,7 @@ function _buildModelRouter(model, options) {
393
415
  _a.trys.push([6, 8, , 9]);
394
416
  return [4 /*yield*/, model.create(body)];
395
417
  case 7:
396
- data = _a.sent();
418
+ data = (_a.sent());
397
419
  return [3 /*break*/, 9];
398
420
  case 8:
399
421
  error_2 = _a.sent();
@@ -401,7 +423,7 @@ function _buildModelRouter(model, options) {
401
423
  disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_2),
402
424
  error: error_2,
403
425
  status: 400,
404
- title: error_2.message,
426
+ title: (0, errors_1.errorMessage)(error_2),
405
427
  });
406
428
  case 9:
407
429
  if (!options.populatePaths) return [3 /*break*/, 13];
@@ -420,7 +442,7 @@ function _buildModelRouter(model, options) {
420
442
  disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_3),
421
443
  error: error_3,
422
444
  status: 400,
423
- title: "Populate error: ".concat(error_3.message),
445
+ title: "Populate error: ".concat((0, errors_1.errorMessage)(error_3)),
424
446
  });
425
447
  case 13:
426
448
  if (!options.postCreate) return [3 /*break*/, 17];
@@ -437,7 +459,7 @@ function _buildModelRouter(model, options) {
437
459
  disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_4),
438
460
  error: error_4,
439
461
  status: 400,
440
- title: "postCreate hook error: ".concat(error_4.message),
462
+ title: "postCreate hook error: ".concat((0, errors_1.errorMessage)(error_4)),
441
463
  });
442
464
  case 17:
443
465
  _a.trys.push([17, 19, , 20]);
@@ -450,7 +472,7 @@ function _buildModelRouter(model, options) {
450
472
  throw new errors_1.APIError({
451
473
  disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_5),
452
474
  error: error_5,
453
- title: "responseHandler error: ".concat(error_5.message),
475
+ title: "responseHandler error: ".concat((0, errors_1.errorMessage)(error_5)),
454
476
  });
455
477
  case 20: return [2 /*return*/];
456
478
  }
@@ -577,14 +599,14 @@ function _buildModelRouter(model, options) {
577
599
  _o.trys.push([7, 9, , 10]);
578
600
  return [4 /*yield*/, populatedQuery.exec()];
579
601
  case 8:
580
- data = _o.sent();
602
+ data = (_o.sent());
581
603
  return [3 /*break*/, 10];
582
604
  case 9:
583
605
  error_7 = _o.sent();
584
606
  throw new errors_1.APIError({
585
607
  disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_7),
586
608
  error: error_7,
587
- title: "List error: ".concat(error_7.stack),
609
+ title: "List error: ".concat((0, errors_1.errorStack)(error_7)),
588
610
  });
589
611
  case 10:
590
612
  _o.trys.push([10, 12, , 13]);
@@ -597,7 +619,7 @@ function _buildModelRouter(model, options) {
597
619
  throw new errors_1.APIError({
598
620
  disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_8),
599
621
  error: error_8,
600
- title: "responseHandler error: ".concat(error_8.message),
622
+ title: "responseHandler error: ".concat((0, errors_1.errorMessage)(error_8)),
601
623
  });
602
624
  case 13:
603
625
  try {
@@ -631,7 +653,7 @@ function _buildModelRouter(model, options) {
631
653
  throw new errors_1.APIError({
632
654
  disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error),
633
655
  error: error,
634
- title: "Serialization error: ".concat(error.message),
656
+ title: "Serialization error: ".concat((0, errors_1.errorMessage)(error)),
635
657
  });
636
658
  }
637
659
  return [2 /*return*/];
@@ -660,7 +682,7 @@ function _buildModelRouter(model, options) {
660
682
  throw new errors_1.APIError({
661
683
  disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_9),
662
684
  error: error_9,
663
- title: "responseHandler error: ".concat(error_9.message),
685
+ title: "responseHandler error: ".concat((0, errors_1.errorMessage)(error_9)),
664
686
  });
665
687
  case 4: return [2 /*return*/];
666
688
  }
@@ -680,10 +702,10 @@ function _buildModelRouter(model, options) {
680
702
  (0, permissions_1.permissionMiddleware)(model, options),
681
703
  updateValidation,
682
704
  ], (0, exports.asyncHandler)(function (req, res) { return __awaiter(_this, void 0, void 0, function () {
683
- var doc, body, error_10, prevDoc, error_11, populateQuery, error_12, serialized, error_13;
684
- var _a;
685
- return __generator(this, function (_b) {
686
- switch (_b.label) {
705
+ var doc, body, bodyUpdatedAt, error_10, preciseUnmodifiedSince, httpUnmodifiedSince, timestampValue, httpTimestampValue, usingPreciseHeader, usingHttpHeader, clientTimestamp, docRecord, serverTimestamp, serverTimestampValue, serialized, prevDoc, error_11, populateQuery, error_12, serialized, error_13;
706
+ var _a, _b;
707
+ return __generator(this, function (_c) {
708
+ switch (_c.label) {
687
709
  case 0:
688
710
  doc = req.obj;
689
711
  try {
@@ -697,23 +719,24 @@ function _buildModelRouter(model, options) {
697
719
  disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error),
698
720
  error: error,
699
721
  status: 403,
700
- title: "PATCH failed on ".concat(req.params.id, " for user ").concat((_a = req.user) === null || _a === void 0 ? void 0 : _a.id, ": ").concat(error.message),
722
+ title: "PATCH failed on ".concat(req.params.id, " for user ").concat((_a = req.user) === null || _a === void 0 ? void 0 : _a.id, ": ").concat((0, errors_1.errorMessage)(error)),
701
723
  });
702
724
  }
725
+ bodyUpdatedAt = req.body._updatedAt;
726
+ delete req.body._updatedAt;
727
+ if (body && typeof body === "object") {
728
+ delete body._updatedAt;
729
+ }
703
730
  if (!options.preUpdate) return [3 /*break*/, 5];
704
- _b.label = 1;
731
+ _c.label = 1;
705
732
  case 1:
706
- _b.trys.push([1, 3, , 4]);
733
+ _c.trys.push([1, 3, , 4]);
707
734
  return [4 /*yield*/, options.preUpdate(body, req)];
708
735
  case 2:
709
- // TODO: Send flattened dot notation body to preUpdate, then merge the returned body
710
- // with the original body, maintaining the dot notation. This way we don't have to write
711
- // two preUpdate branches downstream, one looking at the dot notation style and
712
- // one looking at normal object style.
713
- body = _b.sent();
736
+ body = _c.sent();
714
737
  return [3 /*break*/, 4];
715
738
  case 3:
716
- error_10 = _b.sent();
739
+ error_10 = _c.sent();
717
740
  if ((0, errors_1.isAPIError)(error_10)) {
718
741
  throw error_10;
719
742
  }
@@ -721,7 +744,7 @@ function _buildModelRouter(model, options) {
721
744
  disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_10),
722
745
  error: error_10,
723
746
  status: 400,
724
- title: "preUpdate hook error on ".concat(req.params.id, ": ").concat(error_10.message),
747
+ title: "preUpdate hook error on ".concat(req.params.id, ": ").concat((0, errors_1.errorMessage)(error_10)),
725
748
  });
726
749
  case 4:
727
750
  if (body === undefined) {
@@ -738,64 +761,117 @@ function _buildModelRouter(model, options) {
738
761
  title: "Update not allowed",
739
762
  });
740
763
  }
741
- _b.label = 5;
764
+ _c.label = 5;
742
765
  case 5:
743
- prevDoc = (0, cloneDeep_1.default)(doc);
744
- _b.label = 6;
766
+ preciseUnmodifiedSince = req.headers["x-unmodified-since-iso"];
767
+ httpUnmodifiedSince = req.headers["if-unmodified-since"];
768
+ timestampValue = Array.isArray(preciseUnmodifiedSince)
769
+ ? preciseUnmodifiedSince[0]
770
+ : preciseUnmodifiedSince;
771
+ httpTimestampValue = Array.isArray(httpUnmodifiedSince)
772
+ ? httpUnmodifiedSince[0]
773
+ : httpUnmodifiedSince;
774
+ if (!(timestampValue || httpTimestampValue || bodyUpdatedAt)) return [3 /*break*/, 7];
775
+ usingPreciseHeader = Boolean(timestampValue);
776
+ usingHttpHeader = !usingPreciseHeader && Boolean(httpTimestampValue);
777
+ clientTimestamp = timestampValue
778
+ ? luxon_1.DateTime.fromISO(timestampValue)
779
+ : httpTimestampValue
780
+ ? luxon_1.DateTime.fromHTTP(httpTimestampValue)
781
+ : luxon_1.DateTime.fromISO(bodyUpdatedAt);
782
+ if (!clientTimestamp.isValid) {
783
+ throw new errors_1.APIError({
784
+ detail: usingPreciseHeader
785
+ ? "X-Unmodified-Since-ISO header could not be parsed as an ISO date"
786
+ : usingHttpHeader
787
+ ? "If-Unmodified-Since header could not be parsed as an HTTP date"
788
+ : "_updatedAt body field could not be parsed as an ISO date",
789
+ status: 400,
790
+ title: "Invalid conflict-detection timestamp",
791
+ });
792
+ }
793
+ docRecord = doc;
794
+ serverTimestamp = null;
795
+ serverTimestampValue = (_b = docRecord.updated) !== null && _b !== void 0 ? _b : docRecord.created;
796
+ if (serverTimestampValue instanceof Date) {
797
+ serverTimestamp = luxon_1.DateTime.fromJSDate(serverTimestampValue);
798
+ }
799
+ else if (typeof serverTimestampValue === "string") {
800
+ serverTimestamp = luxon_1.DateTime.fromISO(serverTimestampValue);
801
+ }
802
+ if (serverTimestamp && !serverTimestamp.isValid) {
803
+ throw new errors_1.APIError({
804
+ detail: "Document timestamp could not be parsed as a date",
805
+ status: 400,
806
+ title: "Invalid server timestamp",
807
+ });
808
+ }
809
+ if (!(serverTimestamp && clientTimestamp < serverTimestamp)) return [3 /*break*/, 7];
810
+ return [4 /*yield*/, responseHandler(doc, "update", req, options)];
745
811
  case 6:
746
- _b.trys.push([6, 8, , 9]);
747
- doc.set(body);
748
- return [4 /*yield*/, doc.save()];
812
+ serialized = _c.sent();
813
+ return [2 /*return*/, res.status(409).json({
814
+ data: serialized,
815
+ error: "Conflict",
816
+ message: "Document was modified since your last read",
817
+ })];
749
818
  case 7:
750
- _b.sent();
751
- return [3 /*break*/, 9];
819
+ prevDoc = (0, cloneDeep_1.default)(doc);
820
+ _c.label = 8;
752
821
  case 8:
753
- error_11 = _b.sent();
822
+ _c.trys.push([8, 10, , 11]);
823
+ doc.set(body);
824
+ return [4 /*yield*/, doc.save()];
825
+ case 9:
826
+ _c.sent();
827
+ return [3 /*break*/, 11];
828
+ case 10:
829
+ error_11 = _c.sent();
754
830
  throw new errors_1.APIError({
755
831
  disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_11),
756
832
  error: error_11,
757
833
  status: 400,
758
- title: "preUpdate hook save error on ".concat(req.params.id, ": ").concat(error_11.message),
834
+ title: "preUpdate hook save error on ".concat(req.params.id, ": ").concat((0, errors_1.errorMessage)(error_11)),
759
835
  });
760
- case 9:
761
- if (!options.populatePaths) return [3 /*break*/, 11];
836
+ case 11:
837
+ if (!options.populatePaths) return [3 /*break*/, 13];
762
838
  populateQuery = model.findById(doc._id);
763
839
  populateQuery = (0, exports.addPopulateToQuery)(populateQuery, options.populatePaths);
764
840
  return [4 /*yield*/, populateQuery.exec()];
765
- case 10:
766
- doc = _b.sent();
767
- _b.label = 11;
768
- case 11:
769
- if (!options.postUpdate) return [3 /*break*/, 15];
770
- _b.label = 12;
771
841
  case 12:
772
- _b.trys.push([12, 14, , 15]);
773
- return [4 /*yield*/, options.postUpdate(doc, body, req, prevDoc)];
842
+ doc = _c.sent();
843
+ _c.label = 13;
774
844
  case 13:
775
- _b.sent();
776
- return [3 /*break*/, 15];
845
+ if (!options.postUpdate) return [3 /*break*/, 17];
846
+ _c.label = 14;
777
847
  case 14:
778
- error_12 = _b.sent();
848
+ _c.trys.push([14, 16, , 17]);
849
+ return [4 /*yield*/, options.postUpdate(doc, body, req, prevDoc)];
850
+ case 15:
851
+ _c.sent();
852
+ return [3 /*break*/, 17];
853
+ case 16:
854
+ error_12 = _c.sent();
779
855
  throw new errors_1.APIError({
780
856
  disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_12),
781
857
  error: error_12,
782
858
  status: 400,
783
- title: "postUpdate hook error on ".concat(req.params.id, ": ").concat(error_12.message),
859
+ title: "postUpdate hook error on ".concat(req.params.id, ": ").concat((0, errors_1.errorMessage)(error_12)),
784
860
  });
785
- case 15:
786
- _b.trys.push([15, 17, , 18]);
861
+ case 17:
862
+ _c.trys.push([17, 19, , 20]);
787
863
  return [4 /*yield*/, responseHandler(doc, "update", req, options)];
788
- case 16:
789
- serialized = _b.sent();
864
+ case 18:
865
+ serialized = _c.sent();
790
866
  return [2 /*return*/, res.json({ data: serialized })];
791
- case 17:
792
- error_13 = _b.sent();
867
+ case 19:
868
+ error_13 = _c.sent();
793
869
  throw new errors_1.APIError({
794
870
  disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_13),
795
871
  error: error_13,
796
- title: "responseHandler error: ".concat(error_13.message),
872
+ title: "responseHandler error: ".concat((0, errors_1.errorMessage)(error_13)),
797
873
  });
798
- case 18: return [2 /*return*/];
874
+ case 20: return [2 /*return*/];
799
875
  }
800
876
  });
801
877
  }); }));
@@ -827,7 +903,7 @@ function _buildModelRouter(model, options) {
827
903
  disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_14),
828
904
  error: error_14,
829
905
  status: 403,
830
- title: "preDelete hook error on ".concat(req.params.id, ": ").concat(error_14.message),
906
+ title: "preDelete hook error on ".concat(req.params.id, ": ").concat((0, errors_1.errorMessage)(error_14)),
831
907
  });
832
908
  case 4:
833
909
  if (body === undefined) {
@@ -865,7 +941,7 @@ function _buildModelRouter(model, options) {
865
941
  disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_15),
866
942
  error: error_15,
867
943
  status: 400,
868
- title: error_15.message,
944
+ title: (0, errors_1.errorMessage)(error_15),
869
945
  });
870
946
  case 10:
871
947
  if (!options.postDelete) return [3 /*break*/, 14];
@@ -882,7 +958,7 @@ function _buildModelRouter(model, options) {
882
958
  disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_16),
883
959
  error: error_16,
884
960
  status: 400,
885
- title: "postDelete hook error: ".concat(error_16.message),
961
+ title: "postDelete hook error: ".concat((0, errors_1.errorMessage)(error_16)),
886
962
  });
887
963
  case 14: return [2 /*return*/, res.status(204).json({})];
888
964
  }
@@ -939,10 +1015,10 @@ function _buildModelRouter(model, options) {
939
1015
  else if (operation === "PATCH" || operation === "DELETE") {
940
1016
  index = void 0;
941
1017
  if ((0, utils_1.isValidObjectId)(itemId)) {
942
- index = array.findIndex(function (x) { return x.id === itemId; });
1018
+ index = array.findIndex(function (x) { return (x === null || x === void 0 ? void 0 : x.id) === itemId; });
943
1019
  }
944
1020
  else {
945
- index = array.findIndex(function (x) { return x === itemId; });
1021
+ index = array.indexOf(itemId);
946
1022
  }
947
1023
  if (index === -1) {
948
1024
  throw new errors_1.APIError({
@@ -981,7 +1057,7 @@ function _buildModelRouter(model, options) {
981
1057
  disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error),
982
1058
  error: error,
983
1059
  status: 403,
984
- title: error.message,
1060
+ title: (0, errors_1.errorMessage)(error),
985
1061
  });
986
1062
  }
987
1063
  if (!options.preUpdate) return [3 /*break*/, 8];
@@ -998,7 +1074,7 @@ function _buildModelRouter(model, options) {
998
1074
  disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_17),
999
1075
  error: error_17,
1000
1076
  status: 400,
1001
- title: "preUpdate hook error on ".concat(req.params.id, ": ").concat(error_17.message),
1077
+ title: "preUpdate hook error on ".concat(req.params.id, ": ").concat((0, errors_1.errorMessage)(error_17)),
1002
1078
  });
1003
1079
  case 7:
1004
1080
  if (body === undefined) {
@@ -1029,7 +1105,7 @@ function _buildModelRouter(model, options) {
1029
1105
  disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_18),
1030
1106
  error: error_18,
1031
1107
  status: 400,
1032
- title: "PATCH Pre Update error on ".concat(req.params.id, ": ").concat(error_18.message),
1108
+ title: "PATCH Pre Update error on ".concat(req.params.id, ": ").concat((0, errors_1.errorMessage)(error_18)),
1033
1109
  });
1034
1110
  case 11:
1035
1111
  if (!options.postUpdate) return [3 /*break*/, 15];
@@ -1046,9 +1122,11 @@ function _buildModelRouter(model, options) {
1046
1122
  disableExternalErrorTracking: (0, errors_1.getDisableExternalErrorTracking)(error_19),
1047
1123
  error: error_19,
1048
1124
  status: 400,
1049
- title: "PATCH Post Update error on ".concat(req.params.id, ": ").concat(error_19.message),
1125
+ title: "PATCH Post Update error on ".concat(req.params.id, ": ").concat((0, errors_1.errorMessage)(error_19)),
1050
1126
  });
1051
- case 15: return [2 /*return*/, res.json({ data: (0, transformers_1.serialize)(req, options, doc) })];
1127
+ case 15: return [2 /*return*/, res.json({
1128
+ data: (0, transformers_1.serialize)(req, options, doc),
1129
+ })];
1052
1130
  }
1053
1131
  });
1054
1132
  });
@@ -1083,35 +1161,6 @@ function _buildModelRouter(model, options) {
1083
1161
  router.use(errors_1.apiErrorMiddleware);
1084
1162
  return router;
1085
1163
  }
1086
- /**
1087
- * Wraps async route handlers to properly catch and forward errors.
1088
- *
1089
- * Since Express doesn't handle async routes well, wrap them with this function.
1090
- * Optionally supports integrated request validation.
1091
- *
1092
- * @param fn - The async route handler function
1093
- * @param options - Optional configuration for validation
1094
- * @returns Express middleware function
1095
- *
1096
- * @example
1097
- * ```typescript
1098
- * // Basic usage without validation
1099
- * router.post("/users", asyncHandler(async (req, res) => {
1100
- * // handler code
1101
- * }));
1102
- *
1103
- * // With integrated validation
1104
- * router.post("/users", asyncHandler(async (req, res) => {
1105
- * // handler code - body is already validated
1106
- * }, {
1107
- * bodySchema: {
1108
- * name: {type: "string", required: true},
1109
- * email: {type: "string", format: "email", required: true},
1110
- * },
1111
- * validate: true,
1112
- * }));
1113
- * ```
1114
- */
1115
1164
  var asyncHandler = function (fn, options) {
1116
1165
  var _a, _b;
1117
1166
  // If no validation options, return simple handler
@@ -1143,13 +1192,13 @@ var asyncHandler = function (fn, options) {
1143
1192
  return;
1144
1193
  }
1145
1194
  try {
1146
- validators[index](req, res, function (err) {
1195
+ validators[index](req, res, (function (err) {
1147
1196
  if (err) {
1148
1197
  next(err);
1149
1198
  return;
1150
1199
  }
1151
1200
  runValidators(index + 1);
1152
- });
1201
+ }));
1153
1202
  }
1154
1203
  catch (err) {
1155
1204
  next(err);
@@ -88,6 +88,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
88
88
  return (mod && mod.__esModule) ? mod : { "default": mod };
89
89
  };
90
90
  Object.defineProperty(exports, "__esModule", { value: true });
91
+ // biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
91
92
  var bun_test_1 = require("bun:test");
92
93
  var Sentry = __importStar(require("@sentry/bun"));
93
94
  var qs_1 = __importDefault(require("qs"));