@terreno/api 0.20.2 → 0.22.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.
Files changed (107) hide show
  1. package/.ai/guidelines/core.md +71 -0
  2. package/.ai/skills/mongoose-schema-safety/SKILL.md +143 -0
  3. package/README.md +54 -1
  4. package/bunfig.toml +1 -1
  5. package/dist/__tests__/versionCheckPlugin.test.js +29 -7
  6. package/dist/actions.openApi.test.js +13 -11
  7. package/dist/api.js +98 -11
  8. package/dist/api.query.test.js +31 -1
  9. package/dist/api.test.js +211 -0
  10. package/dist/auth.test.js +418 -43
  11. package/dist/betterAuth.d.ts +1 -1
  12. package/dist/consentApp.test.js +1 -0
  13. package/dist/example.js +4 -4
  14. package/dist/expressServer.d.ts +0 -22
  15. package/dist/expressServer.js +1 -125
  16. package/dist/expressServer.test.js +90 -91
  17. package/dist/githubAuth.test.js +22 -22
  18. package/dist/logger.d.ts +154 -0
  19. package/dist/logger.js +445 -26
  20. package/dist/logger.test.js +435 -0
  21. package/dist/middleware.d.ts +7 -0
  22. package/dist/middleware.js +58 -1
  23. package/dist/middleware.test.js +159 -0
  24. package/dist/models/consentForm.js +2 -1
  25. package/dist/models/consentResponse.js +2 -1
  26. package/dist/models/versionConfig.js +2 -1
  27. package/dist/openApi.test.js +10 -17
  28. package/dist/openApiBuilder.d.ts +18 -0
  29. package/dist/openApiBuilder.js +21 -0
  30. package/dist/openApiBuilder.test.js +34 -10
  31. package/dist/permissions.test.js +10 -43
  32. package/dist/populate.test.js +10 -42
  33. package/dist/realtime/changeStreamWatcher.d.ts +4 -4
  34. package/dist/realtime/changeStreamWatcher.js +2 -4
  35. package/dist/realtime/queryMatcher.d.ts +1 -1
  36. package/dist/realtime/queryMatcher.js +39 -14
  37. package/dist/realtime/types.d.ts +3 -3
  38. package/dist/requestContext.d.ts +61 -0
  39. package/dist/requestContext.js +74 -0
  40. package/dist/secretProviders.test.js +335 -0
  41. package/dist/syncConsents.test.js +2 -2
  42. package/dist/terrenoApp.d.ts +27 -15
  43. package/dist/terrenoApp.js +24 -14
  44. package/dist/terrenoApp.test.js +52 -0
  45. package/dist/tests/bunSetup.js +66 -262
  46. package/dist/tests/createTestData.d.ts +9 -0
  47. package/dist/tests/createTestData.js +272 -0
  48. package/dist/tests/models.d.ts +71 -0
  49. package/dist/tests/models.js +134 -0
  50. package/dist/tests/mongoTestSetup.d.ts +7 -0
  51. package/dist/tests/mongoTestSetup.js +150 -0
  52. package/dist/tests/testEnv.d.ts +0 -0
  53. package/dist/tests/testEnv.js +6 -0
  54. package/dist/tests/testHelper.d.ts +22 -0
  55. package/dist/tests/testHelper.js +115 -0
  56. package/dist/tests/types.d.ts +29 -0
  57. package/dist/tests/types.js +2 -0
  58. package/dist/tests.d.ts +10 -78
  59. package/dist/tests.js +24 -241
  60. package/dist/transformers.test.js +14 -50
  61. package/package.json +18 -4
  62. package/src/__snapshots__/openApiBuilder.test.ts.snap +1 -0
  63. package/src/__tests__/versionCheckPlugin.test.ts +43 -15
  64. package/src/actions.openApi.test.ts +12 -10
  65. package/src/api.query.test.ts +24 -1
  66. package/src/api.test.ts +169 -0
  67. package/src/api.ts +71 -0
  68. package/src/auth.test.ts +287 -39
  69. package/src/betterAuth.ts +1 -1
  70. package/src/consentApp.test.ts +1 -0
  71. package/src/example.ts +4 -4
  72. package/src/expressServer.test.ts +82 -85
  73. package/src/expressServer.ts +1 -213
  74. package/src/githubAuth.test.ts +22 -22
  75. package/src/logger.test.ts +466 -1
  76. package/src/logger.ts +477 -14
  77. package/src/middleware.test.ts +74 -2
  78. package/src/middleware.ts +57 -0
  79. package/src/models/consentForm.ts +3 -4
  80. package/src/models/consentResponse.ts +6 -4
  81. package/src/models/versionConfig.ts +3 -4
  82. package/src/openApi.test.ts +10 -17
  83. package/src/openApiBuilder.test.ts +27 -10
  84. package/src/openApiBuilder.ts +24 -0
  85. package/src/permissions.test.ts +8 -23
  86. package/src/populate.test.ts +7 -22
  87. package/src/realtime/changeStreamWatcher.ts +15 -10
  88. package/src/realtime/queryMatcher.ts +54 -27
  89. package/src/realtime/types.ts +4 -4
  90. package/src/requestContext.ts +86 -0
  91. package/src/secretProviders.test.ts +219 -1
  92. package/src/syncConsents.test.ts +1 -1
  93. package/src/terrenoApp.test.ts +38 -0
  94. package/src/terrenoApp.ts +37 -15
  95. package/src/tests/bunSetup.ts +22 -236
  96. package/src/tests/createTestData.ts +176 -0
  97. package/src/tests/models.ts +164 -0
  98. package/src/tests/mongoTestSetup.ts +69 -0
  99. package/src/tests/testEnv.ts +4 -0
  100. package/src/tests/testHelper.ts +57 -0
  101. package/src/tests/types.ts +35 -0
  102. package/src/tests.ts +40 -231
  103. package/src/transformers.test.ts +11 -30
  104. package/tsconfig.typedoc.json +4 -0
  105. package/dist/tests/index.d.ts +0 -1
  106. package/dist/tests/index.js +0 -17
  107. package/src/tests/index.ts +0 -1
