@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
@@ -0,0 +1,22 @@
1
+ import type { Plugin } from './plugin.js';
2
+ /** The shallow type information an Application supplies before its Command graph is composed. */
3
+ interface ApplicationEnvironment<Globals = {}, Plugins extends readonly Plugin[] = readonly []> {
4
+ readonly globals: Globals;
5
+ readonly plugins: Plugins;
6
+ }
7
+ interface RegistrationShape {
8
+ environment?: ApplicationEnvironment<unknown, readonly Plugin[]>;
9
+ }
10
+ /** The Application augments this interface once in its own TypeScript compilation context. */
11
+ interface Register extends RegistrationShape {
12
+ }
13
+ type RegisteredEnvironment = Register extends {
14
+ environment: infer Environment;
15
+ } ? Environment extends ApplicationEnvironment<unknown, readonly Plugin[]> ? Environment : never : ApplicationEnvironment;
16
+ type RegisteredGlobals = RegisteredEnvironment['globals'];
17
+ /** A private type marker avoids pretending parsed global values exist before an invocation. */
18
+ declare const applicationEnvironment: unique symbol;
19
+ type EnvironmentOf<Value extends {
20
+ readonly [applicationEnvironment]: ApplicationEnvironment<unknown, readonly Plugin[]>;
21
+ }> = Value[typeof applicationEnvironment];
22
+ export type { ApplicationEnvironment, EnvironmentOf, Register, RegisteredEnvironment, RegisteredGlobals, applicationEnvironment, };
@@ -0,0 +1 @@
1
+ export {};
package/dist/errors.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { StandardSchemaV1 } from '@standard-schema/spec';
2
- import type { InputIdentity, Renderer } from './types.js';
2
+ import type { InputIdentity } from './types.js';
3
3
  /**
4
4
  * The two short-group faults. A value option that is not last in its group names that option's
5
5
  * spelling; a group that mixes scopes names the whole group and the two letters that disagree.
@@ -14,28 +14,25 @@ type ShortGroupFault = {
14
14
  global: string;
15
15
  other: string;
16
16
  };
17
- /** One registration: the class it names, read as the prototype and the name that class holds. */
18
- interface Registration {
19
- name: string;
20
- prototype: unknown;
21
- render: (failure: LoomError) => unknown;
22
- }
23
- /** Phantom key. It marks a failure registration and holds no runtime value. */
24
- declare const failureRegistration: unique symbol;
25
- /** The runtime value `renderFailure` returns. Its pair lives in the registry above. */
26
- declare class RegisteredFailure {
27
- readonly [failureRegistration]: true;
28
- constructor(registration: Registration);
29
- }
17
+ /**
18
+ * Core's own text for one failure: its message under the category prefix its class carries, with
19
+ * the trailing newline every view's text carries. The four categories are disjoint branches of the
20
+ * hierarchy, so one ordered test reads every class, and a class without a prefix of its own writes
21
+ * the sentence alone. It is the default view of every failure class and the text the plain
22
+ * fallback path writes, so it runs no application code and nothing downstream composes its newline.
23
+ */
24
+ export declare function defaultText(failure: LoomError): string;
30
25
  /** How a diagnostic names one Command inside a sentence: by name, or as the unnamed root. */
31
26
  export declare function commandSubject(name: string | null): string;
27
+ /** The routed path names the Command a sentence speaks of; an empty path is the root. */
28
+ export declare function routedSubject(command: readonly string[]): string;
32
29
  /** The same subject at the start of a sentence. */
33
30
  export declare function commandSentence(name: string | null): string;
34
31
  /**
35
32
  * Every failure `run()` reports is an instance of a public class. Each class carries the facts its
36
- * sentence interpolates, so a renderer reads them instead of parsing prose, and the exit status is
33
+ * sentence interpolates, so a view reads them instead of parsing prose, and the exit status is
37
34
  * a field of the base, so a subclass inherits it. `message` never carries a category prefix; the
38
- * default renderers add it.
35
+ * default views add it.
39
36
  */
