@terreno/api 0.20.1 → 0.21.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 (65) 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/dist/__tests__/versionCheckPlugin.test.js +29 -7
  5. package/dist/actions.openApi.test.js +13 -11
  6. package/dist/api.js +98 -11
  7. package/dist/api.query.test.js +31 -1
  8. package/dist/api.test.js +211 -0
  9. package/dist/auth.test.js +10 -10
  10. package/dist/betterAuth.d.ts +1 -1
  11. package/dist/consentApp.test.js +1 -0
  12. package/dist/example.js +4 -4
  13. package/dist/expressServer.d.ts +0 -22
  14. package/dist/expressServer.js +1 -125
  15. package/dist/expressServer.test.js +90 -91
  16. package/dist/githubAuth.test.js +22 -22
  17. package/dist/logger.d.ts +154 -0
  18. package/dist/logger.js +445 -26
  19. package/dist/logger.test.js +435 -0
  20. package/dist/middleware.d.ts +7 -0
  21. package/dist/middleware.js +58 -1
  22. package/dist/middleware.test.js +159 -0
  23. package/dist/openApi.test.js +10 -17
  24. package/dist/openApiBuilder.test.js +18 -10
  25. package/dist/realtime/changeStreamWatcher.d.ts +4 -4
  26. package/dist/realtime/changeStreamWatcher.js +2 -4
  27. package/dist/realtime/queryMatcher.d.ts +1 -1
  28. package/dist/realtime/queryMatcher.js +39 -14
  29. package/dist/realtime/types.d.ts +3 -3
  30. package/dist/requestContext.d.ts +61 -0
  31. package/dist/requestContext.js +74 -0
  32. package/dist/secretProviders.test.js +335 -0
  33. package/dist/terrenoApp.d.ts +27 -15
  34. package/dist/terrenoApp.js +24 -14
  35. package/dist/terrenoApp.test.js +52 -0
  36. package/dist/tests/bunSetup.js +61 -7
  37. package/dist/tests.js +27 -4
  38. package/package.json +1 -1
  39. package/src/__tests__/versionCheckPlugin.test.ts +43 -15
  40. package/src/actions.openApi.test.ts +12 -10
  41. package/src/api.query.test.ts +24 -1
  42. package/src/api.test.ts +169 -0
  43. package/src/api.ts +71 -0
  44. package/src/auth.test.ts +10 -10
  45. package/src/betterAuth.ts +1 -1
  46. package/src/consentApp.test.ts +1 -0
  47. package/src/example.ts +4 -4
  48. package/src/expressServer.test.ts +82 -85
  49. package/src/expressServer.ts +1 -213
  50. package/src/githubAuth.test.ts +22 -22
  51. package/src/logger.test.ts +466 -1
  52. package/src/logger.ts +477 -14
  53. package/src/middleware.test.ts +74 -2
  54. package/src/middleware.ts +57 -0
  55. package/src/openApi.test.ts +10 -17
  56. package/src/openApiBuilder.test.ts +18 -10
  57. package/src/realtime/changeStreamWatcher.ts +15 -10
  58. package/src/realtime/queryMatcher.ts +54 -27
  59. package/src/realtime/types.ts +4 -4
  60. package/src/requestContext.ts +86 -0
  61. package/src/secretProviders.test.ts +219 -1
  62. package/src/terrenoApp.test.ts +38 -0
  63. package/src/terrenoApp.ts +37 -15
  64. package/src/tests/bunSetup.ts +16 -3
  65. package/src/tests.ts +17 -4
