@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.
- package/dist/configurationPlugin.d.ts +2 -1
- package/dist/configurationPlugin.js +16 -9
- package/dist/errors.d.ts +6 -6
- package/dist/errors.js +22 -22
- package/dist/errors.test.d.ts +1 -0
- package/dist/errors.test.js +280 -0
- package/dist/middleware.d.ts +1 -1
- package/dist/middleware.js +4 -3
- package/dist/notifiers/zoomNotifier.js +12 -11
- package/dist/openApiCompat.js +2 -1
- package/dist/openApiEtag.d.ts +1 -1
- package/dist/openApiEtag.js +4 -3
- package/dist/openApiValidator.d.ts +12 -12
- package/dist/openApiValidator.js +59 -58
- package/dist/plugins.test.js +22 -0
- package/dist/scriptRunner.d.ts +8 -7
- package/dist/secretProviders.js +17 -7
- package/package.json +1 -1
- package/src/configurationPlugin.ts +18 -9
- package/src/errors.test.ts +302 -0
- package/src/errors.ts +18 -13
- package/src/middleware.ts +6 -2
- package/src/notifiers/zoomNotifier.ts +4 -3
- package/src/openApiCompat.ts +2 -1
- package/src/openApiEtag.ts +2 -2
- package/src/openApiValidator.ts +46 -46
- package/src/plugins.test.ts +29 -0
- package/src/scriptRunner.ts +23 -27
- package/src/secretProviders.ts +27 -9
|
@@ -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
|
|
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.
|
|
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:
|
|
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
|
-
|
|
141
|
-
|
|
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*/,
|
|
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
|
|
64
|
-
export declare
|
|
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
|
|
71
|
-
export declare
|
|
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
|
|
75
|
-
export declare
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
216
|
-
|
|
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
|
+
});
|
package/dist/middleware.d.ts
CHANGED
|
@@ -7,4 +7,4 @@ import type { NextFunction, Request, Response } from "express";
|
|
|
7
7
|
*
|
|
8
8
|
* Expected header: `App-Version`
|
|
9
9
|
*/
|
|
10
|
-
export declare
|
|
10
|
+
export declare const sentryAppVersionMiddleware: (req: Request, _res: Response, next: NextFunction) => void;
|
package/dist/middleware.js
CHANGED
|
@@ -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 =
|
|
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
|
|
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
|
|
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,
|
|
110
|
-
return __generator(this, function (
|
|
111
|
-
switch (
|
|
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
|
-
|
|
152
|
+
_m.label = 1;
|
|
153
153
|
case 1:
|
|
154
|
-
|
|
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
|
-
|
|
162
|
+
_m.sent();
|
|
163
163
|
return [3 /*break*/, 4];
|
|
164
164
|
case 3:
|
|
165
|
-
error_1 =
|
|
166
|
-
|
|
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(
|
|
172
|
+
title: "Error posting to Zoom: ".concat(errorMessage),
|
|
172
173
|
});
|
|
173
174
|
}
|
|
174
175
|
return [3 /*break*/, 4];
|
package/dist/openApiCompat.js
CHANGED
|
@@ -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
|
-
|
|
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
|
/**
|
package/dist/openApiEtag.d.ts
CHANGED
|
@@ -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
|
|
7
|
+
export declare const openApiEtagMiddleware: (req: Request, res: Response, next: NextFunction) => void;
|
package/dist/openApiEtag.js
CHANGED
|
@@ -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 =
|
|
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
|
|
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;
|