@webpieces/core-context 0.3.316 → 0.3.318

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/core-context",
3
- "version": "0.3.316",
3
+ "version": "0.3.318",
4
4
  "description": "AsyncLocalStorage-based context management for request-scoped data",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -22,7 +22,7 @@
22
22
  "access": "public"
23
23
  },
24
24
  "dependencies": {
25
- "@webpieces/core-util": "0.3.316",
25
+ "@webpieces/core-util": "0.3.318",
26
26
  "@inversifyjs/binding-decorators": "1.1.5",
27
27
  "inversify": "7.10.4",
28
28
  "reflect-metadata": "0.2.2"
@@ -18,8 +18,17 @@ declare class RequestContextImpl {
18
18
  private storage;
19
19
  constructor();
20
20
  /**
21
- * Run a function with a new context.
22
- * This is typically called at the beginning of a request.
21
+ * Open THE request scope. A transport calls this once, at the beginning of a request.
22
+ *
23
+ * Nesting is a bug, not a feature, so it throws. AsyncLocalStorage would happily let a second
24
+ * `run()` install a fresh empty Map that SHADOWS the outer one: every value the outer scope
25
+ * holds becomes invisible, `fillFromRequest` mints a second request id, and the two halves of a
26
+ * request end up in different traces. Nothing would tell you.
27
+ *
28
+ * With this guard the setup is right or it is loud. It mirrors
29
+ * `RequestContextHeaders.fillFromRequest()`, which throws when there is NO active scope.
30
+ *
31
+ * @throws Error when a RequestContext is already active.
23
32
  */
24
33
  run<T>(fn: () => T): T;
25
34
  /**
@@ -29,6 +38,21 @@ declare class RequestContextImpl {
29
38
  getHeader<T = unknown>(key: ContextKey): T | undefined;
30
39
  putHeader(key: ContextKey, value: unknown): void;
31
40
  hasHeader(key: ContextKey): boolean;
41
+ /**
42
+ /**
43
+ * Build the masked field map for LOGGING: every logged key in the global
44
+ * {@link HeaderRegistry} read straight from this context, secured values
45
+ * masked (via {@link ContextKey.maskIfSecured}), keyed by each key's `name`.
46
+ *
47
+ * Callers: RecordingFilter + NodeProxyClient.recordCall, which snapshot the context into a
48
+ * test FIXTURE. The @webpieces/winston and @webpieces/bunyan backends also stamp these fields
49
+ * onto every record, and they own the "log emitted outside RequestContext.run(...)" complaint —
50
+ * reporting it HERE would recurse (the error line itself re-enters buildLogFields).
51
+ *
52
+ * Returns an EMPTY map outside a `run(...)` block rather than throwing: a fixture snapshot or a
53
+ * log line is never worth crashing a request over.
54
+ */
55
+ buildLogFields(): Map<string, string>;
32
56
  /**
33
57
  * Store the transport-neutral {@link HttpRequest} for this request. Called once, above the
34
58
  * api boundary, by whichever transport is driving the router (the express adapter, or the
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RequestContext = void 0;
4
4
  const async_hooks_1 = require("async_hooks");
5
+ const core_util_1 = require("@webpieces/core-util");
5
6
  /** Reserved context key under which the current HttpRequest is stored. */
6
7
  const HTTP_REQUEST_KEY = '__webpieces_http_request__';
7
8
  /**
@@ -24,10 +25,25 @@ class RequestContextImpl {
24
25
  this.storage = new async_hooks_1.AsyncLocalStorage();
25
26
  }
26
27
  /**
27
- * Run a function with a new context.
28
- * This is typically called at the beginning of a request.
28
+ * Open THE request scope. A transport calls this once, at the beginning of a request.
29
+ *
30
+ * Nesting is a bug, not a feature, so it throws. AsyncLocalStorage would happily let a second
31
+ * `run()` install a fresh empty Map that SHADOWS the outer one: every value the outer scope
32
+ * holds becomes invisible, `fillFromRequest` mints a second request id, and the two halves of a
33
+ * request end up in different traces. Nothing would tell you.
34
+ *
35
+ * With this guard the setup is right or it is loud. It mirrors
36
+ * `RequestContextHeaders.fillFromRequest()`, which throws when there is NO active scope.
37
+ *
38
+ * @throws Error when a RequestContext is already active.
29
39
  */
30
40
  run(fn) {
41
+ if (this.isActive()) {
42
+ throw new Error('RequestContext.run(...) called inside an active RequestContext. Nesting installs a ' +
43
+ 'fresh empty context that shadows the outer one: its values go invisible and a second ' +
44
+ 'request id is minted. Exactly ONE scope per request — the transport opens it.');
45
+ }
46
+ // webpieces-disable no-any-unknown -- context values are heterogeneous (strings, recorder, meta objects)
31
47
  const store = new Map();
32
48
  return this.storage.run(store, fn);
33
49
  }
@@ -48,6 +64,28 @@ class RequestContextImpl {
48
64
  hasHeader(key) {
49
65
  return this.has(key.name);
50
66
  }
67
+ /**
68
+ /**
69
+ * Build the masked field map for LOGGING: every logged key in the global
70
+ * {@link HeaderRegistry} read straight from this context, secured values
71
+ * masked (via {@link ContextKey.maskIfSecured}), keyed by each key's `name`.
72
+ *
73
+ * Callers: RecordingFilter + NodeProxyClient.recordCall, which snapshot the context into a
74
+ * test FIXTURE. The @webpieces/winston and @webpieces/bunyan backends also stamp these fields
75
+ * onto every record, and they own the "log emitted outside RequestContext.run(...)" complaint —
76
+ * reporting it HERE would recurse (the error line itself re-enters buildLogFields).
77
+ *
78
+ * Returns an EMPTY map outside a `run(...)` block rather than throwing: a fixture snapshot or a
79
+ * log line is never worth crashing a request over.
80
+ */
81
+ buildLogFields() {
82
+ if (!this.isActive()) {
83
+ return new Map();
84
+ }
85
+ // The registry owns the keys and each ContextKey masks its own value; we only supply
86
+ // WHERE to read from. The browser's ContextMgr calls the same method with its store's read.
87
+ return core_util_1.HeaderRegistry.get().buildLogFields((key) => this.getHeader(key));
88
+ }
51
89
  /**
52
90
  * Store the transport-neutral {@link HttpRequest} for this request. Called once, above the
53
91
  * api boundary, by whichever transport is driving the router (the express adapter, or the
@@ -1 +1 @@
1
- {"version":3,"file":"RequestContext.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/RequestContext.ts"],"names":[],"mappings":"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","sourcesContent":["import { AsyncLocalStorage } from 'async_hooks';\nimport { ContextKey } from '@webpieces/core-util';\nimport { HttpRequest } from './HttpRequest';\n\n/** Reserved context key under which the current HttpRequest is stored. */\nconst HTTP_REQUEST_KEY = '__webpieces_http_request__';\n\n/**\n * Context management using AsyncLocalStorage.\n * Similar to Java WebPieces Context class that uses ThreadLocal.\n *\n * This allows storing request-scoped data that is automatically available\n * throughout the async call chain, similar to MDC (Mapped Diagnostic Context).\n *\n * Example usage:\n * ```typescript\n * Context.put('REQUEST_ID', '12345');\n * await someAsyncOperation();\n * const id = Context.get('REQUEST_ID'); // Still available!\n * ```\n */\nclass RequestContextImpl {\n private storage: AsyncLocalStorage<Map<string, any>>;\n\n constructor() {\n this.storage = new AsyncLocalStorage<Map<string, any>>();\n }\n\n /**\n * Run a function with a new context.\n * This is typically called at the beginning of a request.\n */\n run<T>(fn: () => T): T {\n const store = new Map<string, any>();\n return this.storage.run(store, fn);\n }\n\n /**\n * Run a function with a specific context.\n */\n runWithContext<T>(context: Map<string, any>, fn: () => T): T {\n return this.storage.run(context, fn);\n }\n\n // webpieces-disable no-any-unknown -- context values are heterogeneous (strings, recorder, meta objects)\n getHeader<T = unknown>(key: ContextKey): T | undefined {\n return this.get<T>(key.name);\n }\n\n // webpieces-disable no-any-unknown -- context values are heterogeneous (strings, recorder, meta objects)\n putHeader(key: ContextKey, value: unknown): void {\n this.put(key.name, value);\n }\n\n hasHeader(key: ContextKey): boolean {\n return this.has(key.name);\n }\n\n /**\n * Store the transport-neutral {@link HttpRequest} for this request. Called once, above the\n * api boundary, by whichever transport is driving the router (the express adapter, or the\n * in-process client). Filters/auth read it back via {@link getRequest} so they never touch\n * express — the same chain then runs over HTTP and in-process.\n */\n setRequest(request: HttpRequest): void {\n this.put(HTTP_REQUEST_KEY, request);\n }\n\n /** The current {@link HttpRequest}, or undefined if none was set for this context. */\n getRequest(): HttpRequest | undefined {\n return this.get<HttpRequest>(HTTP_REQUEST_KEY);\n }\n\n // webpieces-disable no-any-unknown -- context values are heterogeneous (strings, recorder, meta objects)\n getHeaders(keys: ContextKey[]): unknown[] {\n return keys.map(key => this.getHeader(key));\n }\n\n /**\n * Store a value in the current context.\n */\n put(key: string, value: any): void {\n const store = this.storage.getStore();\n if (!store) {\n throw new Error('No context available. Did you call Context.run() first?');\n }\n store.set(key, value);\n }\n\n /**\n * Retrieve a value from the current context.\n */\n get<T = any>(key: string): T | undefined {\n const store = this.storage.getStore();\n return store?.get(key);\n }\n\n /**\n * Remove a value from the current context.\n */\n remove(key: string): void {\n const store = this.storage.getStore();\n store?.delete(key);\n }\n\n /**\n * Clear all values from the current context.\n */\n clear(): void {\n const store = this.storage.getStore();\n store?.clear();\n }\n\n /**\n * Copy the current context to a new Map.\n * Used by XPromise to preserve context across async boundaries.\n */\n copyContext(): Map<string, any> {\n const store = this.storage.getStore();\n if (!store) {\n return new Map();\n }\n return new Map(store);\n }\n\n /**\n * Set the entire context from a Map.\n * Used by XPromise to restore context.\n */\n setContext(context: Map<string, any>): void {\n const store = this.storage.getStore();\n if (!store) {\n throw new Error('No context available. Did you call Context.run() first?');\n }\n store.clear();\n context.forEach((value, key) => {\n store.set(key, value);\n });\n }\n\n /**\n * Get all context entries.\n */\n getAll(): Map<string, any> {\n const store = this.storage.getStore();\n return store ? new Map(store) : new Map();\n }\n\n /**\n * Check if a key exists in the context.\n */\n has(key: string): boolean {\n const store = this.storage.getStore();\n return store?.has(key) ?? false;\n }\n\n /**\n * Check if RequestContext is currently active.\n * Returns true if we're inside a RequestContext.run() block, false otherwise.\n *\n * Useful for tests to verify context is set up before making API calls.\n */\n isActive(): boolean {\n return this.storage.getStore() !== undefined;\n }\n\n}\n\n\n\n/**\n * Global singleton instance of RequestContext.\n * Use this throughout your application.\n */\nexport const RequestContext = new RequestContextImpl();\n"]}
1
+ {"version":3,"file":"RequestContext.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/RequestContext.ts"],"names":[],"mappings":"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","sourcesContent":["import { AsyncLocalStorage } from 'async_hooks';\nimport { ContextKey, HeaderRegistry } from '@webpieces/core-util';\nimport { HttpRequest } from './HttpRequest';\n\n/** Reserved context key under which the current HttpRequest is stored. */\nconst HTTP_REQUEST_KEY = '__webpieces_http_request__';\n\n/**\n * Context management using AsyncLocalStorage.\n * Similar to Java WebPieces Context class that uses ThreadLocal.\n *\n * This allows storing request-scoped data that is automatically available\n * throughout the async call chain, similar to MDC (Mapped Diagnostic Context).\n *\n * Example usage:\n * ```typescript\n * Context.put('REQUEST_ID', '12345');\n * await someAsyncOperation();\n * const id = Context.get('REQUEST_ID'); // Still available!\n * ```\n */\nclass RequestContextImpl {\n private storage: AsyncLocalStorage<Map<string, any>>;\n\n constructor() {\n this.storage = new AsyncLocalStorage<Map<string, any>>();\n }\n\n /**\n * Open THE request scope. A transport calls this once, at the beginning of a request.\n *\n * Nesting is a bug, not a feature, so it throws. AsyncLocalStorage would happily let a second\n * `run()` install a fresh empty Map that SHADOWS the outer one: every value the outer scope\n * holds becomes invisible, `fillFromRequest` mints a second request id, and the two halves of a\n * request end up in different traces. Nothing would tell you.\n *\n * With this guard the setup is right or it is loud. It mirrors\n * `RequestContextHeaders.fillFromRequest()`, which throws when there is NO active scope.\n *\n * @throws Error when a RequestContext is already active.\n */\n run<T>(fn: () => T): T {\n if (this.isActive()) {\n throw new Error(\n 'RequestContext.run(...) called inside an active RequestContext. Nesting installs a ' +\n 'fresh empty context that shadows the outer one: its values go invisible and a second ' +\n 'request id is minted. Exactly ONE scope per request — the transport opens it.',\n );\n }\n // webpieces-disable no-any-unknown -- context values are heterogeneous (strings, recorder, meta objects)\n const store = new Map<string, any>();\n return this.storage.run(store, fn);\n }\n\n /**\n * Run a function with a specific context.\n */\n runWithContext<T>(context: Map<string, any>, fn: () => T): T {\n return this.storage.run(context, fn);\n }\n\n // webpieces-disable no-any-unknown -- context values are heterogeneous (strings, recorder, meta objects)\n getHeader<T = unknown>(key: ContextKey): T | undefined {\n return this.get<T>(key.name);\n }\n\n // webpieces-disable no-any-unknown -- context values are heterogeneous (strings, recorder, meta objects)\n putHeader(key: ContextKey, value: unknown): void {\n this.put(key.name, value);\n }\n\n hasHeader(key: ContextKey): boolean {\n return this.has(key.name);\n }\n\n /**\n /**\n * Build the masked field map for LOGGING: every logged key in the global\n * {@link HeaderRegistry} read straight from this context, secured values\n * masked (via {@link ContextKey.maskIfSecured}), keyed by each key's `name`.\n *\n * Callers: RecordingFilter + NodeProxyClient.recordCall, which snapshot the context into a\n * test FIXTURE. The @webpieces/winston and @webpieces/bunyan backends also stamp these fields\n * onto every record, and they own the \"log emitted outside RequestContext.run(...)\" complaint —\n * reporting it HERE would recurse (the error line itself re-enters buildLogFields).\n *\n * Returns an EMPTY map outside a `run(...)` block rather than throwing: a fixture snapshot or a\n * log line is never worth crashing a request over.\n */\n buildLogFields(): Map<string, string> {\n if (!this.isActive()) {\n return new Map<string, string>();\n }\n // The registry owns the keys and each ContextKey masks its own value; we only supply\n // WHERE to read from. The browser's ContextMgr calls the same method with its store's read.\n return HeaderRegistry.get().buildLogFields((key: ContextKey) => this.getHeader<string>(key));\n }\n\n /**\n * Store the transport-neutral {@link HttpRequest} for this request. Called once, above the\n * api boundary, by whichever transport is driving the router (the express adapter, or the\n * in-process client). Filters/auth read it back via {@link getRequest} so they never touch\n * express — the same chain then runs over HTTP and in-process.\n */\n setRequest(request: HttpRequest): void {\n this.put(HTTP_REQUEST_KEY, request);\n }\n\n /** The current {@link HttpRequest}, or undefined if none was set for this context. */\n getRequest(): HttpRequest | undefined {\n return this.get<HttpRequest>(HTTP_REQUEST_KEY);\n }\n\n // webpieces-disable no-any-unknown -- context values are heterogeneous (strings, recorder, meta objects)\n getHeaders(keys: ContextKey[]): unknown[] {\n return keys.map(key => this.getHeader(key));\n }\n\n /**\n * Store a value in the current context.\n */\n put(key: string, value: any): void {\n const store = this.storage.getStore();\n if (!store) {\n throw new Error('No context available. Did you call Context.run() first?');\n }\n store.set(key, value);\n }\n\n /**\n * Retrieve a value from the current context.\n */\n get<T = any>(key: string): T | undefined {\n const store = this.storage.getStore();\n return store?.get(key);\n }\n\n /**\n * Remove a value from the current context.\n */\n remove(key: string): void {\n const store = this.storage.getStore();\n store?.delete(key);\n }\n\n /**\n * Clear all values from the current context.\n */\n clear(): void {\n const store = this.storage.getStore();\n store?.clear();\n }\n\n /**\n * Copy the current context to a new Map.\n * Used by XPromise to preserve context across async boundaries.\n */\n copyContext(): Map<string, any> {\n const store = this.storage.getStore();\n if (!store) {\n return new Map();\n }\n return new Map(store);\n }\n\n /**\n * Set the entire context from a Map.\n * Used by XPromise to restore context.\n */\n setContext(context: Map<string, any>): void {\n const store = this.storage.getStore();\n if (!store) {\n throw new Error('No context available. Did you call Context.run() first?');\n }\n store.clear();\n context.forEach((value, key) => {\n store.set(key, value);\n });\n }\n\n /**\n * Get all context entries.\n */\n getAll(): Map<string, any> {\n const store = this.storage.getStore();\n return store ? new Map(store) : new Map();\n }\n\n /**\n * Check if a key exists in the context.\n */\n has(key: string): boolean {\n const store = this.storage.getStore();\n return store?.has(key) ?? false;\n }\n\n /**\n * Check if RequestContext is currently active.\n * Returns true if we're inside a RequestContext.run() block, false otherwise.\n *\n * Useful for tests to verify context is set up before making API calls.\n */\n isActive(): boolean {\n return this.storage.getStore() !== undefined;\n }\n\n}\n\n\n\n/**\n * Global singleton instance of RequestContext.\n * Use this throughout your application.\n */\nexport const RequestContext = new RequestContextImpl();\n"]}
@@ -0,0 +1,61 @@
1
+ import { TestCaseRecorder } from '@webpieces/core-util';
2
+ import { HttpRequest } from './HttpRequest';
3
+ /**
4
+ * RequestContextHeaders - the magic context ↔ the wire, for a SERVER. Both directions live here:
5
+ *
6
+ * inbound {@link fillFromRequest} the published HttpRequest's headers -> the context
7
+ * outbound {@link buildOutboundHeaders} the context -> the next hop's headers
8
+ *
9
+ * Reads the AsyncLocalStorage-backed {@link RequestContext} straight through — no ContextReader,
10
+ * no ContextMgr, no abstract base. A server has exactly one place its context lives, and the
11
+ * indirection only hid the failure below. (The browser's answer is `ContextMgr` in
12
+ * @webpieces/core-util, which reads an app-held store because a browser has no ambient scope.)
13
+ *
14
+ * FAILS FAST outside a RequestContext. Silently sending an outbound call with NO request id or
15
+ * tenant is far worse than a loud error — the trace just disappears and you find out in production. Every server-side client (RPC and Cloud Tasks) therefore only works
16
+ * inside `RequestContext.run(...)`, which a top-level server filter normally establishes for you.
17
+ *
18
+ * Stateless once built, so it binds as a framework singleton every server-side client shares.
19
+ */
20
+ export declare class RequestContextHeaders {
21
+ /**
22
+ * EVERY transferred key with a non-empty value, under its wire name. Nothing is rewritten.
23
+ *
24
+ * That includes `x-request-id`, which propagates unchanged: one id correlates the whole call
25
+ * tree, so the callee keeps ours rather than minting its own. ({@link fillFromRequest} only
26
+ * generates an id when the inbound request carries none.)
27
+ *
28
+ * Values are RAW (unmasked) — this map goes on the wire, not in logs.
29
+ *
30
+ * @throws Error when called outside `RequestContext.run(...)` — see the class doc.
31
+ */
32
+ buildOutboundHeaders(): Map<string, string>;
33
+ /**
34
+ * INBOUND — the exact inverse of {@link buildOutboundHeaders}. Publish the request, move every
35
+ * transferrable header off it into the context (read by wire name, stored under the key's
36
+ * `name`), and mint an `x-request-id` if the caller sent none.
37
+ *
38
+ * The request is a PARAMETER, not something we fish back out of the context. Publishing and
39
+ * filling are therefore one atomic step that cannot be half-done or done out of order — the
40
+ * older `setRequest()` + `fillContext()` pair could silently skip the transfer entirely when a
41
+ * caller forgot the first half.
42
+ *
43
+ * This is a PRECONDITION of calling into http-routing, and it belongs ABOVE the api boundary.
44
+ * `WebpiecesMiddleware` does it for every HTTP request; a non-webpieces transport (or a test
45
+ * driving `createApiClient` directly) must do the same. The api proxy only checks that a
46
+ * request scope exists — it never builds one.
47
+ *
48
+ * @throws Error when called outside `RequestContext.run(...)`.
49
+ */
50
+ fillFromRequest(request: HttpRequest): void;
51
+ /** The id every log line of this request, and every downstream hop, will carry. */
52
+ private generateRequestId;
53
+ /**
54
+ * The recorder travelling in the context, when a test is recording this call. Absent in normal
55
+ * operation, and ALWAYS absent in a browser — which is why recording lives on the server-side
56
+ * client and never in the isomorphic core.
57
+ */
58
+ findRecorder(): TestCaseRecorder | undefined;
59
+ /** Guard both directions: no ambient request scope means there is no context to fill or read. */
60
+ private requireActiveContext;
61
+ }
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RequestContextHeaders = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const inversify_1 = require("inversify");
6
+ const core_util_1 = require("@webpieces/core-util");
7
+ const frameworkProvide_1 = require("./frameworkProvide");
8
+ const RequestContext_1 = require("./RequestContext");
9
+ /**
10
+ * RequestContextHeaders - the magic context ↔ the wire, for a SERVER. Both directions live here:
11
+ *
12
+ * inbound {@link fillFromRequest} the published HttpRequest's headers -> the context
13
+ * outbound {@link buildOutboundHeaders} the context -> the next hop's headers
14
+ *
15
+ * Reads the AsyncLocalStorage-backed {@link RequestContext} straight through — no ContextReader,
16
+ * no ContextMgr, no abstract base. A server has exactly one place its context lives, and the
17
+ * indirection only hid the failure below. (The browser's answer is `ContextMgr` in
18
+ * @webpieces/core-util, which reads an app-held store because a browser has no ambient scope.)
19
+ *
20
+ * FAILS FAST outside a RequestContext. Silently sending an outbound call with NO request id or
21
+ * tenant is far worse than a loud error — the trace just disappears and you find out in production. Every server-side client (RPC and Cloud Tasks) therefore only works
22
+ * inside `RequestContext.run(...)`, which a top-level server filter normally establishes for you.
23
+ *
24
+ * Stateless once built, so it binds as a framework singleton every server-side client shares.
25
+ */
26
+ let RequestContextHeaders = class RequestContextHeaders {
27
+ /**
28
+ * EVERY transferred key with a non-empty value, under its wire name. Nothing is rewritten.
29
+ *
30
+ * That includes `x-request-id`, which propagates unchanged: one id correlates the whole call
31
+ * tree, so the callee keeps ours rather than minting its own. ({@link fillFromRequest} only
32
+ * generates an id when the inbound request carries none.)
33
+ *
34
+ * Values are RAW (unmasked) — this map goes on the wire, not in logs.
35
+ *
36
+ * @throws Error when called outside `RequestContext.run(...)` — see the class doc.
37
+ */
38
+ buildOutboundHeaders() {
39
+ this.requireActiveContext();
40
+ const headers = new Map();
41
+ // getTransferredKeys() is precomputed at configure() time.
42
+ for (const key of core_util_1.HeaderRegistry.get().getTransferredKeys()) {
43
+ const value = RequestContext_1.RequestContext.getHeader(key);
44
+ if (value !== undefined && value !== null && value !== '') {
45
+ headers.set(key.httpHeader, value);
46
+ }
47
+ }
48
+ return headers;
49
+ }
50
+ /**
51
+ * INBOUND — the exact inverse of {@link buildOutboundHeaders}. Publish the request, move every
52
+ * transferrable header off it into the context (read by wire name, stored under the key's
53
+ * `name`), and mint an `x-request-id` if the caller sent none.
54
+ *
55
+ * The request is a PARAMETER, not something we fish back out of the context. Publishing and
56
+ * filling are therefore one atomic step that cannot be half-done or done out of order — the
57
+ * older `setRequest()` + `fillContext()` pair could silently skip the transfer entirely when a
58
+ * caller forgot the first half.
59
+ *
60
+ * This is a PRECONDITION of calling into http-routing, and it belongs ABOVE the api boundary.
61
+ * `WebpiecesMiddleware` does it for every HTTP request; a non-webpieces transport (or a test
62
+ * driving `createApiClient` directly) must do the same. The api proxy only checks that a
63
+ * request scope exists — it never builds one.
64
+ *
65
+ * @throws Error when called outside `RequestContext.run(...)`.
66
+ */
67
+ fillFromRequest(request) {
68
+ this.requireActiveContext();
69
+ RequestContext_1.RequestContext.setRequest(request);
70
+ // getTransferredKeys() is precomputed at configure() time.
71
+ for (const key of core_util_1.HeaderRegistry.get().getTransferredKeys()) {
72
+ const values = request.getHeaderValues(key);
73
+ if (values && values.length > 0) {
74
+ RequestContext_1.RequestContext.putHeader(key, values[0]);
75
+ }
76
+ }
77
+ if (!RequestContext_1.RequestContext.hasHeader(core_util_1.WebpiecesCoreHeaders.REQUEST_ID)) {
78
+ RequestContext_1.RequestContext.putHeader(core_util_1.WebpiecesCoreHeaders.REQUEST_ID, this.generateRequestId());
79
+ }
80
+ }
81
+ /** The id every log line of this request, and every downstream hop, will carry. */
82
+ generateRequestId() {
83
+ return `svrGenReqId-${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
84
+ }
85
+ /**
86
+ * The recorder travelling in the context, when a test is recording this call. Absent in normal
87
+ * operation, and ALWAYS absent in a browser — which is why recording lives on the server-side
88
+ * client and never in the isomorphic core.
89
+ */
90
+ findRecorder() {
91
+ if (!RequestContext_1.RequestContext.isActive()) {
92
+ return undefined;
93
+ }
94
+ return RequestContext_1.RequestContext.getHeader(core_util_1.RecorderKeys.RECORDER);
95
+ }
96
+ /** Guard both directions: no ambient request scope means there is no context to fill or read. */
97
+ requireActiveContext() {
98
+ if (!RequestContext_1.RequestContext.isActive()) {
99
+ throw new Error('No active RequestContext. A webpieces server-side client only works inside ' +
100
+ 'RequestContext.run(...), which a top-level server filter normally establishes. ' +
101
+ 'In a test, wrap the call: await RequestContext.run(async () => client.foo(req));');
102
+ }
103
+ }
104
+ };
105
+ exports.RequestContextHeaders = RequestContextHeaders;
106
+ exports.RequestContextHeaders = RequestContextHeaders = tslib_1.__decorate([
107
+ (0, frameworkProvide_1.provideFrameworkSingleton)(),
108
+ (0, inversify_1.injectable)()
109
+ ], RequestContextHeaders);
110
+ //# sourceMappingURL=RequestContextHeaders.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RequestContextHeaders.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/RequestContextHeaders.ts"],"names":[],"mappings":";;;;AAAA,yCAAuC;AACvC,oDAK8B;AAC9B,yDAA+D;AAE/D,qDAAkD;AAElD;;;;;;;;;;;;;;;;GAgBG;AAGI,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IAC9B;;;;;;;;;;OAUG;IACH,oBAAoB;QAChB,IAAI,CAAC,oBAAoB,EAAE,CAAC;QAE5B,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;QAC1C,2DAA2D;QAC3D,KAAK,MAAM,GAAG,IAAI,0BAAc,CAAC,GAAG,EAAE,CAAC,kBAAkB,EAAE,EAAE,CAAC;YAC1D,MAAM,KAAK,GAAG,+BAAc,CAAC,SAAS,CAAS,GAAG,CAAC,CAAC;YACpD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;gBACxD,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,UAAW,EAAE,KAAK,CAAC,CAAC;YACxC,CAAC;QACL,CAAC;QAED,OAAO,OAAO,CAAC;IACnB,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,eAAe,CAAC,OAAoB;QAChC,IAAI,CAAC,oBAAoB,EAAE,CAAC;QAE5B,+BAAc,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QAEnC,2DAA2D;QAC3D,KAAK,MAAM,GAAG,IAAI,0BAAc,CAAC,GAAG,EAAE,CAAC,kBAAkB,EAAE,EAAE,CAAC;YAC1D,MAAM,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;YAC5C,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC9B,+BAAc,CAAC,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YAC7C,CAAC;QACL,CAAC;QAED,IAAI,CAAC,+BAAc,CAAC,SAAS,CAAC,gCAAoB,CAAC,UAAU,CAAC,EAAE,CAAC;YAC7D,+BAAc,CAAC,SAAS,CAAC,gCAAoB,CAAC,UAAU,EAAE,IAAI,CAAC,iBAAiB,EAAE,CAAC,CAAC;QACxF,CAAC;IACL,CAAC;IAED,mFAAmF;IAC3E,iBAAiB;QACrB,OAAO,eAAe,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;IACtF,CAAC;IAED;;;;OAIG;IACH,YAAY;QACR,IAAI,CAAC,+BAAc,CAAC,QAAQ,EAAE,EAAE,CAAC;YAC7B,OAAO,SAAS,CAAC;QACrB,CAAC;QACD,OAAO,+BAAc,CAAC,SAAS,CAAmB,wBAAY,CAAC,QAAQ,CAAC,CAAC;IAC7E,CAAC;IAED,iGAAiG;IACzF,oBAAoB;QACxB,IAAI,CAAC,+BAAc,CAAC,QAAQ,EAAE,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CACX,6EAA6E;gBAC7E,iFAAiF;gBACjF,kFAAkF,CACrF,CAAC;QACN,CAAC;IACL,CAAC;CACJ,CAAA;AAzFY,sDAAqB;gCAArB,qBAAqB;IAFjC,IAAA,4CAAyB,GAAE;IAC3B,IAAA,sBAAU,GAAE;GACA,qBAAqB,CAyFjC","sourcesContent":["import { injectable } from 'inversify';\nimport {\n HeaderRegistry,\n RecorderKeys,\n TestCaseRecorder,\n WebpiecesCoreHeaders,\n} from '@webpieces/core-util';\nimport { provideFrameworkSingleton } from './frameworkProvide';\nimport { HttpRequest } from './HttpRequest';\nimport { RequestContext } from './RequestContext';\n\n/**\n * RequestContextHeaders - the magic context ↔ the wire, for a SERVER. Both directions live here:\n *\n * inbound {@link fillFromRequest} the published HttpRequest's headers -> the context\n * outbound {@link buildOutboundHeaders} the context -> the next hop's headers\n *\n * Reads the AsyncLocalStorage-backed {@link RequestContext} straight through — no ContextReader,\n * no ContextMgr, no abstract base. A server has exactly one place its context lives, and the\n * indirection only hid the failure below. (The browser's answer is `ContextMgr` in\n * @webpieces/core-util, which reads an app-held store because a browser has no ambient scope.)\n *\n * FAILS FAST outside a RequestContext. Silently sending an outbound call with NO request id or\n * tenant is far worse than a loud error — the trace just disappears and you find out in production. Every server-side client (RPC and Cloud Tasks) therefore only works\n * inside `RequestContext.run(...)`, which a top-level server filter normally establishes for you.\n *\n * Stateless once built, so it binds as a framework singleton every server-side client shares.\n */\n@provideFrameworkSingleton()\n@injectable()\nexport class RequestContextHeaders {\n /**\n * EVERY transferred key with a non-empty value, under its wire name. Nothing is rewritten.\n *\n * That includes `x-request-id`, which propagates unchanged: one id correlates the whole call\n * tree, so the callee keeps ours rather than minting its own. ({@link fillFromRequest} only\n * generates an id when the inbound request carries none.)\n *\n * Values are RAW (unmasked) — this map goes on the wire, not in logs.\n *\n * @throws Error when called outside `RequestContext.run(...)` — see the class doc.\n */\n buildOutboundHeaders(): Map<string, string> {\n this.requireActiveContext();\n\n const headers = new Map<string, string>();\n // getTransferredKeys() is precomputed at configure() time.\n for (const key of HeaderRegistry.get().getTransferredKeys()) {\n const value = RequestContext.getHeader<string>(key);\n if (value !== undefined && value !== null && value !== '') {\n headers.set(key.httpHeader!, value);\n }\n }\n\n return headers;\n }\n\n /**\n * INBOUND — the exact inverse of {@link buildOutboundHeaders}. Publish the request, move every\n * transferrable header off it into the context (read by wire name, stored under the key's\n * `name`), and mint an `x-request-id` if the caller sent none.\n *\n * The request is a PARAMETER, not something we fish back out of the context. Publishing and\n * filling are therefore one atomic step that cannot be half-done or done out of order — the\n * older `setRequest()` + `fillContext()` pair could silently skip the transfer entirely when a\n * caller forgot the first half.\n *\n * This is a PRECONDITION of calling into http-routing, and it belongs ABOVE the api boundary.\n * `WebpiecesMiddleware` does it for every HTTP request; a non-webpieces transport (or a test\n * driving `createApiClient` directly) must do the same. The api proxy only checks that a\n * request scope exists — it never builds one.\n *\n * @throws Error when called outside `RequestContext.run(...)`.\n */\n fillFromRequest(request: HttpRequest): void {\n this.requireActiveContext();\n\n RequestContext.setRequest(request);\n\n // getTransferredKeys() is precomputed at configure() time.\n for (const key of HeaderRegistry.get().getTransferredKeys()) {\n const values = request.getHeaderValues(key);\n if (values && values.length > 0) {\n RequestContext.putHeader(key, values[0]);\n }\n }\n\n if (!RequestContext.hasHeader(WebpiecesCoreHeaders.REQUEST_ID)) {\n RequestContext.putHeader(WebpiecesCoreHeaders.REQUEST_ID, this.generateRequestId());\n }\n }\n\n /** The id every log line of this request, and every downstream hop, will carry. */\n private generateRequestId(): string {\n return `svrGenReqId-${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;\n }\n\n /**\n * The recorder travelling in the context, when a test is recording this call. Absent in normal\n * operation, and ALWAYS absent in a browser — which is why recording lives on the server-side\n * client and never in the isomorphic core.\n */\n findRecorder(): TestCaseRecorder | undefined {\n if (!RequestContext.isActive()) {\n return undefined;\n }\n return RequestContext.getHeader<TestCaseRecorder>(RecorderKeys.RECORDER);\n }\n\n /** Guard both directions: no ambient request scope means there is no context to fill or read. */\n private requireActiveContext(): void {\n if (!RequestContext.isActive()) {\n throw new Error(\n 'No active RequestContext. A webpieces server-side client only works inside ' +\n 'RequestContext.run(...), which a top-level server filter normally establishes. ' +\n 'In a test, wrap the call: await RequestContext.run(async () => client.foo(req));',\n );\n }\n }\n}\n"]}
@@ -1,5 +1,23 @@
1
1
  import { ContainerModule } from 'inversify';
2
2
  import type { ServiceIdentifier } from 'inversify';
3
+ /**
4
+ * Framework-only DI provider decorators — a SEPARATE registry from the client-facing
5
+ * @provideSingleton (which uses @inversifyjs/binding-decorators' single global registry).
6
+ *
7
+ * WHY: binding-decorators registers every @provideSingleton class under ONE global
8
+ * reflect-metadata key, and buildProviderModule() scoops up that whole key. If webpieces
9
+ * framework classes (RouteBuilderImpl, the filters, WebpiecesRouter) used @provideSingleton,
10
+ * a CLIENT app's buildProviderModule() would drag those framework internals into its own
11
+ * container. To keep the two worlds separate:
12
+ * - packages/** (framework libs) MUST use provideFrameworkSingleton (this registry),
13
+ * enforced by the no-global-providesingleton-in-packages ESLint rule.
14
+ * - apps/** (and downstream client projects) use plain @provideSingleton (the global one).
15
+ * The router loads BOTH buildFrameworkModule() and buildProviderModule(), so everything
16
+ * resolves — but a client's buildProviderModule() only ever sees the client's own classes.
17
+ */
18
+ type AnyCtor = new (...args: any[]) => unknown;
19
+ /** How a framework binding is scoped. Always explicit — never inherited from the container. */
20
+ export type FrameworkScope = 'singleton' | 'transient';
3
21
  /**
4
22
  * Framework equivalent of @provideSingleton: registers the class as a singleton bound to
5
23
  * itself, into the webpieces framework registry (NOT the binding-decorators global one).
@@ -11,8 +29,36 @@ export declare function provideFrameworkSingleton(): ClassDecorator;
11
29
  */
12
30
  export declare function provideFrameworkSingletonAs<T>(serviceIdentifier: ServiceIdentifier<T>): ClassDecorator;
13
31
  /**
14
- * Build a ContainerModule binding every provideFrameworkSingleton(As) class. Load this into
15
- * the webpieces framework + app containers (the router does this) alongside the client's own
16
- * buildProviderModule().
32
+ * Framework equivalent of @provideTransient: a NEW instance on every resolve. Use it for a
33
+ * class a {@link Provider} hands out per call e.g. one ProxyClient per API contract.
34
+ */
35
+ export declare function provideFrameworkTransient(): ClassDecorator;
36
+ /**
37
+ * Register a {@link Provider} subclass as the DI token that hands out `target` instances.
38
+ *
39
+ * The provider caches nothing; `target`'s own binding scope decides whether callers share one
40
+ * instance (provideFrameworkSingleton -> lazy singleton) or get a fresh one each `get()`
41
+ * (provideFrameworkTransient -> 1-to-many).
42
+ *
43
+ * The provider itself is a singleton — it holds only the resolve-lambda.
44
+ *
45
+ * `Provider<T>` is erased at runtime and cannot be its own token, so name one after T. The DI-graph
46
+ * analyzer reads `target` from HERE, which is why it can draw `Consumer -> T` with no provider box:
47
+ * a Provider is DI plumbing, not wiring anyone needs to see.
48
+ *
49
+ * ```typescript
50
+ * // webpieces-disable no-symbol-di-tokens -- Provider<T> is erased at runtime; T names the token
51
+ * export const PROXY_CLIENT_PROVIDER = Symbol.for('ProxyClientProvider');
52
+ * bindFrameworkProvider(PROXY_CLIENT_PROVIDER, NodeProxyClient);
53
+ *
54
+ * constructor(@inject(PROXY_CLIENT_PROVIDER) private readonly provider: Provider<NodeProxyClient>) {}
55
+ * ```
56
+ */
57
+ export declare function bindFrameworkProvider(token: ServiceIdentifier, target: AnyCtor): void;
58
+ /**
59
+ * Build a ContainerModule binding every provideFrameworkSingleton(As)/Transient class, then
60
+ * every registered Provider. Load this into the webpieces framework + app containers (the
61
+ * router does this) alongside the client's own buildProviderModule().
17
62
  */
18
63
  export declare function buildFrameworkModule(): ContainerModule;
64
+ export {};
@@ -2,18 +2,34 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.provideFrameworkSingleton = provideFrameworkSingleton;
4
4
  exports.provideFrameworkSingletonAs = provideFrameworkSingletonAs;
5
+ exports.provideFrameworkTransient = provideFrameworkTransient;
6
+ exports.bindFrameworkProvider = bindFrameworkProvider;
5
7
  exports.buildFrameworkModule = buildFrameworkModule;
6
8
  const inversify_1 = require("inversify");
9
+ const provide_1 = require("./provide");
7
10
  class FrameworkBinding {
8
11
  serviceIdentifier;
9
12
  target;
10
- constructor(serviceIdentifier, target) {
13
+ scope;
14
+ constructor(serviceIdentifier, target, scope = 'singleton') {
11
15
  this.serviceIdentifier = serviceIdentifier;
12
16
  this.target = target;
17
+ this.scope = scope;
18
+ }
19
+ }
20
+ /** A Provider token paired with the class its get() resolves. See {@link bindFrameworkProvider}. */
21
+ class FrameworkProviderBinding {
22
+ token;
23
+ target;
24
+ constructor(token, target) {
25
+ this.token = token;
26
+ this.target = target;
13
27
  }
14
28
  }
15
29
  /** The webpieces-only binding registry (a plain module-level list, one per hosted core-context). */
16
30
  const frameworkRegistry = [];
31
+ /** Provider<T> bindings, applied after frameworkRegistry so their targets are already bound. */
32
+ const frameworkProviderRegistry = [];
17
33
  /**
18
34
  * Framework equivalent of @provideSingleton: registers the class as a singleton bound to
19
35
  * itself, into the webpieces framework registry (NOT the binding-decorators global one).
@@ -37,14 +53,65 @@ function provideFrameworkSingletonAs(serviceIdentifier) {
37
53
  };
38
54
  }
39
55
  /**
40
- * Build a ContainerModule binding every provideFrameworkSingleton(As) class. Load this into
41
- * the webpieces framework + app containers (the router does this) alongside the client's own
42
- * buildProviderModule().
56
+ * Framework equivalent of @provideTransient: a NEW instance on every resolve. Use it for a
57
+ * class a {@link Provider} hands out per call e.g. one ProxyClient per API contract.
58
+ */
59
+ // webpieces-disable no-function-outside-class -- a decorator factory cannot be a class method
60
+ function provideFrameworkTransient() {
61
+ // webpieces-disable no-any-unknown -- decorator target is any class constructor
62
+ return (target) => {
63
+ frameworkRegistry.push(new FrameworkBinding(target, target, 'transient'));
64
+ return target;
65
+ };
66
+ }
67
+ /**
68
+ * Register a {@link Provider} subclass as the DI token that hands out `target` instances.
69
+ *
70
+ * The provider caches nothing; `target`'s own binding scope decides whether callers share one
71
+ * instance (provideFrameworkSingleton -> lazy singleton) or get a fresh one each `get()`
72
+ * (provideFrameworkTransient -> 1-to-many).
73
+ *
74
+ * The provider itself is a singleton — it holds only the resolve-lambda.
75
+ *
76
+ * `Provider<T>` is erased at runtime and cannot be its own token, so name one after T. The DI-graph
77
+ * analyzer reads `target` from HERE, which is why it can draw `Consumer -> T` with no provider box:
78
+ * a Provider is DI plumbing, not wiring anyone needs to see.
79
+ *
80
+ * ```typescript
81
+ * // webpieces-disable no-symbol-di-tokens -- Provider<T> is erased at runtime; T names the token
82
+ * export const PROXY_CLIENT_PROVIDER = Symbol.for('ProxyClientProvider');
83
+ * bindFrameworkProvider(PROXY_CLIENT_PROVIDER, NodeProxyClient);
84
+ *
85
+ * constructor(@inject(PROXY_CLIENT_PROVIDER) private readonly provider: Provider<NodeProxyClient>) {}
86
+ * ```
87
+ */
88
+ // webpieces-disable no-function-outside-class -- registry side-effect, called at module scope beside the decorators
89
+ function bindFrameworkProvider(token, target) {
90
+ frameworkProviderRegistry.push(new FrameworkProviderBinding(token, target));
91
+ }
92
+ /**
93
+ * Build a ContainerModule binding every provideFrameworkSingleton(As)/Transient class, then
94
+ * every registered Provider. Load this into the webpieces framework + app containers (the
95
+ * router does this) alongside the client's own buildProviderModule().
43
96
  */
44
97
  function buildFrameworkModule() {
45
98
  return new inversify_1.ContainerModule((options) => {
46
99
  for (const binding of frameworkRegistry) {
47
- options.bind(binding.serviceIdentifier).to(binding.target).inSingletonScope();
100
+ const bindTo = options.bind(binding.serviceIdentifier).to(binding.target);
101
+ if (binding.scope === 'transient') {
102
+ bindTo.inTransientScope();
103
+ }
104
+ else {
105
+ bindTo.inSingletonScope();
106
+ }
107
+ }
108
+ for (const binding of frameworkProviderRegistry) {
109
+ // toDynamicValue so the provider closes over the ResolutionContext. Each get() then
110
+ // re-resolves `target`, letting TARGET's scope decide shared-vs-fresh.
111
+ options
112
+ .bind(binding.token)
113
+ .toDynamicValue((context) => new provide_1.Provider(() => context.get(binding.target)))
114
+ .inSingletonScope();
48
115
  }
49
116
  });
50
117
  }
@@ -1 +1 @@
1
- {"version":3,"file":"frameworkProvide.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/frameworkProvide.ts"],"names":[],"mappings":";;AAoCA,8DAMC;AAMD,kEAMC;AAOD,oDAMC;AAnED,yCAA4C;AAsB5C,MAAM,gBAAgB;IAEE;IACA;IAFpB,YACoB,iBAAoC,EACpC,MAAe;QADf,sBAAiB,GAAjB,iBAAiB,CAAmB;QACpC,WAAM,GAAN,MAAM,CAAS;IAChC,CAAC;CACP;AAED,oGAAoG;AACpG,MAAM,iBAAiB,GAAuB,EAAE,CAAC;AAEjD;;;GAGG;AACH,SAAgB,yBAAyB;IACrC,gFAAgF;IAChF,OAAO,CAAC,MAAW,EAAE,EAAE;QACnB,iBAAiB,CAAC,IAAI,CAAC,IAAI,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QAC7D,OAAO,MAAM,CAAC;IAClB,CAAC,CAAC;AACN,CAAC;AAED;;;GAGG;AACH,SAAgB,2BAA2B,CAAI,iBAAuC;IAClF,gFAAgF;IAChF,OAAO,CAAC,MAAW,EAAE,EAAE;QACnB,iBAAiB,CAAC,IAAI,CAAC,IAAI,gBAAgB,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC,CAAC;QACxE,OAAO,MAAM,CAAC;IAClB,CAAC,CAAC;AACN,CAAC;AAED;;;;GAIG;AACH,SAAgB,oBAAoB;IAChC,OAAO,IAAI,2BAAe,CAAC,CAAC,OAAmC,EAAE,EAAE;QAC/D,KAAK,MAAM,OAAO,IAAI,iBAAiB,EAAE,CAAC;YACtC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,gBAAgB,EAAE,CAAC;QAClF,CAAC;IACL,CAAC,CAAC,CAAC;AACP,CAAC","sourcesContent":["import { ContainerModule } from 'inversify';\nimport type { ContainerModuleLoadOptions, ServiceIdentifier } from 'inversify';\n\n/**\n * Framework-only DI provider decorators — a SEPARATE registry from the client-facing\n * @provideSingleton (which uses @inversifyjs/binding-decorators' single global registry).\n *\n * WHY: binding-decorators registers every @provideSingleton class under ONE global\n * reflect-metadata key, and buildProviderModule() scoops up that whole key. If webpieces\n * framework classes (RouteBuilderImpl, the filters, WebpiecesRouter) used @provideSingleton,\n * a CLIENT app's buildProviderModule() would drag those framework internals into its own\n * container. To keep the two worlds separate:\n * - packages/** (framework libs) MUST use provideFrameworkSingleton (this registry),\n * enforced by the no-global-providesingleton-in-packages ESLint rule.\n * - apps/** (and downstream client projects) use plain @provideSingleton (the global one).\n * The router loads BOTH buildFrameworkModule() and buildProviderModule(), so everything\n * resolves — but a client's buildProviderModule() only ever sees the client's own classes.\n */\n\n// webpieces-disable no-any-unknown -- decorator targets are arbitrary class constructors\ntype AnyCtor = new (...args: any[]) => unknown;\n\nclass FrameworkBinding {\n constructor(\n public readonly serviceIdentifier: ServiceIdentifier,\n public readonly target: AnyCtor,\n ) {}\n}\n\n/** The webpieces-only binding registry (a plain module-level list, one per hosted core-context). */\nconst frameworkRegistry: FrameworkBinding[] = [];\n\n/**\n * Framework equivalent of @provideSingleton: registers the class as a singleton bound to\n * itself, into the webpieces framework registry (NOT the binding-decorators global one).\n */\nexport function provideFrameworkSingleton(): ClassDecorator {\n // webpieces-disable no-any-unknown -- decorator target is any class constructor\n return (target: any) => {\n frameworkRegistry.push(new FrameworkBinding(target, target));\n return target;\n };\n}\n\n/**\n * Framework equivalent of @provideSingletonAs: binds the impl to a token (Symbol or abstract\n * class) as a singleton, into the webpieces framework registry.\n */\nexport function provideFrameworkSingletonAs<T>(serviceIdentifier: ServiceIdentifier<T>): ClassDecorator {\n // webpieces-disable no-any-unknown -- decorator target is any class constructor\n return (target: any) => {\n frameworkRegistry.push(new FrameworkBinding(serviceIdentifier, target));\n return target;\n };\n}\n\n/**\n * Build a ContainerModule binding every provideFrameworkSingleton(As) class. Load this into\n * the webpieces framework + app containers (the router does this) alongside the client's own\n * buildProviderModule().\n */\nexport function buildFrameworkModule(): ContainerModule {\n return new ContainerModule((options: ContainerModuleLoadOptions) => {\n for (const binding of frameworkRegistry) {\n options.bind(binding.serviceIdentifier).to(binding.target).inSingletonScope();\n }\n });\n}\n"]}
1
+ {"version":3,"file":"frameworkProvide.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/frameworkProvide.ts"],"names":[],"mappings":";;AAoDA,8DAMC;AAMD,kEAMC;AAOD,8DAMC;AAwBD,sDAEC;AAOD,oDAoBC;AAxID,yCAA4C;AAE5C,uCAAqC;AAwBrC,MAAM,gBAAgB;IAEE;IACA;IACA;IAHpB,YACoB,iBAAoC,EACpC,MAAe,EACf,QAAwB,WAAW;QAFnC,sBAAiB,GAAjB,iBAAiB,CAAmB;QACpC,WAAM,GAAN,MAAM,CAAS;QACf,UAAK,GAAL,KAAK,CAA8B;IACpD,CAAC;CACP;AAED,oGAAoG;AACpG,MAAM,wBAAwB;IAEN;IACA;IAFpB,YACoB,KAAwB,EACxB,MAAe;QADf,UAAK,GAAL,KAAK,CAAmB;QACxB,WAAM,GAAN,MAAM,CAAS;IAChC,CAAC;CACP;AAED,oGAAoG;AACpG,MAAM,iBAAiB,GAAuB,EAAE,CAAC;AAEjD,gGAAgG;AAChG,MAAM,yBAAyB,GAA+B,EAAE,CAAC;AAEjE;;;GAGG;AACH,SAAgB,yBAAyB;IACrC,gFAAgF;IAChF,OAAO,CAAC,MAAW,EAAE,EAAE;QACnB,iBAAiB,CAAC,IAAI,CAAC,IAAI,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QAC7D,OAAO,MAAM,CAAC;IAClB,CAAC,CAAC;AACN,CAAC;AAED;;;GAGG;AACH,SAAgB,2BAA2B,CAAI,iBAAuC;IAClF,gFAAgF;IAChF,OAAO,CAAC,MAAW,EAAE,EAAE;QACnB,iBAAiB,CAAC,IAAI,CAAC,IAAI,gBAAgB,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC,CAAC;QACxE,OAAO,MAAM,CAAC;IAClB,CAAC,CAAC;AACN,CAAC;AAED;;;GAGG;AACH,8FAA8F;AAC9F,SAAgB,yBAAyB;IACrC,gFAAgF;IAChF,OAAO,CAAC,MAAW,EAAE,EAAE;QACnB,iBAAiB,CAAC,IAAI,CAAC,IAAI,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;QAC1E,OAAO,MAAM,CAAC;IAClB,CAAC,CAAC;AACN,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,oHAAoH;AACpH,SAAgB,qBAAqB,CAAC,KAAwB,EAAE,MAAe;IAC3E,yBAAyB,CAAC,IAAI,CAAC,IAAI,wBAAwB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;AAChF,CAAC;AAED;;;;GAIG;AACH,SAAgB,oBAAoB;IAChC,OAAO,IAAI,2BAAe,CAAC,CAAC,OAAmC,EAAE,EAAE;QAC/D,KAAK,MAAM,OAAO,IAAI,iBAAiB,EAAE,CAAC;YACtC,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YAC1E,IAAI,OAAO,CAAC,KAAK,KAAK,WAAW,EAAE,CAAC;gBAChC,MAAM,CAAC,gBAAgB,EAAE,CAAC;YAC9B,CAAC;iBAAM,CAAC;gBACJ,MAAM,CAAC,gBAAgB,EAAE,CAAC;YAC9B,CAAC;QACL,CAAC;QACD,KAAK,MAAM,OAAO,IAAI,yBAAyB,EAAE,CAAC;YAC9C,oFAAoF;YACpF,uEAAuE;YACvE,OAAO;iBACF,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;iBACnB,cAAc,CAAC,CAAC,OAA0B,EAAE,EAAE,CAC3C,IAAI,kBAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;iBACnD,gBAAgB,EAAE,CAAC;QAC5B,CAAC;IACL,CAAC,CAAC,CAAC;AACP,CAAC","sourcesContent":["import { ContainerModule } from 'inversify';\nimport type { ContainerModuleLoadOptions, ResolutionContext, ServiceIdentifier } from 'inversify';\nimport { Provider } from './provide';\n\n/**\n * Framework-only DI provider decorators — a SEPARATE registry from the client-facing\n * @provideSingleton (which uses @inversifyjs/binding-decorators' single global registry).\n *\n * WHY: binding-decorators registers every @provideSingleton class under ONE global\n * reflect-metadata key, and buildProviderModule() scoops up that whole key. If webpieces\n * framework classes (RouteBuilderImpl, the filters, WebpiecesRouter) used @provideSingleton,\n * a CLIENT app's buildProviderModule() would drag those framework internals into its own\n * container. To keep the two worlds separate:\n * - packages/** (framework libs) MUST use provideFrameworkSingleton (this registry),\n * enforced by the no-global-providesingleton-in-packages ESLint rule.\n * - apps/** (and downstream client projects) use plain @provideSingleton (the global one).\n * The router loads BOTH buildFrameworkModule() and buildProviderModule(), so everything\n * resolves — but a client's buildProviderModule() only ever sees the client's own classes.\n */\n\n// webpieces-disable no-any-unknown -- decorator targets are arbitrary class constructors\ntype AnyCtor = new (...args: any[]) => unknown;\n\n/** How a framework binding is scoped. Always explicit — never inherited from the container. */\nexport type FrameworkScope = 'singleton' | 'transient';\n\nclass FrameworkBinding {\n constructor(\n public readonly serviceIdentifier: ServiceIdentifier,\n public readonly target: AnyCtor,\n public readonly scope: FrameworkScope = 'singleton',\n ) {}\n}\n\n/** A Provider token paired with the class its get() resolves. See {@link bindFrameworkProvider}. */\nclass FrameworkProviderBinding {\n constructor(\n public readonly token: ServiceIdentifier,\n public readonly target: AnyCtor,\n ) {}\n}\n\n/** The webpieces-only binding registry (a plain module-level list, one per hosted core-context). */\nconst frameworkRegistry: FrameworkBinding[] = [];\n\n/** Provider<T> bindings, applied after frameworkRegistry so their targets are already bound. */\nconst frameworkProviderRegistry: FrameworkProviderBinding[] = [];\n\n/**\n * Framework equivalent of @provideSingleton: registers the class as a singleton bound to\n * itself, into the webpieces framework registry (NOT the binding-decorators global one).\n */\nexport function provideFrameworkSingleton(): ClassDecorator {\n // webpieces-disable no-any-unknown -- decorator target is any class constructor\n return (target: any) => {\n frameworkRegistry.push(new FrameworkBinding(target, target));\n return target;\n };\n}\n\n/**\n * Framework equivalent of @provideSingletonAs: binds the impl to a token (Symbol or abstract\n * class) as a singleton, into the webpieces framework registry.\n */\nexport function provideFrameworkSingletonAs<T>(serviceIdentifier: ServiceIdentifier<T>): ClassDecorator {\n // webpieces-disable no-any-unknown -- decorator target is any class constructor\n return (target: any) => {\n frameworkRegistry.push(new FrameworkBinding(serviceIdentifier, target));\n return target;\n };\n}\n\n/**\n * Framework equivalent of @provideTransient: a NEW instance on every resolve. Use it for a\n * class a {@link Provider} hands out per call — e.g. one ProxyClient per API contract.\n */\n// webpieces-disable no-function-outside-class -- a decorator factory cannot be a class method\nexport function provideFrameworkTransient(): ClassDecorator {\n // webpieces-disable no-any-unknown -- decorator target is any class constructor\n return (target: any) => {\n frameworkRegistry.push(new FrameworkBinding(target, target, 'transient'));\n return target;\n };\n}\n\n/**\n * Register a {@link Provider} subclass as the DI token that hands out `target` instances.\n *\n * The provider caches nothing; `target`'s own binding scope decides whether callers share one\n * instance (provideFrameworkSingleton -> lazy singleton) or get a fresh one each `get()`\n * (provideFrameworkTransient -> 1-to-many).\n *\n * The provider itself is a singleton — it holds only the resolve-lambda.\n *\n * `Provider<T>` is erased at runtime and cannot be its own token, so name one after T. The DI-graph\n * analyzer reads `target` from HERE, which is why it can draw `Consumer -> T` with no provider box:\n * a Provider is DI plumbing, not wiring anyone needs to see.\n *\n * ```typescript\n * // webpieces-disable no-symbol-di-tokens -- Provider<T> is erased at runtime; T names the token\n * export const PROXY_CLIENT_PROVIDER = Symbol.for('ProxyClientProvider');\n * bindFrameworkProvider(PROXY_CLIENT_PROVIDER, NodeProxyClient);\n *\n * constructor(@inject(PROXY_CLIENT_PROVIDER) private readonly provider: Provider<NodeProxyClient>) {}\n * ```\n */\n// webpieces-disable no-function-outside-class -- registry side-effect, called at module scope beside the decorators\nexport function bindFrameworkProvider(token: ServiceIdentifier, target: AnyCtor): void {\n frameworkProviderRegistry.push(new FrameworkProviderBinding(token, target));\n}\n\n/**\n * Build a ContainerModule binding every provideFrameworkSingleton(As)/Transient class, then\n * every registered Provider. Load this into the webpieces framework + app containers (the\n * router does this) alongside the client's own buildProviderModule().\n */\nexport function buildFrameworkModule(): ContainerModule {\n return new ContainerModule((options: ContainerModuleLoadOptions) => {\n for (const binding of frameworkRegistry) {\n const bindTo = options.bind(binding.serviceIdentifier).to(binding.target);\n if (binding.scope === 'transient') {\n bindTo.inTransientScope();\n } else {\n bindTo.inSingletonScope();\n }\n }\n for (const binding of frameworkProviderRegistry) {\n // toDynamicValue so the provider closes over the ResolutionContext. Each get() then\n // re-resolves `target`, letting TARGET's scope decide shared-vs-fresh.\n options\n .bind(binding.token)\n .toDynamicValue((context: ResolutionContext) =>\n new Provider(() => context.get(binding.target)))\n .inSingletonScope();\n }\n });\n}\n"]}
package/src/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export { RequestContext } from './RequestContext';
2
2
  export { HttpRequest } from './HttpRequest';
3
3
  export { provideSingleton, provideSingletonAs, provideTransient } from './provide';
4
- export { provideFrameworkSingleton, provideFrameworkSingletonAs, buildFrameworkModule, } from './frameworkProvide';
5
- export { ContextMgr, RequestIdChainProcessor } from '@webpieces/core-util';
4
+ export { Provider } from './provide';
5
+ export { provideFrameworkSingleton, provideFrameworkSingletonAs, provideFrameworkTransient, bindFrameworkProvider, buildFrameworkModule, } from './frameworkProvide';
6
+ export type { FrameworkScope } from './frameworkProvide';
7
+ export { RequestContextHeaders } from './RequestContextHeaders';
6
8
  export { RequestContextReader } from './RequestContextReader';
package/src/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RequestContextReader = exports.RequestIdChainProcessor = exports.ContextMgr = exports.buildFrameworkModule = exports.provideFrameworkSingletonAs = exports.provideFrameworkSingleton = exports.provideTransient = exports.provideSingletonAs = exports.provideSingleton = exports.HttpRequest = exports.RequestContext = void 0;
3
+ exports.RequestContextReader = exports.RequestContextHeaders = exports.buildFrameworkModule = exports.bindFrameworkProvider = exports.provideFrameworkTransient = exports.provideFrameworkSingletonAs = exports.provideFrameworkSingleton = exports.Provider = exports.provideTransient = exports.provideSingletonAs = exports.provideSingleton = exports.HttpRequest = exports.RequestContext = void 0;
4
4
  // Context management with AsyncLocalStorage
5
5
  var RequestContext_1 = require("./RequestContext");
6
6
  Object.defineProperty(exports, "RequestContext", { enumerable: true, get: function () { return RequestContext_1.RequestContext; } });
@@ -12,18 +12,27 @@ var provide_1 = require("./provide");
12
12
  Object.defineProperty(exports, "provideSingleton", { enumerable: true, get: function () { return provide_1.provideSingleton; } });
13
13
  Object.defineProperty(exports, "provideSingletonAs", { enumerable: true, get: function () { return provide_1.provideSingletonAs; } });
14
14
  Object.defineProperty(exports, "provideTransient", { enumerable: true, get: function () { return provide_1.provideTransient; } });
15
+ // Guice-style Provider<T> — lazy singleton OR fresh-per-get, decided by T's binding scope.
16
+ var provide_2 = require("./provide");
17
+ Object.defineProperty(exports, "Provider", { enumerable: true, get: function () { return provide_2.Provider; } });
15
18
  // Framework-only DI registry (packages/** use these; keeps framework classes out of a
16
19
  // client's buildProviderModule() global scan). See frameworkProvide.ts.
17
20
  var frameworkProvide_1 = require("./frameworkProvide");
18
21
  Object.defineProperty(exports, "provideFrameworkSingleton", { enumerable: true, get: function () { return frameworkProvide_1.provideFrameworkSingleton; } });
19
22
  Object.defineProperty(exports, "provideFrameworkSingletonAs", { enumerable: true, get: function () { return frameworkProvide_1.provideFrameworkSingletonAs; } });
23
+ Object.defineProperty(exports, "provideFrameworkTransient", { enumerable: true, get: function () { return frameworkProvide_1.provideFrameworkTransient; } });
24
+ Object.defineProperty(exports, "bindFrameworkProvider", { enumerable: true, get: function () { return frameworkProvide_1.bindFrameworkProvider; } });
20
25
  Object.defineProperty(exports, "buildFrameworkModule", { enumerable: true, get: function () { return frameworkProvide_1.buildFrameworkModule; } });
21
- // Outbound-header machinery MOVED to browser+node @webpieces/core-util so the
22
- // isomorphic http-client can use ContextMgr without pulling in this Node-only
23
- // (AsyncLocalStorage) package. Re-exported here for back-compat.
24
- var core_util_1 = require("@webpieces/core-util");
25
- Object.defineProperty(exports, "ContextMgr", { enumerable: true, get: function () { return core_util_1.ContextMgr; } });
26
- Object.defineProperty(exports, "RequestIdChainProcessor", { enumerable: true, get: function () { return core_util_1.RequestIdChainProcessor; } });
26
+ // Outbound headers for a SERVER: reads RequestContext directly, fails fast outside
27
+ // RequestContext.run(...). Server-side clients (http-client-node, cloudtasks-client) and
28
+ // http-routing use THIS.
29
+ //
30
+ // ContextMgr is deliberately NOT re-exported. It is the browser's answer (an app-held store),
31
+ // and only @webpieces/http-client-browser may name it importing it here would let a node
32
+ // package reach for a ContextReader it has no use for.
33
+ var RequestContextHeaders_1 = require("./RequestContextHeaders");
34
+ Object.defineProperty(exports, "RequestContextHeaders", { enumerable: true, get: function () { return RequestContextHeaders_1.RequestContextHeaders; } });
35
+ // The browser store's server counterpart, still used by the logging packages + http-server filters.
27
36
  var RequestContextReader_1 = require("./RequestContextReader");
28
37
  Object.defineProperty(exports, "RequestContextReader", { enumerable: true, get: function () { return RequestContextReader_1.RequestContextReader; } });
29
38
  //# sourceMappingURL=index.js.map
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/index.ts"],"names":[],"mappings":";;;AAAA,4CAA4C;AAC5C,mDAAkD;AAAzC,gHAAA,cAAc,OAAA;AACvB,mGAAmG;AACnG,6CAA4C;AAAnC,0GAAA,WAAW,OAAA;AAEpB,mFAAmF;AACnF,qCAAmF;AAA1E,2GAAA,gBAAgB,OAAA;AAAE,6GAAA,kBAAkB,OAAA;AAAE,2GAAA,gBAAgB,OAAA;AAC/D,sFAAsF;AACtF,wEAAwE;AACxE,uDAI4B;AAHxB,6HAAA,yBAAyB,OAAA;AACzB,+HAAA,2BAA2B,OAAA;AAC3B,wHAAA,oBAAoB,OAAA;AAGxB,gFAAgF;AAChF,8EAA8E;AAC9E,iEAAiE;AACjE,kDAA2E;AAAlE,uGAAA,UAAU,OAAA;AAAE,oHAAA,uBAAuB,OAAA;AAC5C,+DAA8D;AAArD,4HAAA,oBAAoB,OAAA","sourcesContent":["// Context management with AsyncLocalStorage\nexport { RequestContext } from './RequestContext';\n// Transport-neutral request stored in the context (http-routing's request type; re-exported there)\nexport { HttpRequest } from './HttpRequest';\n\n// DI provider decorators (shared DI seam; http-routing re-exports for back-compat)\nexport { provideSingleton, provideSingletonAs, provideTransient } from './provide';\n// Framework-only DI registry (packages/** use these; keeps framework classes out of a\n// client's buildProviderModule() global scan). See frameworkProvide.ts.\nexport {\n provideFrameworkSingleton,\n provideFrameworkSingletonAs,\n buildFrameworkModule,\n} from './frameworkProvide';\n\n// Outbound-header machinery MOVED to browser+node @webpieces/core-util so the\n// isomorphic http-client can use ContextMgr without pulling in this Node-only\n// (AsyncLocalStorage) package. Re-exported here for back-compat.\nexport { ContextMgr, RequestIdChainProcessor } from '@webpieces/core-util';\nexport { RequestContextReader } from './RequestContextReader';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/index.ts"],"names":[],"mappings":";;;AAAA,4CAA4C;AAC5C,mDAAkD;AAAzC,gHAAA,cAAc,OAAA;AACvB,mGAAmG;AACnG,6CAA4C;AAAnC,0GAAA,WAAW,OAAA;AAEpB,mFAAmF;AACnF,qCAAmF;AAA1E,2GAAA,gBAAgB,OAAA;AAAE,6GAAA,kBAAkB,OAAA;AAAE,2GAAA,gBAAgB,OAAA;AAC/D,2FAA2F;AAC3F,qCAAqC;AAA5B,mGAAA,QAAQ,OAAA;AACjB,sFAAsF;AACtF,wEAAwE;AACxE,uDAM4B;AALxB,6HAAA,yBAAyB,OAAA;AACzB,+HAAA,2BAA2B,OAAA;AAC3B,6HAAA,yBAAyB,OAAA;AACzB,yHAAA,qBAAqB,OAAA;AACrB,wHAAA,oBAAoB,OAAA;AAIxB,mFAAmF;AACnF,yFAAyF;AACzF,yBAAyB;AACzB,EAAE;AACF,8FAA8F;AAC9F,2FAA2F;AAC3F,uDAAuD;AACvD,iEAAgE;AAAvD,8HAAA,qBAAqB,OAAA;AAC9B,oGAAoG;AACpG,+DAA8D;AAArD,4HAAA,oBAAoB,OAAA","sourcesContent":["// Context management with AsyncLocalStorage\nexport { RequestContext } from './RequestContext';\n// Transport-neutral request stored in the context (http-routing's request type; re-exported there)\nexport { HttpRequest } from './HttpRequest';\n\n// DI provider decorators (shared DI seam; http-routing re-exports for back-compat)\nexport { provideSingleton, provideSingletonAs, provideTransient } from './provide';\n// Guice-style Provider<T> — lazy singleton OR fresh-per-get, decided by T's binding scope.\nexport { Provider } from './provide';\n// Framework-only DI registry (packages/** use these; keeps framework classes out of a\n// client's buildProviderModule() global scan). See frameworkProvide.ts.\nexport {\n provideFrameworkSingleton,\n provideFrameworkSingletonAs,\n provideFrameworkTransient,\n bindFrameworkProvider,\n buildFrameworkModule,\n} from './frameworkProvide';\nexport type { FrameworkScope } from './frameworkProvide';\n\n// Outbound headers for a SERVER: reads RequestContext directly, fails fast outside\n// RequestContext.run(...). Server-side clients (http-client-node, cloudtasks-client) and\n// http-routing use THIS.\n//\n// ContextMgr is deliberately NOT re-exported. It is the browser's answer (an app-held store),\n// and only @webpieces/http-client-browser may name it — importing it here would let a node\n// package reach for a ContextReader it has no use for.\nexport { RequestContextHeaders } from './RequestContextHeaders';\n// The browser store's server counterpart, still used by the logging packages + http-server filters.\nexport { RequestContextReader } from './RequestContextReader';\n"]}
package/src/provide.d.ts CHANGED
@@ -47,3 +47,43 @@ export declare function provideSingletonAs<T>(serviceIdentifier: ServiceIdentifi
47
47
  * ```
48
48
  */
49
49
  export declare function provideTransient(): ClassDecorator;
50
+ /**
51
+ * Provider<T> — Guice's object-oriented `Provider<T>`, which inversify does not have.
52
+ *
53
+ * Inversify's own `Provider<T>` is a FUNCTION type `(...args) => Promise<T>` and its
54
+ * `toProvider()` binding is deprecated ("Providers will be removed in v8"), so we model
55
+ * Guice's seam ourselves.
56
+ *
57
+ * It caches NOTHING, because `ResolutionContext.get()` already applies the BOUND SCOPE of `T`:
58
+ *
59
+ * T bound @provideFrameworkSingleton -> every get() returns the SAME instance, built on the
60
+ * first call. That is a LAZY SINGLETON.
61
+ * T bound @provideFrameworkTransient -> every get() builds a NEW instance. That is 1-to-many.
62
+ *
63
+ * A provider that cached internally would break the transient case outright: the second get()
64
+ * would hand back the first instance.
65
+ *
66
+ * `get()` is SYNCHRONOUS, like Guice's. An async `get()` would force every consumer (e.g.
67
+ * `ClientHttpFactory.createClient`) to become async, and neither Angular's `useFactory` nor
68
+ * inversify's `toDynamicValue` can await.
69
+ *
70
+ * TypeScript erases generics, so `Provider<T>` has NO runtime identity and cannot itself be a DI
71
+ * token. Register it against a Symbol naming T, with {@link bindFrameworkProvider}, and inject it
72
+ * by that token — the declared type is what a reader needs, the Symbol is what inversify needs:
73
+ *
74
+ * ```typescript
75
+ * // webpieces-disable no-symbol-di-tokens -- Provider<T> is erased at runtime; T names the token
76
+ * export const TASK_PROXY_PROVIDER = Symbol.for('TaskProxyClientProvider');
77
+ * bindFrameworkProvider(TASK_PROXY_PROVIDER, TaskProxyClient);
78
+ *
79
+ * constructor(@inject(TASK_PROXY_PROVIDER) private readonly provider: Provider<TaskProxyClient>) {}
80
+ * ```
81
+ *
82
+ * Inject a Provider when you need a dependency LATER or REPEATEDLY rather than at construction
83
+ * time — a lazily-created singleton, or a fresh instance per call.
84
+ */
85
+ export declare class Provider<T> {
86
+ private readonly resolve;
87
+ constructor(resolve: () => T);
88
+ get(): T;
89
+ }
package/src/provide.js CHANGED
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Provider = void 0;
3
4
  exports.provideSingleton = provideSingleton;
4
5
  exports.provideSingletonAs = provideSingletonAs;
5
6
  exports.provideTransient = provideTransient;
@@ -61,7 +62,56 @@ function provideSingletonAs(serviceIdentifier) {
61
62
  function provideTransient() {
62
63
  // webpieces-disable no-any-unknown -- decorator target is any class constructor
63
64
  return (target) => {
64
- return (0, binding_decorators_1.provide)(target)(target);
65
+ // Call inTransientScope() EXPLICITLY. Omitting the scope call inherits the container's
66
+ // defaultScope which, while Transient by default in inversify 7, would silently flip
67
+ // meaning if anyone ever passed `new Container({ defaultScope: ... })`.
68
+ // webpieces-disable no-any-unknown -- inversify's own fluent-syntax generic for a self-binding
69
+ return (0, binding_decorators_1.provide)(target, (bind) => bind.inTransientScope())(target);
65
70
  };
66
71
  }
72
+ /**
73
+ * Provider<T> — Guice's object-oriented `Provider<T>`, which inversify does not have.
74
+ *
75
+ * Inversify's own `Provider<T>` is a FUNCTION type `(...args) => Promise<T>` and its
76
+ * `toProvider()` binding is deprecated ("Providers will be removed in v8"), so we model
77
+ * Guice's seam ourselves.
78
+ *
79
+ * It caches NOTHING, because `ResolutionContext.get()` already applies the BOUND SCOPE of `T`:
80
+ *
81
+ * T bound @provideFrameworkSingleton -> every get() returns the SAME instance, built on the
82
+ * first call. That is a LAZY SINGLETON.
83
+ * T bound @provideFrameworkTransient -> every get() builds a NEW instance. That is 1-to-many.
84
+ *
85
+ * A provider that cached internally would break the transient case outright: the second get()
86
+ * would hand back the first instance.
87
+ *
88
+ * `get()` is SYNCHRONOUS, like Guice's. An async `get()` would force every consumer (e.g.
89
+ * `ClientHttpFactory.createClient`) to become async, and neither Angular's `useFactory` nor
90
+ * inversify's `toDynamicValue` can await.
91
+ *
92
+ * TypeScript erases generics, so `Provider<T>` has NO runtime identity and cannot itself be a DI
93
+ * token. Register it against a Symbol naming T, with {@link bindFrameworkProvider}, and inject it
94
+ * by that token — the declared type is what a reader needs, the Symbol is what inversify needs:
95
+ *
96
+ * ```typescript
97
+ * // webpieces-disable no-symbol-di-tokens -- Provider<T> is erased at runtime; T names the token
98
+ * export const TASK_PROXY_PROVIDER = Symbol.for('TaskProxyClientProvider');
99
+ * bindFrameworkProvider(TASK_PROXY_PROVIDER, TaskProxyClient);
100
+ *
101
+ * constructor(@inject(TASK_PROXY_PROVIDER) private readonly provider: Provider<TaskProxyClient>) {}
102
+ * ```
103
+ *
104
+ * Inject a Provider when you need a dependency LATER or REPEATEDLY rather than at construction
105
+ * time — a lazily-created singleton, or a fresh instance per call.
106
+ */
107
+ class Provider {
108
+ resolve;
109
+ constructor(resolve) {
110
+ this.resolve = resolve;
111
+ }
112
+ get() {
113
+ return this.resolve();
114
+ }
115
+ }
116
+ exports.Provider = Provider;
67
117
  //# sourceMappingURL=provide.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"provide.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/provide.ts"],"names":[],"mappings":";;AAyBA,4CAKC;AAcD,gDAEC;AAcD,4CAKC;AAjED,4BAA0B;AAC1B,wEAA0D;AAG1D;;;;;;;GAOG;AAEH;;;;;;;;;;;GAWG;AACH,SAAgB,gBAAgB;IAC5B,gFAAgF;IAChF,OAAO,CAAC,MAAW,EAAE,EAAE;QACnB,OAAO,IAAA,4BAAO,EAAC,MAAM,EAAE,CAAC,IAAuC,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IACzG,CAAC,CAAC;AACN,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAgB,kBAAkB,CAAI,iBAAuC;IACzE,OAAO,IAAA,4BAAO,EAAC,iBAAiB,EAAE,CAAC,IAAiC,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC;AACtG,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAgB,gBAAgB;IAC5B,gFAAgF;IAChF,OAAO,CAAC,MAAW,EAAE,EAAE;QACnB,OAAO,IAAA,4BAAO,EAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;IACnC,CAAC,CAAC;AACN,CAAC","sourcesContent":["import 'reflect-metadata';\nimport { provide } from '@inversifyjs/binding-decorators';\nimport type { BindInWhenOnFluentSyntax, ServiceIdentifier } from 'inversify';\n\n/**\n * DI provider decorators (the lightweight DI seam shared across webpieces).\n *\n * These live in @webpieces/core-context — the lowest package that already owns\n * request-scoped context — so libraries (cloudtasks-client, http-client, …) can\n * register singletons WITHOUT depending on the server-side @webpieces/http-routing\n * package. http-routing re-exports them for back-compat.\n */\n\n/**\n * Provides a singleton-scoped dependency.\n * When called without arguments, the decorated class binds to itself.\n *\n * Usage:\n * ```typescript\n * @provideSingleton()\n * export class SaveController {\n * // ...\n * }\n * ```\n */\nexport function provideSingleton(): ClassDecorator {\n // webpieces-disable no-any-unknown -- decorator target is any class constructor\n return (target: any) => {\n return provide(target, (bind: BindInWhenOnFluentSyntax<unknown>) => bind.inSingletonScope())(target);\n };\n}\n\n/**\n * Provides a singleton-scoped dependency bound to a specific token (Symbol or abstract class).\n * Use this in libraries/apis-external/** to bind an impl to the Symbol defined in libraries/apis/**.\n *\n * Usage:\n * ```typescript\n * import { SOME_API_TOKEN } from '@myorg/some-api';\n *\n * @provideSingletonAs(SOME_API_TOKEN)\n * export class SomeApiImpl { ... }\n * ```\n */\nexport function provideSingletonAs<T>(serviceIdentifier: ServiceIdentifier<T>): ClassDecorator {\n return provide(serviceIdentifier, (bind: BindInWhenOnFluentSyntax<T>) => bind.inSingletonScope());\n}\n\n/**\n * Provides a transient-scoped dependency (new instance every time).\n * When called without arguments, the decorated class binds to itself.\n *\n * Usage:\n * ```typescript\n * @provideTransient()\n * export class TransientController {\n * // ...\n * }\n * ```\n */\nexport function provideTransient(): ClassDecorator {\n // webpieces-disable no-any-unknown -- decorator target is any class constructor\n return (target: any) => {\n return provide(target)(target);\n };\n}\n"]}
1
+ {"version":3,"file":"provide.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/provide.ts"],"names":[],"mappings":";;;AAyBA,4CAKC;AAcD,gDAEC;AAcD,4CASC;AArED,4BAA0B;AAC1B,wEAA0D;AAG1D;;;;;;;GAOG;AAEH;;;;;;;;;;;GAWG;AACH,SAAgB,gBAAgB;IAC5B,gFAAgF;IAChF,OAAO,CAAC,MAAW,EAAE,EAAE;QACnB,OAAO,IAAA,4BAAO,EAAC,MAAM,EAAE,CAAC,IAAuC,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IACzG,CAAC,CAAC;AACN,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAgB,kBAAkB,CAAI,iBAAuC;IACzE,OAAO,IAAA,4BAAO,EAAC,iBAAiB,EAAE,CAAC,IAAiC,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC;AACtG,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAgB,gBAAgB;IAC5B,gFAAgF;IAChF,OAAO,CAAC,MAAW,EAAE,EAAE;QACnB,uFAAuF;QACvF,qFAAqF;QACrF,wEAAwE;QACxE,+FAA+F;QAC/F,OAAO,IAAA,4BAAO,EAAC,MAAM,EAAE,CAAC,IAAuC,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IACzG,CAAC,CAAC;AACN,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAa,QAAQ;IACY;IAA7B,YAA6B,OAAgB;QAAhB,YAAO,GAAP,OAAO,CAAS;IAAG,CAAC;IAEjD,GAAG;QACC,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;IAC1B,CAAC;CACJ;AAND,4BAMC","sourcesContent":["import 'reflect-metadata';\nimport { provide } from '@inversifyjs/binding-decorators';\nimport type { BindInWhenOnFluentSyntax, ServiceIdentifier } from 'inversify';\n\n/**\n * DI provider decorators (the lightweight DI seam shared across webpieces).\n *\n * These live in @webpieces/core-context — the lowest package that already owns\n * request-scoped context — so libraries (cloudtasks-client, http-client, …) can\n * register singletons WITHOUT depending on the server-side @webpieces/http-routing\n * package. http-routing re-exports them for back-compat.\n */\n\n/**\n * Provides a singleton-scoped dependency.\n * When called without arguments, the decorated class binds to itself.\n *\n * Usage:\n * ```typescript\n * @provideSingleton()\n * export class SaveController {\n * // ...\n * }\n * ```\n */\nexport function provideSingleton(): ClassDecorator {\n // webpieces-disable no-any-unknown -- decorator target is any class constructor\n return (target: any) => {\n return provide(target, (bind: BindInWhenOnFluentSyntax<unknown>) => bind.inSingletonScope())(target);\n };\n}\n\n/**\n * Provides a singleton-scoped dependency bound to a specific token (Symbol or abstract class).\n * Use this in libraries/apis-external/** to bind an impl to the Symbol defined in libraries/apis/**.\n *\n * Usage:\n * ```typescript\n * import { SOME_API_TOKEN } from '@myorg/some-api';\n *\n * @provideSingletonAs(SOME_API_TOKEN)\n * export class SomeApiImpl { ... }\n * ```\n */\nexport function provideSingletonAs<T>(serviceIdentifier: ServiceIdentifier<T>): ClassDecorator {\n return provide(serviceIdentifier, (bind: BindInWhenOnFluentSyntax<T>) => bind.inSingletonScope());\n}\n\n/**\n * Provides a transient-scoped dependency (new instance every time).\n * When called without arguments, the decorated class binds to itself.\n *\n * Usage:\n * ```typescript\n * @provideTransient()\n * export class TransientController {\n * // ...\n * }\n * ```\n */\nexport function provideTransient(): ClassDecorator {\n // webpieces-disable no-any-unknown -- decorator target is any class constructor\n return (target: any) => {\n // Call inTransientScope() EXPLICITLY. Omitting the scope call inherits the container's\n // defaultScope which, while Transient by default in inversify 7, would silently flip\n // meaning if anyone ever passed `new Container({ defaultScope: ... })`.\n // webpieces-disable no-any-unknown -- inversify's own fluent-syntax generic for a self-binding\n return provide(target, (bind: BindInWhenOnFluentSyntax<unknown>) => bind.inTransientScope())(target);\n };\n}\n\n/**\n * Provider<T> — Guice's object-oriented `Provider<T>`, which inversify does not have.\n *\n * Inversify's own `Provider<T>` is a FUNCTION type `(...args) => Promise<T>` and its\n * `toProvider()` binding is deprecated (\"Providers will be removed in v8\"), so we model\n * Guice's seam ourselves.\n *\n * It caches NOTHING, because `ResolutionContext.get()` already applies the BOUND SCOPE of `T`:\n *\n * T bound @provideFrameworkSingleton -> every get() returns the SAME instance, built on the\n * first call. That is a LAZY SINGLETON.\n * T bound @provideFrameworkTransient -> every get() builds a NEW instance. That is 1-to-many.\n *\n * A provider that cached internally would break the transient case outright: the second get()\n * would hand back the first instance.\n *\n * `get()` is SYNCHRONOUS, like Guice's. An async `get()` would force every consumer (e.g.\n * `ClientHttpFactory.createClient`) to become async, and neither Angular's `useFactory` nor\n * inversify's `toDynamicValue` can await.\n *\n * TypeScript erases generics, so `Provider<T>` has NO runtime identity and cannot itself be a DI\n * token. Register it against a Symbol naming T, with {@link bindFrameworkProvider}, and inject it\n * by that token — the declared type is what a reader needs, the Symbol is what inversify needs:\n *\n * ```typescript\n * // webpieces-disable no-symbol-di-tokens -- Provider<T> is erased at runtime; T names the token\n * export const TASK_PROXY_PROVIDER = Symbol.for('TaskProxyClientProvider');\n * bindFrameworkProvider(TASK_PROXY_PROVIDER, TaskProxyClient);\n *\n * constructor(@inject(TASK_PROXY_PROVIDER) private readonly provider: Provider<TaskProxyClient>) {}\n * ```\n *\n * Inject a Provider when you need a dependency LATER or REPEATEDLY rather than at construction\n * time — a lazily-created singleton, or a fresh instance per call.\n */\nexport class Provider<T> {\n constructor(private readonly resolve: () => T) {}\n\n get(): T {\n return this.resolve();\n }\n}\n"]}