@@ -0,0 +1,71 @@
1
+ ### @terreno/api
2
+
3
+ REST API framework providing:
4
+
5
+ - **modelRouter**: Auto-generates CRUD endpoints for Mongoose models
6
+ - **Permissions**: `IsAuthenticated`, `IsOwner`, `IsAdmin`, `IsAuthenticatedOrReadOnly`
7
+ - **Query Filters**: `OwnerQueryFilter` for filtering list queries by owner
8
+ - **setupServer**: Express server setup with auth, OpenAPI, and middleware
9
+ - **APIError**: Standardized error handling
10
+ - **logger**: Winston-based logging
11
+
12
+ Key imports:
13
+
14
+ ```typescript
15
+ import {
16
+ modelRouter,
17
+ setupServer,
18
+ Permissions,
19
+ OwnerQueryFilter,
20
+ APIError,
21
+ logger,
22
+ asyncHandler,
23
+ authenticateMiddleware,
24
+ } from "@terreno/api";
25
+ ```
26
+
27
+ #### modelRouter Usage
28
+
29
+ ```typescript
30
+ import {modelRouter, modelRouterOptions, Permissions} from "@terreno/api";
31
+
32
+ const router = modelRouter(YourModel, {
33
+ permissions: {
34
+ list: [Permissions.IsAuthenticated],
35
+ create: [Permissions.IsAuthenticated],
36
+ read: [Permissions.IsOwner],
37
+ update: [Permissions.IsOwner],
38
+ delete: [], // Disabled
39
+ },
40
+ sort: "-created",
41
+ queryFields: ["_id", "type", "name"],
42
+ });
43
+ ```
44
+
45
+ #### Custom Routes
46
+
47
+ For non-CRUD endpoints, use the OpenAPI builder:
48
+
49
+ ```typescript
50
+ import {asyncHandler, authenticateMiddleware, createOpenApiBuilder} from "@terreno/api";
51
+
52
+ router.get("/yourRoute/:id", [
53
+ authenticateMiddleware(),
54
+ createOpenApiBuilder(options)
55
+ .withTags(["yourTag"])
56
+ .withSummary("Brief summary")
57
+ .withPathParameter("id", {type: "string"})
58
+ .withResponse(200, {data: {type: "object"}})
59
+ .build(),
60
+ ], asyncHandler(async (req, res) => {
61
+ return res.json({data: result});
62
+ }));
63
+ ```
64
+
65
+ #### API Conventions
66
+
67
+ - Throw `APIError` with appropriate status codes: `throw new APIError({status: 400, title: "Message"})`
68
+ - Do not use `Model.findOne` — use `Model.findExactlyOne` or `Model.findOneOrThrow`
69
+ - Define statics/methods by direct assignment: `schema.methods = {bar() {}}`
70
+ - All model types live in `src/types/models/`
71
+ - In routes: `req.user` is `UserDocument | undefined`
@@ -0,0 +1,143 @@
1
+ ---
2
+ name: mongoose-schema-safety
3
+ description: >-
4
+ Invoke when making any Mongoose schema change: adding/removing/renaming
5
+ fields, creating a new model, adding indexes, or writing a backfill migration.
6
+ Provides the five-type pattern, risk matrix, type file checklist, and rollout
7
+ safety steps for Terreno backends.
8
+ ---
9
+ # Mongoose Schema Safety — Terreno
10
+
11
+ Typical Terreno apps keep models under `backend/src/models/` and types under `backend/src/types/models/`. The Terreno monorepo also uses `example-backend/` and `@terreno/ai` — adjust paths below to match your repo.
12
+
13
+ ## Where Schemas and Types Live
14
+
15
+ | Package | Schemas | Types |
16
+ |---------|---------|-------|
17
+ | `@terreno/api` (built-in models like `User`, `ConsentForm`) | `api/src/models/*.ts` | `api/src/types/*.ts` (or co-located in the model file via `mongoose.Schema<Doc, Model, Methods>` generics) |
18
+ | `example-backend` (`User`, `Todo`, `Configuration`) | `example-backend/src/models/*.ts` | `example-backend/src/types/models/*.ts` (`userTypes.ts`, `todoTypes.ts`, …) |
19
+ | `@terreno/ai` (`AIRequest`, `GptHistory`) | `ai/src/models/*.ts` | `ai/src/types/index.ts` |
20
+
21
+ Every field in every schema **must** have a `description` (see the API rule `01-model-field-descriptions.md`). Descriptions flow through to the OpenAPI spec via `mongoose-to-swagger`.
22
+
23
+ ## The Five-Type Pattern
24
+
25
+ Every model has manually-maintained TypeScript types. **Types are NOT auto-generated** — every schema change must include a matching type update in the same commit/PR.
26
+
27
+ ```typescript
28
+ import type {DefaultDoc, DefaultModel, DefaultStatics} from "@terreno/api";
29
+
30
+ // 1. Instance methods (on the document)
31
+ export type YourModelMethods = {
32
+ customMethod: (this: YourModelDocument, param: string) => Promise<void>;
33
+ };
34
+
35
+ // 2. Static methods (on the model class) — extend DefaultStatics for findExactlyOne / findOneOrNone
36
+ export type YourModelStatics = DefaultStatics<YourModelDocument> & {
37
+ customStatic: (this: YourModelModel, param: string) => Promise<YourModelDocument>;
38
+ };
39
+
40
+ // 3. Model type combining document + statics
41
+ export type YourModelModel = DefaultModel<YourModelDocument> & YourModelStatics;
42
+
43
+ // 4. Schema type
44
+ export type YourModelSchema = mongoose.Schema<YourModelDocument, YourModelModel, YourModelMethods>;
45
+
46
+ // 5. Document type (the shape of a single record)
47
+ export type YourModelDocument = DefaultDoc & YourModelMethods & {
48
+ fieldName: string;
49
+ optionalField?: number;
50
+ enumField: "value1" | "value2";
51
+ };
52
+ ```
53
+
54
+ Follow existing type files in `example-backend/src/types/models/` and `ai/src/types/index.ts` for naming and structure.
55
+
56
+ ## Statics & Methods — Direct Assignment
57
+
58
+ Define statics and methods by direct assignment on the schema — not via `.method()` / `.static()` / `.add()`:
59
+
60
+ ```typescript
61
+ schema.methods = {
62
+ getDisplayName(this: YourModelDocument): string {
63
+ return this.name;
64
+ },
65
+ };
66
+ schema.statics = {
67
+ async findByEmail(this: YourModelModel, email: string) {
68
+ return this.findOneOrNone({email});
69
+ },
70
+ };
71
+ ```
72
+
73
+ ## Critical Mongoose Rules
74
+
75
+ - **Never use `Model.findOne`** — use `Model.findExactlyOne` (throws on 0 or many) or `Model.findOneOrNone` (throws on many). These come from the `findExactlyOne` / `findOneOrNone` plugins in `@terreno/api`.
76
+ - Apply the standard plugins (`createdUpdatedPlugin`, `isDeletedPlugin`, `findOneOrNone`, `findExactlyOne`) on every new model. Most existing models go through `addDefaultPlugins`.
77
+ - `checkModelsStrict()` runs at non-prod startup (`server.ts`) and validates schema consistency — keep it passing.
78
+
79
+ ## Schema Change Risk Matrix
80
+
81
+ | Change Type | Risk Level | Required Mitigation |
82
+ |-------------|-----------|---------------------|
83
+ | Add optional field | Low | Safe to ship directly (still requires `description`) |
84
+ | Add required field | High | Must provide a default value, OR write a backfill migration script first |
85
+ | Remove field | Medium | Soft-remove first (mark optional, stop writing); hard-remove in next PR after deploys settle |
86
+ | Rename field | High | Three-step: add new → backfill → remove old (separate PRs) |
87
+ | Change field type | Critical | Treat as rename: new field + migration + remove old |
88
+ | Add index | Medium | Safe in code, but build can slow writes on large collections — coordinate with ops |
89
+ | Remove index | Low | Safe to ship directly |
90
+ | Add unique index | High | Dedup migration must run first; otherwise the index build fails on existing duplicates |
91
+
92
+ ## Migration Scripts
93
+
94
+ Migration / backfill scripts live in `example-backend/src/scripts/` (e.g. `syncConsents.ts`, `seedConsents.ts`). They use the `ScriptRunner` type and `BackgroundTask` model from `@terreno/api` (`api/src/scriptRunner.ts`):
95
+
96
+ ```typescript
97
+ import type {ScriptContext, ScriptResult, ScriptRunner} from "@terreno/api";
98
+
99
+ export const run: ScriptRunner = async (wetRun, ctx) => {
100
+ const results: string[] = [];
101
+ // ... do work ...
102
+ if (wetRun) {
103
+ // commit changes
104
+ }
105
+ return {success: true, results};
106
+ };
107
+ ```
108
+
109
+ Always run with `wetRun = false` first to verify the dry-run output. Use `ctx.checkCancellation()`, `ctx.addLog()`, and `ctx.updateProgress()` for long-running tasks.
110
+
111
+ ## Cross-Package Ripple
112
+
113
+ A schema change in one place often ripples:
114
+
115
+ - **API surface affected?** Regenerate the SDK with `cd example-frontend && bun run sdk` (or invoke the `generate-sdk` skill). The OpenAPI spec is derived from the schema, so the frontend hooks will go stale otherwise.
116
+ - **`modelRouter` config?** If you added/removed a field, update `queryFields`, `populatePaths`, and `responseHandler` for any router that touches the model.
117
+ - **Admin panel?** If the model is registered in `AdminApp`, update `listFields` so the table reflects the new shape.
118
+ - **Populated refs?** If another model references this one via `ObjectId` + `ref`, and the referenced document shape changed, the consumer's populated type may need updating.
119
+
120
+ ## Post-Change Checklist
121
+
122
+ - [ ] All new fields have a `description` (flows to OpenAPI)
123
+ - [ ] Type file updated in the same commit/PR — field names, optionality, enum values, array types, refs, statics/methods all match the schema
124
+ - [ ] For a new model: all five types (`Document`, `Methods`, `Statics`, `Model`, `Schema`) created
125
+ - [ ] Statics/methods assigned directly on schema (`schema.statics = {...}`, `schema.methods = {...}`) — not `.static()` / `.method()`
126
+ - [ ] Other model type files checked — any model that populates this one updated if its shape changed
127
+ - [ ] Migration script written (and dry-run verified with `wetRun = false`) for any backfill
128
+ - [ ] `modelRouter` config updated (`queryFields`, `populatePaths`, `responseHandler`) if needed
129
+ - [ ] `AdminApp` `listFields` updated if the model is in the admin panel
130
+ - [ ] `bun run sdk` run from `example-frontend/` if the API response shape changed
131
+ - [ ] Test covers old-format document behavior (missing new field) if the change rolls out before a backfill
132
+ - [ ] Unique index: dedup migration written and run before the index is added
133
+ - [ ] `checkModelsStrict()` still passes (it runs on non-prod startup)
134
+ - [ ] No `Model.findOne` introduced — use `findExactlyOne` / `findOneOrNone`
135
+
136
+ ## Common Pitfalls
137
+
138
+ - Adding a required field without a default — first deploy fails for documents that pre-date the field
139
+ - Skipping the type file update — TS happily compiles older callers but new ones break at runtime
140
+ - Adding a `unique` index without dedup — index build fails on collections with existing duplicates
141
+ - Renaming a field in one PR — frontend gets old field, backend writes new field, neither side is happy
142
+ - Forgetting to regenerate the SDK after a shape change — frontend types drift from reality
143
+ - Using `.method()` / `.static()` API — terreno's convention is direct assignment, and mixing styles makes types hard to maintain
package/README.md CHANGED
@@ -15,7 +15,7 @@ model instances.
15
15
  - **Authentication** — JWT with email/password and GitHub OAuth support
