@webpieces/core-context 0.4.605 → 0.4.606

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.4.605",
3
+ "version": "0.4.606",
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.4.605",
25
+ "@webpieces/core-util": "0.4.606",
26
26
  "@inversifyjs/binding-decorators": "1.1.5",
27
27
  "inversify": "7.10.4",
28
28
  "reflect-metadata": "0.2.2"
@@ -0,0 +1,120 @@
1
+ /**
2
+ * A capability TOKEN, not data — the thing you must be holding to build a {@link CapturedContext} or
3
+ * to unpack one.
4
+ *
5
+ * It exists because `CapturedContext` needs a producer that {@link RequestContext} (a different class,
6
+ * in a different file) can call, and TypeScript's `private` is class-scoped: a plain public
7
+ * `CapturedContext.capture(map)` would be exactly the hand-assembled-payload hole this class exists to
8
+ * close. So the producer takes a token whose constructor is private and whose only instance is
9
+ * {@link INTERNAL}, and this module is deliberately NOT re-exported from the package barrel — in any
10
+ * form, type or value.
11
+ *
12
+ * On its own that stops a consumer NAMING the token but not SUPPLYING one, since `null as never`
13
+ * type-checks. It is the barrel's `export type { CapturedContext }` that finishes the job: with no
14
+ * class object on the package surface there is no `capture(...)` to call, cast or not. The two halves
15
+ * are load-bearing together, which is why each names the other.
16
+ *
17
+ * Per CLAUDE.md this is a class rather than a `Symbol` or an object literal: it is nominal (the private
18
+ * `brand` field stops a structurally-identical `{}` from satisfying it) and it has exactly one
19
+ * instantiation point.
20
+ */
21
+ export declare class ContextCaptureAuthority {
22
+ /**
23
+ * Nominal brand. Without a private member the class is structurally `{}`, and any object at all
24
+ * would typecheck as an authority.
25
+ */
26
+ private readonly brand;
27
+ /** The ONE token that exists. The constructor below is private, so no other can be made. */
28
+ static readonly INTERNAL: ContextCaptureAuthority;
29
+ private constructor();
30
+ /** Kept honest — the brand is read here so it is a real field, not a type-only fiction. */
31
+ describe(): string;
32
+ }
33
+ /**
34
+ * CapturedContext — an OPAQUE snapshot of a {@link RequestContext} scope.
35
+ *
36
+ * ## What it is for
37
+ *
38
+ * `AsyncLocalStorage` follows `await`, `.then()` and ordinary callbacks on its own, so the vast
39
+ * majority of code never needs this. What it does NOT follow is work whose async chain was BROKEN and
40
+ * re-rooted somewhere else: an item pushed onto an in-memory queue during a request and drained later
41
+ * by a background loop, a batch flushed on a scheduler tick, an `EventEmitter` listener fired from a
42
+ * socket the request does not own, a retry re-armed from a top-level timer, a hand-off to a worker
43
+ * pool. In each of those the work executes under a DIFFERENT (or no) context, so the request id, the
44
+ * log fields and the proven identity would silently vanish from everything the work logs or calls.
45
+ *
46
+ * The answer is two halves: `RequestContext.copyContext()` where the work is ENQUEUED, and
47
+ * `RequestContext.runWithContext(captured, fn)` (or `restoreContext(captured)`) where it RUNS.
48
+ *
49
+ * ## Why it is opaque instead of a `Map<string, unknown>`
50
+ *
51
+ * A restored context legitimately contains TRUSTED values — reinstating what the original scope had
52
+ * proven is the entire point — so the restore side cannot type-check its payload the way
53
+ * `putTrusted`/`getTrusted` do. That left the `Map`-taking signature that this class DELETED (a
54
+ * now-removed `setContext(map)`, and `runWithContext(map, fn)`) as a complete bypass of the trust
55
+ * system: handing it `new Map([['userId', 'victim']])` forged a proven identity in one line, without
56
+ * ever typing a trust verb, and the only thing standing against it was a doc comment saying "the Map
57
+ * must come from copyContext()". An agent picks whatever compiles, so a doc comment is not
58
+ * enforcement.
59
+ *
60
+ * Making the PAYLOAD opaque solves it without type-checking the contents: the only way to obtain one is
61
+ * a real capture of a real scope, so whatever it holds was, by construction, already in a context that
62
+ * something legitimately wrote. Concretely:
63
+ *
64
+ * - the constructor is `private`, and there is no public factory — {@link capture} demands a
65
+ * {@link ContextCaptureAuthority} that cannot be named outside this package;
66
+ * - the package barrel exports this class as a TYPE ONLY, so a consumer never receives the class
67
+ * object at all and cannot reach `capture` even with a cast. That second half matters: a token whose
68
+ * TYPE is unexported still stops nothing on its own, because `capture(null as never, forgedMap)`
69
+ * type-checks. Withholding the class object is what actually closes it;
70
+ * - the entries live in `#entries`, a genuine ECMAScript private field, so they are unreachable at
71
+ * RUNTIME as well as at compile time — no `Object.keys`, no cast, no index signature;
72
+ * - the map is defensively copied ON CAPTURE and again ON RESTORE, so a caller who still holds the
73
+ * live context (or who keeps writing to it after capturing) cannot reach through the snapshot, and a
74
+ * snapshot can be restored repeatedly without the first restore's mutations bleeding into the second.
75
+ *
76
+ * There is deliberately no reader: nothing hands the entries back out. That is why `getAll()` is gone
77
+ * rather than re-typed to return one of these — a `CapturedContext` you cannot read is useless as a
78
+ * `getAll`, and a readable one would be the raw enumeration of every trusted value all over again.
79
+ *
80
+ * The one residual: a consumer holding a snapshot can still cast a token into `toFreshStore` and read
81
+ * the entries back out as a plain Map. That is knowingly accepted, and it is the same asymmetry
82
+ * `RequestContext.getAny` states — FORGING a trusted value is the dangerous direction and is closed
83
+ * here; reading one you were already legitimately handed, without saying `getTrusted`, costs you
84
+ * nothing but the type. Closing it too would mean no method could take the token at all, which is to
85
+ * say no restore could exist.
86
+ */
87
+ export declare class CapturedContext {
88
+ #private;
89
+ /**
90
+ * PRIVATE — a CapturedContext can only come from {@link capture}, which in turn can only be called
91
+ * by code holding a {@link ContextCaptureAuthority}. Copies the map so the snapshot is never a
92
+ * window onto a live store.
93
+ */
94
+ private constructor();
95
+ /**
96
+ * The ONLY producer. `authority` is a compile-time capability, not a runtime check — so it is
97
+ * referenced below only to keep it from being an unused parameter. Note the token alone is not the
98
+ * guarantee (a cast can supply one); the guarantee is that the barrel exports this class as a TYPE
99
+ * ONLY, so no consumer ever holds the class object this static hangs off. See the class doc.
100
+ */
101
+ static capture(authority: ContextCaptureAuthority, live: Map<string, unknown>): CapturedContext;
102
+ /**
103
+ * Overwrite a LIVE store with this snapshot — the engine behind `RequestContext.restoreContext`.
104
+ * Write-only by design: it pushes entries in and hands nothing back, so it is not a side door onto
105
+ * the snapshot's contents.
106
+ */
107
+ restoreInto(authority: ContextCaptureAuthority, live: Map<string, unknown>): void;
108
+ /**
109
+ * A FRESH store holding this snapshot — the engine behind `RequestContext.runWithContext`, which
110
+ * opens a new AsyncLocalStorage scope around it. Fresh (a copy) rather than the internal map, so
111
+ * everything the restored scope writes stays in that scope and the snapshot remains reusable.
112
+ */
113
+ toFreshStore(authority: ContextCaptureAuthority): Map<string, unknown>;
114
+ /**
115
+ * How many entries the snapshot holds. The one thing it will tell you about itself — a count is
116
+ * not a value, so it leaks nothing, and it lets a caller (and a test) see that a capture taken
117
+ * outside an active scope is simply empty rather than an error.
118
+ */
119
+ size(): number;
120
+ }
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CapturedContext = exports.ContextCaptureAuthority = void 0;
4
+ /**
5
+ * A capability TOKEN, not data — the thing you must be holding to build a {@link CapturedContext} or
6
+ * to unpack one.
7
+ *
8
+ * It exists because `CapturedContext` needs a producer that {@link RequestContext} (a different class,
9
+ * in a different file) can call, and TypeScript's `private` is class-scoped: a plain public
10
+ * `CapturedContext.capture(map)` would be exactly the hand-assembled-payload hole this class exists to
11
+ * close. So the producer takes a token whose constructor is private and whose only instance is
12
+ * {@link INTERNAL}, and this module is deliberately NOT re-exported from the package barrel — in any
13
+ * form, type or value.
14
+ *
15
+ * On its own that stops a consumer NAMING the token but not SUPPLYING one, since `null as never`
16
+ * type-checks. It is the barrel's `export type { CapturedContext }` that finishes the job: with no
17
+ * class object on the package surface there is no `capture(...)` to call, cast or not. The two halves
18
+ * are load-bearing together, which is why each names the other.
19
+ *
20
+ * Per CLAUDE.md this is a class rather than a `Symbol` or an object literal: it is nominal (the private
21
+ * `brand` field stops a structurally-identical `{}` from satisfying it) and it has exactly one
22
+ * instantiation point.
23
+ */
24
+ class ContextCaptureAuthority {
25
+ /**
26
+ * Nominal brand. Without a private member the class is structurally `{}`, and any object at all
27
+ * would typecheck as an authority.
28
+ */
29
+ brand = 'webpieces.context-capture';
30
+ /** The ONE token that exists. The constructor below is private, so no other can be made. */
31
+ static INTERNAL = new ContextCaptureAuthority();
32
+ constructor() { }
33
+ /** Kept honest — the brand is read here so it is a real field, not a type-only fiction. */
34
+ describe() {
35
+ return this.brand;
36
+ }
37
+ }
38
+ exports.ContextCaptureAuthority = ContextCaptureAuthority;
39
+ /**
40
+ * CapturedContext — an OPAQUE snapshot of a {@link RequestContext} scope.
41
+ *
42
+ * ## What it is for
43
+ *
44
+ * `AsyncLocalStorage` follows `await`, `.then()` and ordinary callbacks on its own, so the vast
45
+ * majority of code never needs this. What it does NOT follow is work whose async chain was BROKEN and
46
+ * re-rooted somewhere else: an item pushed onto an in-memory queue during a request and drained later
47
+ * by a background loop, a batch flushed on a scheduler tick, an `EventEmitter` listener fired from a
48
+ * socket the request does not own, a retry re-armed from a top-level timer, a hand-off to a worker
49
+ * pool. In each of those the work executes under a DIFFERENT (or no) context, so the request id, the
50
+ * log fields and the proven identity would silently vanish from everything the work logs or calls.
51
+ *
52
+ * The answer is two halves: `RequestContext.copyContext()` where the work is ENQUEUED, and
53
+ * `RequestContext.runWithContext(captured, fn)` (or `restoreContext(captured)`) where it RUNS.
54
+ *
55
+ * ## Why it is opaque instead of a `Map<string, unknown>`
56
+ *
57
+ * A restored context legitimately contains TRUSTED values — reinstating what the original scope had
58
+ * proven is the entire point — so the restore side cannot type-check its payload the way
59
+ * `putTrusted`/`getTrusted` do. That left the `Map`-taking signature that this class DELETED (a
60
+ * now-removed `setContext(map)`, and `runWithContext(map, fn)`) as a complete bypass of the trust
61
+ * system: handing it `new Map([['userId', 'victim']])` forged a proven identity in one line, without
62
+ * ever typing a trust verb, and the only thing standing against it was a doc comment saying "the Map
63
+ * must come from copyContext()". An agent picks whatever compiles, so a doc comment is not
64
+ * enforcement.
65
+ *
66
+ * Making the PAYLOAD opaque solves it without type-checking the contents: the only way to obtain one is
67
+ * a real capture of a real scope, so whatever it holds was, by construction, already in a context that
68
+ * something legitimately wrote. Concretely:
69
+ *
70
+ * - the constructor is `private`, and there is no public factory — {@link capture} demands a
71
+ * {@link ContextCaptureAuthority} that cannot be named outside this package;
72
+ * - the package barrel exports this class as a TYPE ONLY, so a consumer never receives the class
73
+ * object at all and cannot reach `capture` even with a cast. That second half matters: a token whose
74
+ * TYPE is unexported still stops nothing on its own, because `capture(null as never, forgedMap)`
75
+ * type-checks. Withholding the class object is what actually closes it;
76
+ * - the entries live in `#entries`, a genuine ECMAScript private field, so they are unreachable at
77
+ * RUNTIME as well as at compile time — no `Object.keys`, no cast, no index signature;
78
+ * - the map is defensively copied ON CAPTURE and again ON RESTORE, so a caller who still holds the
79
+ * live context (or who keeps writing to it after capturing) cannot reach through the snapshot, and a
80
+ * snapshot can be restored repeatedly without the first restore's mutations bleeding into the second.
81
+ *
82
+ * There is deliberately no reader: nothing hands the entries back out. That is why `getAll()` is gone
83
+ * rather than re-typed to return one of these — a `CapturedContext` you cannot read is useless as a
84
+ * `getAll`, and a readable one would be the raw enumeration of every trusted value all over again.
85
+ *
86
+ * The one residual: a consumer holding a snapshot can still cast a token into `toFreshStore` and read
87
+ * the entries back out as a plain Map. That is knowingly accepted, and it is the same asymmetry
88
+ * `RequestContext.getAny` states — FORGING a trusted value is the dangerous direction and is closed
89
+ * here; reading one you were already legitimately handed, without saying `getTrusted`, costs you
90
+ * nothing but the type. Closing it too would mean no method could take the token at all, which is to
91
+ * say no restore could exist.
92
+ */
93
+ class CapturedContext {
94
+ /**
95
+ * A real ECMAScript private field, not a TypeScript `private`. The distinction matters here: `#`
96
+ * is enforced by the runtime, so the snapshot's contents cannot be reached by a cast, by
97
+ * `Object.entries`, or by `as unknown as { entries: Map<string, unknown> }`.
98
+ */
99
+ // webpieces-disable no-any-unknown -- the context store is deliberately type-erased; each ContextKey carries its own value type and the typed verbs re-apply it on read
100
+ #entries;
101
+ /**
102
+ * PRIVATE — a CapturedContext can only come from {@link capture}, which in turn can only be called
103
+ * by code holding a {@link ContextCaptureAuthority}. Copies the map so the snapshot is never a
104
+ * window onto a live store.
105
+ */
106
+ // webpieces-disable no-any-unknown -- see #entries
107
+ constructor(entries) {
108
+ this.#entries = new Map(entries);
109
+ }
110
+ /**
111
+ * The ONLY producer. `authority` is a compile-time capability, not a runtime check — so it is
112
+ * referenced below only to keep it from being an unused parameter. Note the token alone is not the
113
+ * guarantee (a cast can supply one); the guarantee is that the barrel exports this class as a TYPE
114
+ * ONLY, so no consumer ever holds the class object this static hangs off. See the class doc.
115
+ */
116
+ // webpieces-disable no-any-unknown -- see #entries
117
+ // webpieces-disable no-function-outside-class -- static factory standing in for the (private) constructor; making it an instance method would mean an instance already existed, which is the thing being created
118
+ static capture(authority, live) {
119
+ void authority;
120
+ return new CapturedContext(live);
121
+ }
122
+ /**
123
+ * Overwrite a LIVE store with this snapshot — the engine behind `RequestContext.restoreContext`.
124
+ * Write-only by design: it pushes entries in and hands nothing back, so it is not a side door onto
125
+ * the snapshot's contents.
126
+ */
127
+ // webpieces-disable no-any-unknown -- see #entries
128
+ restoreInto(authority, live) {
129
+ void authority;
130
+ live.clear();
131
+ for (const name of this.#entries.keys()) {
132
+ live.set(name, this.#entries.get(name));
133
+ }
134
+ }
135
+ /**
136
+ * A FRESH store holding this snapshot — the engine behind `RequestContext.runWithContext`, which
137
+ * opens a new AsyncLocalStorage scope around it. Fresh (a copy) rather than the internal map, so
138
+ * everything the restored scope writes stays in that scope and the snapshot remains reusable.
139
+ */
140
+ // webpieces-disable no-any-unknown -- see #entries
141
+ toFreshStore(authority) {
142
+ void authority;
143
+ return new Map(this.#entries);
144
+ }
145
+ /**
146
+ * How many entries the snapshot holds. The one thing it will tell you about itself — a count is
147
+ * not a value, so it leaks nothing, and it lets a caller (and a test) see that a capture taken
148
+ * outside an active scope is simply empty rather than an error.
149
+ */
150
+ size() {
151
+ return this.#entries.size;
152
+ }
153
+ }
154
+ exports.CapturedContext = CapturedContext;
155
+ //# sourceMappingURL=CapturedContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CapturedContext.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/CapturedContext.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAa,uBAAuB;IAChC;;;OAGG;IACc,KAAK,GAAW,2BAA2B,CAAC;IAE7D,4FAA4F;IAC5F,MAAM,CAAU,QAAQ,GAAG,IAAI,uBAAuB,EAAE,CAAC;IAEzD,gBAAuB,CAAC;IAExB,2FAA2F;IAC3F,QAAQ;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC;IACtB,CAAC;;AAfL,0DAgBC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,MAAa,eAAe;IACxB;;;;OAIG;IACH,wKAAwK;IAC/J,QAAQ,CAAuB;IAExC;;;;OAIG;IACH,mDAAmD;IACnD,YAAoB,OAA6B;QAC7C,IAAI,CAAC,QAAQ,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;IAED;;;;;OAKG;IACH,mDAAmD;IACnD,iNAAiN;IACjN,MAAM,CAAC,OAAO,CAAC,SAAkC,EAAE,IAA0B;QACzE,KAAK,SAAS,CAAC;QACf,OAAO,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC;IAED;;;;OAIG;IACH,mDAAmD;IACnD,WAAW,CAAC,SAAkC,EAAE,IAA0B;QACtE,KAAK,SAAS,CAAC;QACf,IAAI,CAAC,KAAK,EAAE,CAAC;QACb,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;YACtC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5C,CAAC;IACL,CAAC;IAED;;;;OAIG;IACH,mDAAmD;IACnD,YAAY,CAAC,SAAkC;QAC3C,KAAK,SAAS,CAAC;QACf,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC;IAED;;;;OAIG;IACH,IAAI;QACA,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC9B,CAAC;CACJ;AAjED,0CAiEC","sourcesContent":["/**\n * A capability TOKEN, not data — the thing you must be holding to build a {@link CapturedContext} or\n * to unpack one.\n *\n * It exists because `CapturedContext` needs a producer that {@link RequestContext} (a different class,\n * in a different file) can call, and TypeScript's `private` is class-scoped: a plain public\n * `CapturedContext.capture(map)` would be exactly the hand-assembled-payload hole this class exists to\n * close. So the producer takes a token whose constructor is private and whose only instance is\n * {@link INTERNAL}, and this module is deliberately NOT re-exported from the package barrel — in any\n * form, type or value.\n *\n * On its own that stops a consumer NAMING the token but not SUPPLYING one, since `null as never`\n * type-checks. It is the barrel's `export type { CapturedContext }` that finishes the job: with no\n * class object on the package surface there is no `capture(...)` to call, cast or not. The two halves\n * are load-bearing together, which is why each names the other.\n *\n * Per CLAUDE.md this is a class rather than a `Symbol` or an object literal: it is nominal (the private\n * `brand` field stops a structurally-identical `{}` from satisfying it) and it has exactly one\n * instantiation point.\n */\nexport class ContextCaptureAuthority {\n /**\n * Nominal brand. Without a private member the class is structurally `{}`, and any object at all\n * would typecheck as an authority.\n */\n private readonly brand: string = 'webpieces.context-capture';\n\n /** The ONE token that exists. The constructor below is private, so no other can be made. */\n static readonly INTERNAL = new ContextCaptureAuthority();\n\n private constructor() {}\n\n /** Kept honest — the brand is read here so it is a real field, not a type-only fiction. */\n describe(): string {\n return this.brand;\n }\n}\n\n/**\n * CapturedContext — an OPAQUE snapshot of a {@link RequestContext} scope.\n *\n * ## What it is for\n *\n * `AsyncLocalStorage` follows `await`, `.then()` and ordinary callbacks on its own, so the vast\n * majority of code never needs this. What it does NOT follow is work whose async chain was BROKEN and\n * re-rooted somewhere else: an item pushed onto an in-memory queue during a request and drained later\n * by a background loop, a batch flushed on a scheduler tick, an `EventEmitter` listener fired from a\n * socket the request does not own, a retry re-armed from a top-level timer, a hand-off to a worker\n * pool. In each of those the work executes under a DIFFERENT (or no) context, so the request id, the\n * log fields and the proven identity would silently vanish from everything the work logs or calls.\n *\n * The answer is two halves: `RequestContext.copyContext()` where the work is ENQUEUED, and\n * `RequestContext.runWithContext(captured, fn)` (or `restoreContext(captured)`) where it RUNS.\n *\n * ## Why it is opaque instead of a `Map<string, unknown>`\n *\n * A restored context legitimately contains TRUSTED values — reinstating what the original scope had\n * proven is the entire point — so the restore side cannot type-check its payload the way\n * `putTrusted`/`getTrusted` do. That left the `Map`-taking signature that this class DELETED (a\n * now-removed `setContext(map)`, and `runWithContext(map, fn)`) as a complete bypass of the trust\n * system: handing it `new Map([['userId', 'victim']])` forged a proven identity in one line, without\n * ever typing a trust verb, and the only thing standing against it was a doc comment saying \"the Map\n * must come from copyContext()\". An agent picks whatever compiles, so a doc comment is not\n * enforcement.\n *\n * Making the PAYLOAD opaque solves it without type-checking the contents: the only way to obtain one is\n * a real capture of a real scope, so whatever it holds was, by construction, already in a context that\n * something legitimately wrote. Concretely:\n *\n * - the constructor is `private`, and there is no public factory — {@link capture} demands a\n * {@link ContextCaptureAuthority} that cannot be named outside this package;\n * - the package barrel exports this class as a TYPE ONLY, so a consumer never receives the class\n * object at all and cannot reach `capture` even with a cast. That second half matters: a token whose\n * TYPE is unexported still stops nothing on its own, because `capture(null as never, forgedMap)`\n * type-checks. Withholding the class object is what actually closes it;\n * - the entries live in `#entries`, a genuine ECMAScript private field, so they are unreachable at\n * RUNTIME as well as at compile time — no `Object.keys`, no cast, no index signature;\n * - the map is defensively copied ON CAPTURE and again ON RESTORE, so a caller who still holds the\n * live context (or who keeps writing to it after capturing) cannot reach through the snapshot, and a\n * snapshot can be restored repeatedly without the first restore's mutations bleeding into the second.\n *\n * There is deliberately no reader: nothing hands the entries back out. That is why `getAll()` is gone\n * rather than re-typed to return one of these — a `CapturedContext` you cannot read is useless as a\n * `getAll`, and a readable one would be the raw enumeration of every trusted value all over again.\n *\n * The one residual: a consumer holding a snapshot can still cast a token into `toFreshStore` and read\n * the entries back out as a plain Map. That is knowingly accepted, and it is the same asymmetry\n * `RequestContext.getAny` states — FORGING a trusted value is the dangerous direction and is closed\n * here; reading one you were already legitimately handed, without saying `getTrusted`, costs you\n * nothing but the type. Closing it too would mean no method could take the token at all, which is to\n * say no restore could exist.\n */\nexport class CapturedContext {\n /**\n * A real ECMAScript private field, not a TypeScript `private`. The distinction matters here: `#`\n * is enforced by the runtime, so the snapshot's contents cannot be reached by a cast, by\n * `Object.entries`, or by `as unknown as { entries: Map<string, unknown> }`.\n */\n // webpieces-disable no-any-unknown -- the context store is deliberately type-erased; each ContextKey carries its own value type and the typed verbs re-apply it on read\n readonly #entries: Map<string, unknown>;\n\n /**\n * PRIVATE — a CapturedContext can only come from {@link capture}, which in turn can only be called\n * by code holding a {@link ContextCaptureAuthority}. Copies the map so the snapshot is never a\n * window onto a live store.\n */\n // webpieces-disable no-any-unknown -- see #entries\n private constructor(entries: Map<string, unknown>) {\n this.#entries = new Map(entries);\n }\n\n /**\n * The ONLY producer. `authority` is a compile-time capability, not a runtime check — so it is\n * referenced below only to keep it from being an unused parameter. Note the token alone is not the\n * guarantee (a cast can supply one); the guarantee is that the barrel exports this class as a TYPE\n * ONLY, so no consumer ever holds the class object this static hangs off. See the class doc.\n */\n // webpieces-disable no-any-unknown -- see #entries\n // webpieces-disable no-function-outside-class -- static factory standing in for the (private) constructor; making it an instance method would mean an instance already existed, which is the thing being created\n static capture(authority: ContextCaptureAuthority, live: Map<string, unknown>): CapturedContext {\n void authority;\n return new CapturedContext(live);\n }\n\n /**\n * Overwrite a LIVE store with this snapshot — the engine behind `RequestContext.restoreContext`.\n * Write-only by design: it pushes entries in and hands nothing back, so it is not a side door onto\n * the snapshot's contents.\n */\n // webpieces-disable no-any-unknown -- see #entries\n restoreInto(authority: ContextCaptureAuthority, live: Map<string, unknown>): void {\n void authority;\n live.clear();\n for (const name of this.#entries.keys()) {\n live.set(name, this.#entries.get(name));\n }\n }\n\n /**\n * A FRESH store holding this snapshot — the engine behind `RequestContext.runWithContext`, which\n * opens a new AsyncLocalStorage scope around it. Fresh (a copy) rather than the internal map, so\n * everything the restored scope writes stays in that scope and the snapshot remains reusable.\n */\n // webpieces-disable no-any-unknown -- see #entries\n toFreshStore(authority: ContextCaptureAuthority): Map<string, unknown> {\n void authority;\n return new Map(this.#entries);\n }\n\n /**\n * How many entries the snapshot holds. The one thing it will tell you about itself — a count is\n * not a value, so it leaks nothing, and it lets a caller (and a test) see that a capture taken\n * outside an active scope is simply empty rather than an error.\n */\n size(): number {\n return this.#entries.size;\n }\n}\n"]}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * COMPILE-TIME assertions that a context can only be restored from a REAL capture.
3
+ *
4
+ * The runtime half — that a snapshot round-trips, and that mutating the source afterwards does not
5
+ * reach it — is in `CapturedContext.spec.ts`. What a spec CANNOT express is the half that matters
6
+ * most: that `RequestContext.restoreContext(new Map([['userId', 'victim']]))` does not compile. Each
7
+ * `@ts-expect-error` below fails the build with TS2578 the day its line starts compiling again.
8
+ *
9
+ * In COMPILED source deliberately — `tsconfig.lib.json` excludes specs and vitest strips types with
10
+ * esbuild, so a `@ts-expect-error` in a `.spec.ts` is inert and the suite would pass either way. See
11
+ * `RequestContextTrustCompileAssertions` for the trust-verb half.
12
+ */
13
+ export declare class CapturedContextCompileAssertions {
14
+ /** THE hole this change closes: a hand-assembled payload forging a trusted value. */
15
+ cannotRestoreAHandBuiltMap(): void;
16
+ /** Same hole through the other door. */
17
+ cannotRunWithAHandBuiltMap(): void;
18
+ /** Nor by asserting an object literal into the shape — the private `#entries` makes it nominal. */
19
+ cannotFakeTheShape(): void;
20
+ /** Nor by constructing one directly — the constructor is private. */
21
+ cannotConstructOneDirectly(): void;
22
+ /**
23
+ * Nor by calling the factory: it demands a capability token whose own constructor is private, so
24
+ * even code that can NAME the token (this package) cannot mint a second one.
25
+ */
26
+ cannotMintAnAuthority(): void;
27
+ /** And the factory is not callable without one at all. */
28
+ cannotCaptureWithoutAnAuthority(): void;
29
+ /** POSITIVE: the real round trip must keep compiling — restoring a proven value IS the point. */
30
+ theRealRoundTripCompiles(): void;
31
+ }
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CapturedContextCompileAssertions = void 0;
4
+ const CapturedContext_1 = require("./CapturedContext");
5
+ const RequestContext_1 = require("./RequestContext");
6
+ /**
7
+ * COMPILE-TIME assertions that a context can only be restored from a REAL capture.
8
+ *
9
+ * The runtime half — that a snapshot round-trips, and that mutating the source afterwards does not
10
+ * reach it — is in `CapturedContext.spec.ts`. What a spec CANNOT express is the half that matters
11
+ * most: that `RequestContext.restoreContext(new Map([['userId', 'victim']]))` does not compile. Each
12
+ * `@ts-expect-error` below fails the build with TS2578 the day its line starts compiling again.
13
+ *
14
+ * In COMPILED source deliberately — `tsconfig.lib.json` excludes specs and vitest strips types with
15
+ * esbuild, so a `@ts-expect-error` in a `.spec.ts` is inert and the suite would pass either way. See
16
+ * `RequestContextTrustCompileAssertions` for the trust-verb half.
17
+ */
18
+ class CapturedContextCompileAssertions {
19
+ /** THE hole this change closes: a hand-assembled payload forging a trusted value. */
20
+ cannotRestoreAHandBuiltMap() {
21
+ // @ts-expect-error - restoreContext takes an opaque CapturedContext, never a raw Map
22
+ RequestContext_1.RequestContext.restoreContext(new Map([['userId', 'victim']]));
23
+ }
24
+ /** Same hole through the other door. */
25
+ cannotRunWithAHandBuiltMap() {
26
+ // @ts-expect-error - runWithContext takes an opaque CapturedContext, never a raw Map
27
+ RequestContext_1.RequestContext.runWithContext(new Map([['userId', 'victim']]), () => undefined);
28
+ }
29
+ /** Nor by asserting an object literal into the shape — the private `#entries` makes it nominal. */
30
+ cannotFakeTheShape() {
31
+ // @ts-expect-error - an object literal is not a CapturedContext; #entries is not satisfiable
32
+ const forged = { size: () => 1 };
33
+ void forged;
34
+ }
35
+ /** Nor by constructing one directly — the constructor is private. */
36
+ cannotConstructOneDirectly() {
37
+ // @ts-expect-error - the constructor is private; copyContext() is the only producer
38
+ const forged = new CapturedContext_1.CapturedContext(new Map([['userId', 'victim']]));
39
+ void forged;
40
+ }
41
+ /**
42
+ * Nor by calling the factory: it demands a capability token whose own constructor is private, so
43
+ * even code that can NAME the token (this package) cannot mint a second one.
44
+ */
45
+ cannotMintAnAuthority() {
46
+ // @ts-expect-error - ContextCaptureAuthority's constructor is private; INTERNAL is the only one
47
+ const forgedAuthority = new CapturedContext_1.ContextCaptureAuthority();
48
+ void forgedAuthority;
49
+ }
50
+ /** And the factory is not callable without one at all. */
51
+ cannotCaptureWithoutAnAuthority() {
52
+ // @ts-expect-error - capture() requires a ContextCaptureAuthority as its first argument
53
+ CapturedContext_1.CapturedContext.capture(new Map([['userId', 'victim']]));
54
+ }
55
+ /** POSITIVE: the real round trip must keep compiling — restoring a proven value IS the point. */
56
+ theRealRoundTripCompiles() {
57
+ const captured = RequestContext_1.RequestContext.copyContext();
58
+ RequestContext_1.RequestContext.runWithContext(captured, () => {
59
+ RequestContext_1.RequestContext.restoreContext(captured);
60
+ });
61
+ }
62
+ }
63
+ exports.CapturedContextCompileAssertions = CapturedContextCompileAssertions;
64
+ //# sourceMappingURL=CapturedContextCompileAssertions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CapturedContextCompileAssertions.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/CapturedContextCompileAssertions.ts"],"names":[],"mappings":";;;AAAA,uDAA6E;AAC7E,qDAAkD;AAElD;;;;;;;;;;;GAWG;AACH,MAAa,gCAAgC;IACzC,qFAAqF;IACrF,0BAA0B;QACtB,qFAAqF;QACrF,+BAAc,CAAC,cAAc,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IACnE,CAAC;IAED,wCAAwC;IACxC,0BAA0B;QACtB,qFAAqF;QACrF,+BAAc,CAAC,cAAc,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACpF,CAAC;IAED,mGAAmG;IACnG,kBAAkB;QACd,6FAA6F;QAC7F,MAAM,MAAM,GAAoB,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC;QAClD,KAAK,MAAM,CAAC;IAChB,CAAC;IAED,qEAAqE;IACrE,0BAA0B;QACtB,oFAAoF;QACpF,MAAM,MAAM,GAAG,IAAI,iCAAe,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QACpE,KAAK,MAAM,CAAC;IAChB,CAAC;IAED;;;OAGG;IACH,qBAAqB;QACjB,gGAAgG;QAChG,MAAM,eAAe,GAAG,IAAI,yCAAuB,EAAE,CAAC;QACtD,KAAK,eAAe,CAAC;IACzB,CAAC;IAED,0DAA0D;IAC1D,+BAA+B;QAC3B,wFAAwF;QACxF,iCAAe,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7D,CAAC;IAED,iGAAiG;IACjG,wBAAwB;QACpB,MAAM,QAAQ,GAAoB,+BAAc,CAAC,WAAW,EAAE,CAAC;QAC/D,+BAAc,CAAC,cAAc,CAAC,QAAQ,EAAE,GAAG,EAAE;YACzC,+BAAc,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;IACP,CAAC;CACJ;AAlDD,4EAkDC","sourcesContent":["import { CapturedContext, ContextCaptureAuthority } from './CapturedContext';\nimport { RequestContext } from './RequestContext';\n\n/**\n * COMPILE-TIME assertions that a context can only be restored from a REAL capture.\n *\n * The runtime half — that a snapshot round-trips, and that mutating the source afterwards does not\n * reach it — is in `CapturedContext.spec.ts`. What a spec CANNOT express is the half that matters\n * most: that `RequestContext.restoreContext(new Map([['userId', 'victim']]))` does not compile. Each\n * `@ts-expect-error` below fails the build with TS2578 the day its line starts compiling again.\n *\n * In COMPILED source deliberately — `tsconfig.lib.json` excludes specs and vitest strips types with\n * esbuild, so a `@ts-expect-error` in a `.spec.ts` is inert and the suite would pass either way. See\n * `RequestContextTrustCompileAssertions` for the trust-verb half.\n */\nexport class CapturedContextCompileAssertions {\n /** THE hole this change closes: a hand-assembled payload forging a trusted value. */\n cannotRestoreAHandBuiltMap(): void {\n // @ts-expect-error - restoreContext takes an opaque CapturedContext, never a raw Map\n RequestContext.restoreContext(new Map([['userId', 'victim']]));\n }\n\n /** Same hole through the other door. */\n cannotRunWithAHandBuiltMap(): void {\n // @ts-expect-error - runWithContext takes an opaque CapturedContext, never a raw Map\n RequestContext.runWithContext(new Map([['userId', 'victim']]), () => undefined);\n }\n\n /** Nor by asserting an object literal into the shape — the private `#entries` makes it nominal. */\n cannotFakeTheShape(): void {\n // @ts-expect-error - an object literal is not a CapturedContext; #entries is not satisfiable\n const forged: CapturedContext = { size: () => 1 };\n void forged;\n }\n\n /** Nor by constructing one directly — the constructor is private. */\n cannotConstructOneDirectly(): void {\n // @ts-expect-error - the constructor is private; copyContext() is the only producer\n const forged = new CapturedContext(new Map([['userId', 'victim']]));\n void forged;\n }\n\n /**\n * Nor by calling the factory: it demands a capability token whose own constructor is private, so\n * even code that can NAME the token (this package) cannot mint a second one.\n */\n cannotMintAnAuthority(): void {\n // @ts-expect-error - ContextCaptureAuthority's constructor is private; INTERNAL is the only one\n const forgedAuthority = new ContextCaptureAuthority();\n void forgedAuthority;\n }\n\n /** And the factory is not callable without one at all. */\n cannotCaptureWithoutAnAuthority(): void {\n // @ts-expect-error - capture() requires a ContextCaptureAuthority as its first argument\n CapturedContext.capture(new Map([['userId', 'victim']]));\n }\n\n /** POSITIVE: the real round trip must keep compiling — restoring a proven value IS the point. */\n theRealRoundTripCompiles(): void {\n const captured: CapturedContext = RequestContext.copyContext();\n RequestContext.runWithContext(captured, () => {\n RequestContext.restoreContext(captured);\n });\n }\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import { ContextKey, AnyContextKey } from '@webpieces/core-util';
2
2
  import { HttpRequest } from './HttpRequest';
3
+ import { CapturedContext } from './CapturedContext';
3
4
  /**
4
5
  * Context management using AsyncLocalStorage.
5
6
  * Similar to Java WebPieces Context class that uses ThreadLocal.
@@ -32,16 +33,26 @@ declare class RequestContextImpl {
32
33
  */
33
34
  run<T>(fn: () => T): T;
34
35
  /**
35
- * Run a function with a specific context — the restore half of {@link copyContext}, used to carry
36
- * a context across an async boundary (XPromise).
36
+ * Open a NEW scope pre-loaded with a snapshot — the restore half of {@link copyContext}, for work
37
+ * whose async chain was broken and re-rooted elsewhere (a queued job drained by a background loop,
38
+ * a batch flushed on a timer, an event listener fired from a socket the request does not own). See
39
+ * {@link CapturedContext} for the full list and for why the payload is opaque.
37
40
  *
38
- * FRAMEWORK-INTERNAL. Unlike the raw string accessors, this deliberately CANNOT be guarded against
39
- * registered key names: a restored context legitimately contains trusted values, since restoring
40
- * them is the entire purpose. So the guarantee here is narrower and worth stating plainly — the
41
- * Map must be one this class produced via `copyContext()`, never one assembled by hand. Handing it
42
- * a hand-built Map forges whatever it contains, and no type or check will stop you.
41
+ * A restored context legitimately contains TRUSTED values reinstating what the original scope
42
+ * proved is the entire point so this cannot type-check its contents the way the trust verbs do.
43
+ * The guarantee instead comes from the PAYLOAD: a {@link CapturedContext} can only be produced by
44
+ * {@link copyContext}, so there is no hand-assembled Map to hand it and no way to forge one.
45
+ *
46
+ * The snapshot is copied into a fresh store, so writes inside `fn` stay inside `fn` and the
47
+ * snapshot stays reusable.
48
+ *
49
+ * Deliberately NOT guarded against nesting the way {@link run} is. `run`'s guard exists because a
50
+ * second EMPTY scope shadowing the first is always a bug; here the inner scope is a faithful copy
51
+ * of a real one, which is the whole point — a worker that restores a snapshot inside a scope it
52
+ * opened per job is correct, not a mistake. Prefer this over {@link restoreContext} unless you
53
+ * specifically need the CURRENT scope overwritten in place.
43
54
  */
44
- runWithContext<T>(context: Map<string, any>, fn: () => T): T;
55
+ runWithContext<T>(captured: CapturedContext, fn: () => T): T;
45
56
  /**
46
57
  * Read a value the framework PROVED — a verified JWT claim, or a fact an app derived from a
47
58
  * verified credential. Does not compile for an untrusted key, so a reader can never mistake a
@@ -193,22 +204,35 @@ declare class RequestContextImpl {
193
204
  */
194
205
  clear(): void;
195
206
  /**
196
- * Copy the current context to a new Map.
197
- * Used by XPromise to preserve context across async boundaries.
198
- */
199
- copyContext(): Map<string, any>;
200
- /**
201
- * Set the entire context from a Map. Used by XPromise to restore context.
207
+ * Snapshot this scope so the work you are about to hand off keeps its request id, log fields and
208
+ * proven identity. The ONLY producer of a {@link CapturedContext} — which is what makes the
209
+ * restore side unforgeable, since there is no other way to obtain the payload it accepts.
202
210
  *
203
- * Same FRAMEWORK-INTERNAL caveat as {@link runWithContext}: the Map must have come from
204
- * `copyContext()`. It cannot be trust-checked, because a faithful restore has to reinstate the
205
- * trusted values the original scope had proven.
211
+ * Outside a `run(...)` block this returns an EMPTY snapshot rather than throwing: capturing "no
212
+ * context" is a legitimate thing for a background caller to do, and restoring it simply installs
213
+ * nothing.
214
+ *
215
+ * The snapshot is a defensive COPY — writes to this context after capturing do not reach it.
206
216
  */
207
- setContext(context: Map<string, any>): void;
217
+ copyContext(): CapturedContext;
208
218
  /**
209
- * Get all context entries.
219
+ * Overwrite the ACTIVE scope with a snapshot. The in-place twin of {@link runWithContext}, and the
220
+ * one you almost never want: prefer `runWithContext`, which gives the restored work its OWN scope
221
+ * and cannot disturb the caller's. Reach for this only when something else owns the scope and it
222
+ * must be re-pointed in place.
223
+ *
224
+ * OVERWRITE, not merge — `clear()` runs first, so every entry the active scope holds and the
225
+ * snapshot does not is DROPPED. That includes the empty case: `restoreContext(copyContext())`
226
+ * taken outside a scope wipes the request id and every proven identity from a live request, and
227
+ * says nothing. That is faithful (a snapshot restores exactly what it captured) but it is the
228
+ * sharp edge of this method and the reason `runWithContext` is the default.
229
+ *
230
+ * Takes only a {@link CapturedContext} for the reason spelled out there — the DELETED Map-taking
231
+ * form let `new Map([['userId','victim']])` forge a proven identity in one line.
232
+ *
233
+ * @throws Error when no RequestContext is active.
210
234
  */
211
- getAll(): Map<string, any>;
235
+ restoreContext(captured: CapturedContext): void;
212
236
  /**
213
237
  * Check if a key exists in the context.
214
238
  */
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RequestContext = void 0;
4
4
  const async_hooks_1 = require("async_hooks");
5
5
  const core_util_1 = require("@webpieces/core-util");
6
+ const CapturedContext_1 = require("./CapturedContext");
6
7
  /** Reserved context key under which the current HttpRequest is stored. */
7
8
  const HTTP_REQUEST_KEY = '__webpieces_http_request__';
8
9
  /**
@@ -48,17 +49,27 @@ class RequestContextImpl {
48
49
  return this.storage.run(store, fn);
49
50
  }
50
51
  /**
51
- * Run a function with a specific context — the restore half of {@link copyContext}, used to carry
52
- * a context across an async boundary (XPromise).
52
+ * Open a NEW scope pre-loaded with a snapshot — the restore half of {@link copyContext}, for work
53
+ * whose async chain was broken and re-rooted elsewhere (a queued job drained by a background loop,
54
+ * a batch flushed on a timer, an event listener fired from a socket the request does not own). See
55
+ * {@link CapturedContext} for the full list and for why the payload is opaque.
53
56
  *
54
- * FRAMEWORK-INTERNAL. Unlike the raw string accessors, this deliberately CANNOT be guarded against
55
- * registered key names: a restored context legitimately contains trusted values, since restoring
56
- * them is the entire purpose. So the guarantee here is narrower and worth stating plainly — the
57
- * Map must be one this class produced via `copyContext()`, never one assembled by hand. Handing it
58
- * a hand-built Map forges whatever it contains, and no type or check will stop you.
57
+ * A restored context legitimately contains TRUSTED values reinstating what the original scope
58
+ * proved is the entire point so this cannot type-check its contents the way the trust verbs do.
59
+ * The guarantee instead comes from the PAYLOAD: a {@link CapturedContext} can only be produced by
60
+ * {@link copyContext}, so there is no hand-assembled Map to hand it and no way to forge one.
61
+ *
62
+ * The snapshot is copied into a fresh store, so writes inside `fn` stay inside `fn` and the
63
+ * snapshot stays reusable.
64
+ *
65
+ * Deliberately NOT guarded against nesting the way {@link run} is. `run`'s guard exists because a
66
+ * second EMPTY scope shadowing the first is always a bug; here the inner scope is a faithful copy
67
+ * of a real one, which is the whole point — a worker that restores a snapshot inside a scope it
68
+ * opened per job is correct, not a mistake. Prefer this over {@link restoreContext} unless you
69
+ * specifically need the CURRENT scope overwritten in place.
59
70
  */
60
- runWithContext(context, fn) {
61
- return this.storage.run(context, fn);
71
+ runWithContext(captured, fn) {
72
+ return this.storage.run(captured.toFreshStore(CapturedContext_1.ContextCaptureAuthority.INTERNAL), fn);
62
73
  }
63
74
  /**
64
75
  * Read a value the framework PROVED — a verified JWT claim, or a fact an app derived from a
@@ -323,39 +334,44 @@ class RequestContextImpl {
323
334
  store?.clear();
324
335
  }
325
336
  /**
326
- * Copy the current context to a new Map.
327
- * Used by XPromise to preserve context across async boundaries.
337
+ * Snapshot this scope so the work you are about to hand off keeps its request id, log fields and
338
+ * proven identity. The ONLY producer of a {@link CapturedContext} — which is what makes the
339
+ * restore side unforgeable, since there is no other way to obtain the payload it accepts.
340
+ *
341
+ * Outside a `run(...)` block this returns an EMPTY snapshot rather than throwing: capturing "no
342
+ * context" is a legitimate thing for a background caller to do, and restoring it simply installs
343
+ * nothing.
344
+ *
345
+ * The snapshot is a defensive COPY — writes to this context after capturing do not reach it.
328
346
  */
329
347
  copyContext() {
330
348
  const store = this.storage.getStore();
331
- if (!store) {
332
- return new Map();
333
- }
334
- return new Map(store);
349
+ return CapturedContext_1.CapturedContext.capture(CapturedContext_1.ContextCaptureAuthority.INTERNAL, store ?? new Map());
335
350
  }
336
351
  /**
337
- * Set the entire context from a Map. Used by XPromise to restore context.
352
+ * Overwrite the ACTIVE scope with a snapshot. The in-place twin of {@link runWithContext}, and the
353
+ * one you almost never want: prefer `runWithContext`, which gives the restored work its OWN scope
354
+ * and cannot disturb the caller's. Reach for this only when something else owns the scope and it
355
+ * must be re-pointed in place.
356
+ *
357
+ * OVERWRITE, not merge — `clear()` runs first, so every entry the active scope holds and the
358
+ * snapshot does not is DROPPED. That includes the empty case: `restoreContext(copyContext())`
359
+ * taken outside a scope wipes the request id and every proven identity from a live request, and
360
+ * says nothing. That is faithful (a snapshot restores exactly what it captured) but it is the
361
+ * sharp edge of this method and the reason `runWithContext` is the default.
338
362
  *
339
- * Same FRAMEWORK-INTERNAL caveat as {@link runWithContext}: the Map must have come from
340
- * `copyContext()`. It cannot be trust-checked, because a faithful restore has to reinstate the
341
- * trusted values the original scope had proven.
363
+ * Takes only a {@link CapturedContext} for the reason spelled out there — the DELETED Map-taking
364
+ * form let `new Map([['userId','victim']])` forge a proven identity in one line.
365
+ *
366
+ * @throws Error when no RequestContext is active.
342
367
  */
343
- setContext(context) {
368
+ restoreContext(captured) {
344
369
  const store = this.storage.getStore();
345
370
  if (!store) {
346
- throw new Error('No context available. Did you call Context.run() first?');
371
+ throw new Error('No context available to restore into. Either open one with RequestContext.run(...) ' +
372
+ 'first, or use RequestContext.runWithContext(captured, fn), which opens its own.');
347
373
  }
348
- store.clear();
349
- context.forEach((value, key) => {
350
- store.set(key, value);
351
- });
352
- }
353
- /**
354
- * Get all context entries.
355
- */
356
- getAll() {
357
- const store = this.storage.getStore();
358
- return store ? new Map(store) : new Map();
374
+ captured.restoreInto(CapturedContext_1.ContextCaptureAuthority.INTERNAL, store);
359
375
  }
360
376
  /**
361
377
  * Check if a key exists in the context.
@@ -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, AnyContextKey, HeaderRegistry, ServiceInfo } 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 — the restore half of {@link copyContext}, used to carry\n * a context across an async boundary (XPromise).\n *\n * FRAMEWORK-INTERNAL. Unlike the raw string accessors, this deliberately CANNOT be guarded against\n * registered key names: a restored context legitimately contains trusted values, since restoring\n * them is the entire purpose. So the guarantee here is narrower and worth stating plainly — the\n * Map must be one this class produced via `copyContext()`, never one assembled by hand. Handing it\n * a hand-built Map forges whatever it contains, and no type or check will stop you.\n */\n runWithContext<T>(context: Map<string, any>, fn: () => T): T {\n return this.storage.run(context, fn);\n }\n\n /**\n * Read a value the framework PROVED — a verified JWT claim, or a fact an app derived from a\n * verified credential. Does not compile for an untrusted key, so a reader can never mistake a\n * caller-asserted value for an authenticated one.\n *\n * This is the ONLY read that is safe to feed into an authorization decision. If you find\n * yourself wanting `getUntrusted` for that, the fix is to make the key trusted and have an\n * authenticator vouch for it — not to use the other verb.\n *\n * The return type is the key's OWN value type `V` — `string` for wire/log keys, `ApiCallInfo`\n * for the api tag, `TestCaseRecorder` for the recorder — INFERRED from the key, never asserted\n * by the caller. This is the typed public surface over the deliberately type-erased backing Map.\n */\n getTrusted<V>(key: ContextKey<V, 'trusted'>): V | undefined {\n return this.readByName<V>(key.name);\n }\n\n /**\n * Read a value a caller merely ASSERTED — a browser-minted actionId, a recording flag, an\n * in-process log tag. Does not compile for a trusted key: reading a proven fact through the\n * untrusted verb would under-claim and hide, at the call site, that the value IS reliable.\n *\n * Treat everything this returns as attacker-controlled. It is fine for logging, tracing,\n * routing hints and rate-limit bucketing; it is never an input to \"may they do this?\".\n */\n getUntrusted<V>(key: ContextKey<V, 'untrusted'>): V | undefined {\n return this.readByName<V>(key.name);\n }\n\n /**\n * Store a value the framework PROVED. A distinct, greppable verb precisely so that writing a\n * trusted value is something code has to do ON PURPOSE — `grep -rn putTrusted` lists every place\n * in the repo that claims to have proven something, which is a reviewable set.\n *\n * Callers are the framework `AuthFilter` (stamping {@link ContextTuple}s an app's JwtHook derived\n * from a verified credential) and app code that has itself verified something out-of-band — the\n * signed-webhook case: Twilio/WhatsApp proves the phone number, the app looks up the userId, and\n * that userId is every bit as proven as a JWT claim.\n *\n * Does not compile for an untrusted key.\n */\n putTrusted<V>(key: ContextKey<V, 'trusted'>, value: V): void {\n this.writeByName(key.name, value);\n }\n\n /**\n * Store a caller-asserted value. `value` is type-checked against the key's value type `V`, so you\n * cannot put a number under a `ContextKey<string>` or a raw object under a typed key.\n *\n * Does not compile for a trusted key — which is what stops the inbound-header path, the api-tag\n * seam and ordinary app code from being side doors that forge a trusted value.\n */\n putUntrusted<V>(key: ContextKey<V, 'untrusted'>, value: V): void {\n this.writeByName(key.name, value);\n }\n\n /**\n * Read a key of ANY trust level and ANY value type, as `unknown`.\n *\n * FRAMEWORK SERIALIZATION ONLY — the log-field builders below, the outbound header builder, and\n * the {@link ContextReader} seam. Those loop over `HeaderRegistry` key arrays that are mixed in\n * both value type and trust, and they are not making a trust DECISION: they are copying values to\n * a log line or to the wire.\n *\n * It is deliberately read-only and has no write twin. A `putAny` would re-open the exact hole the\n * typed verbs close, because forging a trusted value is the dangerous direction; reading one\n * without saying `getTrusted` only costs you the `unknown` return type.\n */\n // webpieces-disable no-any-unknown -- key-agnostic serialization read: the key array is mixed in value type, so unknown is the honest return\n getAny(key: AnyContextKey): unknown {\n return this.readByName<unknown>(key.name);\n }\n\n /** Clear one context key. Used by the api-tag seam's set → log → remove span (see LogApiCall). */\n removeKey(key: AnyContextKey): void {\n this.storage.getStore()?.delete(key.name);\n }\n\n hasKey(key: AnyContextKey): boolean {\n return this.storage.getStore()?.has(key.name) ?? false;\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.maskForLogs}), 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 const fields = new Map<string, string>();\n if (!this.isActive()) {\n return fields;\n }\n // The registry owns WHICH keys log (getLoggedKeys); we read each straight from THIS context\n // and each ContextKey masks its own secured value. String-only — this map feeds wire/MDC +\n // recorder fixtures — so an object-valued key (API_CALL_INFO) is guarded out by the\n // typeof-string check; objects ride buildStructuredLogFields instead. (Was a HeaderRegistry\n // method taking a read callback; only the server ever called it, so the seam was dead weight.)\n for (const key of HeaderRegistry.get().getLoggedKeys()) {\n // getLoggedKeys() is AnyContextKey[] — mixed in BOTH value type and trust — so this reads\n // through getAny (serialization, not a trust decision) and narrows with the typeof-string\n // guard rather than asserting a value type per key.\n const value = this.getAny(key);\n if (typeof value === 'string' && value) {\n fields.set(key.name, key.maskForLogs(value));\n }\n }\n return fields;\n }\n\n /**\n * The STRUCTURED field map for the node logging backends: like {@link buildLogFields}, but values\n * may be OBJECTS, so an object-valued logged key ({@link WebpiecesCoreHeaders.API_CALL_INFO} holding\n * an {@link ApiCallInfo}) survives as an object and the winston/bunyan backends nest it into\n * `jsonPayload.api`. Reads values UNTYPED (not `<string>`) so the object comes through intact.\n *\n * Outside a `run(...)` block it returns just the `svcName` + `version` entries below (not a fully\n * empty map): a log line is never worth crashing over, and startup/background lines must still say\n * which service and build emitted them.\n *\n * PLUS this service's `svcName` and this build's `version` from {@link ServiceInfo}. Neither is a\n * {@link ContextKey} — they are process-global identity facts, added HERE (BEFORE the active-context\n * check) so EVERY log line of BOTH node backends (winston/bunyan read this one map) says which\n * service and build emitted it — request path, startup, and background jobs alike — with no\n * per-backend duplication. This is the SINGLE place both are stamped, keeping the two backends\n * symmetrical (jsonPayload.svcName + jsonPayload.version). Read via the non-throwing\n * {@link ServiceInfo.getName} / {@link ServiceInfo.getVersion}, so each is simply ABSENT until\n * `setInfo` has run — logging keeps working before the service is identified, then the fields start\n * appearing. Caller-set `svcName`/`version` headers (there are none by convention) would be\n * overwritten here; that is intentional — the ServiceInfo identity is authoritative.\n */\n buildStructuredLogFields(): Map<string, string | object> {\n const fields = new Map<string, string | object>();\n // This service's `svcName` + this build's `version` from ServiceInfo — NOT ContextKeys, they are\n // process-global identity facts. Added FIRST, BEFORE the active-context check, so they ride EVERY\n // line of both node backends (they read this one map) — including startup and background-job lines\n // emitted with NO active RequestContext. Treated identically and read per-record via the\n // non-throwing getters, so each is simply ABSENT until setInfo has run, then starts appearing —\n // even if setInfo runs after a backend was constructed. This is the ONE place both facts are\n // stamped, so winston and bunyan stay symmetrical (jsonPayload.svcName + jsonPayload.version).\n const svcName = ServiceInfo.getName();\n if (svcName) {\n fields.set('svcName', svcName);\n }\n const version = ServiceInfo.getVersion();\n if (version) {\n fields.set('version', version);\n }\n if (!this.isActive()) {\n return fields;\n }\n // Like buildLogFields, but values may be OBJECTS (API_CALL_INFO): read UNTYPED so the object\n // survives and winston/bunyan nest it into jsonPayload.<name>. Secured STRING values are still\n // masked per key; non-string primitives are ignored rather than String()-flattened. (Inlined\n // from HeaderRegistry for the same reason as buildLogFields — only the server called it.)\n for (const key of HeaderRegistry.get().getLoggedKeys()) {\n const value = this.getAny(key);\n if (value === undefined || value === null) {\n continue;\n }\n if (typeof value === 'string') {\n if (value) {\n fields.set(key.name, key.maskForLogs(value));\n }\n } else if (typeof value === 'object') {\n fields.set(key.name, value);\n }\n }\n return fields;\n }\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 /**\n * Store a value under a RAW STRING key — the escape hatch for the framework's own reserved,\n * UNREGISTERED slots ('__webpieces_http_request__', the AuthFilter principal, the Cloud Tasks\n * schedule frame). Those are internal plumbing, not context keys, so they have no ContextKey and\n * no trust level.\n *\n * REJECTS any name that belongs to a registered {@link ContextKey}. Without that check this\n * method is a complete bypass of the trust system — `put('userId', req.body.userId)` would forge\n * a trusted value while never typing `putTrusted`, and an agent picks whatever compiles. The\n * check is necessarily a RUNTIME one: the registry is populated at `configure()` time, so \"is\n * this string a registered key name\" is not a fact a type can express.\n *\n * @throws Error when `key` is a registered ContextKey name — naming the verb to use instead.\n */\n // webpieces-disable no-any-unknown -- reserved-slot values are heterogeneous (HttpRequest, principal, schedule frame)\n put(key: string, value: any): void {\n this.rejectRegisteredName(key, 'putTrusted / putUntrusted');\n this.writeByName(key, value);\n }\n\n /**\n * Retrieve a value stored under a RAW STRING key. Same reserved-slot purpose, and the same\n * rejection, as {@link put} — reading `get('userId')` would hand back a trusted value without the\n * call site ever saying `getTrusted`, which is exactly the ambiguity this whole change removes.\n *\n * @throws Error when `key` is a registered ContextKey name — naming the verb to use instead.\n */\n // webpieces-disable no-any-unknown -- reserved-slot values are heterogeneous; callers name the concrete type\n get<T = any>(key: string): T | undefined {\n this.rejectRegisteredName(key, 'getTrusted / getUntrusted / getAny');\n return this.readByName<T>(key);\n }\n\n /**\n * Remove a value stored under a RAW STRING key. Registered names are rejected here too: deleting\n * a trusted key out from under a reader is a trust decision, so it goes through {@link removeKey}\n * with the key in hand.\n *\n * @throws Error when `key` is a registered ContextKey name.\n */\n remove(key: string): void {\n this.rejectRegisteredName(key, 'removeKey(key)');\n this.storage.getStore()?.delete(key);\n }\n\n /**\n * The guard behind the three raw-string accessors above. Silent (a no-op) until\n * `HeaderRegistry.configure(...)` has run, which is correct rather than lax: with no registry\n * there are no registered keys, so there is no trusted value to launder.\n */\n private rejectRegisteredName(name: string, useInstead: string): void {\n if (!HeaderRegistry.isConfigured()) {\n return;\n }\n const key = HeaderRegistry.get().findByName(name);\n if (key) {\n throw new Error(\n `RequestContext string accessors cannot touch '${name}' — it is a registered ` +\n `ContextKey (trust: '${key.trust}'). The raw string form hides whether the value is ` +\n `a proven fact or something a caller asserted, so it is a bypass of the trust ` +\n `system. Use ${useInstead} with the ContextKey itself.`,\n );\n }\n }\n\n /** The type-erased read. Every typed verb above funnels here; nothing else reads the store. */\n private readByName<T>(name: string): T | undefined {\n return this.storage.getStore()?.get(name);\n }\n\n /** The type-erased write. Every typed verb above funnels here; nothing else writes the store. */\n // webpieces-disable no-any-unknown -- context values are heterogeneous (strings, recorder, meta objects)\n private writeByName(name: 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(name, value);\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. Used by XPromise to restore context.\n *\n * Same FRAMEWORK-INTERNAL caveat as {@link runWithContext}: the Map must have come from\n * `copyContext()`. It cannot be trust-checked, because a faithful restore has to reinstate the\n * trusted values the original scope had proven.\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 /**\n * Presence of a value under a RAW STRING key. Guarded like its three siblings: `has('userId')`\n * alongside `hasKey(WebpiecesCoreHeaders.USER_ID)` would be a second spelling of one question,\n * and the string form is the one that says nothing about whether the value can be believed.\n *\n * @throws Error when `key` is a registered ContextKey name.\n */\n has(key: string): boolean {\n this.rejectRegisteredName(key, 'hasKey(key)');\n return this.storage.getStore()?.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, AnyContextKey, HeaderRegistry, ServiceInfo } from '@webpieces/core-util';\nimport { HttpRequest } from './HttpRequest';\nimport { CapturedContext, ContextCaptureAuthority } from './CapturedContext';\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 * Open a NEW scope pre-loaded with a snapshot — the restore half of {@link copyContext}, for work\n * whose async chain was broken and re-rooted elsewhere (a queued job drained by a background loop,\n * a batch flushed on a timer, an event listener fired from a socket the request does not own). See\n * {@link CapturedContext} for the full list and for why the payload is opaque.\n *\n * A restored context legitimately contains TRUSTED values — reinstating what the original scope\n * proved is the entire point — so this cannot type-check its contents the way the trust verbs do.\n * The guarantee instead comes from the PAYLOAD: a {@link CapturedContext} can only be produced by\n * {@link copyContext}, so there is no hand-assembled Map to hand it and no way to forge one.\n *\n * The snapshot is copied into a fresh store, so writes inside `fn` stay inside `fn` and the\n * snapshot stays reusable.\n *\n * Deliberately NOT guarded against nesting the way {@link run} is. `run`'s guard exists because a\n * second EMPTY scope shadowing the first is always a bug; here the inner scope is a faithful copy\n * of a real one, which is the whole point — a worker that restores a snapshot inside a scope it\n * opened per job is correct, not a mistake. Prefer this over {@link restoreContext} unless you\n * specifically need the CURRENT scope overwritten in place.\n */\n runWithContext<T>(captured: CapturedContext, fn: () => T): T {\n return this.storage.run(captured.toFreshStore(ContextCaptureAuthority.INTERNAL), fn);\n }\n\n /**\n * Read a value the framework PROVED — a verified JWT claim, or a fact an app derived from a\n * verified credential. Does not compile for an untrusted key, so a reader can never mistake a\n * caller-asserted value for an authenticated one.\n *\n * This is the ONLY read that is safe to feed into an authorization decision. If you find\n * yourself wanting `getUntrusted` for that, the fix is to make the key trusted and have an\n * authenticator vouch for it — not to use the other verb.\n *\n * The return type is the key's OWN value type `V` — `string` for wire/log keys, `ApiCallInfo`\n * for the api tag, `TestCaseRecorder` for the recorder — INFERRED from the key, never asserted\n * by the caller. This is the typed public surface over the deliberately type-erased backing Map.\n */\n getTrusted<V>(key: ContextKey<V, 'trusted'>): V | undefined {\n return this.readByName<V>(key.name);\n }\n\n /**\n * Read a value a caller merely ASSERTED — a browser-minted actionId, a recording flag, an\n * in-process log tag. Does not compile for a trusted key: reading a proven fact through the\n * untrusted verb would under-claim and hide, at the call site, that the value IS reliable.\n *\n * Treat everything this returns as attacker-controlled. It is fine for logging, tracing,\n * routing hints and rate-limit bucketing; it is never an input to \"may they do this?\".\n */\n getUntrusted<V>(key: ContextKey<V, 'untrusted'>): V | undefined {\n return this.readByName<V>(key.name);\n }\n\n /**\n * Store a value the framework PROVED. A distinct, greppable verb precisely so that writing a\n * trusted value is something code has to do ON PURPOSE — `grep -rn putTrusted` lists every place\n * in the repo that claims to have proven something, which is a reviewable set.\n *\n * Callers are the framework `AuthFilter` (stamping {@link ContextTuple}s an app's JwtHook derived\n * from a verified credential) and app code that has itself verified something out-of-band — the\n * signed-webhook case: Twilio/WhatsApp proves the phone number, the app looks up the userId, and\n * that userId is every bit as proven as a JWT claim.\n *\n * Does not compile for an untrusted key.\n */\n putTrusted<V>(key: ContextKey<V, 'trusted'>, value: V): void {\n this.writeByName(key.name, value);\n }\n\n /**\n * Store a caller-asserted value. `value` is type-checked against the key's value type `V`, so you\n * cannot put a number under a `ContextKey<string>` or a raw object under a typed key.\n *\n * Does not compile for a trusted key — which is what stops the inbound-header path, the api-tag\n * seam and ordinary app code from being side doors that forge a trusted value.\n */\n putUntrusted<V>(key: ContextKey<V, 'untrusted'>, value: V): void {\n this.writeByName(key.name, value);\n }\n\n /**\n * Read a key of ANY trust level and ANY value type, as `unknown`.\n *\n * FRAMEWORK SERIALIZATION ONLY — the log-field builders below, the outbound header builder, and\n * the {@link ContextReader} seam. Those loop over `HeaderRegistry` key arrays that are mixed in\n * both value type and trust, and they are not making a trust DECISION: they are copying values to\n * a log line or to the wire.\n *\n * It is deliberately read-only and has no write twin. A `putAny` would re-open the exact hole the\n * typed verbs close, because forging a trusted value is the dangerous direction; reading one\n * without saying `getTrusted` only costs you the `unknown` return type.\n */\n // webpieces-disable no-any-unknown -- key-agnostic serialization read: the key array is mixed in value type, so unknown is the honest return\n getAny(key: AnyContextKey): unknown {\n return this.readByName<unknown>(key.name);\n }\n\n /** Clear one context key. Used by the api-tag seam's set → log → remove span (see LogApiCall). */\n removeKey(key: AnyContextKey): void {\n this.storage.getStore()?.delete(key.name);\n }\n\n hasKey(key: AnyContextKey): boolean {\n return this.storage.getStore()?.has(key.name) ?? false;\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.maskForLogs}), 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 const fields = new Map<string, string>();\n if (!this.isActive()) {\n return fields;\n }\n // The registry owns WHICH keys log (getLoggedKeys); we read each straight from THIS context\n // and each ContextKey masks its own secured value. String-only — this map feeds wire/MDC +\n // recorder fixtures — so an object-valued key (API_CALL_INFO) is guarded out by the\n // typeof-string check; objects ride buildStructuredLogFields instead. (Was a HeaderRegistry\n // method taking a read callback; only the server ever called it, so the seam was dead weight.)\n for (const key of HeaderRegistry.get().getLoggedKeys()) {\n // getLoggedKeys() is AnyContextKey[] — mixed in BOTH value type and trust — so this reads\n // through getAny (serialization, not a trust decision) and narrows with the typeof-string\n // guard rather than asserting a value type per key.\n const value = this.getAny(key);\n if (typeof value === 'string' && value) {\n fields.set(key.name, key.maskForLogs(value));\n }\n }\n return fields;\n }\n\n /**\n * The STRUCTURED field map for the node logging backends: like {@link buildLogFields}, but values\n * may be OBJECTS, so an object-valued logged key ({@link WebpiecesCoreHeaders.API_CALL_INFO} holding\n * an {@link ApiCallInfo}) survives as an object and the winston/bunyan backends nest it into\n * `jsonPayload.api`. Reads values UNTYPED (not `<string>`) so the object comes through intact.\n *\n * Outside a `run(...)` block it returns just the `svcName` + `version` entries below (not a fully\n * empty map): a log line is never worth crashing over, and startup/background lines must still say\n * which service and build emitted them.\n *\n * PLUS this service's `svcName` and this build's `version` from {@link ServiceInfo}. Neither is a\n * {@link ContextKey} — they are process-global identity facts, added HERE (BEFORE the active-context\n * check) so EVERY log line of BOTH node backends (winston/bunyan read this one map) says which\n * service and build emitted it — request path, startup, and background jobs alike — with no\n * per-backend duplication. This is the SINGLE place both are stamped, keeping the two backends\n * symmetrical (jsonPayload.svcName + jsonPayload.version). Read via the non-throwing\n * {@link ServiceInfo.getName} / {@link ServiceInfo.getVersion}, so each is simply ABSENT until\n * `setInfo` has run — logging keeps working before the service is identified, then the fields start\n * appearing. Caller-set `svcName`/`version` headers (there are none by convention) would be\n * overwritten here; that is intentional — the ServiceInfo identity is authoritative.\n */\n buildStructuredLogFields(): Map<string, string | object> {\n const fields = new Map<string, string | object>();\n // This service's `svcName` + this build's `version` from ServiceInfo — NOT ContextKeys, they are\n // process-global identity facts. Added FIRST, BEFORE the active-context check, so they ride EVERY\n // line of both node backends (they read this one map) — including startup and background-job lines\n // emitted with NO active RequestContext. Treated identically and read per-record via the\n // non-throwing getters, so each is simply ABSENT until setInfo has run, then starts appearing —\n // even if setInfo runs after a backend was constructed. This is the ONE place both facts are\n // stamped, so winston and bunyan stay symmetrical (jsonPayload.svcName + jsonPayload.version).\n const svcName = ServiceInfo.getName();\n if (svcName) {\n fields.set('svcName', svcName);\n }\n const version = ServiceInfo.getVersion();\n if (version) {\n fields.set('version', version);\n }\n if (!this.isActive()) {\n return fields;\n }\n // Like buildLogFields, but values may be OBJECTS (API_CALL_INFO): read UNTYPED so the object\n // survives and winston/bunyan nest it into jsonPayload.<name>. Secured STRING values are still\n // masked per key; non-string primitives are ignored rather than String()-flattened. (Inlined\n // from HeaderRegistry for the same reason as buildLogFields — only the server called it.)\n for (const key of HeaderRegistry.get().getLoggedKeys()) {\n const value = this.getAny(key);\n if (value === undefined || value === null) {\n continue;\n }\n if (typeof value === 'string') {\n if (value) {\n fields.set(key.name, key.maskForLogs(value));\n }\n } else if (typeof value === 'object') {\n fields.set(key.name, value);\n }\n }\n return fields;\n }\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 /**\n * Store a value under a RAW STRING key — the escape hatch for the framework's own reserved,\n * UNREGISTERED slots ('__webpieces_http_request__', the AuthFilter principal, the Cloud Tasks\n * schedule frame). Those are internal plumbing, not context keys, so they have no ContextKey and\n * no trust level.\n *\n * REJECTS any name that belongs to a registered {@link ContextKey}. Without that check this\n * method is a complete bypass of the trust system — `put('userId', req.body.userId)` would forge\n * a trusted value while never typing `putTrusted`, and an agent picks whatever compiles. The\n * check is necessarily a RUNTIME one: the registry is populated at `configure()` time, so \"is\n * this string a registered key name\" is not a fact a type can express.\n *\n * @throws Error when `key` is a registered ContextKey name — naming the verb to use instead.\n */\n // webpieces-disable no-any-unknown -- reserved-slot values are heterogeneous (HttpRequest, principal, schedule frame)\n put(key: string, value: any): void {\n this.rejectRegisteredName(key, 'putTrusted / putUntrusted');\n this.writeByName(key, value);\n }\n\n /**\n * Retrieve a value stored under a RAW STRING key. Same reserved-slot purpose, and the same\n * rejection, as {@link put} — reading `get('userId')` would hand back a trusted value without the\n * call site ever saying `getTrusted`, which is exactly the ambiguity this whole change removes.\n *\n * @throws Error when `key` is a registered ContextKey name — naming the verb to use instead.\n */\n // webpieces-disable no-any-unknown -- reserved-slot values are heterogeneous; callers name the concrete type\n get<T = any>(key: string): T | undefined {\n this.rejectRegisteredName(key, 'getTrusted / getUntrusted / getAny');\n return this.readByName<T>(key);\n }\n\n /**\n * Remove a value stored under a RAW STRING key. Registered names are rejected here too: deleting\n * a trusted key out from under a reader is a trust decision, so it goes through {@link removeKey}\n * with the key in hand.\n *\n * @throws Error when `key` is a registered ContextKey name.\n */\n remove(key: string): void {\n this.rejectRegisteredName(key, 'removeKey(key)');\n this.storage.getStore()?.delete(key);\n }\n\n /**\n * The guard behind the three raw-string accessors above. Silent (a no-op) until\n * `HeaderRegistry.configure(...)` has run, which is correct rather than lax: with no registry\n * there are no registered keys, so there is no trusted value to launder.\n */\n private rejectRegisteredName(name: string, useInstead: string): void {\n if (!HeaderRegistry.isConfigured()) {\n return;\n }\n const key = HeaderRegistry.get().findByName(name);\n if (key) {\n throw new Error(\n `RequestContext string accessors cannot touch '${name}' — it is a registered ` +\n `ContextKey (trust: '${key.trust}'). The raw string form hides whether the value is ` +\n `a proven fact or something a caller asserted, so it is a bypass of the trust ` +\n `system. Use ${useInstead} with the ContextKey itself.`,\n );\n }\n }\n\n /** The type-erased read. Every typed verb above funnels here; nothing else reads the store. */\n private readByName<T>(name: string): T | undefined {\n return this.storage.getStore()?.get(name);\n }\n\n /** The type-erased write. Every typed verb above funnels here; nothing else writes the store. */\n // webpieces-disable no-any-unknown -- context values are heterogeneous (strings, recorder, meta objects)\n private writeByName(name: 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(name, value);\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 * Snapshot this scope so the work you are about to hand off keeps its request id, log fields and\n * proven identity. The ONLY producer of a {@link CapturedContext} — which is what makes the\n * restore side unforgeable, since there is no other way to obtain the payload it accepts.\n *\n * Outside a `run(...)` block this returns an EMPTY snapshot rather than throwing: capturing \"no\n * context\" is a legitimate thing for a background caller to do, and restoring it simply installs\n * nothing.\n *\n * The snapshot is a defensive COPY — writes to this context after capturing do not reach it.\n */\n copyContext(): CapturedContext {\n const store = this.storage.getStore();\n return CapturedContext.capture(ContextCaptureAuthority.INTERNAL, store ?? new Map());\n }\n\n /**\n * Overwrite the ACTIVE scope with a snapshot. The in-place twin of {@link runWithContext}, and the\n * one you almost never want: prefer `runWithContext`, which gives the restored work its OWN scope\n * and cannot disturb the caller's. Reach for this only when something else owns the scope and it\n * must be re-pointed in place.\n *\n * OVERWRITE, not merge — `clear()` runs first, so every entry the active scope holds and the\n * snapshot does not is DROPPED. That includes the empty case: `restoreContext(copyContext())`\n * taken outside a scope wipes the request id and every proven identity from a live request, and\n * says nothing. That is faithful (a snapshot restores exactly what it captured) but it is the\n * sharp edge of this method and the reason `runWithContext` is the default.\n *\n * Takes only a {@link CapturedContext} for the reason spelled out there — the DELETED Map-taking\n * form let `new Map([['userId','victim']])` forge a proven identity in one line.\n *\n * @throws Error when no RequestContext is active.\n */\n restoreContext(captured: CapturedContext): void {\n const store = this.storage.getStore();\n if (!store) {\n throw new Error(\n 'No context available to restore into. Either open one with RequestContext.run(...) ' +\n 'first, or use RequestContext.runWithContext(captured, fn), which opens its own.',\n );\n }\n captured.restoreInto(ContextCaptureAuthority.INTERNAL, store);\n }\n\n /**\n * Check if a key exists in the context.\n */\n /**\n * Presence of a value under a RAW STRING key. Guarded like its three siblings: `has('userId')`\n * alongside `hasKey(WebpiecesCoreHeaders.USER_ID)` would be a second spelling of one question,\n * and the string form is the one that says nothing about whether the value can be believed.\n *\n * @throws Error when `key` is a registered ContextKey name.\n */\n has(key: string): boolean {\n this.rejectRegisteredName(key, 'hasKey(key)');\n return this.storage.getStore()?.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"]}
package/src/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { RequestContext } from './RequestContext';
2
+ export type { CapturedContext } from './CapturedContext';
2
3
  export { RequestContextApiCallContext } from './RequestContextApiCallContext';
3
4
  export { HttpRequest } from './HttpRequest';
4
5
  export { provideSingletonDefaultForApi } from './provide';
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,oGAAoG;AACpG,uGAAuG;AACvG,4FAA4F;AAC5F,+EAA8E;AAArE,4IAAA,4BAA4B,OAAA;AACrC,mGAAmG;AACnG,6CAA4C;AAAnC,0GAAA,WAAW,OAAA;AAEpB,mFAAmF;AACnF,qCAA0D;AAAjD,wHAAA,6BAA6B,OAAA;AACtC,2FAA2F;AAC3F,qCAAqC;AAA5B,mGAAA,QAAQ,OAAA;AACjB,sFAAsF;AACtF,wEAAwE;AACxE,uDAM4B;AALxB,6HAAA,yBAAyB,OAAA;AACzB,0IAAA,sCAAsC,OAAA;AACtC,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;AAC7B,uDAA2E;AAAlE,oHAAA,gBAAgB,OAAA;AAAE,uHAAA,mBAAmB,OAAA","sourcesContent":["// Context management with AsyncLocalStorage\nexport { RequestContext } from './RequestContext';\n// SERVER impl of the core-util ApiCallContext seam, bound to RequestContext. Importing it here runs\n// its install() side effect, so LogApiCall (core-util, browser-safe) stamps the real RequestContext on\n// a Node server without importing it. A browser never loads core-context → keeps the no-op.\nexport { RequestContextApiCallContext } from './RequestContextApiCallContext';\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 { provideSingletonDefaultForApi } 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 provideFrameworkSingletonDefaultForApi,\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';\nexport { PendingWireTrust, PendingTrustedValue } from './PendingWireTrust';\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;AAWvB,oGAAoG;AACpG,uGAAuG;AACvG,4FAA4F;AAC5F,+EAA8E;AAArE,4IAAA,4BAA4B,OAAA;AACrC,mGAAmG;AACnG,6CAA4C;AAAnC,0GAAA,WAAW,OAAA;AAEpB,mFAAmF;AACnF,qCAA0D;AAAjD,wHAAA,6BAA6B,OAAA;AACtC,2FAA2F;AAC3F,qCAAqC;AAA5B,mGAAA,QAAQ,OAAA;AACjB,sFAAsF;AACtF,wEAAwE;AACxE,uDAM4B;AALxB,6HAAA,yBAAyB,OAAA;AACzB,0IAAA,sCAAsC,OAAA;AACtC,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;AAC7B,uDAA2E;AAAlE,oHAAA,gBAAgB,OAAA;AAAE,uHAAA,mBAAmB,OAAA","sourcesContent":["// Context management with AsyncLocalStorage\nexport { RequestContext } from './RequestContext';\n// The OPAQUE snapshot type that copyContext() produces and restoreContext()/runWithContext() accept.\n//\n// `export type`, NOT `export` — deliberately. Consumers need to NAME it (a field, a queue entry, a\n// parameter) and nothing more. A VALUE export would hand them the class object, and with it the static\n// `capture(...)`, whose capability token a cast can supply even though this barrel never exports the\n// token's type: `CapturedContext.capture(null as never, new Map([['userId','victim']]))` would compile\n// and forge a proven identity — the exact hole this whole change closes. A type-only export removes the\n// class object from the package surface, so there is no factory to reach and copyContext() really is\n// the only producer. (ContextCaptureAuthority is not exported here in any form.)\nexport type { CapturedContext } from './CapturedContext';\n// SERVER impl of the core-util ApiCallContext seam, bound to RequestContext. Importing it here runs\n// its install() side effect, so LogApiCall (core-util, browser-safe) stamps the real RequestContext on\n// a Node server without importing it. A browser never loads core-context → keeps the no-op.\nexport { RequestContextApiCallContext } from './RequestContextApiCallContext';\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 { provideSingletonDefaultForApi } 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 provideFrameworkSingletonDefaultForApi,\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';\nexport { PendingWireTrust, PendingTrustedValue } from './PendingWireTrust';\n"]}