@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
@@ -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
+ });
@@ -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
  }); });
@@ -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;
@@ -168,13 +167,11 @@ function addRoutesWithBuilder(router, options) {
168
167
  return __generator(this, function (_a) {
169
168
  process.env.REFRESH_TOKEN_SECRET = "testsecret1234";
170
169
  process.env.ENABLE_SWAGGER = "true";
171
- app = (0, expressServer_1.setupServer)({
172
- addRoutes: addRoutesWithBuilder,
170
+ app = new terrenoApp_1.TerrenoApp({
171
+ configureApp: addRoutesWithBuilder,
173
172
  skipListen: true,
174
173
  userModel: tests_1.UserModel,
175
- });
176
- (0, auth_1.setupAuth)(app, tests_1.UserModel);
177
- (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
174
+ }).build();
178
175
  return [2 /*return*/];
179
176
  });
180
177
  }); });
@@ -367,7 +364,11 @@ function addRoutesWithBuilder(router, options) {
367
364
  return [4 /*yield*/, server.get("/food/stats").expect(200)];
368
365
  case 1:
369
366
  res = _a.sent();
370
- (0, bun_test_1.expect)(res.body).toEqual({ avgCalories: 250, count: 10 });
367
+ (0, bun_test_1.expect)(res.body).toEqual({
368
+ avgCalories: 250,
369
+ count: 10,
370
+ requestId: res.headers["x-request-id"],
371
+ });
371
372
  return [2 /*return*/];
372
373
  }
373
374
  });
@@ -384,7 +385,10 @@ function addRoutesWithBuilder(router, options) {
384
385
  .expect(201)];
385
386
  case 1:
386
387
  res = _a.sent();
387
- (0, bun_test_1.expect)(res.body).toEqual({ reportId: "report-123" });
388
+ (0, bun_test_1.expect)(res.body).toEqual({
389
+ reportId: "report-123",
390
+ requestId: res.headers["x-request-id"],
391
+ });
388
392
  return [2 /*return*/];
389
393
  }
390
394
  });
@@ -414,7 +418,11 @@ function addRoutesWithBuilder(router, options) {
414
418
  return [4 /*yield*/, server.get("/food/categories/cat-123").expect(200)];
415
419
  case 1:
416
420
  res = _a.sent();
417
- (0, bun_test_1.expect)(res.body).toEqual({ id: "cat-123", name: "Fruits" });
421
+ (0, bun_test_1.expect)(res.body).toEqual({
422
+ id: "cat-123",
423
+ name: "Fruits",
424
+ requestId: res.headers["x-request-id"],
425
+ });
418
426
  return [2 /*return*/];
419
427
  }
420
428
  });
@@ -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
  */
@@ -120,7 +120,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
120
120
  };
121
121
  Object.defineProperty(exports, "__esModule", { value: true });
122
122
  exports.stopChangeStreamWatcher = exports.startChangeStreamWatcher = exports.emitToDocumentAndQueryRooms = exports.emitToAuthorizedRoom = exports.serializeDoc = exports.ensureApiId = exports.resolveRooms = exports.mapOperationType = void 0;
123
- // biome-ignore-all lint/suspicious/noExplicitAny: change stream and socket handlers use dynamic document shapes
124
123
  var Sentry = __importStar(require("@sentry/bun"));
125
124
  var luxon_1 = require("luxon");
126
125
  var mongoose_1 = __importDefault(require("mongoose"));