16
16
  - **Permissions** — Fine-grained access control (IsAuthenticated, IsOwner, IsAdmin, etc.)
17
17
  - **OpenAPI** — Automatic spec generation from models and routes
18
- - **Logging** — Winston-based logging with Google Cloud and Sentry support
18
+ - **Logging** — Winston-based logging with scoped & feature-flagged loggers, automatic request correlation, and Google Cloud / Sentry support
19
19
 
20
20
  ## Getting started
21
21
 
@@ -165,6 +165,59 @@ setupServer({
165
165
 
166
166
  **Learn more:** See the [GitHub OAuth how-to guide](../docs/how-to/add-github-oauth.md) for complete setup instructions.
167
167
 
168
+ ## Logging
169
+
170
+ @terreno/api provides a Winston-based `logger`, two helpers (`createScopedLogger` and
171
+ `createFeatureFlaggedLogger`), and automatic request/job correlation. Always use these instead of
172
+ `console.log` for permanent server logs.
173
+
174
+ ```typescript
175
+ import {createFeatureFlaggedLogger, createScopedLogger, logger} from "@terreno/api";
176
+
177
+ // Global logger
178
+ logger.info("Server started", {port: 4000});
179
+ logger.error("Failed to process", {error});
180
+ await chargeCard(id).catch(logger.catch); // logs + captures the exception
181
+
182
+ // Scoped logger: a stable prefix + labels on every line, ideal for multi-step workflows
183
+ const log = createScopedLogger({
184
+ prefix: "[InvoicePay]",
185
+ labels: {invoiceId: invoice._id.toString()},
186
+ });
187
+ log.info("Starting capture"); // -> "[InvoicePay] Starting capture invoiceId=… requestId=…"
188
+
189
+ // Feature-flagged logger: silent until isEnabled() returns true (no redeploy needed)
190
+ const debugLog = createFeatureFlaggedLogger({
191
+ isEnabled: () => process.env.DEBUG_BILLING === "true",
192
+ target: log,
193
+ });
194
+ debugLog.debug("optional detail");
195
+ ```
196
+
197
+ ### Correlation
198
+
199
+ While a request or job scope is active, every log line is automatically tagged with `requestId`,
200
+ `userId`, `traceId`, and related fields, so all lines for one request can be grouped together. HTTP
201
+ requests get a scope automatically (the framework also echoes an `X-Request-ID` response header).
202
+ For background jobs, scripts, and cron tasks, open a scope with `runWithRequestContext`:
203
+
204
+ ```typescript
205
+ import {createScopedLogger, runWithRequestContext} from "@terreno/api";
206
+
207
+ await runWithRequestContext({jobId: "nightly-sync"}, async () => {
208
+ const log = createScopedLogger({prefix: "[NightlySync]"});
209
+ log.info("started"); // includes jobId + a generated requestId on every line
210
+ await sync();
211
+ });
212
+ ```
213
+
214
+ In production, pass a structured transport (e.g. `@google-cloud/logging-winston`) via
215
+ `loggingOptions`/`setupLogging` so the correlation fields and labels reach Log Explorer as
216
+ `jsonPayload`.
217
+
218
+ **Learn more:** See the [Logging & Tracing reference](../docs/reference/api.md#logging--tracing) for
219
+ the full API, label conventions, and Google Cloud Logging / Sentry details.
220
+
168
221
  ## Sentry
169
222
  To enable Sentry, create a "src/sentryInstrumment.ts" file in your project.
170
223
 
@@ -114,7 +114,11 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
114
114
  case 1:
115
115
  res = _a.sent();
116
116
  (0, bun_test_1.expect)(res.status).toBe(200);
117
- (0, bun_test_1.expect)(res.body).toEqual({ pollingIntervalMs: 86400000, status: "ok" });
117
+ (0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
118
+ (0, bun_test_1.expect)(res.body).toEqual(bun_test_1.expect.objectContaining({
119
+ pollingIntervalMs: 86400000,
120
+ status: "ok",
121
+ }));
118
122
  return [2 /*return*/];
119
123
  }
120
124
  });
@@ -127,7 +131,8 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
127
131
  case 1:
128
132
  res = _a.sent();
129
133
  (0, bun_test_1.expect)(res.status).toBe(200);
130
- (0, bun_test_1.expect)(res.body).toEqual({ status: "ok" });
134
+ (0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
135
+ (0, bun_test_1.expect)(res.body).toEqual(bun_test_1.expect.objectContaining({ status: "ok" }));
131
136
  return [2 /*return*/];
132
137
  }
133
138
  });
