@terreno/api 0.13.2 → 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 (175) hide show
  1. package/dist/__tests__/versionCheckPlugin.test.js +53 -3
  2. package/dist/api.arrayOperations.test.js +1 -0
  3. package/dist/api.asyncHandler.test.d.ts +1 -0
  4. package/dist/api.asyncHandler.test.js +236 -0
  5. package/dist/api.d.ts +15 -4
  6. package/dist/api.errors.test.js +1 -0
  7. package/dist/api.hooks.test.js +1 -0
  8. package/dist/api.js +153 -104
  9. package/dist/api.query.test.js +1 -0
  10. package/dist/api.test.js +174 -0
  11. package/dist/auth.d.ts +10 -5
  12. package/dist/auth.js +163 -90
  13. package/dist/auth.test.js +159 -0
  14. package/dist/betterAuthApp.test.js +1 -0
  15. package/dist/betterAuthSetup.d.ts +5 -6
  16. package/dist/betterAuthSetup.js +17 -14
  17. package/dist/betterAuthSetup.test.js +1 -0
  18. package/dist/config.d.ts +48 -0
  19. package/dist/config.js +248 -0
  20. package/dist/config.test.d.ts +1 -0
  21. package/dist/config.test.js +328 -0
  22. package/dist/configuration.test.js +1 -0
  23. package/dist/configurationApp.d.ts +1 -1
  24. package/dist/configurationApp.js +17 -13
  25. package/dist/configurationPlugin.test.js +1 -0
  26. package/dist/consentApp.test.js +1 -0
  27. package/dist/envConfigurationPlugin.d.ts +2 -0
  28. package/dist/envConfigurationPlugin.js +173 -0
  29. package/dist/envConfigurationPlugin.test.d.ts +1 -0
  30. package/dist/envConfigurationPlugin.test.js +322 -0
  31. package/dist/errors.d.ts +18 -7
  32. package/dist/errors.js +106 -10
  33. package/dist/errors.test.js +16 -1
  34. package/dist/example.js +16 -7
  35. package/dist/expressServer.d.ts +10 -9
  36. package/dist/expressServer.js +62 -53
  37. package/dist/expressServer.test.js +53 -2
  38. package/dist/githubAuth.d.ts +2 -1
  39. package/dist/githubAuth.js +41 -26
  40. package/dist/githubAuth.test.js +1 -0
  41. package/dist/index.d.ts +4 -0
  42. package/dist/index.js +4 -0
  43. package/dist/logger.d.ts +1 -1
  44. package/dist/logger.js +42 -20
  45. package/dist/models/versionConfig.d.ts +2 -0
  46. package/dist/models/versionConfig.js +8 -0
  47. package/dist/notifiers/googleChatNotifier.js +14 -16
  48. package/dist/notifiers/googleChatNotifier.test.js +1 -0
  49. package/dist/notifiers/slackNotifier.js +16 -14
  50. package/dist/notifiers/slackNotifier.test.js +41 -3
  51. package/dist/notifiers/zoomNotifier.js +7 -10
  52. package/dist/notifiers/zoomNotifier.test.js +1 -0
  53. package/dist/openApi.d.ts +1 -1
  54. package/dist/openApi.test.js +1 -0
  55. package/dist/openApiBuilder.d.ts +39 -6
  56. package/dist/openApiBuilder.js +1 -31
  57. package/dist/openApiBuilder.test.js +1 -0
  58. package/dist/openApiValidator.js +1 -0
  59. package/dist/openApiValidator.test.js +65 -0
  60. package/dist/permissions.d.ts +4 -4
  61. package/dist/permissions.js +67 -65
  62. package/dist/permissions.middleware.test.js +1 -0
  63. package/dist/permissions.test.js +1 -0
  64. package/dist/plugins.d.ts +5 -5
  65. package/dist/plugins.js +18 -9
  66. package/dist/plugins.test.js +1 -1
  67. package/dist/populate.d.ts +15 -8
  68. package/dist/populate.js +23 -24
  69. package/dist/populate.test.js +1 -0
  70. package/dist/realtime/changeStreamWatcher.d.ts +73 -0
  71. package/dist/realtime/changeStreamWatcher.js +720 -0
  72. package/dist/realtime/index.d.ts +6 -0
  73. package/dist/realtime/index.js +27 -0
  74. package/dist/realtime/queryMatcher.d.ts +14 -0
  75. package/dist/realtime/queryMatcher.js +250 -0
  76. package/dist/realtime/queryStore.d.ts +37 -0
  77. package/dist/realtime/queryStore.js +195 -0
  78. package/dist/realtime/realtime.test.d.ts +10 -0
  79. package/dist/realtime/realtime.test.js +2158 -0
  80. package/dist/realtime/realtimeApp.d.ts +93 -0
  81. package/dist/realtime/realtimeApp.js +560 -0
  82. package/dist/realtime/registry.d.ts +40 -0
  83. package/dist/realtime/registry.js +38 -0
  84. package/dist/realtime/socketUser.d.ts +10 -0
  85. package/dist/realtime/socketUser.js +17 -0
  86. package/dist/realtime/types.d.ts +100 -0
  87. package/dist/realtime/types.js +2 -0
  88. package/dist/requestContext.d.ts +37 -0
  89. package/dist/requestContext.js +344 -0
  90. package/dist/requestContext.test.d.ts +1 -0
  91. package/dist/requestContext.test.js +241 -0
  92. package/dist/terrenoApp.d.ts +8 -0
  93. package/dist/terrenoApp.js +50 -13
  94. package/dist/terrenoApp.test.js +194 -21
  95. package/dist/terrenoPlugin.d.ts +11 -0
  96. package/dist/tests/bunSetup.js +1 -0
  97. package/dist/tests.js +1 -1
  98. package/dist/transformers.d.ts +2 -2
  99. package/dist/transformers.js +5 -3
  100. package/dist/transformers.test.js +90 -0
  101. package/dist/types/consentResponse.d.ts +6 -3
  102. package/dist/versionCheckPlugin.d.ts +2 -0
  103. package/dist/versionCheckPlugin.js +18 -12
  104. package/package.json +4 -2
  105. package/src/__tests__/versionCheckPlugin.test.ts +37 -3
  106. package/src/api.arrayOperations.test.ts +1 -0
  107. package/src/api.asyncHandler.test.ts +177 -0
  108. package/src/api.errors.test.ts +1 -0
  109. package/src/api.hooks.test.ts +1 -0
  110. package/src/api.query.test.ts +1 -0
  111. package/src/api.test.ts +132 -0
  112. package/src/api.ts +199 -84
  113. package/src/auth.test.ts +160 -0
  114. package/src/auth.ts +120 -50
  115. package/src/betterAuthApp.test.ts +1 -0
  116. package/src/betterAuthSetup.test.ts +1 -0
  117. package/src/betterAuthSetup.ts +46 -19
  118. package/src/config.test.ts +255 -0
  119. package/src/config.ts +206 -0
  120. package/src/configuration.test.ts +1 -0
  121. package/src/configurationApp.ts +59 -24
  122. package/src/configurationPlugin.test.ts +1 -0
  123. package/src/consentApp.test.ts +1 -0
  124. package/src/envConfigurationPlugin.test.ts +143 -0
  125. package/src/envConfigurationPlugin.ts +100 -0
  126. package/src/errors.test.ts +19 -1
  127. package/src/errors.ts +94 -20
  128. package/src/example.ts +46 -21
  129. package/src/express.d.ts +18 -1
  130. package/src/expressServer.test.ts +50 -2
  131. package/src/expressServer.ts +80 -50
  132. package/src/githubAuth.test.ts +1 -0
  133. package/src/githubAuth.ts +59 -38
  134. package/src/index.ts +4 -0
  135. package/src/logger.ts +47 -17
  136. package/src/models/versionConfig.ts +13 -2
  137. package/src/notifiers/googleChatNotifier.test.ts +1 -0
  138. package/src/notifiers/googleChatNotifier.ts +7 -9
  139. package/src/notifiers/slackNotifier.test.ts +29 -3
  140. package/src/notifiers/slackNotifier.ts +9 -7
  141. package/src/notifiers/zoomNotifier.test.ts +1 -0
  142. package/src/notifiers/zoomNotifier.ts +8 -11
  143. package/src/openApi.test.ts +1 -0
  144. package/src/openApi.ts +4 -4
  145. package/src/openApiBuilder.test.ts +1 -0
  146. package/src/openApiBuilder.ts +14 -11
  147. package/src/openApiValidator.test.ts +59 -0
  148. package/src/openApiValidator.ts +3 -2
  149. package/src/permissions.middleware.test.ts +1 -0
  150. package/src/permissions.test.ts +1 -0
  151. package/src/permissions.ts +30 -25
  152. package/src/plugins.test.ts +1 -1
  153. package/src/plugins.ts +21 -14
  154. package/src/populate.test.ts +1 -0
  155. package/src/populate.ts +44 -36
  156. package/src/realtime/changeStreamWatcher.ts +568 -0
  157. package/src/realtime/index.ts +34 -0
  158. package/src/realtime/queryMatcher.ts +179 -0
  159. package/src/realtime/queryStore.ts +132 -0
  160. package/src/realtime/realtime.test.ts +1755 -0
  161. package/src/realtime/realtimeApp.ts +478 -0
  162. package/src/realtime/registry.ts +64 -0
  163. package/src/realtime/socketUser.ts +25 -0
  164. package/src/realtime/types.ts +112 -0
  165. package/src/requestContext.test.ts +196 -0
  166. package/src/requestContext.ts +368 -0
  167. package/src/terrenoApp.test.ts +137 -11
  168. package/src/terrenoApp.ts +64 -17
  169. package/src/terrenoPlugin.ts +12 -0
  170. package/src/tests/bunSetup.ts +1 -0
  171. package/src/tests.ts +7 -2
  172. package/src/transformers.test.ts +70 -2
  173. package/src/transformers.ts +15 -7
  174. package/src/types/consentResponse.ts +8 -10
  175. package/src/versionCheckPlugin.ts +15 -7
