@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.
- package/.ai/guidelines/core.md +71 -0
- package/.ai/skills/mongoose-schema-safety/SKILL.md +143 -0
- package/README.md +54 -1
- package/bunfig.toml +1 -1
- package/dist/__tests__/versionCheckPlugin.test.js +29 -7
- package/dist/actions.openApi.test.js +13 -11
- package/dist/api.js +98 -11
- package/dist/api.query.test.js +31 -1
- package/dist/api.test.js +211 -0
- package/dist/auth.test.js +418 -43
- package/dist/betterAuth.d.ts +1 -1
- package/dist/consentApp.test.js +1 -0
- package/dist/example.js +4 -4
- package/dist/expressServer.d.ts +0 -22
- package/dist/expressServer.js +1 -125
- package/dist/expressServer.test.js +90 -91
- package/dist/githubAuth.test.js +22 -22
- package/dist/logger.d.ts +154 -0
- package/dist/logger.js +445 -26
- package/dist/logger.test.js +435 -0
- package/dist/middleware.d.ts +7 -0
- package/dist/middleware.js +58 -1
- package/dist/middleware.test.js +159 -0
- package/dist/models/consentForm.js +2 -1
- package/dist/models/consentResponse.js +2 -1
- package/dist/models/versionConfig.js +2 -1
- package/dist/openApi.test.js +10 -17
- package/dist/openApiBuilder.d.ts +18 -0
- package/dist/openApiBuilder.js +21 -0
- package/dist/openApiBuilder.test.js +34 -10
- package/dist/permissions.test.js +10 -43
- package/dist/populate.test.js +10 -42
- package/dist/realtime/changeStreamWatcher.d.ts +4 -4
- package/dist/realtime/changeStreamWatcher.js +2 -4
- package/dist/realtime/queryMatcher.d.ts +1 -1
- package/dist/realtime/queryMatcher.js +39 -14
- package/dist/realtime/types.d.ts +3 -3
- package/dist/requestContext.d.ts +61 -0
- package/dist/requestContext.js +74 -0
- package/dist/secretProviders.test.js +335 -0
- package/dist/syncConsents.test.js +2 -2
- package/dist/terrenoApp.d.ts +27 -15
- package/dist/terrenoApp.js +24 -14
- package/dist/terrenoApp.test.js +52 -0
- package/dist/tests/bunSetup.js +66 -262
- package/dist/tests/createTestData.d.ts +9 -0
- package/dist/tests/createTestData.js +272 -0
- package/dist/tests/models.d.ts +71 -0
- package/dist/tests/models.js +134 -0
- package/dist/tests/mongoTestSetup.d.ts +7 -0
- package/dist/tests/mongoTestSetup.js +150 -0
- package/dist/tests/testEnv.d.ts +0 -0
- package/dist/tests/testEnv.js +6 -0
- package/dist/tests/testHelper.d.ts +22 -0
- package/dist/tests/testHelper.js +115 -0
- package/dist/tests/types.d.ts +29 -0
- package/dist/tests/types.js +2 -0
- package/dist/tests.d.ts +10 -78
- package/dist/tests.js +24 -241
- package/dist/transformers.test.js +14 -50
- package/package.json +18 -4
- package/src/__snapshots__/openApiBuilder.test.ts.snap +1 -0
- package/src/__tests__/versionCheckPlugin.test.ts +43 -15
- package/src/actions.openApi.test.ts +12 -10
- package/src/api.query.test.ts +24 -1
- package/src/api.test.ts +169 -0
- package/src/api.ts +71 -0
- package/src/auth.test.ts +287 -39
- package/src/betterAuth.ts +1 -1
- package/src/consentApp.test.ts +1 -0
- package/src/example.ts +4 -4
- package/src/expressServer.test.ts +82 -85
- package/src/expressServer.ts +1 -213
- package/src/githubAuth.test.ts +22 -22
- package/src/logger.test.ts +466 -1
- package/src/logger.ts +477 -14
- package/src/middleware.test.ts +74 -2
- package/src/middleware.ts +57 -0
- package/src/models/consentForm.ts +3 -4
- package/src/models/consentResponse.ts +6 -4
- package/src/models/versionConfig.ts +3 -4
- package/src/openApi.test.ts +10 -17
- package/src/openApiBuilder.test.ts +27 -10
- package/src/openApiBuilder.ts +24 -0
- package/src/permissions.test.ts +8 -23
- package/src/populate.test.ts +7 -22
- package/src/realtime/changeStreamWatcher.ts +15 -10
- package/src/realtime/queryMatcher.ts +54 -27
- package/src/realtime/types.ts +4 -4
- package/src/requestContext.ts +86 -0
- package/src/secretProviders.test.ts +219 -1
- package/src/syncConsents.test.ts +1 -1
- package/src/terrenoApp.test.ts +38 -0
- package/src/terrenoApp.ts +37 -15
- package/src/tests/bunSetup.ts +22 -236
- package/src/tests/createTestData.ts +176 -0
- package/src/tests/models.ts +164 -0
- package/src/tests/mongoTestSetup.ts +69 -0
- package/src/tests/testEnv.ts +4 -0
- package/src/tests/testHelper.ts +57 -0
- package/src/tests/types.ts +35 -0
- package/src/tests.ts +40 -231
- package/src/transformers.test.ts +11 -30
- package/tsconfig.typedoc.json +4 -0
- package/dist/tests/index.d.ts +0 -1
- package/dist/tests/index.js +0 -17
- package/src/tests/index.ts +0 -1
|
@@ -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 = (
|
|
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:
|
|
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
|
-
|
|
68
|
-
(
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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
|
-
|
|
108
|
-
|
|
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
|
-
|
|
113
|
-
|
|
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
|
-
|
|
118
|
-
|
|
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;
|
package/dist/realtime/types.d.ts
CHANGED
|
@@ -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:
|
|
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:
|
|
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,
|
|
97
|
+
query: Record<string, unknown>;
|
|
98
98
|
/** Client-provided queryId (ignored — server computes a canonical ID) */
|
|
99
99
|
queryId?: string;
|
|
100
100
|
}
|
package/dist/requestContext.d.ts
CHANGED
|
@@ -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;
|
package/dist/requestContext.js
CHANGED
|
@@ -72,6 +72,31 @@ var __values = (this && this.__values) || function(o) {
|
|
|
72
72
|
};
|
|
73
73
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
74
74
|
exports.requestContextMiddleware = exports.updateRequestContextFromRequest = exports.runWithRequestContextAttributes = exports.runWithRequestContext = exports.getCurrentRequestContextAttributes = exports.setRequestContext = exports.applyRequestContextToSentry = exports.getCurrentLogContext = exports.getCurrentRequestContext = exports.getRequestContextFromAttributes = exports.getSessionIdFromJwtPayload = exports.REQUEST_CONTEXT_ATTRIBUTE_NAMES = void 0;
|
|
75
|
+
/**
|
|
76
|
+
* Request/job correlation for `@terreno/api`.
|
|
77
|
+
*
|
|
78
|
+
* Correlation is how every log line emitted while handling one request (or one background job) can
|
|
79
|
+
* be tied back together. It is built on Node's {@link AsyncLocalStorage}: a {@link RequestContext}
|
|
80
|
+
* (with `requestId`, `userId`, `traceId`, etc.) is stored for the duration of a callback, and the
|
|
81
|
+
* logger's Winston format reads it from there and merges it into each line. Nothing needs to be
|
|
82
|
+
* threaded through function arguments.
|
|
83
|
+
*
|
|
84
|
+
* Two ways a scope is established:
|
|
85
|
+
*
|
|
86
|
+
* - **HTTP**: {@link requestContextMiddleware} runs first in the middleware stack. It derives a
|
|
87
|
+
* `requestId` from incoming headers ({@link REQUEST_CONTEXT_ATTRIBUTE_NAMES}, `x-correlation-id`,
|
|
88
|
+
* Cloud Trace, or W3C `traceparent`) or generates one, echoes it back as `X-Request-ID`, and runs
|
|
89
|
+
* the rest of the request inside the scope.
|
|
90
|
+
* - **Jobs/scripts**: {@link runWithRequestContext} (or {@link runWithRequestContextAttributes})
|
|
91
|
+
* establishes the same scope manually so background work is just as traceable.
|
|
92
|
+
*
|
|
93
|
+
* The active context is also pushed to Sentry tags/context via {@link applyRequestContextToSentry},
|
|
94
|
+
* and is exposed to logging via {@link getCurrentLogContext} / {@link getCurrentRequestContext}.
|
|
95
|
+
*
|
|
96
|
+
* @see {@link runWithRequestContext}
|
|
97
|
+
* @see {@link getCurrentLogContext}
|
|
98
|
+
* @module requestContext
|
|
99
|
+
*/
|
|
75
100
|
var node_async_hooks_1 = require("node:async_hooks");
|
|
76
101
|
var node_crypto_1 = require("node:crypto");
|
|
77
102
|
var Sentry = __importStar(require("@sentry/bun"));
|
|
@@ -84,6 +109,11 @@ var TRACE_ID_HEADER = "x-trace-id";
|
|
|
84
109
|
var TRACE_PARENT_HEADER = "traceparent";
|
|
85
110
|
var TRACE_SAMPLED_HEADER = "x-trace-sampled";
|
|
86
111
|
var USER_ID_HEADER = "x-user-id";
|
|
112
|
+
/**
|
|
113
|
+
* Canonical HTTP header names for each correlation field. Use these to propagate context to
|
|
114
|
+
* downstream services (pair with {@link getCurrentRequestContextAttributes}) or to read it from an
|
|
115
|
+
* incoming request (pair with {@link getRequestContextFromAttributes}).
|
|
116
|
+
*/
|
|
87
117
|
exports.REQUEST_CONTEXT_ATTRIBUTE_NAMES = {
|
|
88
118
|
jobId: JOB_ID_HEADER,
|
|
89
119
|
requestId: "x-request-id",
|
|
@@ -193,10 +223,19 @@ var getRequestContextFromAttributes = function (attributes) {
|
|
|
193
223
|
};
|
|
194
224
|
};
|
|
195
225
|
exports.getRequestContextFromAttributes = getRequestContextFromAttributes;
|
|
226
|
+
/**
|
|
227
|
+
* Returns the full {@link RequestContext} for the active AsyncLocalStorage scope, or `undefined`
|
|
228
|
+
* when called outside any request/job scope. The logger uses this to enrich each line.
|
|
229
|
+
*/
|
|
196
230
|
var getCurrentRequestContext = function () {
|
|
197
231
|
return requestContextStorage.getStore();
|
|
198
232
|
};
|
|
199
233
|
exports.getCurrentRequestContext = getCurrentRequestContext;
|
|
234
|
+
/**
|
|
235
|
+
* Returns the active correlation fields as a plain object (empty when outside a scope). This is the
|
|
236
|
+
* shape attached to Sentry log attributes and is handy when you need to log or forward the current
|
|
237
|
+
* context yourself.
|
|
238
|
+
*/
|
|
200
239
|
var getCurrentLogContext = function () {
|
|
201
240
|
var context = (0, exports.getCurrentRequestContext)();
|
|
202
241
|
if (!context) {
|
|
@@ -266,6 +305,11 @@ var setAttribute = function (attributes, name, value) {
|
|
|
266
305
|
}
|
|
267
306
|
attributes[name] = String(value);
|
|
268
307
|
};
|
|
308
|
+
/**
|
|
309
|
+
* Serializes the active correlation context into HTTP header attributes (keyed by
|
|
310
|
+
* {@link REQUEST_CONTEXT_ATTRIBUTE_NAMES}) so it can be propagated on outbound requests to other
|
|
311
|
+
* services, keeping the same `requestId`/`traceId` across service boundaries.
|
|
312
|
+
*/
|
|
269
313
|
var getCurrentRequestContextAttributes = function (overrides) {
|
|
270
314
|
if (overrides === void 0) { overrides = {}; }
|
|
271
315
|
var context = __assign(__assign({}, (0, exports.getCurrentLogContext)()), overrides);
|
|
@@ -280,6 +324,23 @@ var getCurrentRequestContextAttributes = function (overrides) {
|
|
|
280
324
|
return attributes;
|
|
281
325
|
};
|
|
282
326
|
exports.getCurrentRequestContextAttributes = getCurrentRequestContextAttributes;
|
|
327
|
+
/**
|
|
328
|
+
* Runs `callback` inside a fresh correlation scope so every log line it emits shares the same
|
|
329
|
+
* identifiers — the manual equivalent of {@link requestContextMiddleware} for background jobs,
|
|
330
|
+
* cron tasks, scripts, queue consumers, etc. A `requestId` is generated when not supplied, and the
|
|
331
|
+
* context is mirrored to Sentry.
|
|
332
|
+
*
|
|
333
|
+
* @example
|
|
334
|
+
* ```typescript
|
|
335
|
+
* import {createScopedLogger, runWithRequestContext} from "@terreno/api";
|
|
336
|
+
*
|
|
337
|
+
* await runWithRequestContext({jobId: "nightly-sync"}, async () => {
|
|
338
|
+
* const log = createScopedLogger({prefix: "[NightlySync]"});
|
|
339
|
+
* log.info("started"); // includes jobId + a generated requestId on every line
|
|
340
|
+
* await sync();
|
|
341
|
+
* });
|
|
342
|
+
* ```
|
|
343
|
+
*/
|
|
283
344
|
var runWithRequestContext = function (context, callback) {
|
|
284
345
|
var _a;
|
|
285
346
|
var nextContext = __assign(__assign({}, context), { requestId: (_a = context.requestId) !== null && _a !== void 0 ? _a : (0, node_crypto_1.randomUUID)() });
|
|
@@ -289,6 +350,11 @@ var runWithRequestContext = function (context, callback) {
|
|
|
289
350
|
});
|
|
290
351
|
};
|
|
291
352
|
exports.runWithRequestContext = runWithRequestContext;
|
|
353
|
+
/**
|
|
354
|
+
* Like {@link runWithRequestContext}, but seeds the scope from raw header attributes (for example
|
|
355
|
+
* those received on an incoming message or forwarded by another service). Parses Cloud Trace / W3C
|
|
356
|
+
* `traceparent` into `traceId`/`spanId` via {@link getRequestContextFromAttributes}.
|
|
357
|
+
*/
|
|
292
358
|
var runWithRequestContextAttributes = function (attributes, callback) {
|
|
293
359
|
if (attributes === void 0) { attributes = {}; }
|
|
294
360
|
return (0, exports.runWithRequestContext)((0, exports.getRequestContextFromAttributes)(attributes), callback);
|
|
@@ -312,6 +378,14 @@ var updateRequestContextFromRequest = function (req, res) {
|
|
|
312
378
|
}
|
|
313
379
|
};
|
|
314
380
|
exports.updateRequestContextFromRequest = updateRequestContextFromRequest;
|
|
381
|
+
/**
|
|
382
|
+
* Express middleware that opens a correlation scope for the request. Mounted early by `TerrenoApp` /
|
|
383
|
+
* `setupServer`, it resolves a `requestId` (from request-id/correlation headers, Cloud Trace, or
|
|
384
|
+
* W3C `traceparent`, else a new UUID), captures any `jobId`/`sessionId`/trace fields, echoes
|
|
385
|
+
* `X-Request-ID` back to the client, and runs the remaining middleware inside the scope so all
|
|
386
|
+
* downstream logs are correlated. A later auth-aware pass ({@link updateRequestContextFromRequest})
|
|
387
|
+
* fills in `userId`/`sessionId`.
|
|
388
|
+
*/
|
|
315
389
|
var requestContextMiddleware = function (req, res, next) {
|
|
316
390
|
var cloudTraceContext = parseGoogleCloudTraceContext(getHeader(req, CLOUD_TRACE_CONTEXT_HEADER));
|
|
317
391
|
var traceParentContext = parseTraceParent(getHeader(req, TRACE_PARENT_HEADER));
|