@@ -140,7 +145,8 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
140
145
  case 1:
141
146
  res = _a.sent();
142
147
  (0, bun_test_1.expect)(res.status).toBe(200);
143
- (0, bun_test_1.expect)(res.body).toEqual({ status: "ok" });
148
+ (0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
149
+ (0, bun_test_1.expect)(res.body).toEqual(bun_test_1.expect.objectContaining({ status: "ok" }));
144
150
  return [2 /*return*/];
145
151
  }
146
152
  });
@@ -159,12 +165,13 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
159
165
  case 2:
160
166
  res = _a.sent();
161
167
  (0, bun_test_1.expect)(res.status).toBe(200);
162
- (0, bun_test_1.expect)(res.body).toEqual({
168
+ (0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
169
+ (0, bun_test_1.expect)(res.body).toEqual(bun_test_1.expect.objectContaining({
163
170
  pollingIntervalMs: 86400000,
164
171
  requiredVersion: 50,
165
172
  status: "ok",
166
173
  warningVersion: 100,
167
- });
174
+ }));
168
175
  return [2 /*return*/];
169
176
  }
170
177
  });
@@ -184,6 +191,7 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
184
191
  case 2:
185
192
  res = _a.sent();
186
193
  (0, bun_test_1.expect)(res.status).toBe(200);
