@terreno/api 0.11.7 → 0.11.8

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.
@@ -75,7 +75,8 @@ export interface ConfigurationStatics<T extends object> {
75
75
  * const full = await AppConfig.getConfig(); // typed as AppConfigDocument
76
76
  * ```
77
77
  */
78
- export type ConfigurationModel<T extends object> = Model<T> & ConfigurationStatics<T>;
78
+ export interface ConfigurationModel<T extends object> extends Model<T>, ConfigurationStatics<T> {
79
+ }
79
80
  /**
80
81
  * Mongoose schema plugin that adds singleton configuration behavior.
81
82
  *
@@ -50,6 +50,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
50
50
  exports.configurationPlugin = void 0;
51
51
  var errors_1 = require("./errors");
52
52
  var logger_1 = require("./logger");
53
+ var plugins_1 = require("./plugins");
53
54
  // ---------------------------------------------------------------------------
54
55
  // Plugin
55
56
  // ---------------------------------------------------------------------------
@@ -78,6 +79,8 @@ var logger_1 = require("./logger");
78
79
  */
79
80
  var configurationPlugin = function (schema, options) {
80
81
  var pluginOptions = options !== null && options !== void 0 ? options : {};
82
+ // Apply findOneOrNone so the singleton lookup avoids bare Model.findOne (idempotent).
83
+ (0, plugins_1.findOneOrNone)(schema);
81
84
  // Add a sentinel field with a unique index to enforce singleton at the DB level.
82
85
  // All config documents get _singleton: "config", and the unique index prevents duplicates.
83
86
  schema.add({
@@ -92,7 +95,7 @@ var configurationPlugin = function (schema, options) {
92
95
  switch (_a.label) {
93
96
  case 0:
94
97
  if (!this.isNew) return [3 /*break*/, 2];
95
- return [4 /*yield*/, this.constructor.findOne({})];
98
+ return [4 /*yield*/, this.constructor.exists({})];
96
99
  case 1:
97
100
  existing = _a.sent();
98
101
  if (existing) {
@@ -126,28 +129,32 @@ var configurationPlugin = function (schema, options) {
126
129
  // Static: get the singleton configuration document or a value at a path (race-safe via upsert)
127
130
  schema.statics.getConfig = function (key) {
128
131
  return __awaiter(this, void 0, void 0, function () {
129
- var config, err_1, parts, value, parts_1, parts_1_1, part;
132
+ var findSingleton, config, created, err_1, parts, value, parts_1, parts_1_1, part;
130
133
  var e_1, _a;
134
+ var _this = this;
131
135
  return __generator(this, function (_b) {
132
136
  switch (_b.label) {
133
- case 0: return [4 /*yield*/, this.findOne({})];
137
+ case 0:
138
+ findSingleton = function () {
139
+ return _this.findOneOrNone({});
140
+ };
141
+ return [4 /*yield*/, findSingleton()];
134
142
  case 1:
135
143
  config = _b.sent();
136
144
  if (!!config) return [3 /*break*/, 8];
137
145
  _b.label = 2;
138
146
  case 2:
139
147
  _b.trys.push([2, 4, , 8]);
140
- // Use `new` + `save` instead of `create({})` so Mongoose initializes
141
- // nested subdocument defaults (create({}) skips them).
142
- config = new this();
143
- return [4 /*yield*/, config.save()];
148
+ created = new this();
149
+ return [4 /*yield*/, created.save()];
144
150
  case 3:
145
151
  _b.sent();
152
+ config = created;
146
153
  return [3 /*break*/, 8];
147
154
  case 4:
148
155
  err_1 = _b.sent();
149
156
  if (!((err_1 === null || err_1 === void 0 ? void 0 : err_1.status) === 409)) return [3 /*break*/, 6];
150
- return [4 /*yield*/, this.findOne({})];
157
+ return [4 /*yield*/, findSingleton()];
151
158
  case 5:
152
159
  config = _b.sent();
153
160
  return [3 /*break*/, 7];
@@ -158,7 +165,7 @@ var configurationPlugin = function (schema, options) {
158
165
  return [2 /*return*/, config];
159
166
  }
160
167
  parts = key.split(".");
161
- value = config.toObject();
168
+ value = config === null || config === void 0 ? void 0 : config.toObject();
162
169
  try {
163
170
  for (parts_1 = __values(parts), parts_1_1 = parts_1.next(); !parts_1_1.done; parts_1_1 = parts_1.next()) {
164
171
  part = parts_1_1.value;
package/dist/errors.d.ts CHANGED
@@ -60,16 +60,16 @@ export declare class APIError extends Error {
60
60
  disableExternalErrorTracking?: boolean;
61
61
  constructor(data: APIErrorConstructor);
62
62
  }
63
- export declare function errorsPlugin(schema: Schema): void;
64
- export declare function isAPIError(error: Error): error is APIError;
63
+ export declare const errorsPlugin: (schema: Schema) => void;
64
+ export declare const isAPIError: (error: Error) => error is APIError;
65
65
  /**
66
66
  * Safely extracts the disableExternalErrorTracking property from an error.
67
67
  * Works with both APIError instances and regular Error objects that may have
68
68
  * this property attached.
69
69
  */
70
- export declare function getDisableExternalErrorTracking(error: unknown): boolean | undefined;
71
- export declare function getAPIErrorBody(error: APIError): {
70
+ export declare const getDisableExternalErrorTracking: (error: unknown) => boolean | undefined;
71
+ export declare const getAPIErrorBody: (error: APIError) => {
72
72
  [id: string]: any;
73
73
  };
74
- export declare function apiUnauthorizedMiddleware(err: Error, _req: Request, res: Response, next: NextFunction): void;
75
- export declare function apiErrorMiddleware(err: Error, _req: Request, res: Response, next: NextFunction): void;
74
+ export declare const apiUnauthorizedMiddleware: (err: Error, _req: Request, res: Response, next: NextFunction) => void;
75
+ export declare const apiErrorMiddleware: (err: Error, _req: Request, res: Response, next: NextFunction) => void;
package/dist/errors.js CHANGED
@@ -59,13 +59,7 @@ var __values = (this && this.__values) || function(o) {
59
59
  throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
60
60
  };
61
61
  Object.defineProperty(exports, "__esModule", { value: true });
62
- exports.APIError = void 0;
63
- exports.errorsPlugin = errorsPlugin;
64
- exports.isAPIError = isAPIError;
65
- exports.getDisableExternalErrorTracking = getDisableExternalErrorTracking;
66
- exports.getAPIErrorBody = getAPIErrorBody;
67
- exports.apiUnauthorizedMiddleware = apiUnauthorizedMiddleware;
68
- exports.apiErrorMiddleware = apiErrorMiddleware;
62
+ exports.apiErrorMiddleware = exports.apiUnauthorizedMiddleware = exports.getAPIErrorBody = exports.getDisableExternalErrorTracking = exports.isAPIError = exports.errorsPlugin = exports.APIError = void 0;
69
63
  // https://jsonapi.org/format/#errors
70
64
  var Sentry = __importStar(require("@sentry/bun"));
71
65
  var mongoose_1 = require("mongoose");
@@ -129,7 +123,7 @@ exports.APIError = APIError;
129
123
  // may not be fully initialized when this module loads.
130
124
  // Create an errors field for storing error information in a JSONAPI compatible form directly on a
131
125
  // model.
132
- function errorsPlugin(schema) {
126
+ var errorsPlugin = function (schema) {
133
127
  var errorSchema = new mongoose_1.Schema({
134
128
  code: { description: "Application-specific error code", type: String },
135
129
  detail: { description: "Human-readable explanation of the error", type: String },
@@ -151,18 +145,20 @@ function errorsPlugin(schema) {
151
145
  title: { description: "Short summary of the error", required: true, type: String },
152
146
  });
153
147
  schema.add({ apiErrors: errorSchema });
154
- }
155
- function isAPIError(error) {
148
+ };
149
+ exports.errorsPlugin = errorsPlugin;
150
+ var isAPIError = function (error) {
156
151
  return error.name === "APIError";
157
- }
152
+ };
153
+ exports.isAPIError = isAPIError;
158
154
  /**
159
155
  * Safely extracts the disableExternalErrorTracking property from an error.
160
156
  * Works with both APIError instances and regular Error objects that may have
161
157
  * this property attached.
162
158
  */
163
- function getDisableExternalErrorTracking(error) {
159
+ var getDisableExternalErrorTracking = function (error) {
164
160
  if (error instanceof Error) {
165
- if (isAPIError(error)) {
161
+ if ((0, exports.isAPIError)(error)) {
166
162
  return error.disableExternalErrorTracking;
167
163
  }
168
164
  }
@@ -170,11 +166,12 @@ function getDisableExternalErrorTracking(error) {
170
166
  return error.disableExternalErrorTracking;
171
167
  }
172
168
  return undefined;
173
- }
169
+ };
170
+ exports.getDisableExternalErrorTracking = getDisableExternalErrorTracking;
174
171
  // Creates an APIError body to send to clients as JSON. Errors don't have a toJSON defined,
175
172
  // and we want to strip out things like message, name, and stack for the client.
176
173
  // There is almost certainly a more elegant solution to this.
177
- function getAPIErrorBody(error) {
174
+ var getAPIErrorBody = function (error) {
178
175
  var e_1, _a;
179
176
  var errorData = { status: error.status, title: error.title };
180
177
  try {
@@ -202,8 +199,9 @@ function getAPIErrorBody(error) {
202
199
  finally { if (e_1) throw e_1.error; }
203
200
  }
204
201
  return errorData;
205
- }
206
- function apiUnauthorizedMiddleware(err, _req, res, next) {
202
+ };
203
+ exports.getAPIErrorBody = getAPIErrorBody;
204
+ var apiUnauthorizedMiddleware = function (err, _req, res, next) {
207
205
  if (err.message === "Unauthorized") {
208
206
  // not using the actual APIError class here because we don't want to log it as an error.
209
207
  res.status(401).json({ status: 401, title: "Unauthorized" }).send();
@@ -211,15 +209,17 @@ function apiUnauthorizedMiddleware(err, _req, res, next) {
211
209
  else {
212
210
  next(err);
213
211
  }
214
- }
215
- function apiErrorMiddleware(err, _req, res, next) {
216
- if (isAPIError(err)) {
212
+ };
213
+ exports.apiUnauthorizedMiddleware = apiUnauthorizedMiddleware;
214
+ var apiErrorMiddleware = function (err, _req, res, next) {
215
+ if ((0, exports.isAPIError)(err)) {
217
216
  if (!err.disableExternalErrorTracking) {
218
217
  Sentry.captureException(err);
219
218
  }
220
- res.status(err.status).json(getAPIErrorBody(err)).send();
219
+ res.status(err.status).json((0, exports.getAPIErrorBody)(err)).send();
221
220
  }
222
221
  else {
223
222
  next(err);
224
223
  }
225
- }
224
+ };
225
+ exports.apiErrorMiddleware = apiErrorMiddleware;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,280 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ var bun_test_1 = require("bun:test");
37
+ var Sentry = __importStar(require("@sentry/bun"));
38
+ var mongoose_1 = require("mongoose");
39
+ var errors_1 = require("./errors");
40
+ var buildResponse = function () {
41
+ var res = {
42
+ json: (0, bun_test_1.mock)(function () { return res; }),
43
+ send: (0, bun_test_1.mock)(function () { return res; }),
44
+ status: (0, bun_test_1.mock)(function () { return res; }),
45
+ };
46
+ return res;
47
+ };
48
+ (0, bun_test_1.describe)("APIError", function () {
49
+ (0, bun_test_1.it)("creates an error with the provided fields", function () {
50
+ var error = new errors_1.APIError({
51
+ code: "validation-failed",
52
+ detail: "Email is invalid",
53
+ id: "abc-123",
54
+ links: { about: "https://example.com/help", type: "https://example.com/types/validation" },
55
+ meta: { requestId: "req-1" },
56
+ source: { header: "x-foo", parameter: "limit", pointer: "/data/email" },
57
+ status: 400,
58
+ title: "Validation failed",
59
+ });
60
+ (0, bun_test_1.expect)(error).toBeInstanceOf(Error);
61
+ (0, bun_test_1.expect)(error.name).toBe("APIError");
62
+ (0, bun_test_1.expect)(error.title).toBe("Validation failed");
63
+ (0, bun_test_1.expect)(error.detail).toBe("Email is invalid");
64
+ (0, bun_test_1.expect)(error.code).toBe("validation-failed");
65
+ (0, bun_test_1.expect)(error.status).toBe(400);
66
+ (0, bun_test_1.expect)(error.id).toBe("abc-123");
67
+ (0, bun_test_1.expect)(error.links).toEqual({
68
+ about: "https://example.com/help",
69
+ type: "https://example.com/types/validation",
70
+ });
71
+ (0, bun_test_1.expect)(error.source).toEqual({
72
+ header: "x-foo",
73
+ parameter: "limit",
74
+ pointer: "/data/email",
75
+ });
76
+ (0, bun_test_1.expect)(error.meta).toEqual({ requestId: "req-1" });
77
+ });
78
+ (0, bun_test_1.it)("includes the title and detail in the error message", function () {
79
+ var error = new errors_1.APIError({ detail: "Something exploded", title: "Boom" });
80
+ (0, bun_test_1.expect)(error.message).toBe("Boom: Something exploded");
81
+ });
82
+ (0, bun_test_1.it)("includes the wrapped error stack in the message", function () {
83
+ var _a;
84
+ var wrapped = new Error("inner");
85
+ var error = new errors_1.APIError({ error: wrapped, title: "Outer" });
86
+ (0, bun_test_1.expect)(error.message).toContain("Outer");
87
+ (0, bun_test_1.expect)(error.message).toContain((_a = wrapped.stack) !== null && _a !== void 0 ? _a : "");
88
+ });
89
+ (0, bun_test_1.it)("defaults status to 500 when status is omitted", function () {
90
+ var error = new errors_1.APIError({ title: "No status" });
91
+ (0, bun_test_1.expect)(error.status).toBe(500);
92
+ });
93
+ (0, bun_test_1.it)("forces status to 500 when below 400", function () {
94
+ var error = new errors_1.APIError({ status: 200, title: "Too low" });
95
+ (0, bun_test_1.expect)(error.status).toBe(500);
96
+ });
97
+ (0, bun_test_1.it)("forces status to 500 when above 599", function () {
98
+ var error = new errors_1.APIError({ status: 600, title: "Too high" });
99
+ (0, bun_test_1.expect)(error.status).toBe(500);
100
+ });
101
+ (0, bun_test_1.it)("defaults meta to an empty object when not provided", function () {
102
+ var error = new errors_1.APIError({ title: "No meta" });
103
+ (0, bun_test_1.expect)(error.meta).toEqual({});
104
+ });
105
+ (0, bun_test_1.it)("merges fields into meta", function () {
106
+ var _a;
107
+ var error = new errors_1.APIError({
108
+ fields: { email: "Required", name: "Required" },
109
+ title: "Validation",
110
+ });
111
+ (0, bun_test_1.expect)((_a = error.meta) === null || _a === void 0 ? void 0 : _a.fields).toEqual({ email: "Required", name: "Required" });
112
+ });
113
+ (0, bun_test_1.it)("respects disableExternalErrorTracking", function () {
114
+ var trackedError = new errors_1.APIError({ title: "Tracked" });
115
+ var untrackedError = new errors_1.APIError({
116
+ disableExternalErrorTracking: true,
117
+ title: "Untracked",
118
+ });
119
+ (0, bun_test_1.expect)(trackedError.disableExternalErrorTracking).toBeUndefined();
120
+ (0, bun_test_1.expect)(untrackedError.disableExternalErrorTracking).toBe(true);
121
+ });
122
+ });
123
+ (0, bun_test_1.describe)("isAPIError", function () {
124
+ (0, bun_test_1.it)("returns true for an APIError instance", function () {
125
+ (0, bun_test_1.expect)((0, errors_1.isAPIError)(new errors_1.APIError({ title: "Boom" }))).toBe(true);
126
+ });
127
+ (0, bun_test_1.it)("returns false for a regular Error", function () {
128
+ (0, bun_test_1.expect)((0, errors_1.isAPIError)(new Error("nope"))).toBe(false);
129
+ });
130
+ (0, bun_test_1.it)("returns true for any error whose name is APIError", function () {
131
+ var err = new Error("custom");
132
+ err.name = "APIError";
133
+ (0, bun_test_1.expect)((0, errors_1.isAPIError)(err)).toBe(true);
134
+ });
135
+ });
136
+ (0, bun_test_1.describe)("getDisableExternalErrorTracking", function () {
137
+ (0, bun_test_1.it)("returns the flag from an APIError", function () {
138
+ var error = new errors_1.APIError({ disableExternalErrorTracking: true, title: "Test" });
139
+ (0, bun_test_1.expect)((0, errors_1.getDisableExternalErrorTracking)(error)).toBe(true);
140
+ });
141
+ (0, bun_test_1.it)("returns undefined for a plain Error without the flag", function () {
142
+ (0, bun_test_1.expect)((0, errors_1.getDisableExternalErrorTracking)(new Error("plain"))).toBeUndefined();
143
+ });
144
+ (0, bun_test_1.it)("returns the flag when attached to a non-APIError object", function () {
145
+ var error = { disableExternalErrorTracking: false };
146
+ (0, bun_test_1.expect)((0, errors_1.getDisableExternalErrorTracking)(error)).toBe(false);
147
+ });
148
+ (0, bun_test_1.it)("returns undefined for primitives and null", function () {
149
+ (0, bun_test_1.expect)((0, errors_1.getDisableExternalErrorTracking)(null)).toBeUndefined();
150
+ (0, bun_test_1.expect)((0, errors_1.getDisableExternalErrorTracking)(undefined)).toBeUndefined();
151
+ (0, bun_test_1.expect)((0, errors_1.getDisableExternalErrorTracking)("string")).toBeUndefined();
152
+ (0, bun_test_1.expect)((0, errors_1.getDisableExternalErrorTracking)(42)).toBeUndefined();
153
+ });
154
+ (0, bun_test_1.it)("returns undefined for an object missing the property", function () {
155
+ (0, bun_test_1.expect)((0, errors_1.getDisableExternalErrorTracking)({ foo: "bar" })).toBeUndefined();
156
+ });
157
+ });
158
+ (0, bun_test_1.describe)("getAPIErrorBody", function () {
159
+ (0, bun_test_1.it)("returns title and status by default", function () {
160
+ var error = new errors_1.APIError({ status: 404, title: "Not Found" });
161
+ var body = (0, errors_1.getAPIErrorBody)(error);
162
+ (0, bun_test_1.expect)(body).toEqual({ meta: {}, status: 404, title: "Not Found" });
163
+ });
164
+ (0, bun_test_1.it)("includes optional fields when set", function () {
165
+ var error = new errors_1.APIError({
166
+ code: "not-found",
167
+ detail: "Could not find resource",
168
+ disableExternalErrorTracking: true,
169
+ id: "err-1",
170
+ links: { about: "https://example.com/help" },
171
+ source: { pointer: "/data/id" },
172
+ status: 404,
173
+ title: "Not Found",
174
+ });
175
+ var body = (0, errors_1.getAPIErrorBody)(error);
176
+ (0, bun_test_1.expect)(body).toEqual({
177
+ code: "not-found",
178
+ detail: "Could not find resource",
179
+ disableExternalErrorTracking: true,
180
+ id: "err-1",
181
+ links: { about: "https://example.com/help" },
182
+ meta: {},
183
+ source: { pointer: "/data/id" },
184
+ status: 404,
185
+ title: "Not Found",
186
+ });
187
+ });
188
+ (0, bun_test_1.it)("omits empty meta and unset optional fields", function () {
189
+ var error = new errors_1.APIError({ status: 400, title: "Bad" });
190
+ // meta defaults to {} which is truthy, so it is included.
191
+ var body = (0, errors_1.getAPIErrorBody)(error);
192
+ (0, bun_test_1.expect)(body.meta).toEqual({});
193
+ (0, bun_test_1.expect)(body.code).toBeUndefined();
194
+ (0, bun_test_1.expect)(body.detail).toBeUndefined();
195
+ (0, bun_test_1.expect)(body.id).toBeUndefined();
196
+ (0, bun_test_1.expect)(body.links).toBeUndefined();
197
+ (0, bun_test_1.expect)(body.source).toBeUndefined();
198
+ });
199
+ });
200
+ (0, bun_test_1.describe)("errorsPlugin", function () {
201
+ (0, bun_test_1.it)("adds an apiErrors array field to the schema", function () {
202
+ var schema = new mongoose_1.Schema({ name: String });
203
+ (0, errors_1.errorsPlugin)(schema);
204
+ var path = schema.path("apiErrors");
205
+ (0, bun_test_1.expect)(path).toBeDefined();
206
+ });
207
+ (0, bun_test_1.it)("requires title on each error subdocument", function () {
208
+ var schema = new mongoose_1.Schema({ name: String });
209
+ (0, errors_1.errorsPlugin)(schema);
210
+ var path = schema.path("apiErrors");
211
+ // Inspect the embedded error schema for the title definition.
212
+ var embedded = path;
213
+ var titlePath = embedded.schema.path("title");
214
+ (0, bun_test_1.expect)(titlePath).toBeDefined();
215
+ (0, bun_test_1.expect)(titlePath.isRequired).toBe(true);
216
+ });
217
+ });
218
+ (0, bun_test_1.describe)("apiUnauthorizedMiddleware", function () {
219
+ var res;
220
+ var next;
221
+ var req = {};
222
+ (0, bun_test_1.beforeEach)(function () {
223
+ res = buildResponse();
224
+ next = (0, bun_test_1.mock)(function () { });
225
+ });
226
+ (0, bun_test_1.it)("returns a 401 JSON response when the message is Unauthorized", function () {
227
+ (0, errors_1.apiUnauthorizedMiddleware)(new Error("Unauthorized"), req, res, next);
228
+ (0, bun_test_1.expect)(res.status).toHaveBeenCalledWith(401);
229
+ (0, bun_test_1.expect)(res.json).toHaveBeenCalledWith({ status: 401, title: "Unauthorized" });
230
+ (0, bun_test_1.expect)(res.send).toHaveBeenCalled();
231
+ (0, bun_test_1.expect)(next).not.toHaveBeenCalled();
232
+ });
233
+ (0, bun_test_1.it)("forwards other errors to next", function () {
234
+ var err = new Error("Something else");
235
+ (0, errors_1.apiUnauthorizedMiddleware)(err, req, res, next);
236
+ (0, bun_test_1.expect)(next).toHaveBeenCalledWith(err);
237
+ (0, bun_test_1.expect)(res.status).not.toHaveBeenCalled();
238
+ });
239
+ });
240
+ (0, bun_test_1.describe)("apiErrorMiddleware", function () {
241
+ var res;
242
+ var next;
243
+ var req = {};
244
+ var captureExceptionSpy = Sentry.captureException;
245
+ (0, bun_test_1.beforeEach)(function () {
246
+ var _a;
247
+ res = buildResponse();
248
+ next = (0, bun_test_1.mock)(function () { });
249
+ (_a = captureExceptionSpy.mockClear) === null || _a === void 0 ? void 0 : _a.call(captureExceptionSpy);
250
+ });
251
+ (0, bun_test_1.it)("responds with the APIError status and body", function () {
252
+ var err = new errors_1.APIError({ detail: "missing", status: 404, title: "Not Found" });
253
+ (0, errors_1.apiErrorMiddleware)(err, req, res, next);
254
+ (0, bun_test_1.expect)(res.status).toHaveBeenCalledWith(404);
255
+ (0, bun_test_1.expect)(res.json).toHaveBeenCalledWith((0, errors_1.getAPIErrorBody)(err));
256
+ (0, bun_test_1.expect)(res.send).toHaveBeenCalled();
257
+ (0, bun_test_1.expect)(next).not.toHaveBeenCalled();
258
+ });
259
+ (0, bun_test_1.it)("captures the exception with Sentry by default", function () {
260
+ var err = new errors_1.APIError({ status: 500, title: "Boom" });
261
+ (0, errors_1.apiErrorMiddleware)(err, req, res, next);
262
+ (0, bun_test_1.expect)(captureExceptionSpy).toHaveBeenCalledWith(err);
263
+ });
264
+ (0, bun_test_1.it)("does not capture the exception when disableExternalErrorTracking is true", function () {
265
+ var err = new errors_1.APIError({
266
+ disableExternalErrorTracking: true,
267
+ status: 500,
268
+ title: "Quiet",
269
+ });
270
+ (0, errors_1.apiErrorMiddleware)(err, req, res, next);
271
+ (0, bun_test_1.expect)(captureExceptionSpy).not.toHaveBeenCalled();
272
+ (0, bun_test_1.expect)(res.status).toHaveBeenCalledWith(500);
273
+ });
274
+ (0, bun_test_1.it)("forwards non-APIError errors to next", function () {
275
+ var err = new Error("not an api error");
276
+ (0, errors_1.apiErrorMiddleware)(err, req, res, next);
277
+ (0, bun_test_1.expect)(next).toHaveBeenCalledWith(err);
278
+ (0, bun_test_1.expect)(res.status).not.toHaveBeenCalled();
279
+ });
280
+ });
@@ -7,4 +7,4 @@ import type { NextFunction, Request, Response } from "express";
7
7
  *
8
8
  * Expected header: `App-Version`
9
9
  */
10
- export declare function sentryAppVersionMiddleware(req: Request, _res: Response, next: NextFunction): void;
10
+ export declare const sentryAppVersionMiddleware: (req: Request, _res: Response, next: NextFunction) => void;
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.sentryAppVersionMiddleware = sentryAppVersionMiddleware;
36
+ exports.sentryAppVersionMiddleware = void 0;
37
37
  var Sentry = __importStar(require("@sentry/bun"));
38
38
  /**
39
39
  * Express middleware that captures the app version from the request header
@@ -43,10 +43,11 @@ var Sentry = __importStar(require("@sentry/bun"));
43
43
  *
44
44
  * Expected header: `App-Version`
45
45
  */
46
- function sentryAppVersionMiddleware(req, _res, next) {
46
+ var sentryAppVersionMiddleware = function (req, _res, next) {
47
47
  var appVersion = req.get("App-Version");
48
48
  if (appVersion) {
49
49
  Sentry.getCurrentScope().setTag("app_version", appVersion);
50
50
  }
51
51
  next();
52
- }
52
+ };
53
+ exports.sentryAppVersionMiddleware = sentryAppVersionMiddleware;
@@ -103,12 +103,12 @@ 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;
107
- var _e, _f, _g, _h, _j, _k, _l, _m;
106
+ var zoomWebhooksString, msg, zoomWebhooks, zoomChannel, zoomWebhookUrl, msg, zoomToken, msg, messageBody, error_1, errorMessage;
107
+ var _e, _f, _g, _h, _j, _k;
108
108
  var header = _c.header, body = _c.body, subheader = _c.subheader;
109
- var channel = _d.channel, _o = _d.shouldThrow, shouldThrow = _o === void 0 ? false : _o, env = _d.env;
110
- return __generator(this, function (_p) {
111
- switch (_p.label) {
109
+ var channel = _d.channel, _l = _d.shouldThrow, shouldThrow = _l === void 0 ? false : _l, env = _d.env;
110
+ return __generator(this, function (_m) {
111
+ switch (_m.label) {
112
112
  case 0:
113
113
  zoomWebhooksString = process.env.ZOOM_CHAT_WEBHOOKS;
114
114
  if (!zoomWebhooksString) {
@@ -149,9 +149,9 @@ var sendToZoom = function (_a, _b) { return __awaiter(void 0, [_a, _b], void 0,
149
149
  text: subheader,
150
150
  };
151
151
  }
152
- _p.label = 1;
152
+ _m.label = 1;
153
153
  case 1:
154
- _p.trys.push([1, 3, , 4]);
154
+ _m.trys.push([1, 3, , 4]);
155
155
  return [4 /*yield*/, axios_1.default.post("".concat(zoomWebhookUrl, "?format=full"), { content: messageBody }, {
156
156
  headers: {
157
157
  Authorization: zoomToken,
@@ -159,16 +159,17 @@ var sendToZoom = function (_a, _b) { return __awaiter(void 0, [_a, _b], void 0,
159
159
  },
160
160
  })];
161
161
  case 2:
162
- _p.sent();
162
+ _m.sent();
163
163
  return [3 /*break*/, 4];
164
164
  case 3:
165
- error_1 = _p.sent();
166
- logger_1.logger.error("Error posting to Zoom: ".concat((_l = error_1.text) !== null && _l !== void 0 ? _l : error_1.message));
165
+ 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));
167
168
  Sentry.captureException(error_1);
168
169
  if (shouldThrow) {
169
170
  throw new errors_1.APIError({
170
171
  status: 500,
171
- title: "Error posting to Zoom: ".concat((_m = error_1.text) !== null && _m !== void 0 ? _m : error_1.message),
172
+ title: "Error posting to Zoom: ".concat(errorMessage),
172
173
  });
173
174
  }
174
175
  return [3 /*break*/, 4];
@@ -158,7 +158,7 @@ var patchRouterStack = function (stack) {
158
158
  */
159
159
  var patchAppUse = function (app) {
160
160
  var originalUse = app.use.bind(app);
161
- app.use = function patchedUse() {
161
+ var patchedUse = function () {
162
162
  var _a, _b;
163
163
  var args = [];
164
164
  for (var _i = 0; _i < arguments.length; _i++) {
@@ -182,6 +182,7 @@ var patchAppUse = function (app) {
182
182
  }
183
183
  return result;
184
184
  };
185
+ app.use = patchedUse;
185
186
  };
186
187
  exports.patchAppUse = patchAppUse;
187
188
  /**
@@ -4,4 +4,4 @@ import type { NextFunction, Request, Response } from "express";
4
4
  * This middleware should be added before the @wesleytodd/openapi middleware
5
5
  * to intercept requests to /openapi.json and add conditional request support.
6
6
  */
7
- export declare function openApiEtagMiddleware(req: Request, res: Response, next: NextFunction): void;
7
+ export declare const openApiEtagMiddleware: (req: Request, res: Response, next: NextFunction) => void;
@@ -3,14 +3,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.openApiEtagMiddleware = openApiEtagMiddleware;
6
+ exports.openApiEtagMiddleware = void 0;
7
7
  var node_crypto_1 = __importDefault(require("node:crypto"));
8
8
  /**
9
9
  * Middleware to add ETag support for OpenAPI JSON endpoint.
10
10
  * This middleware should be added before the @wesleytodd/openapi middleware
11
11
  * to intercept requests to /openapi.json and add conditional request support.
12
12
  */
13
- function openApiEtagMiddleware(req, res, next) {
13
+ var openApiEtagMiddleware = function (req, res, next) {
14
14
  // Only handle GET requests to /openapi.json
15
15
  if (req.method !== "GET" || req.path !== "/openapi.json") {
16
16
  next();
@@ -35,4 +35,5 @@ function openApiEtagMiddleware(req, res, next) {
35
35
  return originalJson(body);
36
36
  };
37
37
  next();
38
- }
38
+ };
39
+ exports.openApiEtagMiddleware = openApiEtagMiddleware;