@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.
- package/bunfig.unit.toml +3 -0
- package/dist/api.d.ts +31 -13
- package/dist/errors.js +7 -1
- package/dist/expressServer.d.ts +1 -1
- package/dist/expressServer.js +3 -9
- package/dist/expressServer.test.js +4 -7
- package/dist/openApi.d.ts +6 -5
- package/dist/openApi.test.js +5 -1
- package/dist/openApiBuilder.d.ts +2 -2
- package/dist/openApiEtag.test.d.ts +1 -0
- package/dist/openApiEtag.test.js +86 -0
- package/dist/permissions.middleware.test.d.ts +1 -0
- package/dist/permissions.middleware.test.js +341 -0
- package/dist/populate.d.ts +2 -2
- package/dist/syncConsents.js +2 -2
- package/dist/tests/bunSetup.js +27 -22
- package/package.json +4 -1
- package/src/api.ts +39 -12
- package/src/errors.ts +8 -5
- package/src/expressServer.test.ts +4 -11
- package/src/expressServer.ts +5 -9
- package/src/openApi.test.ts +5 -1
- package/src/openApi.ts +14 -7
- package/src/openApiBuilder.ts +3 -3
- package/src/openApiEtag.test.ts +112 -0
- package/src/permissions.middleware.test.ts +197 -0
- package/src/populate.ts +2 -2
- package/src/syncConsents.ts +1 -1
- package/src/tests/bunSetup.ts +14 -8
|
@@ -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
|
+
});
|
package/dist/populate.d.ts
CHANGED
|
@@ -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?:
|
|
10
|
+
extraModelProperties?: Record<string, unknown>;
|
|
11
11
|
}): {
|
|
12
|
-
properties:
|
|
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>;
|
package/dist/syncConsents.js
CHANGED
|
@@ -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,
|
|
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,
|
|
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];
|
package/dist/tests/bunSetup.js
CHANGED
|
@@ -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
|
-
|
|
75
|
-
return
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
88
|
-
return
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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.
|
|
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,
|
|
114
|
-
) => Record<string,
|
|
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?:
|
|
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:
|
|
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?: (
|
|
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?:
|
|
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?:
|
|
260
|
-
list?:
|
|
261
|
-
create?:
|
|
262
|
-
update?:
|
|
263
|
-
delete?:
|
|
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?:
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
package/src/expressServer.ts
CHANGED
|
@@ -42,7 +42,7 @@ export function setupEnvironment(): void {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
export type AddRoutes = (router: Router, options?: Partial<ModelRouterOptions<
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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(
|
|
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
|
}
|
package/src/openApi.test.ts
CHANGED
|
@@ -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
|
|
184
|
+
options?.openApi?.component("schemas", "LimitedUser", {
|
|
181
185
|
properties: {
|
|
182
186
|
email: {
|
|
183
187
|
description: "LimitedUser's email",
|