package/dist/logger.js CHANGED
@@ -83,50 +83,70 @@ 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.logger = exports.winstonLogger = void 0;
87
- exports.setupLogging = setupLogging;
86
+ exports.setupLogging = exports.logger = exports.winstonLogger = void 0;
88
87
  var node_fs_1 = __importDefault(require("node:fs"));
89
88
  var node_util_1 = require("node:util");
90
89
  var Sentry = __importStar(require("@sentry/bun"));
91
90
  var winston_1 = __importDefault(require("winston"));
92
- function isPrimitive(val) {
91
+ var requestContext_1 = require("./requestContext");
92
+ var isPrimitive = function (val) {
93
93
  return val === null || (typeof val !== "object" && typeof val !== "function");
94
- }
95
- function formatWithInspect(val) {
94
+ };
95
+ var formatWithInspect = function (val) {
96
96
  var prefix = isPrimitive(val) ? "" : "\n";
97
97
  var shouldFormat = typeof val !== "string";
98
98
  return prefix + (shouldFormat ? (0, node_util_1.inspect)(val, { colors: true, depth: null }) : val);
99
- }
99
+ };
100
+ var addRequestContextFormat = winston_1.default.format(function (info) {
101
+ var context = (0, requestContext_1.getCurrentLogContext)();
102
+ return __assign(__assign({}, context), info);
103
+ });
104
+ var formatContext = function (info) {
105
+ var contextParts = [
106
+ info.requestId ? "requestId=".concat(info.requestId) : undefined,
107
+ info.jobId ? "jobId=".concat(info.jobId) : undefined,
108
+ info.sessionId ? "sessionId=".concat(info.sessionId) : undefined,
109
+ info.userId ? "userId=".concat(info.userId) : undefined,
110
+ info.traceId ? "traceId=".concat(info.traceId) : undefined,
111
+ ].filter(Boolean);
112
+ if (contextParts.length === 0) {
113
+ return "";
114
+ }
115
+ return " ".concat(contextParts.join(" "));
116
+ };
100
117
  // Winston doesn't operate like console.log by default, e.g. `logger.error('error',