@@ -208,7 +207,6 @@ var canReadDocument = function (entry, user, doc) { return __awaiter(void 0, voi
208
207
  * Exported for testing.
209
208
  */
210
209
  var resolveRooms = function (entry, doc, method) {
211
- var _a, _b, _c;
212
210
  var roomStrategy = entry.config.roomStrategy;
213
211
  // Use the collection tag (e.g. "todos") for model rooms, matching what the frontend subscribes to
214
212
  var collectionTag = getCollectionTag(entry.routePath);
@@ -219,7 +217,7 @@ var resolveRooms = function (entry, doc, method) {
219
217
  }
220
218
  switch (roomStrategy) {
221
219
  case "owner": {
222
- var ownerId = (_c = (_b = (_a = doc === null || doc === void 0 ? void 0 : doc.ownerId) === null || _a === void 0 ? void 0 : _a.toString) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : doc === null || doc === void 0 ? void 0 : doc.ownerId;
220
+ var ownerId = (doc === null || doc === void 0 ? void 0 : doc.ownerId) != null ? String(doc.ownerId) : undefined;
223
221
  if (ownerId) {
224
222
  return ["user:".concat(ownerId)];
225
223
  }
@@ -625,7 +623,7 @@ var startChangeStreamWatcher = function (io, config, debug) {
625
623
  }
626
624
  }
627
625
  else {
628
- rooms = (0, exports.resolveRooms)(entry, fullDocument, method);
626
+ rooms = (0, exports.resolveRooms)(entry, fullDocument !== null && fullDocument !== void 0 ? fullDocument : {}, method);
629
627
  }
630
628
  collection = getCollectionTag(entry.routePath);
631
629
  event_1 = __assign({ collection: collection, id: docId, method: method, model: entry.modelName, timestamp: luxon_1.DateTime.now().toMillis() }, (change.operationType === "update" && ((_e = change.updateDescription) === null || _e === void 0 ? void 0 : _e.updatedFields)
@@ -11,4 +11,4 @@
11
11
  * @param query - MongoDB-style query object
12
12
  * @returns true if the document matches all query conditions
13
13
  */
14
- export declare const matchesQuery: (doc: any, query: Record<string, any>) => boolean;
14
+ export declare const matchesQuery: (doc: Record<string, unknown>, query: Record<string, unknown>) => boolean;
@@ -1,5 +1,4 @@
1
1
  "use strict";
2
- // biome-ignore-all lint/suspicious/noExplicitAny: MongoDB query matcher evaluates dynamic filter shapes
3
2
  /**
4
3
  * Simple in-memory MongoDB query matcher.
5
4
  * Evaluates a MongoDB-style query object against a document without hitting the database.
@@ -63,14 +62,32 @@ var normalize = function (value) {
63
62
  return value;
64
63
  }
65
64
  // Handle ObjectId-like objects with toString
66
- if (typeof value === "object" &&
67
- typeof value.toString === "function" &&
68
- ((_a = value.constructor) === null || _a === void 0 ? void 0 : _a.name) !== "Object" &&
69
- !Array.isArray(value)) {
70
- return value.toString();
65
+ if (typeof value === "object" && !Array.isArray(value)) {
66
+ var obj = value;
67
+ var ctorName = (_a = obj.constructor) === null || _a === void 0 ? void 0 : _a.name;
68
+ if (typeof obj.toString === "function" && ctorName !== "Object") {
69
+ return String(value);
70
+ }
71
71
  }
72
72
  return value;
73
73
  };
74
+ /**
75
+ * JS abstract relational comparison on unknown values.
76
+ * Numeric operands compare numerically; everything else compares as strings.
77
+ * This mirrors the coercion behaviour of `>` / `<` on the `any`-typed values
78
+ * that MongoDB in-memory matching historically received.
79
+ */
80
+ var compareValues = function (a, b) {
81
+ if (typeof a === "number" && typeof b === "number") {
82
+ return a - b;
83
+ }
84
+ if (typeof a === "string" && typeof b === "string") {
85
+ return a < b ? -1 : a > b ? 1 : 0;
86
+ }
87
+ var numA = Number(a);
88
+ var numB = Number(b);
89
+ return numA - numB;
90
+ };
74
91
  var matchesCondition = function (rawValue, condition) {
75
92
  var e_2, _a;
76
93
  var value = normalize(rawValue);
@@ -99,26 +116,34 @@ var matchesCondition = function (rawValue, condition) {
99
116
  return false;
100
117
  }
101
118
  break;
102
- case "$gt":
103
- if (!(value > normOp)) {
119
+ case "$gt": {
120
+ var cmp = compareValues(value, normOp);
121
+ if (Number.isNaN(cmp) || cmp <= 0) {
104
122
  return false;
105
123
  }
106
124
  break;
107
- case "$gte":
108
- if (!(value >= normOp)) {
125
+ }
126
+ case "$gte": {
127
+ var cmp = compareValues(value, normOp);
128
+ if (Number.isNaN(cmp) || cmp < 0) {
109
129
  return false;
110
130
  }
111
131
  break;
112
- case "$lt":
113
- if (!(value < normOp)) {
132
+ }
133
+ case "$lt": {
134
+ var cmp = compareValues(value, normOp);
135
+ if (Number.isNaN(cmp) || cmp >= 0) {
114
136
  return false;
115
137
  }
116
138
  break;
117
- case "$lte":
118
- if (!(value <= normOp)) {
139
+ }
140
+ case "$lte": {
141
+ var cmp = compareValues(value, normOp);
142
+ if (Number.isNaN(cmp) || cmp > 0) {
119
143
  return false;
120
144
  }
121
145
  break;
146
+ }
122
147
  case "$in": {
123
148
  if (!Array.isArray(operand)) {
124
149
  return false;
@@ -13,9 +13,9 @@ export interface RealtimeConfig {
13
13
  * - 'broadcast': emit to all authenticated sockets
14
14
  * - function: custom room resolver returning room name(s)
15
15
  */
16
- roomStrategy: "owner" | "model" | "broadcast" | ((doc: any, method: string, req: express.Request) => string[]);
16
+ roomStrategy: "owner" | "model" | "broadcast" | ((doc: Record<string, unknown>, method: string, req: express.Request) => string[]);
17
17
  /** Custom serializer for real-time events. Falls back to the modelRouter responseHandler. */
18
- realtimeResponseHandler?: (doc: any, method: string) => any;
18
+ realtimeResponseHandler?: (doc: Record<string, unknown>, method: string) => unknown;
19
19
  }
20
20
  /**
21
21
  * A real-time sync event emitted to clients via WebSocket.
@@ -94,7 +94,7 @@ export interface QuerySubscription {
94
94
  /** Collection tag (e.g. "todos") */
95
95
  collection: string;
96
96
  /** MongoDB-style query filter (e.g. {completed: false}) */
97
- query: Record<string, any>;
97
+ query: Record<string, unknown>;
98
98
  /** Client-provided queryId (ignored — server computes a canonical ID) */
99
99
  queryId?: string;
100
100
  }
@@ -1,15 +1,32 @@
1
1
  import type express from "express";
2
2
  import type { JwtPayload } from "jsonwebtoken";
3
+ /**
4
+ * Correlation fields stored in AsyncLocalStorage for the lifetime of a request or job. Every log
5
+ * line emitted inside the scope is enriched with these. `requestId` is the only required field; the
6
+ * rest are populated when headers, trace context, or auth supply them.
7
+ */
3
8
  export interface RequestContext {
9
+ /** Background job identifier (from `x-job-id` or set via {@link runWithRequestContext}). */
4
10
  jobId?: string;
11
+ /** Stable id shared by all log lines for one request/job; echoed to clients as `X-Request-ID`. */
5
12
  requestId: string;
13
+ /** Auth session id, resolved from the JWT/Better Auth session or `x-session-id`. */
6
14
  sessionId?: string;
15
+ /** Distributed-tracing span id, parsed from Cloud Trace or W3C `traceparent`. */
7
16
  spanId?: string;
17
+ /** Distributed-tracing trace id, parsed from Cloud Trace or W3C `traceparent`. */
8
18
  traceId?: string;
19
+ /** Whether the trace is sampled, per the incoming trace headers. */
9
20
  traceSampled?: boolean;
21
+ /** Authenticated user id, populated after auth middleware runs. */
10
22
  userId?: string;
11
23
  }
12
24
  export type RequestContextAttributes = Record<string, string>;
25
+ /**
26
+ * Canonical HTTP header names for each correlation field. Use these to propagate context to
27
+ * downstream services (pair with {@link getCurrentRequestContextAttributes}) or to read it from an
28
+ * incoming request (pair with {@link getRequestContextFromAttributes}).
29
+ */
13
30
  export declare const REQUEST_CONTEXT_ATTRIBUTE_NAMES: {
14
31
  readonly jobId: "x-job-id";
15
32
  readonly requestId: "x-request-id";
@@ -26,12 +43,56 @@ export interface JwtSessionPayload extends JwtPayload {
26
43
  }
27
44
  export declare const getSessionIdFromJwtPayload: (payload?: JwtSessionPayload | null) => string | undefined;
28
45
  export declare const getRequestContextFromAttributes: (attributes?: Record<string, string | undefined>) => RequestContext;
46
+ /**
47
+ * Returns the full {@link RequestContext} for the active AsyncLocalStorage scope, or `undefined`
48
+ * when called outside any request/job scope. The logger uses this to enrich each line.
49
+ */
29
50
  export declare const getCurrentRequestContext: () => RequestContext | undefined;
51
+ /**
52
+ * Returns the active correlation fields as a plain object (empty when outside a scope). This is the
53
+ * shape attached to Sentry log attributes and is handy when you need to log or forward the current
54
+ * context yourself.
55
+ */
30
56
  export declare const getCurrentLogContext: () => Partial<RequestContext>;
31
57
  export declare const applyRequestContextToSentry: (context?: Partial<RequestContext>) => void;
32
58
  export declare const setRequestContext: (updates: Partial<RequestContext>) => void;
59
+ /**
60
+ * Serializes the active correlation context into HTTP header attributes (keyed by
61
+ * {@link REQUEST_CONTEXT_ATTRIBUTE_NAMES}) so it can be propagated on outbound requests to other
62
+ * services, keeping the same `requestId`/`traceId` across service boundaries.
63
+ */
33
64
  export declare const getCurrentRequestContextAttributes: (overrides?: Partial<RequestContext>) => RequestContextAttributes;
65
+ /**
66
+ * Runs `callback` inside a fresh correlation scope so every log line it emits shares the same
67
+ * identifiers — the manual equivalent of {@link requestContextMiddleware} for background jobs,
68
+ * cron tasks, scripts, queue consumers, etc. A `requestId` is generated when not supplied, and the
69
+ * context is mirrored to Sentry.
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * import {createScopedLogger, runWithRequestContext} from "@terreno/api";
74
+ *
75
+ * await runWithRequestContext({jobId: "nightly-sync"}, async () => {
76
+ * const log = createScopedLogger({prefix: "[NightlySync]"});
77
+ * log.info("started"); // includes jobId + a generated requestId on every line
78
+ * await sync();
79
+ * });
80
+ * ```
81
+ */
34
82
  export declare const runWithRequestContext: <T>(context: Partial<RequestContext>, callback: () => T) => T;
83
+ /**
84
+ * Like {@link runWithRequestContext}, but seeds the scope from raw header attributes (for example
85
+ * those received on an incoming message or forwarded by another service). Parses Cloud Trace / W3C
86
+ * `traceparent` into `traceId`/`spanId` via {@link getRequestContextFromAttributes}.
87
+ */
35
88
  export declare const runWithRequestContextAttributes: <T>(attributes: Record<string, string | undefined> | undefined, callback: () => T) => T;
36
89
  export declare const updateRequestContextFromRequest: (req: express.Request, res?: express.Response) => void;
90
+ /**
91
+ * Express middleware that opens a correlation scope for the request. Mounted early by `TerrenoApp` /
92
+ * `setupServer`, it resolves a `requestId` (from request-id/correlation headers, Cloud Trace, or
93
+ * W3C `traceparent`, else a new UUID), captures any `jobId`/`sessionId`/trace fields, echoes
94
+ * `X-Request-ID` back to the client, and runs the remaining middleware inside the scope so all
95
+ * downstream logs are correlated. A later auth-aware pass ({@link updateRequestContextFromRequest})
96
+ * fills in `userId`/`sessionId`.
97
+ */
37
98
  export declare const requestContextMiddleware: (req: express.Request, res: express.Response, next: express.NextFunction) => void;