cli-kiss 0.2.3 → 0.2.4

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/src/lib/Reader.ts CHANGED
@@ -7,44 +7,58 @@ import {
7
7
  } from "./Typo";
8
8
 
9
9
  /**
10
- * Opaque key identifying a registered option within a {@link ReaderArgs} instance.
11
- * Returned by {@link ReaderArgs.registerOption}; passed to {@link ReaderArgs.getOptionValues}.
10
+ * Opaque key returned by {@link ReaderArgs.registerOption}.
12
11
  */
13
12
  export type ReaderOptionKey = (string | { __brand: "ReaderOptionKey" }) & {
14
13
  __brand: "ReaderOptionKey";
15
14
  };
16
15
 
17
16
  /**
18
- * Option registration and query interface, implemented by {@link ReaderArgs}.
19
- * Exposed separately from {@link ReaderPositionals} so parsers depend only on what they need.
17
+ * Parsing behaviour for a registered option, passed to {@link ReaderArgs.registerOption}.
18
+ */
19
+ export type ReaderOptionParsing = {
20
+ consumeShortGroup: boolean;
21
+ consumeNextArg: (
22
+ inlined: string | null,
23
+ separated: Array<string>,
24
+ next: string | undefined,
25
+ ) => boolean;
26
+ };
27
+
28
+ /**
29
+ * Result of parsing an option, including its inlined value and any following separated values.
30
+ */
31
+ export type ReaderOptionValue = {
32
+ inlined: string | null;
33
+ separated: Array<string>;
34
+ };
35
+
36
+ /**
37
+ * Option registration/query interface. Subset of {@link ReaderArgs}.
20
38
  */
21
39
  export type ReaderOptions = {
22
40
  /**
23
- * Registers an option so the parser can recognise it.
41
+ * Registers an option; all `longs` and `shorts` share the same key.
24
42
  *
25
43
  * @param definition.longs - Long-form names (without `--`).
26
44
  * @param definition.shorts - Short-form names (without `-`).
27
- * @param definition.valued - `true` if the option takes a value; `false` for flags.
45
+ * @param definition.parsing - Parsing behaviour.
28
46
  * @returns A {@link ReaderOptionKey} for later retrieval.
29
47
  * @throws `Error` if a name is already registered or short names overlap.
30
48
  */
31
49
  registerOption(definition: {
32
50
  longs: Array<string>;
33
51
  shorts: Array<string>;
34
- valued: boolean;
52
+ parsing: ReaderOptionParsing;
35
53
  }): ReaderOptionKey;
36
54
  /**
37
- * Returns all values collected for the option identified by `key`.
38
- *
39
- * @param key - Key from {@link ReaderOptions.registerOption}.
40
- * @returns Raw string values, one per occurrence; empty if never provided.
41
- * @throws `Error` if `key` was not registered.
55
+ * Returns all values collected for `key`.
42
56
  */
43
- getOptionValues(key: ReaderOptionKey): Array<string>;
57
+ getOptionValues(key: ReaderOptionKey): Array<ReaderOptionValue>;
44
58
  };
45
59
 
46
60
  /**
47
- * Positional token consumption interface, implemented by {@link ReaderArgs}.
61
+ * Positional consumption interface. Subset of {@link ReaderArgs}.
48
62
  */
