@webpieces/core-context 0.3.314 → 0.3.317

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.314",
3
+ "version": "0.3.317",
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.314",
25
+ "@webpieces/core-util": "0.3.317",
26
26
  "@inversifyjs/binding-decorators": "1.1.5",
27
27
  "inversify": "7.10.4",
28
28
  "reflect-metadata": "0.2.2"
@@ -29,6 +29,21 @@ declare class RequestContextImpl {
29
29
  getHeader<T = unknown>(key: ContextKey): T | undefined;
30
30
  putHeader(key: ContextKey, value: unknown): void;
31
31
  hasHeader(key: ContextKey): boolean;
32
+ /**
33
+ /**
34
+ * Build the masked field map for LOGGING: every logged key in the global
35
+ * {@link HeaderRegistry} read straight from this context, secured values
36
+ * masked (via {@link ContextKey.maskIfSecured}), keyed by each key's `name`.
37
+ *
38
+ * Callers: RecordingFilter + NodeProxyClient.recordCall, which snapshot the context into a
39
+ * test FIXTURE. The @webpieces/winston and @webpieces/bunyan backends also stamp these fields
40
+ * onto every record, and they own the "log emitted outside RequestContext.run(...)" complaint —
41
+ * reporting it HERE would recurse (the error line itself re-enters buildLogFields).
42
+ *
43
+ * Returns an EMPTY map outside a `run(...)` block rather than throwing: a fixture snapshot or a
44
+ * log line is never worth crashing a request over.
45
+ */
46
+ buildLogFields(): Map<string, string>;
32
47
  /**
33
48
  * Store the transport-neutral {@link HttpRequest} for this request. Called once, above the
34
49
  * 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
  /**
@@ -48,6 +49,28 @@ class RequestContextImpl {
48
49
  hasHeader(key) {
49
50
  return this.has(key.name);
50
51
  }
52
+ /**
53
+ /**
54
+ * Build the masked field map for LOGGING: every logged key in the global
55
+ * {@link HeaderRegistry} read straight from this context, secured values
56
+ * masked (via {@link ContextKey.maskIfSecured}), keyed by each key's `name`.
57
+ *
58
+ * Callers: RecordingFilter + NodeProxyClient.recordCall, which snapshot the context into a
59
+ * test FIXTURE. The @webpieces/winston and @webpieces/bunyan backends also stamp these fields
60
+ * onto every record, and they own the "log emitted outside RequestContext.run(...)" complaint —
61
+ * reporting it HERE would recurse (the error line itself re-enters buildLogFields).
62
+ *
63
+ * Returns an EMPTY map outside a `run(...)` block rather than throwing: a fixture snapshot or a
64
+ * log line is never worth crashing a request over.
65
+ */
66
+ buildLogFields() {
67
+ if (!this.isActive()) {
68
+ return new Map();
69
+ }
70
+ // The registry owns the keys and each ContextKey masks its own value; we only supply
71
+ // WHERE to read from. The browser's ContextMgr calls the same method with its store's read.
72
+ return core_util_1.HeaderRegistry.get().buildLogFields((key) => this.getHeader(key));
73
+ }
51
74
  /**
52
75
  * Store the transport-neutral {@link HttpRequest} for this request. Called once, above the
53
76
  * 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 * 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 /**\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,26 @@
1
1
  import { ContainerModule } from 'inversify';
2
2
  import type { ServiceIdentifier } from 'inversify';
3
+ import { Provider } from './provide';
4
+ /**
5
+ * Framework-only DI provider decorators — a SEPARATE registry from the client-facing
6
+ * @provideSingleton (which uses @inversifyjs/binding-decorators' single global registry).
7
+ *
8
+ * WHY: binding-decorators registers every @provideSingleton class under ONE global
9
+ * reflect-metadata key, and buildProviderModule() scoops up that whole key. If webpieces
10
+ * framework classes (RouteBuilderImpl, the filters, WebpiecesRouter) used @provideSingleton,
11
+ * a CLIENT app's buildProviderModule() would drag those framework internals into its own
12
+ * container. To keep the two worlds separate:
13
+ * - packages/** (framework libs) MUST use provideFrameworkSingleton (this registry),
14
+ * enforced by the no-global-providesingleton-in-packages ESLint rule.
15
+ * - apps/** (and downstream client projects) use plain @provideSingleton (the global one).
16
+ * The router loads BOTH buildFrameworkModule() and buildProviderModule(), so everything
17
+ * resolves — but a client's buildProviderModule() only ever sees the client's own classes.
18
+ */
19
+ type AnyCtor = new (...args: any[]) => unknown;
20
+ /** A {@link Provider} subclass: constructed from the resolve-lambda by buildFrameworkModule. */
21
+ type ProviderCtor = new (resolve: () => any) => Provider<any>;
22
+ /** How a framework binding is scoped. Always explicit — never inherited from the container. */
23
+ export type FrameworkScope = 'singleton' | 'transient';
3
24
  /**
4
25
  * Framework equivalent of @provideSingleton: registers the class as a singleton bound to
5
26
  * itself, into the webpieces framework registry (NOT the binding-decorators global one).
@@ -11,8 +32,29 @@ export declare function provideFrameworkSingleton(): ClassDecorator;
11
32
  */
12
33
  export declare function provideFrameworkSingletonAs<T>(serviceIdentifier: ServiceIdentifier<T>): ClassDecorator;
13
34
  /**
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().
35
+ * Framework equivalent of @provideTransient: a NEW instance on every resolve. Use it for a
36
+ * class a {@link Provider} hands out per call e.g. one ProxyClient per API contract.
37
+ */
38
+ export declare function provideFrameworkTransient(): ClassDecorator;
39
+ /**
40
+ * Register a {@link Provider} subclass as the DI token that hands out `target` instances.
41
+ *
42
+ * The provider caches nothing; `target`'s own binding scope decides whether callers share one
43
+ * instance (provideFrameworkSingleton -> lazy singleton) or get a fresh one each `get()`
44
+ * (provideFrameworkTransient -> 1-to-many).
45
+ *
46
+ * The provider itself is a singleton — it holds only the resolve-lambda.
47
+ *
48
+ * ```typescript
49
+ * export class ProxyClientProvider extends ContainerProvider<NodeProxyClient> {}
50
+ * bindFrameworkProvider(ProxyClientProvider, NodeProxyClient);
51
+ * ```
52
+ */
53
+ export declare function bindFrameworkProvider(providerClass: ProviderCtor, target: AnyCtor): void;
54
+ /**
55
+ * Build a ContainerModule binding every provideFrameworkSingleton(As)/Transient class, then
56
+ * every registered Provider. Load this into the webpieces framework + app containers (the
57
+ * router does this) alongside the client's own buildProviderModule().
17
58
  */
18
59
  export declare function buildFrameworkModule(): ContainerModule;
60
+ export {};
@@ -2,18 +2,33 @@
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");
7
9
  class FrameworkBinding {
8
10
  serviceIdentifier;
9
11
  target;
10
- constructor(serviceIdentifier, target) {
12
+ scope;
13
+ constructor(serviceIdentifier, target, scope = 'singleton') {
11
14
  this.serviceIdentifier = serviceIdentifier;
12
15
  this.target = target;
16
+ this.scope = scope;
17
+ }
18
+ }
19
+ /** A Provider subclass paired with the class it resolves. See {@link bindFrameworkProvider}. */
20
+ class FrameworkProviderBinding {
21
+ providerClass;
22
+ target;
23
+ constructor(providerClass, target) {
24
+ this.providerClass = providerClass;
25
+ this.target = target;
13
26
  }
14
27
  }
15
28
  /** The webpieces-only binding registry (a plain module-level list, one per hosted core-context). */
16
29
  const frameworkRegistry = [];
30
+ /** Provider<T> bindings, applied after frameworkRegistry so their targets are already bound. */
31
+ const frameworkProviderRegistry = [];
17
32
  /**
18
33
  * Framework equivalent of @provideSingleton: registers the class as a singleton bound to
19
34
  * itself, into the webpieces framework registry (NOT the binding-decorators global one).
@@ -37,14 +52,58 @@ function provideFrameworkSingletonAs(serviceIdentifier) {
37
52
  };
38
53
  }
39
54
  /**
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().
55
+ * Framework equivalent of @provideTransient: a NEW instance on every resolve. Use it for a
56
+ * class a {@link Provider} hands out per call e.g. one ProxyClient per API contract.
57
+ */
58
+ // webpieces-disable no-function-outside-class -- a decorator factory cannot be a class method
59
+ function provideFrameworkTransient() {
60
+ // webpieces-disable no-any-unknown -- decorator target is any class constructor
61
+ return (target) => {
62
+ frameworkRegistry.push(new FrameworkBinding(target, target, 'transient'));
63
+ return target;
64
+ };
65
+ }
66
+ /**
67
+ * Register a {@link Provider} subclass as the DI token that hands out `target` instances.
68
+ *
69
+ * The provider caches nothing; `target`'s own binding scope decides whether callers share one
70
+ * instance (provideFrameworkSingleton -> lazy singleton) or get a fresh one each `get()`
71
+ * (provideFrameworkTransient -> 1-to-many).
72
+ *
73
+ * The provider itself is a singleton — it holds only the resolve-lambda.
74
+ *
75
+ * ```typescript
76
+ * export class ProxyClientProvider extends ContainerProvider<NodeProxyClient> {}
77
+ * bindFrameworkProvider(ProxyClientProvider, NodeProxyClient);
78
+ * ```
79
+ */
80
+ // webpieces-disable no-function-outside-class -- registry side-effect, called at module scope beside the decorators
81
+ function bindFrameworkProvider(providerClass, target) {
82
+ frameworkProviderRegistry.push(new FrameworkProviderBinding(providerClass, target));
83
+ }
84
+ /**
85
+ * Build a ContainerModule binding every provideFrameworkSingleton(As)/Transient class, then
86
+ * every registered Provider. Load this into the webpieces framework + app containers (the
87
+ * router does this) alongside the client's own buildProviderModule().
43
88
  */
44
89
  function buildFrameworkModule() {
45
90
  return new inversify_1.ContainerModule((options) => {
46
91
  for (const binding of frameworkRegistry) {
47
- options.bind(binding.serviceIdentifier).to(binding.target).inSingletonScope();
92
+ const bindTo = options.bind(binding.serviceIdentifier).to(binding.target);
93
+ if (binding.scope === 'transient') {
94
+ bindTo.inTransientScope();
95
+ }
96
+ else {
97
+ bindTo.inSingletonScope();
98
+ }
99
+ }
100
+ for (const binding of frameworkProviderRegistry) {
101
+ // toDynamicValue so the provider closes over the ResolutionContext. Each get() then
102
+ // re-resolves `target`, letting TARGET's scope decide shared-vs-fresh.
103
+ options
104
+ .bind(binding.providerClass)
105
+ .toDynamicValue((context) => new binding.providerClass(() => context.get(binding.target)))
106
+ .inSingletonScope();
48
107
  }
49
108
  });
50
109
  }
@@ -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":";;AAwDA,8DAMC;AAMD,kEAMC;AAOD,8DAMC;AAiBD,sDAEC;AAOD,oDAoBC;AArID,yCAA4C;AA8B5C,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,gGAAgG;AAChG,MAAM,wBAAwB;IAEN;IACA;IAFpB,YACoB,aAA2B,EAC3B,MAAe;QADf,kBAAa,GAAb,aAAa,CAAc;QAC3B,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;;;;;;;;;;;;;GAaG;AACH,oHAAoH;AACpH,SAAgB,qBAAqB,CAAC,aAA2B,EAAE,MAAe;IAC9E,yBAAyB,CAAC,IAAI,CAAC,IAAI,wBAAwB,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC;AACxF,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,aAAa,CAAC;iBAC3B,cAAc,CAAC,CAAC,OAA0B,EAAE,EAAE,CAC3C,IAAI,OAAO,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;iBAChE,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/** A {@link Provider} subclass: constructed from the resolve-lambda by buildFrameworkModule. */\n// webpieces-disable no-any-unknown -- the provider's T is erased at the registry boundary\ntype ProviderCtor = new (resolve: () => any) => Provider<any>;\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 subclass paired with the class it resolves. See {@link bindFrameworkProvider}. */\nclass FrameworkProviderBinding {\n constructor(\n public readonly providerClass: ProviderCtor,\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 * ```typescript\n * export class ProxyClientProvider extends ContainerProvider<NodeProxyClient> {}\n * bindFrameworkProvider(ProxyClientProvider, NodeProxyClient);\n * ```\n */\n// webpieces-disable no-function-outside-class -- registry side-effect, called at module scope beside the decorators\nexport function bindFrameworkProvider(providerClass: ProviderCtor, target: AnyCtor): void {\n frameworkProviderRegistry.push(new FrameworkProviderBinding(providerClass, 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.providerClass)\n .toDynamicValue((context: ResolutionContext) =>\n new binding.providerClass(() => 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, ContainerProvider } 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.ContainerProvider = 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,28 @@ 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; } });
18
+ Object.defineProperty(exports, "ContainerProvider", { enumerable: true, get: function () { return provide_2.ContainerProvider; } });
15
19
  // Framework-only DI registry (packages/** use these; keeps framework classes out of a
16
20
  // client's buildProviderModule() global scan). See frameworkProvide.ts.
17
21
  var frameworkProvide_1 = require("./frameworkProvide");
18
22
  Object.defineProperty(exports, "provideFrameworkSingleton", { enumerable: true, get: function () { return frameworkProvide_1.provideFrameworkSingleton; } });
19
23
  Object.defineProperty(exports, "provideFrameworkSingletonAs", { enumerable: true, get: function () { return frameworkProvide_1.provideFrameworkSingletonAs; } });
24
+ Object.defineProperty(exports, "provideFrameworkTransient", { enumerable: true, get: function () { return frameworkProvide_1.provideFrameworkTransient; } });
25
+ Object.defineProperty(exports, "bindFrameworkProvider", { enumerable: true, get: function () { return frameworkProvide_1.bindFrameworkProvider; } });
20
26
  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; } });