@@ -32,10 +32,52 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __generator = (this && this.__generator) || function (thisArg, body) {
45
+ 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);
46
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
47
+ function verb(n) { return function (v) { return step([n, v]); }; }
48
+ function step(op) {
49
+ if (f) throw new TypeError("Generator is already executing.");
50
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
51
+ 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;
52
+ if (y = 0, t) op = [op[0] & 2, t.value];
53
+ switch (op[0]) {
54
+ case 0: case 1: t = op; break;
55
+ case 4: _.label++; return { value: op[1], done: false };
56
+ case 5: _.label++; y = op[1]; op = [0]; continue;
57
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
58
+ default:
59
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
60
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
61
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
62
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
63
+ if (t[2]) _.ops.pop();
64
+ _.trys.pop(); continue;
65
+ }
66
+ op = body.call(thisArg, _);
67
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
68
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
69
+ }
70
+ };
71
+ var __importDefault = (this && this.__importDefault) || function (mod) {
72
+ return (mod && mod.__esModule) ? mod : { "default": mod };
73
+ };
35
74
  Object.defineProperty(exports, "__esModule", { value: true });
36
75
  var bun_test_1 = require("bun:test");
37
76
  var Sentry = __importStar(require("@sentry/bun"));
77
+ var express_1 = __importDefault(require("express"));
78
+ var supertest_1 = __importDefault(require("supertest"));
38
79
  var middleware_1 = require("./middleware");
80
+ var requestContext_1 = require("./requestContext");
39
81
  var buildReq = function (headers) {
40
82
  return {
41
83
  get: function (name) { return headers[name]; },
@@ -80,3 +122,120 @@ var buildNext = function () { return (0, bun_test_1.mock)(function () { }); };
80
122
  (0, bun_test_1.expect)(next.mock.calls[0]).toHaveLength(0);
81
123
  });
82
124
  });