101
118
  // error)` only prints the message and no args. Add handling for all the args,
102
119
  // while also supporting splat logging.
103
- function printf(timestamp) {
120
+ var printf = function (timestamp) {
104
121
  if (timestamp === void 0) { timestamp = false; }
105
122
  return function (info) {
106
123
  var msg = formatWithInspect(info.message);
107
- var splatArgs = (info[Symbol.for("splat")] || []);
124
+ var splatKey = Symbol.for("splat");
125
+ var splatArgs = (info[splatKey] || []);
108
126
  var rest = splatArgs.map(function (data) { return formatWithInspect(data); }).join(" ");
127
+ var context = formatContext(info);
109
128
  if (timestamp) {
110
- return "".concat(info.timestamp, " - ").concat(info.level, ": ").concat(msg, " ").concat(rest);
129
+ return "".concat(info.timestamp, " - ").concat(info.level, ": ").concat(msg).concat(context, " ").concat(rest);
111
130
  }
112
- return "".concat(info.level, ": ").concat(msg, " ").concat(rest);
131
+ return "".concat(info.level, ": ").concat(msg).concat(context, " ").concat(rest);
113
132
  };
114
- }
133
+ };
115
134
  // Setup a global, default rejection handler.
116
135
  winston_1.default.add(new winston_1.default.transports.Console({
117
136
  debugStdout: true,
118
- format: winston_1.default.format.combine(winston_1.default.format.colorize(), winston_1.default.format.simple(), winston_1.default.format.printf(printf(false))),
137
+ format: winston_1.default.format.combine(addRequestContextFormat(), winston_1.default.format.colorize(), winston_1.default.format.simple(), winston_1.default.format.printf(printf(false))),
119
138
  handleExceptions: true,
120
139
  handleRejections: true,
121
140
  level: "error",
122
141
  }));
