@loomcli/core 0.2.0 → 0.4.0
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/application.d.ts +59 -34
- package/dist/application.js +162 -59
- package/dist/chain.d.ts +25 -6
- package/dist/chain.js +99 -15
- package/dist/command.d.ts +146 -42
- package/dist/command.js +642 -78
- package/dist/environment.d.ts +22 -0
- package/dist/environment.js +1 -0
- package/dist/errors.d.ts +31 -59
- package/dist/errors.js +49 -106
- package/dist/extension.d.ts +80 -20
- package/dist/extension.js +115 -30
- package/dist/globals.d.ts +14 -25
- package/dist/globals.js +10 -61
- package/dist/glyphs.generated.d.ts +464 -0
- package/dist/glyphs.generated.js +491 -0
- package/dist/host.js +2 -1
- package/dist/index.d.ts +15 -6
- package/dist/index.js +5 -2
- package/dist/inspect.d.ts +38 -4
- package/dist/inspect.js +69 -6
- package/dist/lanes.d.ts +26 -0
- package/dist/lanes.js +45 -0
- package/dist/output.d.ts +93 -15
- package/dist/output.js +307 -34
- package/dist/plugin.d.ts +32 -16
- package/dist/plugin.js +46 -18
- package/dist/rendering.d.ts +21 -0
- package/dist/rendering.js +72 -0
- package/dist/sequence.d.ts +41 -0
- package/dist/sequence.js +225 -0
- package/dist/style-ansi.d.ts +13 -0
- package/dist/style-ansi.js +306 -0
- package/dist/style-layout.d.ts +29 -0
- package/dist/style-layout.js +228 -0
- package/dist/style-resolve.d.ts +6 -0
- package/dist/style-resolve.js +26 -0
- package/dist/style-state.d.ts +14 -0
- package/dist/style-state.js +179 -0
- package/dist/style-wire.d.ts +31 -0
- package/dist/style-wire.js +201 -0
- package/dist/style.d.ts +86 -0
- package/dist/style.js +201 -0
- package/dist/theme.d.ts +3 -0
- package/dist/theme.js +22 -0
- package/dist/types.d.ts +174 -21
- package/dist/validation.d.ts +8 -1
- package/dist/validation.js +17 -2
- package/dist/view.d.ts +180 -0
- package/dist/view.js +307 -0
- package/package.json +2 -1
package/dist/types.d.ts
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import type { Readable, Writable } from 'node:stream';
|
|
3
3
|
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
4
4
|
import type { ExtensionValue } from './extension.js';
|
|
5
|
+
import type { ResultNode } from './inspect.js';
|
|
6
|
+
import type { RenderingPolicy } from './rendering.js';
|
|
7
|
+
import type { ContextualStyle } from './style.js';
|
|
5
8
|
type LowercaseLetter = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z';
|
|
6
9
|
type ShortAlias = LowercaseLetter | Uppercase<LowercaseLetter>;
|
|
7
10
|
type OptionSpelling = {
|
|
@@ -89,6 +92,7 @@ export interface OutputTerminal extends InputTerminal {
|
|
|
89
92
|
rows: number | undefined;
|
|
90
93
|
}
|
|
91
94
|
export interface Host {
|
|
95
|
+
platform: string;
|
|
92
96
|
argv: string[];
|
|
93
97
|
cwd: string;
|
|
94
98
|
env: Record<string, string | undefined>;
|
|
@@ -102,6 +106,7 @@ export interface Host {
|
|
|
102
106
|
stderr: Writable;
|
|
103
107
|
}
|
|
104
108
|
export interface RunOptions {
|
|
109
|
+
rendering?: RenderingPolicy;
|
|
105
110
|
host?: Partial<Host>;
|
|
106
111
|
/**
|
|
107
112
|
* A caller-owned signal that cancels the run. Core subscribes to it at run entry and honors an
|
|
@@ -143,24 +148,167 @@ export type ValidationContext = {
|
|
|
143
148
|
passthrough: readonly string[];
|
|
144
149
|
supplied: SuppliedInputs;
|
|
145
150
|
};
|
|
151
|
+
/** Immutable authoring and measurement context for one view destination. */
|
|
152
|
+
export interface ViewContext {
|
|
153
|
+
readonly style: ContextualStyle;
|
|
154
|
+
readonly width: (text: string) => number;
|
|
155
|
+
}
|
|
156
|
+
/** A pure synchronous view turns one typed value into the marked text core resolves. */
|
|
157
|
+
export interface View<Data> {
|
|
158
|
+
render: (data: Readonly<Data>, context: ViewContext) => string;
|
|
159
|
+
/** A view has one shape; the row view of Results is the other. */
|
|
160
|
+
row?: never;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* A row view renders a sequence one row at a time.
|
|
164
|
+
* `head` and `tail` open and close the sequence, and each defaults to the empty string.
|
|
165
|
+
* Every function is pure and synchronous and owns the newlines in the text it returns.
|
|
166
|
+
*/
|
|
167
|
+
export interface RowView<Row> {
|
|
168
|
+
row: (row: Readonly<Row>, index: number, context: ViewContext) => string;
|
|
169
|
+
head?: (context: ViewContext) => string;
|
|
170
|
+
tail?: (count: number, context: ViewContext) => string;
|
|
171
|
+
/** A row view has one shape; the whole view of Rendered output is the other. */
|
|
172
|
+
render?: never;
|
|
173
|
+
}
|
|
174
|
+
/** The views record of a value result: every entry renders the whole value. */
|
|
175
|
+
export type ResultViews<Value> = Readonly<Record<string, View<Value>>>;
|
|
176
|
+
/**
|
|
177
|
+
* The views record of a rows result: a whole view over the collected rows, which core buffers the
|
|
178
|
+
* sequence for, or a row view, which core feeds as the rows arrive.
|
|
179
|
+
*/
|
|
180
|
+
export type RowViews<Row> = Readonly<Record<string, View<readonly Row[]> | RowView<Row>>>;
|
|
181
|
+
/**
|
|
182
|
+
* One view a result names, with its data type erased, as the view registry erases a declared
|
|
183
|
+
* view's. The write site reads each function back through the key that resolved it.
|
|
184
|
+
*/
|
|
185
|
+
export type ResultView = View<never> | RowView<never>;
|
|
186
|
+
/**
|
|
187
|
+
* One declared result as the write site reads it: the unit the action emits, the view names it
|
|
188
|
+
* declares in record order, and the key core renders when nothing selects another.
|
|
189
|
+
*/
|
|
190
|
+
export interface DeclaredResult {
|
|
191
|
+
default: string;
|
|
192
|
+
kind: 'value' | 'rows';
|
|
193
|
+
views: ReadonlyMap<string, ResultView>;
|
|
194
|
+
}
|
|
195
|
+
/** Phantom key. It brands the surface a lifecycle hook receives, so a forged value is not one. */
|
|
196
|
+
export declare const attachedCommand: unique symbol;
|
|
197
|
+
/**
|
|
198
|
+
* One Command as `onCommandAttach` receives it: the facts `inspect()` publishes, with their types
|
|
199
|
+
* erased, and the authoring calls a hook may make. Each call returns a new value whose facts hold
|
|
200
|
+
* what the call added, so a hook reads its own earlier calls back. The calls a hook cannot make are
|
|
201
|
+
* absent, because each of them changes what the action was compiled against or the graph's shape.
|
|
202
|
+
*/
|
|
203
|
+
export interface AttachedCommand {
|
|
204
|
+
readonly [attachedCommand]: true;
|
|
205
|
+
/** The Command's own name, and `null` for the root. */
|
|
206
|
+
readonly name: string | null;
|
|
207
|
+
readonly path: readonly string[];
|
|
208
|
+
readonly hasAction: boolean;
|
|
209
|
+
/** The declared argument names, in declaration order. */
|
|
210
|
+
readonly arguments: readonly string[];
|
|
211
|
+
/** The declared local option names, in declaration order. */
|
|
212
|
+
readonly options: readonly string[];
|
|
213
|
+
readonly result: ResultNode | null;
|
|
214
|
+
/**
|
|
215
|
+
* The extension record `inspect()` would publish for this Command at this hook: the author's
|
|
216
|
+
* layers, then every value an earlier hook or this hook's earlier `extend()` calls added.
|
|
217
|
+
*/
|
|
218
|
+
readonly extensions: Readonly<Record<string, unknown>>;
|
|
219
|
+
argument(name: string, config: ArgumentConfig): AttachedCommand;
|
|
220
|
+
option(name: string, config: OptionConfig): AttachedCommand;
|
|
221
|
+
views(replacements: Readonly<Record<string, ResultView>>, options?: {
|
|
222
|
+
default?: string;
|
|
223
|
+
}): AttachedCommand;
|
|
224
|
+
extend(...values: readonly ExtensionValue<'command'>[]): AttachedCommand;
|
|
225
|
+
}
|
|
146
226
|
/**
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* holds no output handle. A throw or a non-string return is a renderer failure.
|
|
227
|
+
* The lifecycle hook core calls once per Command at graph build, in installation order, each
|
|
228
|
+
* receiving what the previous plugin's hook returned.
|
|
150
229
|
*/
|
|
151
|
-
export
|
|
152
|
-
|
|
230
|
+
export type CommandAttachHook = (command: AttachedCommand) => AttachedCommand;
|
|
231
|
+
/**
|
|
232
|
+
* What `out.results` accepts for one declared result. The declaration rides in the declared types
|
|
233
|
+
* as a closed discriminant, so a Command that declares none carries the neutral `unknown` and its
|
|
234
|
+
* `out.results` takes `never`. The parameter is never a union, so distribution reaches one member.
|
|
235
|
+
*/
|
|
236
|
+
export type ResultInput<Result> = Result extends {
|
|
237
|
+
kind: 'value';
|
|
238
|
+
value: infer Value;
|
|
239
|
+
} ? Value : Result extends {
|
|
240
|
+
kind: 'rows';
|
|
241
|
+
row: infer Row;
|
|
242
|
+
} ? Iterable<Row> | AsyncIterable<Row> : never;
|
|
243
|
+
/**
|
|
244
|
+
* The result an `out` carries where the declaration is not in hand. Its `results` accepts any
|
|
245
|
+
* value, because the authoring call already checked what the Command declares, and the channel
|
|
246
|
+
* core builds for an action carries that declaration at run time.
|
|
247
|
+
*/
|
|
248
|
+
export interface OpenResult {
|
|
249
|
+
kind: 'value';
|
|
250
|
+
value: unknown;
|
|
153
251
|
}
|
|
154
|
-
|
|
252
|
+
/** The record a `views()` call takes, which is the shape the carried result names. */
|
|
253
|
+
export type ResultViewsOf<Result> = Result extends {
|
|
254
|
+
kind: 'value';
|
|
255
|
+
value: infer Value;
|
|
256
|
+
} ? ResultViews<Value> : Result extends {
|
|
257
|
+
kind: 'rows';
|
|
258
|
+
row: infer Row;
|
|
259
|
+
} ? RowViews<Row> : never;
|
|
260
|
+
export interface Out<Result = unknown> {
|
|
155
261
|
print(message: string): Promise<void>;
|
|
156
262
|
info(message: string): Promise<void>;
|
|
157
263
|
success(message: string): Promise<void>;
|
|
158
264
|
warn(message: string): Promise<void>;
|
|
159
265
|
error(message: string): Promise<void>;
|
|
160
|
-
/** The neutral
|
|
161
|
-
render<Data>(data: Data,
|
|
266
|
+
/** The neutral view call: a rendered value has no purpose and no destination. */
|
|
267
|
+
render<Data>(data: Data, view: View<Data>): Promise<void>;
|
|
268
|
+
/** The same call over a sequence: core writes each row's text as the source yields it. */
|
|
269
|
+
render<Row>(rows: Iterable<Row> | AsyncIterable<Row>, view: RowView<Row>): Promise<void>;
|
|
270
|
+
/**
|
|
271
|
+
* The Command's own result, emitted once. Property syntax keeps the parameter contravariant, so
|
|
272
|
+
* a neutral `Out` never stands in for one that carries a declaration.
|
|
273
|
+
*/
|
|
274
|
+
results: (value: ResultInput<Result>) => Promise<void>;
|
|
162
275
|
fatal(message: string): never;
|
|
163
276
|
}
|
|
277
|
+
/**
|
|
278
|
+
* What the action's channel answers to: the routed path its diagnostics name, the result the
|
|
279
|
+
* routed Command declared, and the view this run selected. The declaration decides the
|
|
280
|
+
* destinations the channel carries, so the redirect is read from the graph once and never from the
|
|
281
|
+
* view a run selected; `view` decides the rendering alone, and is `null` when nothing selected one
|
|
282
|
+
* and the declaration's default stands.
|
|
283
|
+
*/
|
|
284
|
+
export interface ResultBinding {
|
|
285
|
+
path: readonly string[];
|
|
286
|
+
result: DeclaredResult | undefined;
|
|
287
|
+
view: string | null;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* The routed Command's invocation after parsing and validation, which every middleware reads. The
|
|
291
|
+
* values are what the action receives, the output of each declaration's schema, for that Command's
|
|
292
|
+
* own arguments and local options; global and plugin option values are not here. The records are
|
|
293
|
+
* untyped and frozen, because a middleware runs ahead of every action and the graph carries no
|
|
294
|
+
* type for a value.
|
|
295
|
+
*/
|
|
296
|
+
export interface Request {
|
|
297
|
+
readonly args: Readonly<Record<string, unknown>>;
|
|
298
|
+
readonly options: Readonly<Record<string, unknown>>;
|
|
299
|
+
readonly passthrough: readonly string[];
|
|
300
|
+
}
|
|
301
|
+
/** The channel one action receives, with the emission the results lane holds it to. */
|
|
302
|
+
export interface ActionChannel {
|
|
303
|
+
out: Out<OpenResult>;
|
|
304
|
+
/** Whether the action emitted its result, which the missing rule reads after it returned. */
|
|
305
|
+
emitted: () => boolean;
|
|
306
|
+
/**
|
|
307
|
+
* Stops every sequence this channel still has pending. The action's own failure stays primary,
|
|
308
|
+
* so a sequence it never awaited is stopped rather than drained.
|
|
309
|
+
*/
|
|
310
|
+
stop: () => void;
|
|
311
|
+
}
|
|
164
312
|
export type StringOption = OptionSpelling & Presence & Multiplicity & Omission & Described & Listed & OptionExtensions & {
|
|
165
313
|
type: 'string';
|
|
166
314
|
polarity?: never;
|
|
@@ -251,8 +399,9 @@ export type BooleanOption = (OptionSpelling & Described & Listed & OptionExtensi
|
|
|
251
399
|
export type OptionConfig = StringOption | BooleanOption;
|
|
252
400
|
/**
|
|
253
401
|
* The parsing part of a string option config, which is all a plugin option declares. A plugin
|
|
254
|
-
* option carries no schema and no presence rule, because
|
|
255
|
-
* validation context every schema is promised cannot exist. Its middleware interprets
|
|
402
|
+
* option carries no schema and no presence rule, because the pre-scan consumes it ahead of routing,
|
|
403
|
+
* where the validation context every schema is promised cannot exist. Its middleware interprets
|
|
404
|
+
* the value.
|
|
256
405
|
* A Boolean plugin option is an ordinary `BooleanOption`, which already declares none of them.
|
|
257
406
|
*/
|
|
258
407
|
export type PluginStringOption = OptionSpelling & Multiplicity & Described & Listed & OptionExtensions & {
|
|
@@ -273,36 +422,40 @@ export type OptionValue<Config extends OptionConfig> = Config extends StringOpti
|
|
|
273
422
|
} | {
|
|
274
423
|
validateOmitted: true;
|
|
275
424
|
} ? never : undefined) : boolean;
|
|
276
|
-
export interface ActionContext<Args, Options = {}> {
|
|
425
|
+
export interface ActionContext<Args, Options = {}, Result = unknown> {
|
|
426
|
+
readonly style: ContextualStyle;
|
|
277
427
|
args: Args;
|
|
278
428
|
options: Options;
|
|
279
429
|
passthrough: string[];
|
|
280
|
-
out: Out
|
|
430
|
+
out: Out<Result>;
|
|
281
431
|
host: Host;
|
|
282
432
|
/** The run's cancellation signal, which a caller or an installed signals owner aborts. */
|
|
283
433
|
signal: AbortSignal;
|
|
284
434
|
}
|
|
285
|
-
export type Action<Args, Options = {}> = (context: ActionContext<Args, Options>) => unknown;
|
|
435
|
+
export type Action<Args, Options = {}, Result = unknown> = (context: ActionContext<Args, Options, Result>) => unknown;
|
|
286
436
|
/** Phantom key. It keeps the inferred declaration types exact and holds no runtime value. */
|
|
287
437
|
export declare const declaredTypes: unique symbol;
|
|
288
438
|
/**
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
* a return position, which makes them invariant: a child's globals must be the parent's own type,
|
|
292
|
-
* not a subset and not a superset, because one table serves every Command in the graph.
|
|
439
|
+
* Arguments and local options describe the action's own inputs. Globals are a requirement on the
|
|
440
|
+
* Receiving Application: a library that needs none can attach wherever its local keys are disjoint.
|
|
293
441
|
*/
|
|
294
|
-
export interface DeclaredTypes<Args, Options, Globals> {
|
|
442
|
+
export interface DeclaredTypes<Args, Options, Globals, Result = unknown> {
|
|
295
443
|
args: Args;
|
|
296
|
-
globals: (value: Globals) =>
|
|
444
|
+
globals: (value: Globals) => void;
|
|
297
445
|
options: Options;
|
|
446
|
+
/**
|
|
447
|
+
* The result the Command declares, or `unknown` where it declares none. A plain field keeps it
|
|
448
|
+
* covariant, so a child that carries one still satisfies a neutral `Command` annotation.
|
|
449
|
+
*/
|
|
450
|
+
result: Result;
|
|
298
451
|
}
|
|
299
452
|
/**
|
|
300
453
|
* The handler one declaration accepts. It reads the phantom types, not the `action()` call, so it
|
|
301
454
|
* holds on a fresh declaration, on a partly declared one, and on one that registered its action.
|
|
302
455
|
*/
|
|
303
456
|
export type ActionHandler<Declaration> = Declaration extends {
|
|
304
|
-
[declaredTypes]: DeclaredTypes<infer Args, infer Options, infer Globals>;
|
|
305
|
-
} ? Action<Args, Globals & Options> : never;
|
|
457
|
+
[declaredTypes]: DeclaredTypes<infer Args, infer Options, infer Globals, infer Result>;
|
|
458
|
+
} ? Action<Args, Globals & Options, Result> : never;
|
|
306
459
|
/** The args object an extracted handler receives for this declaration. */
|
|
307
460
|
export type ActionArgs<Declaration> = ActionArgument<Declaration> extends {
|
|
308
461
|
args: infer Args;
|
package/dist/validation.d.ts
CHANGED
|
@@ -36,6 +36,8 @@ export interface Invocation {
|
|
|
36
36
|
host: Host;
|
|
37
37
|
inputs: ScopedInputs;
|
|
38
38
|
passthrough: readonly string[];
|
|
39
|
+
/** The run's cancellation signal, which stops this phase between two schema calls. */
|
|
40
|
+
signal: AbortSignal;
|
|
39
41
|
supplied: SuppliedValues;
|
|
40
42
|
}
|
|
41
43
|
/**
|
|
@@ -50,6 +52,11 @@ declare class ValidatedInputs {
|
|
|
50
52
|
argument<Name extends string, Config extends ArgumentConfig>(input: ArgumentInput<Name, Config>): Record<Name, ArgumentValue<Config>>;
|
|
51
53
|
/** The one-key record this option contributes to `options`, typed by its own config. */
|
|
52
54
|
option<Name extends string, Config extends OptionConfig>(input: OptionInput<Name, Config>): Record<Name, OptionValue<Config>>;
|
|
55
|
+
/**
|
|
56
|
+
* One validated value read without its declared type, which is what an untyped reader such as a
|
|
57
|
+
* middleware's request receives. The declared types stay with the two readers above.
|
|
58
|
+
*/
|
|
59
|
+
read(input: InputDeclaration): unknown;
|
|
53
60
|
}
|
|
54
61
|
export type { ValidatedInputs };
|
|
55
62
|
/**
|
|
@@ -65,7 +72,7 @@ export declare function captureConfig<Config extends ArgumentConfig | OptionConf
|
|
|
65
72
|
export declare function validatesOmission(input: InputDeclaration): boolean;
|
|
66
73
|
/**
|
|
67
74
|
* The dotted path an issue names inside a value, or `undefined` when the issue names the value
|
|
68
|
-
* itself. Core's default text and an application's own
|
|
75
|
+
* itself. Core's default text and an application's own view read a position through this one
|
|
69
76
|
* helper, so a rejected item reads alike wherever its diagnostic is written.
|
|
70
77
|
*/
|
|
71
78
|
export declare function issuePath(issue: StandardSchemaV1.Issue): string | undefined;
|
package/dist/validation.js
CHANGED
|
@@ -29,6 +29,13 @@ class ValidatedInputs {
|
|
|
29
29
|
option(input) {
|
|
30
30
|
return this.#field(input);
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* One validated value read without its declared type, which is what an untyped reader such as a
|
|
34
|
+
* middleware's request receives. The declared types stay with the two readers above.
|
|
35
|
+
*/
|
|
36
|
+
read(input) {
|
|
37
|
+
return this.#values.get(input);
|
|
38
|
+
}
|
|
32
39
|
#field(input) {
|
|
33
40
|
// Last resort: no typed path exists. The map stores every validated value as `unknown`.
|
|
34
41
|
// An object literal with a generic computed key does not type as `Record<Name, _>` either.
|
|
@@ -227,7 +234,7 @@ async function validate(input, raw, context) {
|
|
|
227
234
|
}
|
|
228
235
|
/**
|
|
229
236
|
* The dotted path an issue names inside a value, or `undefined` when the issue names the value
|
|
230
|
-
* itself. Core's default text and an application's own
|
|
237
|
+
* itself. Core's default text and an application's own view read a position through this one
|
|
231
238
|
* helper, so a rejected item reads alike wherever its diagnostic is written.
|
|
232
239
|
*/
|
|
233
240
|
export function issuePath(issue) {
|
|
@@ -239,7 +246,7 @@ export function issuePath(issue) {
|
|
|
239
246
|
/**
|
|
240
247
|
* The issues one rejection reports. A schema that returned none still rejected the value, so the
|
|
241
248
|
* placeholder stands in for its silence. Reporting takes this list once: the reported problem
|
|
242
|
-
* carries it and the default text is derived from it, so a
|
|
249
|
+
* carries it and the default text is derived from it, so a view and core read the same issues.
|
|
243
250
|
*/
|
|
244
251
|
function reported(issues) {
|
|
245
252
|
return issues.length === 0
|
|
@@ -366,6 +373,14 @@ export async function validateValues(invocation) {
|
|
|
366
373
|
lines.push(...messages(suppliedName(entry.input, spelling), issues));
|
|
367
374
|
};
|
|
368
375
|
for (const entry of declarations) {
|
|
376
|
+
if (invocation.signal.aborted) {
|
|
377
|
+
/**
|
|
378
|
+
* A cancelled run starts no further schema call. The one already in flight was awaited
|
|
379
|
+
* above, and whatever this phase collected is never raised, because the run resolves its
|
|
380
|
+
* cancellation code instead.
|
|
381
|
+
*/
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
369
384
|
const { input } = entry;
|
|
370
385
|
if (input.kind === 'option' && input.config.type === 'boolean') {
|
|
371
386
|
values.set(input, booleanValue(supplied.options, input.name, input.config));
|
package/dist/view.d.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type { LoomError } from './errors.js';
|
|
2
|
+
import type { RowView, View, ViewContext } from './types.js';
|
|
3
|
+
/** Phantom key. It brands a declared view and holds no runtime value. */
|
|
4
|
+
declare const declaredView: unique symbol;
|
|
5
|
+
/** Phantom key. It makes a declared view invariant in its data type and holds no runtime value. */
|
|
6
|
+
declare const invariant: unique symbol;
|
|
7
|
+
/** Phantom key. It brands a view override and holds no runtime value. */
|
|
8
|
+
declare const viewOverride: unique symbol;
|
|
9
|
+
/** The brand one declared view carries, as an extension value carries its own. */
|
|
10
|
+
interface DeclaredViewBrand {
|
|
11
|
+
readonly [declaredView]: true;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* One declared view: the identity a diagnostic names it by, and the default view function an
|
|
15
|
+
* override replaces. The witness makes `Data` invariant, so a declared view is never reassigned as
|
|
16
|
+
* a declared view of another data type and a replacement that requires data the key does not carry
|
|
17
|
+
* is a compile error.
|
|
18
|
+
*/
|
|
19
|
+
interface DeclaredView<Data> extends View<Data>, DeclaredViewBrand {
|
|
20
|
+
readonly identity: string;
|
|
21
|
+
readonly [invariant]: (data: Data) => Data;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* One declared row view: the same brand and witness a declared view carries, over the row type.
|
|
25
|
+
* `override` keyed by it takes a row view, under the rules a declared view follows unchanged.
|
|
26
|
+
*/
|
|
27
|
+
interface DeclaredRowView<Row> extends RowView<Row>, DeclaredViewBrand {
|
|
28
|
+
readonly identity: string;
|
|
29
|
+
readonly [invariant]: (row: Row) => Row;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The supertype a contribution list uses. It keeps the identity, the brand, and a view function of
|
|
33
|
+
* either shape over `never`, and drops the invariance witness, because a list cannot carry one type
|
|
34
|
+
* parameter per element and an invariant type has no common supertype across data types.
|
|
35
|
+
*/
|
|
36
|
+
type AnyDeclaredView = (View<never> | RowView<never>) & DeclaredViewBrand & {
|
|
37
|
+
readonly identity: string;
|
|
38
|
+
};
|
|
39
|
+
/** A failure class as an override key, so `UsageError` and an application's subclass both fit. */
|
|
40
|
+
type FailureClass<Failure extends LoomError> = abstract new (...args: never[]) => Failure;
|
|
41
|
+
/**
|
|
42
|
+
* One stored view function, with the data type erased. A registry holds one entry per key and
|
|
43
|
+
* cannot carry a type parameter per entry, so `override(key, replacement)` is where the
|
|
44
|
+
* replacement is typed against the key it answers.
|
|
45
|
+
*/
|
|
46
|
+
type ViewFunction = (data: never, context: ViewContext) => unknown;
|
|
47
|
+
/** One stored row function, with the row type erased for the same reason. */
|
|
48
|
+
type RowFunction = (row: never, index: number, context: ViewContext) => unknown;
|
|
49
|
+
/** One stored function that opens a sequence and reads the context alone. */
|
|
50
|
+
type HeadFunction = (context: ViewContext) => unknown;
|
|
51
|
+
/** One stored function that closes a sequence and reads its completed row count. */
|
|
52
|
+
type TailFunction = (count: number, context: ViewContext) => unknown;
|
|
53
|
+
/** One stored view value: the functions of whichever shape its author supplied. */
|
|
54
|
+
interface StoredView {
|
|
55
|
+
render?: ViewFunction | undefined;
|
|
56
|
+
row?: RowFunction | undefined;
|
|
57
|
+
head?: HeadFunction | undefined;
|
|
58
|
+
tail?: TailFunction | undefined;
|
|
59
|
+
}
|
|
60
|
+
/** Which of the two exclusive view functions one value carries, or that it carries the wrong set. */
|
|
61
|
+
type ViewShape = 'both' | 'neither' | 'render' | 'row';
|
|
62
|
+
/**
|
|
63
|
+
* The shape one value names. The argument is read defensively, because every call that takes a view
|
|
64
|
+
* is reachable from JavaScript and `null` is one such value.
|
|
65
|
+
*/
|
|
66
|
+
declare function shapeOf(value: {
|
|
67
|
+
render?: unknown;
|
|
68
|
+
row?: unknown;
|
|
69
|
+
} | null | undefined): ViewShape;
|
|
70
|
+
/**
|
|
71
|
+
* One declared view of either shape: an identity and its default functions. The data type is
|
|
72
|
+
* inferred from the function's first parameter when that parameter is an object type or a
|
|
73
|
+
* `readonly` array, and is stated for a primitive or a union, because inference runs through
|
|
74
|
+
* `Readonly<Data>`. The two shapes are told apart by the function present, and a definition that
|
|
75
|
+
* carries both or neither is rejected here rather than guessed at.
|
|
76
|
+
*/
|
|
77
|
+
declare function view<Data>(identity: string, definition: View<Data>): DeclaredView<Data>;
|
|
78
|
+
declare function view<Row>(identity: string, definition: RowView<Row>): DeclaredRowView<Row>;
|
|
79
|
+
/**
|
|
80
|
+
* What one override replaces: a declared view, a failure class read as its prototype, or a value
|
|
81
|
+
* that is neither, which build reports as the entry fault of the list that holds it.
|
|
82
|
+
*/
|
|
83
|
+
type OverrideKey = {
|
|
84
|
+
kind: 'view';
|
|
85
|
+
view: AnyDeclaredView;
|
|
86
|
+
} | {
|
|
87
|
+
kind: 'failure';
|
|
88
|
+
name: string;
|
|
89
|
+
prototype: object;
|
|
90
|
+
} | {
|
|
91
|
+
kind: 'invalid';
|
|
92
|
+
};
|
|
93
|
+
/** One override: the key it answers and the view value that supersedes the default. */
|
|
94
|
+
interface OverrideRecord {
|
|
95
|
+
key: OverrideKey;
|
|
96
|
+
replacement: StoredView;
|
|
97
|
+
}
|
|
98
|
+
/** The runtime value `override()` returns. Its pair lives in the registry above. */
|
|
99
|
+
declare class OverrideDeclaration {
|
|
100
|
+
readonly [viewOverride]: true;
|
|
101
|
+
constructor(record: OverrideRecord);
|
|
102
|
+
}
|
|
103
|
+
/** An opaque override pairing one key with the view function that replaces its default. */
|
|
104
|
+
type ViewOverride = Pick<OverrideDeclaration, typeof viewOverride>;
|
|
105
|
+
/** What a plugin's own list holds: the views it declares and the overrides it makes. */
|
|
106
|
+
type ViewContribution = AnyDeclaredView | ViewOverride;
|
|
107
|
+
/**
|
|
108
|
+
* One override pairing a key with a replacement view. Under a declared view the replacement is
|
|
109
|
+
* typed from the view's data; under a failure class it is typed from the class's instances, which
|
|
110
|
+
* is the typed path for a class-keyed list, because an array literal cannot carry a different type
|
|
111
|
+
* parameter per element.
|
|
112
|
+
*/
|
|
113
|
+
declare function override<Data>(key: DeclaredView<Data>, replacement: NoInfer<View<Data>>): ViewOverride;
|
|
114
|
+
declare function override<Row>(key: DeclaredRowView<Row>, replacement: NoInfer<RowView<Row>>): ViewOverride;
|
|
115
|
+
declare function override<Failure extends LoomError>(key: FailureClass<Failure> & {
|
|
116
|
+
readonly [declaredView]?: never;
|
|
117
|
+
}, replacement: NoInfer<View<Failure>>): ViewOverride;
|
|
118
|
+
/** One contributor's overrides, read once per build and consulted in contributor order. */
|
|
119
|
+
interface ViewContributions {
|
|
120
|
+
failures: Map<unknown, StoredView>;
|
|
121
|
+
views: Map<AnyDeclaredView, StoredView>;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Every contributor in resolution order: the application's overrides, then each installed plugin's
|
|
125
|
+
* in installation order. The declaring view's own default answers when no contributor does.
|
|
126
|
+
*/
|
|
127
|
+
type ViewRegistry = readonly ViewContributions[];
|
|
128
|
+
/** The identities one build has met, so a second object under one identity is visible. */
|
|
129
|
+
type ViewIdentities = Map<string, AnyDeclaredView>;
|
|
130
|
+
/** How one contributor's diagnostics name it, and whether its list may declare a view. */
|
|
131
|
+
interface ViewSubject {
|
|
132
|
+
/** A plugin declares views beside its overrides; an application overrides alone. */
|
|
133
|
+
declares: boolean;
|
|
134
|
+
sentence: string;
|
|
135
|
+
}
|
|
136
|
+
/** The identity register one build starts from, holding the views core itself declares. */
|
|
137
|
+
declare function viewIdentities(declared: readonly AnyDeclaredView[]): ViewIdentities;
|
|
138
|
+
/**
|
|
139
|
+
* One contributor's own overrides, with the identity of every declared view it lists or names as a
|
|
140
|
+
* key registered. Listing a declared view is what puts its identity on the graph; an application
|
|
141
|
+
* lists overrides alone, so a declaration in its list is the same fault as any other value.
|
|
142
|
+
*/
|
|
143
|
+
declare function buildViews(subject: ViewSubject, declared: unknown, identities: ViewIdentities): ViewContributions;
|
|
144
|
+
/**
|
|
145
|
+
* The view function one declared view resolves to: the first contributor that overrides it, then
|
|
146
|
+
* its own default. A bare view is never overridden, so it resolves to its own function.
|
|
147
|
+
*/
|
|
148
|
+
declare function resolveView<Data>(registry: ViewRegistry, value: View<Data>): (data: Data, context: ViewContext) => unknown;
|
|
149
|
+
/** The three functions one sequence writes through: `head`, `row` per item, and `tail`. */
|
|
150
|
+
interface ResolvedRowView<Row> {
|
|
151
|
+
row: (row: Row, index: number, context: ViewContext) => unknown;
|
|
152
|
+
head: HeadFunction | undefined;
|
|
153
|
+
tail: TailFunction | undefined;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The row functions one row view resolves to, by the walk `resolveView` defines. A replacement
|
|
157
|
+
* supplies the whole shape, so an override that omits `head` drops the default's `head` with it.
|
|
158
|
+
*/
|
|
159
|
+
declare function resolveRowView<Row>(registry: ViewRegistry, value: RowView<Row>): ResolvedRowView<Row>;
|
|
160
|
+
/**
|
|
161
|
+
* The report of one failure: the text core writes, and whether a view produced it. An unrendered
|
|
162
|
+
* report carries core's own text, which the plain fallback path writes beside the diagnostic
|
|
163
|
+
* naming the view that could not answer.
|
|
164
|
+
*/
|
|
165
|
+
type FailureReport = {
|
|
166
|
+
kind: 'rendered';
|
|
167
|
+
text: string;
|
|
168
|
+
} | {
|
|
169
|
+
kind: 'unrendered';
|
|
170
|
+
text: string;
|
|
171
|
+
reason: string;
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* The text core writes for one failure. Resolution walks the registry as `resolveFailure` defines
|
|
175
|
+
* it and falls to core's own default text, which escapes the raw facts it interpolates. A
|
|
176
|
+
* `FatalError` keeps the authored marked message it was given.
|
|
177
|
+
*/
|
|
178
|
+
declare function describeFailure(registry: ViewRegistry, failure: LoomError, context?: ViewContext): FailureReport;
|
|
179
|
+
export type { AnyDeclaredView, DeclaredRowView, DeclaredView, DeclaredViewBrand, FailureClass, FailureReport, ResolvedRowView, ViewContribution, ViewContributions, ViewIdentities, ViewOverride, ViewRegistry, ViewShape, };
|
|
180
|
+
export { buildViews, describeFailure, override, resolveRowView, resolveView, shapeOf, view, viewIdentities, };
|