125
+ (0, bun_test_1.describe)("jsonResponseRequestIdMiddleware", function () {
126
+ var buildStackedApp = function () {
127
+ var app = (0, express_1.default)();
128
+ app.use(requestContext_1.requestContextMiddleware);
129
+ app.use(middleware_1.jsonResponseRequestIdMiddleware);
130
+ app.get("/object", function (_req, res) {
131
+ return res.json({ hello: "world" });
132
+ });
133
+ app.get("/array", function (_req, res) {
134
+ return res.json([1, 2]);
135
+ });
136
+ app.get("/openapi.json", function (_req, res) {
137
+ return res.json({ openapi: "3.0.0", paths: {} });
138
+ });
139
+ app.get("/openapi/components/schemas/Food.json", function (_req, res) {
140
+ return res.json({ description: "A food", type: "object" });
141
+ });
142
+ app.get("/openapi/validate", function (_req, res) {
143
+ return res.json({ document: { openapi: "3.0.0" }, valid: true });
144
+ });
145
+ return app;
146
+ };
147
+ (0, bun_test_1.it)("adds requestId to object JSON bodies and matches X-Request-ID header", function () { return __awaiter(void 0, void 0, void 0, function () {
148
+ var app, res;
149
+ return __generator(this, function (_a) {
150
+ switch (_a.label) {
151
+ case 0:
152
+ app = buildStackedApp();
153
+ return [4 /*yield*/, (0, supertest_1.default)(app).get("/object").expect(200)];
154
+ case 1:
155
+ res = _a.sent();
156
+ (0, bun_test_1.expect)(res.body.hello).toBe("world");
157
+ (0, bun_test_1.expect)(res.body.requestId).toBeDefined();
158
+ (0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
159
+ return [2 /*return*/];
160
+ }
161
+ });
162
+ }); });
163
+ (0, bun_test_1.it)("does not wrap JSON array bodies", function () { return __awaiter(void 0, void 0, void 0, function () {
164
+ var app, res;
165
+ return __generator(this, function (_a) {
166
+ switch (_a.label) {
167
+ case 0:
168
+ app = buildStackedApp();
169
+ return [4 /*yield*/, (0, supertest_1.default)(app).get("/array").expect(200)];
170
+ case 1:
171
+ res = _a.sent();
172
+ (0, bun_test_1.expect)(res.body).toEqual([1, 2]);
173
+ (0, bun_test_1.expect)(res.headers["x-request-id"]).toBeDefined();
174
+ return [2 /*return*/];
175
+ }
176
+ });
177
+ }); });
178
+ (0, bun_test_1.it)("does not inject requestId into GET /openapi.json bodies", function () { return __awaiter(void 0, void 0, void 0, function () {
179
+ var app, res;
180
+ return __generator(this, function (_a) {
181
+ switch (_a.label) {
182
+ case 0:
183
+ app = buildStackedApp();
184
+ return [4 /*yield*/, (0, supertest_1.default)(app).get("/openapi.json").expect(200)];
185
+ case 1:
186
+ res = _a.sent();
187
+ (0, bun_test_1.expect)(res.body).toEqual({ openapi: "3.0.0", paths: {} });
188
+ (0, bun_test_1.expect)(res.body.requestId).toBeUndefined();
189
+ return [2 /*return*/];
190
+ }
191
+ });
192
+ }); });
193
+ (0, bun_test_1.it)("does not inject requestId into GET /openapi/components/...json bodies", function () { return __awaiter(void 0, void 0, void 0, function () {
194
+ var app, res;
195
+ return __generator(this, function (_a) {
196
+ switch (_a.label) {
197
+ case 0:
198
+ app = buildStackedApp();
199
+ return [4 /*yield*/, (0, supertest_1.default)(app).get("/openapi/components/schemas/Food.json").expect(200)];
200
+ case 1:
201
+ res = _a.sent();
202
+ (0, bun_test_1.expect)(res.body).toEqual({ description: "A food", type: "object" });
203
+ (0, bun_test_1.expect)(res.body.requestId).toBeUndefined();
204
+ return [2 /*return*/];
205
+ }
206
+ });
207
+ }); });
208
+ (0, bun_test_1.it)("does not inject requestId into GET /openapi/validate bodies", function () { return __awaiter(void 0, void 0, void 0, function () {
209
+ var app, res;
210
+ return __generator(this, function (_a) {
211
+ switch (_a.label) {
212
+ case 0:
213
+ app = buildStackedApp();
214
+ return [4 /*yield*/, (0, supertest_1.default)(app).get("/openapi/validate").expect(200)];
215
+ case 1:
216
+ res = _a.sent();
217
+ (0, bun_test_1.expect)(res.body).toEqual({ document: { openapi: "3.0.0" }, valid: true });
218
+ (0, bun_test_1.expect)(res.body.requestId).toBeUndefined();
219
+ return [2 /*return*/];
220
+ }
221
+ });
222
+ }); });
223
+ (0, bun_test_1.it)("uses incoming X-Request-ID on wrapped object responses", function () { return __awaiter(void 0, void 0, void 0, function () {
224
+ var app, res;
225
+ return __generator(this, function (_a) {
226
+ switch (_a.label) {
227
+ case 0:
228
+ app = buildStackedApp();
229
+ return [4 /*yield*/, (0, supertest_1.default)(app)
230
+ .get("/object")
231
+ .set("X-Request-ID", "client-rid-99")
232
+ .expect(200)];
233
+ case 1:
234
+ res = _a.sent();
235
+ (0, bun_test_1.expect)(res.body.requestId).toBe("client-rid-99");
236
+ (0, bun_test_1.expect)(res.headers["x-request-id"]).toBe("client-rid-99");
237
+ return [2 /*return*/];
238
+ }
239
+ });
240
+ }); });
241
+ });
@@ -2,6 +2,7 @@
2
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
+ var _a;
5
6
  Object.defineProperty(exports, "__esModule", { value: true });
6
7
  exports.ConsentForm = void 0;
7
8
  var mongoose_1 = __importDefault(require("mongoose"));
@@ -120,4 +121,4 @@ consentFormSchema.plugin(plugins_1.createdUpdatedPlugin);
120
121
  consentFormSchema.plugin(plugins_1.isDeletedPlugin);
121
122
  consentFormSchema.plugin(plugins_1.findOneOrNone);
122
123
  consentFormSchema.plugin(plugins_1.findExactlyOne);
123
- exports.ConsentForm = mongoose_1.default.model("ConsentForm", consentFormSchema);
124
+ exports.ConsentForm = (_a = mongoose_1.default.models.ConsentForm) !== null && _a !== void 0 ? _a : mongoose_1.default.model("ConsentForm", consentFormSchema);
@@ -2,6 +2,7 @@
2
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
+ var _a;
5
6
  Object.defineProperty(exports, "__esModule", { value: true });
6
7
  exports.ConsentResponse = void 0;
7
8
  var mongoose_1 = __importDefault(require("mongoose"));
@@ -70,4 +71,4 @@ consentResponseSchema.plugin(plugins_1.createdUpdatedPlugin);
70
71
  consentResponseSchema.plugin(plugins_1.isDeletedPlugin);
71
72
  consentResponseSchema.plugin(plugins_1.findOneOrNone);
72
73
  consentResponseSchema.plugin(plugins_1.findExactlyOne);
73
- exports.ConsentResponse = mongoose_1.default.model("ConsentResponse", consentResponseSchema);
74
+ exports.ConsentResponse = (_a = mongoose_1.default.models.ConsentResponse) !== null && _a !== void 0 ? _a : mongoose_1.default.model("ConsentResponse", consentResponseSchema);
@@ -2,6 +2,7 @@
2
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
+ var _a;
5
6
  Object.defineProperty(exports, "__esModule", { value: true });
6
7
  exports.VersionConfig = void 0;
7
8
  var mongoose_1 = __importDefault(require("mongoose"));
@@ -71,4 +72,4 @@ versionConfigSchema.plugin(plugins_1.createdUpdatedPlugin);
71
72
  versionConfigSchema.plugin(plugins_1.isDeletedPlugin);
72
73
  versionConfigSchema.plugin(plugins_1.findOneOrNone);
73
74
  versionConfigSchema.plugin(plugins_1.findExactlyOne);
74
- exports.VersionConfig = mongoose_1.default.model("VersionConfig", versionConfigSchema);
75
+ exports.VersionConfig = (_a = mongoose_1.default.models.VersionConfig) !== null && _a !== void 0 ? _a : mongoose_1.default.model("VersionConfig", versionConfigSchema);
@@ -70,10 +70,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
70
70
  var bun_test_1 = require("bun:test");
71
71
  var supertest_1 = __importDefault(require("supertest"));
72
72
  var api_1 = require("./api");
73
- var auth_1 = require("./auth");
74
- var expressServer_1 = require("./expressServer");
75
73
  var openApi_1 = require("./openApi");
76
74
  var permissions_1 = require("./permissions");
75
+ var terrenoApp_1 = require("./terrenoApp");
77
76
  var tests_1 = require("./tests");
78
77
  function getMessageSummaryOpenApiMiddleware(options) {
79
78
  if (!options.openApi) {
@@ -136,13 +135,11 @@ function addRoutes(router, options) {
136
135
  return __generator(this, function (_a) {
137
136
  process.env.REFRESH_TOKEN_SECRET = "testsecret1234";
138
137
  process.env.ENABLE_SWAGGER = "true";
139
- app = (0, expressServer_1.setupServer)({
140
- addRoutes: addRoutes,
138
+ app = new terrenoApp_1.TerrenoApp({
139
+ configureApp: addRoutes,
141
140
  skipListen: true,
142
141
  userModel: tests_1.UserModel,
143
- });
144
- (0, auth_1.setupAuth)(app, tests_1.UserModel);
145
- (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
142
+ }).build();
146
143
  return [2 /*return*/];
147
144
  });
148
145
  }); });
@@ -342,13 +339,11 @@ function addRoutesPopulate(router, options) {
342
339
  return __generator(this, function (_a) {
343
340
  process.env.REFRESH_TOKEN_SECRET = "testsecret1234";
344
341
  process.env.ENABLE_SWAGGER = "false";
345
- app = (0, expressServer_1.setupServer)({
346
- addRoutes: addRoutes,
342
+ app = new terrenoApp_1.TerrenoApp({
343
+ configureApp: addRoutes,
347
344
  skipListen: true,
348
345
  userModel: tests_1.UserModel,
349
- });
350
- (0, auth_1.setupAuth)(app, tests_1.UserModel);
351
- (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
346
+ }).build();
352
347
  return [2 /*return*/];
353
348
  });
354
349
  }); });