49
63
  export type ReaderPositionals = {
50
64
  /**
@@ -69,10 +83,9 @@ export class ReaderArgs {
69
83
  #args: ReadonlyArray<string>;
70
84
  #parsedIndex: number;
71
85
  #parsedDouble: boolean;
72
- #keyByLong: Map<string, ReaderOptionKey>;
73
- #keyByShort: Map<string, ReaderOptionKey>;
74
- #valuedByKey: Map<ReaderOptionKey, boolean>;
75
- #resultByKey: Map<ReaderOptionKey, Array<string>>;
86
+ #optionContextByLong: Map<string, ReaderOptionContext>;
87
+ #optionContextByShort: Map<string, ReaderOptionContext>;
88
+ #optionContextByKey: Map<ReaderOptionKey, ReaderOptionContext>;
76
89
 
77
90
  /**
78
91
  * @param args - Raw CLI tokens (e.g. `process.argv.slice(2)`). Not mutated.
@@ -81,27 +94,25 @@ export class ReaderArgs {
81
94
  this.#args = args;
82
95
  this.#parsedIndex = 0;
83
96
  this.#parsedDouble = false;
84
- this.#keyByLong = new Map();
85
- this.#keyByShort = new Map();
86
- this.#valuedByKey = new Map();
87
- this.#resultByKey = new Map();
97
+ this.#optionContextByLong = new Map();
98
+ this.#optionContextByShort = new Map();
99
+ this.#optionContextByKey = new Map();
88
100
  }
89
101
 
90
102
  /**
91
103
  * Registers an option; all `longs` and `shorts` share the same key.
92
- * Short names support stacking (e.g. `-abc`) and inline values (e.g. `-nvalue`),
93
- * but must not be prefixes of one another.
104
+ * Short names must not be prefixes of one another.
94
105
  *
95
106
  * @param definition.longs - Long-form names (without `--`).
96
107
  * @param definition.shorts - Short-form names (without `-`).
97
- * @param definition.valued - `true` if the option takes a value; `false` for flags.
108
+ * @param definition.parsing - Parsing behaviour.
98
109
  * @returns A {@link ReaderOptionKey} for {@link ReaderArgs.getOptionValues}.
99
110
  * @throws `Error` if any name is already registered or short names overlap.
100
111
  */
101
112
  registerOption(definition: {
102
113
  longs: Array<string>;
103
114
  shorts: Array<string>;
104
- valued: boolean;
115
+ parsing: ReaderOptionParsing;
105
116
  }) {
106
117
  const key = [
107
118
  ...definition.longs.map((long) => `--${long}`),
@@ -111,7 +122,7 @@ export class ReaderArgs {
111
122
  if (!this.#isValidOptionName(long)) {
112
123
  throw new Error(`Invalid option name: --${long}`);
113
124
  }
114
- if (this.#keyByLong.has(long)) {
125
+ if (this.#optionContextByLong.has(long)) {
115
126
  throw new Error(`Option already registered: --${long}`);
116
127
  }
117
128
  }
@@ -119,18 +130,18 @@ export class ReaderArgs {
119
130
  if (!this.#isValidOptionName(short)) {
120
131
  throw new Error(`Invalid option name: -${short}`);
121
132
  }
122
- if (this.#keyByShort.has(short)) {
133
+ if (this.#optionContextByShort.has(short)) {
123
134
  throw new Error(`Option already registered: -${short}`);
124
135
  }
125
136
  for (let i = 0; i < short.length; i++) {
126
137
  const shortSlice = short.slice(0, i);
127
- if (this.#keyByShort.has(shortSlice)) {
138
+ if (this.#optionContextByShort.has(shortSlice)) {
128
139
  throw new Error(
129
140
  `Option -${short} overlap with shorter option: -${shortSlice}`,
130
141
  );
131
142
  }
132
143
  }
133
- for (const shortOther of this.#keyByShort.keys()) {
144
+ for (const shortOther of this.#optionContextByShort.keys()) {
134
145
  if (shortOther.startsWith(short)) {
135
146
  throw new Error(
136
147
  `Option -${short} overlap with longer option: -${shortOther}`,
@@ -138,35 +149,37 @@ export class ReaderArgs {
138
149
  }
139
150
  }
140
151
  }
152
+ const optionContext = {
153
+ parsing: definition.parsing,
154
+ results: new Array<ReaderOptionValue>(),
155
+ };
141
156
  for (const long of definition.longs) {
142
- this.#keyByLong.set(long, key);
157
+ this.#optionContextByLong.set(long, optionContext);
143
158
  }
144
159
  for (const short of definition.shorts) {
145
- this.#keyByShort.set(short, key);
160
+ this.#optionContextByShort.set(short, optionContext);
146
161
  }
147
- this.#valuedByKey.set(key, definition.valued);
148
- this.#resultByKey.set(key, new Array<string>());
162
+ this.#optionContextByKey.set(key, optionContext);
149
163
  return key;
150
164
  }
151
165
 
152
166
  /**
153
- * Returns all values collected for the option key.
167
+ * Returns all values collected for `key`.
154
168
  *
155
169
  * @param key - Key from {@link ReaderArgs.registerOption}.
156
- * @returns String values, one per occurrence.
170
+ * @returns One entry per occurrence.
157
171
  * @throws `Error` if `key` was not registered.
158
172
  */
159
- getOptionValues(key: ReaderOptionKey): Array<string> {
160
- const optionResult = this.#resultByKey.get(key);
161
- if (optionResult === undefined) {
173
+ getOptionValues(key: ReaderOptionKey): Array<ReaderOptionValue> {
174
+ const optionContext = this.#optionContextByKey.get(key);
175
+ if (optionContext === undefined) {
162
176
  throw new Error(`Unregistered option: ${key}`);
163
177
  }
164
- return optionResult;
178
+ return optionContext.results;
165
179
  }
166
180
 
167
181
  /**
168
- * Returns the next bare positional token.
169
- * Parse intervening options as side-effects.
182
+ * Returns the next positional token; parses intervening options as a side-effect.
170
183
  * All tokens after `--` are treated as positionals.
171
184
  *
172
185
  * @returns The next positional, or `undefined` when exhausted.
@@ -175,19 +188,19 @@ export class ReaderArgs {
175
188
  consumePositional(): string | undefined {
176
189
  while (true) {
177
190
  const arg = this.#consumeArg();
178
- if (arg === null) {
191
+ if (arg === undefined) {
179
192
  return undefined;
180
193
  }
181
- if (this.#processedAsPositional(arg)) {
194
+ if (!this.#tryConsumeAsOption(arg)) {
182
195
  return arg;
183
196
  }
184
197
  }
185
198
  }
186
199
 
187
- #consumeArg(): string | null {
200
+ #consumeArg(): string | undefined {
188
201
  const arg = this.#args[this.#parsedIndex];
189
202
  if (arg === undefined) {
190
- return null;
203
+ return undefined;
191
204
  }
192
205
  this.#parsedIndex++;
193
206
  if (!this.#parsedDouble) {
@@ -199,9 +212,9 @@ export class ReaderArgs {
199
212
  return arg;
200
213
  }
201
214
 
202
- #processedAsPositional(arg: string): boolean {
215
+ #tryConsumeAsOption(arg: string): boolean {
203
216
  if (this.#parsedDouble) {
204
- return true;
217
+ return false;
205
218
  }
206
219
  if (arg.startsWith("--")) {
207
220
  const valueIndexStart = arg.indexOf("=");
@@ -213,80 +226,90 @@ export class ReaderArgs {
213
226
  arg.slice(valueIndexStart + 1),
214
227
  );
215
228
  }
216
- return false;
229
+ return true;
217
230
  }
218
231
  if (arg.startsWith("-")) {
219
232
  let shortIndexStart = 1;
220
233
  let shortIndexEnd = 2;
221
234
  while (shortIndexEnd <= arg.length) {
222
- const result = this.#tryConsumeOptionShort(
223
- arg.slice(shortIndexStart, shortIndexEnd),
224
- arg.slice(shortIndexEnd),
225
- );
226
- if (result === true) {
227
- return false;
228
- }
229
- if (result === false) {
235
+ const short = arg.slice(shortIndexStart, shortIndexEnd);
236
+ const optionContext = this.#optionContextByShort.get(short);
237
+ if (optionContext !== undefined) {
238
+ const rest = arg.slice(shortIndexEnd);
239
+ if (this.#tryConsumeOptionShort(optionContext, short, rest)) {
240
+ return true;
241
+ }
230
242
  shortIndexStart = shortIndexEnd;
231
243
  }
232
244
  shortIndexEnd++;
233
245
  }
234
246
  throw new TypoError(
235
247
  new TypoText(
236
- new TypoString(`-${arg.slice(shortIndexStart)}`, typoStyleConstants),
237
- new TypoString(`: Unexpected unknown option`),
248
+ new TypoString(`Unexpected unknown option(s): `),
249
+ new TypoString(`-${arg.slice(shortIndexStart)}`, typoStyleQuote),
238
250
  ),
239
251
  );
240
252
  }
241
- return true;
253
+ return false;
242
254
  }
243
255
 
244
- #consumeOptionLong(long: string, direct: string | null): void {
256
+ #consumeOptionLong(long: string, inlined: string | null): void {
245
257
  const constant = `--${long}`;
246
- const key = this.#keyByLong.get(long);
247
- if (key !== undefined) {
248
- if (direct !== null) {
249
- return this.#acknowledgeOption(key, direct);
250
- }
251
- const valued = this.#valuedByKey.get(key);
252
- if (valued) {
253
- return this.#acknowledgeOption(key, this.#consumeOptionValue(constant));
254
- }
255
- return this.#acknowledgeOption(key, "true");
258
+ const optionContext = this.#optionContextByLong.get(long);
259
+ if (optionContext !== undefined) {
260
+ return this.#consumeOptionValues(optionContext, constant, inlined);
256
261
  }
257
262
  throw new TypoError(
258
263
  new TypoText(
259
- new TypoString(constant, typoStyleConstants),
260
- new TypoString(`: Unexpected unknown option`),
264
+ new TypoString(`Unexpected unknown option: `),
265
+ new TypoString(constant, typoStyleQuote),
261
266
  ),
262
267
  );
263
268
  }
264
269
 
265
- #tryConsumeOptionShort(short: string, rest: string): boolean | null {
266
- const key = this.#keyByShort.get(short);
267
- if (key !== undefined) {
268
- if (rest.startsWith("=")) {
269
- this.#acknowledgeOption(key, rest.slice(1));
270
- return true;
271
- }
272
- const valued = this.#valuedByKey.get(key);
273
- if (valued) {
274
- if (rest === "") {
275
- this.#acknowledgeOption(key, this.#consumeOptionValue(`-${short}`));
276
- } else {
277
- this.#acknowledgeOption(key, rest);
278
- }
279
- return true;
280
- }
281
- this.#acknowledgeOption(key, "true");
282
- return rest === "";
270
+ #tryConsumeOptionShort(
271
+ optionContext: ReaderOptionContext,
272
+ short: string,
273
+ rest: string,
274
+ ): boolean {
275
+ const constant = `-${short}`;
276
+ if (rest.startsWith("=")) {
277
+ this.#consumeOptionValues(optionContext, constant, rest.slice(1));
278
+ return true;
279
+ }
280
+ if (rest.length === 0) {
281
+ this.#consumeOptionValues(optionContext, constant, null);
282
+ return true;
283
+ }
284
+ if (optionContext.parsing.consumeShortGroup) {
285
+ this.#consumeOptionValues(optionContext, constant, rest);
286
+ return true;
283
287
  }
284
- return null;
288
+ this.#consumeOptionValues(optionContext, constant, null);
289
+ return false;
285
290
  }
286
291
 
287
- #consumeOptionValue(constant: string) {
292
+ #consumeOptionValues(
293
+ optionContext: ReaderOptionContext,
294
+ constant: string,
295
+ inlined: string | null,
296
+ ) {
297
+ const separated = new Array<string>();
298
+ while (
299
+ optionContext.parsing.consumeNextArg(
300
+ inlined,
301
+ separated,
302
+ this.#args[this.#parsedIndex],
303
+ )
304
+ ) {
305
+ separated.push(this.#consumeOptionValue(constant));
306
+ }
307
+ optionContext.results.push({ inlined, separated });
308
+ }
309
+
310
+ #consumeOptionValue(constant: string): string {
288
311
  const arg = this.#consumeArg();
289
- if (arg === null) {
312
+ if (arg === undefined) {
290
313
  throw new TypoError(
291
314
  new TypoText(
292
315
  new TypoString(constant, typoStyleConstants),
@@ -316,11 +339,12 @@ export class ReaderArgs {
316
339
  return arg;
317
340
  }
318
341
 
319
- #acknowledgeOption(key: ReaderOptionKey, value: string) {
320
- this.getOptionValues(key).push(value);
321
- }
322
-
323
342
  #isValidOptionName(name: string): boolean {
324
343
  return name.length > 0 && !name.includes("=");
325
344
  }
326
345
  }
346
+
347
+ type ReaderOptionContext = {
348
+ parsing: ReaderOptionParsing;
349
+ results: Array<ReaderOptionValue>;
350
+ };
package/src/lib/Run.ts CHANGED
@@ -7,15 +7,16 @@ import { usageToStyledLines } from "./Usage";
7
7
  * Main entry point: parses CLI arguments, executes the matched command, and exits.
8
8
  * Handles `--help`, `--version`, usage-on-error, and exit codes.
9
9
  *
10
- * Exit codes: `0` on success / `--help` / `--version`; `1` on parse or execution error.
10
+ * Exit codes:
11
+ * - `0` on success / `--help` / `--version`
12
+ * - `1` on parse error or execution error.
11
13
  *
12
- * @typeParam Context - Passed unchanged to the command handler; use to inject dependencies.
14
+ * @typeParam Context - Forwarded unchanged to the handler.
13
15
  *
14
16
  * @param cliName - Program name used in usage and `--version` output.
15
17
  * @param cliArgs - Raw arguments, typically `process.argv.slice(2)`.
16
- * @param context - Forwarded to the command handler, injected dependencies.
18
+ * @param context - Forwarded to the handler.
17
19
  * @param command - Root {@link Command}.
18
- * @param options - Optional runner configuration.
19
20
  * @param options.useTtyColors - Color mode: `true` (always), `false` (never),
20
21
  * `"mock"` (snapshot-friendly), `undefined` (auto-detect from env).
21
22
  * @param options.usageOnHelp - Enables `--help` flag (default `true`).
@@ -34,7 +35,7 @@ import { usageToStyledLines } from "./Usage";
34
35
  * { description: "Greet someone" },
35
36
  * operation(
36
37
  * { options: {}, positionals: [positionalRequired({ type: typeString, label: "NAME" })] },
37
- * async (_ctx, { positionals: [name] }) => {
38
+ * async function (_ctx, { positionals: [name] }) {
38
39
  * console.log(`Hello, ${name}!`);
39
40
  * },
40
41
  * ),
@@ -51,7 +52,7 @@ export async function runAndExit<Context>(
51
52
  context: Context,
52
53
  command: Command<Context, void>,
53
54
  options?: {
54
- useTtyColors?: boolean | undefined | "mock";
55
+ useTtyColors?: boolean | undefined | "mock"; // TODO - flag setter option
55
56
  usageOnHelp?: boolean | undefined;
56
57
  usageOnError?: boolean | undefined;
57
58
  buildVersion?: string | undefined;
@@ -65,7 +66,10 @@ export async function runAndExit<Context>(
65
66
  readerArgs.registerOption({
66
67
  shorts: [],
67
68
  longs: ["help"],
68
- valued: false,
69
+ parsing: {
70
+ consumeShortGroup: false,
71
+ consumeNextArg: () => false,
72
+ },
69
73
  });
70
74
  }
71
75
  const buildVersion = options?.buildVersion;
@@ -73,7 +77,10 @@ export async function runAndExit<Context>(
73
77
  readerArgs.registerOption({
74
78
  shorts: [],
75
79
  longs: ["version"],
76
- valued: false,
80
+ parsing: {
81
+ consumeShortGroup: false,
82
+ consumeNextArg: () => false,
83
+ },
77
84
  });
78
85
  }
79
86
  /*
@@ -84,6 +91,7 @@ export async function runAndExit<Context>(
84
91
  longs: ["completion"],
85
92
  });
86
93
  */
94
+ // TODO - handle color flag ?
87
95
  const commandDecoder = command.consumeAndMakeDecoder(readerArgs);
88
96
  while (true) {
89
97
  try {
@@ -93,15 +101,8 @@ export async function runAndExit<Context>(
93
101
  }
94
102
  } catch (_) {}
95
103
  }
104
+ const typoSupport = computeTypoSupport(options?.useTtyColors);
96
105
  const onExit = options?.onExit ?? process.exit;
97
- const typoSupport =
98
- options?.useTtyColors === undefined
99
- ? TypoSupport.inferFromProcess()
100
- : options.useTtyColors === "mock"
101
- ? TypoSupport.mock()
102
- : options.useTtyColors
103
- ? TypoSupport.tty()
104
- : TypoSupport.none();
105
106
  if (usageOnHelp) {
106
107
  if (readerArgs.getOptionValues("--help" as any).length > 0) {
107
108
  console.log(computeUsageString(cliName, commandDecoder, typoSupport));
@@ -151,7 +152,19 @@ function computeUsageString<Context, Result>(
151
152
  ) {
152
153
  return usageToStyledLines({
153
154
  cliName,
154
- commandUsage: commandDecoder.generateUsage(),
155
+ usage: commandDecoder.generateUsage(),
155
156
  typoSupport,
156
157
  }).join("\n");
157
158
  }
159
+
160
+ function computeTypoSupport(
161
+ useTtyColors: boolean | undefined | "mock",
162
+ ): TypoSupport {
163
+ return useTtyColors === undefined
164
+ ? TypoSupport.inferFromProcess()
165
+ : useTtyColors === "mock"
166
+ ? TypoSupport.mock()
167
+ : useTtyColors
168
+ ? TypoSupport.tty()
169
+ : TypoSupport.none();
170
+ }
package/src/lib/Type.ts CHANGED
@@ -18,11 +18,11 @@ import {
18
18
  */
19
19
  export type Type<Value> = {
20
20
  /**
21
- * Human-readable name shown in help and error messages (e.g. `"String"`, `"Number"`).
21
+ * Human-readable name shown in help and errors (e.g. `"String"`, `"Number"`).
22
22
  */
23
23
  content: string;
24
24
  /**
25
- * Converts a raw CLI string into `Value`.
25
+ * Decodes a raw CLI string into `Value`.
26
26
  *
27
27
  * @param input - Raw string from the command line.
28
28
  * @returns The decoded value.
@@ -32,24 +32,27 @@ export type Type<Value> = {
32
32
  };
33
33
 
34
34
  /**
35
- * Decodes `"true"` / `"yes"` `true` and `"false"` / `"no"` → `false` (case-insensitive).
36
- * Used internally by {@link optionFlag} for the `--flag=<value>` syntax.
35
+ * Decodes a string to `boolean` (case-insensitive).
36
+ * Used by {@link optionFlag} for `--flag=<value>`.
37
37
  *
38
38
  * @example
39
39
  * ```ts
40
+ * typeBoolean.decoder("true") // → true
40
41
  * typeBoolean.decoder("yes") // → true
42
+ * typeBoolean.decoder("y") // → true
41
43
  * typeBoolean.decoder("false") // → false
42
- * typeBoolean.decoder("1") // throws TypoError
44
+ * typeBoolean.decoder("no") // false
45
+ * typeBoolean.decoder("n") // → false
43
46
  * ```
44
47
  */
45
48
  export const typeBoolean: Type<boolean> = {
46
49
  content: "Boolean",
47
50
  decoder(input: string) {
48
51
  const lower = input.toLowerCase();
49
- if (lower === "true" || lower === "yes") {
52
+ if (booleanValuesTrue.has(lower)) {
50
53
  return true;
51
54
  }
52
- if (lower === "false" || lower === "no") {
55
+ if (booleanValuesFalse.has(lower)) {
53
56
  return false;
54
57
  }
55
58
  throw new TypoError(
@@ -60,9 +63,11 @@ export const typeBoolean: Type<boolean> = {
60
63
  );
61
64
  },
62
65
  };
66
+ const booleanValuesTrue = new Set(["true", "yes", "on", "1", "y", "t"]);
67
+ const booleanValuesFalse = new Set(["false", "no", "off", "0", "n", "f"]);
63
68
 
64
69
  /**
65
- * Parses a date/time string via `Date.parse` into a `Date` object.
70
+ * Parses a date/time string via `Date.parse`.
66
71
  * Accepts any format supported by `Date.parse`, including ISO 8601.
67
72
  *
68
73
  * @example
@@ -93,8 +98,7 @@ export const typeDate: Type<Date> = {
93
98
  };
94
99
 
95
100
  /**
96
- * Parses a string into a `number` via `Number()`.
97
- * Accepts integers, floats, and scientific notation; `NaN` throws a {@link TypoError}.
101
+ * Parses a string to `number` via `Number()`; `NaN` throws {@link TypoError}.
98
102
  *
99
103
  * @example
100
104
  * ```ts
@@ -124,8 +128,8 @@ export const typeNumber: Type<number> = {
124
128
  };
125
129
 
126
130
  /**
127
- * Parses an integer string into a `bigint` via `BigInt()`.
128
- * Floats and non-numeric strings throw a {@link TypoError}.
131
+ * Parses an integer string to `bigint` via `BigInt()`.
132
+ * Floats and non-numeric strings throw {@link TypoError}.
129
133
  *
130
134
  * @example
131
135
  * ```ts
@@ -151,8 +155,8 @@ export const typeInteger: Type<bigint> = {
151
155
  };
152
156
 
153
157
  /**
154
- * Parses an absolute URL string into a `URL` object.
155
- * Relative or malformed URLs throw a {@link TypoError}.
158
+ * Parses an absolute URL string to a `URL` object.
159
+ * Relative or malformed URLs throw {@link TypoError}.
156
160
  *
157
161
  * @example
158
162
  * ```ts
@@ -193,7 +197,7 @@ export const typeString: Type<string> = {
193
197
  };
194
198
 
195
199
  /**
196
- * Creates a {@link Type} by chaining `before`'s decoder with an `after` transformation.
200
+ * Chains `before`'s decoder with an `after` transformation.
197
201
  * `before` errors are prefixed with `"from: <content>"` for traceability.
198
202
  *
199
203
  * @typeParam Before - Intermediate type from `before.decoder`.
@@ -240,8 +244,8 @@ export function typeMapped<Before, After>(
240
244
  }
241
245
 
242
246
  /**
243
- * Creates a {@link Type}`<string>` accepting only a fixed set of string values.
244
- * Out-of-set inputs throw a {@link TypoError} listing up to 5 valid options.
247
+ * Creates a {@link Type}`<string>` that only accepts a fixed set of values.
248
+ * Out-of-set inputs throw {@link TypoError} listing up to 5 valid options.
245
249
  *
246
250
  * @param content - Name shown in help and errors (e.g. `"Environment"`).
247
251
  * @param values - Ordered list of accepted values.
@@ -291,7 +295,7 @@ export function typeOneOf<const Value extends string>(
291
295
  }
292
296
 
293
297
  /**
294
- * Splits a delimited string into a fixed-length typed tuple.
298
+ * Splits a delimited string into a typed tuple.
295
299
  * Each part is decoded by the corresponding element type; wrong count or decode failure throws {@link TypoError}.
296
300
  *
297
301
  * @typeParam Elements - Tuple of decoded value types (inferred from `elementTypes`).
@@ -316,14 +320,14 @@ export function typeTuple<const Elements extends Array<any>>(
316
320
  content: elementTypes
317
321
  .map((elementType) => elementType.content)
318
322
  .join(separator),
319
- decoder(value: string) {
320
- const splits = value.split(separator, elementTypes.length);
323
+ decoder(input: string) {
324
+ const splits = input.split(separator, elementTypes.length);
321
325
  if (splits.length !== elementTypes.length) {
322
326
  throw new TypoError(
323
327
  new TypoText(
324
328
  new TypoString(`Found ${splits.length} splits: `),
325
329
  new TypoString(`Expected ${elementTypes.length} splits from: `),
326
- new TypoString(`"${value}"`, typoStyleQuote),
330
+ new TypoString(`"${input}"`, typoStyleQuote),
327
331
  ),
328
332
  );
329
333
  }
@@ -343,7 +347,7 @@ export function typeTuple<const Elements extends Array<any>>(
343
347
  }
344
348
 
345
349
  /**
346
- * Splits a delimited string into a variable-length typed array.
350
+ * Splits a delimited string into a typed array.
347
351
  * Each part is decoded by `elementType`; failed decodes throw {@link TypoError}.
348
352
  * Note: splitting an empty string yields one empty element — prefer {@link optionRepeatable} for a zero-default.
349
353
  *