icore 1.0.2 → 1.0.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/readme.md CHANGED
@@ -5,77 +5,28 @@
5
5
  [![types](https://img.shields.io/npm/types/icore.svg)](https://www.npmjs.com/package/icore)
6
6
  [![license](https://img.shields.io/npm/l/icore.svg)](LICENSE)
7
7
 
8
- Small dependency-free command line interface mechanics for [Node.js®](https://nodejs.org) applications.
8
+ Small dependency-free command line interface mechanics for Node.js applications.
9
9
 
10
- ### How it works?
10
+ `icore` describes CLI commands with typed option schemas, resolves command
11
+ registries, validates primitive options, and passes typed input to handlers. It
12
+ stops at CLI mechanics: your application still owns business rules, SDK calls,
13
+ process lifecycle, and output formatting.
11
14
 
12
- ![yuml diagram](http://yuml.me/diagram/scruffy;dir:LR;/class/[*argv*%20{bg:gray}|External;users%20get%20--limit%2010%20--json]->[*matches*%20{bg:lavender}|System;parse,%20resolve,%20validate,%20infer]->[*typed%20result*%20{bg:honeydew}|Container;command=users/get;%20limit=10;%20json=true]->[*your%20app*%20{bg:cornsilk}|System;business%20logic%20and%20output])
13
-
14
- `icore` helps describe CLI commands with small option schemas and keeps command
15
- handlers focused on application work. It standardizes argument parsing,
16
- primitive option validation, defaults, choices, numeric ranges, and positional
17
- argument checks. It can also resolve commands from a registry and report whether
18
- each option was provided explicitly by the user or filled from a default.
19
-
20
- ## Non-goals
15
+ ## How It Works
21
16
 
22
- `icore` stops at CLI mechanics. It does not build API DTOs, call SDKs,
23
- format output, manage process lifecycle, or validate business rules.
17
+ ![yuml diagram](http://yuml.me/diagram/scruffy;dir:LR;/class/[*argv*%20{bg:gray}|External;users%20get%20--limit%2010%20--json]->[*matches*%20{bg:lavender}|System;parse,%20resolve,%20validate,%20infer]->[*typed%20result*%20{bg:honeydew}|Container;command=users/get;%20limit=10;%20json=true]->[*your%20app*%20{bg:cornsilk}|System;business%20logic%20and%20output])
24
18
 
25
- ## Table of Contents
19
+ [API Reference](docs/api.md) | [Examples](docs/examples.md)
26
20
 
27
- - [Installation](#installation)
28
- - [Requirements](#requirements)
29
- - [Non-goals](#non-goals)
30
- - [Why icore?](#why-icore)
31
- - [Supported Syntax](#supported-syntax)
32
- - [Basic Usage](#basic-usage)
33
- - [Design Goals](#design-goals)
34
- - [What icore Handles](#what-icore-handles)
35
- - [What icore Does Not Handle](#what-icore-does-not-handle)
36
- - [Option Schemas](#option-schemas)
37
- - [API](#api)
21
+ ## Install
38
22
 
39
- ## Installation
23
+ To use `icore` in your project, run:
40
24
 
41
25
  ```sh
42
26
  npm install icore
43
27
  ```
44
28
 
45
- ## Requirements
46
-
47
- - Node.js `>=16.9.0`
48
- - TypeScript is supported through bundled declaration files.
49
-
50
- ## Why icore?
51
-
52
- Use `icore` when you need more than raw argument parsing:
53
-
54
- - typed command definitions;
55
- - command registry resolution;
56
- - required options;
57
- - string choices;
58
- - number parsing with integer, minimum, and maximum constraints;
59
- - option presence metadata;
60
- - typed command handler input.
61
-
62
- Use [`node:util.parseArgs`](https://nodejs.org/api/util.html#utilparseargsconfig)
63
- directly when you only need low-level argument parsing.
64
-
65
- ## Supported Syntax
66
-
67
- | Syntax | Supported | Notes |
68
- |---|---:|---|
69
- | `--name value` | yes | string and number options |
70
- | `--name=value` | yes | string and number options |
71
- | `--flag` | yes | boolean options |
72
- | `--flag=true` | no | boolean options are flag-only |
73
- | `-f` | no | short aliases are not supported |
74
- | `--no-cache` | no | negative boolean flags are not supported |
75
- | repeated options | no | duplicates are rejected |
76
- | multiple values | no | arrays are not supported |
77
-
78
- ## Basic Usage
29
+ ## Quick Start
79
30
 
80
31
  ```ts
81
32
  import { defineCommand, runCommand } from 'icore';
@@ -107,542 +58,45 @@ const output = await runCommand(
107
58
  console.log(output);
108
59
  ```
109
60
 
110
- The command handler receives parsed options, user-provided option metadata,
111
- remaining positionals, and caller provided context.
112
-
113
- CommonJS is supported:
114
-
115
- ```js
116
- const {
117
- defineCommand,
118
- runCommand
119
- } = require('icore');
120
- ```
121
-
122
- ## Design Goals
123
-
124
- - describe CLI mechanics declaratively;
125
- - use literal option types: `type: 'string' | 'boolean' | 'number'`;
126
- - keep handlers free from repetitive option parsing;
127
- - keep domain and API semantics outside the framework;
128
- - provide predictable user-facing errors;
129
- - avoid runtime dependencies.
130
-
131
- ## What icore Handles
132
-
133
- `icore` handles generic CLI mechanics:
134
-
135
- - parsing long options;
136
- - validating known options;
137
- - rejecting duplicated options;
138
- - checking required options;
139
- - applying and validating defaults;
140
- - validating string choices;
141
- - validating boolean flag form;
142
- - composing option schemas;
143
- - defining command registries;
144
- - resolving commands by path;
145
- - preserving user-provided option metadata;
146
- - parsing numbers;
147
- - validating integer, minimum, and maximum numeric constraints;
148
- - checking command path and extra positional arguments.
149
-
150
- ## What icore Does Not Handle
151
-
152
- `icore` intentionally does not handle application-specific behavior:
153
-
154
- - building API request DTOs;
155
- - calling SDKs or network clients;
156
- - managing database, HTTP, or gRPC lifecycle;
157
- - checking business invariants such as `from <= to`;
158
- - resolving mutually exclusive command modes;
159
- - formatting output as JSON, tables, CSV, or other application formats.
160
-
161
- Keep those decisions near the command handler or in your application layer.
162
-
163
- ## Option Schemas
164
-
165
- Options are described as plain objects.
166
-
167
- Option names are exact. `icore` does not normalize camelCase to kebab-case or
168
- kebab-case to camelCase. Use quoted object keys when your public CLI option
169
- contains `-`.
170
-
171
- ### String Options
172
-
173
- ```ts
174
- const schema = {
175
- token: {
176
- type: 'string',
177
- required: true
178
- },
179
- format: {
180
- type: 'string',
181
- choices: ['json', 'table'],
182
- default: 'table'
183
- }
184
- } as const;
185
- ```
186
-
187
- String options reject missing required values, blank strings, boolean flag form,
188
- and values outside `choices`.
189
-
190
- ### Boolean Options
191
-
192
- ```ts
193
- const schema = {
194
- insecure: {
195
- type: 'boolean'
196
- }
197
- } as const;
198
- ```
199
-
200
- Boolean options accept flag form only:
201
-
202
- ```sh
203
- --insecure
204
- ```
205
-
206
- Explicit values are rejected:
207
-
208
- ```sh
209
- --insecure true
210
- --insecure=true
211
- ```
212
-
213
- ### Number Options
214
-
215
- ```ts
216
- const schema = {
217
- limit: {
218
- type: 'number',
219
- integer: true,
220
- min: 1,
221
- max: 1000,
222
- default: 100
223
- }
224
- } as const;
225
- ```
226
-
227
- Number options parse decimal numeric values and can validate integer and range
228
- constraints. Defaults are validated with the same rules as user-provided values.
229
-
230
- ## API
231
-
232
- ### `parseArgv(args, schema?)`
233
-
234
- Parses raw CLI arguments into positionals and raw option values.
235
-
236
- ```ts
237
- import { parseArgv } from 'icore';
238
-
239
- const argv = parseArgv([
240
- 'users',
241
- 'get-accounts',
242
- '--format=json',
243
- '--insecure'
244
- ]);
245
- ```
246
-
247
- Result:
248
-
249
- ```ts
250
- {
251
- positionals: ['users', 'get-accounts'],
252
- options: {
253
- format: 'json',
254
- insecure: true
255
- }
256
- }
257
- ```
258
-
259
- When an option schema is provided, boolean options are parsed as flag-only
260
- options without consuming the following positional argument:
261
-
262
- ```ts
263
- const argv = parseArgv([
264
- 'users',
265
- 'get-accounts',
266
- '--insecure',
267
- 'extra'
268
- ], {
269
- insecure: {
270
- type: 'boolean'
271
- }
272
- });
273
- ```
274
-
275
- Result:
276
-
277
- ```ts
278
- {
279
- positionals: ['users', 'get-accounts', 'extra'],
280
- options: {
281
- insecure: true
282
- }
283
- }
284
- ```
285
-
286
- ### `parseOptions(schema, values)`
287
-
288
- Validates raw option values using an option schema and returns typed options.
289
-
290
- ```ts
291
- import { parseOptions } from 'icore';
292
-
293
- const options = parseOptions({
294
- format: {
295
- type: 'string',
296
- choices: ['json', 'table'],
297
- default: 'table'
298
- },
299
- depth: {
300
- type: 'number',
301
- integer: true,
302
- min: 1,
303
- required: true
304
- }
305
- } as const, {
306
- depth: '10'
307
- });
308
- ```
309
-
310
- Result:
311
-
312
- ```ts
313
- {
314
- format: 'table',
315
- depth: 10
316
- }
317
- ```
318
-
319
- ### `parseOptionsDetailed(schema, values)`
320
-
321
- Validates raw option values and returns parsed options together with
322
- user-provided metadata.
323
-
324
- ```ts
325
- import { parseOptionsDetailed } from 'icore';
326
-
327
- const result = parseOptionsDetailed({
328
- token: {
329
- type: 'string',
330
- required: true
331
- },
332
- format: {
333
- type: 'string',
334
- choices: ['json', 'table'],
335
- default: 'table'
336
- }
337
- } as const, {
338
- token: 'secret'
339
- });
340
- ```
341
-
342
- Result:
343
-
344
- ```ts
345
- {
346
- options: {
347
- token: 'secret',
348
- format: 'table'
349
- },
350
- provided: {
351
- token: true,
352
- format: false
353
- }
354
- }
355
- ```
356
-
357
- `provided` is useful when a default value and an omitted option have different
358
- application-level meaning.
359
-
360
- ### `defineCommand(command)`
361
-
362
- Defines a command while preserving its option schema types.
363
-
364
- ```ts
365
- import { defineCommand } from 'icore';
366
-
367
- const command = defineCommand({
368
- path: ['marketdata', 'get-order-book'],
369
- options: {
370
- 'instrument-id': {
371
- type: 'string',
372
- required: true
373
- },
374
- depth: {
375
- type: 'number',
376
- integer: true,
377
- min: 1,
378
- required: true
379
- },
380
- format: {
381
- type: 'string',
382
- choices: ['json', 'table'],
383
- default: 'table'
384
- }
385
- },
386
- async handle({ options, context }) {
387
- const response = await context.sdk.marketdata.getOrderBook({
388
- instrumentId: options['instrument-id'],
389
- depth: options.depth
390
- });
391
-
392
- return context.formatOrderBook(response, options.format);
393
- }
394
- });
395
- ```
396
-
397
- ### `defineCommandRegistry(commands)`
398
-
399
- Defines a command registry while preserving literal command path types.
400
-
401
- ```ts
402
- import { defineCommandRegistry } from 'icore';
403
-
404
- const registry = defineCommandRegistry([
405
- usersGetAccountsCommand,
406
- marketdataGetOrderBookCommand
407
- ] as const);
408
- ```
409
-
410
- The registry exposes ordered commands and derived command names:
411
-
412
- ```ts
413
- registry.commandNames;
414
- ```
415
-
416
- Result:
417
-
418
- ```ts
419
- [
420
- 'users get-accounts',
421
- 'marketdata get-order-book'
422
- ]
423
- ```
424
-
425
- Duplicate command paths are rejected.
426
-
427
- ### `isCommandName(registry, value)`
428
-
429
- Checks whether an unknown value is a command name registered in the registry.
430
-
431
- ```ts
432
- if (isCommandName(registry, value)) {
433
- // value is narrowed to the registry command name union.
434
- }
435
- ```
436
-
437
- ### `resolveCommand(registry, positionals)`
438
-
439
- Resolves a command from already parsed positional arguments.
440
-
441
- ```ts
442
- import { resolveCommand } from 'icore';
443
-
444
- const resolved = resolveCommand(registry, [
445
- 'users',
446
- 'get-accounts',
447
- 'extra'
448
- ]);
449
- ```
450
-
451
- Result:
452
-
453
- ```ts
454
- {
455
- name: 'users get-accounts',
456
- path: ['users', 'get-accounts'],
457
- command: usersGetAccountsCommand,
458
- positionals: ['extra']
459
- }
460
- ```
461
-
462
- When several command paths match, the most specific command wins. For example,
463
- `users get-accounts` is preferred over `users`.
464
-
465
- ### `resolveCommandFromArgs(registry, args)`
466
-
467
- Resolves a command from raw CLI arguments. Each candidate command is parsed with
468
- its own option schema, so boolean flags do not accidentally consume command path
469
- segments.
470
-
471
- ```ts
472
- const resolved = resolveCommandFromArgs(registry, [
473
- '--verbose',
474
- 'users',
475
- 'get-accounts'
476
- ]);
477
- ```
478
-
479
- ### `runCommandFromRegistry(registry, args, context)`
480
-
481
- Resolves a command from a registry and runs its handler.
482
-
483
- ```ts
484
- const output = await runCommandFromRegistry(
485
- registry,
486
- ['users', 'get-accounts', '--format', 'json'],
487
- context
488
- );
489
- ```
490
-
491
- This is registry-level mechanics only. Application-specific setup, API request
492
- building, and output formatting still belong outside `icore`.
493
-
494
- ### `mergeOptionsSchema(...schemas)`
495
-
496
- Merges multiple option schemas while preserving literal option definition types.
497
- Later schemas override earlier schemas with the same option name.
498
-
499
- ```ts
500
- import { mergeOptionsSchema } from 'icore';
501
-
502
- const sdkOptions = {
503
- token: {
504
- type: 'string',
505
- required: true
506
- }
507
- } as const;
508
-
509
- const formatOptions = {
510
- format: {
511
- type: 'string',
512
- choices: ['json', 'table'],
513
- default: 'table'
514
- }
515
- } as const;
516
-
517
- const options = mergeOptionsSchema(sdkOptions, formatOptions);
518
- ```
519
-
520
- ### `runCommand(command, args, context)`
521
-
522
- Parses arguments, validates options, checks command positionals, and runs the
523
- handler.
524
-
525
- ```ts
526
- const output = await runCommand(
527
- command,
528
- [
529
- 'marketdata',
530
- 'get-order-book',
531
- '--instrument-id',
532
- 'instrument-id',
533
- '--depth',
534
- '10'
535
- ],
536
- context
537
- );
538
- ```
539
-
540
- By default, extra positionals are rejected. A command can opt in to extra
541
- positionals with `allowExtraPositionals: true`.
542
-
543
- ## Type Inference
544
-
545
- Use `InferOptions` when you need the parsed option type explicitly.
546
-
547
- ```ts
548
- import type { InferOptions } from 'icore';
549
-
550
- const schema = {
551
- format: {
552
- type: 'string',
553
- choices: ['json', 'table'],
554
- default: 'table'
555
- },
556
- dryRun: {
557
- type: 'boolean'
558
- }
559
- } as const;
560
-
561
- type Options = InferOptions<typeof schema>;
562
- ```
563
-
564
- `Options` is equivalent to:
565
-
566
- ```ts
567
- type Options = {
568
- format: 'json' | 'table';
569
- dryRun: boolean | undefined;
570
- };
571
- ```
572
-
573
- Required options and options with defaults are always present. Optional options
574
- without defaults are returned as `T | undefined`.
575
-
576
- Use `InferProvidedOptions` when you need the option presence type explicitly.
577
-
578
- ```ts
579
- import type { InferProvidedOptions } from 'icore';
580
-
581
- type Provided = InferProvidedOptions<typeof schema>;
582
- ```
583
-
584
- `Provided` maps every schema option to `boolean`. `true` means the user
585
- specified that option explicitly; defaults keep the flag `false`.
586
-
587
- Use `MergeOptionsSchemas` when you need the merged schema type explicitly.
588
-
589
- ```ts
590
- import type { MergeOptionsSchemas } from 'icore';
591
-
592
- type Schema = MergeOptionsSchemas<[typeof sdkOptions, typeof formatOptions]>;
593
- ```
594
-
595
- Use `CommandName` when you need the inferred command name type explicitly.
596
-
597
- ```ts
598
- import type { CommandName } from 'icore';
599
-
600
- type Name = CommandName<typeof usersGetAccountsCommand>;
601
- ```
602
-
603
- `Name` is equivalent to:
604
-
605
- ```ts
606
- type Name = 'users get-accounts';
607
- ```
61
+ ## Why icore?
608
62
 
609
- ## Error Messages
63
+ Use `icore` when you need more than raw argument parsing:
610
64
 
611
- `icore` throws regular `Error` objects with predictable user-facing messages.
612
- Applications should treat these messages as display text, not as a
613
- machine-readable API.
65
+ - typed command definitions;
66
+ - command registry resolution;
67
+ - required options;
68
+ - string choices;
69
+ - number parsing with integer, minimum, and maximum constraints;
70
+ - option presence metadata;
71
+ - typed command handler input.
614
72
 
615
- Examples:
73
+ Use [`node:util.parseArgs`](https://nodejs.org/api/util.html#utilparseargsconfig)
74
+ directly when you only need low-level argument parsing.
616
75
 
617
- ```txt
618
- Unexpected argument '--unknown'
619
- Unexpected duplicate argument '--format'
620
- Unexpected duplicate command 'users get-accounts'
621
- Unknown command: users get-unknown
622
- Expected required argument '--token'
623
- Expected '--format' as one of: json, table
624
- Expected '--depth' as integer
625
- Expected '--depth' to be greater than or equal to 1
626
- ```
76
+ ## Supported Syntax
627
77
 
628
- Applications can catch these errors and decide how to print them.
78
+ | Syntax | Supported | Notes |
79
+ |---|---:|---|
80
+ | `--name value` | yes | string and number options |
81
+ | `--name=value` | yes | string and number options |
82
+ | `--flag` | yes | boolean options |
83
+ | `--flag=true` | no | boolean options are flag-only |
84
+ | `-f` | no | short aliases are not supported |
85
+ | `--no-cache` | no | negative boolean flags are not supported |
86
+ | repeated options | no | duplicates are rejected |
87
+ | multiple values | no | arrays are not supported |
629
88
 
630
- ## Project Boundary
89
+ ## Documentation
631
90
 
632
- `icore` is intended to be a small CLI mechanics module. It should not grow into a
633
- domain-specific framework for a particular SDK or API.
91
+ - [Design notes](docs/design.md)
92
+ - [Project policies](docs/policy/index.md)
634
93
 
635
- Good responsibilities for `icore`:
94
+ ## Requirements
636
95
 
637
- - option schema evaluation;
638
- - command path checking;
639
- - common argument errors;
640
- - typed command handler input.
96
+ - Node.js `>=16.9.0`
97
+ - TypeScript declarations are included.
98
+ - Runtime dependencies: none.
641
99
 
642
- Responsibilities that should stay outside `icore`:
100
+ ## License
643
101
 
644
- - business validation;
645
- - SDK lifecycle management;
646
- - provider-specific request modes;
647
- - generated contract mapping;
648
- - presentation formatting.
102
+ [MIT](LICENSE)