40
37
  export declare abstract class LoomError extends Error {
41
38
  readonly exitCode: 1 | 2;
@@ -109,50 +106,33 @@ export declare class DeclarationError extends LoomError {
109
106
  export declare class FatalError extends LoomError {
110
107
  constructor(message: string);
111
108
  }
112
- /** Exit 1: an unexpected exception, a non-error throw, or a renderer that could not answer. */
109
+ /** Exit 1: an unexpected exception, a non-error throw, or a view that could not answer. */
113
110
  export declare class InternalError extends LoomError {
114
111
  readonly cause: unknown;
115
112
  constructor(message: string, cause: unknown);
116
113
  }
114
+ /** The four ways the results lane is broken, each named where core meets it. */
115
+ export type ResultFault = 'missing' | 'repeated' | 'undeclared' | 'middleware';
116
+ /**
117
+ * Exit 1: the promise a declared result makes was not kept. It wraps no thrown value, so its
118
+ * `cause` is `undefined`, and it extends `InternalError`, so an override of that class brands it
119
+ * and its default text carries the same prefix, while an override keyed by this class reaches it
120
+ * alone.
121
+ */
122
+ export declare class ResultError extends InternalError {
123
+ readonly path: readonly string[];
124
+ readonly kind: ResultFault;
125
+ readonly cause: undefined;
126
+ constructor(kind: ResultFault, path: readonly string[]);
127
+ }
117
128
  /** What a diagnostic says about an unexpected value, whether or not it was an Error. */
118
129
  export declare function reasonOf(thrown: unknown): string;
119
130
  /**
120
- * Why a returned value is not the text a renderer owes. A renderer is synchronous, so a returned
121
- * promise is a non-string return like any other, and its rejection is adopted and swallowed here:
122
- * an unobserved rejection would end the process before the invocation could report anything.
131
+ * Why a returned value is not the text a view owes. A view is synchronous, so a returned promise is
132
+ * a non-string return like any other, and its rejection is adopted and swallowed here: an
133
+ * unobserved rejection would end the process before the invocation could report anything.
123
134
  */
124
135
  export declare function notTextReason(value: unknown): string;
125
136
  /** Every thrown value reaches reporting as a failure class; anything else is internal. */
126
137
  export declare function toFailure(thrown: unknown): LoomError;
127
- /** An opaque registration pairing one failure class with a renderer for its instances. */
128
- export type FailureRenderer = Pick<RegisteredFailure, typeof failureRegistration>;
129
- /**
130
- * A registration pairing one failure class with a renderer for its instances. The helper is the
131
- * typed path for a class-keyed list, because an array literal cannot carry a different type
132
- * parameter per element.
133
- */
134
- export declare function renderFailure<Failure extends LoomError>(type: abstract new (...args: never[]) => Failure, renderer: Renderer<Failure>): FailureRenderer;
135
- /** The renderers one application registered, keyed by the class each one names. */
136
- export type FailureRegistry = ReadonlyMap<unknown, Registration>;
137
- /** One class answers to one renderer, so a second registration for it is a declaration fault. */
138
- export declare function buildFailures(failures: readonly FailureRenderer[]): FailureRegistry;
139
- /**
140
- * The report of one failure: the text core writes, and whether a registered renderer produced it.
141
- * An unrendered report carries core's own text, which the plain fallback path writes beside the
142
- * diagnostic naming the renderer that could not answer.
143
- */
144
- export type FailureReport = {
145
- kind: 'rendered';
146
- text: string;
147
- } | {
148
- kind: 'unrendered';
149
- text: string;
150
- reason: string;
151
- };
152
- /**
153
- * The text core writes for one failure. Resolution walks the failure's prototype chain most
154
- * derived first through the application's registrations, then falls to core's own text, so a
155
- * registration for a base class brands every failure below it.
156
- */
157
- export declare function describeFailure(registry: FailureRegistry, failure: LoomError): FailureReport;
158
138
  export {};
package/dist/errors.js CHANGED
@@ -1,7 +1,27 @@
1
- /** The routed path names the Command a token fault belongs to; an empty path is the root. */
1
+ /** The same subject at the start of a sentence, where a token fault names its Command. */
2
2
  function routedSentence(command) {
3
- const name = command.at(-1);
4
- return name === undefined ? 'The root Command' : commandSentence(name);
3
+ const subject = routedSubject(command);
4
+ return `${subject.slice(0, 1).toUpperCase()}${subject.slice(1)}`;
5
+ }
6
+ /** The sentence one results-lane fault reports, which names the Command that holds it. */
7
+ function resultMessage(kind, command) {
8
+ if (kind === 'missing') {
9
+ return `${routedSentence(command)} declares a result and its action returned without emitting one. Call out.results() once.`;
10
+ }
11
+ if (kind === 'repeated') {
12
+ return `${routedSentence(command)} emitted its result twice. Call out.results() once.`;
13
+ }
14
+ if (kind === 'undeclared') {
15
+ return `${routedSentence(command)} declares no result. Declare one with result() or rows() before action().`;
16
+ }
17
+ return `A middleware called out.results() on ${routedSubject(command)}. Only the action emits a result.`;
18
+ }
19
+ /**
20
+ * The clause that offers the candidates, which is absent when there are none: every child of the
21
+ * Command is hidden, so the diagnostic ends after the sentence that names the fault.
22
+ */
23
+ function offering(candidates) {
24
+ return candidates.length === 0 ? '' : ` Use one of: ${candidates.join(', ')}.`;
5
25
  }
6
26
  function shortGroupMessage(fault) {
7
27
  return fault.reason === 'value-position'
@@ -10,12 +30,12 @@ function shortGroupMessage(fault) {
10
30
  }
11
31
  /**
12
32
  * Core's own text for one failure: its message under the category prefix its class carries, with
13
- * the trailing newline every renderer's text carries. The four categories are disjoint branches of
14
- * the hierarchy, so one ordered test reads every class, and a class without a prefix of its own
15
- * writes the sentence alone. A default rendering is the same kind of value as a custom one, so
16
- * nothing downstream composes the newline for it.
33
+ * the trailing newline every view's text carries. The four categories are disjoint branches of the
34
+ * hierarchy, so one ordered test reads every class, and a class without a prefix of its own writes
35
+ * the sentence alone. It is the default view of every failure class and the text the plain
36
+ * fallback path writes, so it runs no application code and nothing downstream composes its newline.
17
37
  */
18
- function defaultText(failure) {
38
+ export function defaultText(failure) {
19
39
  if (failure instanceof UsageError) {
20
40
  return `Invalid input: ${failure.message}\n`;
21
41
  }
@@ -27,36 +47,15 @@ function defaultText(failure) {
27
47
  }
28
48
  return `${failure.message}\n`;
29
49
  }
30
- /** Every prototype in a failure's chain, most derived first, so one walk reads the registry. */
31
- function chainOf(failure) {
32
- const chain = [];
33
- let prototype = Object.getPrototypeOf(failure);
34
- while (prototype !== null) {
35
- chain.push(prototype);
36
- prototype = Object.getPrototypeOf(prototype);
37
- }
38
- return chain;
39
- }
40
- /** Authored registrations register here, so the public type publishes nothing to reach. */
41
- const nodes = new WeakMap();
42
- /** The runtime value `renderFailure` returns. Its pair lives in the registry above. */
43
- class RegisteredFailure {
44
- constructor(registration) {
45
- nodes.set(this, registration);
46
- }
47
- }
48
- /** Reads the pair behind a registered value; anything else is a declaration error. */
49
- function nodeOf(value) {
50
- const registration = nodes.get(value);
51
- if (!registration) {
52
- throw new DeclarationError('The Application holds a value that is not a failure renderer. Supply the value returned by renderFailure(type, renderer).');
53
- }
54
- return registration;
55
- }
56
50
  /** How a diagnostic names one Command inside a sentence: by name, or as the unnamed root. */
57
51
  export function commandSubject(name) {
58
52
  return name === null ? 'the root Command' : `Command "${name}"`;
59
53
  }
54
+ /** The routed path names the Command a sentence speaks of; an empty path is the root. */
55
+ export function routedSubject(command) {
56
+ const name = command.at(-1);
57
+ return name === undefined ? 'the root Command' : commandSubject(name);
58
+ }
60
59
  /** The same subject at the start of a sentence. */
61
60
  export function commandSentence(name) {
62
61
  const subject = commandSubject(name);
@@ -64,9 +63,9 @@ export function commandSentence(name) {
64
63
  }
65
64
  /**
66
65
  * Every failure `run()` reports is an instance of a public class. Each class carries the facts its
67
- * sentence interpolates, so a renderer reads them instead of parsing prose, and the exit status is
66
+ * sentence interpolates, so a view reads them instead of parsing prose, and the exit status is
68
67
  * a field of the base, so a subclass inherits it. `message` never carries a category prefix; the
69
- * default renderers add it.
68
+ * default views add it.
70
69
  */
71
70
  export class LoomError extends Error {
72
71
  exitCode;
@@ -96,7 +95,7 @@ export class UnknownCommandError extends UsageError {
96
95
  token;
97
96
  candidates;
98
97
  constructor(token, candidates) {
99
- super(`Unknown command "${token}". Use one of: ${candidates.join(', ')}.`);
98
+ super(`Unknown command "${token}".${offering(candidates)}`);
100
99
  this.candidates = candidates;
101
100
  this.name = 'UnknownCommandError';
102
101
  this.token = token;
@@ -107,7 +106,7 @@ export class NonCallableCommandError extends UsageError {
107
106
  command;
108
107
  candidates;
109
108
  constructor(command, candidates) {
110
- super(`${routedSentence(command)} requires a subcommand. Use one of: ${candidates.join(', ')}.`);
109
+ super(`${routedSentence(command)} requires a subcommand.${offering(candidates)}`);
111
110
  this.candidates = candidates;
112
111
  this.command = command;
113
112
  this.name = 'NonCallableCommandError';
@@ -186,7 +185,7 @@ export class FatalError extends LoomError {
186
185
  this.name = 'FatalError';
187
186
  }
188
187
  }
189
- /** Exit 1: an unexpected exception, a non-error throw, or a renderer that could not answer. */
188
+ /** Exit 1: an unexpected exception, a non-error throw, or a view that could not answer. */
190
189
  export class InternalError extends LoomError {
191
190
  cause;
192
191
  constructor(message, cause) {
@@ -195,67 +194,36 @@ export class InternalError extends LoomError {
195
194
  this.name = 'InternalError';
196
195
  }
197
196
  }
197
+ /**
198
+ * Exit 1: the promise a declared result makes was not kept. It wraps no thrown value, so its
199
+ * `cause` is `undefined`, and it extends `InternalError`, so an override of that class brands it
200
+ * and its default text carries the same prefix, while an override keyed by this class reaches it
201
+ * alone.
202
+ */
203
+ export class ResultError extends InternalError {
204
+ path;
205
+ kind;
206
+ constructor(kind, path) {
207
+ super(resultMessage(kind, path), undefined);
208
+ this.kind = kind;
209
+ this.name = 'ResultError';
210
+ this.path = path;
211
+ }
212
+ }
198
213
  /** What a diagnostic says about an unexpected value, whether or not it was an Error. */
199
214
  export function reasonOf(thrown) {
200
215
  return thrown instanceof Error ? thrown.message : 'An unknown error occurred.';
201
216
  }
202
217
  /**
203
- * Why a returned value is not the text a renderer owes. A renderer is synchronous, so a returned
204
- * promise is a non-string return like any other, and its rejection is adopted and swallowed here:
205
- * an unobserved rejection would end the process before the invocation could report anything.
218
+ * Why a returned value is not the text a view owes. A view is synchronous, so a returned promise is
219
+ * a non-string return like any other, and its rejection is adopted and swallowed here: an
220
+ * unobserved rejection would end the process before the invocation could report anything.
206
221
  */
207
222
  export function notTextReason(value) {
208
223
  void Promise.resolve(value).catch(() => undefined);
209
- return `The renderer returned ${typeof value} instead of a string.`;
224
+ return `The view returned ${typeof value} instead of a string.`;
210
225
  }
211
226
  /** Every thrown value reaches reporting as a failure class; anything else is internal. */
212
227
  export function toFailure(thrown) {
213
228
  return thrown instanceof LoomError ? thrown : new InternalError(reasonOf(thrown), thrown);
214
229
  }
215
- /**
216
- * A registration pairing one failure class with a renderer for its instances. The helper is the
217
- * typed path for a class-keyed list, because an array literal cannot carry a different type
218
- * parameter per element.
219
- */
220
- export function renderFailure(type, renderer) {
221
- return new RegisteredFailure({
222
- name: 'name' in type && typeof type.name === 'string' ? type.name : 'a failure class',
223
- prototype: 'prototype' in type ? type.prototype : undefined,
224
- // Resolution reaches this registration through the same class, so the test always holds.
225
- render: (failure) => (failure instanceof type ? renderer.render(failure) : undefined),
226
- });
227
- }
228
- /** One class answers to one renderer, so a second registration for it is a declaration fault. */
229
- export function buildFailures(failures) {
230
- const registry = new Map();
231
- for (const failure of failures) {
232
- const registration = nodeOf(failure);
233
- if (registry.has(registration.prototype)) {
234
- throw new DeclarationError(`The Application registers two failure renderers for "${registration.name}". Remove one registration.`);
235
- }
236
- registry.set(registration.prototype, registration);
237
- }
238
- return registry;
239
- }
240
- /**
241
- * The text core writes for one failure. Resolution walks the failure's prototype chain most
242
- * derived first through the application's registrations, then falls to core's own text, so a
243
- * registration for a base class brands every failure below it.
244
- */
245
- export function describeFailure(registry, failure) {
246
- const registration = chainOf(failure)
247
- .map((prototype) => registry.get(prototype))
248
- .find((entry) => entry !== undefined);
249
- if (!registration) {
250
- return { kind: 'rendered', text: defaultText(failure) };
251
- }
252
- try {
253
- const text = registration.render(failure);
254
- return typeof text === 'string'
255
- ? { kind: 'rendered', text }
256
- : { kind: 'unrendered', reason: notTextReason(text), text: defaultText(failure) };
257
- }
258
- catch (error) {
259
- return { kind: 'unrendered', reason: reasonOf(error), text: defaultText(failure) };
260
- }
261
- }
@@ -0,0 +1,99 @@
1
+ import type { StandardSchemaV1 } from '@standard-schema/spec';
2
+ import type { ArgumentNode, CommandNode, OptionNode } from './inspect.js';
3
+ /** The three declaration kinds an extension can name, each with its own node in the graph. */
4
+ type ExtensionTarget = 'argument' | 'command' | 'option';
5
+ /**
6
+ * The descriptor supertype a plugin's `extensions` list uses. It publishes the identity and the
7
+ * target and erases both the schema and the factory call signature, because a schema-typed call
8
+ * signature relates only by schema identity and a list cannot name one schema per element. A
9
+ * descriptor is assignable to it; an extension value, which publishes its brand alone, is not.
10
+ */
11
+ interface AnyExtension {
12
+ readonly identity: string;
13
+ readonly target: ExtensionTarget;
14
+ }
15
+ /** One carried value: the input its author supplied and the descriptor that produced it. */
16
+ interface CarriedValue {
17
+ descriptor: AnyExtension;
18
+ input: unknown;
19
+ }
20
+ /** Phantom key. It brands an extension value with its target and holds no runtime value. */
21
+ declare const extensionTarget: unique symbol;
22
+ /**
23
+ * The value one extension produces, branded with its target so a value on the wrong declaration is
24
+ * a compile error. It publishes the brand alone, so a value is never mistaken for the descriptor
25
+ * that produced it, and the input it carries stays private to this package.
26
+ */
27
+ declare class ExtensionCarrier<Target extends ExtensionTarget> {
28
+ readonly [extensionTarget]: (target: Target) => Target;
29
+ constructor(record: CarriedValue);
30
+ }
31
+ /** A branded extension value, keyed by its extension's identity and typed by its target. */
32
+ type ExtensionValue<Target extends ExtensionTarget> = Pick<ExtensionCarrier<Target>, typeof extensionTarget>;
33
+ /**
34
+ * A descriptor that is also a factory: calling it with the schema's input returns the branded value
35
+ * a declaration carries. The schema is public so a typed read recovers the output type.
36
+ */
37
+ interface Extension<Target extends ExtensionTarget = ExtensionTarget, Schema extends StandardSchemaV1 = StandardSchemaV1> extends AnyExtension {
38
+ (input: StandardSchemaV1.InferInput<Schema>): ExtensionValue<Target>;
39
+ readonly schema: Schema;
40
+ readonly target: Target;
41
+ }
42
+ /**
43
+ * One typed fact a plugin defines for one target. The descriptor is compared by reference wherever
44
+ * it appears, so one identity means one descriptor and a duplicated package copy is visible.
45
+ */
46
+ declare function extension<Target extends ExtensionTarget, Schema extends StandardSchemaV1>(identity: string, config: {
47
+ schema: Schema;
48
+ target: Target;
49
+ }): Extension<Target, Schema>;
50
+ /** The node kind one descriptor's target names, so a read against another kind cannot compile. */
51
+ type NodeFor<Target extends ExtensionTarget> = Target extends 'command' ? CommandNode : Target extends 'option' ? OptionNode : ArgumentNode;
52
+ /** Stored output is plain data the graph froze, so every read of it is read-only to any depth. */
53
+ type DeepReadonly<Value> = Value extends readonly (infer Item)[] ? readonly DeepReadonly<Item>[] : Value extends object ? {
54
+ readonly [Key in keyof Value]: DeepReadonly<Value[Key]>;
55
+ } : Value;
56
+ /**
57
+ * The typed read of one extension value. It takes the node kind the descriptor targets, returns the
58
+ * stored output or `undefined`, compares the descriptor by reference with the one that produced the
59
+ * value, and runs no schema.
60
+ */
61
+ declare function readExtension<Target extends ExtensionTarget, Schema extends StandardSchemaV1>(node: NodeFor<Target>, descriptor: Extension<Target, Schema>): DeepReadonly<StandardSchemaV1.InferOutput<Schema>> | undefined;
62
+ /** The declaration one `extensions` slot belongs to, as its own diagnostics name it. */
63
+ interface ExtensionSubject {
64
+ /** The subject after a preposition, such as `on Command "get"`. */
65
+ phrase: string;
66
+ /** The subject at the start of a sentence, such as `Command "get"`. */
67
+ sentence: string;
68
+ }
69
+ /** Every descriptor one build has met, so a second descriptor under one identity is visible. */
70
+ type DescriptorRegistry = Map<string, AnyExtension>;
71
+ /**
72
+ * The record each declaration published during one build, keyed by the declaration itself. The
73
+ * records live here rather than on the declaration, because one build's outputs belong to that
74
+ * build alone and inspection reads them back with the nodes it renders.
75
+ */
76
+ type ExtensionRecords = Map<object, Readonly<Record<string, unknown>>>;
77
+ /** Whether a value is a descriptor: a callable object carrying an identity and a declared target. */
78
+ declare function isDescriptor(value: unknown): value is AnyExtension;
79
+ /** One identity means one descriptor, wherever on the graph that descriptor appears. */
80
+ declare function registerDescriptor(descriptors: DescriptorRegistry, descriptor: AnyExtension): void;
81
+ /** Everything one `extensions` slot needs to answer: whose it is, and what it may carry. */
82
+ interface ExtensionSlot {
83
+ declared: unknown;
84
+ descriptors: DescriptorRegistry;
85
+ subject: ExtensionSubject;
86
+ target: ExtensionTarget;
87
+ }
88
+ /**
89
+ * The frozen record one declaration publishes: each carried value validated once, synchronously,
90
+ * and stored under its extension's identity. The descriptors that produced them are recorded beside
91
+ * the record, so a typed read compares by reference without the graph carrying a reference.
92
+ */
93
+ declare function buildExtensions(slot: ExtensionSlot): Readonly<Record<string, unknown>>;
94
+ /** Layers validate in authoring order; replacement updates existing keys without reinsertion. */
95
+ declare function buildCommandExtensions(slot: Omit<ExtensionSlot, 'declared' | 'target'> & {
96
+ layers: readonly unknown[];
97
+ }): Readonly<Record<string, unknown>>;
98
+ export type { AnyExtension, DeepReadonly, DescriptorRegistry, Extension, ExtensionRecords, ExtensionSubject, ExtensionTarget, ExtensionValue, };
99
+ export { buildCommandExtensions, buildExtensions, extension, isDescriptor, readExtension, registerDescriptor, };