@geekmidas/services 10.0.0-alpha.15 → 10.0.0-alpha.150

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.
@@ -1,4 +1,4 @@
1
- import { t as Service } from "./types-DDxt5Kcu.mjs";
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-I7GKGDgi.d.mts.map
184
+ //# sourceMappingURL=ServiceDiscovery-D8OKCBzy.d.mts.map
@@ -0,0 +1 @@
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 { t as Service } from "./types-DDxt5Kcu.cjs";
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-YbZxbSHi.d.cts.map
184
+ //# sourceMappingURL=ServiceDiscovery-zMTNKupf.d.cts.map
@@ -0,0 +1 @@
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
 
@@ -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"}
@@ -1,4 +1,4 @@
1
- import { n as ServiceContext } from "./types-DDxt5Kcu.cjs";
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.
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.cts","names":[],"sources":["../src/context.ts"],"mappings":";;;;;;;iBAQiB;EAChB,QAAQ;EACR;EACA;;;;;;;qBAsHY,gBAAgB;;;;;;;;;;;;;;;;;;;;;wBA0Db,sBAAsB,GACrC,MAAM,oBACN,UAAU,IAAI,QAAQ,KACpB,IAAI,QAAQ;;;;;;;;;;;;;wBAgBC,oBAAoB,MAAM;;;;;;wBAS1B"}
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"}
@@ -1,4 +1,4 @@
1
- import { n as ServiceContext } from "./types-DDxt5Kcu.mjs";
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.
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.mts","names":[],"sources":["../src/context.ts"],"mappings":";;;;;;;iBAQiB;EAChB,QAAQ;EACR;EACA;;;;;;;qBAsHY,gBAAgB;;;;;;;;;;;;;;;;;;;;;wBA0Db,sBAAsB,GACrC,MAAM,oBACN,UAAU,IAAI,QAAQ,KACpB,IAAI,QAAQ;;;;;;;;;;;;;wBAgBC,oBAAoB,MAAM;;;;;;wBAS1B"}
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
@@ -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 ServiceContext, r as ServiceRegisterOptions, t as Service } from "./types-DDxt5Kcu.cjs";
2
- import { RequestContextData, enterRequestContext, exitRequestContext, runWithRequestContext, serviceContext } from "./context.cjs";
3
- import { n as ServiceDiscovery, r as ServiceRecord, t as ExtractServiceNames } from "./ServiceDiscovery-YbZxbSHi.cjs";
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 ServiceContext, r as ServiceRegisterOptions, t as Service } from "./types-DDxt5Kcu.mjs";
2
- import { RequestContextData, enterRequestContext, exitRequestContext, runWithRequestContext, serviceContext } from "./context.mjs";
3
- import { n as ServiceDiscovery, r as ServiceRecord, t as ExtractServiceNames } from "./ServiceDiscovery-I7GKGDgi.mjs";
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 { t as Service } from "./types-DDxt5Kcu.cjs";
2
- import { n as ServiceDiscovery, r as ServiceRecord } from "./ServiceDiscovery-YbZxbSHi.cjs";
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 { t as Service } from "./types-DDxt5Kcu.mjs";
2
- import { n as ServiceDiscovery, r as ServiceRecord } from "./ServiceDiscovery-I7GKGDgi.mjs";
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 { t as Service } from "./types-DDxt5Kcu.cjs";
2
- import { n as ServiceDiscovery, r as ServiceRecord } from "./ServiceDiscovery-YbZxbSHi.cjs";
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 { t as Service } from "./types-DDxt5Kcu.mjs";
2
- import { n as ServiceDiscovery, r as ServiceRecord } from "./ServiceDiscovery-I7GKGDgi.mjs";
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 { ServiceContext as n, ServiceRegisterOptions as r, Service as t };
90
- //# sourceMappingURL=types-DDxt5Kcu.d.cts.map
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 { ServiceContext as n, ServiceRegisterOptions as r, Service as t };
90
- //# sourceMappingURL=types-DDxt5Kcu.d.mts.map
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.15",
3
+ "version": "10.0.0-alpha.150",
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": "^10.0.0-alpha.15",
65
- "@geekmidas/logger": "^10.0.0-alpha.15"
64
+ "@geekmidas/envkit": "^10.0.0-alpha.150",
65
+ "@geekmidas/logger": "^10.0.0-alpha.150"
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": "^10.0.0-alpha.15",
90
- "@geekmidas/logger": "^10.0.0-alpha.15"
89
+ "@geekmidas/envkit": "^10.0.0-alpha.150",
90
+ "@geekmidas/logger": "^10.0.0-alpha.150"
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":"ServiceDiscovery-I7GKGDgi.d.mts","names":[],"sources":["../src/ServiceDiscovery.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAkCa,iBAAiB,kBAAkB;WAkD1B,WAAW;;iBAhDjB;;UAEP;;UAEA;;;;;;;;;;;;;;SAeD,YAAY,UAAU,4BAC5B,WAAW,wBACT,iBAAiB;;;;;;;;;;;;;SAmBb;;;;;EAQc,YAAA,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 +0,0 @@
1
- {"version":3,"file":"ServiceDiscovery-YbZxbSHi.d.cts","names":[],"sources":["../src/ServiceDiscovery.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAkCa,iBAAiB,kBAAkB;WAkD1B,WAAW;;iBAhDjB;;UAEP;;UAEA;;;;;;;;;;;;;;SAeD,YAAY,UAAU,4BAC5B,WAAW,wBACT,iBAAiB;;;;;;;;;;;;;SAmBb;;;;;EAQc,YAAA,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 +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"}