194
+ (0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
187
195
  (0, bun_test_1.expect)(res.body.status).toBe("warning");
188
196
  (0, bun_test_1.expect)(res.body.message).toBe("Please update!");
189
197
  return [2 /*return*/];
@@ -206,6 +214,7 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
206
214
  case 2:
207
215
  res = _a.sent();
208
216
  (0, bun_test_1.expect)(res.status).toBe(200);
217
+ (0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
209
218
  (0, bun_test_1.expect)(res.body.status).toBe("required");
210
219
  (0, bun_test_1.expect)(res.body.message).toBe("Update required");
211
220
  (0, bun_test_1.expect)(res.body.updateUrl).toBe("https://example.com/update");
@@ -228,10 +237,12 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
228
237
  return [4 /*yield*/, app.get("/version-check").query({ platform: "web", version: 100 })];
229
238
  case 2:
230
239
  webRes = _a.sent();
240
+ (0, bun_test_1.expect)(webRes.body.requestId).toBe(webRes.headers["x-request-id"]);
231
241
  (0, bun_test_1.expect)(webRes.body.status).toBe("ok");
232
242
  return [4 /*yield*/, app.get("/version-check").query({ platform: "mobile", version: 100 })];
233
243
  case 3:
234
244
  mobileRes = _a.sent();
245
+ (0, bun_test_1.expect)(mobileRes.body.requestId).toBe(mobileRes.headers["x-request-id"]);
235
246
  (0, bun_test_1.expect)(mobileRes.body.status).toBe("required");
236
247
  return [2 /*return*/];
237
248
  }
@@ -250,6 +261,7 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
250
261
  return [4 /*yield*/, app.get("/version-check").query({ platform: "invalid", version: 50 })];
251
262
  case 2:
252
263
  res = _a.sent();
264
+ (0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
253
265
  (0, bun_test_1.expect)(res.body.status).toBe("required");
254
266
  return [2 /*return*/];
255
267
  }
@@ -269,12 +281,13 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
269
281
  case 2:
270
282
  res = _a.sent();
271
283
  (0, bun_test_1.expect)(res.status).toBe(200);
272
- (0, bun_test_1.expect)(res.body).toEqual({
284
+ (0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
285
+ (0, bun_test_1.expect)(res.body).toEqual(bun_test_1.expect.objectContaining({
273
286
  pollingIntervalMs: 86400000,
274
287
  requiredVersion: 50,
275
288
  status: "ok",
276
289
  warningVersion: 100,
277
- });
290
+ }));
278
291
  return [2 /*return*/];
279
292
  }
280
293
  });