@@ -371,13 +366,11 @@ function addRoutesPopulate(router, options) {
371
366
  (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
372
367
  return __generator(this, function (_a) {
373
368
  process.env.REFRESH_TOKEN_SECRET = "testsecret1234";
374
- app = (0, expressServer_1.setupServer)({
375
- addRoutes: addRoutesPopulate,
369
+ app = new terrenoApp_1.TerrenoApp({
370
+ configureApp: addRoutesPopulate,
376
371
  skipListen: true,
377
372
  userModel: tests_1.UserModel,
378
- });
379
- (0, auth_1.setupAuth)(app, tests_1.UserModel);
380
- (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
373
+ }).build();
381
374
  return [2 /*return*/];
382
375
  });
383
376
  }); });
@@ -272,6 +272,24 @@ export declare class OpenApiMiddlewareBuilder {
272
272
  * ```
273
273
  */
274
274
  withSummary(summary: string): this;
275
+ /**
276
+ * Sets an explicit `operationId` for the OpenAPI operation.
277
+ *
278
+ * The `operationId` is a unique string used to identify an operation. Client and SDK
279
+ * generators (e.g. RTK Query codegen) derive generated function and hook names from it,
280
+ * so setting it keeps generated names stable and readable for routes whose URL path would
281
+ * otherwise produce unwieldy names (e.g. deeply nested routes). It must be unique across
282
+ * the whole OpenAPI document.
283
+ *
284
+ * @param operationId - Unique operation identifier (e.g. "getUserStats")
285
+ * @returns The builder instance for chaining
286
+ *
287
+ * @example
288
+ * ```typescript
289
+ * builder.withOperationId("getUserStats");
290
+ * ```
291
+ */
292
+ withOperationId(operationId: string): this;
275
293
  /**
276
294
  * Sets the description for the OpenAPI operation.
277
295
  *
@@ -121,6 +121,27 @@ var OpenApiMiddlewareBuilder = /** @class */ (function () {
121
121
  this.config.summary = summary;
122
122
  return this;
123
123
  };
124
+ /**
125
+ * Sets an explicit `operationId` for the OpenAPI operation.
126
+ *
127
+ * The `operationId` is a unique string used to identify an operation. Client and SDK
128
+ * generators (e.g. RTK Query codegen) derive generated function and hook names from it,
129
+ * so setting it keeps generated names stable and readable for routes whose URL path would
130
+ * otherwise produce unwieldy names (e.g. deeply nested routes). It must be unique across
131
+ * the whole OpenAPI document.
132
+ *
133
+ * @param operationId - Unique operation identifier (e.g. "getUserStats")
134
+ * @returns The builder instance for chaining
135
+ *
136
+ * @example
137
+ * ```typescript
138
+ * builder.withOperationId("getUserStats");
139
+ * ```
140
+ */
141
+ OpenApiMiddlewareBuilder.prototype.withOperationId = function (operationId) {
142
+ this.config.operationId = operationId;
143
+ return this;
144
+ };
124
145
  /**
125
146
  * Sets the description for the OpenAPI operation.
126
147
  *
@@ -54,10 +54,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
54
54
  var bun_test_1 = require("bun:test");
55
55
  var supertest_1 = __importDefault(require("supertest"));
56
56
  var api_1 = require("./api");
57
- var auth_1 = require("./auth");
58
- var expressServer_1 = require("./expressServer");
59
57
  var openApiBuilder_1 = require("./openApiBuilder");
60
58
  var permissions_1 = require("./permissions");
59
+ var terrenoApp_1 = require("./terrenoApp");
61
60
  var tests_1 = require("./tests");
62
61
  function addRoutesWithBuilder(router, options) {
63
62
  var _this = this;
@@ -65,6 +64,7 @@ function addRoutesWithBuilder(router, options) {
65
64
  var statsMiddleware = (0, openApiBuilder_1.createOpenApiBuilder)(options !== null && options !== void 0 ? options : {})
66
65
  .withTags(["Stats"])
67
66
  .withSummary("Get food statistics")
67
+ .withOperationId("getFoodStats")
68
68
  .withDescription("Returns aggregated statistics about food items")
69
69
  .withQueryParameter("category", { type: "string" }, {
70
70
  description: "Filter by food category",
@@ -168,13 +168,11 @@ function addRoutesWithBuilder(router, options) {
168
168
  return __generator(this, function (_a) {
169
169
  process.env.REFRESH_TOKEN_SECRET = "testsecret1234";
170
170
  process.env.ENABLE_SWAGGER = "true";
171
- app = (0, expressServer_1.setupServer)({
172
- addRoutes: addRoutesWithBuilder,
171
+ app = new terrenoApp_1.TerrenoApp({
172
+ configureApp: addRoutesWithBuilder,
173
173
  skipListen: true,
174
174
  userModel: tests_1.UserModel,
175
- });
176
- (0, auth_1.setupAuth)(app, tests_1.UserModel);
177
- (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
175
+ }).build();
178
176
  return [2 /*return*/];
179
177
  });
180
178
  }); });
@@ -223,6 +221,21 @@ function addRoutesWithBuilder(router, options) {
223
221
  }
224
222
  });
225
223
  }); });
224
+ (0, bun_test_1.it)("includes the explicit operationId in OpenAPI spec", function () { return __awaiter(void 0, void 0, void 0, function () {
225
+ var res, statsPath;
226
+ return __generator(this, function (_a) {
227
+ switch (_a.label) {
228
+ case 0:
229
+ server = (0, supertest_1.default)(app);
230
+ return [4 /*yield*/, server.get("/openapi.json").expect(200)];
231
+ case 1:
232
+ res = _a.sent();
233
+ statsPath = res.body.paths["/food/stats"];
234
+ (0, bun_test_1.expect)(statsPath.get.operationId).toBe("getFoodStats");
235
+ return [2 /*return*/];
236
+ }
237
+ });
238
+ }); });
226
239
  (0, bun_test_1.it)("includes request body schema in OpenAPI spec", function () { return __awaiter(void 0, void 0, void 0, function () {
227
240
  var res, reportsPath, requestBody, schema;
228
241
  return __generator(this, function (_a) {
@@ -367,7 +380,11 @@ function addRoutesWithBuilder(router, options) {
367
380
  return [4 /*yield*/, server.get("/food/stats").expect(200)];
368
381
  case 1:
369
382
  res = _a.sent();
370
- (0, bun_test_1.expect)(res.body).toEqual({ avgCalories: 250, count: 10 });
383
+ (0, bun_test_1.expect)(res.body).toEqual({
384
+ avgCalories: 250,
385
+ count: 10,
386
+ requestId: res.headers["x-request-id"],
387
+ });
371
388
  return [2 /*return*/];
372
389
  }
373
390
  });
@@ -384,7 +401,10 @@ function addRoutesWithBuilder(router, options) {
384
401
  .expect(201)];
385
402
  case 1:
386
403
  res = _a.sent();
387
- (0, bun_test_1.expect)(res.body).toEqual({ reportId: "report-123" });
404
+ (0, bun_test_1.expect)(res.body).toEqual({
405
+ reportId: "report-123",
406
+ requestId: res.headers["x-request-id"],
407
+ });
388
408
  return [2 /*return*/];
389
409
  }
390
410
  });
@@ -414,7 +434,11 @@ function addRoutesWithBuilder(router, options) {
414
434
  return [4 /*yield*/, server.get("/food/categories/cat-123").expect(200)];
415
435
  case 1:
416
436
  res = _a.sent();
417
- (0, bun_test_1.expect)(res.body).toEqual({ id: "cat-123", name: "Fruits" });
437
+ (0, bun_test_1.expect)(res.body).toEqual({
438
+ id: "cat-123",
439
+ name: "Fruits",
440
+ requestId: res.headers["x-request-id"],
441
+ });
418
442
  return [2 /*return*/];
419
443
  }
420
444
  });
@@ -35,22 +35,6 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
35
35
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
36
  }
37
37
  };
38
- var __read = (this && this.__read) || function (o, n) {
39
- var m = typeof Symbol === "function" && o[Symbol.iterator];
40
- if (!m) return o;
41
- var i = m.call(o), r, ar = [], e;
42
- try {
43
- while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
44
- }
45
- catch (error) { e = { error: error }; }
46
- finally {
47
- try {
48
- if (r && !r.done && (m = i["return"])) m.call(i);
49
- }
50
- finally { if (e) throw e.error; }
51
- }
52
- return ar;
53
- };
54
38
  var __importDefault = (this && this.__importDefault) || function (mod) {
55
39
  return (mod && mod.__esModule) ? mod : { "default": mod };
56
40
  };
@@ -66,30 +50,13 @@ var tests_1 = require("./tests");
66
50
  var server;
67
51
  var app;
68
52
  (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
69
- var _a, admin, notAdmin;
70
- return __generator(this, function (_b) {
71
- switch (_b.label) {
53
+ return __generator(this, function (_a) {
54
+ switch (_a.label) {
72
55
  case 0:
73
56
  process.env.REFRESH_TOKEN_SECRET = "testsecret1234";
74
- return [4 /*yield*/, (0, tests_1.setupDb)()];
57
+ return [4 /*yield*/, (0, tests_1.setupTestData)()];
75
58
  case 1:
76
- _a = __read.apply(void 0, [_b.sent(), 2]), admin = _a[0], notAdmin = _a[1];
77
- return [4 /*yield*/, Promise.all([
78
- tests_1.FoodModel.create({
79
- calories: 1,
80
- created: new Date(),
81
- name: "Spinach",
82
- ownerId: notAdmin._id,
83
- }),
84
- tests_1.FoodModel.create({
85
- calories: 100,
86
- created: Date.now() - 10,
87
- name: "Apple",
88
- ownerId: admin._id,
89
- }),
90
- ])];
91
- case 2:
92
- _b.sent();
59
+ _a.sent();
93
60
  app = (0, tests_1.getBaseServer)();
94
61
  (0, auth_1.setupAuth)(app, tests_1.UserModel);
95
62
  (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
@@ -125,7 +92,7 @@ var tests_1 = require("./tests");
125
92
  case 0: return [4 /*yield*/, server.get("/food").expect(200)];
126
93
  case 1:
127
94
  res = _a.sent();
128
- (0, bun_test_1.expect)(res.body.data).toHaveLength(2);
95
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(4);
129
96
  return [2 /*return*/];
130
97
  }
131
98
  });
@@ -137,7 +104,7 @@ var tests_1 = require("./tests");
137
104
  case 0: return [4 /*yield*/, server.get("/food").expect(200)];
138
105
  case 1:
139
106
  res = _a.sent();
140
- (0, bun_test_1.expect)(res.body.data).toHaveLength(2);
107
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(4);
141
108
  return [4 /*yield*/, server.get("/food/".concat(res.body.data[0]._id)).expect(200)];
142
109
  case 2:
143
110
  res2 = _a.sent();
@@ -213,7 +180,7 @@ var tests_1 = require("./tests");
213
180
  case 0: return [4 /*yield*/, agent.get("/food").expect(200)];
214
181
  case 1:
215
182
  res = _a.sent();
216
- (0, bun_test_1.expect)(res.body.data).toHaveLength(2);
183
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(4);
217
184
  return [2 /*return*/];
218
185
  }
219
186
  });
@@ -225,7 +192,7 @@ var tests_1 = require("./tests");
225
192
  case 0: return [4 /*yield*/, agent.get("/food").expect(200)];
226
193
  case 1:
227
194
  res = _a.sent();
228
- (0, bun_test_1.expect)(res.body.data).toHaveLength(2);
195
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(4);
229
196
  return [4 /*yield*/, server.get("/food/".concat(res.body.data[0]._id)).expect(200)];
230
197
  case 2:
231
198
  res2 = _a.sent();
@@ -326,7 +293,7 @@ var tests_1 = require("./tests");
326
293
  case 0: return [4 /*yield*/, agent.get("/food")];
327
294
  case 1:
328
295
  res = _a.sent();
329
- (0, bun_test_1.expect)(res.body.data).toHaveLength(2);
296
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(4);
330
297
  return [2 /*return*/];
331
298
  }
332
299
  });
@@ -338,7 +305,7 @@ var tests_1 = require("./tests");
338
305
  case 0: return [4 /*yield*/, agent.get("/food")];
339
306
  case 1:
340
307
  res = _a.sent();
341
- (0, bun_test_1.expect)(res.body.data).toHaveLength(2);
308
+ (0, bun_test_1.expect)(res.body.data).toHaveLength(4);
342
309
  return [4 /*yield*/, agent.get("/food/".concat(res.body.data[0]._id))];
343
310
  case 2:
344
311
  res2 = _a.sent();
@@ -68,22 +68,6 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
68
68
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
69
69
  }
70
70
  };
71
- var __read = (this && this.__read) || function (o, n) {
72
- var m = typeof Symbol === "function" && o[Symbol.iterator];
73
- if (!m) return o;
74
- var i = m.call(o), r, ar = [], e;
75
- try {
76
- while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
77
- }
78
- catch (error) { e = { error: error }; }
79
- finally {
80
- try {
81
- if (r && !r.done && (m = i["return"])) m.call(i);
82
- }
83
- finally { if (e) throw e.error; }
84
- }
85
- return ar;
86
- };
87
71
  Object.defineProperty(exports, "__esModule", { value: true });
88
72
  // biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
89
73
  var bun_test_1 = require("bun:test");
@@ -96,31 +80,15 @@ var tests_1 = require("./tests");
96
80
  // noExplicitAny: typing as HydratedDocument<Food> causes cascading errors on populated field access patterns (e.g. populated.ownerId.name)
97
81
  var spinach;
98
82
  (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
99
- var _a, _b;
100
- return __generator(this, function (_c) {
101
- switch (_c.label) {
102
- case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
83
+ var testData;
84
+ return __generator(this, function (_a) {
85
+ switch (_a.label) {
86
+ case 0: return [4 /*yield*/, (0, tests_1.setupTestData)()];
103
87
  case 1:
104
- _a = __read.apply(void 0, [_c.sent(), 2]), admin = _a[0], notAdmin = _a[1];
105
- return [4 /*yield*/, Promise.all([
106
- tests_1.FoodModel.create({
107
- calories: 1,
108
- created: new Date("2021-12-03T00:00:20.000Z"),
109
- eatenBy: [admin._id],
110
- hidden: false,
111
- likesIds: [
112
- { likes: true, userId: admin._id },
113
- { likes: false, userId: notAdmin._id },
114
- ],
115
- name: "Spinach",
116
- ownerId: admin._id,
117
- source: {
118
- name: "Brand",
119
- },
120
- }),
121
- ])];
122
- case 2:
123
- _b = __read.apply(void 0, [_c.sent(), 1]), spinach = _b[0];
88
+ testData = _a.sent();
89
+ admin = testData.users.admin;
90
+ notAdmin = testData.users.notAdmin;
91
+ spinach = testData.foods.spinach;
124
92
  return [2 /*return*/];
125
93
  }
126
94
  });
@@ -139,7 +107,7 @@ var tests_1 = require("./tests");
139
107
  return [4 /*yield*/, populated.populate("likesIds.userId")];
140
108
  case 3:
141
109
  populated = _c.sent();
142
- (0, bun_test_1.expect)(populated.ownerId.name).toBe("Admin");
110
+ (0, bun_test_1.expect)(populated.ownerId.name).toBe("Not Admin");
143
111
  (0, bun_test_1.expect)(populated.eatenBy[0].id).toBe(admin.id);
144
112
  (0, bun_test_1.expect)(populated.eatenBy[0].name).toBe("Admin");
145
113
  (0, bun_test_1.expect)(populated.likesIds[0].userId.id).toBe(admin.id);
@@ -148,7 +116,7 @@ var tests_1 = require("./tests");
148
116
  (0, bun_test_1.expect)(populated.likesIds[1].userId.name).toBe("Not Admin");
149
117
  unpopulated = (0, populate_1.unpopulate)(populated, "ownerId");
150
118
  (0, bun_test_1.expect)(spinach.ownerId.name).toBeUndefined();
151
- (0, bun_test_1.expect)(unpopulated.ownerId.toString()).toBe(admin.id);
119
+ (0, bun_test_1.expect)(unpopulated.ownerId.toString()).toBe(notAdmin.id);
152
120
  // Ensure nothing else was touched.
153
121
  (0, bun_test_1.expect)(populated.likesIds[0].userId.id).toBe(admin.id);
154
122
  (0, bun_test_1.expect)(populated.likesIds[0].userId.name).toBe("Admin");
@@ -21,7 +21,7 @@ export declare const mapOperationType: (operationType: string, change: ChangeStr
21
21
  * Determine which Socket.io rooms to emit to based on the room strategy.
22
22
  * Exported for testing.
23
23
  */
24
- export declare const resolveRooms: (entry: RealtimeRegistryEntry, doc: any, method: string) => string[];
24
+ export declare const resolveRooms: (entry: RealtimeRegistryEntry, doc: Record<string, unknown>, method: string) => string[];
25
25
  /**
26
26
  * Ensure serialized documents include `id` to match REST API responses.
27
27
  * Change stream fullDocument payloads are raw BSON objects with `_id` only.
@@ -43,8 +43,8 @@ export declare const ensureApiId: (data: unknown) => unknown;
43
43
  * would risk leaking unsanitized fields (e.g. `hash`/`salt`) that the handler
44
44
  * was supposed to strip.
45
45
  */
46
- export declare const serializeDoc: (entry: RealtimeRegistryEntry, doc: any, method: "create" | "update" | "delete", user?: User) => Promise<any>;
47
- export declare const emitToAuthorizedRoom: (io: Server, room: string, event: RealtimeEvent, entry: RealtimeRegistryEntry, fullDocument: any, logDebug: (msg: string) => void) => Promise<void>;
46
+ export declare const serializeDoc: (entry: RealtimeRegistryEntry, doc: Record<string, unknown>, method: "create" | "update" | "delete", user?: User) => Promise<unknown>;
47
+ export declare const emitToAuthorizedRoom: (io: Server, room: string, event: RealtimeEvent, entry: RealtimeRegistryEntry, fullDocument: Record<string, unknown> | undefined, logDebug: (msg: string) => void) => Promise<void>;
48
48
  /**
49
49
  * Emit a sync event to document-specific and query rooms.
50
50
  *
@@ -61,7 +61,7 @@ export declare const emitToAuthorizedRoom: (io: Server, room: string, event: Rea
61
61
  *
62
62
  * Exported for testing.
63
63
  */
64
- export declare const emitToDocumentAndQueryRooms: (io: Server, collection: string, event: RealtimeEvent, fullDocument: any, logDebug: (msg: string) => void, entry?: RealtimeRegistryEntry) => Promise<void>;
64
+ export declare const emitToDocumentAndQueryRooms: (io: Server, collection: string, event: RealtimeEvent, fullDocument: Record<string, unknown> | undefined, logDebug: (msg: string) => void, entry?: RealtimeRegistryEntry) => Promise<void>;
65
65
  /**
66
66
  * Start watching MongoDB change streams and emitting real-time events.
67
67
  */