@optique/core 1.0.0-dev.1801 → 1.0.0-dev.1802
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/dist/annotations.cjs +1 -1
- package/dist/annotations.d.cts +1 -1
- package/dist/annotations.d.ts +1 -1
- package/dist/annotations.js +1 -1
- package/dist/context.cjs +0 -23
- package/dist/context.d.cts +33 -47
- package/dist/context.d.ts +33 -47
- package/dist/context.js +0 -22
- package/dist/facade.cjs +77 -47
- package/dist/facade.d.cts +21 -16
- package/dist/facade.d.ts +21 -16
- package/dist/facade.js +77 -47
- package/dist/parser.d.cts +1 -1
- package/dist/parser.d.ts +1 -1
- package/dist/valueparser.d.cts +1 -1
- package/dist/valueparser.d.ts +1 -1
- package/package.json +1 -1
package/dist/annotations.cjs
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
const annotationKey = Symbol.for("@optique/core/parser/annotation");
|
|
18
18
|
/**
|
|
19
19
|
* Internal marker attached during the first pass of `runWith()` so wrappers
|
|
20
|
-
* with side effects can defer work until
|
|
20
|
+
* with side effects can defer work until two-pass contexts have resolved.
|
|
21
21
|
*
|
|
22
22
|
* @internal
|
|
23
23
|
*/
|
package/dist/annotations.d.cts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
declare const annotationKey: unique symbol;
|
|
17
17
|
/**
|
|
18
18
|
* Internal marker attached during the first pass of `runWith()` so wrappers
|
|
19
|
-
* with side effects can defer work until
|
|
19
|
+
* with side effects can defer work until two-pass contexts have resolved.
|
|
20
20
|
*
|
|
21
21
|
* @internal
|
|
22
22
|
*/
|
package/dist/annotations.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
declare const annotationKey: unique symbol;
|
|
17
17
|
/**
|
|
18
18
|
* Internal marker attached during the first pass of `runWith()` so wrappers
|
|
19
|
-
* with side effects can defer work until
|
|
19
|
+
* with side effects can defer work until two-pass contexts have resolved.
|
|
20
20
|
*
|
|
21
21
|
* @internal
|
|
22
22
|
*/
|
package/dist/annotations.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
const annotationKey = Symbol.for("@optique/core/parser/annotation");
|
|
17
17
|
/**
|
|
18
18
|
* Internal marker attached during the first pass of `runWith()` so wrappers
|
|
19
|
-
* with side effects can defer work until
|
|
19
|
+
* with side effects can defer work until two-pass contexts have resolved.
|
|
20
20
|
*
|
|
21
21
|
* @internal
|
|
22
22
|
*/
|
package/dist/context.cjs
CHANGED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
//#region src/context.ts
|
|
3
|
-
/**
|
|
4
|
-
* Checks whether a context is static (returns annotations without needing
|
|
5
|
-
* parsed results).
|
|
6
|
-
*
|
|
7
|
-
* A context is considered static if it declares `mode: "static"` or if
|
|
8
|
-
* `getAnnotations()` called without arguments returns a non-empty
|
|
9
|
-
* annotations object synchronously.
|
|
10
|
-
*
|
|
11
|
-
* @param context The source context to check.
|
|
12
|
-
* @returns `true` if the context is static, `false` otherwise.
|
|
13
|
-
* @since 0.10.0
|
|
14
|
-
*/
|
|
15
|
-
function isStaticContext(context) {
|
|
16
|
-
if (context.mode !== void 0) return context.mode === "static";
|
|
17
|
-
const result = context.getAnnotations();
|
|
18
|
-
if (result instanceof Promise) return false;
|
|
19
|
-
return Object.getOwnPropertySymbols(result).length > 0;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
//#endregion
|
|
23
|
-
exports.isStaticContext = isStaticContext;
|
package/dist/context.d.cts
CHANGED
|
@@ -3,16 +3,15 @@ import { Annotations } from "./annotations.cjs";
|
|
|
3
3
|
//#region src/context.d.ts
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Declares whether a {@link SourceContext}
|
|
7
|
-
*
|
|
6
|
+
* Declares whether a {@link SourceContext} participates only in the initial
|
|
7
|
+
* annotation collection (`"single-pass"`) or is recollected after a usable
|
|
8
|
+
* first parse pass (`"two-pass"`).
|
|
8
9
|
*
|
|
9
|
-
* Used as the type of the
|
|
10
|
-
* When set, {@link isStaticContext} reads this value directly instead of
|
|
11
|
-
* calling `getAnnotations()`, preventing any side effects.
|
|
10
|
+
* Used as the type of the required `phase` field on {@link SourceContext}.
|
|
12
11
|
*
|
|
13
12
|
* @since 1.0.0
|
|
14
13
|
*/
|
|
15
|
-
type
|
|
14
|
+
type SourceContextPhase = "single-pass" | "two-pass";
|
|
16
15
|
/**
|
|
17
16
|
* Brand symbol for ParserValuePlaceholder type.
|
|
18
17
|
* @internal
|
|
@@ -50,12 +49,14 @@ type ParserValuePlaceholder = {
|
|
|
50
49
|
/**
|
|
51
50
|
* A source context that can provide data to parsers via annotations.
|
|
52
51
|
*
|
|
53
|
-
* Source contexts are used to inject external data (like environment
|
|
54
|
-
* or config files) into the parsing process. They can be either:
|
|
52
|
+
* Source contexts are used to inject external data (like environment
|
|
53
|
+
* variables or config files) into the parsing process. They can be either:
|
|
55
54
|
*
|
|
56
|
-
* - *
|
|
57
|
-
*
|
|
58
|
-
*
|
|
55
|
+
* - *Single-pass*: The runner collects annotations once before parsing
|
|
56
|
+
* (e.g., environment variables)
|
|
57
|
+
* - *Two-pass*: The runner collects annotations before parsing and then
|
|
58
|
+
* recollects them after a usable first parse pass (e.g., config files whose
|
|
59
|
+
* path is determined by a CLI option)
|
|
59
60
|
*
|
|
60
61
|
* Contexts may optionally implement `Disposable` or `AsyncDisposable` for
|
|
61
62
|
* cleanup. When present, `runWith()` and `runWithSync()` call the dispose
|
|
@@ -68,9 +69,10 @@ type ParserValuePlaceholder = {
|
|
|
68
69
|
*
|
|
69
70
|
* @example
|
|
70
71
|
* ```typescript
|
|
71
|
-
* //
|
|
72
|
+
* // Single-pass context example (environment variables)
|
|
72
73
|
* const envContext: SourceContext = {
|
|
73
74
|
* id: Symbol.for("@myapp/env"),
|
|
75
|
+
* phase: "single-pass",
|
|
74
76
|
* getAnnotations() {
|
|
75
77
|
* return {
|
|
76
78
|
* [Symbol.for("@myapp/env")]: {
|
|
@@ -81,7 +83,7 @@ type ParserValuePlaceholder = {
|
|
|
81
83
|
* }
|
|
82
84
|
* };
|
|
83
85
|
*
|
|
84
|
-
* //
|
|
86
|
+
* // Two-pass context that requires options from runWith()
|
|
85
87
|
* interface ConfigContext extends SourceContext<{
|
|
86
88
|
* getConfigPath: (parsed: ParserValuePlaceholder) => string | undefined;
|
|
87
89
|
* }> {
|
|
@@ -107,40 +109,37 @@ interface SourceContext<TRequiredOptions = void> {
|
|
|
107
109
|
*/
|
|
108
110
|
readonly $requiredOptions?: TRequiredOptions;
|
|
109
111
|
/**
|
|
110
|
-
*
|
|
112
|
+
* Declares whether this context is collected once or recollected after a
|
|
113
|
+
* usable first parse pass.
|
|
111
114
|
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* If omitted, {@link isStaticContext} falls back to calling
|
|
117
|
-
* `getAnnotations()` with no arguments to determine static-ness.
|
|
115
|
+
* `single-pass` contexts contribute only their phase-1 annotations to the
|
|
116
|
+
* final parse. `two-pass` contexts are called again with the first-pass
|
|
117
|
+
* parsed value (or a best-effort seed extracted from parser state) and that
|
|
118
|
+
* second return value becomes the context's final annotation snapshot.
|
|
118
119
|
*
|
|
119
120
|
* @since 1.0.0
|
|
120
121
|
*/
|
|
121
|
-
readonly
|
|
122
|
+
readonly phase: SourceContextPhase;
|
|
122
123
|
/**
|
|
123
124
|
* Get annotations to inject into parsing.
|
|
124
125
|
*
|
|
125
|
-
* This method is called
|
|
126
|
+
* This method is called during phase 1 for every context and during phase 2
|
|
127
|
+
* only for `two-pass` contexts:
|
|
126
128
|
*
|
|
127
|
-
* 1. *
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* (e.g., reading a config file whose path was determined in the first
|
|
134
|
-
* pass). Deferred or otherwise unresolved fields may be `undefined`.
|
|
135
|
-
* This second return value is treated as the context's final annotation
|
|
129
|
+
* 1. *Phase 1*: `parsed` is `undefined`.
|
|
130
|
+
* 2. *Phase 2*: `parsed` contains the first pass result, or a best-effort
|
|
131
|
+
* partial value extracted from parser state when the first pass reached a
|
|
132
|
+
* usable intermediate state but still did not complete successfully.
|
|
133
|
+
* Deferred or otherwise unresolved fields may be `undefined`. This
|
|
134
|
+
* second return value is treated as the context's final annotation
|
|
136
135
|
* snapshot for the second parse pass, replacing that context's phase-one
|
|
137
136
|
* contribution. If the runner cannot extract a usable value at all, this
|
|
138
137
|
* second call is skipped and the original parse failure is reported
|
|
139
138
|
* instead.
|
|
140
139
|
*
|
|
141
140
|
* @param parsed Optional parsed result from a previous parse pass.
|
|
142
|
-
*
|
|
143
|
-
*
|
|
141
|
+
* `single-pass` contexts can ignore this parameter.
|
|
142
|
+
* `two-pass` contexts use this to extract or refine data.
|
|
144
143
|
* @param options Optional context-required options provided by the caller
|
|
145
144
|
* of `runWith()`. These are the options declared via the
|
|
146
145
|
* `TRequiredOptions` type parameter.
|
|
@@ -193,18 +192,5 @@ interface SourceContext<TRequiredOptions = void> {
|
|
|
193
192
|
*/
|
|
194
193
|
[Symbol.asyncDispose]?(): void | PromiseLike<void>;
|
|
195
194
|
}
|
|
196
|
-
/**
|
|
197
|
-
* Checks whether a context is static (returns annotations without needing
|
|
198
|
-
* parsed results).
|
|
199
|
-
*
|
|
200
|
-
* A context is considered static if it declares `mode: "static"` or if
|
|
201
|
-
* `getAnnotations()` called without arguments returns a non-empty
|
|
202
|
-
* annotations object synchronously.
|
|
203
|
-
*
|
|
204
|
-
* @param context The source context to check.
|
|
205
|
-
* @returns `true` if the context is static, `false` otherwise.
|
|
206
|
-
* @since 0.10.0
|
|
207
|
-
*/
|
|
208
|
-
declare function isStaticContext(context: SourceContext<unknown>): boolean;
|
|
209
195
|
//#endregion
|
|
210
|
-
export { type Annotations, ParserValuePlaceholder, SourceContext,
|
|
196
|
+
export { type Annotations, ParserValuePlaceholder, SourceContext, SourceContextPhase };
|
package/dist/context.d.ts
CHANGED
|
@@ -3,16 +3,15 @@ import { Annotations } from "./annotations.js";
|
|
|
3
3
|
//#region src/context.d.ts
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Declares whether a {@link SourceContext}
|
|
7
|
-
*
|
|
6
|
+
* Declares whether a {@link SourceContext} participates only in the initial
|
|
7
|
+
* annotation collection (`"single-pass"`) or is recollected after a usable
|
|
8
|
+
* first parse pass (`"two-pass"`).
|
|
8
9
|
*
|
|
9
|
-
* Used as the type of the
|
|
10
|
-
* When set, {@link isStaticContext} reads this value directly instead of
|
|
11
|
-
* calling `getAnnotations()`, preventing any side effects.
|
|
10
|
+
* Used as the type of the required `phase` field on {@link SourceContext}.
|
|
12
11
|
*
|
|
13
12
|
* @since 1.0.0
|
|
14
13
|
*/
|
|
15
|
-
type
|
|
14
|
+
type SourceContextPhase = "single-pass" | "two-pass";
|
|
16
15
|
/**
|
|
17
16
|
* Brand symbol for ParserValuePlaceholder type.
|
|
18
17
|
* @internal
|
|
@@ -50,12 +49,14 @@ type ParserValuePlaceholder = {
|
|
|
50
49
|
/**
|
|
51
50
|
* A source context that can provide data to parsers via annotations.
|
|
52
51
|
*
|
|
53
|
-
* Source contexts are used to inject external data (like environment
|
|
54
|
-
* or config files) into the parsing process. They can be either:
|
|
52
|
+
* Source contexts are used to inject external data (like environment
|
|
53
|
+
* variables or config files) into the parsing process. They can be either:
|
|
55
54
|
*
|
|
56
|
-
* - *
|
|
57
|
-
*
|
|
58
|
-
*
|
|
55
|
+
* - *Single-pass*: The runner collects annotations once before parsing
|
|
56
|
+
* (e.g., environment variables)
|
|
57
|
+
* - *Two-pass*: The runner collects annotations before parsing and then
|
|
58
|
+
* recollects them after a usable first parse pass (e.g., config files whose
|
|
59
|
+
* path is determined by a CLI option)
|
|
59
60
|
*
|
|
60
61
|
* Contexts may optionally implement `Disposable` or `AsyncDisposable` for
|
|
61
62
|
* cleanup. When present, `runWith()` and `runWithSync()` call the dispose
|
|
@@ -68,9 +69,10 @@ type ParserValuePlaceholder = {
|
|
|
68
69
|
*
|
|
69
70
|
* @example
|
|
70
71
|
* ```typescript
|
|
71
|
-
* //
|
|
72
|
+
* // Single-pass context example (environment variables)
|
|
72
73
|
* const envContext: SourceContext = {
|
|
73
74
|
* id: Symbol.for("@myapp/env"),
|
|
75
|
+
* phase: "single-pass",
|
|
74
76
|
* getAnnotations() {
|
|
75
77
|
* return {
|
|
76
78
|
* [Symbol.for("@myapp/env")]: {
|
|
@@ -81,7 +83,7 @@ type ParserValuePlaceholder = {
|
|
|
81
83
|
* }
|
|
82
84
|
* };
|
|
83
85
|
*
|
|
84
|
-
* //
|
|
86
|
+
* // Two-pass context that requires options from runWith()
|
|
85
87
|
* interface ConfigContext extends SourceContext<{
|
|
86
88
|
* getConfigPath: (parsed: ParserValuePlaceholder) => string | undefined;
|
|
87
89
|
* }> {
|
|
@@ -107,40 +109,37 @@ interface SourceContext<TRequiredOptions = void> {
|
|
|
107
109
|
*/
|
|
108
110
|
readonly $requiredOptions?: TRequiredOptions;
|
|
109
111
|
/**
|
|
110
|
-
*
|
|
112
|
+
* Declares whether this context is collected once or recollected after a
|
|
113
|
+
* usable first parse pass.
|
|
111
114
|
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* If omitted, {@link isStaticContext} falls back to calling
|
|
117
|
-
* `getAnnotations()` with no arguments to determine static-ness.
|
|
115
|
+
* `single-pass` contexts contribute only their phase-1 annotations to the
|
|
116
|
+
* final parse. `two-pass` contexts are called again with the first-pass
|
|
117
|
+
* parsed value (or a best-effort seed extracted from parser state) and that
|
|
118
|
+
* second return value becomes the context's final annotation snapshot.
|
|
118
119
|
*
|
|
119
120
|
* @since 1.0.0
|
|
120
121
|
*/
|
|
121
|
-
readonly
|
|
122
|
+
readonly phase: SourceContextPhase;
|
|
122
123
|
/**
|
|
123
124
|
* Get annotations to inject into parsing.
|
|
124
125
|
*
|
|
125
|
-
* This method is called
|
|
126
|
+
* This method is called during phase 1 for every context and during phase 2
|
|
127
|
+
* only for `two-pass` contexts:
|
|
126
128
|
*
|
|
127
|
-
* 1. *
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* (e.g., reading a config file whose path was determined in the first
|
|
134
|
-
* pass). Deferred or otherwise unresolved fields may be `undefined`.
|
|
135
|
-
* This second return value is treated as the context's final annotation
|
|
129
|
+
* 1. *Phase 1*: `parsed` is `undefined`.
|
|
130
|
+
* 2. *Phase 2*: `parsed` contains the first pass result, or a best-effort
|
|
131
|
+
* partial value extracted from parser state when the first pass reached a
|
|
132
|
+
* usable intermediate state but still did not complete successfully.
|
|
133
|
+
* Deferred or otherwise unresolved fields may be `undefined`. This
|
|
134
|
+
* second return value is treated as the context's final annotation
|
|
136
135
|
* snapshot for the second parse pass, replacing that context's phase-one
|
|
137
136
|
* contribution. If the runner cannot extract a usable value at all, this
|
|
138
137
|
* second call is skipped and the original parse failure is reported
|
|
139
138
|
* instead.
|
|
140
139
|
*
|
|
141
140
|
* @param parsed Optional parsed result from a previous parse pass.
|
|
142
|
-
*
|
|
143
|
-
*
|
|
141
|
+
* `single-pass` contexts can ignore this parameter.
|
|
142
|
+
* `two-pass` contexts use this to extract or refine data.
|
|
144
143
|
* @param options Optional context-required options provided by the caller
|
|
145
144
|
* of `runWith()`. These are the options declared via the
|
|
146
145
|
* `TRequiredOptions` type parameter.
|
|
@@ -193,18 +192,5 @@ interface SourceContext<TRequiredOptions = void> {
|
|
|
193
192
|
*/
|
|
194
193
|
[Symbol.asyncDispose]?(): void | PromiseLike<void>;
|
|
195
194
|
}
|
|
196
|
-
/**
|
|
197
|
-
* Checks whether a context is static (returns annotations without needing
|
|
198
|
-
* parsed results).
|
|
199
|
-
*
|
|
200
|
-
* A context is considered static if it declares `mode: "static"` or if
|
|
201
|
-
* `getAnnotations()` called without arguments returns a non-empty
|
|
202
|
-
* annotations object synchronously.
|
|
203
|
-
*
|
|
204
|
-
* @param context The source context to check.
|
|
205
|
-
* @returns `true` if the context is static, `false` otherwise.
|
|
206
|
-
* @since 0.10.0
|
|
207
|
-
*/
|
|
208
|
-
declare function isStaticContext(context: SourceContext<unknown>): boolean;
|
|
209
195
|
//#endregion
|
|
210
|
-
export { type Annotations, ParserValuePlaceholder, SourceContext,
|
|
196
|
+
export { type Annotations, ParserValuePlaceholder, SourceContext, SourceContextPhase };
|
package/dist/context.js
CHANGED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
//#region src/context.ts
|
|
2
|
-
/**
|
|
3
|
-
* Checks whether a context is static (returns annotations without needing
|
|
4
|
-
* parsed results).
|
|
5
|
-
*
|
|
6
|
-
* A context is considered static if it declares `mode: "static"` or if
|
|
7
|
-
* `getAnnotations()` called without arguments returns a non-empty
|
|
8
|
-
* annotations object synchronously.
|
|
9
|
-
*
|
|
10
|
-
* @param context The source context to check.
|
|
11
|
-
* @returns `true` if the context is static, `false` otherwise.
|
|
12
|
-
* @since 0.10.0
|
|
13
|
-
*/
|
|
14
|
-
function isStaticContext(context) {
|
|
15
|
-
if (context.mode !== void 0) return context.mode === "static";
|
|
16
|
-
const result = context.getAnnotations();
|
|
17
|
-
if (result instanceof Promise) return false;
|
|
18
|
-
return Object.getOwnPropertySymbols(result).length > 0;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
//#endregion
|
|
22
|
-
export { isStaticContext };
|
package/dist/facade.cjs
CHANGED
|
@@ -1081,41 +1081,62 @@ function mergeAnnotations(annotationsList) {
|
|
|
1081
1081
|
}
|
|
1082
1082
|
return result;
|
|
1083
1083
|
}
|
|
1084
|
+
function validateContextPhases(contexts) {
|
|
1085
|
+
for (const context of contexts) {
|
|
1086
|
+
const phase = context.phase;
|
|
1087
|
+
if (phase !== "single-pass" && phase !== "two-pass") throw new TypeError(`Context ${String(context.id)} must declare phase as "single-pass" or "two-pass".`);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1084
1090
|
/**
|
|
1085
1091
|
* Collects phase 1 annotations from all contexts and determines whether
|
|
1086
1092
|
* two-phase parsing is needed.
|
|
1087
1093
|
*
|
|
1088
1094
|
* @param contexts Source contexts to collect annotations from.
|
|
1089
1095
|
* @param options Optional context-required options to pass to each context.
|
|
1090
|
-
* @returns Promise with merged annotations
|
|
1096
|
+
* @returns Promise with merged annotations, per-context snapshots, and a
|
|
1097
|
+
* two-phase hint.
|
|
1091
1098
|
*/
|
|
1092
1099
|
async function collectPhase1Annotations(contexts, options) {
|
|
1093
1100
|
const annotationsList = [];
|
|
1094
|
-
|
|
1101
|
+
const snapshots = [];
|
|
1102
|
+
let needsTwoPhase = false;
|
|
1095
1103
|
for (const context of contexts) {
|
|
1096
1104
|
const result = context.getAnnotations(void 0, options);
|
|
1097
|
-
hasDynamic ||= needsTwoPhaseContext(context, result);
|
|
1098
1105
|
const annotations = result instanceof Promise ? await result : result;
|
|
1099
1106
|
const internalAnnotations = context.getInternalAnnotations?.(void 0, annotations);
|
|
1100
|
-
|
|
1107
|
+
const snapshot = internalAnnotations == null ? annotations : mergeAnnotations([annotations, internalAnnotations]);
|
|
1108
|
+
annotationsList.push(snapshot);
|
|
1109
|
+
snapshots.push(snapshot);
|
|
1110
|
+
needsTwoPhase ||= context.phase === "two-pass";
|
|
1101
1111
|
}
|
|
1102
1112
|
return {
|
|
1103
1113
|
annotations: mergeAnnotations(annotationsList),
|
|
1104
|
-
|
|
1114
|
+
needsTwoPhase,
|
|
1115
|
+
snapshots
|
|
1105
1116
|
};
|
|
1106
1117
|
}
|
|
1107
1118
|
/**
|
|
1108
|
-
* Collects annotations from all contexts.
|
|
1119
|
+
* Collects final annotations from all contexts.
|
|
1120
|
+
*
|
|
1121
|
+
* `single-pass` contexts reuse their phase-1 snapshot. `two-pass` contexts
|
|
1122
|
+
* are recollected with the parsed value and replace their own phase-1
|
|
1123
|
+
* snapshot in the final merge.
|
|
1109
1124
|
*
|
|
1110
1125
|
* @param contexts Source contexts to collect annotations from.
|
|
1126
|
+
* @param phase1Snapshots Per-context snapshots collected during phase 1.
|
|
1111
1127
|
* @param parsed Optional parsed result from a previous parse pass.
|
|
1112
1128
|
* @param options Optional context-required options to pass to each context.
|
|
1113
1129
|
* @returns Promise that resolves to merged annotations.
|
|
1114
1130
|
*/
|
|
1115
|
-
async function
|
|
1131
|
+
async function collectFinalAnnotations(contexts, phase1Snapshots, parsed, options, deferred, deferredKeys) {
|
|
1116
1132
|
const annotationsList = [];
|
|
1117
1133
|
const preparedParsed = prepareParsedForContexts(parsed, deferred, deferredKeys);
|
|
1118
|
-
for (
|
|
1134
|
+
for (let index = 0; index < contexts.length; index++) {
|
|
1135
|
+
const context = contexts[index];
|
|
1136
|
+
if (context.phase === "single-pass") {
|
|
1137
|
+
annotationsList.push(phase1Snapshots[index]);
|
|
1138
|
+
continue;
|
|
1139
|
+
}
|
|
1119
1140
|
const mergedAnnotations = await withPreparedParsedForContext(context, preparedParsed, async (contextParsed) => {
|
|
1120
1141
|
const result = context.getAnnotations(contextParsed, options);
|
|
1121
1142
|
const annotations = result instanceof Promise ? await result : result;
|
|
@@ -1132,49 +1153,51 @@ async function collectAnnotations(contexts, parsed, options, deferred, deferredK
|
|
|
1132
1153
|
*
|
|
1133
1154
|
* @param contexts Source contexts to collect annotations from.
|
|
1134
1155
|
* @param options Optional context-required options to pass to each context.
|
|
1135
|
-
* @returns Merged annotations
|
|
1156
|
+
* @returns Merged annotations, per-context snapshots, and a two-phase hint.
|
|
1136
1157
|
* @throws Error if any context returns a Promise.
|
|
1137
1158
|
*/
|
|
1138
1159
|
function collectPhase1AnnotationsSync(contexts, options) {
|
|
1139
1160
|
const annotationsList = [];
|
|
1140
|
-
|
|
1161
|
+
const snapshots = [];
|
|
1162
|
+
let needsTwoPhase = false;
|
|
1141
1163
|
for (const context of contexts) {
|
|
1142
1164
|
const result = context.getAnnotations(void 0, options);
|
|
1143
1165
|
if (result instanceof Promise) throw new Error(`Context ${String(context.id)} returned a Promise in sync mode. Use runWith() or runWithAsync() for async contexts.`);
|
|
1144
|
-
hasDynamic ||= needsTwoPhaseContext(context, result);
|
|
1145
1166
|
const internalAnnotations = context.getInternalAnnotations?.(void 0, result);
|
|
1146
|
-
|
|
1167
|
+
const snapshot = internalAnnotations == null ? result : mergeAnnotations([result, internalAnnotations]);
|
|
1168
|
+
annotationsList.push(snapshot);
|
|
1169
|
+
snapshots.push(snapshot);
|
|
1170
|
+
needsTwoPhase ||= context.phase === "two-pass";
|
|
1147
1171
|
}
|
|
1148
1172
|
return {
|
|
1149
1173
|
annotations: mergeAnnotations(annotationsList),
|
|
1150
|
-
|
|
1174
|
+
needsTwoPhase,
|
|
1175
|
+
snapshots
|
|
1151
1176
|
};
|
|
1152
1177
|
}
|
|
1153
1178
|
/**
|
|
1154
|
-
*
|
|
1179
|
+
* Collects final annotations from all contexts synchronously.
|
|
1155
1180
|
*
|
|
1156
|
-
*
|
|
1157
|
-
*
|
|
1158
|
-
*
|
|
1159
|
-
*/
|
|
1160
|
-
function needsTwoPhaseContext(context, result) {
|
|
1161
|
-
if (context.mode !== void 0) return context.mode === "dynamic";
|
|
1162
|
-
if (result instanceof Promise) return true;
|
|
1163
|
-
return Object.getOwnPropertySymbols(result).length === 0;
|
|
1164
|
-
}
|
|
1165
|
-
/**
|
|
1166
|
-
* Collects annotations from all contexts synchronously.
|
|
1181
|
+
* `single-pass` contexts reuse their phase-1 snapshot. `two-pass` contexts
|
|
1182
|
+
* are recollected with the parsed value and replace their own phase-1
|
|
1183
|
+
* snapshot in the final merge.
|
|
1167
1184
|
*
|
|
1168
1185
|
* @param contexts Source contexts to collect annotations from.
|
|
1186
|
+
* @param phase1Snapshots Per-context snapshots collected during phase 1.
|
|
1169
1187
|
* @param parsed Optional parsed result from a previous parse pass.
|
|
1170
1188
|
* @param options Optional context-required options to pass to each context.
|
|
1171
1189
|
* @returns Merged annotations.
|
|
1172
1190
|
* @throws Error if any context returns a Promise.
|
|
1173
1191
|
*/
|
|
1174
|
-
function
|
|
1192
|
+
function collectFinalAnnotationsSync(contexts, phase1Snapshots, parsed, options, deferred, deferredKeys) {
|
|
1175
1193
|
const annotationsList = [];
|
|
1176
1194
|
const preparedParsed = prepareParsedForContexts(parsed, deferred, deferredKeys);
|
|
1177
|
-
for (
|
|
1195
|
+
for (let index = 0; index < contexts.length; index++) {
|
|
1196
|
+
const context = contexts[index];
|
|
1197
|
+
if (context.phase === "single-pass") {
|
|
1198
|
+
annotationsList.push(phase1Snapshots[index]);
|
|
1199
|
+
continue;
|
|
1200
|
+
}
|
|
1178
1201
|
const mergedAnnotations = withPreparedParsedForContext(context, preparedParsed, (contextParsed) => {
|
|
1179
1202
|
const result = context.getAnnotations(contextParsed, options);
|
|
1180
1203
|
if (result instanceof Promise) throw new Error(`Context ${String(context.id)} returned a Promise in sync mode. Use runWith() or runWithAsync() for async contexts.`);
|
|
@@ -1228,12 +1251,13 @@ function disposeContextsSync(contexts) {
|
|
|
1228
1251
|
*/
|
|
1229
1252
|
async function runWithBody(parser, programName, contexts, args, options) {
|
|
1230
1253
|
require_validate.validateContextIds(contexts);
|
|
1254
|
+
validateContextPhases(contexts);
|
|
1231
1255
|
if (needsEarlyExit(args, options)) {
|
|
1232
1256
|
if (parser.$mode === "async") return runParser(parser, programName, args, options);
|
|
1233
1257
|
return Promise.resolve(runParser(parser, programName, args, options));
|
|
1234
1258
|
}
|
|
1235
1259
|
const ctxOptions = options.contextOptions;
|
|
1236
|
-
const { annotations: phase1Annotations,
|
|
1260
|
+
const { annotations: phase1Annotations, needsTwoPhase, snapshots: phase1Snapshots } = await collectPhase1Annotations(contexts, ctxOptions);
|
|
1237
1261
|
if (!needsTwoPhase) {
|
|
1238
1262
|
const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
|
|
1239
1263
|
if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
|
|
@@ -1246,7 +1270,7 @@ async function runWithBody(parser, programName, contexts, args, options) {
|
|
|
1246
1270
|
if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
|
|
1247
1271
|
return Promise.resolve(runParser(augmentedParser, programName, args, options));
|
|
1248
1272
|
}
|
|
1249
|
-
const { annotations: finalAnnotations } = await
|
|
1273
|
+
const { annotations: finalAnnotations } = await collectFinalAnnotations(contexts, phase1Snapshots, firstPassSeed.value, ctxOptions, firstPassSeed.deferred, firstPassSeed.deferredKeys);
|
|
1250
1274
|
const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
|
|
1251
1275
|
if (parser.$mode === "async") return runParser(augmentedParser2, programName, args, options);
|
|
1252
1276
|
return Promise.resolve(runParser(augmentedParser2, programName, args, options));
|
|
@@ -1254,28 +1278,28 @@ async function runWithBody(parser, programName, contexts, args, options) {
|
|
|
1254
1278
|
/**
|
|
1255
1279
|
* Runs a parser with multiple source contexts.
|
|
1256
1280
|
*
|
|
1257
|
-
* This function automatically handles
|
|
1258
|
-
* priority. Earlier contexts in the array override later ones.
|
|
1281
|
+
* This function automatically handles single-pass and two-pass contexts with
|
|
1282
|
+
* proper priority. Earlier contexts in the array override later ones.
|
|
1259
1283
|
*
|
|
1260
1284
|
* The function uses a smart two-phase approach:
|
|
1261
1285
|
*
|
|
1262
|
-
* 1. *Phase 1*: Collect annotations from all contexts
|
|
1263
|
-
* their data, dynamic contexts may return empty).
|
|
1286
|
+
* 1. *Phase 1*: Collect annotations from all contexts.
|
|
1264
1287
|
* 2. *First parse*: Parse with Phase 1 annotations. If that pass finishes
|
|
1265
1288
|
* successfully, its value becomes the phase-two input. If the parser
|
|
1266
1289
|
* reaches a usable intermediate state but still does not complete
|
|
1267
1290
|
* successfully, the runner extracts a best-effort seed from that state
|
|
1268
1291
|
* instead.
|
|
1269
|
-
* 3. *Phase 2*: Call `getAnnotations(parsed)` on all contexts with
|
|
1270
|
-
* pass value. Deferred or otherwise unresolved fields in
|
|
1271
|
-
* `undefined`. Each context's phase-two return
|
|
1272
|
-
* phase-one contribution for the final parse, so
|
|
1273
|
-
* annotations that context provided during
|
|
1292
|
+
* 3. *Phase 2*: Call `getAnnotations(parsed)` on all two-pass contexts with
|
|
1293
|
+
* the first pass value. Deferred or otherwise unresolved fields in
|
|
1294
|
+
* `parsed` may be `undefined`. Each two-pass context's phase-two return
|
|
1295
|
+
* value replaces its own phase-one contribution for the final parse, so
|
|
1296
|
+
* returning `{}` clears any annotations that context provided during
|
|
1297
|
+
* phase 1. Single-pass contexts reuse their phase-one snapshot.
|
|
1274
1298
|
* 4. *Second parse*: Parse again with the merged phase-two annotations.
|
|
1275
1299
|
*
|
|
1276
|
-
* If all contexts are
|
|
1277
|
-
*
|
|
1278
|
-
*
|
|
1300
|
+
* If all contexts are single-pass, the second parse is skipped for
|
|
1301
|
+
* optimization. Phase 2 is also skipped when the first pass does not yield
|
|
1302
|
+
* any usable seed at all.
|
|
1279
1303
|
*
|
|
1280
1304
|
* @template TParser The parser type.
|
|
1281
1305
|
* @template THelp Return type when help is shown.
|
|
@@ -1287,6 +1311,8 @@ async function runWithBody(parser, programName, contexts, args, options) {
|
|
|
1287
1311
|
* @returns Promise that resolves to the parsed result.
|
|
1288
1312
|
* @throws {TypeError} If two or more contexts share the same
|
|
1289
1313
|
* {@link SourceContext.id}.
|
|
1314
|
+
* @throws {TypeError} If any context omits `phase` or declares an invalid
|
|
1315
|
+
* phase value.
|
|
1290
1316
|
* @throws {SuppressedError} If the runner throws and a context's disposal
|
|
1291
1317
|
* also throws. The original error is available via `.suppressed` and the
|
|
1292
1318
|
* disposal error via `.error`.
|
|
@@ -1299,6 +1325,7 @@ async function runWithBody(parser, programName, contexts, args, options) {
|
|
|
1299
1325
|
*
|
|
1300
1326
|
* const envContext: SourceContext = {
|
|
1301
1327
|
* id: Symbol.for("@myapp/env"),
|
|
1328
|
+
* phase: "single-pass",
|
|
1302
1329
|
* getAnnotations() {
|
|
1303
1330
|
* return { [Symbol.for("@myapp/env")]: process.env };
|
|
1304
1331
|
* }
|
|
@@ -1342,9 +1369,10 @@ async function runWith(parser, programName, contexts, options) {
|
|
|
1342
1369
|
*/
|
|
1343
1370
|
function runWithSyncBody(parser, programName, contexts, args, options) {
|
|
1344
1371
|
require_validate.validateContextIds(contexts);
|
|
1372
|
+
validateContextPhases(contexts);
|
|
1345
1373
|
if (needsEarlyExit(args, options)) return runParser(parser, programName, args, options);
|
|
1346
1374
|
const ctxOptions = options.contextOptions;
|
|
1347
|
-
const { annotations: phase1Annotations,
|
|
1375
|
+
const { annotations: phase1Annotations, needsTwoPhase, snapshots: phase1Snapshots } = collectPhase1AnnotationsSync(contexts, ctxOptions);
|
|
1348
1376
|
if (!needsTwoPhase) {
|
|
1349
1377
|
const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
|
|
1350
1378
|
return runParser(augmentedParser, programName, args, options);
|
|
@@ -1355,7 +1383,7 @@ function runWithSyncBody(parser, programName, contexts, args, options) {
|
|
|
1355
1383
|
const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
|
|
1356
1384
|
return runParser(augmentedParser, programName, args, options);
|
|
1357
1385
|
}
|
|
1358
|
-
const { annotations: finalAnnotations } =
|
|
1386
|
+
const { annotations: finalAnnotations } = collectFinalAnnotationsSync(contexts, phase1Snapshots, firstPassSeed.value, ctxOptions, firstPassSeed.deferred, firstPassSeed.deferredKeys);
|
|
1359
1387
|
const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
|
|
1360
1388
|
return runParser(augmentedParser2, programName, args, options);
|
|
1361
1389
|
}
|
|
@@ -1364,10 +1392,10 @@ function runWithSyncBody(parser, programName, contexts, args, options) {
|
|
|
1364
1392
|
*
|
|
1365
1393
|
* This is the sync-only variant of {@link runWith}. All contexts must return
|
|
1366
1394
|
* annotations synchronously (not Promises). It uses the same two-phase
|
|
1367
|
-
* best-effort seed extraction as {@link runWith} when
|
|
1368
|
-
* present. In two-phase runs, each context's phase-two return value
|
|
1369
|
-
* that context's phase-one contribution for the final parse, so
|
|
1370
|
-
* clears any annotations that context provided during phase 1.
|
|
1395
|
+
* best-effort seed extraction as {@link runWith} when two-pass contexts are
|
|
1396
|
+
* present. In two-phase runs, each two-pass context's phase-two return value
|
|
1397
|
+
* replaces that context's phase-one contribution for the final parse, so
|
|
1398
|
+
* returning `{}` clears any annotations that context provided during phase 1.
|
|
1371
1399
|
*
|
|
1372
1400
|
* @template TParser The sync parser type.
|
|
1373
1401
|
* @template THelp Return type when help is shown.
|
|
@@ -1381,6 +1409,8 @@ function runWithSyncBody(parser, programName, contexts, args, options) {
|
|
|
1381
1409
|
* {@link runWith} or {@link runWithAsync} for async parsers.
|
|
1382
1410
|
* @throws {TypeError} If two or more contexts share the same
|
|
1383
1411
|
* {@link SourceContext.id}.
|
|
1412
|
+
* @throws {TypeError} If any context omits `phase` or declares an invalid
|
|
1413
|
+
* phase value.
|
|
1384
1414
|
* @throws {Error} If any context returns a Promise or if a context's
|
|
1385
1415
|
* `[Symbol.asyncDispose]` returns a Promise.
|
|
1386
1416
|
* @throws {SuppressedError} If the runner throws and a context's disposal
|
package/dist/facade.d.cts
CHANGED
|
@@ -405,28 +405,28 @@ type ContextOptionsParam<TContexts extends readonly SourceContext<unknown>[], TV
|
|
|
405
405
|
/**
|
|
406
406
|
* Runs a parser with multiple source contexts.
|
|
407
407
|
*
|
|
408
|
-
* This function automatically handles
|
|
409
|
-
* priority. Earlier contexts in the array override later ones.
|
|
408
|
+
* This function automatically handles single-pass and two-pass contexts with
|
|
409
|
+
* proper priority. Earlier contexts in the array override later ones.
|
|
410
410
|
*
|
|
411
411
|
* The function uses a smart two-phase approach:
|
|
412
412
|
*
|
|
413
|
-
* 1. *Phase 1*: Collect annotations from all contexts
|
|
414
|
-
* their data, dynamic contexts may return empty).
|
|
413
|
+
* 1. *Phase 1*: Collect annotations from all contexts.
|
|
415
414
|
* 2. *First parse*: Parse with Phase 1 annotations. If that pass finishes
|
|
416
415
|
* successfully, its value becomes the phase-two input. If the parser
|
|
417
416
|
* reaches a usable intermediate state but still does not complete
|
|
418
417
|
* successfully, the runner extracts a best-effort seed from that state
|
|
419
418
|
* instead.
|
|
420
|
-
* 3. *Phase 2*: Call `getAnnotations(parsed)` on all contexts with
|
|
421
|
-
* pass value. Deferred or otherwise unresolved fields in
|
|
422
|
-
* `undefined`. Each context's phase-two return
|
|
423
|
-
* phase-one contribution for the final parse, so
|
|
424
|
-
* annotations that context provided during
|
|
419
|
+
* 3. *Phase 2*: Call `getAnnotations(parsed)` on all two-pass contexts with
|
|
420
|
+
* the first pass value. Deferred or otherwise unresolved fields in
|
|
421
|
+
* `parsed` may be `undefined`. Each two-pass context's phase-two return
|
|
422
|
+
* value replaces its own phase-one contribution for the final parse, so
|
|
423
|
+
* returning `{}` clears any annotations that context provided during
|
|
424
|
+
* phase 1. Single-pass contexts reuse their phase-one snapshot.
|
|
425
425
|
* 4. *Second parse*: Parse again with the merged phase-two annotations.
|
|
426
426
|
*
|
|
427
|
-
* If all contexts are
|
|
428
|
-
*
|
|
429
|
-
*
|
|
427
|
+
* If all contexts are single-pass, the second parse is skipped for
|
|
428
|
+
* optimization. Phase 2 is also skipped when the first pass does not yield
|
|
429
|
+
* any usable seed at all.
|
|
430
430
|
*
|
|
431
431
|
* @template TParser The parser type.
|
|
432
432
|
* @template THelp Return type when help is shown.
|
|
@@ -438,6 +438,8 @@ type ContextOptionsParam<TContexts extends readonly SourceContext<unknown>[], TV
|
|
|
438
438
|
* @returns Promise that resolves to the parsed result.
|
|
439
439
|
* @throws {TypeError} If two or more contexts share the same
|
|
440
440
|
* {@link SourceContext.id}.
|
|
441
|
+
* @throws {TypeError} If any context omits `phase` or declares an invalid
|
|
442
|
+
* phase value.
|
|
441
443
|
* @throws {SuppressedError} If the runner throws and a context's disposal
|
|
442
444
|
* also throws. The original error is available via `.suppressed` and the
|
|
443
445
|
* disposal error via `.error`.
|
|
@@ -450,6 +452,7 @@ type ContextOptionsParam<TContexts extends readonly SourceContext<unknown>[], TV
|
|
|
450
452
|
*
|
|
451
453
|
* const envContext: SourceContext = {
|
|
452
454
|
* id: Symbol.for("@myapp/env"),
|
|
455
|
+
* phase: "single-pass",
|
|
453
456
|
* getAnnotations() {
|
|
454
457
|
* return { [Symbol.for("@myapp/env")]: process.env };
|
|
455
458
|
* }
|
|
@@ -469,10 +472,10 @@ declare function runWith<TParser extends Parser<Mode, unknown, unknown>, TContex
|
|
|
469
472
|
*
|
|
470
473
|
* This is the sync-only variant of {@link runWith}. All contexts must return
|
|
471
474
|
* annotations synchronously (not Promises). It uses the same two-phase
|
|
472
|
-
* best-effort seed extraction as {@link runWith} when
|
|
473
|
-
* present. In two-phase runs, each context's phase-two return value
|
|
474
|
-
* that context's phase-one contribution for the final parse, so
|
|
475
|
-
* clears any annotations that context provided during phase 1.
|
|
475
|
+
* best-effort seed extraction as {@link runWith} when two-pass contexts are
|
|
476
|
+
* present. In two-phase runs, each two-pass context's phase-two return value
|
|
477
|
+
* replaces that context's phase-one contribution for the final parse, so
|
|
478
|
+
* returning `{}` clears any annotations that context provided during phase 1.
|
|
476
479
|
*
|
|
477
480
|
* @template TParser The sync parser type.
|
|
478
481
|
* @template THelp Return type when help is shown.
|
|
@@ -486,6 +489,8 @@ declare function runWith<TParser extends Parser<Mode, unknown, unknown>, TContex
|
|
|
486
489
|
* {@link runWith} or {@link runWithAsync} for async parsers.
|
|
487
490
|
* @throws {TypeError} If two or more contexts share the same
|
|
488
491
|
* {@link SourceContext.id}.
|
|
492
|
+
* @throws {TypeError} If any context omits `phase` or declares an invalid
|
|
493
|
+
* phase value.
|
|
489
494
|
* @throws {Error} If any context returns a Promise or if a context's
|
|
490
495
|
* `[Symbol.asyncDispose]` returns a Promise.
|
|
491
496
|
* @throws {SuppressedError} If the runner throws and a context's disposal
|
package/dist/facade.d.ts
CHANGED
|
@@ -405,28 +405,28 @@ type ContextOptionsParam<TContexts extends readonly SourceContext<unknown>[], TV
|
|
|
405
405
|
/**
|
|
406
406
|
* Runs a parser with multiple source contexts.
|
|
407
407
|
*
|
|
408
|
-
* This function automatically handles
|
|
409
|
-
* priority. Earlier contexts in the array override later ones.
|
|
408
|
+
* This function automatically handles single-pass and two-pass contexts with
|
|
409
|
+
* proper priority. Earlier contexts in the array override later ones.
|
|
410
410
|
*
|
|
411
411
|
* The function uses a smart two-phase approach:
|
|
412
412
|
*
|
|
413
|
-
* 1. *Phase 1*: Collect annotations from all contexts
|
|
414
|
-
* their data, dynamic contexts may return empty).
|
|
413
|
+
* 1. *Phase 1*: Collect annotations from all contexts.
|
|
415
414
|
* 2. *First parse*: Parse with Phase 1 annotations. If that pass finishes
|
|
416
415
|
* successfully, its value becomes the phase-two input. If the parser
|
|
417
416
|
* reaches a usable intermediate state but still does not complete
|
|
418
417
|
* successfully, the runner extracts a best-effort seed from that state
|
|
419
418
|
* instead.
|
|
420
|
-
* 3. *Phase 2*: Call `getAnnotations(parsed)` on all contexts with
|
|
421
|
-
* pass value. Deferred or otherwise unresolved fields in
|
|
422
|
-
* `undefined`. Each context's phase-two return
|
|
423
|
-
* phase-one contribution for the final parse, so
|
|
424
|
-
* annotations that context provided during
|
|
419
|
+
* 3. *Phase 2*: Call `getAnnotations(parsed)` on all two-pass contexts with
|
|
420
|
+
* the first pass value. Deferred or otherwise unresolved fields in
|
|
421
|
+
* `parsed` may be `undefined`. Each two-pass context's phase-two return
|
|
422
|
+
* value replaces its own phase-one contribution for the final parse, so
|
|
423
|
+
* returning `{}` clears any annotations that context provided during
|
|
424
|
+
* phase 1. Single-pass contexts reuse their phase-one snapshot.
|
|
425
425
|
* 4. *Second parse*: Parse again with the merged phase-two annotations.
|
|
426
426
|
*
|
|
427
|
-
* If all contexts are
|
|
428
|
-
*
|
|
429
|
-
*
|
|
427
|
+
* If all contexts are single-pass, the second parse is skipped for
|
|
428
|
+
* optimization. Phase 2 is also skipped when the first pass does not yield
|
|
429
|
+
* any usable seed at all.
|
|
430
430
|
*
|
|
431
431
|
* @template TParser The parser type.
|
|
432
432
|
* @template THelp Return type when help is shown.
|
|
@@ -438,6 +438,8 @@ type ContextOptionsParam<TContexts extends readonly SourceContext<unknown>[], TV
|
|
|
438
438
|
* @returns Promise that resolves to the parsed result.
|
|
439
439
|
* @throws {TypeError} If two or more contexts share the same
|
|
440
440
|
* {@link SourceContext.id}.
|
|
441
|
+
* @throws {TypeError} If any context omits `phase` or declares an invalid
|
|
442
|
+
* phase value.
|
|
441
443
|
* @throws {SuppressedError} If the runner throws and a context's disposal
|
|
442
444
|
* also throws. The original error is available via `.suppressed` and the
|
|
443
445
|
* disposal error via `.error`.
|
|
@@ -450,6 +452,7 @@ type ContextOptionsParam<TContexts extends readonly SourceContext<unknown>[], TV
|
|
|
450
452
|
*
|
|
451
453
|
* const envContext: SourceContext = {
|
|
452
454
|
* id: Symbol.for("@myapp/env"),
|
|
455
|
+
* phase: "single-pass",
|
|
453
456
|
* getAnnotations() {
|
|
454
457
|
* return { [Symbol.for("@myapp/env")]: process.env };
|
|
455
458
|
* }
|
|
@@ -469,10 +472,10 @@ declare function runWith<TParser extends Parser<Mode, unknown, unknown>, TContex
|
|
|
469
472
|
*
|
|
470
473
|
* This is the sync-only variant of {@link runWith}. All contexts must return
|
|
471
474
|
* annotations synchronously (not Promises). It uses the same two-phase
|
|
472
|
-
* best-effort seed extraction as {@link runWith} when
|
|
473
|
-
* present. In two-phase runs, each context's phase-two return value
|
|
474
|
-
* that context's phase-one contribution for the final parse, so
|
|
475
|
-
* clears any annotations that context provided during phase 1.
|
|
475
|
+
* best-effort seed extraction as {@link runWith} when two-pass contexts are
|
|
476
|
+
* present. In two-phase runs, each two-pass context's phase-two return value
|
|
477
|
+
* replaces that context's phase-one contribution for the final parse, so
|
|
478
|
+
* returning `{}` clears any annotations that context provided during phase 1.
|
|
476
479
|
*
|
|
477
480
|
* @template TParser The sync parser type.
|
|
478
481
|
* @template THelp Return type when help is shown.
|
|
@@ -486,6 +489,8 @@ declare function runWith<TParser extends Parser<Mode, unknown, unknown>, TContex
|
|
|
486
489
|
* {@link runWith} or {@link runWithAsync} for async parsers.
|
|
487
490
|
* @throws {TypeError} If two or more contexts share the same
|
|
488
491
|
* {@link SourceContext.id}.
|
|
492
|
+
* @throws {TypeError} If any context omits `phase` or declares an invalid
|
|
493
|
+
* phase value.
|
|
489
494
|
* @throws {Error} If any context returns a Promise or if a context's
|
|
490
495
|
* `[Symbol.asyncDispose]` returns a Promise.
|
|
491
496
|
* @throws {SuppressedError} If the runner throws and a context's disposal
|
package/dist/facade.js
CHANGED
|
@@ -1081,41 +1081,62 @@ function mergeAnnotations(annotationsList) {
|
|
|
1081
1081
|
}
|
|
1082
1082
|
return result;
|
|
1083
1083
|
}
|
|
1084
|
+
function validateContextPhases(contexts) {
|
|
1085
|
+
for (const context of contexts) {
|
|
1086
|
+
const phase = context.phase;
|
|
1087
|
+
if (phase !== "single-pass" && phase !== "two-pass") throw new TypeError(`Context ${String(context.id)} must declare phase as "single-pass" or "two-pass".`);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1084
1090
|
/**
|
|
1085
1091
|
* Collects phase 1 annotations from all contexts and determines whether
|
|
1086
1092
|
* two-phase parsing is needed.
|
|
1087
1093
|
*
|
|
1088
1094
|
* @param contexts Source contexts to collect annotations from.
|
|
1089
1095
|
* @param options Optional context-required options to pass to each context.
|
|
1090
|
-
* @returns Promise with merged annotations
|
|
1096
|
+
* @returns Promise with merged annotations, per-context snapshots, and a
|
|
1097
|
+
* two-phase hint.
|
|
1091
1098
|
*/
|
|
1092
1099
|
async function collectPhase1Annotations(contexts, options) {
|
|
1093
1100
|
const annotationsList = [];
|
|
1094
|
-
|
|
1101
|
+
const snapshots = [];
|
|
1102
|
+
let needsTwoPhase = false;
|
|
1095
1103
|
for (const context of contexts) {
|
|
1096
1104
|
const result = context.getAnnotations(void 0, options);
|
|
1097
|
-
hasDynamic ||= needsTwoPhaseContext(context, result);
|
|
1098
1105
|
const annotations = result instanceof Promise ? await result : result;
|
|
1099
1106
|
const internalAnnotations = context.getInternalAnnotations?.(void 0, annotations);
|
|
1100
|
-
|
|
1107
|
+
const snapshot = internalAnnotations == null ? annotations : mergeAnnotations([annotations, internalAnnotations]);
|
|
1108
|
+
annotationsList.push(snapshot);
|
|
1109
|
+
snapshots.push(snapshot);
|
|
1110
|
+
needsTwoPhase ||= context.phase === "two-pass";
|
|
1101
1111
|
}
|
|
1102
1112
|
return {
|
|
1103
1113
|
annotations: mergeAnnotations(annotationsList),
|
|
1104
|
-
|
|
1114
|
+
needsTwoPhase,
|
|
1115
|
+
snapshots
|
|
1105
1116
|
};
|
|
1106
1117
|
}
|
|
1107
1118
|
/**
|
|
1108
|
-
* Collects annotations from all contexts.
|
|
1119
|
+
* Collects final annotations from all contexts.
|
|
1120
|
+
*
|
|
1121
|
+
* `single-pass` contexts reuse their phase-1 snapshot. `two-pass` contexts
|
|
1122
|
+
* are recollected with the parsed value and replace their own phase-1
|
|
1123
|
+
* snapshot in the final merge.
|
|
1109
1124
|
*
|
|
1110
1125
|
* @param contexts Source contexts to collect annotations from.
|
|
1126
|
+
* @param phase1Snapshots Per-context snapshots collected during phase 1.
|
|
1111
1127
|
* @param parsed Optional parsed result from a previous parse pass.
|
|
1112
1128
|
* @param options Optional context-required options to pass to each context.
|
|
1113
1129
|
* @returns Promise that resolves to merged annotations.
|
|
1114
1130
|
*/
|
|
1115
|
-
async function
|
|
1131
|
+
async function collectFinalAnnotations(contexts, phase1Snapshots, parsed, options, deferred, deferredKeys) {
|
|
1116
1132
|
const annotationsList = [];
|
|
1117
1133
|
const preparedParsed = prepareParsedForContexts(parsed, deferred, deferredKeys);
|
|
1118
|
-
for (
|
|
1134
|
+
for (let index = 0; index < contexts.length; index++) {
|
|
1135
|
+
const context = contexts[index];
|
|
1136
|
+
if (context.phase === "single-pass") {
|
|
1137
|
+
annotationsList.push(phase1Snapshots[index]);
|
|
1138
|
+
continue;
|
|
1139
|
+
}
|
|
1119
1140
|
const mergedAnnotations = await withPreparedParsedForContext(context, preparedParsed, async (contextParsed) => {
|
|
1120
1141
|
const result = context.getAnnotations(contextParsed, options);
|
|
1121
1142
|
const annotations = result instanceof Promise ? await result : result;
|
|
@@ -1132,49 +1153,51 @@ async function collectAnnotations(contexts, parsed, options, deferred, deferredK
|
|
|
1132
1153
|
*
|
|
1133
1154
|
* @param contexts Source contexts to collect annotations from.
|
|
1134
1155
|
* @param options Optional context-required options to pass to each context.
|
|
1135
|
-
* @returns Merged annotations
|
|
1156
|
+
* @returns Merged annotations, per-context snapshots, and a two-phase hint.
|
|
1136
1157
|
* @throws Error if any context returns a Promise.
|
|
1137
1158
|
*/
|
|
1138
1159
|
function collectPhase1AnnotationsSync(contexts, options) {
|
|
1139
1160
|
const annotationsList = [];
|
|
1140
|
-
|
|
1161
|
+
const snapshots = [];
|
|
1162
|
+
let needsTwoPhase = false;
|
|
1141
1163
|
for (const context of contexts) {
|
|
1142
1164
|
const result = context.getAnnotations(void 0, options);
|
|
1143
1165
|
if (result instanceof Promise) throw new Error(`Context ${String(context.id)} returned a Promise in sync mode. Use runWith() or runWithAsync() for async contexts.`);
|
|
1144
|
-
hasDynamic ||= needsTwoPhaseContext(context, result);
|
|
1145
1166
|
const internalAnnotations = context.getInternalAnnotations?.(void 0, result);
|
|
1146
|
-
|
|
1167
|
+
const snapshot = internalAnnotations == null ? result : mergeAnnotations([result, internalAnnotations]);
|
|
1168
|
+
annotationsList.push(snapshot);
|
|
1169
|
+
snapshots.push(snapshot);
|
|
1170
|
+
needsTwoPhase ||= context.phase === "two-pass";
|
|
1147
1171
|
}
|
|
1148
1172
|
return {
|
|
1149
1173
|
annotations: mergeAnnotations(annotationsList),
|
|
1150
|
-
|
|
1174
|
+
needsTwoPhase,
|
|
1175
|
+
snapshots
|
|
1151
1176
|
};
|
|
1152
1177
|
}
|
|
1153
1178
|
/**
|
|
1154
|
-
*
|
|
1179
|
+
* Collects final annotations from all contexts synchronously.
|
|
1155
1180
|
*
|
|
1156
|
-
*
|
|
1157
|
-
*
|
|
1158
|
-
*
|
|
1159
|
-
*/
|
|
1160
|
-
function needsTwoPhaseContext(context, result) {
|
|
1161
|
-
if (context.mode !== void 0) return context.mode === "dynamic";
|
|
1162
|
-
if (result instanceof Promise) return true;
|
|
1163
|
-
return Object.getOwnPropertySymbols(result).length === 0;
|
|
1164
|
-
}
|
|
1165
|
-
/**
|
|
1166
|
-
* Collects annotations from all contexts synchronously.
|
|
1181
|
+
* `single-pass` contexts reuse their phase-1 snapshot. `two-pass` contexts
|
|
1182
|
+
* are recollected with the parsed value and replace their own phase-1
|
|
1183
|
+
* snapshot in the final merge.
|
|
1167
1184
|
*
|
|
1168
1185
|
* @param contexts Source contexts to collect annotations from.
|
|
1186
|
+
* @param phase1Snapshots Per-context snapshots collected during phase 1.
|
|
1169
1187
|
* @param parsed Optional parsed result from a previous parse pass.
|
|
1170
1188
|
* @param options Optional context-required options to pass to each context.
|
|
1171
1189
|
* @returns Merged annotations.
|
|
1172
1190
|
* @throws Error if any context returns a Promise.
|
|
1173
1191
|
*/
|
|
1174
|
-
function
|
|
1192
|
+
function collectFinalAnnotationsSync(contexts, phase1Snapshots, parsed, options, deferred, deferredKeys) {
|
|
1175
1193
|
const annotationsList = [];
|
|
1176
1194
|
const preparedParsed = prepareParsedForContexts(parsed, deferred, deferredKeys);
|
|
1177
|
-
for (
|
|
1195
|
+
for (let index = 0; index < contexts.length; index++) {
|
|
1196
|
+
const context = contexts[index];
|
|
1197
|
+
if (context.phase === "single-pass") {
|
|
1198
|
+
annotationsList.push(phase1Snapshots[index]);
|
|
1199
|
+
continue;
|
|
1200
|
+
}
|
|
1178
1201
|
const mergedAnnotations = withPreparedParsedForContext(context, preparedParsed, (contextParsed) => {
|
|
1179
1202
|
const result = context.getAnnotations(contextParsed, options);
|
|
1180
1203
|
if (result instanceof Promise) throw new Error(`Context ${String(context.id)} returned a Promise in sync mode. Use runWith() or runWithAsync() for async contexts.`);
|
|
@@ -1228,12 +1251,13 @@ function disposeContextsSync(contexts) {
|
|
|
1228
1251
|
*/
|
|
1229
1252
|
async function runWithBody(parser, programName, contexts, args, options) {
|
|
1230
1253
|
validateContextIds(contexts);
|
|
1254
|
+
validateContextPhases(contexts);
|
|
1231
1255
|
if (needsEarlyExit(args, options)) {
|
|
1232
1256
|
if (parser.$mode === "async") return runParser(parser, programName, args, options);
|
|
1233
1257
|
return Promise.resolve(runParser(parser, programName, args, options));
|
|
1234
1258
|
}
|
|
1235
1259
|
const ctxOptions = options.contextOptions;
|
|
1236
|
-
const { annotations: phase1Annotations,
|
|
1260
|
+
const { annotations: phase1Annotations, needsTwoPhase, snapshots: phase1Snapshots } = await collectPhase1Annotations(contexts, ctxOptions);
|
|
1237
1261
|
if (!needsTwoPhase) {
|
|
1238
1262
|
const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
|
|
1239
1263
|
if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
|
|
@@ -1246,7 +1270,7 @@ async function runWithBody(parser, programName, contexts, args, options) {
|
|
|
1246
1270
|
if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
|
|
1247
1271
|
return Promise.resolve(runParser(augmentedParser, programName, args, options));
|
|
1248
1272
|
}
|
|
1249
|
-
const { annotations: finalAnnotations } = await
|
|
1273
|
+
const { annotations: finalAnnotations } = await collectFinalAnnotations(contexts, phase1Snapshots, firstPassSeed.value, ctxOptions, firstPassSeed.deferred, firstPassSeed.deferredKeys);
|
|
1250
1274
|
const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
|
|
1251
1275
|
if (parser.$mode === "async") return runParser(augmentedParser2, programName, args, options);
|
|
1252
1276
|
return Promise.resolve(runParser(augmentedParser2, programName, args, options));
|
|
@@ -1254,28 +1278,28 @@ async function runWithBody(parser, programName, contexts, args, options) {
|
|
|
1254
1278
|
/**
|
|
1255
1279
|
* Runs a parser with multiple source contexts.
|
|
1256
1280
|
*
|
|
1257
|
-
* This function automatically handles
|
|
1258
|
-
* priority. Earlier contexts in the array override later ones.
|
|
1281
|
+
* This function automatically handles single-pass and two-pass contexts with
|
|
1282
|
+
* proper priority. Earlier contexts in the array override later ones.
|
|
1259
1283
|
*
|
|
1260
1284
|
* The function uses a smart two-phase approach:
|
|
1261
1285
|
*
|
|
1262
|
-
* 1. *Phase 1*: Collect annotations from all contexts
|
|
1263
|
-
* their data, dynamic contexts may return empty).
|
|
1286
|
+
* 1. *Phase 1*: Collect annotations from all contexts.
|
|
1264
1287
|
* 2. *First parse*: Parse with Phase 1 annotations. If that pass finishes
|
|
1265
1288
|
* successfully, its value becomes the phase-two input. If the parser
|
|
1266
1289
|
* reaches a usable intermediate state but still does not complete
|
|
1267
1290
|
* successfully, the runner extracts a best-effort seed from that state
|
|
1268
1291
|
* instead.
|
|
1269
|
-
* 3. *Phase 2*: Call `getAnnotations(parsed)` on all contexts with
|
|
1270
|
-
* pass value. Deferred or otherwise unresolved fields in
|
|
1271
|
-
* `undefined`. Each context's phase-two return
|
|
1272
|
-
* phase-one contribution for the final parse, so
|
|
1273
|
-
* annotations that context provided during
|
|
1292
|
+
* 3. *Phase 2*: Call `getAnnotations(parsed)` on all two-pass contexts with
|
|
1293
|
+
* the first pass value. Deferred or otherwise unresolved fields in
|
|
1294
|
+
* `parsed` may be `undefined`. Each two-pass context's phase-two return
|
|
1295
|
+
* value replaces its own phase-one contribution for the final parse, so
|
|
1296
|
+
* returning `{}` clears any annotations that context provided during
|
|
1297
|
+
* phase 1. Single-pass contexts reuse their phase-one snapshot.
|
|
1274
1298
|
* 4. *Second parse*: Parse again with the merged phase-two annotations.
|
|
1275
1299
|
*
|
|
1276
|
-
* If all contexts are
|
|
1277
|
-
*
|
|
1278
|
-
*
|
|
1300
|
+
* If all contexts are single-pass, the second parse is skipped for
|
|
1301
|
+
* optimization. Phase 2 is also skipped when the first pass does not yield
|
|
1302
|
+
* any usable seed at all.
|
|
1279
1303
|
*
|
|
1280
1304
|
* @template TParser The parser type.
|
|
1281
1305
|
* @template THelp Return type when help is shown.
|
|
@@ -1287,6 +1311,8 @@ async function runWithBody(parser, programName, contexts, args, options) {
|
|
|
1287
1311
|
* @returns Promise that resolves to the parsed result.
|
|
1288
1312
|
* @throws {TypeError} If two or more contexts share the same
|
|
1289
1313
|
* {@link SourceContext.id}.
|
|
1314
|
+
* @throws {TypeError} If any context omits `phase` or declares an invalid
|
|
1315
|
+
* phase value.
|
|
1290
1316
|
* @throws {SuppressedError} If the runner throws and a context's disposal
|
|
1291
1317
|
* also throws. The original error is available via `.suppressed` and the
|
|
1292
1318
|
* disposal error via `.error`.
|
|
@@ -1299,6 +1325,7 @@ async function runWithBody(parser, programName, contexts, args, options) {
|
|
|
1299
1325
|
*
|
|
1300
1326
|
* const envContext: SourceContext = {
|
|
1301
1327
|
* id: Symbol.for("@myapp/env"),
|
|
1328
|
+
* phase: "single-pass",
|
|
1302
1329
|
* getAnnotations() {
|
|
1303
1330
|
* return { [Symbol.for("@myapp/env")]: process.env };
|
|
1304
1331
|
* }
|
|
@@ -1342,9 +1369,10 @@ async function runWith(parser, programName, contexts, options) {
|
|
|
1342
1369
|
*/
|
|
1343
1370
|
function runWithSyncBody(parser, programName, contexts, args, options) {
|
|
1344
1371
|
validateContextIds(contexts);
|
|
1372
|
+
validateContextPhases(contexts);
|
|
1345
1373
|
if (needsEarlyExit(args, options)) return runParser(parser, programName, args, options);
|
|
1346
1374
|
const ctxOptions = options.contextOptions;
|
|
1347
|
-
const { annotations: phase1Annotations,
|
|
1375
|
+
const { annotations: phase1Annotations, needsTwoPhase, snapshots: phase1Snapshots } = collectPhase1AnnotationsSync(contexts, ctxOptions);
|
|
1348
1376
|
if (!needsTwoPhase) {
|
|
1349
1377
|
const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
|
|
1350
1378
|
return runParser(augmentedParser, programName, args, options);
|
|
@@ -1355,7 +1383,7 @@ function runWithSyncBody(parser, programName, contexts, args, options) {
|
|
|
1355
1383
|
const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
|
|
1356
1384
|
return runParser(augmentedParser, programName, args, options);
|
|
1357
1385
|
}
|
|
1358
|
-
const { annotations: finalAnnotations } =
|
|
1386
|
+
const { annotations: finalAnnotations } = collectFinalAnnotationsSync(contexts, phase1Snapshots, firstPassSeed.value, ctxOptions, firstPassSeed.deferred, firstPassSeed.deferredKeys);
|
|
1359
1387
|
const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
|
|
1360
1388
|
return runParser(augmentedParser2, programName, args, options);
|
|
1361
1389
|
}
|
|
@@ -1364,10 +1392,10 @@ function runWithSyncBody(parser, programName, contexts, args, options) {
|
|
|
1364
1392
|
*
|
|
1365
1393
|
* This is the sync-only variant of {@link runWith}. All contexts must return
|
|
1366
1394
|
* annotations synchronously (not Promises). It uses the same two-phase
|
|
1367
|
-
* best-effort seed extraction as {@link runWith} when
|
|
1368
|
-
* present. In two-phase runs, each context's phase-two return value
|
|
1369
|
-
* that context's phase-one contribution for the final parse, so
|
|
1370
|
-
* clears any annotations that context provided during phase 1.
|
|
1395
|
+
* best-effort seed extraction as {@link runWith} when two-pass contexts are
|
|
1396
|
+
* present. In two-phase runs, each two-pass context's phase-two return value
|
|
1397
|
+
* replaces that context's phase-one contribution for the final parse, so
|
|
1398
|
+
* returning `{}` clears any annotations that context provided during phase 1.
|
|
1371
1399
|
*
|
|
1372
1400
|
* @template TParser The sync parser type.
|
|
1373
1401
|
* @template THelp Return type when help is shown.
|
|
@@ -1381,6 +1409,8 @@ function runWithSyncBody(parser, programName, contexts, args, options) {
|
|
|
1381
1409
|
* {@link runWith} or {@link runWithAsync} for async parsers.
|
|
1382
1410
|
* @throws {TypeError} If two or more contexts share the same
|
|
1383
1411
|
* {@link SourceContext.id}.
|
|
1412
|
+
* @throws {TypeError} If any context omits `phase` or declares an invalid
|
|
1413
|
+
* phase value.
|
|
1384
1414
|
* @throws {Error} If any context returns a Promise or if a context's
|
|
1385
1415
|
* `[Symbol.asyncDispose]` returns a Promise.
|
|
1386
1416
|
* @throws {SuppressedError} If the runner throws and a context's disposal
|
package/dist/parser.d.cts
CHANGED
|
@@ -215,7 +215,7 @@ interface Parser<M extends Mode = "sync", TValue = unknown, TState = unknown> {
|
|
|
215
215
|
* A type-appropriate default value used as a stand-in during deferred
|
|
216
216
|
* prompt resolution. When present, combinators like `prompt()` use this
|
|
217
217
|
* value instead of an internal sentinel during two-phase parsing, so that
|
|
218
|
-
* `map()` transforms and
|
|
218
|
+
* `map()` transforms and two-pass contexts always receive a valid value
|
|
219
219
|
* of type {@link TValue}.
|
|
220
220
|
*
|
|
221
221
|
* This property is set automatically by `option()` and `argument()` from
|
package/dist/parser.d.ts
CHANGED
|
@@ -215,7 +215,7 @@ interface Parser<M extends Mode = "sync", TValue = unknown, TState = unknown> {
|
|
|
215
215
|
* A type-appropriate default value used as a stand-in during deferred
|
|
216
216
|
* prompt resolution. When present, combinators like `prompt()` use this
|
|
217
217
|
* value instead of an internal sentinel during two-phase parsing, so that
|
|
218
|
-
* `map()` transforms and
|
|
218
|
+
* `map()` transforms and two-pass contexts always receive a valid value
|
|
219
219
|
* of type {@link TValue}.
|
|
220
220
|
*
|
|
221
221
|
* This property is set automatically by `option()` and `argument()` from
|
package/dist/valueparser.d.cts
CHANGED
|
@@ -100,7 +100,7 @@ interface ValueParser<M extends Mode = "sync", T = unknown> {
|
|
|
100
100
|
* A type-appropriate default value used as a stand-in during deferred
|
|
101
101
|
* prompt resolution. When an interactive prompt is deferred during
|
|
102
102
|
* two-phase parsing, this value is used instead of an internal sentinel
|
|
103
|
-
* so that `map()` transforms and
|
|
103
|
+
* so that `map()` transforms and two-pass contexts always receive a valid
|
|
104
104
|
* value of type {@link T}.
|
|
105
105
|
*
|
|
106
106
|
* The placeholder does not need to be meaningful; it only needs to be
|
package/dist/valueparser.d.ts
CHANGED
|
@@ -100,7 +100,7 @@ interface ValueParser<M extends Mode = "sync", T = unknown> {
|
|
|
100
100
|
* A type-appropriate default value used as a stand-in during deferred
|
|
101
101
|
* prompt resolution. When an interactive prompt is deferred during
|
|
102
102
|
* two-phase parsing, this value is used instead of an internal sentinel
|
|
103
|
-
* so that `map()` transforms and
|
|
103
|
+
* so that `map()` transforms and two-pass contexts always receive a valid
|
|
104
104
|
* value of type {@link T}.
|
|
105
105
|
*
|
|
106
106
|
* The placeholder does not need to be meaningful; it only needs to be
|