@@ -294,6 +307,7 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
294
307
  case 2:
295
308
  res = _a.sent();
296
309
  (0, bun_test_1.expect)(res.status).toBe(200);
310
+ (0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
297
311
  (0, bun_test_1.expect)(res.body.pollingIntervalMs).toBe(3600000);
298
312
  return [2 /*return*/];
299
313
  }
@@ -313,6 +327,7 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
313
327
  case 2:
314
328
  res = _a.sent();
315
329
  (0, bun_test_1.expect)(res.status).toBe(200);
330
+ (0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
316
331
  (0, bun_test_1.expect)(res.body.pollingIntervalMs).toBe(86400000);
317
332
  return [2 /*return*/];
318
333
  }
@@ -332,6 +347,7 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
332
347
  case 2:
333
348
  res = _a.sent();
334
349
  (0, bun_test_1.expect)(res.status).toBe(200);
350
+ (0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
335
351
  (0, bun_test_1.expect)(res.body.status).toBe("required");
336
352
  return [2 /*return*/];
337
353
  }
@@ -351,6 +367,7 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
351
367
  case 2:
352
368
  res = _a.sent();
353
369
  (0, bun_test_1.expect)(res.status).toBe(200);
370
+ (0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
354
371
  (0, bun_test_1.expect)(res.body.status).toBe("warning");
355
372
  (0, bun_test_1.expect)(res.body.message).toBe("A new version is available. Please update for the best experience.");
356
373
  return [2 /*return*/];
@@ -371,6 +388,7 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
371
388
  case 2:
372
389
  res = _a.sent();
373
390
  (0, bun_test_1.expect)(res.status).toBe(200);
391
+ (0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
374
392
  (0, bun_test_1.expect)(res.body.status).toBe("required");
375
393
  (0, bun_test_1.expect)(res.body.message).toBe("This version is no longer supported. Please update to continue.");
376
394
  return [2 /*return*/];
@@ -391,6 +409,7 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
391
409
  case 2:
392
410
  res = _a.sent();
393
411
  (0, bun_test_1.expect)(res.status).toBe(200);
412
+ (0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
394
413
  (0, bun_test_1.expect)(res.body.status).toBe("warning");
395
414
  return [2 /*return*/];
396
415
  }
@@ -409,11 +428,13 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
409
428
  return [4 /*yield*/, app.get("/version-check").query({ platform: "web", version: 150 })];
410
429
  case 2:
411
430
  warningRes = _a.sent();
431
+ (0, bun_test_1.expect)(warningRes.body.requestId).toBe(warningRes.headers["x-request-id"]);
412
432
  (0, bun_test_1.expect)(warningRes.body.status).toBe("warning");
413
433
  (0, bun_test_1.expect)(warningRes.body.message).toBe("A new version is available. Please update for the best experience.");
414
434
  return [4 /*yield*/, app.get("/version-check").query({ platform: "web", version: 50 })];
415
435
  case 3:
416
436
  requiredRes = _a.sent();
437
+ (0, bun_test_1.expect)(requiredRes.body.requestId).toBe(requiredRes.headers["x-request-id"]);
417
438
  (0, bun_test_1.expect)(requiredRes.body.status).toBe("required");
418
439
  (0, bun_test_1.expect)(requiredRes.body.message).toBe("This version is no longer supported. Please update to continue.");
419
440
  return [2 /*return*/];
@@ -428,6 +449,7 @@ var versionCheckPlugin_1 = require("../versionCheckPlugin");
428
449
  case 1:
429
450
  res = _a.sent();
430
451
  (0, bun_test_1.expect)(res.status).toBe(200);
452
+ (0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
431
453
  return [2 /*return*/];
432
454
  }
433
455
  });
