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