@loomcli/core 0.1.1 → 0.3.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.
Files changed (57) hide show
  1. package/dist/application.d.ts +73 -28
  2. package/dist/application.js +334 -99
  3. package/dist/chain.d.ts +68 -0
  4. package/dist/chain.js +372 -0
  5. package/dist/command.d.ts +201 -46
  6. package/dist/command.js +713 -57
  7. package/dist/environment.d.ts +22 -0
  8. package/dist/environment.js +1 -0
  9. package/dist/errors.d.ts +31 -51
  10. package/dist/errors.js +58 -90
  11. package/dist/extension.d.ts +99 -0
  12. package/dist/extension.js +330 -0
  13. package/dist/facts.d.ts +39 -0
  14. package/dist/facts.js +95 -0
  15. package/dist/globals.d.ts +49 -28
  16. package/dist/globals.js +104 -58
  17. package/dist/glyphs.generated.d.ts +464 -0
  18. package/dist/glyphs.generated.js +491 -0
  19. package/dist/host.js +2 -1
  20. package/dist/index.d.ts +21 -6
  21. package/dist/index.js +7 -2
  22. package/dist/inspect.d.ts +69 -12
  23. package/dist/inspect.js +83 -26
  24. package/dist/lanes.d.ts +26 -0
  25. package/dist/lanes.js +45 -0
  26. package/dist/options.d.ts +7 -0
  27. package/dist/options.js +9 -0
  28. package/dist/output.d.ts +93 -15
  29. package/dist/output.js +307 -34
  30. package/dist/plugin.d.ts +132 -0
  31. package/dist/plugin.js +278 -0
  32. package/dist/rendering.d.ts +21 -0
  33. package/dist/rendering.js +72 -0
  34. package/dist/sequence.d.ts +41 -0
  35. package/dist/sequence.js +225 -0
  36. package/dist/signals.d.ts +52 -0
  37. package/dist/signals.js +85 -0
  38. package/dist/style-ansi.d.ts +13 -0
  39. package/dist/style-ansi.js +306 -0
  40. package/dist/style-layout.d.ts +29 -0
  41. package/dist/style-layout.js +228 -0
  42. package/dist/style-resolve.d.ts +6 -0
  43. package/dist/style-resolve.js +26 -0
  44. package/dist/style-state.d.ts +14 -0
  45. package/dist/style-state.js +179 -0
  46. package/dist/style-wire.d.ts +31 -0
  47. package/dist/style-wire.js +201 -0
  48. package/dist/style.d.ts +86 -0
  49. package/dist/style.js +201 -0
  50. package/dist/theme.d.ts +3 -0
  51. package/dist/theme.js +22 -0
  52. package/dist/types.d.ts +222 -26
  53. package/dist/validation.d.ts +12 -3
  54. package/dist/validation.js +34 -17
  55. package/dist/view.d.ts +180 -0
  56. package/dist/view.js +307 -0
  57. package/package.json +2 -1
@@ -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,16 +72,18 @@ 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 renderer read a position through this one
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;
72
79
  /**
73
80
  * Every declaration rule that reads the declaration alone. It is synchronous, so `inspect()` and
74
81
  * `run()` apply exactly the same rules, and only validating a default through its schema, which
75
- * can be asynchronous, is left to `run()`.
82
+ * can be asynchronous, is left to `run()`. A contributor that declares under its own name, such as
83
+ * a plugin, supplies the subject its diagnostics read with; every other caller is named by the
84
+ * declaration itself.
76
85
  */
77
- export declare function checkDeclarations(inputs: readonly InputDeclaration[]): void;
86
+ export declare function checkDeclarations(inputs: readonly InputDeclaration[], named?: string): void;
78
87
  /**
79
88
  * Every declared default, validated before any token is read. The host is captured by then, so a
80
89
  * default's schema reads the same Host its action will, under the `default` phase.
@@ -1,5 +1,6 @@
1
1
  import { schemaOptions } from './context.js';
2
2
  import { DeclarationError, InputError } from './errors.js';
3
+ import { booleanValue } from './options.js';
3
4
  /** Every declaration in validation order: the globals first, then the reading Command's own. */
