@avstantso/utils-names-tree 1.2.1 → 1.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.
- package/CHANGELOG.md +22 -0
- package/README.md +309 -133
- package/dist/_global/_register.d.ts +287 -38
- package/dist/index.js +142 -19
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -40,9 +40,9 @@ import '@avstantso/utils-names-tree';
|
|
|
40
40
|
|
|
41
41
|
// Use utilities from the global namespace
|
|
42
42
|
const formTree = AVStantso.NamesTree.Names({
|
|
43
|
-
form:
|
|
44
|
-
label:
|
|
45
|
-
placeholder:
|
|
43
|
+
form: 1,
|
|
44
|
+
label: 1,
|
|
45
|
+
placeholder: 1
|
|
46
46
|
});
|
|
47
47
|
```
|
|
48
48
|
|
|
@@ -55,9 +55,9 @@ import { NamesTree } from '@avstantso/utils-names-tree';
|
|
|
55
55
|
|
|
56
56
|
// Use imported NamesTree namespace
|
|
57
57
|
const formTree = NamesTree.Names({
|
|
58
|
-
form:
|
|
59
|
-
label:
|
|
60
|
-
placeholder:
|
|
58
|
+
form: 1,
|
|
59
|
+
label: 1,
|
|
60
|
+
placeholder: 1
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
console.log(`${formTree.label}`); // 'label'
|
|
@@ -94,11 +94,11 @@ import { NamesTree } from '@avstantso/utils-names-tree';
|
|
|
94
94
|
|
|
95
95
|
const data = {
|
|
96
96
|
user: {
|
|
97
|
-
profile:
|
|
98
|
-
settings:
|
|
97
|
+
profile: 1,
|
|
98
|
+
settings: 1
|
|
99
99
|
},
|
|
100
100
|
admin: {
|
|
101
|
-
dashboard:
|
|
101
|
+
dashboard: 1
|
|
102
102
|
}
|
|
103
103
|
};
|
|
104
104
|
|
|
@@ -116,37 +116,45 @@ console.log(tree.user.profile._path); // 'user/profile'
|
|
|
116
116
|
|
|
117
117
|
## Built-in Kinds
|
|
118
118
|
|
|
119
|
-
NamesTree comes with
|
|
120
|
-
|
|
121
|
-
###
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
119
|
+
NamesTree comes with 20+ predefined kind methods that determine how tree paths are formatted.
|
|
120
|
+
|
|
121
|
+
### Basic Kinds
|
|
122
|
+
|
|
123
|
+
| Kind | Separator | Example Input | Example Output |
|
|
124
|
+
|------|-----------|---------------|----------------|
|
|
125
|
+
| `name` | - | `['user', 'profile']` | `'profile'` |
|
|
126
|
+
| `slash` / `path` | `/` | `['user', 'profile']` | `'user/profile'` |
|
|
127
|
+
| `backslash` | `\` | `['user', 'profile']` | `'user\profile'` |
|
|
128
|
+
| `dot` / `i18n` / `domain` | `.` | `['schemas', 'login']` | `'schemas.login'` |
|
|
129
|
+
| `dash` | `-` | `['btn', 'primary']` | `'btn-primary'` |
|
|
130
|
+
| `underscore` / `snake` | `_` | `['user', 'name']` | `'user_name'` |
|
|
131
|
+
| `doubleColon` / `namespace` | `::` | `['std', 'vector']` | `'std::vector'` |
|
|
132
|
+
| `colon` | `:` | `['a', 'b']` | `'a:b'` |
|
|
133
|
+
| `arrow` | `->` | `['a', 'b']` | `'a->b'` |
|
|
134
|
+
| `amp` | `&` | `['a', 'b']` | `'a&b'` |
|
|
135
|
+
| `hash` | `#` | `['a', 'b']` | `'a#b'` |
|
|
136
|
+
| `comma` / `csv` | `,` | `['a', 'b']` | `'a,b'` |
|
|
137
|
+
| `semicolon` | `;` | `['a', 'b']` | `'a;b'` |
|
|
138
|
+
| `space` / `title` | ` ` | `['hello', 'world']` | `'hello world'` |
|
|
139
|
+
| `pipe` | `\|` | `['a', 'b']` | `'a\|b'` |
|
|
140
|
+
|
|
141
|
+
### Derived Kinds (with prefixes/transformations)
|
|
142
|
+
|
|
143
|
+
| Kind | Description | Example Input | Example Output |
|
|
144
|
+
|------|-------------|---------------|----------------|
|
|
145
|
+
| `url` | URL with leading `/` | `['user', 'profile']` | `'/user/profile'` |
|
|
146
|
+
| `query` | Query string | `['a', 'b']` | `'?a&b'` |
|
|
147
|
+
| `longArg` | CLI long argument | `['dry', 'run']` | `'--dry-run'` |
|
|
148
|
+
| `shortArg` | CLI short argument | `['v']` | `'-v'` |
|
|
149
|
+
| `envVar` | Environment variable | `['db', 'host']` | `'DB_HOST'` |
|
|
150
|
+
| `cssClass` | CSS class selector | `['btn', 'primary']` | `'.btn-primary'` |
|
|
151
|
+
| `cssId` | CSS ID selector | `['main', 'content']` | `'#main-content'` |
|
|
152
|
+
| `anchor` | HTML anchor | `['section']` | `'#section'` |
|
|
153
|
+
| `scoped` | Scoped package | `['org', 'pkg']` | `'@org/pkg'` |
|
|
154
|
+
| `param` | Route parameter (last) | `['users', 'id']` | `':id'` |
|
|
155
|
+
| `params` | Route parameters (all) | `['users', 'id']` | `':users/:id'` |
|
|
156
|
+
| `arg` | Template arg (last) | `['users', 'id']` | `'{id}'` |
|
|
157
|
+
| `args` | Template args (all) | `['users', 'id']` | `'{users}/{id}'` |
|
|
150
158
|
|
|
151
159
|
---
|
|
152
160
|
|
|
@@ -176,9 +184,9 @@ NamesTree.Names<T>(
|
|
|
176
184
|
import { NamesTree } from '@avstantso/utils-names-tree';
|
|
177
185
|
|
|
178
186
|
const form = {
|
|
179
|
-
form:
|
|
180
|
-
label:
|
|
181
|
-
placeholder:
|
|
187
|
+
form: 1,
|
|
188
|
+
label: 1,
|
|
189
|
+
placeholder: 1
|
|
182
190
|
};
|
|
183
191
|
|
|
184
192
|
const formTree = NamesTree.Names(form);
|
|
@@ -197,12 +205,12 @@ console.log(formDict); // { form: 'form', label: 'label', placeholder: 'placehol
|
|
|
197
205
|
import { NamesTree } from '@avstantso/utils-names-tree';
|
|
198
206
|
|
|
199
207
|
const form = {
|
|
200
|
-
form:
|
|
201
|
-
label:
|
|
202
|
-
placeholder:
|
|
208
|
+
form: 1,
|
|
209
|
+
label: 1,
|
|
210
|
+
placeholder: 1
|
|
203
211
|
};
|
|
204
212
|
|
|
205
|
-
const formTree = NamesTree.Names(form, { prefix: 'xxx-' });
|
|
213
|
+
const formTree = NamesTree.Names(form, { prefix: 'xxx-' } as const);
|
|
206
214
|
const formDict = formTree.Name;
|
|
207
215
|
|
|
208
216
|
console.log(formDict);
|
|
@@ -272,12 +280,12 @@ import { NamesTree } from '@avstantso/utils-names-tree';
|
|
|
272
280
|
const validationKeys = NamesTree.I18ns({
|
|
273
281
|
user: {
|
|
274
282
|
email: {
|
|
275
|
-
required:
|
|
276
|
-
invalid:
|
|
283
|
+
required: 1,
|
|
284
|
+
invalid: 1
|
|
277
285
|
},
|
|
278
286
|
password: {
|
|
279
|
-
required:
|
|
280
|
-
tooShort:
|
|
287
|
+
required: 1,
|
|
288
|
+
tooShort: 1
|
|
281
289
|
}
|
|
282
290
|
}
|
|
283
291
|
});
|
|
@@ -318,15 +326,15 @@ import { NamesTree } from '@avstantso/utils-names-tree';
|
|
|
318
326
|
|
|
319
327
|
const Urls = {
|
|
320
328
|
profile: {
|
|
321
|
-
login:
|
|
322
|
-
logout:
|
|
323
|
-
settings:
|
|
324
|
-
registration:
|
|
325
|
-
reset:
|
|
329
|
+
login: 1,
|
|
330
|
+
logout: 1,
|
|
331
|
+
settings: 1,
|
|
332
|
+
registration: 1,
|
|
333
|
+
reset: 1
|
|
326
334
|
},
|
|
327
335
|
admin: {
|
|
328
|
-
roles:
|
|
329
|
-
users:
|
|
336
|
+
roles: 1,
|
|
337
|
+
users: 1
|
|
330
338
|
}
|
|
331
339
|
};
|
|
332
340
|
|
|
@@ -373,7 +381,7 @@ const Urls = { profile: { settings: '' } };
|
|
|
373
381
|
const UrlsTree = NamesTree.Urls(Urls);
|
|
374
382
|
|
|
375
383
|
// Get array of trees, one per kind
|
|
376
|
-
const [NAMES, PATHS, URLS] = UrlsTree
|
|
384
|
+
const [NAMES, PATHS, URLS] = UrlsTree.$splitted;
|
|
377
385
|
|
|
378
386
|
console.log(`${NAMES.profile.settings}`); // 'settings'
|
|
379
387
|
console.log(NAMES.profile.settings); // 'settings'
|
|
@@ -385,17 +393,48 @@ console.log(`${URLS.profile.settings}`); // '/profile/settings'
|
|
|
385
393
|
console.log(URLS.profile.settings); // '/profile/settings'
|
|
386
394
|
```
|
|
387
395
|
|
|
396
|
+
**Using Root Alias:**
|
|
397
|
+
```typescript
|
|
398
|
+
import { NamesTree } from '@avstantso/utils-names-tree';
|
|
399
|
+
|
|
400
|
+
const Urls = { profile: { settings: '' } };
|
|
401
|
+
const UrlsTree = NamesTree.Urls(Urls, { rootAlias: 'Home' } as const);
|
|
402
|
+
|
|
403
|
+
// Access root via alias
|
|
404
|
+
console.log(`${UrlsTree.Home}`); // ''
|
|
405
|
+
console.log(`${UrlsTree.Url.Home}`); // '/'
|
|
406
|
+
|
|
407
|
+
// Splitted trees also have the alias
|
|
408
|
+
const [NAMES, PATHS, URLS] = UrlsTree.$splitted;
|
|
409
|
+
console.log(`${URLS.Home}`); // '/'
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
**Using Default Kind:**
|
|
413
|
+
```typescript
|
|
414
|
+
import { NamesTree } from '@avstantso/utils-names-tree';
|
|
415
|
+
|
|
416
|
+
const data = { profile: { settings: '' } };
|
|
417
|
+
|
|
418
|
+
// Default toString() uses first kind ('name')
|
|
419
|
+
const tree1 = NamesTree(data, 'name', 'path', 'url');
|
|
420
|
+
console.log(`${tree1.profile.settings}`); // 'settings'
|
|
421
|
+
|
|
422
|
+
// Override default toString() to use 'url' kind
|
|
423
|
+
const tree2 = NamesTree(data, { defaultKind: 'url' } as const, 'name', 'path', 'url');
|
|
424
|
+
console.log(`${tree2.profile.settings}`); // '/profile/settings'
|
|
425
|
+
```
|
|
426
|
+
|
|
388
427
|
---
|
|
389
428
|
|
|
390
429
|
## Advanced Features
|
|
391
430
|
|
|
392
431
|
### Tree Merging
|
|
393
432
|
|
|
394
|
-
Combine multiple data sources into a single tree
|
|
433
|
+
Combine multiple data sources into a single tree. Can be used for extending basic trees or adding route handlers to URL structures.
|
|
395
434
|
|
|
396
435
|
**Method:**
|
|
397
436
|
```typescript
|
|
398
|
-
tree
|
|
437
|
+
tree.$merge<M>(data: M): NamesTree<T & M, Kinds>
|
|
399
438
|
```
|
|
400
439
|
|
|
401
440
|
**Parameters:**
|
|
@@ -409,8 +448,8 @@ import { NamesTree } from '@avstantso/utils-names-tree';
|
|
|
409
448
|
|
|
410
449
|
const Urls = {
|
|
411
450
|
profile: {
|
|
412
|
-
login:
|
|
413
|
-
settings:
|
|
451
|
+
login: 1,
|
|
452
|
+
settings: 1
|
|
414
453
|
}
|
|
415
454
|
};
|
|
416
455
|
|
|
@@ -421,7 +460,7 @@ function profile() {
|
|
|
421
460
|
return 123;
|
|
422
461
|
}
|
|
423
462
|
|
|
424
|
-
const merged = UrlsTree
|
|
463
|
+
const merged = UrlsTree.$merge({ profile });
|
|
425
464
|
|
|
426
465
|
// Call the function
|
|
427
466
|
console.log(merged.profile()); // 123
|
|
@@ -439,9 +478,9 @@ import express from 'express';
|
|
|
439
478
|
const routes = {
|
|
440
479
|
api: {
|
|
441
480
|
users: {
|
|
442
|
-
list:
|
|
443
|
-
create:
|
|
444
|
-
detail:
|
|
481
|
+
list: 1,
|
|
482
|
+
create: 1,
|
|
483
|
+
detail: 1
|
|
445
484
|
}
|
|
446
485
|
}
|
|
447
486
|
};
|
|
@@ -456,7 +495,7 @@ const handlers = {
|
|
|
456
495
|
}
|
|
457
496
|
};
|
|
458
497
|
|
|
459
|
-
const RoutesTree = NamesTree.Urls(routes)
|
|
498
|
+
const RoutesTree = NamesTree.Urls(routes).$merge(handlers);
|
|
460
499
|
|
|
461
500
|
const app = express();
|
|
462
501
|
|
|
@@ -476,7 +515,7 @@ Extract individual trees for each kind from a multi-kind tree.
|
|
|
476
515
|
|
|
477
516
|
**Property:**
|
|
478
517
|
```typescript
|
|
479
|
-
tree
|
|
518
|
+
tree.$splitted: Array<NamesTree<T, [Kind]>>
|
|
480
519
|
```
|
|
481
520
|
|
|
482
521
|
**Returns:** Array of single-kind trees in the order kinds were specified
|
|
@@ -485,10 +524,10 @@ tree.splitted: Array<NamesTree<T, [Kind]>>
|
|
|
485
524
|
```typescript
|
|
486
525
|
import { NamesTree } from '@avstantso/utils-names-tree';
|
|
487
526
|
|
|
488
|
-
const data = { user: { profile:
|
|
527
|
+
const data = { user: { profile: 1 } };
|
|
489
528
|
const tree = NamesTree.Urls(data);
|
|
490
529
|
|
|
491
|
-
const [names, paths, urls] = tree
|
|
530
|
+
const [names, paths, urls] = tree.$splitted;
|
|
492
531
|
|
|
493
532
|
console.log(`${names.user.profile}`); // 'profile'
|
|
494
533
|
console.log(`${paths.user.profile}`); // 'user/profile'
|
|
@@ -514,34 +553,50 @@ NamesTree._RegKinds(kinds: Record<string, Kinds.Method>): void
|
|
|
514
553
|
type Method = (path: string[], options?: Options) => string
|
|
515
554
|
```
|
|
516
555
|
|
|
556
|
+
**TypeScript Support:**
|
|
557
|
+
|
|
558
|
+
For proper type inference with custom kinds, extend the `KindsMeta` interface:
|
|
559
|
+
|
|
560
|
+
```typescript
|
|
561
|
+
declare global {
|
|
562
|
+
namespace AVStantso {
|
|
563
|
+
namespace NamesTree {
|
|
564
|
+
interface KindsMeta {
|
|
565
|
+
bem: '__'; // separator or KindMeta object
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
```
|
|
571
|
+
|
|
517
572
|
**Example:**
|
|
518
573
|
```typescript
|
|
519
574
|
import { NamesTree } from '@avstantso/utils-names-tree';
|
|
520
575
|
|
|
521
|
-
// Register custom kind for CSS
|
|
576
|
+
// Register custom kind for BEM-style CSS (block__element)
|
|
522
577
|
NamesTree._RegKinds({
|
|
523
|
-
|
|
578
|
+
bem: (path, options) => {
|
|
524
579
|
const { prefix = '' } = options || {};
|
|
525
|
-
return `${prefix}${path.join('__')}`;
|
|
580
|
+
return `${prefix}${path.join('__')}`;
|
|
526
581
|
}
|
|
527
582
|
});
|
|
528
583
|
|
|
529
584
|
// Use custom kind
|
|
530
585
|
const components = {
|
|
531
586
|
button: {
|
|
532
|
-
|
|
533
|
-
|
|
587
|
+
icon: 1,
|
|
588
|
+
label: 1
|
|
534
589
|
}
|
|
535
590
|
};
|
|
536
591
|
|
|
537
|
-
const
|
|
592
|
+
const bemTree = NamesTree(components, 'bem');
|
|
538
593
|
|
|
539
|
-
console.log(`${
|
|
540
|
-
console.log(`${
|
|
594
|
+
console.log(`${bemTree.button.icon}`); // 'button__icon'
|
|
595
|
+
console.log(`${bemTree.button.label}`); // 'button__label'
|
|
541
596
|
|
|
542
597
|
// With prefix
|
|
543
|
-
const prefixedTree = NamesTree(components, { prefix: 'app-' }, '
|
|
544
|
-
console.log(`${prefixedTree.button.
|
|
598
|
+
const prefixedTree = NamesTree(components, { prefix: 'app-' } as const, 'bem');
|
|
599
|
+
console.log(`${prefixedTree.button.icon}`); // 'app-button__icon'
|
|
545
600
|
```
|
|
546
601
|
|
|
547
602
|
**Example: GraphQL Field Paths**
|
|
@@ -556,9 +611,9 @@ NamesTree._RegKinds({
|
|
|
556
611
|
|
|
557
612
|
const schema = {
|
|
558
613
|
user: {
|
|
559
|
-
firstName:
|
|
560
|
-
lastName:
|
|
561
|
-
emailAddress:
|
|
614
|
+
firstName: 1,
|
|
615
|
+
lastName: 1,
|
|
616
|
+
emailAddress: 1
|
|
562
617
|
}
|
|
563
618
|
};
|
|
564
619
|
|
|
@@ -578,14 +633,37 @@ Union type for valid tree source data.
|
|
|
578
633
|
|
|
579
634
|
**Type Definition:**
|
|
580
635
|
```typescript
|
|
581
|
-
type Source =
|
|
636
|
+
type Source = TS.Type.Builtin // string | number | boolean | null | undefined | symbol | bigint
|
|
582
637
|
|
|
583
638
|
namespace Source {
|
|
584
639
|
type Root = object | Function
|
|
585
640
|
}
|
|
586
641
|
```
|
|
587
642
|
|
|
588
|
-
**Description:**
|
|
643
|
+
**Description:**
|
|
644
|
+
- `Source` — any built-in primitive type for leaf nodes (values are ignored)
|
|
645
|
+
- `Source.Root` — nested objects or functions for tree structure
|
|
646
|
+
|
|
647
|
+
Leaf node values are ignored — only the object structure (keys) matters for generating paths.
|
|
648
|
+
|
|
649
|
+
**Flexible Leaf Values:**
|
|
650
|
+
```typescript
|
|
651
|
+
// All of these are equivalent - leaf values are ignored
|
|
652
|
+
const data1 = { user: { name: '' } };
|
|
653
|
+
const data2 = { user: { name: 1 } };
|
|
654
|
+
const data3 = { user: { name: true } };
|
|
655
|
+
const data4 = { user: { name: null } };
|
|
656
|
+
|
|
657
|
+
// Using numbers is shorter to type
|
|
658
|
+
const form = {
|
|
659
|
+
form: 1,
|
|
660
|
+
label: 1,
|
|
661
|
+
placeholder: 1
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
const tree = NamesTree.Names(form);
|
|
665
|
+
console.log(tree.Name); // { form: 'form', label: 'label', placeholder: 'placeholder' }
|
|
666
|
+
```
|
|
589
667
|
|
|
590
668
|
---
|
|
591
669
|
|
|
@@ -610,11 +688,15 @@ Configuration options for tree creation.
|
|
|
610
688
|
```typescript
|
|
611
689
|
interface Options {
|
|
612
690
|
prefix?: string;
|
|
691
|
+
rootAlias?: string;
|
|
692
|
+
defaultKind?: Kind;
|
|
613
693
|
}
|
|
614
694
|
```
|
|
615
695
|
|
|
616
696
|
**Properties:**
|
|
617
697
|
- `prefix` - Optional string to prepend to all generated values (default: `''`)
|
|
698
|
+
- `rootAlias` - Optional alias for tree root as additional property (e.g., `'Home'` for URL trees). The alias is `enumerable: true`, so it appears in `Object.keys()` and iterations
|
|
699
|
+
- `defaultKind` - Optional kind to use for default `toString()` output (defaults to first kind in tree)
|
|
618
700
|
|
|
619
701
|
---
|
|
620
702
|
|
|
@@ -626,17 +708,21 @@ Type-safe tree node structure.
|
|
|
626
708
|
```typescript
|
|
627
709
|
type Node<
|
|
628
710
|
T extends Source = Source,
|
|
711
|
+
O extends Options = Options,
|
|
629
712
|
KDs extends readonly Kind[] = Kinds.All,
|
|
630
|
-
S extends boolean = false
|
|
631
|
-
|
|
713
|
+
S extends boolean = false,
|
|
714
|
+
P extends TS.Keys = []
|
|
715
|
+
> = (S extends true ? unknown : _NodeKinds<O, KDs, P>) & Node.Data<T, O, KDs, S, P>
|
|
632
716
|
```
|
|
633
717
|
|
|
634
718
|
**Type Parameters:**
|
|
635
719
|
- `T` - Source data type
|
|
720
|
+
- `O` - Options type (for prefix inference)
|
|
636
721
|
- `KDs` - Tuple of kinds available on this node
|
|
637
|
-
- `S` - If true, allows string
|
|
722
|
+
- `S` - If true, allows resolved string types for leaf nodes (for splitted trees)
|
|
723
|
+
- `P` - Path stack for compile-time kind resolution
|
|
638
724
|
|
|
639
|
-
**Description:** Represents a tree node with properties for data access and kind-specific formatters.
|
|
725
|
+
**Description:** Represents a tree node with properties for data access and kind-specific formatters. The `P` parameter enables compile-time path tracking for precise type inference of kind accessors.
|
|
640
726
|
|
|
641
727
|
---
|
|
642
728
|
|
|
@@ -671,28 +757,56 @@ const urls = NamesTree.Urls(routeData);
|
|
|
671
757
|
const urls = NamesTree(routeData, 'name', 'path', 'url');
|
|
672
758
|
```
|
|
673
759
|
|
|
674
|
-
### 2.
|
|
760
|
+
### 2. Use Short Leaf Values
|
|
761
|
+
|
|
762
|
+
Leaf node values are ignored — use `1` or any short value instead of empty strings for brevity.
|
|
763
|
+
|
|
764
|
+
```typescript
|
|
765
|
+
// Verbose
|
|
766
|
+
const form1 = { name: '', email: '', password: '' };
|
|
767
|
+
|
|
768
|
+
// Concise - same result
|
|
769
|
+
const form2 = { name: 1, email: 1, password: 1 };
|
|
770
|
+
|
|
771
|
+
const tree = NamesTree.Names(form2);
|
|
772
|
+
console.log(tree.Name); // { name: 'name', email: 'email', password: 'password' }
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
### 3. Leverage Type Inference
|
|
675
776
|
|
|
676
|
-
|
|
777
|
+
TypeScript automatically infers key names as literal types — no `as const` needed for data structures.
|
|
677
778
|
|
|
678
779
|
```typescript
|
|
679
780
|
const routes = {
|
|
680
|
-
admin: { users:
|
|
681
|
-
}
|
|
781
|
+
admin: { users: 1, roles: 1 }
|
|
782
|
+
};
|
|
682
783
|
|
|
683
784
|
const tree = NamesTree.Urls(routes);
|
|
684
785
|
|
|
685
786
|
// TypeScript knows these properties exist
|
|
686
787
|
tree.admin.users; // OK
|
|
687
788
|
tree.admin.posts; // Error: Property 'posts' does not exist
|
|
789
|
+
|
|
790
|
+
// Kind accessors have precise literal types
|
|
791
|
+
tree.admin.users._url; // Type: '/admin/users'
|
|
688
792
|
```
|
|
689
793
|
|
|
690
|
-
|
|
794
|
+
**Note:** Use `as const` for options when you need literal types:
|
|
795
|
+
```typescript
|
|
796
|
+
// Options need 'as const' for precise type inference
|
|
797
|
+
const tree = NamesTree.Urls(data, { rootAlias: 'Home' } as const);
|
|
798
|
+
tree.Home; // Property 'Home' exists
|
|
799
|
+
|
|
800
|
+
const form = NamesTree.Names(data, { prefix: 'app-' } as const);
|
|
801
|
+
form.Name.user; // Type: 'app-user'
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
### 4. Use String Coercion for Output
|
|
691
805
|
|
|
692
806
|
Trees automatically convert to strings via `toString()` method.
|
|
693
807
|
|
|
694
808
|
```typescript
|
|
695
|
-
const tree = NamesTree.Names({ user:
|
|
809
|
+
const tree = NamesTree.Names({ user: 1 });
|
|
696
810
|
|
|
697
811
|
// Explicit string coercion
|
|
698
812
|
console.log(`${tree.user}`); // 'user'
|
|
@@ -701,7 +815,7 @@ console.log(`${tree.user}`); // 'user'
|
|
|
701
815
|
const url = `/api/${tree.user}`; // '/api/user'
|
|
702
816
|
```
|
|
703
817
|
|
|
704
|
-
###
|
|
818
|
+
### 5. Centralize Route Definitions
|
|
705
819
|
|
|
706
820
|
Define routes once and derive all variations from a single source.
|
|
707
821
|
|
|
@@ -709,15 +823,15 @@ Define routes once and derive all variations from a single source.
|
|
|
709
823
|
// routes.ts
|
|
710
824
|
export const RouteStructure = {
|
|
711
825
|
auth: {
|
|
712
|
-
login:
|
|
713
|
-
logout:
|
|
714
|
-
register:
|
|
826
|
+
login: 1,
|
|
827
|
+
logout: 1,
|
|
828
|
+
register: 1
|
|
715
829
|
},
|
|
716
830
|
dashboard: {
|
|
717
|
-
home:
|
|
718
|
-
analytics:
|
|
831
|
+
home: 1,
|
|
832
|
+
analytics: 1
|
|
719
833
|
}
|
|
720
|
-
}
|
|
834
|
+
};
|
|
721
835
|
|
|
722
836
|
export const Routes = NamesTree.Urls(RouteStructure);
|
|
723
837
|
|
|
@@ -734,17 +848,17 @@ router.get(`${Routes.Url.auth.login}`, loginHandler);
|
|
|
734
848
|
fetch(`${Routes.Path.dashboard.home}`);
|
|
735
849
|
```
|
|
736
850
|
|
|
737
|
-
###
|
|
851
|
+
### 6. Combine with Enums for Constants
|
|
738
852
|
|
|
739
853
|
Use trees for structured constants that need multiple representations.
|
|
740
854
|
|
|
741
855
|
```typescript
|
|
742
856
|
const StatusStructure = {
|
|
743
|
-
pending:
|
|
744
|
-
active:
|
|
745
|
-
completed:
|
|
746
|
-
archived:
|
|
747
|
-
}
|
|
857
|
+
pending: 1,
|
|
858
|
+
active: 1,
|
|
859
|
+
completed: 1,
|
|
860
|
+
archived: 1
|
|
861
|
+
};
|
|
748
862
|
|
|
749
863
|
const StatusTree = NamesTree.Names(StatusStructure);
|
|
750
864
|
|
|
@@ -768,17 +882,17 @@ import { NamesTree } from '@avstantso/utils-names-tree';
|
|
|
768
882
|
|
|
769
883
|
const AppRoutes = NamesTree.Urls({
|
|
770
884
|
public: {
|
|
771
|
-
home:
|
|
772
|
-
about:
|
|
773
|
-
contact:
|
|
885
|
+
home: 1,
|
|
886
|
+
about: 1,
|
|
887
|
+
contact: 1
|
|
774
888
|
},
|
|
775
889
|
auth: {
|
|
776
|
-
login:
|
|
777
|
-
signup:
|
|
890
|
+
login: 1,
|
|
891
|
+
signup: 1
|
|
778
892
|
},
|
|
779
893
|
dashboard: {
|
|
780
|
-
overview:
|
|
781
|
-
settings:
|
|
894
|
+
overview: 1,
|
|
895
|
+
settings: 1
|
|
782
896
|
}
|
|
783
897
|
});
|
|
784
898
|
|
|
@@ -798,16 +912,16 @@ import { NamesTree } from '@avstantso/utils-names-tree';
|
|
|
798
912
|
const i18nKeys = NamesTree.I18ns({
|
|
799
913
|
common: {
|
|
800
914
|
buttons: {
|
|
801
|
-
save:
|
|
802
|
-
cancel:
|
|
803
|
-
delete:
|
|
915
|
+
save: 1,
|
|
916
|
+
cancel: 1,
|
|
917
|
+
delete: 1
|
|
804
918
|
}
|
|
805
919
|
},
|
|
806
920
|
errors: {
|
|
807
921
|
validation: {
|
|
808
|
-
required:
|
|
809
|
-
email:
|
|
810
|
-
minLength:
|
|
922
|
+
required: 1,
|
|
923
|
+
email: 1,
|
|
924
|
+
minLength: 1
|
|
811
925
|
}
|
|
812
926
|
}
|
|
813
927
|
});
|
|
@@ -823,11 +937,11 @@ t(`${i18nKeys.errors.validation.required}`); // 'errors.validation.required'
|
|
|
823
937
|
import { NamesTree } from '@avstantso/utils-names-tree';
|
|
824
938
|
|
|
825
939
|
const styles = NamesTree.Names({
|
|
826
|
-
button:
|
|
827
|
-
input:
|
|
828
|
-
label:
|
|
829
|
-
container:
|
|
830
|
-
}, { prefix: 'app-' });
|
|
940
|
+
button: 1,
|
|
941
|
+
input: 1,
|
|
942
|
+
label: 1,
|
|
943
|
+
container: 1
|
|
944
|
+
}, { prefix: 'app-' } as const);
|
|
831
945
|
|
|
832
946
|
// Use in JSX
|
|
833
947
|
<button className={styles.Name.button}>Click Me</button>
|
|
@@ -844,13 +958,13 @@ import { NamesTree } from '@avstantso/utils-names-tree';
|
|
|
844
958
|
|
|
845
959
|
const FormFields = NamesTree.Names({
|
|
846
960
|
user: {
|
|
847
|
-
firstName:
|
|
848
|
-
lastName:
|
|
849
|
-
email:
|
|
961
|
+
firstName: 1,
|
|
962
|
+
lastName: 1,
|
|
963
|
+
email: 1,
|
|
850
964
|
address: {
|
|
851
|
-
street:
|
|
852
|
-
city:
|
|
853
|
-
zipCode:
|
|
965
|
+
street: 1,
|
|
966
|
+
city: 1,
|
|
967
|
+
zipCode: 1
|
|
854
968
|
}
|
|
855
969
|
}
|
|
856
970
|
});
|
|
@@ -861,6 +975,68 @@ const FormFields = NamesTree.Names({
|
|
|
861
975
|
<input name={FormFields.Name.user.address.city} />
|
|
862
976
|
```
|
|
863
977
|
|
|
978
|
+
### Environment Variables
|
|
979
|
+
|
|
980
|
+
```typescript
|
|
981
|
+
import { NamesTree } from '@avstantso/utils-names-tree';
|
|
982
|
+
|
|
983
|
+
const envVars = NamesTree({
|
|
984
|
+
db: {
|
|
985
|
+
host: 1,
|
|
986
|
+
port: 1,
|
|
987
|
+
name: 1
|
|
988
|
+
},
|
|
989
|
+
api: {
|
|
990
|
+
key: 1,
|
|
991
|
+
secret: 1
|
|
992
|
+
}
|
|
993
|
+
}, 'envVar');
|
|
994
|
+
|
|
995
|
+
console.log(envVars.db.host._envVar); // 'DB_HOST'
|
|
996
|
+
console.log(envVars.api.key._envVar); // 'API_KEY'
|
|
997
|
+
```
|
|
998
|
+
|
|
999
|
+
### CLI Arguments
|
|
1000
|
+
|
|
1001
|
+
```typescript
|
|
1002
|
+
import { NamesTree } from '@avstantso/utils-names-tree';
|
|
1003
|
+
|
|
1004
|
+
const cliArgs = NamesTree({
|
|
1005
|
+
verbose: 1,
|
|
1006
|
+
dry: { run: 1 },
|
|
1007
|
+
output: { file: 1 }
|
|
1008
|
+
}, 'longArg', 'shortArg');
|
|
1009
|
+
|
|
1010
|
+
console.log(cliArgs.verbose._longArg); // '--verbose'
|
|
1011
|
+
console.log(cliArgs.verbose._shortArg); // '-verbose'
|
|
1012
|
+
console.log(cliArgs.dry.run._longArg); // '--dry-run'
|
|
1013
|
+
```
|
|
1014
|
+
|
|
1015
|
+
### Route Parameters
|
|
1016
|
+
|
|
1017
|
+
```typescript
|
|
1018
|
+
import { NamesTree } from '@avstantso/utils-names-tree';
|
|
1019
|
+
|
|
1020
|
+
const apiRoutes = NamesTree({
|
|
1021
|
+
users: {
|
|
1022
|
+
id: {
|
|
1023
|
+
posts: {
|
|
1024
|
+
postId: 1
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}, 'url', 'params', 'args');
|
|
1029
|
+
|
|
1030
|
+
// Express-style route
|
|
1031
|
+
console.log(apiRoutes.users.id._params); // ':users/:id'
|
|
1032
|
+
|
|
1033
|
+
// OpenAPI-style route
|
|
1034
|
+
console.log(apiRoutes.users.id.posts.postId._args); // '{users}/{id}/{posts}/{postId}'
|
|
1035
|
+
|
|
1036
|
+
// Combined with url
|
|
1037
|
+
console.log(apiRoutes.users.id._url); // '/users/id'
|
|
1038
|
+
```
|
|
1039
|
+
|
|
864
1040
|
---
|
|
865
1041
|
|
|
866
1042
|
## Requirements
|