@@ -70,8 +70,6 @@ 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 permissions_1 = require("./permissions");
76
74
  var terrenoApp_1 = require("./terrenoApp");
77
75
  var tests_1 = require("./tests");
@@ -137,6 +135,7 @@ var primeActionOpenApiRoutes = function (server, foodId) { return __awaiter(void
137
135
  }); };
138
136
  var assertActionOpenApiSpec = function (spec) {
139
137
  var _a, _b, _c, _d, _e;
138
+ (0, bun_test_1.expect)(spec.requestId).toBeUndefined();
140
139
  var paths = spec.paths;
141
140
  var collectionPath = paths["/food/summarize"];
142
141
  var instancePath = paths["/food/{id}/ping"];
@@ -193,7 +192,7 @@ var assertActionOpenApiSpec = function (spec) {
193
192
  }); });
194
193
  (0, bun_test_1.describe)("TerrenoApp", function () {
195
194
  (0, bun_test_1.it)("includes action operations in openapi.json after first request", function () { return __awaiter(void 0, void 0, void 0, function () {
196
- var foodRegistration, app, server, specRes;
195
+ var foodRegistration, app, server, specRes, pingRes;
197
196
  return __generator(this, function (_a) {
198
197
  switch (_a.label) {
199
198
  case 0:
@@ -212,26 +211,29 @@ var assertActionOpenApiSpec = function (spec) {
212
211
  case 2:
213
212
  specRes = _a.sent();
214
213
  assertActionOpenApiSpec(specRes.body);
214
+ return [4 /*yield*/, server.get("/food/".concat(foodId, "/ping")).expect(200)];
215
+ case 3:
216
+ pingRes = _a.sent();
217
+ (0, bun_test_1.expect)(pingRes.body.data).toEqual({ id: foodId });
218
+ (0, bun_test_1.expect)(pingRes.body.requestId).toBe(pingRes.headers["x-request-id"]);
215
219
  return [2 /*return*/];
216
220
  }
217
221
  });
218
222
  }); });
219
223
  });
220
- (0, bun_test_1.describe)("setupServer", function () {
224
+ (0, bun_test_1.describe)("configureApp", function () {
221
225
  var app;
222
226
  (0, bun_test_1.beforeEach)(function () {
223
- var addRoutes = function (router, routerOptions) {
227
+ var configureApp = function (router, routerOptions) {
224
228
  router.use("/food", (0, api_1.modelRouter)(tests_1.FoodModel, __assign(__assign({}, foodActionRouterOptions), routerOptions)));
225
229
  };
226
- app = (0, expressServer_1.setupServer)({
227
- addRoutes: addRoutes,
230
+ app = new terrenoApp_1.TerrenoApp({
231
+ configureApp: configureApp,
228
232
  skipListen: true,
229
233
  userModel: tests_1.UserModel,
230
- });
231
- (0, auth_1.setupAuth)(app, tests_1.UserModel);
232
- (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
234
+ }).build();
233
235
  });
234
- (0, bun_test_1.it)("emits the same action operations on first hit via legacy setupServer", function () { return __awaiter(void 0, void 0, void 0, function () {
236
+ (0, bun_test_1.it)("emits the same action operations on first hit via configureApp", function () { return __awaiter(void 0, void 0, void 0, function () {
235
237
  var server, specRes;
236
238
  return __generator(this, function (_a) {
237
239
  switch (_a.label) {