@optique/core 1.0.0-dev.1801 → 1.0.0-dev.1804
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 +75 -47
- package/dist/facade.d.cts +21 -16
- package/dist/facade.d.ts +21 -16
- package/dist/facade.js +75 -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,61 @@ 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
|
-
let
|
|
1101
|
+
let snapshots;
|
|
1095
1102
|
for (const context of contexts) {
|
|
1096
1103
|
const result = context.getAnnotations(void 0, options);
|
|
1097
|
-
hasDynamic ||= needsTwoPhaseContext(context, result);
|
|
1098
1104
|
const annotations = result instanceof Promise ? await result : result;
|
|
1099
1105
|
const internalAnnotations = context.getInternalAnnotations?.(void 0, annotations);
|
|
1100
|
-
|
|
1106
|
+
const snapshot = internalAnnotations == null ? annotations : mergeAnnotations([annotations, internalAnnotations]);
|
|
1107
|
+
annotationsList.push(snapshot);
|
|
1108
|
+
if (snapshots != null) snapshots.push(snapshot);
|
|
1109
|
+
else if (context.phase === "two-pass") snapshots = [...annotationsList];
|
|
1101
1110
|
}
|
|
1102
1111
|
return {
|
|
1103
1112
|
annotations: mergeAnnotations(annotationsList),
|
|
1104
|
-
|
|
1113
|
+
needsTwoPhase: snapshots != null,
|
|
1114
|
+
snapshots: snapshots ?? []
|
|
1105
1115
|
};
|
|
1106
1116
|
}
|
|
1107
1117
|
/**
|
|
1108
|
-
* Collects annotations from all contexts.
|
|
1118
|
+
* Collects final annotations from all contexts.
|
|
1119
|
+
*
|
|
1120
|
+
* `single-pass` contexts reuse their phase-1 snapshot. `two-pass` contexts
|
|
1121
|
+
* are recollected with the parsed value and replace their own phase-1
|
|
1122
|
+
* snapshot in the final merge.
|
|
1109
1123
|
*
|
|
1110
1124
|
* @param contexts Source contexts to collect annotations from.
|
|
1125
|
+
* @param phase1Snapshots Per-context snapshots collected during phase 1.
|
|
1111
1126
|
* @param parsed Optional parsed result from a previous parse pass.
|
|
1112
1127
|
* @param options Optional context-required options to pass to each context.
|
|
1113
1128
|
* @returns Promise that resolves to merged annotations.
|
|
1114
1129
|
*/
|
|
1115
|
-
async function
|
|
1130
|
+
async function collectFinalAnnotations(contexts, phase1Snapshots, parsed, options, deferred, deferredKeys) {
|
|
1116
1131
|
const annotationsList = [];
|
|
1117
1132
|
const preparedParsed = prepareParsedForContexts(parsed, deferred, deferredKeys);
|
|
1118
|
-
for (
|
|
1133
|
+
for (let index = 0; index < contexts.length; index++) {
|
|
1134
|
+
const context = contexts[index];
|
|
1135
|
+
if (context.phase === "single-pass") {
|
|
1136
|
+
annotationsList.push(phase1Snapshots[index]);
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1119
1139
|
const mergedAnnotations = await withPreparedParsedForContext(context, preparedParsed, async (contextParsed) => {
|
|
1120
1140
|
const result = context.getAnnotations(contextParsed, options);
|
|
1121
1141
|
const annotations = result instanceof Promise ? await result : result;
|
|
@@ -1132,49 +1152,50 @@ async function collectAnnotations(contexts, parsed, options, deferred, deferredK
|
|
|
1132
1152
|
*
|
|
1133
1153
|
* @param contexts Source contexts to collect annotations from.
|
|
1134
1154
|
* @param options Optional context-required options to pass to each context.
|
|
1135
|
-
* @returns Merged annotations
|
|
1155
|
+
* @returns Merged annotations, per-context snapshots, and a two-phase hint.
|
|
1136
1156
|
* @throws Error if any context returns a Promise.
|
|
1137
1157
|
*/
|
|
1138
1158
|
function collectPhase1AnnotationsSync(contexts, options) {
|
|
1139
1159
|
const annotationsList = [];
|
|
1140
|
-
let
|
|
1160
|
+
let snapshots;
|
|
1141
1161
|
for (const context of contexts) {
|
|
1142
1162
|
const result = context.getAnnotations(void 0, options);
|
|
1143
1163
|
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
1164
|
const internalAnnotations = context.getInternalAnnotations?.(void 0, result);
|
|
1146
|
-
|
|
1165
|
+
const snapshot = internalAnnotations == null ? result : mergeAnnotations([result, internalAnnotations]);
|
|
1166
|
+
annotationsList.push(snapshot);
|
|
1167
|
+
if (snapshots != null) snapshots.push(snapshot);
|
|
1168
|
+
else if (context.phase === "two-pass") snapshots = [...annotationsList];
|
|
1147
1169
|
}
|
|
1148
1170
|
return {
|
|
1149
1171
|
annotations: mergeAnnotations(annotationsList),
|
|
1150
|
-
|
|
1172
|
+
needsTwoPhase: snapshots != null,
|
|
1173
|
+
snapshots: snapshots ?? []
|
|
1151
1174
|
};
|
|
1152
1175
|
}
|
|
1153
1176
|
/**
|
|
1154
|
-
*
|
|
1177
|
+
* Collects final annotations from all contexts synchronously.
|
|
1155
1178
|
*
|
|
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.
|
|
1179
|
+
* `single-pass` contexts reuse their phase-1 snapshot. `two-pass` contexts
|
|
1180
|
+
* are recollected with the parsed value and replace their own phase-1
|
|
1181
|
+
* snapshot in the final merge.
|
|
1167
1182
|
*
|
|
1168
1183
|
* @param contexts Source contexts to collect annotations from.
|
|
1184
|
+
* @param phase1Snapshots Per-context snapshots collected during phase 1.
|
|
1169
1185
|
* @param parsed Optional parsed result from a previous parse pass.
|
|
1170
1186
|
* @param options Optional context-required options to pass to each context.
|
|
1171
1187
|
* @returns Merged annotations.
|
|
1172
1188
|
* @throws Error if any context returns a Promise.
|
|
1173
1189
|
*/
|
|
1174
|
-
function
|
|
1190
|
+
function collectFinalAnnotationsSync(contexts, phase1Snapshots, parsed, options, deferred, deferredKeys) {
|
|
1175
1191
|
const annotationsList = [];
|
|
1176
1192
|
const preparedParsed = prepareParsedForContexts(parsed, deferred, deferredKeys);
|
|
1177
|
-
for (
|
|
1193
|
+
for (let index = 0; index < contexts.length; index++) {
|
|
1194
|
+
const context = contexts[index];
|
|
1195
|
+
if (context.phase === "single-pass") {
|
|
1196
|
+
annotationsList.push(phase1Snapshots[index]);
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
1178
1199
|
const mergedAnnotations = withPreparedParsedForContext(context, preparedParsed, (contextParsed) => {
|
|
1179
1200
|
const result = context.getAnnotations(contextParsed, options);
|
|
1180
1201
|
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 +1249,13 @@ function disposeContextsSync(contexts) {
|
|
|
1228
1249
|
*/
|
|
1229
1250
|
async function runWithBody(parser, programName, contexts, args, options) {
|
|
1230
1251
|
require_validate.validateContextIds(contexts);
|
|
1252
|
+
validateContextPhases(contexts);
|
|
1231
1253
|
if (needsEarlyExit(args, options)) {
|
|
1232
1254
|
if (parser.$mode === "async") return runParser(parser, programName, args, options);
|
|
1233
1255
|
return Promise.resolve(runParser(parser, programName, args, options));
|
|
1234
1256
|
}
|
|
1235
1257
|
const ctxOptions = options.contextOptions;
|
|
1236
|
-
const { annotations: phase1Annotations,
|
|
1258
|
+
const { annotations: phase1Annotations, needsTwoPhase, snapshots: phase1Snapshots } = await collectPhase1Annotations(contexts, ctxOptions);
|
|
1237
1259
|
if (!needsTwoPhase) {
|
|
1238
1260
|
const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
|
|
1239
1261
|
if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
|
|
@@ -1246,7 +1268,7 @@ async function runWithBody(parser, programName, contexts, args, options) {
|
|
|
1246
1268
|
if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
|
|
1247
1269
|
return Promise.resolve(runParser(augmentedParser, programName, args, options));
|
|
1248
1270
|
}
|
|
1249
|
-
const { annotations: finalAnnotations } = await
|
|
1271
|
+
const { annotations: finalAnnotations } = await collectFinalAnnotations(contexts, phase1Snapshots, firstPassSeed.value, ctxOptions, firstPassSeed.deferred, firstPassSeed.deferredKeys);
|
|
1250
1272
|
const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
|
|
1251
1273
|
if (parser.$mode === "async") return runParser(augmentedParser2, programName, args, options);
|
|
1252
1274
|
return Promise.resolve(runParser(augmentedParser2, programName, args, options));
|
|
@@ -1254,28 +1276,28 @@ async function runWithBody(parser, programName, contexts, args, options) {
|
|
|
1254
1276
|
/**
|
|
1255
1277
|
* Runs a parser with multiple source contexts.
|
|
1256
1278
|
*
|
|
1257
|
-
* This function automatically handles
|
|
1258
|
-
* priority. Earlier contexts in the array override later ones.
|
|
1279
|
+
* This function automatically handles single-pass and two-pass contexts with
|
|
1280
|
+
* proper priority. Earlier contexts in the array override later ones.
|
|
1259
1281
|
*
|
|
1260
1282
|
* The function uses a smart two-phase approach:
|
|
1261
1283
|
*
|
|
1262
|
-
* 1. *Phase 1*: Collect annotations from all contexts
|
|
1263
|
-
* their data, dynamic contexts may return empty).
|
|
1284
|
+
* 1. *Phase 1*: Collect annotations from all contexts.
|
|
1264
1285
|
* 2. *First parse*: Parse with Phase 1 annotations. If that pass finishes
|
|
1265
1286
|
* successfully, its value becomes the phase-two input. If the parser
|
|
1266
1287
|
* reaches a usable intermediate state but still does not complete
|
|
1267
1288
|
* successfully, the runner extracts a best-effort seed from that state
|
|
1268
1289
|
* 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
|
|
1290
|
+
* 3. *Phase 2*: Call `getAnnotations(parsed)` on all two-pass contexts with
|
|
1291
|
+
* the first pass value. Deferred or otherwise unresolved fields in
|
|
1292
|
+
* `parsed` may be `undefined`. Each two-pass context's phase-two return
|
|
1293
|
+
* value replaces its own phase-one contribution for the final parse, so
|
|
1294
|
+
* returning `{}` clears any annotations that context provided during
|
|
1295
|
+
* phase 1. Single-pass contexts reuse their phase-one snapshot.
|
|
1274
1296
|
* 4. *Second parse*: Parse again with the merged phase-two annotations.
|
|
1275
1297
|
*
|
|
1276
|
-
* If all contexts are
|
|
1277
|
-
*
|
|
1278
|
-
*
|
|
1298
|
+
* If all contexts are single-pass, the second parse is skipped for
|
|
1299
|
+
* optimization. Phase 2 is also skipped when the first pass does not yield
|
|
1300
|
+
* any usable seed at all.
|
|
1279
1301
|
*
|
|
1280
1302
|
* @template TParser The parser type.
|
|
1281
1303
|
* @template THelp Return type when help is shown.
|
|
@@ -1287,6 +1309,8 @@ async function runWithBody(parser, programName, contexts, args, options) {
|
|
|
1287
1309
|
* @returns Promise that resolves to the parsed result.
|
|
1288
1310
|
* @throws {TypeError} If two or more contexts share the same
|
|
1289
1311
|
* {@link SourceContext.id}.
|
|
1312
|
+
* @throws {TypeError} If any context omits `phase` or declares an invalid
|
|
1313
|
+
* phase value.
|
|
1290
1314
|
* @throws {SuppressedError} If the runner throws and a context's disposal
|
|
1291
1315
|
* also throws. The original error is available via `.suppressed` and the
|
|
1292
1316
|
* disposal error via `.error`.
|
|
@@ -1299,6 +1323,7 @@ async function runWithBody(parser, programName, contexts, args, options) {
|
|
|
1299
1323
|
*
|
|
1300
1324
|
* const envContext: SourceContext = {
|
|
1301
1325
|
* id: Symbol.for("@myapp/env"),
|
|
1326
|
+
* phase: "single-pass",
|
|
1302
1327
|
* getAnnotations() {
|
|
1303
1328
|
* return { [Symbol.for("@myapp/env")]: process.env };
|
|
1304
1329
|
* }
|
|
@@ -1342,9 +1367,10 @@ async function runWith(parser, programName, contexts, options) {
|
|
|
1342
1367
|
*/
|
|
1343
1368
|
function runWithSyncBody(parser, programName, contexts, args, options) {
|
|
1344
1369
|
require_validate.validateContextIds(contexts);
|
|
1370
|
+
validateContextPhases(contexts);
|
|
1345
1371
|
if (needsEarlyExit(args, options)) return runParser(parser, programName, args, options);
|
|
1346
1372
|
const ctxOptions = options.contextOptions;
|
|
1347
|
-
const { annotations: phase1Annotations,
|
|
1373
|
+
const { annotations: phase1Annotations, needsTwoPhase, snapshots: phase1Snapshots } = collectPhase1AnnotationsSync(contexts, ctxOptions);
|
|
1348
1374
|
if (!needsTwoPhase) {
|
|
1349
1375
|
const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
|
|
1350
1376
|
return runParser(augmentedParser, programName, args, options);
|
|
@@ -1355,7 +1381,7 @@ function runWithSyncBody(parser, programName, contexts, args, options) {
|
|
|
1355
1381
|
const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
|
|
1356
1382
|
return runParser(augmentedParser, programName, args, options);
|
|
1357
1383
|
}
|
|
1358
|
-
const { annotations: finalAnnotations } =
|
|
1384
|
+
const { annotations: finalAnnotations } = collectFinalAnnotationsSync(contexts, phase1Snapshots, firstPassSeed.value, ctxOptions, firstPassSeed.deferred, firstPassSeed.deferredKeys);
|
|
1359
1385
|
const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
|
|
1360
1386
|
return runParser(augmentedParser2, programName, args, options);
|
|
1361
1387
|
}
|
|
@@ -1364,10 +1390,10 @@ function runWithSyncBody(parser, programName, contexts, args, options) {
|
|
|
1364
1390
|
*
|
|
1365
1391
|
* This is the sync-only variant of {@link runWith}. All contexts must return
|
|
1366
1392
|
* 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.
|
|
1393
|
+
* best-effort seed extraction as {@link runWith} when two-pass contexts are
|
|
1394
|
+
* present. In two-phase runs, each two-pass context's phase-two return value
|
|
1395
|
+
* replaces that context's phase-one contribution for the final parse, so
|
|
1396
|
+
* returning `{}` clears any annotations that context provided during phase 1.
|
|
1371
1397
|
*
|
|
1372
1398
|
* @template TParser The sync parser type.
|
|
1373
1399
|
* @template THelp Return type when help is shown.
|
|
@@ -1381,6 +1407,8 @@ function runWithSyncBody(parser, programName, contexts, args, options) {
|
|
|
1381
1407
|
* {@link runWith} or {@link runWithAsync} for async parsers.
|
|
1382
1408
|
* @throws {TypeError} If two or more contexts share the same
|
|
1383
1409
|
* {@link SourceContext.id}.
|
|
1410
|
+
* @throws {TypeError} If any context omits `phase` or declares an invalid
|
|
1411
|
+
* phase value.
|
|
1384
1412
|
* @throws {Error} If any context returns a Promise or if a context's
|
|
1385
1413
|
* `[Symbol.asyncDispose]` returns a Promise.
|
|
1386
1414
|
* @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,61 @@ 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
|
-
let
|
|
1101
|
+
let snapshots;
|
|
1095
1102
|
for (const context of contexts) {
|
|
1096
1103
|
const result = context.getAnnotations(void 0, options);
|
|
1097
|
-
hasDynamic ||= needsTwoPhaseContext(context, result);
|
|
1098
1104
|
const annotations = result instanceof Promise ? await result : result;
|
|
1099
1105
|
const internalAnnotations = context.getInternalAnnotations?.(void 0, annotations);
|
|
1100
|
-
|
|
1106
|
+
const snapshot = internalAnnotations == null ? annotations : mergeAnnotations([annotations, internalAnnotations]);
|
|
1107
|
+
annotationsList.push(snapshot);
|
|
1108
|
+
if (snapshots != null) snapshots.push(snapshot);
|
|
1109
|
+
else if (context.phase === "two-pass") snapshots = [...annotationsList];
|
|
1101
1110
|
}
|
|
1102
1111
|
return {
|
|
1103
1112
|
annotations: mergeAnnotations(annotationsList),
|
|
1104
|
-
|
|
1113
|
+
needsTwoPhase: snapshots != null,
|
|
1114
|
+
snapshots: snapshots ?? []
|
|
1105
1115
|
};
|
|
1106
1116
|
}
|
|
1107
1117
|
/**
|
|
1108
|
-
* Collects annotations from all contexts.
|
|
1118
|
+
* Collects final annotations from all contexts.
|
|
1119
|
+
*
|
|
1120
|
+
* `single-pass` contexts reuse their phase-1 snapshot. `two-pass` contexts
|
|
1121
|
+
* are recollected with the parsed value and replace their own phase-1
|
|
1122
|
+
* snapshot in the final merge.
|
|
1109
1123
|
*
|
|
1110
1124
|
* @param contexts Source contexts to collect annotations from.
|
|
1125
|
+
* @param phase1Snapshots Per-context snapshots collected during phase 1.
|
|
1111
1126
|
* @param parsed Optional parsed result from a previous parse pass.
|
|
1112
1127
|
* @param options Optional context-required options to pass to each context.
|
|
1113
1128
|
* @returns Promise that resolves to merged annotations.
|
|
1114
1129
|
*/
|
|
1115
|
-
async function
|
|
1130
|
+
async function collectFinalAnnotations(contexts, phase1Snapshots, parsed, options, deferred, deferredKeys) {
|
|
1116
1131
|
const annotationsList = [];
|
|
1117
1132
|
const preparedParsed = prepareParsedForContexts(parsed, deferred, deferredKeys);
|
|
1118
|
-
for (
|
|
1133
|
+
for (let index = 0; index < contexts.length; index++) {
|
|
1134
|
+
const context = contexts[index];
|
|
1135
|
+
if (context.phase === "single-pass") {
|
|
1136
|
+
annotationsList.push(phase1Snapshots[index]);
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1119
1139
|
const mergedAnnotations = await withPreparedParsedForContext(context, preparedParsed, async (contextParsed) => {
|
|
1120
1140
|
const result = context.getAnnotations(contextParsed, options);
|
|
1121
1141
|
const annotations = result instanceof Promise ? await result : result;
|
|
@@ -1132,49 +1152,50 @@ async function collectAnnotations(contexts, parsed, options, deferred, deferredK
|
|
|
1132
1152
|
*
|
|
1133
1153
|
* @param contexts Source contexts to collect annotations from.
|
|
1134
1154
|
* @param options Optional context-required options to pass to each context.
|
|
1135
|
-
* @returns Merged annotations
|
|
1155
|
+
* @returns Merged annotations, per-context snapshots, and a two-phase hint.
|
|
1136
1156
|
* @throws Error if any context returns a Promise.
|
|
1137
1157
|
*/
|
|
1138
1158
|
function collectPhase1AnnotationsSync(contexts, options) {
|
|
1139
1159
|
const annotationsList = [];
|
|
1140
|
-
let
|
|
1160
|
+
let snapshots;
|
|
1141
1161
|
for (const context of contexts) {
|
|
1142
1162
|
const result = context.getAnnotations(void 0, options);
|
|
1143
1163
|
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
1164
|
const internalAnnotations = context.getInternalAnnotations?.(void 0, result);
|
|
1146
|
-
|
|
1165
|
+
const snapshot = internalAnnotations == null ? result : mergeAnnotations([result, internalAnnotations]);
|
|
1166
|
+
annotationsList.push(snapshot);
|
|
1167
|
+
if (snapshots != null) snapshots.push(snapshot);
|
|
1168
|
+
else if (context.phase === "two-pass") snapshots = [...annotationsList];
|
|
1147
1169
|
}
|
|
1148
1170
|
return {
|
|
1149
1171
|
annotations: mergeAnnotations(annotationsList),
|
|
1150
|
-
|
|
1172
|
+
needsTwoPhase: snapshots != null,
|
|
1173
|
+
snapshots: snapshots ?? []
|
|
1151
1174
|
};
|
|
1152
1175
|
}
|
|
1153
1176
|
/**
|
|
1154
|
-
*
|
|
1177
|
+
* Collects final annotations from all contexts synchronously.
|
|
1155
1178
|
*
|
|
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.
|
|
1179
|
+
* `single-pass` contexts reuse their phase-1 snapshot. `two-pass` contexts
|
|
1180
|
+
* are recollected with the parsed value and replace their own phase-1
|
|
1181
|
+
* snapshot in the final merge.
|
|
1167
1182
|
*
|
|
1168
1183
|
* @param contexts Source contexts to collect annotations from.
|
|
1184
|
+
* @param phase1Snapshots Per-context snapshots collected during phase 1.
|
|
1169
1185
|
* @param parsed Optional parsed result from a previous parse pass.
|
|
1170
1186
|
* @param options Optional context-required options to pass to each context.
|
|
1171
1187
|
* @returns Merged annotations.
|
|
1172
1188
|
* @throws Error if any context returns a Promise.
|
|
1173
1189
|
*/
|
|
1174
|
-
function
|
|
1190
|
+
function collectFinalAnnotationsSync(contexts, phase1Snapshots, parsed, options, deferred, deferredKeys) {
|
|
1175
1191
|
const annotationsList = [];
|
|
1176
1192
|
const preparedParsed = prepareParsedForContexts(parsed, deferred, deferredKeys);
|
|
1177
|
-
for (
|
|
1193
|
+
for (let index = 0; index < contexts.length; index++) {
|
|
1194
|
+
const context = contexts[index];
|
|
1195
|
+
if (context.phase === "single-pass") {
|
|
1196
|
+
annotationsList.push(phase1Snapshots[index]);
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
1178
1199
|
const mergedAnnotations = withPreparedParsedForContext(context, preparedParsed, (contextParsed) => {
|
|
1179
1200
|
const result = context.getAnnotations(contextParsed, options);
|
|
1180
1201
|
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 +1249,13 @@ function disposeContextsSync(contexts) {
|
|
|
1228
1249
|
*/
|
|
1229
1250
|
async function runWithBody(parser, programName, contexts, args, options) {
|
|
1230
1251
|
validateContextIds(contexts);
|
|
1252
|
+
validateContextPhases(contexts);
|
|
1231
1253
|
if (needsEarlyExit(args, options)) {
|
|
1232
1254
|
if (parser.$mode === "async") return runParser(parser, programName, args, options);
|
|
1233
1255
|
return Promise.resolve(runParser(parser, programName, args, options));
|
|
1234
1256
|
}
|
|
1235
1257
|
const ctxOptions = options.contextOptions;
|
|
1236
|
-
const { annotations: phase1Annotations,
|
|
1258
|
+
const { annotations: phase1Annotations, needsTwoPhase, snapshots: phase1Snapshots } = await collectPhase1Annotations(contexts, ctxOptions);
|
|
1237
1259
|
if (!needsTwoPhase) {
|
|
1238
1260
|
const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
|
|
1239
1261
|
if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
|
|
@@ -1246,7 +1268,7 @@ async function runWithBody(parser, programName, contexts, args, options) {
|
|
|
1246
1268
|
if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
|
|
1247
1269
|
return Promise.resolve(runParser(augmentedParser, programName, args, options));
|
|
1248
1270
|
}
|
|
1249
|
-
const { annotations: finalAnnotations } = await
|
|
1271
|
+
const { annotations: finalAnnotations } = await collectFinalAnnotations(contexts, phase1Snapshots, firstPassSeed.value, ctxOptions, firstPassSeed.deferred, firstPassSeed.deferredKeys);
|
|
1250
1272
|
const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
|
|
1251
1273
|
if (parser.$mode === "async") return runParser(augmentedParser2, programName, args, options);
|
|
1252
1274
|
return Promise.resolve(runParser(augmentedParser2, programName, args, options));
|
|
@@ -1254,28 +1276,28 @@ async function runWithBody(parser, programName, contexts, args, options) {
|
|
|
1254
1276
|
/**
|
|
1255
1277
|
* Runs a parser with multiple source contexts.
|
|
1256
1278
|
*
|
|
1257
|
-
* This function automatically handles
|
|
1258
|
-
* priority. Earlier contexts in the array override later ones.
|
|
1279
|
+
* This function automatically handles single-pass and two-pass contexts with
|
|
1280
|
+
* proper priority. Earlier contexts in the array override later ones.
|
|
1259
1281
|
*
|
|
1260
1282
|
* The function uses a smart two-phase approach:
|
|
1261
1283
|
*
|
|
1262
|
-
* 1. *Phase 1*: Collect annotations from all contexts
|
|
1263
|
-
* their data, dynamic contexts may return empty).
|
|
1284
|
+
* 1. *Phase 1*: Collect annotations from all contexts.
|
|
1264
1285
|
* 2. *First parse*: Parse with Phase 1 annotations. If that pass finishes
|
|
1265
1286
|
* successfully, its value becomes the phase-two input. If the parser
|
|
1266
1287
|
* reaches a usable intermediate state but still does not complete
|
|
1267
1288
|
* successfully, the runner extracts a best-effort seed from that state
|
|
1268
1289
|
* 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
|
|
1290
|
+
* 3. *Phase 2*: Call `getAnnotations(parsed)` on all two-pass contexts with
|
|
1291
|
+
* the first pass value. Deferred or otherwise unresolved fields in
|
|
1292
|
+
* `parsed` may be `undefined`. Each two-pass context's phase-two return
|
|
1293
|
+
* value replaces its own phase-one contribution for the final parse, so
|
|
1294
|
+
* returning `{}` clears any annotations that context provided during
|
|
1295
|
+
* phase 1. Single-pass contexts reuse their phase-one snapshot.
|
|
1274
1296
|
* 4. *Second parse*: Parse again with the merged phase-two annotations.
|
|
1275
1297
|
*
|
|
1276
|
-
* If all contexts are
|
|
1277
|
-
*
|
|
1278
|
-
*
|
|
1298
|
+
* If all contexts are single-pass, the second parse is skipped for
|
|
1299
|
+
* optimization. Phase 2 is also skipped when the first pass does not yield
|
|
1300
|
+
* any usable seed at all.
|
|
1279
1301
|
*
|
|
1280
1302
|
* @template TParser The parser type.
|
|
1281
1303
|
* @template THelp Return type when help is shown.
|
|
@@ -1287,6 +1309,8 @@ async function runWithBody(parser, programName, contexts, args, options) {
|
|
|
1287
1309
|
* @returns Promise that resolves to the parsed result.
|
|
1288
1310
|
* @throws {TypeError} If two or more contexts share the same
|
|
1289
1311
|
* {@link SourceContext.id}.
|
|
1312
|
+
* @throws {TypeError} If any context omits `phase` or declares an invalid
|
|
1313
|
+
* phase value.
|
|
1290
1314
|
* @throws {SuppressedError} If the runner throws and a context's disposal
|
|
1291
1315
|
* also throws. The original error is available via `.suppressed` and the
|
|
1292
1316
|
* disposal error via `.error`.
|
|
@@ -1299,6 +1323,7 @@ async function runWithBody(parser, programName, contexts, args, options) {
|
|
|
1299
1323
|
*
|
|
1300
1324
|
* const envContext: SourceContext = {
|
|
1301
1325
|
* id: Symbol.for("@myapp/env"),
|
|
1326
|
+
* phase: "single-pass",
|
|
1302
1327
|
* getAnnotations() {
|
|
1303
1328
|
* return { [Symbol.for("@myapp/env")]: process.env };
|
|
1304
1329
|
* }
|
|
@@ -1342,9 +1367,10 @@ async function runWith(parser, programName, contexts, options) {
|
|
|
1342
1367
|
*/
|
|
1343
1368
|
function runWithSyncBody(parser, programName, contexts, args, options) {
|
|
1344
1369
|
validateContextIds(contexts);
|
|
1370
|
+
validateContextPhases(contexts);
|
|
1345
1371
|
if (needsEarlyExit(args, options)) return runParser(parser, programName, args, options);
|
|
1346
1372
|
const ctxOptions = options.contextOptions;
|
|
1347
|
-
const { annotations: phase1Annotations,
|
|
1373
|
+
const { annotations: phase1Annotations, needsTwoPhase, snapshots: phase1Snapshots } = collectPhase1AnnotationsSync(contexts, ctxOptions);
|
|
1348
1374
|
if (!needsTwoPhase) {
|
|
1349
1375
|
const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
|
|
1350
1376
|
return runParser(augmentedParser, programName, args, options);
|
|
@@ -1355,7 +1381,7 @@ function runWithSyncBody(parser, programName, contexts, args, options) {
|
|
|
1355
1381
|
const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
|
|
1356
1382
|
return runParser(augmentedParser, programName, args, options);
|
|
1357
1383
|
}
|
|
1358
|
-
const { annotations: finalAnnotations } =
|
|
1384
|
+
const { annotations: finalAnnotations } = collectFinalAnnotationsSync(contexts, phase1Snapshots, firstPassSeed.value, ctxOptions, firstPassSeed.deferred, firstPassSeed.deferredKeys);
|
|
1359
1385
|
const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
|
|
1360
1386
|
return runParser(augmentedParser2, programName, args, options);
|
|
1361
1387
|
}
|
|
@@ -1364,10 +1390,10 @@ function runWithSyncBody(parser, programName, contexts, args, options) {
|
|
|
1364
1390
|
*
|
|
1365
1391
|
* This is the sync-only variant of {@link runWith}. All contexts must return
|
|
1366
1392
|
* 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.
|
|
1393
|
+
* best-effort seed extraction as {@link runWith} when two-pass contexts are
|
|
1394
|
+
* present. In two-phase runs, each two-pass context's phase-two return value
|
|
1395
|
+
* replaces that context's phase-one contribution for the final parse, so
|
|
1396
|
+
* returning `{}` clears any annotations that context provided during phase 1.
|
|
1371
1397
|
*
|
|
1372
1398
|
* @template TParser The sync parser type.
|
|
1373
1399
|
* @template THelp Return type when help is shown.
|
|
@@ -1381,6 +1407,8 @@ function runWithSyncBody(parser, programName, contexts, args, options) {
|
|
|
1381
1407
|
* {@link runWith} or {@link runWithAsync} for async parsers.
|
|
1382
1408
|
* @throws {TypeError} If two or more contexts share the same
|
|
1383
1409
|
* {@link SourceContext.id}.
|
|
1410
|
+
* @throws {TypeError} If any context omits `phase` or declares an invalid
|
|
1411
|
+
* phase value.
|
|
1384
1412
|
* @throws {Error} If any context returns a Promise or if a context's
|
|
1385
1413
|
* `[Symbol.asyncDispose]` returns a Promise.
|
|
1386
1414
|
* @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
|