@terreno/api 0.9.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,341 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || (function () {
30
+ var ownKeys = function(o) {
31
+ ownKeys = Object.getOwnPropertyNames || function (o) {
32
+ var ar = [];
33
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34
+ return ar;
35
+ };
36
+ return ownKeys(o);
37
+ };
38
+ return function (mod) {
39
+ if (mod && mod.__esModule) return mod;
40
+ var result = {};
41
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
42
+ __setModuleDefault(result, mod);
43
+ return result;
44
+ };
45
+ })();
46
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
47
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
48
+ return new (P || (P = Promise))(function (resolve, reject) {
49
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
50
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
51
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
52
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
53
+ });
54
+ };
55
+ var __generator = (this && this.__generator) || function (thisArg, body) {
56
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
57
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
58
+ function verb(n) { return function (v) { return step([n, v]); }; }
59
+ function step(op) {
60
+ if (f) throw new TypeError("Generator is already executing.");
61
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
62
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
63
+ if (y = 0, t) op = [op[0] & 2, t.value];
64
+ switch (op[0]) {
65
+ case 0: case 1: t = op; break;
66
+ case 4: _.label++; return { value: op[1], done: false };
67
+ case 5: _.label++; y = op[1]; op = [0]; continue;
68
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
69
+ default:
70
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
71
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
72
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
73
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
74
+ if (t[2]) _.ops.pop();
75
+ _.trys.pop(); continue;
76
+ }
77
+ op = body.call(thisArg, _);
78
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
79
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
80
+ }
81
+ };
82
+ var __read = (this && this.__read) || function (o, n) {
83
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
84
+ if (!m) return o;
85
+ var i = m.call(o), r, ar = [], e;
86
+ try {
87
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
88
+ }
89
+ catch (error) { e = { error: error }; }
90
+ finally {
91
+ try {
92
+ if (r && !r.done && (m = i["return"])) m.call(i);
93
+ }
94
+ finally { if (e) throw e.error; }
95
+ }
96
+ return ar;
97
+ };
98
+ Object.defineProperty(exports, "__esModule", { value: true });
99
+ var bun_test_1 = require("bun:test");
100
+ var Sentry = __importStar(require("@sentry/bun"));
101
+ var errors_1 = require("./errors");
102
+ var permissions_1 = require("./permissions");
103
+ (0, bun_test_1.describe)("permissionMiddleware", function () {
104
+ var allPermissions = {
105
+ create: [permissions_1.Permissions.IsAny],
106
+ delete: [permissions_1.Permissions.IsAny],
107
+ list: [permissions_1.Permissions.IsAny],
108
+ read: [permissions_1.Permissions.IsAny],
109
+ update: [permissions_1.Permissions.IsAny],
110
+ };
111
+ var buildReq = function (overrides) {
112
+ if (overrides === void 0) { overrides = {}; }
113
+ return __assign({ method: "GET", params: {}, user: { id: "user-1" } }, overrides);
114
+ };
115
+ (0, bun_test_1.it)("calls next immediately for OPTIONS requests", function () { return __awaiter(void 0, void 0, void 0, function () {
116
+ var model, middleware, next;
117
+ return __generator(this, function (_a) {
118
+ switch (_a.label) {
119
+ case 0:
120
+ model = {
121
+ collection: { findOne: (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
122
+ return [2 /*return*/, null];
123
+ }); }); }) },
124
+ findById: (0, bun_test_1.mock)(function () { return ({ exec: (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
125
+ return [2 /*return*/, null];
126
+ }); }); }) }); }),
127
+ modelName: "MockModel",
128
+ };
129
+ middleware = (0, permissions_1.permissionMiddleware)(model, { permissions: allPermissions });
130
+ next = (0, bun_test_1.mock)(function () { });
131
+ return [4 /*yield*/, middleware(buildReq({ method: "OPTIONS" }), {}, next)];
132
+ case 1:
133
+ _a.sent();
134
+ (0, bun_test_1.expect)(next).toHaveBeenCalledTimes(1);
135
+ (0, bun_test_1.expect)(next.mock.calls[0]).toEqual([]);
136
+ (0, bun_test_1.expect)(model.findById).toHaveBeenCalledTimes(0);
137
+ return [2 /*return*/];
138
+ }
139
+ });
140
+ }); });
141
+ (0, bun_test_1.it)("returns APIError for unsupported HTTP methods", function () { return __awaiter(void 0, void 0, void 0, function () {
142
+ var model, middleware, next, _a, error;
143
+ return __generator(this, function (_b) {
144
+ switch (_b.label) {
145
+ case 0:
146
+ model = {
147
+ collection: { findOne: (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
148
+ return [2 /*return*/, null];
149
+ }); }); }) },
150
+ findById: (0, bun_test_1.mock)(function () { return ({ exec: (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
151
+ return [2 /*return*/, null];
152
+ }); }); }) }); }),
153
+ modelName: "MockModel",
154
+ };
155
+ middleware = (0, permissions_1.permissionMiddleware)(model, { permissions: allPermissions });
156
+ next = (0, bun_test_1.mock)(function () { });
157
+ return [4 /*yield*/, middleware(buildReq({ method: "TRACE" }), {}, next)];
158
+ case 1:
159
+ _b.sent();
160
+ (0, bun_test_1.expect)(next).toHaveBeenCalledTimes(1);
161
+ _a = __read(next.mock.calls[0], 1), error = _a[0];
162
+ (0, bun_test_1.expect)(error).toBeInstanceOf(errors_1.APIError);
163
+ (0, bun_test_1.expect)(error.status).toBe(405);
164
+ (0, bun_test_1.expect)(error.title).toContain("Method TRACE not allowed");
165
+ return [2 /*return*/];
166
+ }
167
+ });
168
+ }); });
169
+ (0, bun_test_1.it)("wraps query execution failures in a 500 APIError", function () { return __awaiter(void 0, void 0, void 0, function () {
170
+ var exec, model, middleware, next, _a, error;
171
+ return __generator(this, function (_b) {
172
+ switch (_b.label) {
173
+ case 0:
174
+ exec = (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () {
175
+ return __generator(this, function (_a) {
176
+ throw new Error("query failed");
177
+ });
178
+ }); });
179
+ model = {
180
+ collection: { findOne: (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
181
+ return [2 /*return*/, null];
182
+ }); }); }) },
183
+ findById: (0, bun_test_1.mock)(function () { return ({ exec: exec }); }),
184
+ modelName: "MockModel",
185
+ };
186
+ middleware = (0, permissions_1.permissionMiddleware)(model, { permissions: allPermissions });
187
+ next = (0, bun_test_1.mock)(function () { });
188
+ return [4 /*yield*/, middleware(buildReq({ method: "GET", params: { id: "507f1f77bcf86cd799439011" } }), {}, next)];
189
+ case 1:
190
+ _b.sent();
191
+ (0, bun_test_1.expect)(exec).toHaveBeenCalledTimes(1);
192
+ _a = __read(next.mock.calls[0], 1), error = _a[0];
193
+ (0, bun_test_1.expect)(error).toBeInstanceOf(errors_1.APIError);
194
+ (0, bun_test_1.expect)(error.status).toBe(500);
195
+ (0, bun_test_1.expect)(error.title).toContain("GET failed on 507f1f77bcf86cd799439011");
196
+ return [2 /*return*/];
197
+ }
198
+ });
199
+ }); });
200
+ (0, bun_test_1.it)("captures sentry message when document does not exist", function () { return __awaiter(void 0, void 0, void 0, function () {
201
+ var captureMessageSpy, model, middleware, next, _a, error;
202
+ return __generator(this, function (_b) {
203
+ switch (_b.label) {
204
+ case 0:
205
+ captureMessageSpy = (0, bun_test_1.spyOn)(Sentry, "captureMessage");
206
+ model = {
207
+ collection: { findOne: (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
208
+ return [2 /*return*/, null];
209
+ }); }); }) },
210
+ findById: (0, bun_test_1.mock)(function () { return ({ exec: (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
211
+ return [2 /*return*/, null];
212
+ }); }); }) }); }),
213
+ modelName: "MockModel",
214
+ };
215
+ middleware = (0, permissions_1.permissionMiddleware)(model, { permissions: allPermissions });
216
+ next = (0, bun_test_1.mock)(function () { });
217
+ return [4 /*yield*/, middleware(buildReq({ method: "GET", params: { id: "507f1f77bcf86cd799439011" } }), {}, next)];
218
+ case 1:
219
+ _b.sent();
220
+ (0, bun_test_1.expect)(captureMessageSpy).toHaveBeenCalledWith("Document 507f1f77bcf86cd799439011 not found for model MockModel");
221
+ _a = __read(next.mock.calls[0], 1), error = _a[0];
222
+ (0, bun_test_1.expect)(error).toBeInstanceOf(errors_1.APIError);
223
+ (0, bun_test_1.expect)(error.status).toBe(404);
224
+ (0, bun_test_1.expect)(error.meta).toBeUndefined();
225
+ captureMessageSpy.mockRestore();
226
+ return [2 /*return*/];
227
+ }
228
+ });
229
+ }); });
230
+ (0, bun_test_1.it)("returns hidden reason metadata when document is deleted", function () { return __awaiter(void 0, void 0, void 0, function () {
231
+ var model, middleware, next, _a, error;
232
+ return __generator(this, function (_b) {
233
+ switch (_b.label) {
234
+ case 0:
235
+ model = {
236
+ collection: { findOne: (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
237
+ return [2 /*return*/, ({ deleted: true })];
238
+ }); }); }) },
239
+ findById: (0, bun_test_1.mock)(function () { return ({ exec: (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
240
+ return [2 /*return*/, null];
241
+ }); }); }) }); }),
242
+ modelName: "MockModel",
243
+ };
244
+ middleware = (0, permissions_1.permissionMiddleware)(model, { permissions: allPermissions });
245
+ next = (0, bun_test_1.mock)(function () { });
246
+ return [4 /*yield*/, middleware(buildReq({ method: "GET", params: { id: "507f1f77bcf86cd799439011" } }), {}, next)];
247
+ case 1:
248
+ _b.sent();
249
+ _a = __read(next.mock.calls[0], 1), error = _a[0];
250
+ (0, bun_test_1.expect)(error).toBeInstanceOf(errors_1.APIError);
251
+ (0, bun_test_1.expect)(error.status).toBe(404);
252
+ (0, bun_test_1.expect)(error.meta).toEqual({ deleted: "true" });
253
+ (0, bun_test_1.expect)(error.disableExternalErrorTracking).toBe(true);
254
+ return [2 /*return*/];
255
+ }
256
+ });
257
+ }); });
258
+ (0, bun_test_1.it)("returns hidden reason metadata when document is disabled", function () { return __awaiter(void 0, void 0, void 0, function () {
259
+ var model, middleware, next, _a, error;
260
+ return __generator(this, function (_b) {
261
+ switch (_b.label) {
262
+ case 0:
263
+ model = {
264
+ collection: { findOne: (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
265
+ return [2 /*return*/, ({ disabled: true })];
266
+ }); }); }) },
267
+ findById: (0, bun_test_1.mock)(function () { return ({ exec: (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
268
+ return [2 /*return*/, null];
269
+ }); }); }) }); }),
270
+ modelName: "MockModel",
271
+ };
272
+ middleware = (0, permissions_1.permissionMiddleware)(model, { permissions: allPermissions });
273
+ next = (0, bun_test_1.mock)(function () { });
274
+ return [4 /*yield*/, middleware(buildReq({ method: "GET", params: { id: "507f1f77bcf86cd799439011" } }), {}, next)];
275
+ case 1:
276
+ _b.sent();
277
+ _a = __read(next.mock.calls[0], 1), error = _a[0];
278
+ (0, bun_test_1.expect)(error).toBeInstanceOf(errors_1.APIError);
279
+ (0, bun_test_1.expect)(error.status).toBe(404);
280
+ (0, bun_test_1.expect)(error.meta).toEqual({ disabled: "true" });
281
+ (0, bun_test_1.expect)(error.disableExternalErrorTracking).toBe(true);
282
+ return [2 /*return*/];
283
+ }
284
+ });
285
+ }); });
286
+ (0, bun_test_1.it)("returns hidden reason metadata when document is archived", function () { return __awaiter(void 0, void 0, void 0, function () {
287
+ var model, middleware, next, _a, error;
288
+ return __generator(this, function (_b) {
289
+ switch (_b.label) {
290
+ case 0:
291
+ model = {
292
+ collection: { findOne: (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
293
+ return [2 /*return*/, ({ archived: true })];
294
+ }); }); }) },
295
+ findById: (0, bun_test_1.mock)(function () { return ({ exec: (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
296
+ return [2 /*return*/, null];
297
+ }); }); }) }); }),
298
+ modelName: "MockModel",
299
+ };
300
+ middleware = (0, permissions_1.permissionMiddleware)(model, { permissions: allPermissions });
301
+ next = (0, bun_test_1.mock)(function () { });
302
+ return [4 /*yield*/, middleware(buildReq({ method: "GET", params: { id: "507f1f77bcf86cd799439011" } }), {}, next)];
303
+ case 1:
304
+ _b.sent();
305
+ _a = __read(next.mock.calls[0], 1), error = _a[0];
306
+ (0, bun_test_1.expect)(error).toBeInstanceOf(errors_1.APIError);
307
+ (0, bun_test_1.expect)(error.status).toBe(404);
308
+ (0, bun_test_1.expect)(error.meta).toEqual({ archived: "true" });
309
+ (0, bun_test_1.expect)(error.disableExternalErrorTracking).toBe(true);
310
+ return [2 /*return*/];
311
+ }
312
+ });
313
+ }); });
314
+ (0, bun_test_1.it)("returns plain not found when hidden document has no reason", function () { return __awaiter(void 0, void 0, void 0, function () {
315
+ var model, middleware, next, _a, error;
316
+ return __generator(this, function (_b) {
317
+ switch (_b.label) {
318
+ case 0:
319
+ model = {
320
+ collection: { findOne: (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
321
+ return [2 /*return*/, ({ foo: "bar" })];
322
+ }); }); }) },
323
+ findById: (0, bun_test_1.mock)(function () { return ({ exec: (0, bun_test_1.mock)(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
324
+ return [2 /*return*/, null];
325
+ }); }); }) }); }),
326
+ modelName: "MockModel",
327
+ };
328
+ middleware = (0, permissions_1.permissionMiddleware)(model, { permissions: allPermissions });
329
+ next = (0, bun_test_1.mock)(function () { });
330
+ return [4 /*yield*/, middleware(buildReq({ method: "GET", params: { id: "507f1f77bcf86cd799439011" } }), {}, next)];
331
+ case 1:
332
+ _b.sent();
333
+ _a = __read(next.mock.calls[0], 1), error = _a[0];
334
+ (0, bun_test_1.expect)(error).toBeInstanceOf(errors_1.APIError);
335
+ (0, bun_test_1.expect)(error.status).toBe(404);
336
+ (0, bun_test_1.expect)(error.meta).toBeUndefined();
337
+ return [2 /*return*/];
338
+ }
339
+ });
340
+ }); });
341
+ });
@@ -7,9 +7,9 @@ export type PopulatePath = {
7
7
  export declare const fixMixedFields: (schema: any, properties: Record<string, any>) => void;
8
8
  export declare function getOpenApiSpecForModel(model: any, { populatePaths, extraModelProperties, }?: {
9
9
  populatePaths?: PopulatePath[];
10
- extraModelProperties?: any;
10
+ extraModelProperties?: Record<string, unknown>;
11
11
  }): {
12
- properties: any;
12
+ properties: Record<string, unknown>;
13
13
  required: string[];
14
14
  };
15
15
  export declare function unpopulate<T>(doc: Document<T>, path: string): Document<T>;
@@ -176,7 +176,7 @@ var syncConsents = function (definitions_1) {
176
176
  args_1[_i - 1] = arguments[_i];
177
177
  }
178
178
  return __awaiter(void 0, __spreadArray([definitions_1], __read(args_1), false), void 0, function (definitions, options) {
179
- var _a, deactivateRemoved, _b, dryRun, result, slugs, activeForms, activeBySlug, slugs_1, slugs_1_1, slug, def, existing, newVersion, e_1_1, activeBySlug_1, activeBySlug_1_1, _c, slug, form, e_2_1, summary;
179
+ var _a, deactivateRemoved, _b, dryRun, result, slugs, activeForms, activeBySlug, slugs_1, slugs_1_1, slug, def, existing, newVersion, e_1_1, activeBySlug_1, activeBySlug_1_1, _c, slug, e_2_1, summary;
180
180
  var e_1, _d, e_2, _e;
181
181
  var _f, _g, _h, _j;
182
182
  if (options === void 0) { options = {}; }
@@ -291,7 +291,7 @@ var syncConsents = function (definitions_1) {
291
291
  _k.label = 16;
292
292
  case 16:
293
293
  if (!!activeBySlug_1_1.done) return [3 /*break*/, 20];
294
- _c = __read(activeBySlug_1_1.value, 2), slug = _c[0], form = _c[1];
294
+ _c = __read(activeBySlug_1_1.value, 1), slug = _c[0];
295
295
  if (!!definitions[slug]) return [3 /*break*/, 19];
296
296
  logger_1.logger.info("syncConsents: deactivating \"".concat(slug, "\""), { dryRun: dryRun });
297
297
  if (!!dryRun) return [3 /*break*/, 18];
@@ -70,30 +70,35 @@ var mongoose_1 = __importDefault(require("mongoose"));
70
70
  var winston_1 = __importDefault(require("winston"));
71
71
  var expressServer_1 = require("../expressServer");
72
72
  var logger_1 = require("../logger");
73
+ var shouldConnectToTestDb = process.env.BUN_TEST_DISABLE_DB !== "true";
73
74
  // Connect to MongoDB once for all tests
74
- (0, bun_test_1.beforeAll)(function () { return __awaiter(void 0, void 0, void 0, function () {
75
- return __generator(this, function (_a) {
76
- switch (_a.label) {
77
- case 0: return [4 /*yield*/, mongoose_1.default
78
- .connect("mongodb://127.0.0.1/terreno?&connectTimeoutMS=360000")
79
- .catch(logger_1.logger.catch)];
80
- case 1:
81
- _a.sent();
82
- return [2 /*return*/];
83
- }
84
- });
85
- }); });
75
+ if (shouldConnectToTestDb) {
76
+ (0, bun_test_1.beforeAll)(function () { return __awaiter(void 0, void 0, void 0, function () {
77
+ return __generator(this, function (_a) {
78
+ switch (_a.label) {
79
+ case 0: return [4 /*yield*/, mongoose_1.default
80
+ .connect("mongodb://127.0.0.1/terreno?&connectTimeoutMS=360000")
81
+ .catch(logger_1.logger.catch)];
82
+ case 1:
83
+ _a.sent();
84
+ return [2 /*return*/];
85
+ }
86
+ });
87
+ }); });
88
+ }
86
89
  // Close MongoDB connection after all tests
87
- (0, bun_test_1.afterAll)(function () { return __awaiter(void 0, void 0, void 0, function () {
88
- return __generator(this, function (_a) {
89
- switch (_a.label) {
90
- case 0: return [4 /*yield*/, mongoose_1.default.connection.close()];
91
- case 1:
92
- _a.sent();
93
- return [2 /*return*/];
94
- }
95
- });
96
- }); });
90
+ if (shouldConnectToTestDb) {
91
+ (0, bun_test_1.afterAll)(function () { return __awaiter(void 0, void 0, void 0, function () {
92
+ return __generator(this, function (_a) {
93
+ switch (_a.label) {
94
+ case 0: return [4 /*yield*/, mongoose_1.default.connection.close()];
95
+ case 1:
96
+ _a.sent();
97
+ return [2 /*return*/];
98
+ }
99
+ });
100
+ }); });
101
+ }
97
102
  var logs = [];
98
103
  var SHOW_ALL_LOGS = process.env.SHOW_ALL_TEST_LOGS === "true";
99
104
  // Create a custom stream that captures logs
package/package.json CHANGED
@@ -77,6 +77,9 @@
77
77
  "license": "Apache-2.0",
78
78
  "main": "dist/index.js",
79
79
  "name": "@terreno/api",
80
+ "publishConfig": {
81
+ "access": "public"
82
+ },
80
83
  "peerDependencies": {
81
84
  "mongoose": "^8.0.0"
82
85
  },
@@ -100,5 +103,5 @@
100
103
  "updateSnapshot": "bun test --update-snapshots"
101
104
  },
102
105
  "types": "dist/index.d.ts",
103
- "version": "0.9.2"
106
+ "version": "0.10.0"
104
107
  }
package/src/api.ts CHANGED
@@ -72,6 +72,30 @@ const COMPLEX_QUERY_PARAMS = ["$and", "$or"];
72
72
  */
73
73
  export type RESTMethod = "list" | "create" | "read" | "update" | "delete";
74
74
 
75
+ /**
76
+ * Interface for the vendored @wesleytodd/openapi Express middleware.
77
+ * Provides methods for building OpenAPI documentation from Express routes.
78
+ */
79
+ export interface OpenApiMiddleware {
80
+ /** The middleware itself is callable as Express middleware. */
81
+ (req: express.Request, res: express.Response, next: express.NextFunction): void;
82
+ /** Register a path-level OpenAPI schema, returning an Express middleware that attaches the schema to the route. */
83
+ path: (schema?: Record<string, unknown>) => express.RequestHandler;
84
+ /** Register or retrieve an OpenAPI component definition (schemas, responses, parameters, etc). */
85
+ component: (
86
+ type: string,
87
+ name?: string,
88
+ description?: Record<string, unknown>
89
+ ) => OpenApiMiddleware | {$ref: string} | Record<string, unknown> | undefined;
90
+ /** Shorthand for component("schemas", ...) */
91
+ schema: (
92
+ name?: string,
93
+ description?: Record<string, unknown>
94
+ ) => OpenApiMiddleware | {$ref: string} | Record<string, unknown> | undefined;
95
+ /** The generated OpenAPI document */
96
+ document: Record<string, unknown>;
97
+ }
98
+
75
99
  /**
76
100
  * This is the main configuration.
77
101
  * @param T - the base document type. This should not include Mongoose models, just the types of the object.
@@ -110,8 +134,8 @@ export interface ModelRouterOptions<T> {
110
134
  */
111
135
  queryFilter?: (
112
136
  user?: User,
113
- query?: Record<string, any>
114
- ) => Record<string, any> | null | Promise<Record<string, any> | null>;
137
+ query?: Record<string, unknown>
138
+ ) => Record<string, unknown> | null | Promise<Record<string, unknown> | null>;
115
139
  /**
116
140
  * Transformers allow data to be transformed before actions are executed,
117
141
  * and serialized before being returned to the user.
@@ -137,7 +161,7 @@ export interface ModelRouterOptions<T> {
137
161
  * list queries. Accepts any Mongoose-style queries, and runs for all user types.
138
162
  * defaultQueryParams: \{hidden: false\} // By default, don't show objects with hidden=true
139
163
  * These can be overridden by the user if not disallowed by queryFilter. */
140
- defaultQueryParams?: {[key: string]: any};
164
+ defaultQueryParams?: Record<string, unknown>;
141
165
  /**
142
166
  * Manages Mongoose populations before returning from all methods (list, read, create, etc).
143
167
  * For each population:
@@ -161,14 +185,17 @@ export interface ModelRouterOptions<T> {
161
185
  * or 500. */
162
186
  maxLimit?: number; // defaults to 500
163
187
  /** Custom route setup function. Receives the router and optionally the full options (including openApi). */
164
- endpoints?: (router: any, options?: Partial<ModelRouterOptions<T>>) => void;
188
+ endpoints?: (router: express.Router, options?: Partial<ModelRouterOptions<T>>) => void;
165
189
  /**
166
190
  * Hook that runs after `transformer.transform` but before the object is created.
167
191
  * Can update the body fields based on the request or the user.
168
192
  * Return null to return a generic 403 error. Throw an APIError to return a 400 with specific
169
193
  * error information.
170
194
  */
171
- preCreate?: (value: any, request: express.Request) => T | Promise<T> | null;
195
+ preCreate?: (
196
+ value: Partial<T> | (Partial<T> | undefined)[] | null | undefined,
197
+ request: express.Request
198
+ ) => T | Promise<T> | null;
172
199
  /**
173
200
  * Hook that runs after `transformer.transform` but before changes are made for update operations.
174
201
  * Can update the body fields based on the request or the user.
@@ -250,17 +277,17 @@ export interface ModelRouterOptions<T> {
250
277
  /**
251
278
  * The OpenAPI generator for this server. This is used to generate the OpenAPI documentation.
252
279
  */
253
- openApi?: any;
280
+ openApi?: OpenApiMiddleware;
254
281
  /**
255
282
  * Overwrite parts of the configuration for the OpenAPI generator.
256
283
  * This will be merged with the generated configuration.
257
284
  */
258
285
  openApiOverwrite?: {
259
- get?: any;
260
- list?: any;
261
- create?: any;
262
- update?: any;
263
- delete?: any;
286
+ get?: Record<string, unknown>;
287
+ list?: Record<string, unknown>;
288
+ create?: Record<string, unknown>;
289
+ update?: Record<string, unknown>;
290
+ delete?: Record<string, unknown>;
264
291
  };
265
292
  /**
266
293
  * Overwrite parts of the model properties for the OpenAPI generator.
@@ -268,7 +295,7 @@ export interface ModelRouterOptions<T> {
268
295
  * This is useful if you add custom properties to the model during serialize, for example,
269
296
  * that you want to be documented and typed in the SDK.
270
297
  */
271
- openApiExtraModelProperties?: any;
298
+ openApiExtraModelProperties?: Record<string, unknown>;
272
299
  /**
273
300
  * Enable runtime validation of request bodies against the OpenAPI schema.
274
301
  * When enabled, requests that don't match the documented schema will return 400 errors.
package/src/errors.ts CHANGED
@@ -120,11 +120,14 @@ export class APIError extends Error {
120
120
  this.meta.fields = fields;
121
121
  }
122
122
  this.error = error;
123
- logger.error(
124
- `APIError(${status}): ${title} ${detail ? detail : ""}${
125
- data.error?.stack ? `\n${data.error?.stack}` : ""
126
- }`
127
- );
123
+ const logMessage = `APIError(${status}): ${title} ${detail ? detail : ""}${
124
+ data.error?.stack ? `\n${data.error?.stack}` : ""
125
+ }`;
126
+ if (data.disableExternalErrorTracking) {
127
+ logger.warn(logMessage);
128
+ } else {
129
+ logger.error(logMessage);
130
+ }
128
131
  }
129
132
  }
130
133
 
@@ -310,21 +310,14 @@ describe("expressServer", () => {
310
310
  );
311
311
  });
312
312
 
313
- // Note: The "hourly" and "minutely" aliases have a bug - they convert the
314
- // schedule to a cron expression but then use the original schedule string.
315
- // This test documents that current (buggy) behavior.
316
- it("hourly alias fails due to bug in implementation", () => {
313
+ it("accepts hourly alias", () => {
317
314
  const callback = () => {};
318
- expect(() => cronjob("test-hourly-alias", "hourly", callback)).toThrow(
319
- "Failed to create cronjob"
320
- );
315
+ expect(() => cronjob("test-hourly-alias", "hourly", callback)).not.toThrow();
321
316
  });
322
317
 
323
- it("minutely alias fails due to bug in implementation", () => {
318
+ it("accepts minutely alias", () => {
324
319
  const callback = () => {};
325
- expect(() => cronjob("test-minutely-alias", "minutely", callback)).toThrow(
326
- "Failed to create cronjob"
327
- );
320
+ expect(() => cronjob("test-minutely-alias", "minutely", callback)).not.toThrow();
328
321
  });
329
322
  });
330
323
 
@@ -42,7 +42,7 @@ export function setupEnvironment(): void {
42
42
  }
43
43
  }
44
44
 
45
- export type AddRoutes = (router: Router, options?: Partial<ModelRouterOptions<any>>) => void;
45
+ export type AddRoutes = (router: Router, options?: Partial<ModelRouterOptions<unknown>>) => void;
46
46
 
47
47
  const logRequestsFinished = (req: any, res: any, startTime: bigint) => {
48
48
  const options = (res.locals.loggingOptions ?? {}) as LoggingOptions;
@@ -340,15 +340,11 @@ export function cronjob(
340
340
  schedule: "hourly" | "minutely" | string,
341
341
  callback: () => void
342
342
  ) {
343
- let _cronSchedule = schedule;
344
- if (schedule === "hourly") {
345
- _cronSchedule = "0 * * * *";
346
- } else if (schedule === "minutely") {
347
- _cronSchedule = "* * * * *";
348
- }
349
- logger.info(`Adding cronjob ${name}, running at: ${schedule}`);
343
+ const cronSchedule =
344
+ schedule === "hourly" ? "0 * * * *" : schedule === "minutely" ? "* * * * *" : schedule;
345
+ logger.info(`Adding cronjob ${name}, running at: ${cronSchedule}`);
350
346
  try {
351
- new cron.CronJob(schedule, callback, null, true, "America/Chicago");
347
+ new cron.CronJob(cronSchedule, callback, null, true, "America/Chicago");
352
348
  } catch (error) {
353
349
  throw new Error(`Failed to create cronjob: ${error}`);
354
350
  }
@@ -11,6 +11,10 @@ import {Permissions} from "./permissions";
11
11
  import {FoodModel, setupDb, UserModel} from "./tests";
12
12
 
13
13
  function getMessageSummaryOpenApiMiddleware(options: Partial<ModelRouterOptions<any>>): any {
14
+ if (!options.openApi) {
15
+ throw new Error("Expected openApi to be configured for test routes");
16
+ }
17
+
14
18
  return options.openApi.path({
15
19
  parameters: [
16
20
  {
@@ -177,7 +181,7 @@ describe("openApi", () => {
177
181
  });
178
182
 
179
183
  function addRoutesPopulate(router: Router, options?: Partial<ModelRouterOptions<any>>): void {
180
- options?.openApi.component("schemas", "LimitedUser", {
184
+ options?.openApi?.component("schemas", "LimitedUser", {
181
185
  properties: {
182
186
  email: {
183
187
  description: "LimitedUser's email",