@geekmidas/services 10.0.0-alpha.16 → 10.0.0-alpha.161
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/README.md +10 -0
- package/dist/{ServiceDiscovery-I7GKGDgi.d.mts → ServiceDiscovery-D8OKCBzy.d.mts} +2 -2
- package/dist/{ServiceDiscovery-I7GKGDgi.d.mts.map → ServiceDiscovery-D8OKCBzy.d.mts.map} +1 -1
- package/dist/{ServiceDiscovery-YbZxbSHi.d.cts → ServiceDiscovery-zMTNKupf.d.cts} +2 -2
- package/dist/{ServiceDiscovery-YbZxbSHi.d.cts.map → ServiceDiscovery-zMTNKupf.d.cts.map} +1 -1
- package/dist/context.cjs +51 -0
- package/dist/context.cjs.map +1 -1
- package/dist/context.d.cts +40 -1
- package/dist/context.d.cts.map +1 -1
- package/dist/context.d.mts +40 -1
- package/dist/context.d.mts.map +1 -1
- package/dist/context.mjs +50 -1
- package/dist/context.mjs.map +1 -1
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +4 -4
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +2 -2
- package/dist/middy.d.cts +2 -2
- package/dist/middy.d.mts +2 -2
- package/dist/trpc.d.cts +2 -2
- package/dist/trpc.d.mts +2 -2
- package/dist/{types-DDxt5Kcu.d.cts → types-BJldyZ-B.d.cts} +20 -2
- package/dist/types-BJldyZ-B.d.cts.map +1 -0
- package/dist/{types-DDxt5Kcu.d.mts → types-BJldyZ-B.d.mts} +20 -2
- package/dist/types-BJldyZ-B.d.mts.map +1 -0
- package/package.json +7 -6
- package/dist/types-DDxt5Kcu.d.cts.map +0 -1
- package/dist/types-DDxt5Kcu.d.mts.map +0 -1
package/README.md
CHANGED
|
@@ -529,6 +529,16 @@ async register(envParser: EnvironmentParser<{}>) {
|
|
|
529
529
|
- [@geekmidas/cache](../cache) - Cache services
|
|
530
530
|
- [@geekmidas/events](../events) - Event publisher services
|
|
531
531
|
|
|
532
|
+
## Optional peers
|
|
533
|
+
|
|
534
|
+
<!-- optional-peers:start -->
|
|
535
|
+
|
|
536
|
+
<!-- Generated by `node scripts/check-optional-peers.mjs --write-docs`; CI fails when it drifts. -->
|
|
537
|
+
|
|
538
|
+
No entry imports an optional peer when it loads. `@geekmidas/envkit`, `@geekmidas/logger`, `@middy/core`, `@trpc/server`, `@types/aws-lambda` are loaded only when a feature that uses one runs (an `import()` inside a `try`, whose error names the package to install), or named only in types.
|
|
539
|
+
|
|
540
|
+
<!-- optional-peers:end -->
|
|
541
|
+
|
|
532
542
|
## License
|
|
533
543
|
|
|
534
544
|
MIT
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as Service } from "./types-BJldyZ-B.mjs";
|
|
2
2
|
import { EnvironmentParser } from "@geekmidas/envkit";
|
|
3
3
|
//#region src/ServiceDiscovery.d.ts
|
|
4
4
|
/**
|
|
@@ -181,4 +181,4 @@ type ExtractServiceNames<T extends Service[]> = T[number]['serviceName'];
|
|
|
181
181
|
type ServiceRecord<T extends Service[]> = { [K in T[number] as K['serviceName']]: K extends Service ? Awaited<ReturnType<K['register']>> : never; };
|
|
182
182
|
//#endregion
|
|
183
183
|
export { ServiceDiscovery as n, ServiceRecord as r, ExtractServiceNames as t };
|
|
184
|
-
//# sourceMappingURL=ServiceDiscovery-
|
|
184
|
+
//# sourceMappingURL=ServiceDiscovery-D8OKCBzy.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ServiceDiscovery-
|
|
1
|
+
{"version":3,"file":"ServiceDiscovery-D8OKCBzy.d.mts","names":[],"sources":["../src/ServiceDiscovery.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAkCa,iBAAiB,kBAAkB;WAkD1B,WAAW;;iBAhDjB;;UAEP;;UAEA;;;;;;;;;;;;;;SAeD,YAAY,UAAU,4BAC5B,WAAW,wBACT,iBAAiB;;;;;;;;;;;;;SAmBb;;;;;EAQP,YAAqB,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;EA0B1B,SAAS,UAAU,WAAW,UAAU,IAAI,QAAQ,cAAc;;;;;;;;;;;;;;;;EAwCxE,IAAI,gBAAgB,oBAAoB,MAAM,IAAI,QAAQ,UAAU;;;;;;;;;;;;;;;;;;EA6B9D,QAAQ,iBAAiB,uBAC9B,WAAW,KACT,WAAW,KAAK,YAAY,UAAU;;;;;;;;;;;;;;;;;;;;EA6BzC,IAAI,kBAAkB;;;;;;;;;;;;;KAoBX,oBAAoB,UAAU,aAAa;;;;;;;;;;;;;;;;KAiB3C,cAAc,UAAU,gBAClC,KAAK,aAAa,mBAAmB,UAAU,UAC7C,QAAQ,WAAW"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as Service } from "./types-BJldyZ-B.cjs";
|
|
2
2
|
import { EnvironmentParser } from "@geekmidas/envkit";
|
|
3
3
|
//#region src/ServiceDiscovery.d.ts
|
|
4
4
|
/**
|
|
@@ -181,4 +181,4 @@ type ExtractServiceNames<T extends Service[]> = T[number]['serviceName'];
|
|
|
181
181
|
type ServiceRecord<T extends Service[]> = { [K in T[number] as K['serviceName']]: K extends Service ? Awaited<ReturnType<K['register']>> : never; };
|
|
182
182
|
//#endregion
|
|
183
183
|
export { ServiceDiscovery as n, ServiceRecord as r, ExtractServiceNames as t };
|
|
184
|
-
//# sourceMappingURL=ServiceDiscovery-
|
|
184
|
+
//# sourceMappingURL=ServiceDiscovery-zMTNKupf.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ServiceDiscovery-
|
|
1
|
+
{"version":3,"file":"ServiceDiscovery-zMTNKupf.d.cts","names":[],"sources":["../src/ServiceDiscovery.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAkCa,iBAAiB,kBAAkB;WAkD1B,WAAW;;iBAhDjB;;UAEP;;UAEA;;;;;;;;;;;;;;SAeD,YAAY,UAAU,4BAC5B,WAAW,wBACT,iBAAiB;;;;;;;;;;;;;SAmBb;;;;;EAQP,YAAqB,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;EA0B1B,SAAS,UAAU,WAAW,UAAU,IAAI,QAAQ,cAAc;;;;;;;;;;;;;;;;EAwCxE,IAAI,gBAAgB,oBAAoB,MAAM,IAAI,QAAQ,UAAU;;;;;;;;;;;;;;;;;;EA6B9D,QAAQ,iBAAiB,uBAC9B,WAAW,KACT,WAAW,KAAK,YAAY,UAAU;;;;;;;;;;;;;;;;;;;;EA6BzC,IAAI,kBAAkB;;;;;;;;;;;;;KAoBX,oBAAoB,UAAU,aAAa;;;;;;;;;;;;;;;;KAiB3C,cAAc,UAAU,gBAClC,KAAK,aAAa,mBAAmB,UAAU,UAC7C,QAAQ,WAAW"}
|
package/dist/context.cjs
CHANGED
|
@@ -89,9 +89,58 @@ const serviceContext = {
|
|
|
89
89
|
},
|
|
90
90
|
hasContext() {
|
|
91
91
|
return requestContextStorage.getStore() !== void 0;
|
|
92
|
+
},
|
|
93
|
+
getUser() {
|
|
94
|
+
return requestContextStorage.getStore()?.user;
|
|
92
95
|
}
|
|
93
96
|
};
|
|
94
97
|
/**
|
|
98
|
+
* Say who the current request is for, once its session was verified.
|
|
99
|
+
*
|
|
100
|
+
* Kept on the request's own context, so everything that runs for it from
|
|
101
|
+
* here on — the handler, every service, a query, an event it publishes, work
|
|
102
|
+
* it awaits after any number of hops — reads the same user, and no other
|
|
103
|
+
* request ever does. Its logger is rebound with `userId` (and
|
|
104
|
+
* `sessionId`): every line logged for the request after this, through the
|
|
105
|
+
* logger a handler is handed or one a service captured, says whose it was.
|
|
106
|
+
*
|
|
107
|
+
* Identifying again with another user replaces the first — the bindings are
|
|
108
|
+
* applied to the request's logger as it was before any identification, so a
|
|
109
|
+
* line never carries two users.
|
|
110
|
+
*
|
|
111
|
+
* Outside a request there is nothing to identify, and it does nothing: a
|
|
112
|
+
* session verified from a script is still verified.
|
|
113
|
+
*
|
|
114
|
+
* @returns whether there was a request to identify
|
|
115
|
+
*/
|
|
116
|
+
function identifyRequest(user) {
|
|
117
|
+
const store = requestContextStorage.getStore();
|
|
118
|
+
if (!store) return false;
|
|
119
|
+
const anonymous = anonymousLoggers.get(store) ?? store.logger;
|
|
120
|
+
anonymousLoggers.set(store, anonymous);
|
|
121
|
+
store.user = user;
|
|
122
|
+
store.logger = anonymous.child({
|
|
123
|
+
userId: user.id,
|
|
124
|
+
...user.sessionId ? { sessionId: user.sessionId } : {}
|
|
125
|
+
});
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Each request's logger as it was before {@link identifyRequest} rebound it,
|
|
130
|
+
* so identifying again binds to that rather than stacking a second user.
|
|
131
|
+
*/
|
|
132
|
+
const anonymousLoggers = /* @__PURE__ */ new WeakMap();
|
|
133
|
+
/**
|
|
134
|
+
* The current request's context, or undefined outside one.
|
|
135
|
+
*
|
|
136
|
+
* For code that annotates work when there is a request and carries on when
|
|
137
|
+
* there is not — tagging a query, say — where {@link serviceContext}'s
|
|
138
|
+
* accessors would throw.
|
|
139
|
+
*/
|
|
140
|
+
function currentRequestContext() {
|
|
141
|
+
return requestContextStorage.getStore();
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
95
144
|
* Run a function with request context.
|
|
96
145
|
* Used by endpoint/function/subscriber adaptors.
|
|
97
146
|
*
|
|
@@ -138,8 +187,10 @@ function exitRequestContext() {
|
|
|
138
187
|
requestContextStorage.enterWith(void 0);
|
|
139
188
|
}
|
|
140
189
|
//#endregion
|
|
190
|
+
exports.currentRequestContext = currentRequestContext;
|
|
141
191
|
exports.enterRequestContext = enterRequestContext;
|
|
142
192
|
exports.exitRequestContext = exitRequestContext;
|
|
193
|
+
exports.identifyRequest = identifyRequest;
|
|
143
194
|
exports.runWithRequestContext = runWithRequestContext;
|
|
144
195
|
exports.serviceContext = serviceContext;
|
|
145
196
|
|
package/dist/context.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.cjs","names":["AsyncLocalStorage"],"sources":["../src/context.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport type { Logger } from '@geekmidas/logger';\nimport type { ServiceContext } from './types';\n\n/**\n * Internal storage for request context data.\n * Not exported - services use ServiceContext interface.\n */\nexport interface RequestContextData {\n\tlogger: Logger;\n\trequestId: string;\n\tstartTime: number;\n}\n\n/**\n * Internal AsyncLocalStorage instance for request context.\n * Not exported - use runWithRequestContext() to establish context\n * and serviceContext to access it.\n */\nconst requestContextStorage = new AsyncLocalStorage<RequestContextData>();\n\n/**\n * Resolve the logger for the current request, or throw if there is none.\n */\nfunction resolveRequestLogger(): Logger {\n\tconst store = requestContextStorage.getStore();\n\tif (!store) {\n\t\tthrow new Error(\n\t\t\t'ServiceContext.getLogger() called outside request context. ' +\n\t\t\t\t'Ensure code runs within runWithRequestContext().',\n\t\t);\n\t}\n\treturn store.logger;\n}\n\n/**\n * Create a Logger that re-resolves its underlying logger on every call instead\n * of capturing it once.\n *\n * This is what makes it safe for a **singleton** service to grab the logger a\n * single time (e.g. during `register()`, which `ServiceDiscovery` only runs\n * once and then caches) and reuse that reference for every request: each log\n * call resolves the *current* request's logger from `AsyncLocalStorage`, so\n * requests no longer inherit the first request's logger (and its `requestId`,\n * user bindings, etc.).\n *\n * Implemented as a `Proxy` rather than a fixed list of methods so it forwards\n * the *entire* surface of whatever logger is supplied — including members\n * beyond the base `Logger` interface (e.g. a richer pino-backed logger's\n * `flush()` or `level`) and any methods added to `Logger` in the future.\n *\n * @param bindings - `child()` bindings applied, in order, on top of the\n * resolved logger before each call.\n */\nfunction createRequestScopedLogger(bindings: object[] = []): Logger {\n\t// Memoise the resolved (optionally child) logger per underlying base logger\n\t// so we don't rebuild the child chain on every access within a request.\n\t// Recomputed whenever the current request's logger changes — there is no\n\t// await between the check and use, so this is safe under concurrency.\n\tlet cachedBase: Logger | undefined;\n\tlet cachedResolved: Logger | undefined;\n\n\tconst resolve = (): Logger => {\n\t\tconst base = resolveRequestLogger();\n\t\tif (base !== cachedBase) {\n\t\t\tcachedBase = base;\n\t\t\tcachedResolved = bindings.reduce<Logger>(\n\t\t\t\t(log, obj) => log.child(obj),\n\t\t\t\tbase,\n\t\t\t);\n\t\t}\n\t\treturn cachedResolved as Logger;\n\t};\n\n\treturn new Proxy({} as Logger, {\n\t\tget(_target, prop) {\n\t\t\t// `child()` must stay request-scoped: return a new proxy carrying the\n\t\t\t// extra binding, NOT the underlying logger's child (which would freeze\n\t\t\t// to the current request).\n\t\t\tif (prop === 'child') {\n\t\t\t\treturn (obj: object) => createRequestScopedLogger([...bindings, obj]);\n\t\t\t}\n\t\t\t// Never look like a thenable, and don't answer symbol/inspection probes\n\t\t\t// (util.inspect, Symbol.toPrimitive, etc.) with bound functions.\n\t\t\tif (prop === 'then' || typeof prop === 'symbol') {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\tconst value = (resolve() as unknown as Record<string, unknown>)[prop];\n\t\t\t// Functions are re-resolved at *call* time so detached references\n\t\t\t// (`const info = logger.info`) still target the current request's\n\t\t\t// logger. Non-function members (e.g. `level`) forward as their live\n\t\t\t// value on the current request's logger.\n\t\t\treturn typeof value === 'function'\n\t\t\t\t? (...args: unknown[]) => {\n\t\t\t\t\t\t// Re-resolve at call time so detached references target the\n\t\t\t\t\t\t// current request's logger, and invoke with that logger as\n\t\t\t\t\t\t// `this`: pino's log methods read internal state off the\n\t\t\t\t\t\t// receiver (`this[Symbol(pino.msgPrefix)]`), so calling them\n\t\t\t\t\t\t// unbound throws \"Cannot read properties of undefined\".\n\t\t\t\t\t\tconst resolved = resolve();\n\t\t\t\t\t\tconst fn = (resolved as unknown as Record<string, unknown>)[\n\t\t\t\t\t\t\tprop\n\t\t\t\t\t\t] as (...a: unknown[]) => unknown;\n\t\t\t\t\t\treturn fn.apply(resolved, args);\n\t\t\t\t\t}\n\t\t\t\t: value;\n\t\t},\n\t\t// Keep `'prop' in logger` / hasOwnProperty truthful against the underlying\n\t\t// logger so feature-detection works.\n\t\thas(_target, prop) {\n\t\t\tif (prop === 'child') return true;\n\t\t\tif (prop === 'then' || typeof prop === 'symbol') return false;\n\t\t\treturn prop in (resolve() as object);\n\t\t},\n\t});\n}\n\n/**\n * Stable, process-wide request-scoped logger proxy. Shared across requests on\n * purpose — it carries no request state itself, delegating to the current\n * `AsyncLocalStorage` store on each call.\n */\nconst requestScopedLogger = createRequestScopedLogger();\n\n/**\n * ServiceContext implementation.\n * Singleton that reads from AsyncLocalStorage.\n * Methods throw if called outside a request context (catches bugs early).\n */\nexport const serviceContext: ServiceContext = {\n\tgetLogger() {\n\t\t// Throw eagerly if there is no context, preserving the \"catch bugs early\"\n\t\t// contract for callers that read the logger at an unexpected time.\n\t\tresolveRequestLogger();\n\t\t// Return the shared proxy rather than the raw `store.logger`. A service\n\t\t// that captures this once still logs against the correct per-request\n\t\t// logger because the proxy re-resolves on every call.\n\t\treturn requestScopedLogger;\n\t},\n\n\tgetRequestId() {\n\t\tconst store = requestContextStorage.getStore();\n\t\tif (!store) {\n\t\t\tthrow new Error(\n\t\t\t\t'ServiceContext.getRequestId() called outside request context. ' +\n\t\t\t\t\t'Ensure code runs within runWithRequestContext().',\n\t\t\t);\n\t\t}\n\t\treturn store.requestId;\n\t},\n\n\tgetRequestStartTime() {\n\t\tconst store = requestContextStorage.getStore();\n\t\tif (!store) {\n\t\t\tthrow new Error(\n\t\t\t\t'ServiceContext.getRequestStartTime() called outside request context. ' +\n\t\t\t\t\t'Ensure code runs within runWithRequestContext().',\n\t\t\t);\n\t\t}\n\t\treturn store.startTime;\n\t},\n\n\thasContext() {\n\t\treturn requestContextStorage.getStore() !== undefined;\n\t},\n};\n\n/**\n * Run a function with request context.\n * Used by endpoint/function/subscriber adaptors.\n *\n * @param data - Request context data (logger, requestId, startTime)\n * @param fn - Function to run with context\n * @returns Result of the function\n *\n * @example\n * ```typescript\n * const result = await runWithRequestContext(\n * { logger, requestId, startTime: Date.now() },\n * async () => {\n * // Inside here, serviceContext.getLogger() returns `logger`\n * // serviceContext.getRequestId() returns `requestId`\n * return await handleRequest();\n * }\n * );\n * ```\n */\nexport function runWithRequestContext<T>(\n\tdata: RequestContextData,\n\tfn: () => T | Promise<T>,\n): T | Promise<T> {\n\treturn requestContextStorage.run(data, fn);\n}\n\n/**\n * Mutate the current async task's store so that subsequent code in this task\n * (and any descendants) sees the supplied request context.\n *\n * Unlike `runWithRequestContext`, this does not scope the context to a\n * callback — useful when the caller can't wrap a function, for example in a\n * Vitest fixture that suspends on `use()` and yields control to the test\n * runner before the test body executes.\n *\n * **Test setup only.** In production handlers, prefer `runWithRequestContext`\n * so the frame is automatically cleaned up.\n */\nexport function enterRequestContext(data: RequestContextData): void {\n\trequestContextStorage.enterWith(data);\n}\n\n/**\n * Clear the request context for the current async task. Pairs with\n * `enterRequestContext`. After calling, `serviceContext.hasContext()` returns\n * false for the remainder of the current async resource.\n */\nexport function exitRequestContext(): void {\n\t// AsyncLocalStorage<T>.enterWith requires T, but Node accepts undefined at\n\t// runtime — passing it resets getStore() back to undefined.\n\t(requestContextStorage as unknown as AsyncLocalStorage<unknown>).enterWith(\n\t\tundefined,\n\t);\n}\n"],"mappings":";;;;;;;AAmBA,MAAM,wBAAwB,+BAAIA,EAAAA,CAAAA,kBAAsC;;;;AAKxE,SAAS,uBAA+B;CACvC,MAAM,QAAQ,sBAAsB,SAAS;CAC7C,IAAI,CAAC,OACJ,MAAM,IAAI,MACT,6GAED;CAED,OAAO,MAAM;AACd;;;;;;;;;;;;;;;;;;;;AAqBA,SAAS,0BAA0B,WAAqB,CAAC,GAAW;CAKnE,IAAI;CACJ,IAAI;CAEJ,MAAM,gBAAwB;EAC7B,MAAM,OAAO,qBAAqB;EAClC,IAAI,SAAS,YAAY;GACxB,aAAa;GACb,iBAAiB,SAAS,QACxB,KAAK,QAAQ,IAAI,MAAM,GAAG,GAC3B,IACD;EACD;EACA,OAAO;CACR;CAEA,OAAO,IAAI,MAAM,CAAC,GAAa;EAC9B,IAAI,SAAS,MAAM;GAIlB,IAAI,SAAS,SACZ,QAAQ,QAAgB,0BAA0B,CAAC,GAAG,UAAU,GAAG,CAAC;GAIrE,IAAI,SAAS,UAAU,OAAO,SAAS,UACtC;GAED,MAAM,QAAS,QAAQ,CAAC,CAAwC;GAKhE,OAAO,OAAO,UAAU,cACpB,GAAG,SAAoB;IAMxB,MAAM,WAAW,QAAQ;IAIzB,OAHY,SACX,KAEQ,CAAC,MAAM,UAAU,IAAI;GAC/B,IACC;EACJ;EAGA,IAAI,SAAS,MAAM;GAClB,IAAI,SAAS,SAAS,OAAO;GAC7B,IAAI,SAAS,UAAU,OAAO,SAAS,UAAU,OAAO;GACxD,OAAO,QAAS,QAAQ;EACzB;CACD,CAAC;AACF;;;;;;AAOA,MAAM,sBAAsB,0BAA0B;;;;;;AAOtD,MAAa,iBAAiC;CAC7C,YAAY;EAGX,qBAAqB;EAIrB,OAAO;CACR;CAEA,eAAe;EACd,MAAM,QAAQ,sBAAsB,SAAS;EAC7C,IAAI,CAAC,OACJ,MAAM,IAAI,MACT,gHAED;EAED,OAAO,MAAM;CACd;CAEA,sBAAsB;EACrB,MAAM,QAAQ,sBAAsB,SAAS;EAC7C,IAAI,CAAC,OACJ,MAAM,IAAI,MACT,uHAED;EAED,OAAO,MAAM;CACd;CAEA,aAAa;EACZ,OAAO,sBAAsB,SAAS,MAAM,KAAA;CAC7C;AACD;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,sBACf,MACA,IACiB;CACjB,OAAO,sBAAsB,IAAI,MAAM,EAAE;AAC1C;;;;;;;;;;;;;AAcA,SAAgB,oBAAoB,MAAgC;CACnE,sBAAsB,UAAU,IAAI;AACrC;;;;;;AAOA,SAAgB,qBAA2B;CAG1C,sBAAiE,UAChE,KAAA,CACD;AACD"}
|
|
1
|
+
{"version":3,"file":"context.cjs","names":["AsyncLocalStorage"],"sources":["../src/context.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport type { Logger } from '@geekmidas/logger';\nimport type { RequestUser, ServiceContext } from './types';\n\n/**\n * Internal storage for request context data.\n * Not exported - services use ServiceContext interface.\n */\nexport interface RequestContextData {\n\tlogger: Logger;\n\trequestId: string;\n\tstartTime: number;\n\t/**\n\t * What is running: `POST /orders`, `queue Emails`, `cron rate(1 day)`.\n\t * Carried so that work done on the request's behalf — a database query —\n\t * can say what it was done for.\n\t */\n\toperation?: string;\n\t/**\n\t * Who the request is for, once its session was verified. Set by\n\t * {@link identifyRequest}, which also rebinds {@link logger} with it.\n\t */\n\tuser?: RequestUser;\n}\n\n/**\n * Internal AsyncLocalStorage instance for request context.\n * Not exported - use runWithRequestContext() to establish context\n * and serviceContext to access it.\n */\nconst requestContextStorage = new AsyncLocalStorage<RequestContextData>();\n\n/**\n * Resolve the logger for the current request, or throw if there is none.\n */\nfunction resolveRequestLogger(): Logger {\n\tconst store = requestContextStorage.getStore();\n\tif (!store) {\n\t\tthrow new Error(\n\t\t\t'ServiceContext.getLogger() called outside request context. ' +\n\t\t\t\t'Ensure code runs within runWithRequestContext().',\n\t\t);\n\t}\n\treturn store.logger;\n}\n\n/**\n * Create a Logger that re-resolves its underlying logger on every call instead\n * of capturing it once.\n *\n * This is what makes it safe for a **singleton** service to grab the logger a\n * single time (e.g. during `register()`, which `ServiceDiscovery` only runs\n * once and then caches) and reuse that reference for every request: each log\n * call resolves the *current* request's logger from `AsyncLocalStorage`, so\n * requests no longer inherit the first request's logger (and its `requestId`,\n * user bindings, etc.).\n *\n * Implemented as a `Proxy` rather than a fixed list of methods so it forwards\n * the *entire* surface of whatever logger is supplied — including members\n * beyond the base `Logger` interface (e.g. a richer pino-backed logger's\n * `flush()` or `level`) and any methods added to `Logger` in the future.\n *\n * @param bindings - `child()` bindings applied, in order, on top of the\n * resolved logger before each call.\n */\nfunction createRequestScopedLogger(bindings: object[] = []): Logger {\n\t// Memoise the resolved (optionally child) logger per underlying base logger\n\t// so we don't rebuild the child chain on every access within a request.\n\t// Recomputed whenever the current request's logger changes — there is no\n\t// await between the check and use, so this is safe under concurrency.\n\tlet cachedBase: Logger | undefined;\n\tlet cachedResolved: Logger | undefined;\n\n\tconst resolve = (): Logger => {\n\t\tconst base = resolveRequestLogger();\n\t\tif (base !== cachedBase) {\n\t\t\tcachedBase = base;\n\t\t\tcachedResolved = bindings.reduce<Logger>(\n\t\t\t\t(log, obj) => log.child(obj),\n\t\t\t\tbase,\n\t\t\t);\n\t\t}\n\t\treturn cachedResolved as Logger;\n\t};\n\n\treturn new Proxy({} as Logger, {\n\t\tget(_target, prop) {\n\t\t\t// `child()` must stay request-scoped: return a new proxy carrying the\n\t\t\t// extra binding, NOT the underlying logger's child (which would freeze\n\t\t\t// to the current request).\n\t\t\tif (prop === 'child') {\n\t\t\t\treturn (obj: object) => createRequestScopedLogger([...bindings, obj]);\n\t\t\t}\n\t\t\t// Never look like a thenable, and don't answer symbol/inspection probes\n\t\t\t// (util.inspect, Symbol.toPrimitive, etc.) with bound functions.\n\t\t\tif (prop === 'then' || typeof prop === 'symbol') {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\tconst value = (resolve() as unknown as Record<string, unknown>)[prop];\n\t\t\t// Functions are re-resolved at *call* time so detached references\n\t\t\t// (`const info = logger.info`) still target the current request's\n\t\t\t// logger. Non-function members (e.g. `level`) forward as their live\n\t\t\t// value on the current request's logger.\n\t\t\treturn typeof value === 'function'\n\t\t\t\t? (...args: unknown[]) => {\n\t\t\t\t\t\t// Re-resolve at call time so detached references target the\n\t\t\t\t\t\t// current request's logger, and invoke with that logger as\n\t\t\t\t\t\t// `this`: pino's log methods read internal state off the\n\t\t\t\t\t\t// receiver (`this[Symbol(pino.msgPrefix)]`), so calling them\n\t\t\t\t\t\t// unbound throws \"Cannot read properties of undefined\".\n\t\t\t\t\t\tconst resolved = resolve();\n\t\t\t\t\t\tconst fn = (resolved as unknown as Record<string, unknown>)[\n\t\t\t\t\t\t\tprop\n\t\t\t\t\t\t] as (...a: unknown[]) => unknown;\n\t\t\t\t\t\treturn fn.apply(resolved, args);\n\t\t\t\t\t}\n\t\t\t\t: value;\n\t\t},\n\t\t// Keep `'prop' in logger` / hasOwnProperty truthful against the underlying\n\t\t// logger so feature-detection works.\n\t\thas(_target, prop) {\n\t\t\tif (prop === 'child') return true;\n\t\t\tif (prop === 'then' || typeof prop === 'symbol') return false;\n\t\t\treturn prop in (resolve() as object);\n\t\t},\n\t});\n}\n\n/**\n * Stable, process-wide request-scoped logger proxy. Shared across requests on\n * purpose — it carries no request state itself, delegating to the current\n * `AsyncLocalStorage` store on each call.\n */\nconst requestScopedLogger = createRequestScopedLogger();\n\n/**\n * ServiceContext implementation.\n * Singleton that reads from AsyncLocalStorage.\n * Methods throw if called outside a request context (catches bugs early).\n */\nexport const serviceContext: ServiceContext = {\n\tgetLogger() {\n\t\t// Throw eagerly if there is no context, preserving the \"catch bugs early\"\n\t\t// contract for callers that read the logger at an unexpected time.\n\t\tresolveRequestLogger();\n\t\t// Return the shared proxy rather than the raw `store.logger`. A service\n\t\t// that captures this once still logs against the correct per-request\n\t\t// logger because the proxy re-resolves on every call.\n\t\treturn requestScopedLogger;\n\t},\n\n\tgetRequestId() {\n\t\tconst store = requestContextStorage.getStore();\n\t\tif (!store) {\n\t\t\tthrow new Error(\n\t\t\t\t'ServiceContext.getRequestId() called outside request context. ' +\n\t\t\t\t\t'Ensure code runs within runWithRequestContext().',\n\t\t\t);\n\t\t}\n\t\treturn store.requestId;\n\t},\n\n\tgetRequestStartTime() {\n\t\tconst store = requestContextStorage.getStore();\n\t\tif (!store) {\n\t\t\tthrow new Error(\n\t\t\t\t'ServiceContext.getRequestStartTime() called outside request context. ' +\n\t\t\t\t\t'Ensure code runs within runWithRequestContext().',\n\t\t\t);\n\t\t}\n\t\treturn store.startTime;\n\t},\n\n\thasContext() {\n\t\treturn requestContextStorage.getStore() !== undefined;\n\t},\n\n\tgetUser() {\n\t\treturn requestContextStorage.getStore()?.user;\n\t},\n};\n\n/**\n * Say who the current request is for, once its session was verified.\n *\n * Kept on the request's own context, so everything that runs for it from\n * here on — the handler, every service, a query, an event it publishes, work\n * it awaits after any number of hops — reads the same user, and no other\n * request ever does. Its logger is rebound with `userId` (and\n * `sessionId`): every line logged for the request after this, through the\n * logger a handler is handed or one a service captured, says whose it was.\n *\n * Identifying again with another user replaces the first — the bindings are\n * applied to the request's logger as it was before any identification, so a\n * line never carries two users.\n *\n * Outside a request there is nothing to identify, and it does nothing: a\n * session verified from a script is still verified.\n *\n * @returns whether there was a request to identify\n */\nexport function identifyRequest(user: RequestUser): boolean {\n\tconst store = requestContextStorage.getStore();\n\tif (!store) return false;\n\n\tconst anonymous = anonymousLoggers.get(store) ?? store.logger;\n\tanonymousLoggers.set(store, anonymous);\n\n\tstore.user = user;\n\tstore.logger = anonymous.child({\n\t\tuserId: user.id,\n\t\t...(user.sessionId ? { sessionId: user.sessionId } : {}),\n\t});\n\treturn true;\n}\n\n/**\n * Each request's logger as it was before {@link identifyRequest} rebound it,\n * so identifying again binds to that rather than stacking a second user.\n */\nconst anonymousLoggers = new WeakMap<RequestContextData, Logger>();\n\n/**\n * The current request's context, or undefined outside one.\n *\n * For code that annotates work when there is a request and carries on when\n * there is not — tagging a query, say — where {@link serviceContext}'s\n * accessors would throw.\n */\nexport function currentRequestContext():\n\t| Readonly<RequestContextData>\n\t| undefined {\n\treturn requestContextStorage.getStore();\n}\n\n/**\n * Run a function with request context.\n * Used by endpoint/function/subscriber adaptors.\n *\n * @param data - Request context data (logger, requestId, startTime)\n * @param fn - Function to run with context\n * @returns Result of the function\n *\n * @example\n * ```typescript\n * const result = await runWithRequestContext(\n * { logger, requestId, startTime: Date.now() },\n * async () => {\n * // Inside here, serviceContext.getLogger() returns `logger`\n * // serviceContext.getRequestId() returns `requestId`\n * return await handleRequest();\n * }\n * );\n * ```\n */\nexport function runWithRequestContext<T>(\n\tdata: RequestContextData,\n\tfn: () => T | Promise<T>,\n): T | Promise<T> {\n\treturn requestContextStorage.run(data, fn);\n}\n\n/**\n * Mutate the current async task's store so that subsequent code in this task\n * (and any descendants) sees the supplied request context.\n *\n * Unlike `runWithRequestContext`, this does not scope the context to a\n * callback — useful when the caller can't wrap a function, for example in a\n * Vitest fixture that suspends on `use()` and yields control to the test\n * runner before the test body executes.\n *\n * **Test setup only.** In production handlers, prefer `runWithRequestContext`\n * so the frame is automatically cleaned up.\n */\nexport function enterRequestContext(data: RequestContextData): void {\n\trequestContextStorage.enterWith(data);\n}\n\n/**\n * Clear the request context for the current async task. Pairs with\n * `enterRequestContext`. After calling, `serviceContext.hasContext()` returns\n * false for the remainder of the current async resource.\n */\nexport function exitRequestContext(): void {\n\t// AsyncLocalStorage<T>.enterWith requires T, but Node accepts undefined at\n\t// runtime — passing it resets getStore() back to undefined.\n\t(requestContextStorage as unknown as AsyncLocalStorage<unknown>).enterWith(\n\t\tundefined,\n\t);\n}\n"],"mappings":";;;;;;;AA8BA,MAAM,wBAAwB,+BAAIA,EAAAA,CAAAA,kBAAsC;;;;AAKxE,SAAS,uBAA+B;CACvC,MAAM,QAAQ,sBAAsB,SAAS;CAC7C,IAAI,CAAC,OACJ,MAAM,IAAI,MACT,6GAED;CAED,OAAO,MAAM;AACd;;;;;;;;;;;;;;;;;;;;AAqBA,SAAS,0BAA0B,WAAqB,CAAC,GAAW;CAKnE,IAAI;CACJ,IAAI;CAEJ,MAAM,gBAAwB;EAC7B,MAAM,OAAO,qBAAqB;EAClC,IAAI,SAAS,YAAY;GACxB,aAAa;GACb,iBAAiB,SAAS,QACxB,KAAK,QAAQ,IAAI,MAAM,GAAG,GAC3B,IACD;EACD;EACA,OAAO;CACR;CAEA,OAAO,IAAI,MAAM,CAAC,GAAa;EAC9B,IAAI,SAAS,MAAM;GAIlB,IAAI,SAAS,SACZ,QAAQ,QAAgB,0BAA0B,CAAC,GAAG,UAAU,GAAG,CAAC;GAIrE,IAAI,SAAS,UAAU,OAAO,SAAS,UACtC;GAED,MAAM,QAAS,QAAQ,CAAC,CAAwC;GAKhE,OAAO,OAAO,UAAU,cACpB,GAAG,SAAoB;IAMxB,MAAM,WAAW,QAAQ;IAIzB,OAHY,SACX,KAEQ,CAAC,MAAM,UAAU,IAAI;GAC/B,IACC;EACJ;EAGA,IAAI,SAAS,MAAM;GAClB,IAAI,SAAS,SAAS,OAAO;GAC7B,IAAI,SAAS,UAAU,OAAO,SAAS,UAAU,OAAO;GACxD,OAAO,QAAS,QAAQ;EACzB;CACD,CAAC;AACF;;;;;;AAOA,MAAM,sBAAsB,0BAA0B;;;;;;AAOtD,MAAa,iBAAiC;CAC7C,YAAY;EAGX,qBAAqB;EAIrB,OAAO;CACR;CAEA,eAAe;EACd,MAAM,QAAQ,sBAAsB,SAAS;EAC7C,IAAI,CAAC,OACJ,MAAM,IAAI,MACT,gHAED;EAED,OAAO,MAAM;CACd;CAEA,sBAAsB;EACrB,MAAM,QAAQ,sBAAsB,SAAS;EAC7C,IAAI,CAAC,OACJ,MAAM,IAAI,MACT,uHAED;EAED,OAAO,MAAM;CACd;CAEA,aAAa;EACZ,OAAO,sBAAsB,SAAS,MAAM,KAAA;CAC7C;CAEA,UAAU;EACT,OAAO,sBAAsB,SAAS,CAAC,EAAE;CAC1C;AACD;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,gBAAgB,MAA4B;CAC3D,MAAM,QAAQ,sBAAsB,SAAS;CAC7C,IAAI,CAAC,OAAO,OAAO;CAEnB,MAAM,YAAY,iBAAiB,IAAI,KAAK,KAAK,MAAM;CACvD,iBAAiB,IAAI,OAAO,SAAS;CAErC,MAAM,OAAO;CACb,MAAM,SAAS,UAAU,MAAM;EAC9B,QAAQ,KAAK;EACb,GAAI,KAAK,YAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;CACvD,CAAC;CACD,OAAO;AACR;;;;;AAMA,MAAM,mCAAmB,IAAI,QAAoC;;;;;;;;AASjE,SAAgB,wBAEH;CACZ,OAAO,sBAAsB,SAAS;AACvC;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,sBACf,MACA,IACiB;CACjB,OAAO,sBAAsB,IAAI,MAAM,EAAE;AAC1C;;;;;;;;;;;;;AAcA,SAAgB,oBAAoB,MAAgC;CACnE,sBAAsB,UAAU,IAAI;AACrC;;;;;;AAOA,SAAgB,qBAA2B;CAG1C,sBAAiE,UAChE,KAAA,CACD;AACD"}
|
package/dist/context.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { r as ServiceContext, t as RequestUser } from "./types-BJldyZ-B.cjs";
|
|
2
2
|
import { Logger } from "@geekmidas/logger";
|
|
3
3
|
//#region src/context.d.ts
|
|
4
4
|
/**
|
|
@@ -9,6 +9,17 @@ export interface RequestContextData {
|
|
|
9
9
|
logger: Logger;
|
|
10
10
|
requestId: string;
|
|
11
11
|
startTime: number;
|
|
12
|
+
/**
|
|
13
|
+
* What is running: `POST /orders`, `queue Emails`, `cron rate(1 day)`.
|
|
14
|
+
* Carried so that work done on the request's behalf — a database query —
|
|
15
|
+
* can say what it was done for.
|
|
16
|
+
*/
|
|
17
|
+
operation?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Who the request is for, once its session was verified. Set by
|
|
20
|
+
* {@link identifyRequest}, which also rebinds {@link logger} with it.
|
|
21
|
+
*/
|
|
22
|
+
user?: RequestUser;
|
|
12
23
|
}
|
|
13
24
|
/**
|
|
14
25
|
* ServiceContext implementation.
|
|
@@ -16,6 +27,34 @@ export interface RequestContextData {
|
|
|
16
27
|
* Methods throw if called outside a request context (catches bugs early).
|
|
17
28
|
*/
|
|
18
29
|
export declare const serviceContext: ServiceContext;
|
|
30
|
+
/**
|
|
31
|
+
* Say who the current request is for, once its session was verified.
|
|
32
|
+
*
|
|
33
|
+
* Kept on the request's own context, so everything that runs for it from
|
|
34
|
+
* here on — the handler, every service, a query, an event it publishes, work
|
|
35
|
+
* it awaits after any number of hops — reads the same user, and no other
|
|
36
|
+
* request ever does. Its logger is rebound with `userId` (and
|
|
37
|
+
* `sessionId`): every line logged for the request after this, through the
|
|
38
|
+
* logger a handler is handed or one a service captured, says whose it was.
|
|
39
|
+
*
|
|
40
|
+
* Identifying again with another user replaces the first — the bindings are
|
|
41
|
+
* applied to the request's logger as it was before any identification, so a
|
|
42
|
+
* line never carries two users.
|
|
43
|
+
*
|
|
44
|
+
* Outside a request there is nothing to identify, and it does nothing: a
|
|
45
|
+
* session verified from a script is still verified.
|
|
46
|
+
*
|
|
47
|
+
* @returns whether there was a request to identify
|
|
48
|
+
*/
|
|
49
|
+
export declare function identifyRequest(user: RequestUser): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* The current request's context, or undefined outside one.
|
|
52
|
+
*
|
|
53
|
+
* For code that annotates work when there is a request and carries on when
|
|
54
|
+
* there is not — tagging a query, say — where {@link serviceContext}'s
|
|
55
|
+
* accessors would throw.
|
|
56
|
+
*/
|
|
57
|
+
export declare function currentRequestContext(): Readonly<RequestContextData> | undefined;
|
|
19
58
|
/**
|
|
20
59
|
* Run a function with request context.
|
|
21
60
|
* Used by endpoint/function/subscriber adaptors.
|
package/dist/context.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.cts","names":[],"sources":["../src/context.ts"],"mappings":";;;;;;;iBAQiB;EAChB,QAAQ;EACR;EACA;;;;;;;
|
|
1
|
+
{"version":3,"file":"context.d.cts","names":[],"sources":["../src/context.ts"],"mappings":";;;;;;;iBAQiB;EAChB,QAAQ;EACR;EACA;;;;;;EAMA;;;;;EAKA,OAAO;;;;;;;qBAsHK,gBAAgB;;;;;;;;;;;;;;;;;;;;wBA6Db,gBAAgB,MAAM;;;;;;;;wBA4BtB,yBACb,SAAS;;;;;;;;;;;;;;;;;;;;;wBAyBI,sBAAsB,GACrC,MAAM,oBACN,UAAU,IAAI,QAAQ,KACpB,IAAI,QAAQ;;;;;;;;;;;;;wBAgBC,oBAAoB,MAAM;;;;;;wBAS1B"}
|
package/dist/context.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { r as ServiceContext, t as RequestUser } from "./types-BJldyZ-B.mjs";
|
|
2
2
|
import { Logger } from "@geekmidas/logger";
|
|
3
3
|
//#region src/context.d.ts
|
|
4
4
|
/**
|
|
@@ -9,6 +9,17 @@ export interface RequestContextData {
|
|
|
9
9
|
logger: Logger;
|
|
10
10
|
requestId: string;
|
|
11
11
|
startTime: number;
|
|
12
|
+
/**
|
|
13
|
+
* What is running: `POST /orders`, `queue Emails`, `cron rate(1 day)`.
|
|
14
|
+
* Carried so that work done on the request's behalf — a database query —
|
|
15
|
+
* can say what it was done for.
|
|
16
|
+
*/
|
|
17
|
+
operation?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Who the request is for, once its session was verified. Set by
|
|
20
|
+
* {@link identifyRequest}, which also rebinds {@link logger} with it.
|
|
21
|
+
*/
|
|
22
|
+
user?: RequestUser;
|
|
12
23
|
}
|
|
13
24
|
/**
|
|
14
25
|
* ServiceContext implementation.
|
|
@@ -16,6 +27,34 @@ export interface RequestContextData {
|
|
|
16
27
|
* Methods throw if called outside a request context (catches bugs early).
|
|
17
28
|
*/
|
|
18
29
|
export declare const serviceContext: ServiceContext;
|
|
30
|
+
/**
|
|
31
|
+
* Say who the current request is for, once its session was verified.
|
|
32
|
+
*
|
|
33
|
+
* Kept on the request's own context, so everything that runs for it from
|
|
34
|
+
* here on — the handler, every service, a query, an event it publishes, work
|
|
35
|
+
* it awaits after any number of hops — reads the same user, and no other
|
|
36
|
+
* request ever does. Its logger is rebound with `userId` (and
|
|
37
|
+
* `sessionId`): every line logged for the request after this, through the
|
|
38
|
+
* logger a handler is handed or one a service captured, says whose it was.
|
|
39
|
+
*
|
|
40
|
+
* Identifying again with another user replaces the first — the bindings are
|
|
41
|
+
* applied to the request's logger as it was before any identification, so a
|
|
42
|
+
* line never carries two users.
|
|
43
|
+
*
|
|
44
|
+
* Outside a request there is nothing to identify, and it does nothing: a
|
|
45
|
+
* session verified from a script is still verified.
|
|
46
|
+
*
|
|
47
|
+
* @returns whether there was a request to identify
|
|
48
|
+
*/
|
|
49
|
+
export declare function identifyRequest(user: RequestUser): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* The current request's context, or undefined outside one.
|
|
52
|
+
*
|
|
53
|
+
* For code that annotates work when there is a request and carries on when
|
|
54
|
+
* there is not — tagging a query, say — where {@link serviceContext}'s
|
|
55
|
+
* accessors would throw.
|
|
56
|
+
*/
|
|
57
|
+
export declare function currentRequestContext(): Readonly<RequestContextData> | undefined;
|
|
19
58
|
/**
|
|
20
59
|
* Run a function with request context.
|
|
21
60
|
* Used by endpoint/function/subscriber adaptors.
|
package/dist/context.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.mts","names":[],"sources":["../src/context.ts"],"mappings":";;;;;;;iBAQiB;EAChB,QAAQ;EACR;EACA;;;;;;;
|
|
1
|
+
{"version":3,"file":"context.d.mts","names":[],"sources":["../src/context.ts"],"mappings":";;;;;;;iBAQiB;EAChB,QAAQ;EACR;EACA;;;;;;EAMA;;;;;EAKA,OAAO;;;;;;;qBAsHK,gBAAgB;;;;;;;;;;;;;;;;;;;;wBA6Db,gBAAgB,MAAM;;;;;;;;wBA4BtB,yBACb,SAAS;;;;;;;;;;;;;;;;;;;;;wBAyBI,sBAAsB,GACrC,MAAM,oBACN,UAAU,IAAI,QAAQ,KACpB,IAAI,QAAQ;;;;;;;;;;;;;wBAgBC,oBAAoB,MAAM;;;;;;wBAS1B"}
|
package/dist/context.mjs
CHANGED
|
@@ -89,9 +89,58 @@ const serviceContext = {
|
|
|
89
89
|
},
|
|
90
90
|
hasContext() {
|
|
91
91
|
return requestContextStorage.getStore() !== void 0;
|
|
92
|
+
},
|
|
93
|
+
getUser() {
|
|
94
|
+
return requestContextStorage.getStore()?.user;
|
|
92
95
|
}
|
|
93
96
|
};
|
|
94
97
|
/**
|
|
98
|
+
* Say who the current request is for, once its session was verified.
|
|
99
|
+
*
|
|
100
|
+
* Kept on the request's own context, so everything that runs for it from
|
|
101
|
+
* here on — the handler, every service, a query, an event it publishes, work
|
|
102
|
+
* it awaits after any number of hops — reads the same user, and no other
|
|
103
|
+
* request ever does. Its logger is rebound with `userId` (and
|
|
104
|
+
* `sessionId`): every line logged for the request after this, through the
|
|
105
|
+
* logger a handler is handed or one a service captured, says whose it was.
|
|
106
|
+
*
|
|
107
|
+
* Identifying again with another user replaces the first — the bindings are
|
|
108
|
+
* applied to the request's logger as it was before any identification, so a
|
|
109
|
+
* line never carries two users.
|
|
110
|
+
*
|
|
111
|
+
* Outside a request there is nothing to identify, and it does nothing: a
|
|
112
|
+
* session verified from a script is still verified.
|
|
113
|
+
*
|
|
114
|
+
* @returns whether there was a request to identify
|
|
115
|
+
*/
|
|
116
|
+
function identifyRequest(user) {
|
|
117
|
+
const store = requestContextStorage.getStore();
|
|
118
|
+
if (!store) return false;
|
|
119
|
+
const anonymous = anonymousLoggers.get(store) ?? store.logger;
|
|
120
|
+
anonymousLoggers.set(store, anonymous);
|
|
121
|
+
store.user = user;
|
|
122
|
+
store.logger = anonymous.child({
|
|
123
|
+
userId: user.id,
|
|
124
|
+
...user.sessionId ? { sessionId: user.sessionId } : {}
|
|
125
|
+
});
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Each request's logger as it was before {@link identifyRequest} rebound it,
|
|
130
|
+
* so identifying again binds to that rather than stacking a second user.
|
|
131
|
+
*/
|
|
132
|
+
const anonymousLoggers = /* @__PURE__ */ new WeakMap();
|
|
133
|
+
/**
|
|
134
|
+
* The current request's context, or undefined outside one.
|
|
135
|
+
*
|
|
136
|
+
* For code that annotates work when there is a request and carries on when
|
|
137
|
+
* there is not — tagging a query, say — where {@link serviceContext}'s
|
|
138
|
+
* accessors would throw.
|
|
139
|
+
*/
|
|
140
|
+
function currentRequestContext() {
|
|
141
|
+
return requestContextStorage.getStore();
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
95
144
|
* Run a function with request context.
|
|
96
145
|
* Used by endpoint/function/subscriber adaptors.
|
|
97
146
|
*
|
|
@@ -138,6 +187,6 @@ function exitRequestContext() {
|
|
|
138
187
|
requestContextStorage.enterWith(void 0);
|
|
139
188
|
}
|
|
140
189
|
//#endregion
|
|
141
|
-
export { enterRequestContext, exitRequestContext, runWithRequestContext, serviceContext };
|
|
190
|
+
export { currentRequestContext, enterRequestContext, exitRequestContext, identifyRequest, runWithRequestContext, serviceContext };
|
|
142
191
|
|
|
143
192
|
//# sourceMappingURL=context.mjs.map
|
package/dist/context.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.mjs","names":[],"sources":["../src/context.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport type { Logger } from '@geekmidas/logger';\nimport type { ServiceContext } from './types';\n\n/**\n * Internal storage for request context data.\n * Not exported - services use ServiceContext interface.\n */\nexport interface RequestContextData {\n\tlogger: Logger;\n\trequestId: string;\n\tstartTime: number;\n}\n\n/**\n * Internal AsyncLocalStorage instance for request context.\n * Not exported - use runWithRequestContext() to establish context\n * and serviceContext to access it.\n */\nconst requestContextStorage = new AsyncLocalStorage<RequestContextData>();\n\n/**\n * Resolve the logger for the current request, or throw if there is none.\n */\nfunction resolveRequestLogger(): Logger {\n\tconst store = requestContextStorage.getStore();\n\tif (!store) {\n\t\tthrow new Error(\n\t\t\t'ServiceContext.getLogger() called outside request context. ' +\n\t\t\t\t'Ensure code runs within runWithRequestContext().',\n\t\t);\n\t}\n\treturn store.logger;\n}\n\n/**\n * Create a Logger that re-resolves its underlying logger on every call instead\n * of capturing it once.\n *\n * This is what makes it safe for a **singleton** service to grab the logger a\n * single time (e.g. during `register()`, which `ServiceDiscovery` only runs\n * once and then caches) and reuse that reference for every request: each log\n * call resolves the *current* request's logger from `AsyncLocalStorage`, so\n * requests no longer inherit the first request's logger (and its `requestId`,\n * user bindings, etc.).\n *\n * Implemented as a `Proxy` rather than a fixed list of methods so it forwards\n * the *entire* surface of whatever logger is supplied — including members\n * beyond the base `Logger` interface (e.g. a richer pino-backed logger's\n * `flush()` or `level`) and any methods added to `Logger` in the future.\n *\n * @param bindings - `child()` bindings applied, in order, on top of the\n * resolved logger before each call.\n */\nfunction createRequestScopedLogger(bindings: object[] = []): Logger {\n\t// Memoise the resolved (optionally child) logger per underlying base logger\n\t// so we don't rebuild the child chain on every access within a request.\n\t// Recomputed whenever the current request's logger changes — there is no\n\t// await between the check and use, so this is safe under concurrency.\n\tlet cachedBase: Logger | undefined;\n\tlet cachedResolved: Logger | undefined;\n\n\tconst resolve = (): Logger => {\n\t\tconst base = resolveRequestLogger();\n\t\tif (base !== cachedBase) {\n\t\t\tcachedBase = base;\n\t\t\tcachedResolved = bindings.reduce<Logger>(\n\t\t\t\t(log, obj) => log.child(obj),\n\t\t\t\tbase,\n\t\t\t);\n\t\t}\n\t\treturn cachedResolved as Logger;\n\t};\n\n\treturn new Proxy({} as Logger, {\n\t\tget(_target, prop) {\n\t\t\t// `child()` must stay request-scoped: return a new proxy carrying the\n\t\t\t// extra binding, NOT the underlying logger's child (which would freeze\n\t\t\t// to the current request).\n\t\t\tif (prop === 'child') {\n\t\t\t\treturn (obj: object) => createRequestScopedLogger([...bindings, obj]);\n\t\t\t}\n\t\t\t// Never look like a thenable, and don't answer symbol/inspection probes\n\t\t\t// (util.inspect, Symbol.toPrimitive, etc.) with bound functions.\n\t\t\tif (prop === 'then' || typeof prop === 'symbol') {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\tconst value = (resolve() as unknown as Record<string, unknown>)[prop];\n\t\t\t// Functions are re-resolved at *call* time so detached references\n\t\t\t// (`const info = logger.info`) still target the current request's\n\t\t\t// logger. Non-function members (e.g. `level`) forward as their live\n\t\t\t// value on the current request's logger.\n\t\t\treturn typeof value === 'function'\n\t\t\t\t? (...args: unknown[]) => {\n\t\t\t\t\t\t// Re-resolve at call time so detached references target the\n\t\t\t\t\t\t// current request's logger, and invoke with that logger as\n\t\t\t\t\t\t// `this`: pino's log methods read internal state off the\n\t\t\t\t\t\t// receiver (`this[Symbol(pino.msgPrefix)]`), so calling them\n\t\t\t\t\t\t// unbound throws \"Cannot read properties of undefined\".\n\t\t\t\t\t\tconst resolved = resolve();\n\t\t\t\t\t\tconst fn = (resolved as unknown as Record<string, unknown>)[\n\t\t\t\t\t\t\tprop\n\t\t\t\t\t\t] as (...a: unknown[]) => unknown;\n\t\t\t\t\t\treturn fn.apply(resolved, args);\n\t\t\t\t\t}\n\t\t\t\t: value;\n\t\t},\n\t\t// Keep `'prop' in logger` / hasOwnProperty truthful against the underlying\n\t\t// logger so feature-detection works.\n\t\thas(_target, prop) {\n\t\t\tif (prop === 'child') return true;\n\t\t\tif (prop === 'then' || typeof prop === 'symbol') return false;\n\t\t\treturn prop in (resolve() as object);\n\t\t},\n\t});\n}\n\n/**\n * Stable, process-wide request-scoped logger proxy. Shared across requests on\n * purpose — it carries no request state itself, delegating to the current\n * `AsyncLocalStorage` store on each call.\n */\nconst requestScopedLogger = createRequestScopedLogger();\n\n/**\n * ServiceContext implementation.\n * Singleton that reads from AsyncLocalStorage.\n * Methods throw if called outside a request context (catches bugs early).\n */\nexport const serviceContext: ServiceContext = {\n\tgetLogger() {\n\t\t// Throw eagerly if there is no context, preserving the \"catch bugs early\"\n\t\t// contract for callers that read the logger at an unexpected time.\n\t\tresolveRequestLogger();\n\t\t// Return the shared proxy rather than the raw `store.logger`. A service\n\t\t// that captures this once still logs against the correct per-request\n\t\t// logger because the proxy re-resolves on every call.\n\t\treturn requestScopedLogger;\n\t},\n\n\tgetRequestId() {\n\t\tconst store = requestContextStorage.getStore();\n\t\tif (!store) {\n\t\t\tthrow new Error(\n\t\t\t\t'ServiceContext.getRequestId() called outside request context. ' +\n\t\t\t\t\t'Ensure code runs within runWithRequestContext().',\n\t\t\t);\n\t\t}\n\t\treturn store.requestId;\n\t},\n\n\tgetRequestStartTime() {\n\t\tconst store = requestContextStorage.getStore();\n\t\tif (!store) {\n\t\t\tthrow new Error(\n\t\t\t\t'ServiceContext.getRequestStartTime() called outside request context. ' +\n\t\t\t\t\t'Ensure code runs within runWithRequestContext().',\n\t\t\t);\n\t\t}\n\t\treturn store.startTime;\n\t},\n\n\thasContext() {\n\t\treturn requestContextStorage.getStore() !== undefined;\n\t},\n};\n\n/**\n * Run a function with request context.\n * Used by endpoint/function/subscriber adaptors.\n *\n * @param data - Request context data (logger, requestId, startTime)\n * @param fn - Function to run with context\n * @returns Result of the function\n *\n * @example\n * ```typescript\n * const result = await runWithRequestContext(\n * { logger, requestId, startTime: Date.now() },\n * async () => {\n * // Inside here, serviceContext.getLogger() returns `logger`\n * // serviceContext.getRequestId() returns `requestId`\n * return await handleRequest();\n * }\n * );\n * ```\n */\nexport function runWithRequestContext<T>(\n\tdata: RequestContextData,\n\tfn: () => T | Promise<T>,\n): T | Promise<T> {\n\treturn requestContextStorage.run(data, fn);\n}\n\n/**\n * Mutate the current async task's store so that subsequent code in this task\n * (and any descendants) sees the supplied request context.\n *\n * Unlike `runWithRequestContext`, this does not scope the context to a\n * callback — useful when the caller can't wrap a function, for example in a\n * Vitest fixture that suspends on `use()` and yields control to the test\n * runner before the test body executes.\n *\n * **Test setup only.** In production handlers, prefer `runWithRequestContext`\n * so the frame is automatically cleaned up.\n */\nexport function enterRequestContext(data: RequestContextData): void {\n\trequestContextStorage.enterWith(data);\n}\n\n/**\n * Clear the request context for the current async task. Pairs with\n * `enterRequestContext`. After calling, `serviceContext.hasContext()` returns\n * false for the remainder of the current async resource.\n */\nexport function exitRequestContext(): void {\n\t// AsyncLocalStorage<T>.enterWith requires T, but Node accepts undefined at\n\t// runtime — passing it resets getStore() back to undefined.\n\t(requestContextStorage as unknown as AsyncLocalStorage<unknown>).enterWith(\n\t\tundefined,\n\t);\n}\n"],"mappings":";;;;;;;AAmBA,MAAM,wBAAwB,IAAI,kBAAsC;;;;AAKxE,SAAS,uBAA+B;CACvC,MAAM,QAAQ,sBAAsB,SAAS;CAC7C,IAAI,CAAC,OACJ,MAAM,IAAI,MACT,6GAED;CAED,OAAO,MAAM;AACd;;;;;;;;;;;;;;;;;;;;AAqBA,SAAS,0BAA0B,WAAqB,CAAC,GAAW;CAKnE,IAAI;CACJ,IAAI;CAEJ,MAAM,gBAAwB;EAC7B,MAAM,OAAO,qBAAqB;EAClC,IAAI,SAAS,YAAY;GACxB,aAAa;GACb,iBAAiB,SAAS,QACxB,KAAK,QAAQ,IAAI,MAAM,GAAG,GAC3B,IACD;EACD;EACA,OAAO;CACR;CAEA,OAAO,IAAI,MAAM,CAAC,GAAa;EAC9B,IAAI,SAAS,MAAM;GAIlB,IAAI,SAAS,SACZ,QAAQ,QAAgB,0BAA0B,CAAC,GAAG,UAAU,GAAG,CAAC;GAIrE,IAAI,SAAS,UAAU,OAAO,SAAS,UACtC;GAED,MAAM,QAAS,QAAQ,CAAC,CAAwC;GAKhE,OAAO,OAAO,UAAU,cACpB,GAAG,SAAoB;IAMxB,MAAM,WAAW,QAAQ;IAIzB,OAHY,SACX,KAEQ,CAAC,MAAM,UAAU,IAAI;GAC/B,IACC;EACJ;EAGA,IAAI,SAAS,MAAM;GAClB,IAAI,SAAS,SAAS,OAAO;GAC7B,IAAI,SAAS,UAAU,OAAO,SAAS,UAAU,OAAO;GACxD,OAAO,QAAS,QAAQ;EACzB;CACD,CAAC;AACF;;;;;;AAOA,MAAM,sBAAsB,0BAA0B;;;;;;AAOtD,MAAa,iBAAiC;CAC7C,YAAY;EAGX,qBAAqB;EAIrB,OAAO;CACR;CAEA,eAAe;EACd,MAAM,QAAQ,sBAAsB,SAAS;EAC7C,IAAI,CAAC,OACJ,MAAM,IAAI,MACT,gHAED;EAED,OAAO,MAAM;CACd;CAEA,sBAAsB;EACrB,MAAM,QAAQ,sBAAsB,SAAS;EAC7C,IAAI,CAAC,OACJ,MAAM,IAAI,MACT,uHAED;EAED,OAAO,MAAM;CACd;CAEA,aAAa;EACZ,OAAO,sBAAsB,SAAS,MAAM,KAAA;CAC7C;AACD;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,sBACf,MACA,IACiB;CACjB,OAAO,sBAAsB,IAAI,MAAM,EAAE;AAC1C;;;;;;;;;;;;;AAcA,SAAgB,oBAAoB,MAAgC;CACnE,sBAAsB,UAAU,IAAI;AACrC;;;;;;AAOA,SAAgB,qBAA2B;CAG1C,sBAAiE,UAChE,KAAA,CACD;AACD"}
|
|
1
|
+
{"version":3,"file":"context.mjs","names":[],"sources":["../src/context.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport type { Logger } from '@geekmidas/logger';\nimport type { RequestUser, ServiceContext } from './types';\n\n/**\n * Internal storage for request context data.\n * Not exported - services use ServiceContext interface.\n */\nexport interface RequestContextData {\n\tlogger: Logger;\n\trequestId: string;\n\tstartTime: number;\n\t/**\n\t * What is running: `POST /orders`, `queue Emails`, `cron rate(1 day)`.\n\t * Carried so that work done on the request's behalf — a database query —\n\t * can say what it was done for.\n\t */\n\toperation?: string;\n\t/**\n\t * Who the request is for, once its session was verified. Set by\n\t * {@link identifyRequest}, which also rebinds {@link logger} with it.\n\t */\n\tuser?: RequestUser;\n}\n\n/**\n * Internal AsyncLocalStorage instance for request context.\n * Not exported - use runWithRequestContext() to establish context\n * and serviceContext to access it.\n */\nconst requestContextStorage = new AsyncLocalStorage<RequestContextData>();\n\n/**\n * Resolve the logger for the current request, or throw if there is none.\n */\nfunction resolveRequestLogger(): Logger {\n\tconst store = requestContextStorage.getStore();\n\tif (!store) {\n\t\tthrow new Error(\n\t\t\t'ServiceContext.getLogger() called outside request context. ' +\n\t\t\t\t'Ensure code runs within runWithRequestContext().',\n\t\t);\n\t}\n\treturn store.logger;\n}\n\n/**\n * Create a Logger that re-resolves its underlying logger on every call instead\n * of capturing it once.\n *\n * This is what makes it safe for a **singleton** service to grab the logger a\n * single time (e.g. during `register()`, which `ServiceDiscovery` only runs\n * once and then caches) and reuse that reference for every request: each log\n * call resolves the *current* request's logger from `AsyncLocalStorage`, so\n * requests no longer inherit the first request's logger (and its `requestId`,\n * user bindings, etc.).\n *\n * Implemented as a `Proxy` rather than a fixed list of methods so it forwards\n * the *entire* surface of whatever logger is supplied — including members\n * beyond the base `Logger` interface (e.g. a richer pino-backed logger's\n * `flush()` or `level`) and any methods added to `Logger` in the future.\n *\n * @param bindings - `child()` bindings applied, in order, on top of the\n * resolved logger before each call.\n */\nfunction createRequestScopedLogger(bindings: object[] = []): Logger {\n\t// Memoise the resolved (optionally child) logger per underlying base logger\n\t// so we don't rebuild the child chain on every access within a request.\n\t// Recomputed whenever the current request's logger changes — there is no\n\t// await between the check and use, so this is safe under concurrency.\n\tlet cachedBase: Logger | undefined;\n\tlet cachedResolved: Logger | undefined;\n\n\tconst resolve = (): Logger => {\n\t\tconst base = resolveRequestLogger();\n\t\tif (base !== cachedBase) {\n\t\t\tcachedBase = base;\n\t\t\tcachedResolved = bindings.reduce<Logger>(\n\t\t\t\t(log, obj) => log.child(obj),\n\t\t\t\tbase,\n\t\t\t);\n\t\t}\n\t\treturn cachedResolved as Logger;\n\t};\n\n\treturn new Proxy({} as Logger, {\n\t\tget(_target, prop) {\n\t\t\t// `child()` must stay request-scoped: return a new proxy carrying the\n\t\t\t// extra binding, NOT the underlying logger's child (which would freeze\n\t\t\t// to the current request).\n\t\t\tif (prop === 'child') {\n\t\t\t\treturn (obj: object) => createRequestScopedLogger([...bindings, obj]);\n\t\t\t}\n\t\t\t// Never look like a thenable, and don't answer symbol/inspection probes\n\t\t\t// (util.inspect, Symbol.toPrimitive, etc.) with bound functions.\n\t\t\tif (prop === 'then' || typeof prop === 'symbol') {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\tconst value = (resolve() as unknown as Record<string, unknown>)[prop];\n\t\t\t// Functions are re-resolved at *call* time so detached references\n\t\t\t// (`const info = logger.info`) still target the current request's\n\t\t\t// logger. Non-function members (e.g. `level`) forward as their live\n\t\t\t// value on the current request's logger.\n\t\t\treturn typeof value === 'function'\n\t\t\t\t? (...args: unknown[]) => {\n\t\t\t\t\t\t// Re-resolve at call time so detached references target the\n\t\t\t\t\t\t// current request's logger, and invoke with that logger as\n\t\t\t\t\t\t// `this`: pino's log methods read internal state off the\n\t\t\t\t\t\t// receiver (`this[Symbol(pino.msgPrefix)]`), so calling them\n\t\t\t\t\t\t// unbound throws \"Cannot read properties of undefined\".\n\t\t\t\t\t\tconst resolved = resolve();\n\t\t\t\t\t\tconst fn = (resolved as unknown as Record<string, unknown>)[\n\t\t\t\t\t\t\tprop\n\t\t\t\t\t\t] as (...a: unknown[]) => unknown;\n\t\t\t\t\t\treturn fn.apply(resolved, args);\n\t\t\t\t\t}\n\t\t\t\t: value;\n\t\t},\n\t\t// Keep `'prop' in logger` / hasOwnProperty truthful against the underlying\n\t\t// logger so feature-detection works.\n\t\thas(_target, prop) {\n\t\t\tif (prop === 'child') return true;\n\t\t\tif (prop === 'then' || typeof prop === 'symbol') return false;\n\t\t\treturn prop in (resolve() as object);\n\t\t},\n\t});\n}\n\n/**\n * Stable, process-wide request-scoped logger proxy. Shared across requests on\n * purpose — it carries no request state itself, delegating to the current\n * `AsyncLocalStorage` store on each call.\n */\nconst requestScopedLogger = createRequestScopedLogger();\n\n/**\n * ServiceContext implementation.\n * Singleton that reads from AsyncLocalStorage.\n * Methods throw if called outside a request context (catches bugs early).\n */\nexport const serviceContext: ServiceContext = {\n\tgetLogger() {\n\t\t// Throw eagerly if there is no context, preserving the \"catch bugs early\"\n\t\t// contract for callers that read the logger at an unexpected time.\n\t\tresolveRequestLogger();\n\t\t// Return the shared proxy rather than the raw `store.logger`. A service\n\t\t// that captures this once still logs against the correct per-request\n\t\t// logger because the proxy re-resolves on every call.\n\t\treturn requestScopedLogger;\n\t},\n\n\tgetRequestId() {\n\t\tconst store = requestContextStorage.getStore();\n\t\tif (!store) {\n\t\t\tthrow new Error(\n\t\t\t\t'ServiceContext.getRequestId() called outside request context. ' +\n\t\t\t\t\t'Ensure code runs within runWithRequestContext().',\n\t\t\t);\n\t\t}\n\t\treturn store.requestId;\n\t},\n\n\tgetRequestStartTime() {\n\t\tconst store = requestContextStorage.getStore();\n\t\tif (!store) {\n\t\t\tthrow new Error(\n\t\t\t\t'ServiceContext.getRequestStartTime() called outside request context. ' +\n\t\t\t\t\t'Ensure code runs within runWithRequestContext().',\n\t\t\t);\n\t\t}\n\t\treturn store.startTime;\n\t},\n\n\thasContext() {\n\t\treturn requestContextStorage.getStore() !== undefined;\n\t},\n\n\tgetUser() {\n\t\treturn requestContextStorage.getStore()?.user;\n\t},\n};\n\n/**\n * Say who the current request is for, once its session was verified.\n *\n * Kept on the request's own context, so everything that runs for it from\n * here on — the handler, every service, a query, an event it publishes, work\n * it awaits after any number of hops — reads the same user, and no other\n * request ever does. Its logger is rebound with `userId` (and\n * `sessionId`): every line logged for the request after this, through the\n * logger a handler is handed or one a service captured, says whose it was.\n *\n * Identifying again with another user replaces the first — the bindings are\n * applied to the request's logger as it was before any identification, so a\n * line never carries two users.\n *\n * Outside a request there is nothing to identify, and it does nothing: a\n * session verified from a script is still verified.\n *\n * @returns whether there was a request to identify\n */\nexport function identifyRequest(user: RequestUser): boolean {\n\tconst store = requestContextStorage.getStore();\n\tif (!store) return false;\n\n\tconst anonymous = anonymousLoggers.get(store) ?? store.logger;\n\tanonymousLoggers.set(store, anonymous);\n\n\tstore.user = user;\n\tstore.logger = anonymous.child({\n\t\tuserId: user.id,\n\t\t...(user.sessionId ? { sessionId: user.sessionId } : {}),\n\t});\n\treturn true;\n}\n\n/**\n * Each request's logger as it was before {@link identifyRequest} rebound it,\n * so identifying again binds to that rather than stacking a second user.\n */\nconst anonymousLoggers = new WeakMap<RequestContextData, Logger>();\n\n/**\n * The current request's context, or undefined outside one.\n *\n * For code that annotates work when there is a request and carries on when\n * there is not — tagging a query, say — where {@link serviceContext}'s\n * accessors would throw.\n */\nexport function currentRequestContext():\n\t| Readonly<RequestContextData>\n\t| undefined {\n\treturn requestContextStorage.getStore();\n}\n\n/**\n * Run a function with request context.\n * Used by endpoint/function/subscriber adaptors.\n *\n * @param data - Request context data (logger, requestId, startTime)\n * @param fn - Function to run with context\n * @returns Result of the function\n *\n * @example\n * ```typescript\n * const result = await runWithRequestContext(\n * { logger, requestId, startTime: Date.now() },\n * async () => {\n * // Inside here, serviceContext.getLogger() returns `logger`\n * // serviceContext.getRequestId() returns `requestId`\n * return await handleRequest();\n * }\n * );\n * ```\n */\nexport function runWithRequestContext<T>(\n\tdata: RequestContextData,\n\tfn: () => T | Promise<T>,\n): T | Promise<T> {\n\treturn requestContextStorage.run(data, fn);\n}\n\n/**\n * Mutate the current async task's store so that subsequent code in this task\n * (and any descendants) sees the supplied request context.\n *\n * Unlike `runWithRequestContext`, this does not scope the context to a\n * callback — useful when the caller can't wrap a function, for example in a\n * Vitest fixture that suspends on `use()` and yields control to the test\n * runner before the test body executes.\n *\n * **Test setup only.** In production handlers, prefer `runWithRequestContext`\n * so the frame is automatically cleaned up.\n */\nexport function enterRequestContext(data: RequestContextData): void {\n\trequestContextStorage.enterWith(data);\n}\n\n/**\n * Clear the request context for the current async task. Pairs with\n * `enterRequestContext`. After calling, `serviceContext.hasContext()` returns\n * false for the remainder of the current async resource.\n */\nexport function exitRequestContext(): void {\n\t// AsyncLocalStorage<T>.enterWith requires T, but Node accepts undefined at\n\t// runtime — passing it resets getStore() back to undefined.\n\t(requestContextStorage as unknown as AsyncLocalStorage<unknown>).enterWith(\n\t\tundefined,\n\t);\n}\n"],"mappings":";;;;;;;AA8BA,MAAM,wBAAwB,IAAI,kBAAsC;;;;AAKxE,SAAS,uBAA+B;CACvC,MAAM,QAAQ,sBAAsB,SAAS;CAC7C,IAAI,CAAC,OACJ,MAAM,IAAI,MACT,6GAED;CAED,OAAO,MAAM;AACd;;;;;;;;;;;;;;;;;;;;AAqBA,SAAS,0BAA0B,WAAqB,CAAC,GAAW;CAKnE,IAAI;CACJ,IAAI;CAEJ,MAAM,gBAAwB;EAC7B,MAAM,OAAO,qBAAqB;EAClC,IAAI,SAAS,YAAY;GACxB,aAAa;GACb,iBAAiB,SAAS,QACxB,KAAK,QAAQ,IAAI,MAAM,GAAG,GAC3B,IACD;EACD;EACA,OAAO;CACR;CAEA,OAAO,IAAI,MAAM,CAAC,GAAa;EAC9B,IAAI,SAAS,MAAM;GAIlB,IAAI,SAAS,SACZ,QAAQ,QAAgB,0BAA0B,CAAC,GAAG,UAAU,GAAG,CAAC;GAIrE,IAAI,SAAS,UAAU,OAAO,SAAS,UACtC;GAED,MAAM,QAAS,QAAQ,CAAC,CAAwC;GAKhE,OAAO,OAAO,UAAU,cACpB,GAAG,SAAoB;IAMxB,MAAM,WAAW,QAAQ;IAIzB,OAHY,SACX,KAEQ,CAAC,MAAM,UAAU,IAAI;GAC/B,IACC;EACJ;EAGA,IAAI,SAAS,MAAM;GAClB,IAAI,SAAS,SAAS,OAAO;GAC7B,IAAI,SAAS,UAAU,OAAO,SAAS,UAAU,OAAO;GACxD,OAAO,QAAS,QAAQ;EACzB;CACD,CAAC;AACF;;;;;;AAOA,MAAM,sBAAsB,0BAA0B;;;;;;AAOtD,MAAa,iBAAiC;CAC7C,YAAY;EAGX,qBAAqB;EAIrB,OAAO;CACR;CAEA,eAAe;EACd,MAAM,QAAQ,sBAAsB,SAAS;EAC7C,IAAI,CAAC,OACJ,MAAM,IAAI,MACT,gHAED;EAED,OAAO,MAAM;CACd;CAEA,sBAAsB;EACrB,MAAM,QAAQ,sBAAsB,SAAS;EAC7C,IAAI,CAAC,OACJ,MAAM,IAAI,MACT,uHAED;EAED,OAAO,MAAM;CACd;CAEA,aAAa;EACZ,OAAO,sBAAsB,SAAS,MAAM,KAAA;CAC7C;CAEA,UAAU;EACT,OAAO,sBAAsB,SAAS,CAAC,EAAE;CAC1C;AACD;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,gBAAgB,MAA4B;CAC3D,MAAM,QAAQ,sBAAsB,SAAS;CAC7C,IAAI,CAAC,OAAO,OAAO;CAEnB,MAAM,YAAY,iBAAiB,IAAI,KAAK,KAAK,MAAM;CACvD,iBAAiB,IAAI,OAAO,SAAS;CAErC,MAAM,OAAO;CACb,MAAM,SAAS,UAAU,MAAM;EAC9B,QAAQ,KAAK;EACb,GAAI,KAAK,YAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;CACvD,CAAC;CACD,OAAO;AACR;;;;;AAMA,MAAM,mCAAmB,IAAI,QAAoC;;;;;;;;AASjE,SAAgB,wBAEH;CACZ,OAAO,sBAAsB,SAAS;AACvC;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,sBACf,MACA,IACiB;CACjB,OAAO,sBAAsB,IAAI,MAAM,EAAE;AAC1C;;;;;;;;;;;;;AAcA,SAAgB,oBAAoB,MAAgC;CACnE,sBAAsB,UAAU,IAAI;AACrC;;;;;;AAOA,SAAgB,qBAA2B;CAG1C,sBAAiE,UAChE,KAAA,CACD;AACD"}
|
package/dist/index.cjs
CHANGED
|
@@ -2,7 +2,9 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
2
2
|
const require_context = require("./context.cjs");
|
|
3
3
|
const require_ServiceDiscovery = require("./ServiceDiscovery-C1bAv6vY.cjs");
|
|
4
4
|
exports.ServiceDiscovery = require_ServiceDiscovery.ServiceDiscovery;
|
|
5
|
+
exports.currentRequestContext = require_context.currentRequestContext;
|
|
5
6
|
exports.enterRequestContext = require_context.enterRequestContext;
|
|
6
7
|
exports.exitRequestContext = require_context.exitRequestContext;
|
|
8
|
+
exports.identifyRequest = require_context.identifyRequest;
|
|
7
9
|
exports.runWithRequestContext = require_context.runWithRequestContext;
|
|
8
10
|
exports.serviceContext = require_context.serviceContext;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as
|
|
2
|
-
import { RequestContextData, enterRequestContext, exitRequestContext, runWithRequestContext, serviceContext } from "./context.cjs";
|
|
3
|
-
import { n as ServiceDiscovery, r as ServiceRecord, t as ExtractServiceNames } from "./ServiceDiscovery-
|
|
4
|
-
export { type ExtractServiceNames, type RequestContextData, type Service, type ServiceContext, ServiceDiscovery, type ServiceRecord, type ServiceRegisterOptions, enterRequestContext, exitRequestContext, runWithRequestContext, serviceContext };
|
|
1
|
+
import { i as ServiceRegisterOptions, n as Service, r as ServiceContext, t as RequestUser } from "./types-BJldyZ-B.cjs";
|
|
2
|
+
import { RequestContextData, currentRequestContext, enterRequestContext, exitRequestContext, identifyRequest, runWithRequestContext, serviceContext } from "./context.cjs";
|
|
3
|
+
import { n as ServiceDiscovery, r as ServiceRecord, t as ExtractServiceNames } from "./ServiceDiscovery-zMTNKupf.cjs";
|
|
4
|
+
export { type ExtractServiceNames, type RequestContextData, type RequestUser, type Service, type ServiceContext, ServiceDiscovery, type ServiceRecord, type ServiceRegisterOptions, currentRequestContext, enterRequestContext, exitRequestContext, identifyRequest, runWithRequestContext, serviceContext };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as
|
|
2
|
-
import { RequestContextData, enterRequestContext, exitRequestContext, runWithRequestContext, serviceContext } from "./context.mjs";
|
|
3
|
-
import { n as ServiceDiscovery, r as ServiceRecord, t as ExtractServiceNames } from "./ServiceDiscovery-
|
|
4
|
-
export { type ExtractServiceNames, type RequestContextData, type Service, type ServiceContext, ServiceDiscovery, type ServiceRecord, type ServiceRegisterOptions, enterRequestContext, exitRequestContext, runWithRequestContext, serviceContext };
|
|
1
|
+
import { i as ServiceRegisterOptions, n as Service, r as ServiceContext, t as RequestUser } from "./types-BJldyZ-B.mjs";
|
|
2
|
+
import { RequestContextData, currentRequestContext, enterRequestContext, exitRequestContext, identifyRequest, runWithRequestContext, serviceContext } from "./context.mjs";
|
|
3
|
+
import { n as ServiceDiscovery, r as ServiceRecord, t as ExtractServiceNames } from "./ServiceDiscovery-D8OKCBzy.mjs";
|
|
4
|
+
export { type ExtractServiceNames, type RequestContextData, type RequestUser, type Service, type ServiceContext, ServiceDiscovery, type ServiceRecord, type ServiceRegisterOptions, currentRequestContext, enterRequestContext, exitRequestContext, identifyRequest, runWithRequestContext, serviceContext };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { enterRequestContext, exitRequestContext, runWithRequestContext, serviceContext } from "./context.mjs";
|
|
1
|
+
import { currentRequestContext, enterRequestContext, exitRequestContext, identifyRequest, runWithRequestContext, serviceContext } from "./context.mjs";
|
|
2
2
|
import { t as ServiceDiscovery } from "./ServiceDiscovery-5LlW7BgD.mjs";
|
|
3
|
-
export { ServiceDiscovery, enterRequestContext, exitRequestContext, runWithRequestContext, serviceContext };
|
|
3
|
+
export { ServiceDiscovery, currentRequestContext, enterRequestContext, exitRequestContext, identifyRequest, runWithRequestContext, serviceContext };
|
package/dist/middy.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as ServiceDiscovery, r as ServiceRecord } from "./ServiceDiscovery-
|
|
1
|
+
import { n as Service } from "./types-BJldyZ-B.cjs";
|
|
2
|
+
import { n as ServiceDiscovery, r as ServiceRecord } from "./ServiceDiscovery-zMTNKupf.cjs";
|
|
3
3
|
import { Logger } from "@geekmidas/logger";
|
|
4
4
|
import { EnvironmentParser } from "@geekmidas/envkit";
|
|
5
5
|
import { MiddlewareObj } from "@middy/core";
|
package/dist/middy.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as ServiceDiscovery, r as ServiceRecord } from "./ServiceDiscovery-
|
|
1
|
+
import { n as Service } from "./types-BJldyZ-B.mjs";
|
|
2
|
+
import { n as ServiceDiscovery, r as ServiceRecord } from "./ServiceDiscovery-D8OKCBzy.mjs";
|
|
3
3
|
import { Logger } from "@geekmidas/logger";
|
|
4
4
|
import { EnvironmentParser } from "@geekmidas/envkit";
|
|
5
5
|
import { MiddlewareObj } from "@middy/core";
|
package/dist/trpc.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as ServiceDiscovery, r as ServiceRecord } from "./ServiceDiscovery-
|
|
1
|
+
import { n as Service } from "./types-BJldyZ-B.cjs";
|
|
2
|
+
import { n as ServiceDiscovery, r as ServiceRecord } from "./ServiceDiscovery-zMTNKupf.cjs";
|
|
3
3
|
import { Logger } from "@geekmidas/logger";
|
|
4
4
|
import { EnvironmentParser } from "@geekmidas/envkit";
|
|
5
5
|
import { TRPCMiddlewareBuilder, TRPCMiddlewareFunction } from "@trpc/server";
|
package/dist/trpc.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as ServiceDiscovery, r as ServiceRecord } from "./ServiceDiscovery-
|
|
1
|
+
import { n as Service } from "./types-BJldyZ-B.mjs";
|
|
2
|
+
import { n as ServiceDiscovery, r as ServiceRecord } from "./ServiceDiscovery-D8OKCBzy.mjs";
|
|
3
3
|
import { Logger } from "@geekmidas/logger";
|
|
4
4
|
import { EnvironmentParser } from "@geekmidas/envkit";
|
|
5
5
|
import { TRPCMiddlewareBuilder, TRPCMiddlewareFunction } from "@trpc/server";
|
|
@@ -35,6 +35,24 @@ interface ServiceContext {
|
|
|
35
35
|
* Use this to guard calls if you need to handle both cases.
|
|
36
36
|
*/
|
|
37
37
|
hasContext(): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Who the current request is for, once its session was verified
|
|
40
|
+
* (`identifyRequest`), or `undefined` — before then, for a request with no
|
|
41
|
+
* session, and outside a request.
|
|
42
|
+
*/
|
|
43
|
+
getUser(): RequestUser | undefined;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Who a request is for: the identity its session was verified as.
|
|
47
|
+
*
|
|
48
|
+
* An id, never an address: it rides on every log line and span of the
|
|
49
|
+
* request, and both leave the process for wherever telemetry is kept.
|
|
50
|
+
*/
|
|
51
|
+
interface RequestUser {
|
|
52
|
+
/** The user's id at the identity provider — Better Auth's `user.id`. */
|
|
53
|
+
id: string;
|
|
54
|
+
/** The session the request was made with. */
|
|
55
|
+
sessionId?: string;
|
|
38
56
|
}
|
|
39
57
|
/**
|
|
40
58
|
* Options passed to service register method.
|
|
@@ -86,5 +104,5 @@ interface Service<TName extends string = string, TInstance = unknown> {
|
|
|
86
104
|
register(options: ServiceRegisterOptions): TInstance | Promise<TInstance>;
|
|
87
105
|
}
|
|
88
106
|
//#endregion
|
|
89
|
-
export {
|
|
90
|
-
//# sourceMappingURL=types-
|
|
107
|
+
export { ServiceRegisterOptions as i, Service as n, ServiceContext as r, RequestUser as t };
|
|
108
|
+
//# sourceMappingURL=types-BJldyZ-B.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types-BJldyZ-B.d.cts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;;UAQiB;;;;;;;;;;;;EAYhB,aAAa;;;;;EAMb;;;;;;EAOA;;;;;EAMA;;;;;;EAOA,WAAW;;;;;;;;UASK;;EAEhB;;EAEA;;;;;UAMgB;;EAEhB,WAAW;;EAEX,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA8BO,QAAQ,+BAA+B;;;;EAIvD,aAAa;;;;;;;EAOb,SAAS,SAAS,yBAAyB,YAAY,QAAQ"}
|
|
@@ -35,6 +35,24 @@ interface ServiceContext {
|
|
|
35
35
|
* Use this to guard calls if you need to handle both cases.
|
|
36
36
|
*/
|
|
37
37
|
hasContext(): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Who the current request is for, once its session was verified
|
|
40
|
+
* (`identifyRequest`), or `undefined` — before then, for a request with no
|
|
41
|
+
* session, and outside a request.
|
|
42
|
+
*/
|
|
43
|
+
getUser(): RequestUser | undefined;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Who a request is for: the identity its session was verified as.
|
|
47
|
+
*
|
|
48
|
+
* An id, never an address: it rides on every log line and span of the
|
|
49
|
+
* request, and both leave the process for wherever telemetry is kept.
|
|
50
|
+
*/
|
|
51
|
+
interface RequestUser {
|
|
52
|
+
/** The user's id at the identity provider — Better Auth's `user.id`. */
|
|
53
|
+
id: string;
|
|
54
|
+
/** The session the request was made with. */
|
|
55
|
+
sessionId?: string;
|
|
38
56
|
}
|
|
39
57
|
/**
|
|
40
58
|
* Options passed to service register method.
|
|
@@ -86,5 +104,5 @@ interface Service<TName extends string = string, TInstance = unknown> {
|
|
|
86
104
|
register(options: ServiceRegisterOptions): TInstance | Promise<TInstance>;
|
|
87
105
|
}
|
|
88
106
|
//#endregion
|
|
89
|
-
export {
|
|
90
|
-
//# sourceMappingURL=types-
|
|
107
|
+
export { ServiceRegisterOptions as i, Service as n, ServiceContext as r, RequestUser as t };
|
|
108
|
+
//# sourceMappingURL=types-BJldyZ-B.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types-BJldyZ-B.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;;UAQiB;;;;;;;;;;;;EAYhB,aAAa;;;;;EAMb;;;;;;EAOA;;;;;EAMA;;;;;;EAOA,WAAW;;;;;;;;UASK;;EAEhB;;EAEA;;;;;UAMgB;;EAEhB,WAAW;;EAEX,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA8BO,QAAQ,+BAA+B;;;;EAIvD,aAAa;;;;;;;EAOb,SAAS,SAAS,yBAAyB,YAAY,QAAQ"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geekmidas/services",
|
|
3
|
-
"version": "10.0.0-alpha.
|
|
3
|
+
"version": "10.0.0-alpha.161",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
"@middy/core": ">=7.9.2",
|
|
62
62
|
"@trpc/server": ">=11.19.0",
|
|
63
63
|
"@types/aws-lambda": ">=8.10.163",
|
|
64
|
-
"@geekmidas/envkit": "
|
|
65
|
-
"@geekmidas/logger": "
|
|
64
|
+
"@geekmidas/envkit": ">=10.0.0-alpha.161 <10.0.0",
|
|
65
|
+
"@geekmidas/logger": ">=10.0.0-alpha.161 <10.0.0"
|
|
66
66
|
},
|
|
67
67
|
"peerDependenciesMeta": {
|
|
68
68
|
"@geekmidas/envkit": {
|
|
@@ -86,10 +86,11 @@
|
|
|
86
86
|
"@trpc/server": "~11.19.0",
|
|
87
87
|
"@types/aws-lambda": "~8.10.163",
|
|
88
88
|
"@types/node": "~24.9.1",
|
|
89
|
-
"@geekmidas/envkit": "
|
|
90
|
-
"@geekmidas/logger": "
|
|
89
|
+
"@geekmidas/envkit": ">=10.0.0-alpha.161 <10.0.0",
|
|
90
|
+
"@geekmidas/logger": ">=10.0.0-alpha.161 <10.0.0"
|
|
91
91
|
},
|
|
92
92
|
"scripts": {
|
|
93
|
-
"ts": "tsc --noEmit --skipLibCheck src/**/*.ts"
|
|
93
|
+
"ts": "tsc --noEmit --skipLibCheck src/**/*.ts",
|
|
94
|
+
"build": "tsdown --config ../../tsdown.config.ts --filter ."
|
|
94
95
|
}
|
|
95
96
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types-DDxt5Kcu.d.cts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;;UAQiB;;;;;;;;;;;;EAYhB,aAAa;;;;;EAMb;;;;;;EAOA;;;;;EAMA;;;;;UAMgB;;EAEhB,WAAW;;EAEX,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA8BO,QAAQ,+BAA+B;;;;EAIvD,aAAa;;;;;;;EAOb,SAAS,SAAS,yBAAyB,YAAY,QAAQ"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types-DDxt5Kcu.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;;UAQiB;;;;;;;;;;;;EAYhB,aAAa;;;;;EAMb;;;;;;EAOA;;;;;EAMA;;;;;UAMgB;;EAEhB,WAAW;;EAEX,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA8BO,QAAQ,+BAA+B;;;;EAIvD,aAAa;;;;;;;EAOb,SAAS,SAAS,yBAAyB,YAAY,QAAQ"}
|