27
+ // Outbound headers for a SERVER: reads RequestContext directly, fails fast outside
28
+ // RequestContext.run(...). Server-side clients (http-client-node, cloudtasks-client) and
29
+ // http-routing use THIS.
30
+ //
31
+ // ContextMgr is deliberately NOT re-exported. It is the browser's answer (an app-held store),
32
+ // and only @webpieces/http-client-browser may name it importing it here would let a node
33
+ // package reach for a ContextReader it has no use for.
34
+ var RequestContextHeaders_1 = require("./RequestContextHeaders");
35
+ Object.defineProperty(exports, "RequestContextHeaders", { enumerable: true, get: function () { return RequestContextHeaders_1.RequestContextHeaders; } });
36
+ // The browser store's server counterpart, still used by the logging packages + http-server filters.
27
37
  var RequestContextReader_1 = require("./RequestContextReader");
28
38
  Object.defineProperty(exports, "RequestContextReader", { enumerable: true, get: function () { return RequestContextReader_1.RequestContextReader; } });
29
39
  //# 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,qCAAwD;AAA/C,mGAAA,QAAQ,OAAA;AAAE,4GAAA,iBAAiB,OAAA;AACpC,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, ContainerProvider } 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,42 @@ 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
+ * `get()` is SYNCHRONOUS, like Guice's. An async `get()` would force every consumer
58
+ * (e.g. `ClientHttpFactory.createClient`) to become async, and neither Angular's
59
+ * `useFactory` nor inversify's `toDynamicValue` can await.
60
+ *
61
+ * Inject a Provider when you need a dependency LATER or REPEATEDLY rather than at
62
+ * construction time — a lazily-created singleton, or a fresh instance per call.
63
+ */
64
+ export declare abstract class Provider<T> {
65
+ abstract get(): T;
66
+ }
67
+ /**
68
+ * The container-backed {@link Provider}. It deliberately caches NOTHING, because
69
+ * `ResolutionContext.get()` already applies the BOUND SCOPE of `T`:
70
+ *
71
+ * T bound with @provideSingleton -> every get() returns the SAME instance, constructed on
72
+ * the first call. That is a LAZY SINGLETON.
73
+ * T bound transient -> every get() constructs a NEW instance. That is 1-to-many.
74
+ *
75
+ * A provider that cached internally would break the transient case outright: the second
76
+ * get() would hand back the first instance.
77
+ *
78
+ * Subclass it once per `T` so the subclass IS the DI token (no Symbol tokens), then register
79
+ * the pair with {@link bindFrameworkProvider}:
80
+ * ```typescript
81
+ * export class ProxyClientProvider extends ContainerProvider<NodeProxyClient> {}
82
+ * ```
83
+ */
84
+ export declare class ContainerProvider<T> extends Provider<T> {
85
+ private readonly resolve;
86
+ constructor(resolve: () => T);
87
+ get(): T;
88
+ }
package/src/provide.js CHANGED
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ContainerProvider = 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
+ * `get()` is SYNCHRONOUS, like Guice's. An async `get()` would force every consumer
80
+ * (e.g. `ClientHttpFactory.createClient`) to become async, and neither Angular's
81
+ * `useFactory` nor inversify's `toDynamicValue` can await.
82
+ *
83
+ * Inject a Provider when you need a dependency LATER or REPEATEDLY rather than at
84
+ * construction time — a lazily-created singleton, or a fresh instance per call.
85
+ */
86
+ class Provider {
87
+ }
88
+ exports.Provider = Provider;
89
+ /**
90
+ * The container-backed {@link Provider}. It deliberately caches NOTHING, because
91
+ * `ResolutionContext.get()` already applies the BOUND SCOPE of `T`:
92
+ *
93
+ * T bound with @provideSingleton -> every get() returns the SAME instance, constructed on
94
+ * the first call. That is a LAZY SINGLETON.
95
+ * T bound transient -> every get() constructs a NEW instance. That is 1-to-many.
96
+ *
97
+ * A provider that cached internally would break the transient case outright: the second
98
+ * get() would hand back the first instance.
99
+ *
100
+ * Subclass it once per `T` so the subclass IS the DI token (no Symbol tokens), then register
101
+ * the pair with {@link bindFrameworkProvider}:
102
+ * ```typescript
103
+ * export class ProxyClientProvider extends ContainerProvider<NodeProxyClient> {}
104
+ * ```
105
+ */
106
+ class ContainerProvider extends Provider {
107
+ resolve;
108
+ constructor(resolve) {
109
+ super();
110
+ this.resolve = resolve;
111
+ }
112
+ get() {
113
+ return this.resolve();
114
+ }
115
+ }
116
+ exports.ContainerProvider = ContainerProvider;
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;;;;;;;;;;;;;GAaG;AACH,MAAsB,QAAQ;CAE7B;AAFD,4BAEC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAa,iBAAqB,SAAQ,QAAW;IACpB;IAA7B,YAA6B,OAAgB;QACzC,KAAK,EAAE,CAAC;QADiB,YAAO,GAAP,OAAO,CAAS;IAE7C,CAAC;IAED,GAAG;QACC,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;IAC1B,CAAC;CACJ;AARD,8CAQC","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 * `get()` is SYNCHRONOUS, like Guice's. An async `get()` would force every consumer\n * (e.g. `ClientHttpFactory.createClient`) to become async, and neither Angular's\n * `useFactory` nor inversify's `toDynamicValue` can await.\n *\n * Inject a Provider when you need a dependency LATER or REPEATEDLY rather than at\n * construction time — a lazily-created singleton, or a fresh instance per call.\n */\nexport abstract class Provider<T> {\n abstract get(): T;\n}\n\n/**\n * The container-backed {@link Provider}. It deliberately caches NOTHING, because\n * `ResolutionContext.get()` already applies the BOUND SCOPE of `T`:\n *\n * T bound with @provideSingleton -> every get() returns the SAME instance, constructed on\n * the first call. That is a LAZY SINGLETON.\n * T bound transient -> every get() constructs a NEW instance. That is 1-to-many.\n *\n * A provider that cached internally would break the transient case outright: the second\n * get() would hand back the first instance.\n *\n * Subclass it once per `T` so the subclass IS the DI token (no Symbol tokens), then register\n * the pair with {@link bindFrameworkProvider}:\n * ```typescript\n * export class ProxyClientProvider extends ContainerProvider<NodeProxyClient> {}\n * ```\n */\nexport class ContainerProvider<T> extends Provider<T> {\n constructor(private readonly resolve: () => T) {\n super();\n }\n\n get(): T {\n return this.resolve();\n }\n}\n"]}