@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
@@ -99,6 +99,7 @@ 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 Sentry = __importStar(require("@sentry/bun"));
104
105
  var axios_1 = __importDefault(require("axios"));
@@ -112,11 +112,11 @@ var sendToSlack = function (text_1) {
112
112
  args_1[_i - 1] = arguments[_i];
113
113
  }
114
114
  return __awaiter(void 0, __spreadArray([text_1], __read(args_1), false), void 0, function (text, _a) {
115
- var slackWebhookUrl, slackWebhooksString, slackWebhooks, channel, formattedText, error_1, errorObj;
116
- var _b, _c, _d;
117
- var _e = _a === void 0 ? {} : _a, slackChannel = _e.slackChannel, _f = _e.shouldThrow, shouldThrow = _f === void 0 ? false : _f, env = _e.env, url = _e.url;
118
- return __generator(this, function (_g) {
119
- switch (_g.label) {
115
+ var slackWebhookUrl, slackWebhooksString, slackWebhooks, channel, formattedText, error_1, message;
116
+ var _b;
117
+ var _c = _a === void 0 ? {} : _a, slackChannel = _c.slackChannel, _d = _c.shouldThrow, shouldThrow = _d === void 0 ? false : _d, env = _c.env, url = _c.url;
118
+ return __generator(this, function (_e) {
119
+ switch (_e.label) {
120
120
  case 0:
121
121
  slackWebhookUrl = url;
122
122
  if (!slackWebhookUrl) {
@@ -130,8 +130,10 @@ var sendToSlack = function (text_1) {
130
130
  channel = slackChannel !== null && slackChannel !== void 0 ? slackChannel : "default";
131
131
  slackWebhookUrl = (_b = slackWebhooks[channel]) !== null && _b !== void 0 ? _b : slackWebhooks.default;
132
132
  if (!slackWebhookUrl) {
133
- Sentry.captureException(new Error("No webhook url set in env for ".concat(channel, ". Slack message not sent")));
134
- logger_1.logger.debug("No webhook url set in env for ".concat(channel, "."));
133
+ Sentry.captureException(new errors_1.APIError({
134
+ status: 500,
135
+ title: "No webhook url set in env for ".concat(channel, ". Slack message not sent"),
136
+ }));
135
137
  return [2 /*return*/];
136
138
  }
137
139
  }
@@ -139,24 +141,24 @@ var sendToSlack = function (text_1) {
139
141
  if (env) {
140
142
  formattedText = "[".concat(env.toUpperCase(), "] ").concat(text);
141
143
  }
142
- _g.label = 1;
144
+ _e.label = 1;
143
145
  case 1:
144
- _g.trys.push([1, 3, , 4]);
146
+ _e.trys.push([1, 3, , 4]);
145
147
  return [4 /*yield*/, axios_1.default.post(slackWebhookUrl, {
146
148
  text: formattedText,
147
149
  })];
148
150
  case 2:
149
- _g.sent();
151
+ _e.sent();
150
152
  return [3 /*break*/, 4];
151
153
  case 3:
152
- error_1 = _g.sent();
153
- errorObj = error_1;
154
- logger_1.logger.error("Error posting to slack: ".concat((_c = errorObj.text) !== null && _c !== void 0 ? _c : errorObj.message));
154
+ error_1 = _e.sent();
155
+ message = (0, errors_1.errorMessage)(error_1);
156
+ logger_1.logger.error("Error posting to slack: ".concat(message));
155
157
  Sentry.captureException(error_1);
156
158
  if (shouldThrow) {
157
159
  throw new errors_1.APIError({
158
160
  status: 500,
159
- title: "Error posting to slack: ".concat((_d = errorObj.text) !== null && _d !== void 0 ? _d : errorObj.message),
161
+ title: "Error posting to slack: ".concat(message),
160
162
  });
161
163
  }
162
164
  return [3 /*break*/, 4];
@@ -102,6 +102,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
102
102
  var bun_test_1 = require("bun:test");
103
103
  var Sentry = __importStar(require("@sentry/bun"));
104
104
  var axios_1 = __importDefault(require("axios"));
105
+ var errors_1 = require("../errors");
105
106
  var slackNotifier_1 = require("./slackNotifier");
106
107
  (0, bun_test_1.describe)("sendToSlack", function () {
107
108
  var mockAxiosPost;
@@ -213,8 +214,44 @@ var slackNotifier_1 = require("./slackNotifier");
213
214
  }
214
215
  });
215
216
  }); });
217
+ (0, bun_test_1.it)("reports to Sentry and returns early when channel has no webhook and no default", function () { return __awaiter(void 0, void 0, void 0, function () {
218
+ var captured;
219
+ return __generator(this, function (_a) {
220
+ switch (_a.label) {
221
+ case 0:
222
+ process.env.SLACK_WEBHOOKS = JSON.stringify({ ops: "https://slack.example/ops" });
223
+ return [4 /*yield*/, (0, slackNotifier_1.sendToSlack)("orphan message", { slackChannel: "alerts" })];
224
+ case 1:
225
+ _a.sent();
226
+ (0, bun_test_1.expect)(mockAxiosPost.mock.calls.length).toBe(0);
227
+ (0, bun_test_1.expect)(Sentry.captureException.mock.calls.length).toBe(1);
228
+ captured = Sentry.captureException.mock
229
+ .calls[0][0];
230
+ (0, bun_test_1.expect)(captured).toBeInstanceOf(errors_1.APIError);
231
+ (0, bun_test_1.expect)(captured.title).toContain("alerts");
232
+ return [2 /*return*/];
233
+ }
234
+ });
235
+ }); });
236
+ (0, bun_test_1.it)("posts directly using the url parameter without env lookup", function () { return __awaiter(void 0, void 0, void 0, function () {
237
+ var _a, url, payload;
238
+ return __generator(this, function (_b) {
239
+ switch (_b.label) {
240
+ case 0:
241
+ mockAxiosPost.mockResolvedValue({ status: 200 });
242
+ return [4 /*yield*/, (0, slackNotifier_1.sendToSlack)("direct msg", { url: "https://direct.example/hook" })];
243
+ case 1:
244
+ _b.sent();
245
+ (0, bun_test_1.expect)(mockAxiosPost.mock.calls.length).toBe(1);
246
+ _a = __read(mockAxiosPost.mock.calls[0], 2), url = _a[0], payload = _a[1];
247
+ (0, bun_test_1.expect)(url).toBe("https://direct.example/hook");
248
+ (0, bun_test_1.expect)(payload).toEqual({ text: "direct msg" });
249
+ return [2 /*return*/];
250
+ }
251
+ });
252
+ }); });
216
253
  (0, bun_test_1.it)("captures error and throws APIError when shouldThrow=true", function () { return __awaiter(void 0, void 0, void 0, function () {
217
- var error_1;
254
+ var error_1, apiError;
218
255
  return __generator(this, function (_a) {
219
256
  switch (_a.label) {
220
257
  case 0:
@@ -231,8 +268,9 @@ var slackNotifier_1 = require("./slackNotifier");
231
268
  throw new Error("Expected sendToSlack to throw APIError");
232
269
  case 3:
233
270
  error_1 = _a.sent();
234
- (0, bun_test_1.expect)(error_1.name).toBe("APIError");
235
- (0, bun_test_1.expect)(error_1.title).toMatch(/Error posting to slack/i);
271
+ apiError = error_1;
272
+ (0, bun_test_1.expect)(apiError.name).toBe("APIError");
273
+ (0, bun_test_1.expect)(apiError.title).toMatch(/Error posting to slack/i);
236
274
  return [3 /*break*/, 4];
237
275
  case 4:
238
276
  (0, bun_test_1.expect)(mockAxiosPost.mock.calls.length).toBe(1);
@@ -103,7 +103,7 @@ var logger_1 = require("../logger");
103
103
  * Uses Zoom's rich message format (format=full) with structured header and body.
104
104
  */
105
105
  var sendToZoom = function (_a, _b) { return __awaiter(void 0, [_a, _b], void 0, function (_c, _d) {
106
- var zoomWebhooksString, msg, zoomWebhooks, zoomChannel, zoomWebhookUrl, msg, zoomToken, msg, messageBody, error_1, errorMessage;
106
+ var zoomWebhooksString, msg, zoomWebhooks, zoomChannel, zoomWebhookUrl, msg, zoomToken, msg, messageBody, error_1, message;
107
107
  var _e, _f, _g, _h, _j, _k;
108
108
  var header = _c.header, body = _c.body, subheader = _c.subheader;
109
109
  var channel = _d.channel, _l = _d.shouldThrow, shouldThrow = _l === void 0 ? false : _l, env = _d.env;
@@ -113,8 +113,7 @@ var sendToZoom = function (_a, _b) { return __awaiter(void 0, [_a, _b], void 0,
113
113
  zoomWebhooksString = process.env.ZOOM_CHAT_WEBHOOKS;
114
114
  if (!zoomWebhooksString) {
115
115
  msg = "ZOOM_CHAT_WEBHOOKS not set. Zoom message not sent";
116
- Sentry.captureException(new Error(msg));
117
- logger_1.logger.error(msg);
116
+ Sentry.captureException(new errors_1.APIError({ status: 500, title: msg }));
118
117
  return [2 /*return*/];
119
118
  }
120
119
  zoomWebhooks = JSON.parse(zoomWebhooksString !== null && zoomWebhooksString !== void 0 ? zoomWebhooksString : "{}");
@@ -122,15 +121,13 @@ var sendToZoom = function (_a, _b) { return __awaiter(void 0, [_a, _b], void 0,
122
121
  zoomWebhookUrl = (_f = (_e = zoomWebhooks[zoomChannel]) === null || _e === void 0 ? void 0 : _e.channel) !== null && _f !== void 0 ? _f : (_g = zoomWebhooks.default) === null || _g === void 0 ? void 0 : _g.channel;
123
122
  if (!zoomWebhookUrl) {
124
123
  msg = "No webhook url set in env for ".concat(zoomChannel, ". Zoom message not sent");
125
- Sentry.captureException(new Error(msg));
126
- logger_1.logger.error(msg);
124
+ Sentry.captureException(new errors_1.APIError({ status: 500, title: msg }));
127
125
  return [2 /*return*/];
128
126
  }
129
127
  zoomToken = (_j = (_h = zoomWebhooks[zoomChannel]) === null || _h === void 0 ? void 0 : _h.verificationToken) !== null && _j !== void 0 ? _j : (_k = zoomWebhooks.default) === null || _k === void 0 ? void 0 : _k.verificationToken;
130
128
  if (!zoomToken) {
131
129
  msg = "No verification token set in env for ".concat(zoomChannel, ". Zoom message not sent");
132
- Sentry.captureException(new Error(msg));
133
- logger_1.logger.error(msg);
130
+ Sentry.captureException(new errors_1.APIError({ status: 500, title: msg }));
134
131
  return [2 /*return*/];
135
132
  }
136
133
  messageBody = {
@@ -163,13 +160,13 @@ var sendToZoom = function (_a, _b) { return __awaiter(void 0, [_a, _b], void 0,
163
160
  return [3 /*break*/, 4];
164
161
  case 3:
165
162
  error_1 = _m.sent();
166
- errorMessage = error_1 instanceof Error ? error_1.message : String(error_1);
167
- logger_1.logger.error("Error posting to Zoom: ".concat(errorMessage));
163
+ message = (0, errors_1.errorMessage)(error_1);
164
+ logger_1.logger.error("Error posting to Zoom: ".concat(message));
168
165
  Sentry.captureException(error_1);
169
166
  if (shouldThrow) {
170
167
  throw new errors_1.APIError({
171
168
  status: 500,
172
- title: "Error posting to Zoom: ".concat(errorMessage),
169
+ title: "Error posting to Zoom: ".concat(message),
173
170
  });
174
171
  }
175
172
  return [3 /*break*/, 4];
@@ -99,6 +99,7 @@ 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 Sentry = __importStar(require("@sentry/bun"));
104
105
  var axios_1 = __importDefault(require("axios"));
package/dist/openApi.d.ts CHANGED
@@ -58,4 +58,4 @@ export declare function listOpenApiMiddleware<T>(model: Model<T>, options: Parti
58
58
  export declare function createOpenApiMiddleware<T>(model: Model<T>, options: Partial<ModelRouterOptions<T>>): express.RequestHandler;
59
59
  export declare function patchOpenApiMiddleware<T>(model: Model<T>, options: Partial<ModelRouterOptions<T>>): express.RequestHandler;
60
60
  export declare function deleteOpenApiMiddleware<T>(model: Model<T>, options: Partial<ModelRouterOptions<T>>): express.RequestHandler;
61
- export declare function readOpenApiMiddleware<T>(options: Partial<ModelRouterOptions<T>>, properties: any, required: string[], queryParameters: any): any;
61
+ export declare function readOpenApiMiddleware<T>(options: Partial<ModelRouterOptions<T>>, properties: Record<string, unknown>, required: string[], queryParameters: Array<Record<string, unknown>>): express.RequestHandler;
@@ -66,6 +66,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
66
66
  return (mod && mod.__esModule) ? mod : { "default": mod };
67
67
  };
68
68
  Object.defineProperty(exports, "__esModule", { value: true });
69
+ // biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
69
70
  var bun_test_1 = require("bun:test");
70
71
  var supertest_1 = __importDefault(require("supertest"));
71
72
  var api_1 = require("./api");
@@ -1,3 +1,35 @@
1
+ /**
2
+ * OpenAPI Middleware Builder
3
+ *
4
+ * This module provides a fluent builder pattern for constructing OpenAPI middleware
5
+ * for Express routes that don't directly map to Mongoose models. It allows you to
6
+ * define custom API documentation with full control over request/response schemas.
7
+ *
8
+ * @packageDocumentation
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import {createOpenApiBuilder} from "./openApiBuilder";
13
+ *
14
+ * // Create middleware with custom documentation
15
+ * const middleware = createOpenApiBuilder(options)
16
+ * .withTags(["users"])
17
+ * .withSummary("Get user statistics")
18
+ * .withDescription("Returns aggregated statistics for the current user")
19
+ * .withQueryParameter("period", {type: "string"}, {
20
+ * description: "Time period for statistics",
21
+ * required: false,
22
+ * })
23
+ * .withResponse<{count: number; average: number}>(200, {
24
+ * count: {type: "number", description: "Total count"},
25
+ * average: {type: "number", description: "Average value"},
26
+ * })
27
+ * .build();
28
+ *
29
+ * router.get("/stats", middleware, statsHandler);
30
+ * ```
31
+ */
32
+ import type express from "express";
1
33
  import type { ModelRouterOptions } from "./api";
2
34
  /**
3
35
  * Defines a property within an OpenAPI schema.
@@ -68,7 +100,7 @@ export interface OpenApiSchemaProperty {
68
100
  * };
69
101
  * ```
70
102
  */
71
- export type OpenApiSchema = {
103
+ export interface OpenApiSchema {
72
104
  /** The JSON Schema type (typically "object" or "array") */
73
105
  type: string;
74
106
  /** Property definitions for object types */
@@ -79,7 +111,8 @@ export type OpenApiSchema = {
79
111
  items?: OpenApiSchemaProperty;
80
112
  /** Schema for additional properties or boolean to allow/disallow them */
81
113
  additionalProperties?: OpenApiSchemaProperty | boolean;
82
- };
114
+ [key: string]: unknown;
115
+ }
83
116
  /**
84
117
  * Defines a parameter in an OpenAPI operation.
85
118
  *
@@ -159,7 +192,7 @@ export interface OpenApiResponse {
159
192
  */
160
193
  export interface OpenApiBuildResult {
161
194
  /** The OpenAPI documentation middleware */
162
- middleware: any;
195
+ middleware: express.RequestHandler;
163
196
  /** Request body schema if defined */
164
197
  bodySchema?: Record<string, OpenApiSchemaProperty>;
165
198
  /** Query parameter schemas if defined */
@@ -275,7 +308,7 @@ export declare class OpenApiMiddlewareBuilder {
275
308
  * });
276
309
  * ```
277
310
  */
278
- withRequestBody<T extends Record<string, any>>(schema: {
311
+ withRequestBody<T extends Record<string, unknown>>(schema: {
279
312
  [K in keyof T]: OpenApiSchemaProperty;
280
313
  }, options?: {
281
314
  required?: boolean;
@@ -306,7 +339,7 @@ export declare class OpenApiMiddlewareBuilder {
306
339
  * builder.withResponse(204, "No content");
307
340
  * ```
308
341
  */
309
- withResponse<T extends Record<string, any>>(statusCode: number, schema: {
342
+ withResponse<T extends Record<string, unknown>>(statusCode: number, schema: {
310
343
  [K in keyof T]: OpenApiSchemaProperty;
311
344
  } | string, options?: {
312
345
  description?: string;
@@ -334,7 +367,7 @@ export declare class OpenApiMiddlewareBuilder {
334
367
  * }, {description: "List of users"});
335
368
  * ```
336
369
  */
337
- withArrayResponse<T extends Record<string, any>>(statusCode: number, itemSchema: {
370
+ withArrayResponse<T extends Record<string, unknown>>(statusCode: number, itemSchema: {
338
371
  [K in keyof T]: OpenApiSchemaProperty;
339
372
  }, options?: {
340
373
  description?: string;
@@ -31,37 +31,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
31
31
  };
32
32
  Object.defineProperty(exports, "__esModule", { value: true });
33
33
  exports.createOpenApiBuilder = exports.OpenApiMiddlewareBuilder = void 0;
34
- /**
35
- * OpenAPI Middleware Builder
36
- *
37
- * This module provides a fluent builder pattern for constructing OpenAPI middleware
38
- * for Express routes that don't directly map to Mongoose models. It allows you to
39
- * define custom API documentation with full control over request/response schemas.
40
- *
41
- * @packageDocumentation
42
- *
43
- * @example
44
- * ```typescript
45
- * import {createOpenApiBuilder} from "./openApiBuilder";
46
- *
47
- * // Create middleware with custom documentation
48
- * const middleware = createOpenApiBuilder(options)
49
- * .withTags(["users"])
50
- * .withSummary("Get user statistics")
51
- * .withDescription("Returns aggregated statistics for the current user")
52
- * .withQueryParameter("period", {type: "string"}, {
53
- * description: "Time period for statistics",
54
- * required: false,
55
- * })
56
- * .withResponse<{count: number; average: number}>(200, {
57
- * count: {type: "number", description: "Total count"},
58
- * average: {type: "number", description: "Average value"},
59
- * })
60
- * .build();
61
- *
62
- * router.get("/stats", middleware, statsHandler);
63
- * ```
64
- */
65
34
  var merge_1 = __importDefault(require("lodash/merge"));
66
35
  var logger_1 = require("./logger");
67
36
  var openApi_1 = require("./openApi");
@@ -469,6 +438,7 @@ var OpenApiMiddlewareBuilder = /** @class */ (function () {
469
438
  * router.get("/users/:id", middleware, getUserHandler);
470
439
  * ```
471
440
  */
441
+ // biome-ignore lint/suspicious/noExplicitAny: returns either a single RequestHandler or an array depending on validation config — callers spread or invoke
472
442
  OpenApiMiddlewareBuilder.prototype.build = function () {
473
443
  var _c, _d, _e, _f, _g;
474
444
  var noop = function (_a, _b, next) { return next(); };
@@ -50,6 +50,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
50
50
  return (mod && mod.__esModule) ? mod : { "default": mod };
51
51
  };
52
52
  Object.defineProperty(exports, "__esModule", { value: true });
53
+ // biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
53
54
  var bun_test_1 = require("bun:test");
54
55
  var supertest_1 = __importDefault(require("supertest"));
55
56
  var api_1 = require("./api");
@@ -191,6 +191,7 @@ var getAjvInstance = function () {
191
191
  useDefaults: true,
192
192
  validateSchema: false,
193
193
  });
194
+ // biome-ignore lint/suspicious/noExplicitAny: ajv-formats has a known type compat issue with AJV instances
194
195
  (0, ajv_formats_1.default)(instance);
195
196
  ajvCache.set(key, instance);
196
197
  }
@@ -72,6 +72,7 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
72
72
  return to.concat(ar || Array.prototype.slice.call(from));
73
73
  };
74
74
  Object.defineProperty(exports, "__esModule", { value: true });
75
+ // biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
75
76
  var bun_test_1 = require("bun:test");
76
77
  var api_1 = require("./api");
77
78
  var auth_1 = require("./auth");
@@ -19,8 +19,8 @@ export declare const Permissions: {
19
19
  IsAny: () => boolean;
20
20
  IsAuthenticated: (_method: RESTMethod, user?: User) => boolean;
21
21
  IsAuthenticatedOrReadOnly: (method: RESTMethod, user?: User) => boolean;
22
- IsOwner: (_method: RESTMethod, user?: User, obj?: any) => any;
23
- IsOwnerOrReadOnly: (method: RESTMethod, user?: User, obj?: any) => boolean;
22
+ IsOwner: (_method: RESTMethod, user?: User, obj?: unknown) => boolean;
23
+ IsOwnerOrReadOnly: (method: RESTMethod, user?: User, obj?: unknown) => boolean;
24
24
  };
25
- export declare function checkPermissions<T>(method: RESTMethod, permissions: PermissionMethod<T>[], user?: User, obj?: T): Promise<boolean>;
26
- export declare function permissionMiddleware<T>(model: Model<T>, options: Pick<ModelRouterOptions<T>, "permissions" | "populatePaths">): (req: express.Request, _res: express.Response, next: NextFunction) => Promise<void>;
25
+ export declare const checkPermissions: <T>(method: RESTMethod, permissions: PermissionMethod<T>[], user?: User, obj?: T) => Promise<boolean>;
26
+ export declare const permissionMiddleware: <T>(model: Model<T>, options: Pick<ModelRouterOptions<T>, "permissions" | "populatePaths">) => (req: express.Request, _res: express.Response, next: NextFunction) => Promise<void>;
@@ -83,10 +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
- exports.Permissions = exports.OwnerQueryFilter = void 0;
87
- exports.checkPermissions = checkPermissions;
88
- exports.permissionMiddleware = permissionMiddleware;
89
- // Defaults closed
86
+ exports.permissionMiddleware = exports.checkPermissions = exports.Permissions = exports.OwnerQueryFilter = void 0;
90
87
  var Sentry = __importStar(require("@sentry/bun"));
91
88
  var mongoose_1 = __importDefault(require("mongoose"));
92
89
  var api_1 = require("./api");
@@ -96,7 +93,6 @@ var OwnerQueryFilter = function (user) {
96
93
  if (user) {
97
94
  return { ownerId: user === null || user === void 0 ? void 0 : user.id };
98
95
  }
99
- // Return a null, so we know to return no results.
100
96
  return null;
101
97
  };
102
98
  exports.OwnerQueryFilter = OwnerQueryFilter;
@@ -131,8 +127,10 @@ exports.Permissions = {
131
127
  if (user === null || user === void 0 ? void 0 : user.admin) {
132
128
  return true;
133
129
  }
134
- var ownerId = ((_a = obj === null || obj === void 0 ? void 0 : obj.ownerId) === null || _a === void 0 ? void 0 : _a._id) || (obj === null || obj === void 0 ? void 0 : obj.ownerId);
135
- return (user === null || user === void 0 ? void 0 : user.id) && ownerId && String(ownerId) === String(user === null || user === void 0 ? void 0 : user.id);
130
+ var withOwner = obj;
131
+ var ownerObj = withOwner.ownerId;
132
+ var ownerId = (_a = ownerObj === null || ownerObj === void 0 ? void 0 : ownerObj._id) !== null && _a !== void 0 ? _a : withOwner.ownerId;
133
+ return Boolean((user === null || user === void 0 ? void 0 : user.id) && ownerId && String(ownerId) === String(user === null || user === void 0 ? void 0 : user.id));
136
134
  },
137
135
  IsOwnerOrReadOnly: function (method, user, obj) {
138
136
  // When checking if we can possibly perform the action, return true.
@@ -142,61 +140,59 @@ exports.Permissions = {
142
140
  if (user === null || user === void 0 ? void 0 : user.admin) {
143
141
  return true;
144
142
  }
145
- if ((user === null || user === void 0 ? void 0 : user.id) && (obj === null || obj === void 0 ? void 0 : obj.ownerId) && String(obj === null || obj === void 0 ? void 0 : obj.ownerId) === String(user === null || user === void 0 ? void 0 : user.id)) {
143
+ var withOwner = obj;
144
+ if ((user === null || user === void 0 ? void 0 : user.id) && withOwner.ownerId && String(withOwner.ownerId) === String(user === null || user === void 0 ? void 0 : user.id)) {
146
145
  return true;
147
146
  }
148
147
  return method === "list" || method === "read";
149
148
  },
150
149
  };
151
- function checkPermissions(method, permissions, user, obj) {
152
- return __awaiter(this, void 0, void 0, function () {
153
- var anyTrue, permissions_1, permissions_1_1, perm, e_1_1;
154
- var e_1, _a;
155
- return __generator(this, function (_b) {
156
- switch (_b.label) {
157
- case 0:
158
- anyTrue = false;
159
- _b.label = 1;
160
- case 1:
161
- _b.trys.push([1, 6, 7, 8]);
162
- permissions_1 = __values(permissions), permissions_1_1 = permissions_1.next();
163
- _b.label = 2;
164
- case 2:
165
- if (!!permissions_1_1.done) return [3 /*break*/, 5];
166
- perm = permissions_1_1.value;
167
- return [4 /*yield*/, perm(method, user, obj)];
168
- case 3:
169
- // May or may not be a promise.
170
- if (!(_b.sent())) {
171
- return [2 /*return*/, false];
172
- }
173
- anyTrue = true;
174
- _b.label = 4;
175
- case 4:
176
- permissions_1_1 = permissions_1.next();
177
- return [3 /*break*/, 2];
178
- case 5: return [3 /*break*/, 8];
179
- case 6:
180
- e_1_1 = _b.sent();
181
- e_1 = { error: e_1_1 };
182
- return [3 /*break*/, 8];
183
- case 7:
184
- try {
185
- if (permissions_1_1 && !permissions_1_1.done && (_a = permissions_1.return)) _a.call(permissions_1);
186
- }
187
- finally { if (e_1) throw e_1.error; }
188
- return [7 /*endfinally*/];
189
- case 8: return [2 /*return*/, anyTrue];
190
- }
191
- });
150
+ var checkPermissions = function (method, permissions, user, obj) { return __awaiter(void 0, void 0, void 0, function () {
151
+ var anyTrue, permissions_1, permissions_1_1, perm, e_1_1;
152
+ var e_1, _a;
153
+ return __generator(this, function (_b) {
154
+ switch (_b.label) {
155
+ case 0:
156
+ anyTrue = false;
157
+ _b.label = 1;
158
+ case 1:
159
+ _b.trys.push([1, 6, 7, 8]);
160
+ permissions_1 = __values(permissions), permissions_1_1 = permissions_1.next();
161
+ _b.label = 2;
162
+ case 2:
163
+ if (!!permissions_1_1.done) return [3 /*break*/, 5];
164
+ perm = permissions_1_1.value;
165
+ return [4 /*yield*/, perm(method, user, obj)];
166
+ case 3:
167
+ if (!(_b.sent())) {
168
+ return [2 /*return*/, false];
169
+ }
170
+ anyTrue = true;
171
+ _b.label = 4;
172
+ case 4:
173
+ permissions_1_1 = permissions_1.next();
174
+ return [3 /*break*/, 2];
175
+ case 5: return [3 /*break*/, 8];
176
+ case 6:
177
+ e_1_1 = _b.sent();
178
+ e_1 = { error: e_1_1 };
179
+ return [3 /*break*/, 8];
180
+ case 7:
181
+ try {
182
+ if (permissions_1_1 && !permissions_1_1.done && (_a = permissions_1.return)) _a.call(permissions_1);
183
+ }
184
+ finally { if (e_1) throw e_1.error; }
185
+ return [7 /*endfinally*/];
186
+ case 8: return [2 /*return*/, anyTrue];
187
+ }
192
188
  });
193
- }
189
+ }); };
190
+ exports.checkPermissions = checkPermissions;
194
191
  // Check the permissions for a given model and method. If the method is a read, update, or delete,
195
192
  // finds the relevant object, checks the permissions, and attaches the object to the request as
196
193
  // req.obj.
197
- function permissionMiddleware(model, options) {
198
- var _this = this;
199
- return function (req, _res, next) { return __awaiter(_this, void 0, void 0, function () {
194
+ var permissionMiddleware = function (model, options) {
195
+ return function (req, _res, next) { return __awaiter(void 0, void 0, void 0, function () {
200
196
  var method, reqMethod, builtQuery, populatedQuery, data, error_1, hiddenDoc, error, reason, error, error_2;
201
197
  var _a, _b;
202
198
  return __generator(this, function (_c) {
@@ -233,7 +229,7 @@ function permissionMiddleware(model, options) {
233
229
  title: "Method ".concat(req.method, " not allowed"),
234
230
  });
235
231
  }
236
- return [4 /*yield*/, checkPermissions(method, options.permissions[method], req.user)];
232
+ return [4 /*yield*/, (0, exports.checkPermissions)(method, options.permissions[method], req.user)];
237
233
  case 2:
238
234
  // All methods check for permissions.
239
235
  if (!(_c.sent())) {
@@ -247,14 +243,16 @@ function permissionMiddleware(model, options) {
247
243
  return [2 /*return*/, next()];
248
244
  }
249
245
  builtQuery = model.findById(req.params.id);
250
- populatedQuery = (0, api_1.addPopulateToQuery)(builtQuery, options.populatePaths);
246
+ populatedQuery = (0, api_1.addPopulateToQuery)(
247
+ // biome-ignore lint/suspicious/noExplicitAny: Query types vary based on populate paths
248
+ builtQuery, options.populatePaths);
251
249
  data = void 0;
252
250
  _c.label = 3;
253
251
  case 3:
254
252
  _c.trys.push([3, 5, , 6]);
255
253
  return [4 /*yield*/, populatedQuery.exec()];
256
254
  case 4:
257
- data = _c.sent();
255
+ data = (_c.sent());
258
256
  return [3 /*break*/, 6];
259
257
  case 5:
260
258
  error_1 = _c.sent();
@@ -279,13 +277,16 @@ function permissionMiddleware(model, options) {
279
277
  error.meta = undefined;
280
278
  throw error;
281
279
  }
282
- reason = hiddenDoc.deleted
283
- ? { deleted: "true" }
284
- : hiddenDoc.disabled
285
- ? { disabled: "true" }
286
- : hiddenDoc.archived
287
- ? { archived: "true" }
288
- : null;
280
+ reason = null;
281
+ if (hiddenDoc.deleted) {
282
+ reason = { deleted: "true" };
283
+ }
284
+ else if (hiddenDoc.disabled) {
285
+ reason = { disabled: "true" };
286
+ }
287
+ else if (hiddenDoc.archived) {
288
+ reason = { archived: "true" };
289
+ }
289
290
  // If no reason found, treat as not found
290
291
  if (!reason) {
291
292
  error = new errors_1.APIError({
@@ -302,7 +303,7 @@ function permissionMiddleware(model, options) {
302
303
  status: 404,
303
304
  title: "Document ".concat(req.params.id, " not found for model ").concat(model.modelName),
304
305
  });
305
- case 8: return [4 /*yield*/, checkPermissions(method, options.permissions[method], req.user, data)];
306
+ case 8: return [4 /*yield*/, (0, exports.checkPermissions)(method, options.permissions[method], req.user, data)];
306
307
  case 9:
307
308
  if (!(_c.sent())) {
308
309
  throw new errors_1.APIError({
@@ -320,4 +321,5 @@ function permissionMiddleware(model, options) {
320
321
  }
321
322
  });
322
323
  }); };
323
- }
324
+ };
325
+ exports.permissionMiddleware = permissionMiddleware;
@@ -96,6 +96,7 @@ var __read = (this && this.__read) || function (o, n) {
96
96
  return ar;
97
97
  };
98
98
  Object.defineProperty(exports, "__esModule", { value: true });
99
+ // biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
99
100
  var bun_test_1 = require("bun:test");
100
101
  var Sentry = __importStar(require("@sentry/bun"));
101
102
  var errors_1 = require("./errors");
@@ -55,6 +55,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
55
55
  return (mod && mod.__esModule) ? mod : { "default": mod };
56
56
  };
57
57
  Object.defineProperty(exports, "__esModule", { value: true });
58
+ // biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
58
59
  var bun_test_1 = require("bun:test");
59
60
  var supertest_1 = __importDefault(require("supertest"));
60
61
  var api_1 = require("./api");