@terreno/api 0.11.7 → 0.11.9

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 (44) hide show
  1. package/dist/betterAuthSetup.js +10 -3
  2. package/dist/configurationPlugin.d.ts +2 -1
  3. package/dist/configurationPlugin.js +16 -9
  4. package/dist/errors.d.ts +6 -6
  5. package/dist/errors.js +22 -22
  6. package/dist/errors.test.d.ts +1 -0
  7. package/dist/errors.test.js +280 -0
  8. package/dist/githubAuth.d.ts +3 -3
  9. package/dist/githubAuth.js +16 -16
  10. package/dist/middleware.d.ts +1 -1
  11. package/dist/middleware.js +4 -3
  12. package/dist/middleware.test.d.ts +1 -0
  13. package/dist/middleware.test.js +82 -0
  14. package/dist/notifiers/googleChatNotifier.js +4 -3
  15. package/dist/notifiers/zoomNotifier.js +12 -11
  16. package/dist/openApiCompat.js +2 -1
  17. package/dist/openApiEtag.d.ts +1 -1
  18. package/dist/openApiEtag.js +4 -3
  19. package/dist/openApiValidator.d.ts +12 -12
  20. package/dist/openApiValidator.js +59 -58
  21. package/dist/plugins.d.ts +12 -1
  22. package/dist/plugins.js +34 -1
  23. package/dist/plugins.test.js +212 -8
  24. package/dist/scriptRunner.d.ts +8 -7
  25. package/dist/secretProviders.js +17 -7
  26. package/dist/types/consentForm.d.ts +4 -2
  27. package/package.json +1 -1
  28. package/src/betterAuthSetup.ts +10 -3
  29. package/src/configurationPlugin.ts +18 -9
  30. package/src/errors.test.ts +302 -0
  31. package/src/errors.ts +18 -13
  32. package/src/githubAuth.ts +11 -10
  33. package/src/middleware.test.ts +71 -0
  34. package/src/middleware.ts +6 -2
  35. package/src/notifiers/googleChatNotifier.ts +4 -3
  36. package/src/notifiers/zoomNotifier.ts +4 -3
  37. package/src/openApiCompat.ts +2 -1
  38. package/src/openApiEtag.ts +2 -2
  39. package/src/openApiValidator.ts +46 -46
  40. package/src/plugins.test.ts +130 -0
  41. package/src/plugins.ts +35 -0
  42. package/src/scriptRunner.ts +23 -27
  43. package/src/secretProviders.ts +27 -9
  44. package/src/types/consentForm.ts +6 -4
@@ -62,6 +62,7 @@ var mongodb_1 = require("better-auth/adapters/mongodb");
62
62
  var node_1 = require("better-auth/node");
63
63
  var mongoose_1 = __importDefault(require("mongoose"));
64
64
  var logger_1 = require("./logger");
65
+ var plugins_1 = require("./plugins");
65
66
  /**
66
67
  * Creates a Better Auth instance with MongoDB adapter.
67
68
  */
@@ -135,7 +136,9 @@ var createBetterAuthSessionMiddleware = function (auth, userModel) {
135
136
  if (!((session === null || session === void 0 ? void 0 : session.user) && (session === null || session === void 0 ? void 0 : session.session))) return [3 /*break*/, 7];
136
137
  betterAuthUser = session.user;
137
138
  if (!userModel) return [3 /*break*/, 6];
138
- return [4 /*yield*/, userModel.findOne({ betterAuthId: betterAuthUser.id })];
139
+ return [4 /*yield*/, (0, plugins_1.findOneOrNoneFor)(userModel, {
140
+ betterAuthId: betterAuthUser.id,
141
+ })];
139
142
  case 2:
140
143
  appUser = _a.sent();
141
144
  if (!appUser) return [3 /*break*/, 3];
@@ -185,7 +188,9 @@ var syncBetterAuthUser = function (userModel, betterAuthUser, oauthProvider) { r
185
188
  switch (_a.label) {
186
189
  case 0:
187
190
  _a.trys.push([0, 8, , 9]);
188
- return [4 /*yield*/, userModel.findOne({ betterAuthId: betterAuthUser.id })];
191
+ return [4 /*yield*/, (0, plugins_1.findOneOrNoneFor)(userModel, {
192
+ betterAuthId: betterAuthUser.id,
193
+ })];
189
194
  case 1:
190
195
  existingUser = _a.sent();
191
196
  if (!existingUser) return [3 /*break*/, 3];
@@ -198,7 +203,9 @@ var syncBetterAuthUser = function (userModel, betterAuthUser, oauthProvider) { r
198
203
  case 2:
199
204
  _a.sent();
200
205
  return [2 /*return*/, existingUser];
201
- case 3: return [4 /*yield*/, userModel.findOne({ email: betterAuthUser.email })];
206
+ case 3: return [4 /*yield*/, (0, plugins_1.findOneOrNoneFor)(userModel, {
207
+ email: betterAuthUser.email,
208
+ })];
202
209
  case 4:
203
210
  userByEmail = _a.sent();
204
211
  if (!userByEmail) return [3 /*break*/, 6];
@@ -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
+ });
@@ -46,12 +46,12 @@ export interface GitHubUserFields {
46
46
  * userSchema.plugin(githubUserPlugin);
47
47
  * ```
48
48
  */
49
- export declare function githubUserPlugin(schema: any): void;
49
+ export declare const githubUserPlugin: (schema: any) => void;
50
50
  /**
51
51
  * Sets up GitHub OAuth authentication strategy.
52
52
  * Call this after setupAuth() in your server initialization.
53
53
  */
54
- export declare function setupGitHubAuth(_app: express.Application, userModel: UserModel, githubOptions: GitHubAuthOptions): void;
54
+ export declare const setupGitHubAuth: (_app: express.Application, userModel: UserModel, githubOptions: GitHubAuthOptions) => void;
55
55
  /**
56
56
  * Adds GitHub OAuth routes to the Express application.
57
57
  *
@@ -61,4 +61,4 @@ export declare function setupGitHubAuth(_app: express.Application, userModel: Us
61
61
  * - POST /auth/github/link - Links GitHub account to authenticated user (requires JWT auth)
62
62
  * - DELETE /auth/github/unlink - Unlinks GitHub account from authenticated user (requires JWT auth)
63
63
  */
64
- export declare function addGitHubAuthRoutes(app: express.Application, userModel: UserModel, githubOptions: GitHubAuthOptions, authOptions?: AuthOptions): void;
64
+ export declare const addGitHubAuthRoutes: (app: express.Application, userModel: UserModel, githubOptions: GitHubAuthOptions, authOptions?: AuthOptions) => void;