4
5
  function scoped(inputs) {
5
6
  return [
@@ -28,6 +29,13 @@ class ValidatedInputs {
28
29
  option(input) {
29
30
  return this.#field(input);
30
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
+ }
31
39
  #field(input) {
32
40
  // Last resort: no typed path exists. The map stores every validated value as `unknown`.
33
41
  // An object literal with a generic computed key does not type as `Record<Name, _>` either.
@@ -117,9 +125,8 @@ function holdsRawDefault(input) {
117
125
  * `validateOmitted: true` is the one way an omitted scalar reaches its schema, so every other rule
118
126
  * that already decides absence rejects it, and the flag needs a schema to receive the omission.
119
127
  */
120
- function checkOmissionValidation(input) {
128
+ function checkOmissionValidation(input, subject) {
121
129
  const { config } = input;
122
- const subject = declaredName(input);
123
130
  if (config.required) {
124
131
  throw new DeclarationError(`${subject} is required and declares validateOmitted. Remove validateOmitted or make the input optional.`);
125
132
  }
@@ -133,34 +140,34 @@ function checkOmissionValidation(input) {
133
140
  throw new DeclarationError(`${subject} declares validateOmitted without a schema. Add validate or remove validateOmitted.`);
134
141
  }
135
142
  }
136
- function checkDeclaration(input) {
143
+ function checkDeclaration(input, subject) {
137
144
  const { config } = input;
138
145
  if (input.kind === 'option' && input.config.type === 'boolean') {
139
146
  if ('validate' in config ||
140
147
  'default' in config ||
141
148
  'required' in config ||
142
149
  'validateOmitted' in config) {
143
- throw new DeclarationError(`${declaredName(input)} is Boolean. Remove validate, default, required, and validateOmitted; use polarity to control its absent value.`);
150
+ throw new DeclarationError(`${subject} is Boolean. Remove validate, default, required, and validateOmitted; use polarity to control its absent value.`);
144
151
  }
145
152
  return;
146
153
  }
147
154
  if (config.required !== undefined && typeof config.required !== 'boolean') {
148
- throw new DeclarationError(`${declaredName(input)} required must be Boolean. Use true or false.`);
155
+ throw new DeclarationError(`${subject} required must be Boolean. Use true or false.`);
149
156
  }
150
157
  if (input.kind === 'argument' &&
151
158
  input.config.variadic !== undefined &&
152
159
  typeof input.config.variadic !== 'boolean') {
153
- throw new DeclarationError(`${declaredName(input)} variadic must be Boolean. Use true or false.`);
160
+ throw new DeclarationError(`${subject} variadic must be Boolean. Use true or false.`);
154
161
  }
155
162
  // The test reads presence, not truth, so a declared `undefined` is a declaration to reject.
156
163
  if ('validateOmitted' in config && typeof config.validateOmitted !== 'boolean') {
157
- throw new DeclarationError(`${declaredName(input)} validateOmitted must be Boolean. Use true or false.`);
164
+ throw new DeclarationError(`${subject} validateOmitted must be Boolean. Use true or false.`);
158
165
  }
159
166
  if (config.required && Object.hasOwn(config, 'default')) {
160
- throw new DeclarationError(`${declaredName(input)} is required and declares a default. Remove the default or make the input optional.`);
167
+ throw new DeclarationError(`${subject} is required and declares a default. Remove the default or make the input optional.`);
161
168
  }
162
169
  if (validatesOmission(input)) {
163
- checkOmissionValidation(input);
170
+ checkOmissionValidation(input, subject);
164
171
  }
165
172
  const schema = config.validate;
166
173
  if (schema !== undefined &&
@@ -170,7 +177,7 @@ function checkDeclaration(input) {
170
177
  schema['~standard'].version !== 1 ||
171
178
  typeof schema['~standard'].vendor !== 'string' ||
172
179
  typeof schema['~standard'].validate !== 'function')) {
173
- throw new DeclarationError(`${declaredName(input)} validate must be a Standard Schema v1 object. Supply a compatible schema.`);
180
+ throw new DeclarationError(`${subject} validate must be a Standard Schema v1 object. Supply a compatible schema.`);
174
181
  }
175
182
  }
176
183
  function readIssue(issue) {
@@ -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 renderer read a position through this one
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 renderer and core read the same issues.
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
@@ -259,15 +266,17 @@ function hasDefault(input) {
259
266
  /**
260
267
  * Every declaration rule that reads the declaration alone. It is synchronous, so `inspect()` and
261
268
  * `run()` apply exactly the same rules, and only validating a default through its schema, which
262
- * can be asynchronous, is left to `run()`.
269
+ * can be asynchronous, is left to `run()`. A contributor that declares under its own name, such as
270
+ * a plugin, supplies the subject its diagnostics read with; every other caller is named by the
271
+ * declaration itself.
263
272
  */
264
- export function checkDeclarations(inputs) {
273
+ export function checkDeclarations(inputs, named) {
265
274
  for (const input of inputs) {
266
- checkDeclaration(input);
275
+ checkDeclaration(input, named ?? declaredName(input));
267
276
  }
268
277
  for (const input of inputs.filter((entry) => hasDefault(entry))) {
269
278
  if (input.config.validate === undefined && !holdsRawDefault(input)) {
270
- const subject = declaredName(input);
279
+ const subject = named ?? declaredName(input);
271
280
  throw new DeclarationError(collects(input)
272
281
  ? `${subject} default must be an array of strings without a schema. Supply a string array default.`
273
282
  : `${subject} default must be a string without a schema. Supply a string default.`);
@@ -364,9 +373,17 @@ export async function validateValues(invocation) {
364
373
  lines.push(...messages(suppliedName(entry.input, spelling), issues));
365
374
  };
366
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
+ }
367
384
  const { input } = entry;
368
385
  if (input.kind === 'option' && input.config.type === 'boolean') {
369
- values.set(input, supplied.options.booleans.get(input.name) ?? input.config.polarity === 'negative');
386
+ values.set(input, booleanValue(supplied.options, input.name, input.config));
370
387
  }
371
388
  else {
372
389
  const collected = collects(input);
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, };
package/dist/view.js ADDED
@@ -0,0 +1,307 @@
1
+ import { DeclarationError, defaultText, FatalError, notTextReason, reasonOf } from './errors.js';
2
+ import { escapeText } from './style.js';
3
+ /** Authored declarations register here, so a hand-built object with an identity is a bare view. */
4
+ const declarations = new WeakMap();
5
+ /** The runtime value `view()` returns for a whole view. Its identity and function are public. */
6
+ class ViewDeclaration {
7
+ identity;
8
+ render;
9
+ constructor(identity, definition) {
10
+ this.identity = identity;
11
+ this.render = definition.render;
12
+ declarations.set(this, this);
13
+ Object.freeze(this);
14
+ }
15
+ }
16
+ /** The runtime value `view()` returns for a row view, which carries its three functions. */
17
+ class RowViewDeclaration {
18
+ identity;
19
+ row;
20
+ head;
21
+ tail;
22
+ constructor(identity, definition) {
23
+ this.identity = identity;
24
+ this.row = definition.row;
25
+ if (definition.head) {
26
+ this.head = definition.head;
27
+ }
28
+ if (definition.tail) {
29
+ this.tail = definition.tail;
30
+ }
31
+ declarations.set(this, this);
32
+ Object.freeze(this);
33
+ }
34
+ }
35
+ /**
36
+ * The shape one value names. The argument is read defensively, because every call that takes a view
37
+ * is reachable from JavaScript and `null` is one such value.
38
+ */
39
+ function shapeOf(value) {
40
+ const row = typeof value?.row === 'function';
41
+ const render = typeof value?.render === 'function';
42
+ if (row && render) {
43
+ return 'both';
44
+ }
45
+ if (row) {
46
+ return 'row';
47
+ }
48
+ return render ? 'render' : 'neither';
49
+ }
50
+ function view(identity, definition) {
51
+ const shape = shapeOf(definition);
52
+ if (shape === 'both') {
53
+ throw new DeclarationError(`View "${identity}" carries render and row. Supply one of the two.`);
54
+ }
55
+ if (shape === 'neither') {
56
+ throw new DeclarationError(`View "${identity}" carries neither render nor row. Supply a view with render or a row view with row.`);
57
+ }
58
+ return typeof definition.row === 'function'
59
+ ? new RowViewDeclaration(identity, definition)
60
+ : new ViewDeclaration(identity, definition);
61
+ }
62
+ /** Authored overrides register here, so the public type publishes nothing to reach. */
63
+ const overrides = new WeakMap();
64
+ /** The runtime value `override()` returns. Its pair lives in the registry above. */
65
+ class OverrideDeclaration {
66
+ constructor(record) {
67
+ overrides.set(this, record);
68
+ Object.freeze(this);
69
+ }
70
+ }
71
+ function override(key, replacement) {
72
+ const stored = {
73
+ head: replacement.head,
74
+ render: replacement.render,
75
+ row: replacement.row,
76
+ tail: replacement.tail,
77
+ };
78
+ const declared = declarations.get(key);
79
+ if (declared) {
80
+ return new OverrideDeclaration({ key: { kind: 'view', view: declared }, replacement: stored });
81
+ }
82
+ return new OverrideDeclaration({ key: failureKey(key), replacement: stored });
83
+ }
84
+ /**
85
+ * The key one failure-class override answers. A class is a function whose `prototype` is the
86
+ * object a thrown failure's chain holds, so anything else is no key at all and build reports it as
87
+ * the entry fault of the list that holds it, rather than colliding with every other such value.
88
+ */
89
+ function failureKey(key) {
90
+ if (typeof key !== 'function' || !('prototype' in key)) {
91
+ return { kind: 'invalid' };
92
+ }
93
+ const prototype = key.prototype;
94
+ if (typeof prototype !== 'object' || prototype === null) {
95
+ return { kind: 'invalid' };
96
+ }
97
+ const name = 'name' in key && typeof key.name === 'string' && key.name !== '' ? key.name : 'a failure class';
98
+ return { kind: 'failure', name, prototype };
99
+ }
100
+ /** The identity register one build starts from, holding the views core itself declares. */
101
+ function viewIdentities(declared) {
102
+ const identities = new Map();
103
+ for (const value of declared) {
104
+ registerIdentity(identities, value);
105
+ }
106
+ return identities;
107
+ }
108
+ /** One identity means one declared view, wherever on the graph that view appears. */
109
+ function registerIdentity(identities, declared) {
110
+ const known = identities.get(declared.identity);
111
+ if (known === undefined) {
112
+ identities.set(declared.identity, declared);
113
+ return;
114
+ }
115
+ if (known !== declared) {
116
+ throw new DeclarationError(`View "${declared.identity}" is declared by two distinct objects. Install one copy of the package that declares it.`);
117
+ }
118
+ }
119
+ /** The sentence one contributor's list reports for a value it cannot read. */
120
+ function entryFault(subject) {
121
+ return subject.declares
122
+ ? `${subject.sentence} holds a value that is not a view. Supply the value returned by view(identity, definition) or override(key, view).`
123
+ : `${subject.sentence} holds a value that is not a view override. Supply the value returned by override(key, view).`;
124
+ }
125
+ /** A `views` slot holds a list, so anything else is the same declaration fault. */
126
+ function readContributions(subject, declared) {
127
+ if (declared === undefined) {
128
+ return [];
129
+ }
130
+ if (!Array.isArray(declared)) {
131
+ throw new DeclarationError(subject.declares
132
+ ? `${subject.sentence} declares views that are not an array. Supply a list of declared views and override values.`
133
+ : entryFault(subject));
134
+ }
135
+ return declared;
136
+ }
137
+ /** The override one entry carries; anything else is a declaration fault of the slot. */
138
+ function overrideOf(subject, entry) {
139
+ const record = typeof entry === 'object' && entry !== null ? overrides.get(entry) : undefined;
140
+ if (!record) {
141
+ throw new DeclarationError(entryFault(subject));
142
+ }
143
+ return record;
144
+ }
145
+ /**
146
+ * One override recorded under the key it answers. One key answers to one override inside one
147
+ * contributor, so a second override for it is a declaration fault; the same key overridden by two
148
+ * contributors resolves first-in-wins. A key that is neither a declared view nor a failure class
149
+ * is the entry fault of the list that holds it, reported here rather than at the `override()` call.
150
+ */
151
+ function recordOverride(build, { key, replacement }) {
152
+ if (key.kind === 'invalid') {
153
+ throw new DeclarationError(entryFault(build.subject));
154
+ }
155
+ if (key.kind === 'failure') {
156
+ recordFailureOverride(build, key, replacement);
157
+ return;
158
+ }
159
+ recordViewOverride(build, key.view, replacement);
160
+ }
161
+ /** One failure class answers to one override inside one contributor, keyed by its prototype. */
162
+ function recordFailureOverride({ contributions, subject }, key, replacement) {
163
+ if (contributions.failures.has(key.prototype)) {
164
+ throw new DeclarationError(`${subject.sentence} overrides the view for "${key.name}" twice. Remove one override.`);
165
+ }
166
+ contributions.failures.set(key.prototype, replacement);
167
+ }
168
+ /** Naming a declared view as a key registers its identity, as listing the declaration does. */
169
+ function recordViewOverride({ contributions, identities, subject }, key, replacement) {
170
+ registerIdentity(identities, key);
171
+ if (contributions.views.has(key)) {
172
+ throw new DeclarationError(`${subject.sentence} overrides view "${key.identity}" twice. Remove one override.`);
173
+ }
174
+ contributions.views.set(key, replacement);
175
+ }
176
+ /**
177
+ * One contributor's own overrides, with the identity of every declared view it lists or names as a
178
+ * key registered. Listing a declared view is what puts its identity on the graph; an application
179
+ * lists overrides alone, so a declaration in its list is the same fault as any other value.
180
+ */
181
+ function buildViews(subject, declared, identities) {
182
+ const contributions = { failures: new Map(), views: new Map() };
183
+ for (const entry of readContributions(subject, declared)) {
184
+ const listed = typeof entry === 'object' && entry !== null ? declarations.get(entry) : undefined;
185
+ if (listed && !subject.declares) {
186
+ throw new DeclarationError(entryFault(subject));
187
+ }
188
+ if (listed) {
189
+ registerIdentity(identities, listed);
190
+ }
191
+ else {
192
+ recordOverride({ contributions, identities, subject }, overrideOf(subject, entry));
193
+ }
194
+ }
195
+ return contributions;
196
+ }
197
+ /** One stored view value, read back over the data its own key carries. */
198
+ function readStored(stored) {
199
+ // Last resort: no typed path exists.
200
+ // A registry holds one entry per key and cannot carry a type parameter per entry.
201
+ // A stored view value therefore reads back with its data type erased.
202
+ // It holds because `override(key, replacement)` typed the replacement against its key's data.
203
+ // Resolution reaches a stored value through that key alone.
204
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
205
+ return stored;
206
+ }
207
+ /**
208
+ * The stand-in for a function the stored shape does not carry. A JavaScript author's replacement
209
+ * of the wrong shape reaches it, and the throw is the output-view fault of the write site.
210
+ */
211
+ function missing(name, key) {
212
+ return () => {
213
+ throw new Error(`The replacement view for "${key}" supplies no ${name} function.`);
214
+ };
215
+ }
216
+ /** One stored whole view, read back as the function the write site calls, named by its key. */
217
+ function callView(stored, key) {
218
+ return readStored(stored).render ?? missing('render', key);
219
+ }
220
+ /** Every prototype in a failure's chain, most derived first, so one walk reads one contributor. */
221
+ function chainOf(failure) {
222
+ const chain = [];
223
+ let prototype = Object.getPrototypeOf(failure);
224
+ while (prototype !== null) {
225
+ chain.push(prototype);
226
+ prototype = Object.getPrototypeOf(prototype);
227
+ }
228
+ return chain;
229
+ }
230
+ /**
231
+ * The view function one declared view resolves to: the first contributor that overrides it, then
232
+ * its own default. A bare view is never overridden, so it resolves to its own function.
233
+ */
234
+ function resolveView(registry, value) {
235
+ const declared = declarations.get(value);
236
+ if (declared) {
237
+ for (const contributor of registry) {
238
+ const replacement = contributor.views.get(declared);
239
+ if (replacement) {
240
+ return callView(replacement, declared.identity);
241
+ }
242
+ }
243
+ }
244
+ return (data, context) => value.render(data, context);
245
+ }
246
+ /**
247
+ * The row functions one row view resolves to, by the walk `resolveView` defines. A replacement
248
+ * supplies the whole shape, so an override that omits `head` drops the default's `head` with it.
249
+ */
250
+ function resolveRowView(registry, value) {
251
+ const declared = declarations.get(value);
252
+ if (declared) {
253
+ for (const contributor of registry) {
254
+ const replacement = contributor.views.get(declared);
255
+ if (replacement) {
256
+ const resolved = readStored(replacement);
257
+ const row = resolved.row ?? missing('row', declared.identity);
258
+ return { head: resolved.head, row, tail: resolved.tail };
259
+ }
260
+ }
261
+ }
262
+ return { head: value.head, row: value.row, tail: value.tail };
263
+ }
264
+ /**
265
+ * The override one failure resolves to, or nothing when core's own text answers it. The chain is
266
+ * walked in full at each contributor before the next is consulted, so an application's override
267
+ * for a base class beats a plugin's override for a subclass.
268
+ */
269
+ function resolveFailure(registry, failure) {
270
+ const chain = chainOf(failure);
271
+ for (const contributor of registry) {
272
+ for (const prototype of chain) {
273
+ const replacement = contributor.failures.get(prototype);
274
+ if (replacement) {
275
+ return replacement;
276
+ }
277
+ }
278
+ }
279
+ return undefined;
280
+ }
281
+ /**
282
+ * The text core writes for one failure. Resolution walks the registry as `resolveFailure` defines
283
+ * it and falls to core's own default text, which escapes the raw facts it interpolates. A
284
+ * `FatalError` keeps the authored marked message it was given.
285
+ */
286
+ function describeFailure(registry, failure, context) {
287
+ const replacement = resolveFailure(registry, failure);
288
+ if (!replacement) {
289
+ return {
290
+ kind: 'rendered',
291
+ text: failure instanceof FatalError ? defaultText(failure) : escapeText(defaultText(failure)),
292
+ };
293
+ }
294
+ try {
295
+ if (context === undefined) {
296
+ throw new Error('Missing rendering context.');
297
+ }
298
+ const text = callView(replacement, failure.name)(failure, context);
299
+ return typeof text === 'string'
300
+ ? { kind: 'rendered', text }
301
+ : { kind: 'unrendered', reason: notTextReason(text), text: defaultText(failure) };
302
+ }
303
+ catch (error) {
304
+ return { kind: 'unrendered', reason: reasonOf(error), text: defaultText(failure) };
305
+ }
306
+ }
307
+ export { buildViews, describeFailure, override, resolveRowView, resolveView, shapeOf, view, viewIdentities, };