123
142
  // Setup a default console logger.
124
143
  exports.winstonLogger = winston_1.default.createLogger({
144
+ format: addRequestContextFormat(),
125
145
  level: "debug",
126
146
  transports: [
127
147
  new winston_1.default.transports.Console({
128
148
  debugStdout: true,
129
- format: winston_1.default.format.combine(winston_1.default.format.colorize(), winston_1.default.format.simple(), winston_1.default.format.printf(printf(false))),
149
+ format: winston_1.default.format.combine(addRequestContextFormat(), winston_1.default.format.colorize(), winston_1.default.format.simple(), winston_1.default.format.printf(printf(false))),
130
150
  handleExceptions: true,
131
151
  handleRejections: true,
132
152
  level: "debug",
@@ -134,11 +154,12 @@ exports.winstonLogger = winston_1.default.createLogger({
134
154
  ],
135
155
  });
136
156
  // Helper function to send logs to Sentry if enabled
137
- function sendToSentry(message, level) {
157
+ var sendToSentry = function (message, level) {
138
158
  if (process.env.USE_SENTRY_LOGGING === "true" && Sentry.logger) {
139
- Sentry.logger[level](message);
159
+ var logWithContext = Sentry.logger[level];
160
+ logWithContext(message, (0, requestContext_1.getCurrentLogContext)());
140
161
  }
141
- }
162
+ };
142
163
  exports.logger = {
143
164
  // simple way to log a caught exception. e.g. promise().catch(logger.catch)
144
165
  catch: function (e) {
@@ -186,12 +207,12 @@ exports.logger = {
186
207
  sendToSentry(msg, "warn");
187
208
  },
188
209
  };
189
- function setupLogging(options) {
210
+ var setupLogging = function (options) {
190
211
  var _a, e_1, _b;
191
212
  var _c, _d;
192
213
  exports.winstonLogger.clear();
193
214
  if (!(options === null || options === void 0 ? void 0 : options.disableConsoleLogging)) {
194
- var formats = [winston_1.default.format.simple()];
215
+ var formats = [addRequestContextFormat(), winston_1.default.format.simple()];
195
216
  if (!(options === null || options === void 0 ? void 0 : options.disableConsoleColors)) {
196
217
  formats.push(winston_1.default.format.colorize());
197
218
  }
@@ -211,7 +232,7 @@ function setupLogging(options) {
211
232
  colorize: false,
212
233
  compress: true,
213
234
  dirname: logDirectory,
214
- format: winston_1.default.format.simple(),
235
+ format: winston_1.default.format.combine(addRequestContextFormat(), winston_1.default.format.simple()),
215
236
  // 30 days of retention
216
237
  maxFiles: 30,
217
238
  // 50MB max file size
@@ -246,4 +267,5 @@ function setupLogging(options) {
246
267
  finally { if (e_1) throw e_1.error; }
247
268
  }
248
269
  }
249
- }
270
+ };
271
+ exports.setupLogging = setupLogging;
@@ -8,6 +8,8 @@ export interface VersionConfigDocument extends mongoose.Document {
8
8
  warningMessage: string;
9
9
  requiredMessage: string;
10
10
  updateUrl?: string;
11
+ /** How often clients should poll for version updates, in minutes. Defaults to 1440 (24 hours). */
12
+ pollingIntervalMinutes: number;
11
13
  created?: Date;
12
14
  updated?: Date;
13
15
  }
@@ -19,6 +19,12 @@ var versionConfigSchema = new mongoose_1.default.Schema({
19
19
  min: 0,
20
20
  type: Number,
21
21
  },
22
+ pollingIntervalMinutes: {
23
+ default: 1440,
24
+ description: "How often clients poll for version updates, in minutes (default: 1440 = 24 hours)",
25
+ min: 1,
26
+ type: Number,
27
+ },
22
28
  requiredMessage: {
23
29
  default: "This version is no longer supported. Please update to continue.",
24
30
  description: "Message shown on the blocking screen",
@@ -62,5 +68,7 @@ versionConfigSchema.add({
62
68
  });
63
69
  versionConfigSchema.index({ _singleton: 1 }, { unique: true });
64
70
  versionConfigSchema.plugin(plugins_1.createdUpdatedPlugin);
71
+ versionConfigSchema.plugin(plugins_1.isDeletedPlugin);
65
72
  versionConfigSchema.plugin(plugins_1.findOneOrNone);
73
+ versionConfigSchema.plugin(plugins_1.findExactlyOne);
66
74
  exports.VersionConfig = mongoose_1.default.model("VersionConfig", versionConfigSchema);
@@ -108,17 +108,16 @@ var sendToGoogleChat = function (messageText_1) {
108
108
  args_1[_i - 1] = arguments[_i];
109
109
  }
110
110
  return __awaiter(void 0, __spreadArray([messageText_1], __read(args_1), false), void 0, function (messageText, _a) {
111
- var chatWebhooksString, msg, chatWebhooks, chatChannel, chatWebhookUrl, msg, formattedMessageText, error_1, errorObj;
112
- var _b, _c, _d;
113
- var _e = _a === void 0 ? {} : _a, channel = _e.channel, _f = _e.shouldThrow, shouldThrow = _f === void 0 ? false : _f, env = _e.env;
114
- return __generator(this, function (_g) {
115
- switch (_g.label) {
111
+ var chatWebhooksString, msg, chatWebhooks, chatChannel, chatWebhookUrl, msg, formattedMessageText, error_1, message;
112
+ var _b;
113
+ var _c = _a === void 0 ? {} : _a, channel = _c.channel, _d = _c.shouldThrow, shouldThrow = _d === void 0 ? false : _d, env = _c.env;
114
+ return __generator(this, function (_e) {
115
+ switch (_e.label) {
116
116
  case 0:
117
117
  chatWebhooksString = process.env.GOOGLE_CHAT_WEBHOOKS;
118
118
  if (!chatWebhooksString) {
119
119
  msg = "GOOGLE_CHAT_WEBHOOKS not set. Google Chat message not sent";
120
- Sentry.captureException(new Error(msg));
121
- logger_1.logger.error(msg);
120
+ Sentry.captureException(new errors_1.APIError({ status: 500, title: msg }));
122
121
  return [2 /*return*/];
123
122
  }
124
123
  chatWebhooks = JSON.parse(chatWebhooksString !== null && chatWebhooksString !== void 0 ? chatWebhooksString : "{}");
@@ -126,30 +125,29 @@ var sendToGoogleChat = function (messageText_1) {
126
125
  chatWebhookUrl = (_b = chatWebhooks[chatChannel]) !== null && _b !== void 0 ? _b : chatWebhooks.default;
127
126
  if (!chatWebhookUrl) {
128
127
  msg = "No webhook url set in env for ".concat(chatChannel, ". Google Chat message not sent");
129
- Sentry.captureException(new Error(msg));
130
- logger_1.logger.error(msg);
128
+ Sentry.captureException(new errors_1.APIError({ status: 500, title: msg }));
131
129
  return [2 /*return*/];
132
130
  }
133
131
  formattedMessageText = messageText;
134
132
  if (env) {
135
133
  formattedMessageText = "[".concat(env.toUpperCase(), "] ").concat(messageText);
136
134
  }
137
- _g.label = 1;
135
+ _e.label = 1;
138
136
  case 1:
139
- _g.trys.push([1, 3, , 4]);
137
+ _e.trys.push([1, 3, , 4]);
140
138
  return [4 /*yield*/, axios_1.default.post(chatWebhookUrl, { text: formattedMessageText })];
141
139
  case 2:
142
- _g.sent();
140
+ _e.sent();
143
141
  return [3 /*break*/, 4];
144
142
  case 3:
145
- error_1 = _g.sent();
146
- errorObj = error_1;
147
- logger_1.logger.error("Error posting to Google Chat: ".concat((_c = errorObj.text) !== null && _c !== void 0 ? _c : errorObj.message));
143
+ error_1 = _e.sent();
144
+ message = (0, errors_1.errorMessage)(error_1);
145
+ logger_1.logger.error("Error posting to Google Chat: ".concat(message));
148
146
  Sentry.captureException(error_1);
149
147
  if (shouldThrow) {
150
148
  throw new errors_1.APIError({
151
149
  status: 500,
152
- title: "Error posting to Google Chat: ".concat((_d = errorObj.text) !== null && _d !== void 0 ? _d : errorObj.message),
150
+ title: "Error posting to Google Chat: ".concat(message),
153
151
  });
154
152
  }
155
153
  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"));
@@ -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
  }