@loomcli/core 0.1.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.
@@ -0,0 +1,266 @@
1
+ /// <reference types="node" preserve="true" />
2
+ import type { Readable, Writable } from 'node:stream';
3
+ import type { StandardSchemaV1 } from '@standard-schema/spec';
4
+ 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';
5
+ type ShortAlias = LowercaseLetter | Uppercase<LowercaseLetter>;
6
+ type OptionSpelling = {
7
+ short?: ShortAlias;
8
+ shortOnly?: false;
9
+ } | {
10
+ short: ShortAlias;
11
+ shortOnly: true;
12
+ };
13
+ type Presence = {
14
+ required: true;
15
+ default?: never;
16
+ } | {
17
+ required?: false;
18
+ default?: unknown;
19
+ };
20
+ /** The literal member keeps `multiple: true` exact under contextual typing, as `Presence` does. */
21
+ type Multiplicity = {
22
+ multiple: true;
23
+ } | {
24
+ multiple?: false;
25
+ };
26
+ /**
27
+ * `validateOmitted: true` sends an omitted optional scalar to its own schema. The literal member
28
+ * keeps the flag exact under contextual typing, as `Multiplicity` does.
29
+ */
30
+ type Omission = {
31
+ validateOmitted: true;
32
+ } | {
33
+ validateOmitted?: false;
34
+ };
35
+ /**
36
+ * The tokens one declaration collects before validation: one string, or the whole collection. A
37
+ * multiple option and a variadic argument collect alike, so they share this raw shape.
38
+ */
39
+ type RawValue<Config> = Config extends {
40
+ multiple: true;
41
+ } | {
42
+ variadic: true;
43
+ } ? string[] : string;
44
+ type SchemaOutput<Schema, Raw> = Schema extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<Schema> : Raw;
45
+ type SchemaInput<Schema> = Schema extends StandardSchemaV1 ? StandardSchemaV1.InferInput<Schema> : string;
46
+ /** The named key states the rule, so a rejected declaration name reads as its own diagnostic. */
47
+ interface LiteralNameFault {
48
+ 'Declaration names must be one literal string': never;
49
+ }
50
+ type ActionArgument<Declaration> = ActionHandler<Declaration> extends (context: infer Context) => unknown ? Context : never;
51
+ /** A local option name cannot repeat a key the globals already own; the key names the fault. */
52
+ export type GlobalNameConstraint<Name extends string, Globals> = Name extends keyof Globals ? {
53
+ 'This option name is already declared as a global option': Name;
54
+ } : unknown;
55
+ /** A required record key excludes open strings; distribution rejects each union member. */
56
+ export type NameConstraint<Name extends string, Whole extends string = Name> = {} extends Record<Name, unknown> ? LiteralNameFault : Name extends Whole ? [Whole] extends [Name] ? unknown : LiteralNameFault : LiteralNameFault;
57
+ export type ExitCode = 0 | 1 | 2;
58
+ export interface InputTerminal {
59
+ isTTY: boolean;
60
+ }
61
+ export interface OutputTerminal extends InputTerminal {
62
+ columns: number | undefined;
63
+ rows: number | undefined;
64
+ }
65
+ export interface Host {
66
+ argv: string[];
67
+ cwd: string;
68
+ env: Record<string, string | undefined>;
69
+ terminal: {
70
+ stdin: InputTerminal;
71
+ stdout: OutputTerminal;
72
+ stderr: OutputTerminal;
73
+ };
74
+ stdin: Readable;
75
+ stdout: Writable;
76
+ stderr: Writable;
77
+ }
78
+ export interface RunOptions {
79
+ host?: Partial<Host>;
80
+ }
81
+ /** The declaration one schema call validates, under the name and the scope it was declared in. */
82
+ export interface InputIdentity {
83
+ kind: 'argument' | 'option';
84
+ name: string;
85
+ global: boolean;
86
+ }
87
+ /**
88
+ * The raw tokens of one invocation, keyed by declared name, before any schema or default runs.
89
+ * Each schema call reads its own snapshot, so a collected value is read-only and writing to one
90
+ * changes nothing the parser, a later schema, or the action reads.
91
+ */
92
+ export interface SuppliedInputs {
93
+ /** Raw positional values of the routed Command: one string, or the tokens of a variadic. */
94
+ args: Readonly<Record<string, string | readonly string[] | undefined>>;
95
+ /** Raw option values, globals and locals: a string, every occurrence, or a Boolean presence. */
96
+ options: Readonly<Record<string, string | readonly string[] | boolean | undefined>>;
97
+ }
98
+ /**
99
+ * What core knows when it calls one schema. A default validates before any token is read, so its
100
+ * phase carries the host and the declaration alone. An invocation call adds the routed path, the
101
+ * passthrough tail, and the tokens every declared input received.
102
+ */
103
+ export type ValidationContext = {
104
+ phase: 'default';
105
+ host: Host;
106
+ input: InputIdentity;
107
+ } | {
108
+ phase: 'invocation';
109
+ host: Host;
110
+ input: InputIdentity;
111
+ command: readonly string[];
112
+ passthrough: readonly string[];
113
+ supplied: SuppliedInputs;
114
+ };
115
+ /**
116
+ * One value turned into the exact text core writes. A renderer owns every byte, the trailing
117
+ * newline included. It is synchronous and pure: it receives the value alone, returns a string, and
118
+ * holds no output handle. A throw or a non-string return is a renderer failure.
119
+ */
120
+ export interface Renderer<Data> {
121
+ render: (data: Readonly<Data>) => string;
122
+ }
123
+ export interface Out {
124
+ print(message: string): Promise<void>;
125
+ info(message: string): Promise<void>;
126
+ success(message: string): Promise<void>;
127
+ warn(message: string): Promise<void>;
128
+ error(message: string): Promise<void>;
129
+ /** The neutral presentation call: a rendered value has no purpose and no destination. */
130
+ render<Data>(data: Data, renderer: Renderer<Data>): Promise<void>;
131
+ fatal(message: string): never;
132
+ }
133
+ export type StringOption = OptionSpelling & Presence & Multiplicity & Omission & {
134
+ type: 'string';
135
+ polarity?: never;
136
+ validate?: StandardSchemaV1;
137
+ };
138
+ /** A variadic argument collects the remaining tokens, so it follows the multiple option rules. */
139
+ export type VariadicArgument = Presence & {
140
+ variadic: true;
141
+ validate?: StandardSchemaV1;
142
+ };
143
+ export type ScalarArgument = Presence & Omission & {
144
+ variadic?: false;
145
+ validate?: StandardSchemaV1;
146
+ };
147
+ export type ArgumentConfig = VariadicArgument | ScalarArgument;
148
+ export type ValidatedValue<Config, Raw> = Config extends unknown ? 'validate' extends keyof Config ? SchemaOutput<Config['validate'], Raw> : Raw : never;
149
+ /** Keep each conditional declaration paired with its own schema input type. */
150
+ export type DefaultConstraint<Config> = Config extends unknown ? Config & {
151
+ default?: 'validate' extends keyof Config ? SchemaInput<Config['validate']> : RawValue<Config>;
152
+ } : never;
153
+ /**
154
+ * A multiple option hands its whole collection to one schema, so the declared schema must accept a
155
+ * `string[]` input. The key names the fault, the way the other declaration constraints do.
156
+ */
157
+ export type MultipleConstraint<Config> = Config extends {
158
+ multiple: true;
159
+ } ? 'validate' extends keyof Config ? string[] extends SchemaInput<Config['validate']> ? unknown : {
160
+ 'A multiple option schema must accept a string[] input': Config['validate'];
161
+ } : unknown : unknown;
162
+ /**
163
+ * The declaration rules `validateOmitted: true` needs: a schema that accepts `undefined`, an
164
+ * omission the schema can answer, and no other rule that already decides absence. Each key names
165
+ * its fault, the way the other declaration constraints do.
166
+ */
167
+ export type ValidateOmittedConstraint<Config> = Config extends {
168
+ validateOmitted: true;
169
+ } ? Config extends {
170
+ type: 'boolean';
171
+ } ? {
172
+ 'A Boolean option declares no validateOmitted': never;
173
+ } : Config extends {
174
+ required: true;
175
+ } ? {
176
+ 'A required input rejects validateOmitted': never;
177
+ } : Config extends {
178
+ default: unknown;
179
+ } ? {
180
+ 'A declared default rejects validateOmitted': never;
181
+ } : Config extends {
182
+ multiple: true;
183
+ } | {
184
+ variadic: true;
185
+ } ? {
186
+ 'A collected input validates its omission as an empty array': never;
187
+ } : 'validate' extends keyof Config ? undefined extends SchemaInput<Config['validate']> ? unknown : {
188
+ 'A validateOmitted schema must accept an undefined input': Config['validate'];
189
+ } : {
190
+ 'validateOmitted needs a validate schema to receive the omission': never;
191
+ } : unknown;
192
+ export type ArgumentValue<Config extends ArgumentConfig> = Config extends {
193
+ variadic: true;
194
+ } ? ValidatedValue<Config, string[]> : ValidatedValue<Config, string> | (Config extends {
195
+ required: true;
196
+ } | {
197
+ default: unknown;
198
+ } | {
199
+ validateOmitted: true;
200
+ } ? never : undefined);
201
+ export type BooleanOption = (OptionSpelling & {
202
+ type: 'boolean';
203
+ validate?: never;
204
+ default?: never;
205
+ multiple?: never;
206
+ required?: never;
207
+ validateOmitted?: never;
208
+ polarity?: 'positive' | 'negative';
209
+ }) | {
210
+ type: 'boolean';
211
+ validate?: never;
212
+ default?: never;
213
+ multiple?: never;
214
+ required?: never;
215
+ validateOmitted?: never;
216
+ polarity: 'both';
217
+ short?: ShortAlias;
218
+ shortOnly?: false;
219
+ };
220
+ export type OptionConfig = StringOption | BooleanOption;
221
+ export type OptionValue<Config extends OptionConfig> = Config extends StringOption ? Config extends {
222
+ multiple: true;
223
+ } ? ValidatedValue<Config, string[]> : ValidatedValue<Config, string> | (Config extends {
224
+ required: true;
225
+ } | {
226
+ default: unknown;
227
+ } | {
228
+ validateOmitted: true;
229
+ } ? never : undefined) : boolean;
230
+ export interface ActionContext<Args, Options = {}> {
231
+ args: Args;
232
+ options: Options;
233
+ passthrough: string[];
234
+ out: Out;
235
+ host: Host;
236
+ }
237
+ export type Action<Args, Options = {}> = (context: ActionContext<Args, Options>) => unknown;
238
+ /** Phantom key. It keeps the inferred declaration types exact and holds no runtime value. */
239
+ export declare const declaredTypes: unique symbol;
240
+ /**
241
+ * The three inferred types one declaration carries. The phantom member keeps them exact. Args and
242
+ * options widen, so a child can satisfy a looser reader. The globals appear in both a parameter and
243
+ * a return position, which makes them invariant: a child's globals must be the parent's own type,
244
+ * not a subset and not a superset, because one table serves every Command in the graph.
245
+ */
246
+ export interface DeclaredTypes<Args, Options, Globals> {
247
+ args: Args;
248
+ globals: (value: Globals) => Globals;
249
+ options: Options;
250
+ }
251
+ /**
252
+ * The handler one declaration accepts. It reads the phantom types, not the `action()` call, so it
253
+ * holds on a fresh declaration, on a partly declared one, and on one that registered its action.
254
+ */
255
+ export type ActionHandler<Declaration> = Declaration extends {
256
+ [declaredTypes]: DeclaredTypes<infer Args, infer Options, infer Globals>;
257
+ } ? Action<Args, Globals & Options> : never;
258
+ /** The args object an extracted handler receives for this declaration. */
259
+ export type ActionArgs<Declaration> = ActionArgument<Declaration> extends {
260
+ args: infer Args;
261
+ } ? Args : never;
262
+ /** The options object an extracted handler receives, global values included. */
263
+ export type ActionOptions<Declaration> = ActionArgument<Declaration> extends {
264
+ options: infer Options;
265
+ } ? Options : never;
266
+ export {};
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,83 @@
1
+ import type { StandardSchemaV1 } from '@standard-schema/spec';
2
+ import type { OptionValues } from './options.js';
3
+ import type { ArgumentConfig, ArgumentValue, Host, OptionConfig, OptionValue } from './types.js';
4
+ /**
5
+ * One declared input, typed by its literal name and its own config. The value type is derived from
6
+ * the config, never claimed apart from it, so a declaration cannot be written under a value type
7
+ * that its config does not produce. Untyped readers use the defaults.
8
+ */
9
+ export interface ArgumentInput<Name extends string = string, Config extends ArgumentConfig = ArgumentConfig> {
10
+ readonly kind: 'argument';
11
+ readonly name: Name;
12
+ readonly config: Config;
13
+ }
14
+ export interface OptionInput<Name extends string = string, Config extends OptionConfig = OptionConfig> {
15
+ readonly kind: 'option';
16
+ readonly name: Name;
17
+ readonly config: Config;
18
+ }
19
+ export type InputDeclaration<Name extends string = string> = ArgumentInput<Name> | OptionInput<Name>;
20
+ /** Validated defaults, read before any token is parsed. Values stay `unknown` here. */
21
+ export type DefaultValues = ReadonlyMap<InputDeclaration, unknown>;
22
+ /** The declarations one pass reads, by the scope that holds them: the graph's, then a Command's. */
23
+ export interface ScopedInputs {
24
+ globals: readonly InputDeclaration[];
25
+ locals: readonly InputDeclaration[];
26
+ }
27
+ /** The raw tokens one invocation collected, keyed by declaration and by option name. */
28
+ interface SuppliedValues {
29
+ args: ReadonlyMap<InputDeclaration, string | string[]>;
30
+ options: OptionValues;
31
+ }
32
+ /** Everything one invocation validates: its declarations, its tokens, and where they were read. */
33
+ export interface Invocation {
34
+ command: readonly string[];
35
+ defaults: DefaultValues;
36
+ host: Host;
37
+ inputs: ScopedInputs;
38
+ passthrough: readonly string[];
39
+ supplied: SuppliedValues;
40
+ }
41
+ /**
42
+ * The validated values of one invocation, keyed by declaration. Only `validateValues` constructs
43
+ * one, and its two readers are the only places where a validated value takes its declared type, so
44
+ * every binder reads through them and none asserts on its own.
45
+ */
46
+ declare class ValidatedInputs {
47
+ #private;
48
+ constructor(values: ReadonlyMap<InputDeclaration, unknown>);
49
+ /** The one-key record this argument contributes to `args`, typed by its own config. */
50
+ argument<Name extends string, Config extends ArgumentConfig>(input: ArgumentInput<Name, Config>): Record<Name, ArgumentValue<Config>>;
51
+ /** The one-key record this option contributes to `options`, typed by its own config. */
52
+ option<Name extends string, Config extends OptionConfig>(input: OptionInput<Name, Config>): Record<Name, OptionValue<Config>>;
53
+ }
54
+ export type { ValidatedInputs };
55
+ /**
56
+ * Authoring's snapshot of one config. An array default is the one declared value core hands to an
57
+ * action as its own value, so the declaration keeps a copy and the caller keeps its array. Every
58
+ * other property is captured as declared, because core clones no library object.
59
+ */
60
+ export declare function captureConfig<Config extends ArgumentConfig | OptionConfig>(config: Config): Config;
61
+ /**
62
+ * The declaration flag that sends an omitted value to its own schema. Every declaration reads it
63
+ * here, and the declaration rules below reject it wherever another rule already decides absence.
64
+ */
65
+ export declare function validatesOmission(input: InputDeclaration): boolean;
66
+ /**
67
+ * 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
69
+ * helper, so a rejected item reads alike wherever its diagnostic is written.
70
+ */
71
+ export declare function issuePath(issue: StandardSchemaV1.Issue): string | undefined;
72
+ /**
73
+ * Every declaration rule that reads the declaration alone. It is synchronous, so `inspect()` and
74
+ * `run()` apply exactly the same rules, and only validating a default through its schema, which
75
+ * can be asynchronous, is left to `run()`.
76
+ */
77
+ export declare function checkDeclarations(inputs: readonly InputDeclaration[]): void;
78
+ /**
79
+ * Every declared default, validated before any token is read. The host is captured by then, so a
80
+ * default's schema reads the same Host its action will, under the `default` phase.
81
+ */
82
+ export declare function prepareInputs(inputs: ScopedInputs, host: Host): Promise<DefaultValues>;
83
+ export declare function validateValues(invocation: Invocation): Promise<ValidatedInputs>;