@vicin/sigil 1.2.1 → 1.2.3
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 +72 -0
- package/LICENSE +7 -7
- package/README.md +616 -873
- package/dist/index.d.mts +13 -32
- package/dist/index.d.ts +13 -32
- package/dist/index.global.js +7 -7
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +7 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +7 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +78 -76
package/README.md
CHANGED
|
@@ -1,873 +1,616 @@
|
|
|
1
|
-
# Sigil
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
>
|
|
6
|
-
>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
>
|
|
11
|
-
|
|
12
|
-
**
|
|
13
|
-
|
|
14
|
-
- **
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- **
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
- [
|
|
33
|
-
- [
|
|
34
|
-
- [
|
|
35
|
-
|
|
36
|
-
- [
|
|
37
|
-
- [
|
|
38
|
-
|
|
39
|
-
- [
|
|
40
|
-
- [
|
|
41
|
-
- [
|
|
42
|
-
- [Nominal typing](#nominal-typing)
|
|
43
|
-
- [
|
|
44
|
-
- [
|
|
45
|
-
- [
|
|
46
|
-
- [
|
|
47
|
-
- [
|
|
48
|
-
- [
|
|
49
|
-
- [
|
|
50
|
-
- [
|
|
51
|
-
- [
|
|
52
|
-
- [
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
```ts
|
|
154
|
-
import {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
**
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
**
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
**
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
###
|
|
196
|
-
|
|
197
|
-
**
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
-
|
|
202
|
-
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
###
|
|
216
|
-
|
|
217
|
-
- **
|
|
218
|
-
- **
|
|
219
|
-
- **Type
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
We
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
class
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
class
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
Sigil
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
```
|
|
618
|
-
|
|
619
|
-
### Replace registry
|
|
620
|
-
|
|
621
|
-
In most cases you don't need to replace registry, but if you wanted to define a `Map` and make `Sigil` use it aa a register (e.g. define custom side effects) you can use `SigilRegistry`:
|
|
622
|
-
|
|
623
|
-
```ts
|
|
624
|
-
import { SigilRegistry, updateOptions } from '@vicin/sigil';
|
|
625
|
-
|
|
626
|
-
const myMap = new Map();
|
|
627
|
-
const myRegistry = new SigilRegistry(myMap);
|
|
628
|
-
updateOptions({ registry: myRegistry });
|
|
629
|
-
|
|
630
|
-
// Now 'Sigil' register new labels and constructors to 'myMap'.
|
|
631
|
-
```
|
|
632
|
-
|
|
633
|
-
By default `Sigil` will merge old registry map into `myMap`, to prevent this behavior:
|
|
634
|
-
|
|
635
|
-
```ts
|
|
636
|
-
updateOptions({ registry: myRegistry }, false); // <-- add false here
|
|
637
|
-
```
|
|
638
|
-
|
|
639
|
-
Also you can set registry to `null`, but this is not advised as it disable all registry operations entirely:
|
|
640
|
-
|
|
641
|
-
```ts
|
|
642
|
-
import { updateOptions } from '@vicin/sigil';
|
|
643
|
-
updateOptions({ registry: null }); // No label checks and registry map is freed from memory
|
|
644
|
-
```
|
|
645
|
-
|
|
646
|
-
### globalThis and security
|
|
647
|
-
|
|
648
|
-
By default registry is stored in `globalThis`. to disable this behavior you can:
|
|
649
|
-
|
|
650
|
-
```ts
|
|
651
|
-
import { updateOptions } from '@vicin/sigil';
|
|
652
|
-
updateOptions({ useGlobalRegistry: false });
|
|
653
|
-
```
|
|
654
|
-
|
|
655
|
-
Before applying this change, for registry to function normally you should ensure that `Sigil` is not bundles twice in your app.
|
|
656
|
-
however if you can't ensure that only bundle of `Sigil` is used and don't want class constructors to be accessible globally do this:
|
|
657
|
-
|
|
658
|
-
```ts
|
|
659
|
-
import { updateOptions } from '@vicin/sigil';
|
|
660
|
-
updateOptions({ storeConstructor: false });
|
|
661
|
-
```
|
|
662
|
-
|
|
663
|
-
Now registry only stores label of this classes and all class constructors are in the map are replaced with `null`.
|
|
664
|
-
If you need even more control and like the global registry for classes but want to obscure only some of your classes you can pass this option per class and keep global options as is:
|
|
665
|
-
|
|
666
|
-
```ts
|
|
667
|
-
import { withSigil, Sigil } from '@vicin/sigil';
|
|
668
|
-
|
|
669
|
-
class _X extends Sigil {}
|
|
670
|
-
const X = withSigil(_X, 'X', { storeConstructor: false });
|
|
671
|
-
```
|
|
672
|
-
|
|
673
|
-
Pick whatever pattern you like!
|
|
674
|
-
|
|
675
|
-
### Class typing in registry
|
|
676
|
-
|
|
677
|
-
Unfortunately concrete types of classes is not supported and all classes are stored as `ISigil` type. if you want concrete typings you can wrap registry:
|
|
678
|
-
|
|
679
|
-
```ts
|
|
680
|
-
import { getActiveRegistry } from '@vicin/sigil';
|
|
681
|
-
import { MySigilClass1 } from './file1';
|
|
682
|
-
import { MySigilClass2 } from './file2';
|
|
683
|
-
|
|
684
|
-
interface MyClasses {
|
|
685
|
-
MySigilClass1: typeof MySigilClass1;
|
|
686
|
-
MySigilClass2: typeof MySigilClass2;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
class MySigilRegistry {
|
|
690
|
-
listLabels(): (keyof MyClasses)[] {
|
|
691
|
-
return getActiveRegistry()?.listLabels();
|
|
692
|
-
}
|
|
693
|
-
has(label: string): boolean {
|
|
694
|
-
return getActiveRegistry()?.has(label);
|
|
695
|
-
}
|
|
696
|
-
get<L extends keyof MyClasses>(label: L): MyClasses[L] {
|
|
697
|
-
return getActiveRegistry()?.get(label) as any;
|
|
698
|
-
}
|
|
699
|
-
unregister(label: string): boolean {
|
|
700
|
-
return getActiveRegistry()?.unregister(label);
|
|
701
|
-
}
|
|
702
|
-
clear(): void {
|
|
703
|
-
getActiveRegistry()?.clear();
|
|
704
|
-
}
|
|
705
|
-
replaceRegistry(newRegistry: Map<string, ISigil> | null): void {
|
|
706
|
-
getActiveRegistry()?.replaceRegistry(newRegistry);
|
|
707
|
-
}
|
|
708
|
-
get size(): number {
|
|
709
|
-
return getActiveRegistry()?.size;
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
export const MY_SIGIL_REGISTRY = new MySigilRegistry();
|
|
714
|
-
```
|
|
715
|
-
|
|
716
|
-
Now you have fully typed central class registry!
|
|
717
|
-
|
|
718
|
-
### I don't care about nominal types or central registry, i just want a runtime replacement of 'instanceof'
|
|
719
|
-
|
|
720
|
-
You can run this at the start of your app:
|
|
721
|
-
|
|
722
|
-
```ts
|
|
723
|
-
import { updateOptions } from '@vicin/sigil';
|
|
724
|
-
updateOptions({ autofillLabels: true, storeConstructor: false });
|
|
725
|
-
```
|
|
726
|
-
|
|
727
|
-
now you can omit all `HOF`, `Decorators` and make `Sigil` work in the background:
|
|
728
|
-
|
|
729
|
-
```ts
|
|
730
|
-
import { Sigil } from '@vicin/sigil';
|
|
731
|
-
|
|
732
|
-
class X extends Sigil {}
|
|
733
|
-
class Y extends X {}
|
|
734
|
-
class Z extends Y {}
|
|
735
|
-
|
|
736
|
-
Z.isOfType(new Y()); // true
|
|
737
|
-
Z.isOfType(new X()); // true
|
|
738
|
-
Y.isOfType(new Y()); // false
|
|
739
|
-
```
|
|
740
|
-
|
|
741
|
-
No class constructors are stored globally and no code overhead, moreover if you can ensure that `Sigil` is not bundles twice you can disable `useGlobalRegistry` and no trace of sigil in `globalThis`.
|
|
742
|
-
|
|
743
|
-
---
|
|
744
|
-
|
|
745
|
-
## Security guidance
|
|
746
|
-
|
|
747
|
-
### Recommended defaults & quick rules
|
|
748
|
-
|
|
749
|
-
- Default recommendation for public/shared runtimes (web pages, untrusted workers, serverless):
|
|
750
|
-
|
|
751
|
-
```ts
|
|
752
|
-
updateOptions({ useGlobalRegistry: false, storeConstructor: false });
|
|
753
|
-
```
|
|
754
|
-
|
|
755
|
-
This prevents constructors from being put on `globalThis` and prevents constructors from being stored in the registry map (labels remain, but constructors are `null`).
|
|
756
|
-
|
|
757
|
-
- For private server runtimes (single controlled Node process) where a central registry is desired:
|
|
758
|
-
|
|
759
|
-
```ts
|
|
760
|
-
updateOptions({ useGlobalRegistry: true, storeConstructor: true });
|
|
761
|
-
```
|
|
762
|
-
|
|
763
|
-
Only enable this if you control all bundles and trust the runtime environment.
|
|
764
|
-
|
|
765
|
-
### Per-class sensitivity control
|
|
766
|
-
|
|
767
|
-
If you want the registry in general but need to hide particular classes (e.g., internal or security-sensitive classes), pass `storeConstructor: false` for those classes:
|
|
768
|
-
|
|
769
|
-
```ts
|
|
770
|
-
class _Sensitive extends Sigil {}
|
|
771
|
-
export const Sensitive = withSigil(_Sensitive, '@myorg/internal.Sensitive', {
|
|
772
|
-
storeConstructor: false, // label kept, constructor not stored
|
|
773
|
-
});
|
|
774
|
-
```
|
|
775
|
-
|
|
776
|
-
This keeps the declarative identity but avoids exposing the constructor reference in the registry.
|
|
777
|
-
|
|
778
|
-
### Short warnings (do not rely on Sigil for)
|
|
779
|
-
|
|
780
|
-
- **Not a security boundary:** Registry labels/constructors are discovery metadata — do not put secrets or private instance data in them or rely on them for access control.
|
|
781
|
-
|
|
782
|
-
- **Third-party code can access the registry if `useGlobalRegistry: true`** — only enable that in fully trusted runtimes.
|
|
783
|
-
|
|
784
|
-
---
|
|
785
|
-
|
|
786
|
-
## Troubleshooting & FAQ
|
|
787
|
-
|
|
788
|
-
**Q: My `instanceof` checks fail across bundles — will Sigil fix this?**
|
|
789
|
-
|
|
790
|
-
A: Yes. Sigil uses `Symbol.for(label)` and runtime `SigilTypeSet` membership checks to provide stable identity tests that work across bundles/realms that share the global symbol registry.
|
|
791
|
-
|
|
792
|
-
**Q: I accidentally extended a sigilized class without decorating the subclass; I see an error in DEV. How should I fix it?**
|
|
793
|
-
|
|
794
|
-
A: Use `@WithSigil("@your/label")`, or wrap the subclass with `withSigil` / `withSigilTyped`. Alternatively, you can relax DEV checks using `updateOptions({ skipLabelInheritanceCheck: true })` but be cautious — this weakens guarantees.
|
|
795
|
-
|
|
796
|
-
**Q: I got this error: 'Property 'x' of exported anonymous class type may not be private or protected.', How to fix it?**
|
|
797
|
-
|
|
798
|
-
A: This error comes from the fact that all typed classes (return from `withSigil`, `withSigilTyped` or `typed`) are 'anonymous class' as they are the return of HOF. all you need to do is to export untyped classes (`_Class`) that have private or protected properties. or even export all untyped classes as a good convention even if they are not used.
|
|
799
|
-
|
|
800
|
-
**Q: How do I inspect currently registered labels?**
|
|
801
|
-
|
|
802
|
-
A: Use `getActiveRegistry()?.listLabels()` to get an array of registered labels.
|
|
803
|
-
|
|
804
|
-
**Q: What if i want to omit labeling in some classes while enforce others?**
|
|
805
|
-
|
|
806
|
-
A: You can set `SigilOptions.autofillLabels` to `true`. or if you more strict enviroment you can define empty `@WithSigil()` decorator above classes you don't care about labeling and `Sigil` will generate random label for it, but still throw if you forgot to use a decorator or HOF on a class.
|
|
807
|
-
|
|
808
|
-
---
|
|
809
|
-
|
|
810
|
-
## Deprecated API
|
|
811
|
-
|
|
812
|
-
### REGISTRY
|
|
813
|
-
|
|
814
|
-
`Sigil` have moved from static reference registry to dynamic access and updates, now devs can create `SigilRegistry` class and pass it to `SigilOptions` to be be used by the library internals. however change is done gracefully and `REGISTRY` is still supported with no change in behavior but it's **marked with `deprecated` and will be removed in v2.0.0**.
|
|
815
|
-
|
|
816
|
-
```ts
|
|
817
|
-
import { REGISTRY, getActiveRegistry } from '@vicin/sigil';
|
|
818
|
-
|
|
819
|
-
// from:
|
|
820
|
-
const present = REGISTRY.has('label');
|
|
821
|
-
|
|
822
|
-
// to:
|
|
823
|
-
const present = getActiveRegistry()?.has('label'); // Active registy can be 'null' if 'SigilOptions.registy' is set to null so we used the '?' mark
|
|
824
|
-
```
|
|
825
|
-
|
|
826
|
-
```ts
|
|
827
|
-
import { REGISTRY, updateOptions, SigilRegistry } from '@vicin/sigil';
|
|
828
|
-
|
|
829
|
-
// from:
|
|
830
|
-
const newRegistry = new Map();
|
|
831
|
-
REGISTRY.replaceRegistry(newRegistry);
|
|
832
|
-
|
|
833
|
-
// to
|
|
834
|
-
const newRegistry = new SigilRegistry(); // can pass external map to constructor if needed.
|
|
835
|
-
updateOptions({ registry: newRegistry });
|
|
836
|
-
```
|
|
837
|
-
|
|
838
|
-
### typed
|
|
839
|
-
|
|
840
|
-
Typed was added to add types to output from `Sigilify` mixin, but now mixin do this by default.
|
|
841
|
-
|
|
842
|
-
---
|
|
843
|
-
|
|
844
|
-
## Phantom
|
|
845
|
-
|
|
846
|
-
`Phantom` is another lightweight TypeScript library I created for achieving **nominal typing** on primitives and objects through type-only metadata. It solves the problem of structural typing in TypeScript allowing accidental misuse of identical shapes (e.g., confusing `UserId` and `PostId` as both strings) by enabling compile-time distinctions with features like **brands**, **constrained identities**, **variants for states**, **additive traits**, and **reversible transformations**. This makes it ideal for domain-driven design (DDD) without runtime overhead.
|
|
847
|
-
|
|
848
|
-
`Phantom` works seamlessly in conjunction with `Sigil`, use `Sigil` for nominal identity on classes (runtime-safe checks across bundles), and `Phantom` for primitives/objects. Together, they provide **end-to-end type safety**: e.g., a Sigil-branded `User` class could hold a Phantom-branded `UserId` string property, enforcing domain boundaries at both compile and runtime.
|
|
849
|
-
|
|
850
|
-
- **GitHub: [@phantom](https://github.com/ZiadTaha62/phantom)**
|
|
851
|
-
- **NPM: [@phantom](https://www.npmjs.com/package/@vicin/phantom)**
|
|
852
|
-
|
|
853
|
-
---
|
|
854
|
-
|
|
855
|
-
## Contributing
|
|
856
|
-
|
|
857
|
-
Any contributions you make are **greatly appreciated**.
|
|
858
|
-
|
|
859
|
-
Please see our [CONTRIBUTING.md](./CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
|
|
860
|
-
|
|
861
|
-
## License
|
|
862
|
-
|
|
863
|
-
Distributed under the MIT License. See `LICENSE` for more information.
|
|
864
|
-
|
|
865
|
-
---
|
|
866
|
-
|
|
867
|
-
## Author
|
|
868
|
-
|
|
869
|
-
Built with ❤️ by **Ziad Taha**.
|
|
870
|
-
|
|
871
|
-
- **GitHub: [@ZiadTaha62](https://github.com/ZiadTaha62)**
|
|
872
|
-
- **NPM: [@ziadtaha62](https://www.npmjs.com/~ziadtaha62)**
|
|
873
|
-
- **Vicin: [@vicin](https://www.npmjs.com/org/vicin)**
|
|
1
|
+
# Sigil
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@vicin/sigil) [](https://www.npmjs.com/package/@vicin/sigil) [](LICENSE)  [](https://github.com/ZiadTaha62/sigil/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
> 🎉 First stable release — v1.2! Happy coding! 😄💻🚀
|
|
6
|
+
> 📄 **Changelog:** [CHANGELOG.md](./CHANGELOG.md)
|
|
7
|
+
|
|
8
|
+
`Sigil` is a lightweight TypeScript library for creating nominal identity classes with compile-time branding and reliable runtime type checks. It organizes class identities across your codebase and gives you the power of **nominal typing**, **safe cross-bundle class checks**, and a **central registry** where each class constructor is stored under a unique label.
|
|
9
|
+
|
|
10
|
+
> **Key ideas:**
|
|
11
|
+
>
|
|
12
|
+
> - **Nominal Typing at Compile Time:** Distinguishes structurally similar types (e.g., UserId vs. PostId).
|
|
13
|
+
> - **Reliable Runtime Checks:** Uses symbols instead of instanceof for cross-bundle reliability.
|
|
14
|
+
> - **Inheritance Awareness:** Tracks lineages for subtype/supertype checks.
|
|
15
|
+
> - **Central Registry:** Stores class references by unique labels for easy lookup.
|
|
16
|
+
|
|
17
|
+
## Important Notes Before Using
|
|
18
|
+
|
|
19
|
+
- **Security:** The global registry stores constructors by default, which could expose them. Disable with `{ storeConstructor: false }` for sensitive classes.
|
|
20
|
+
- **Performance:** Minimal overhead, but `.isOfType()` is slower than native `instanceof`. Avoid in ultra-hot paths.
|
|
21
|
+
- **Private Constructors:** HOF pattern allows extending private constructors in types (TypeScript limitation).
|
|
22
|
+
- **Simple instanceof Fix:** If you just need runtime checks without extras, see the [minimal mode](#minimal-mode) in Registry section.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Table of contents
|
|
27
|
+
|
|
28
|
+
- [Quick start](#quick-start)
|
|
29
|
+
- [Install](#install)
|
|
30
|
+
- [Basic usage](#basic-usage)
|
|
31
|
+
- [Decorator pattern](#decorator-pattern)
|
|
32
|
+
- [HOF pattern](#hof-higher-order-function-pattern)
|
|
33
|
+
- [Minimal “first-run” example](#minimal-first-run-example)
|
|
34
|
+
- [Migration](#migration)
|
|
35
|
+
- [Limitations & guarantees](#limitations--guarantees)
|
|
36
|
+
- [What Sigil guarantees](#what-sigil-guarantees)
|
|
37
|
+
- [What Sigil does not guarantee](#what-sigil-does-not-guarantee)
|
|
38
|
+
- [Core concepts](#core-concepts)
|
|
39
|
+
- [Terminology](#terminology)
|
|
40
|
+
- [Purpose and Origins](#purpose-and-origins)
|
|
41
|
+
- [Implementation Mechanics](#implementation-mechanics)
|
|
42
|
+
- [Nominal typing patterns](#nominal-typing-patterns)
|
|
43
|
+
- [HOF pattern](#1-hof-pattern-_classclass)
|
|
44
|
+
- [Decorator pattern](#2-decorator-pattern)
|
|
45
|
+
- [API reference](#api-reference)
|
|
46
|
+
- [Options & configuration](#options--configuration)
|
|
47
|
+
- [Registry](#registry)
|
|
48
|
+
- [Security guidance](#security-guidance)
|
|
49
|
+
- [Minimal mode](#minimal-mode)
|
|
50
|
+
- [Troubleshooting & FAQ](#troubleshooting--faq)
|
|
51
|
+
- [Deprecated API](#deprecated-api)
|
|
52
|
+
- [Phantom](#phantom)
|
|
53
|
+
- [Contributing](#contributing)
|
|
54
|
+
- [License](#license)
|
|
55
|
+
- [Author](#author)
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Quick start
|
|
60
|
+
|
|
61
|
+
### Install
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm install @vicin/sigil
|
|
65
|
+
# or
|
|
66
|
+
yarn add @vicin/sigil
|
|
67
|
+
# or
|
|
68
|
+
pnpm add @vicin/sigil
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Requires TypeScript 5.0+ for decorators; HOFs work on older versions. Node.js 18+ recommended.
|
|
72
|
+
|
|
73
|
+
### Basic usage
|
|
74
|
+
|
|
75
|
+
#### Opt into `Sigil`
|
|
76
|
+
|
|
77
|
+
Use the `Sigil` base class or the `Sigilify` mixin to opt a class into the Sigil runtime contract.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { Sigil, Sigilify } from '@vicin/sigil';
|
|
81
|
+
|
|
82
|
+
// Using the pre-sigilified base class:
|
|
83
|
+
class User extends Sigil {}
|
|
84
|
+
|
|
85
|
+
// Or use Sigilify when you want an ad-hoc class:
|
|
86
|
+
const MyClass = Sigilify(class {}, '@myorg/mypkg.MyClass');
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
This adds runtime metadata to the constructor and allows you to use runtime helpers, see [API reference](#api-reference).
|
|
90
|
+
|
|
91
|
+
#### Extend `Sigil` classes
|
|
92
|
+
|
|
93
|
+
After opting into the `Sigil` contract, labels are passed to child classes to uniquely identify them, they can be supplied using two patterns:
|
|
94
|
+
|
|
95
|
+
##### Decorator pattern
|
|
96
|
+
|
|
97
|
+
Apply a label with the `@WithSigil` decorator. This is handy for small classes or when you prefer decorator syntax.
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { Sigil, WithSigil } from '@vicin/sigil';
|
|
101
|
+
|
|
102
|
+
@WithSigil('@myorg/mypkg.User')
|
|
103
|
+
class User extends Sigil {}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
> Note: When extending an already sigilified class (for example `Sigil`), you must decorate the subclass or use the HOF helpers in DEV mode unless you configured the library otherwise.
|
|
107
|
+
|
|
108
|
+
##### HOF (Higher-Order Function) pattern
|
|
109
|
+
|
|
110
|
+
HOFs work well in many build setups and are idempotent-safe for HMR flows.
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { Sigil, withSigil } from '@vicin/sigil';
|
|
114
|
+
|
|
115
|
+
class _User extends Sigil {}
|
|
116
|
+
const User = withSigil(_User, '@myorg/mypkg.User');
|
|
117
|
+
|
|
118
|
+
const user = new User();
|
|
119
|
+
console.log(User.SigilLabel); // "@myorg/mypkg.User"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Minimal “first-run” example
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import { Sigil, withSigil } from '@vicin/sigil';
|
|
126
|
+
|
|
127
|
+
class _User extends Sigil {
|
|
128
|
+
constructor(public name: string) {
|
|
129
|
+
super();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
export const User = withSigil(_User, '@myorg/mypkg.User');
|
|
133
|
+
|
|
134
|
+
const u = new User('alice');
|
|
135
|
+
|
|
136
|
+
console.log(User.SigilLabel); // "@myorg/mypkg.User"
|
|
137
|
+
console.log(User.isOfType(u)); // true
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Migration
|
|
141
|
+
|
|
142
|
+
Migrating old code into `Sigil` can be done seamlessly with this set-up:
|
|
143
|
+
|
|
144
|
+
1. Set `SigilOptions.autofillLabels` to `true` at the start of the app so no errors are thrown in the migration stage:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { updateOptions } from '@vicin/sigil';
|
|
148
|
+
updateOptions({ autofillLabels: true });
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
2. Pass your base class to `Sigilify` mixin:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import { Sigilify } from '@vicin/sigil';
|
|
155
|
+
|
|
156
|
+
const MySigilBaseClass = Sigilify(MyBaseClass);
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
3. Or extend it with `Sigil`:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import { Sigil } from '@vicin/sigil';
|
|
163
|
+
|
|
164
|
+
class MyBaseClass extends Sigil {} // <-- add 'extends Sigil' here
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Congratulations — you’ve opted into `Sigil` and you can start replacing `instanceof` with `isOfType`, however there is more to add to your system, check [Core concepts](#core-concepts) for more.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Limitations & guarantees
|
|
172
|
+
|
|
173
|
+
This section states clearly what `Sigil` provides and what it does **not** provide.
|
|
174
|
+
|
|
175
|
+
### What Sigil guarantees
|
|
176
|
+
|
|
177
|
+
**1. Stable label → symbol mapping within the same JS global symbol registry.**
|
|
178
|
+
|
|
179
|
+
**2. Reliable runtime identity (when used as intended).**
|
|
180
|
+
|
|
181
|
+
**3. Optional central registry for discovery & serialization helpers.**
|
|
182
|
+
|
|
183
|
+
**4. Nominal typing that is inheritance-aware**
|
|
184
|
+
|
|
185
|
+
### What Sigil does not guarantee
|
|
186
|
+
|
|
187
|
+
**1. Doesn't work across isolated realms (e.g., iframes, workers) without custom bridging.**
|
|
188
|
+
|
|
189
|
+
**2. Not for security/access control — constructors can be discoverable.**
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Core concepts
|
|
194
|
+
|
|
195
|
+
### Terminology
|
|
196
|
+
|
|
197
|
+
- **Label**: A human-readable identity (string) such as `@scope/pkg.ClassName`.
|
|
198
|
+
- **SigilType (symbol)**: `Symbol.for(label)` — for runtime stability.
|
|
199
|
+
- **Type lineage**: Array of symbols for ancestry.
|
|
200
|
+
- **Type set**: Set of symbols for fast checks.
|
|
201
|
+
- **Brand**: TypeScript marker (`__SIGIL_BRAND__`) for nominal types.
|
|
202
|
+
- **Registry**: A global Map of registered `Sigil` classes keyed by their labels.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
### Purpose and Origins
|
|
207
|
+
|
|
208
|
+
Sigil addresses issues in large monorepos and Domain-Driven Design (DDD):
|
|
209
|
+
|
|
210
|
+
- **Unreliable `instanceof`:** Bundling and HMR cause class redefinitions, breaking checks.
|
|
211
|
+
- **Manual Branding Overhead:** Custom identifiers lead to boilerplate and maintenance issues.
|
|
212
|
+
|
|
213
|
+
`Sigil` abstracts these into a **centralized system**, making identity management **explicit** and **error-resistant**.
|
|
214
|
+
|
|
215
|
+
### Implementation Mechanics
|
|
216
|
+
|
|
217
|
+
- **Runtime Contract:** Established via extending `Sigil` or using `Sigilify` mixin.
|
|
218
|
+
- **Update metadata:** With each new child, HOF or decorators are used to attach metadata and update nominal type.
|
|
219
|
+
- **Accessors & Type guards:** Classes expose `SigilLabel`, `SigilType`; instances provide `getSigilLabel()` and `getSigilType()` for querying unique identifier label or symbol. also when typed it hold nominal identity used to prevent subtle bugs.
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
import { Sigil, withSigilTyped, GetInstance } from '@vicin/sigil';
|
|
223
|
+
|
|
224
|
+
// Runtime contract
|
|
225
|
+
class _MyClass extends Sigil {}
|
|
226
|
+
|
|
227
|
+
// Update metadata (append new label)
|
|
228
|
+
const MyClass = withSigilTyped(_MyClass, '@scope/package.MyClass');
|
|
229
|
+
type MyClass = GetInstance<typeof MyClass>;
|
|
230
|
+
|
|
231
|
+
// Accessors & Type guards
|
|
232
|
+
console.log(MyClass.SigilLabel); // '@scope/package.MyClass'
|
|
233
|
+
console.log(new MyClass().getSigilType()); // Symbol.for('@scope/package.MyClass')
|
|
234
|
+
console.log(MyClass.isOfType(new MyClass())); // true
|
|
235
|
+
function x(c: MyClass) {} // Only instances created by 'MyClass' can be passed
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Nominal typing patterns
|
|
241
|
+
|
|
242
|
+
In this part we will discuss conventions to avoid any type errors and have nominal typing with just extra few definition lines.
|
|
243
|
+
We have two patterns, **HOF pattern (`_Class`/`Class`)** and **Decorator pattern**:
|
|
244
|
+
|
|
245
|
+
### 1. HOF pattern (`_Class`/`Class`)
|
|
246
|
+
|
|
247
|
+
Define implementation in an untyped class, then wrap for typing:
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
import { Sigil, withSigilTyped, GetInstance } from '@vicin/sigil';
|
|
251
|
+
|
|
252
|
+
class _X extends Sigil {
|
|
253
|
+
// Class logic here
|
|
254
|
+
}
|
|
255
|
+
export const X = withSigilTyped(_X, 'Label.X');
|
|
256
|
+
export type X = GetInstance<typeof X>;
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
#### `InstanceType<>` vs `GetInstance<>`
|
|
260
|
+
|
|
261
|
+
You should depend on `GetInstance` to get type of instance and avoid using `InstanceType` as it returns `any` if the class constructor is `protected` or `private`.
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
export type X = GetInstance<typeof X>; // <-- works with 'private' and 'protected' constructors as well
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Internally `GetInstance` is just `T extends { prototype: infer R }`.
|
|
268
|
+
|
|
269
|
+
#### Generic propagation
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
class _X<G> extends Sigil {}
|
|
273
|
+
export const X = withSigilTyped(_X, 'Label.X');
|
|
274
|
+
export type X<G> = GetInstance<typeof X<G>>; // <-- Redeclare generics here
|
|
275
|
+
|
|
276
|
+
class _Y<G> extends X<G> {} // and so on...
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
#### Anonymous classes
|
|
280
|
+
|
|
281
|
+
You may see error: `Property 'x' of exported anonymous class type may not be private or protected.`, although this is rare to occur.
|
|
282
|
+
This comes from the fact that all typed classes are `anonymous class` as they are return of HOF. to avoid these error entirely all you need is exporting the untyped classes even if they are un-used as a good convention.
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
export class _X extends Sigil {} // <-- Just add 'export' here
|
|
286
|
+
export const X = withSigilTyped(_X, 'Label.X');
|
|
287
|
+
export type X = GetInstance<typeof X>;
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
#### Private constructors
|
|
291
|
+
|
|
292
|
+
The only limitation in HOF approach is **extending private constructors**:
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
import { Sigil, withSigilTyped, GetInstance } from '@vicin/sigil';
|
|
296
|
+
class _X extends Sigil {
|
|
297
|
+
private constructor() {}
|
|
298
|
+
}
|
|
299
|
+
const X = withSigilTyped(_X, 'X');
|
|
300
|
+
type X = GetInstance<typeof X>;
|
|
301
|
+
|
|
302
|
+
class _Y extends X {} // <-- This is allowed!
|
|
303
|
+
const Y = withSigilTyped(_Y, 'Y');
|
|
304
|
+
type Y = GetInstance<typeof Y>;
|
|
305
|
+
|
|
306
|
+
const y = new Y(); // <-- Type here is any
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Unfortunately this is a limitation in typescript and I couldn't find any solution to address it.
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
### 2. Decorator pattern
|
|
314
|
+
|
|
315
|
+
Inject brand directly in class body:
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
import { Sigil, WithSigil, UpdateSigilBrand } from '@vicin/sigil';
|
|
319
|
+
|
|
320
|
+
@WithSigil('X')
|
|
321
|
+
class X extends Sigil {
|
|
322
|
+
declare __SIGIL_BRAND__: UpdateSigilBrand<'X', Sigil>;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
@WithSigil('Y')
|
|
326
|
+
class Y extends X {
|
|
327
|
+
declare __SIGIL_BRAND__: UpdateSigilBrand<'Y', X>;
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
No `_Class`/`Class` pattern, no `private constructor` issue, no type hacks and only one extra line, but our branding logic now lives in class body.
|
|
332
|
+
|
|
333
|
+
#### Label Consistency
|
|
334
|
+
|
|
335
|
+
Use typeof label for compile-time matching:
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
import { Sigil, WithSigil, UpdateSigilBrand } from '@vicin/sigil';
|
|
339
|
+
|
|
340
|
+
const label = 'X';
|
|
341
|
+
|
|
342
|
+
@WithSigil(label)
|
|
343
|
+
class X extends Sigil {
|
|
344
|
+
declare __SIGIL_BRAND__: UpdateSigilBrand<typeof label, Sigil>;
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## API reference
|
|
351
|
+
|
|
352
|
+
### Primary Exports
|
|
353
|
+
|
|
354
|
+
- **Mixin:**
|
|
355
|
+
- `Sigilify(Base, label?, opts?)`
|
|
356
|
+
|
|
357
|
+
- **Classes:**
|
|
358
|
+
- `Sigil`
|
|
359
|
+
- `SigilError`
|
|
360
|
+
|
|
361
|
+
- **Decorator:**
|
|
362
|
+
- `WithSigil(label, opts?)`
|
|
363
|
+
|
|
364
|
+
- **HOFs:**
|
|
365
|
+
- `withSigil(Class, label?, opts?)`
|
|
366
|
+
- `withSigilTyped(Class, label?, opts?)`
|
|
367
|
+
|
|
368
|
+
- **Helpers:**
|
|
369
|
+
- `isSigilCtor(ctor)`
|
|
370
|
+
- `isSigilInstance(inst)`
|
|
371
|
+
- `isSigilBaseCtor(ctor)`
|
|
372
|
+
- `isSigilBaseInstance(inst)`
|
|
373
|
+
- `isDecorated(ctor)`
|
|
374
|
+
- `isInheritanceChecked(ctor)`
|
|
375
|
+
|
|
376
|
+
- **Options/Registry:**
|
|
377
|
+
- `updateOptions(opts, mergeRegistries?)`
|
|
378
|
+
- `SigilRegistry`
|
|
379
|
+
- `getActiveRegistry`
|
|
380
|
+
- `DEFAULT_LABEL_REGEX`
|
|
381
|
+
- **Types:**
|
|
382
|
+
- `ISigil<Label, ParentSigil?>`
|
|
383
|
+
- `ISigilStatic<Label, ParentSigil?>`
|
|
384
|
+
- `ISigilInstance<Label, ParentSigil?>`
|
|
385
|
+
- `SigilBrandOf<T>`
|
|
386
|
+
- `TypedSigil<SigilClass, Label>`
|
|
387
|
+
- `GetInstance<T>`
|
|
388
|
+
- `UpdateSigilBrand<Label, Base>`
|
|
389
|
+
- `SigilOptions`
|
|
390
|
+
|
|
391
|
+
### Key helpers (runtime)
|
|
392
|
+
|
|
393
|
+
- `Sigil`: a minimal sigilified base class you can extend from.
|
|
394
|
+
- `SigilError`: an `Error` class decorated with a `Sigil` so it can be identified at runtime.
|
|
395
|
+
- `WithSigil(label)`: class decorator that attaches `Sigil` metadata at declaration time.
|
|
396
|
+
- `Sigilify(Base, label?, opts?)`: mixin function that returns a new constructor with `Sigil` types and instance helpers.
|
|
397
|
+
- `withSigil(Class, label?, opts?)`: HOF that validates and decorates an existing class constructor.
|
|
398
|
+
- `withSigilTyped(Class, label?, opts?)`: like `withSigil` but narrows the TypeScript type to include brands.
|
|
399
|
+
- `isSigilCtor(value)`: `true` if `value` is a `Sigil` constructor.
|
|
400
|
+
- `isSigilInstance(value)`: `true` if `value` is an instance of a `Sigil` constructor.
|
|
401
|
+
- `SigilRegistry`: `Sigil` Registry class used to centralize classes across app.
|
|
402
|
+
- `getActiveRegistry`: Getter of active registry being used by `Sigil`.
|
|
403
|
+
- `updateOptions(opts, mergeRegistries?)`: change global runtime options before `Sigil` decoration (e.g., `autofillLabels`, `devMarker`, etc.).
|
|
404
|
+
- `DEFAULT_LABEL_REGEX`: regex that ensures structure of `@scope/package.ClassName` to all labels, it's advised to use it as your `SigilOptions.labelValidation`
|
|
405
|
+
|
|
406
|
+
### Instance & static helpers provided by Sigilified constructors
|
|
407
|
+
|
|
408
|
+
When a constructor is decorated/sigilified it will expose the following **static** getters/methods:
|
|
409
|
+
|
|
410
|
+
- `SigilLabel` — the human label string.
|
|
411
|
+
- `SigilType` — the runtime symbol for the label.
|
|
412
|
+
- `SigilTypeLineage` — readonly array of symbols representing parent → child.
|
|
413
|
+
- `SigilTypeSet` — readonly `Set<symbol>` for O(1) checks.
|
|
414
|
+
- `isSigilified(obj)` — runtime predicate that delegates to `isSigilInstance`.
|
|
415
|
+
- `isOfType(other)` — O(1) membership test using `other`'s `__TYPE_SET__`.
|
|
416
|
+
- `isOfTypeStrict(other)` — strict lineage comparison element-by-element.
|
|
417
|
+
|
|
418
|
+
Instances of sigilified classes expose instance helpers:
|
|
419
|
+
|
|
420
|
+
- `getSigilLabel()` — returns the human label.
|
|
421
|
+
- `getSigilType()` — runtime symbol.
|
|
422
|
+
- `getSigilTypeLineage()` — returns lineage array.
|
|
423
|
+
- `getSigilTypeSet()` — returns readonly Set.
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
## Options & configuration
|
|
428
|
+
|
|
429
|
+
Customize behavior globally at startup:
|
|
430
|
+
|
|
431
|
+
```ts
|
|
432
|
+
import { updateOptions, SigilRegistry } from '@vicin/sigil';
|
|
433
|
+
|
|
434
|
+
updateOptions({
|
|
435
|
+
autofillLabels: false, // Automatically label unlabeled subclasses
|
|
436
|
+
skipLabelInheritanceCheck: false, // Bypass dev inheritance checks -- ALMOST NEVER WANT TO SET THIS TO TRUE, Use 'autofillLabels: true' instead.
|
|
437
|
+
labelValidation: null, // Function or regex, Enforce label format
|
|
438
|
+
devMarker: process.env.NODE_ENV !== 'production', // Toggle dev safeguards
|
|
439
|
+
registry: new SigilRegistry(), // Custom registry instance
|
|
440
|
+
useGlobalRegistry: true, // Store in 'globalThis' for cross-bundle access
|
|
441
|
+
storeConstructor: true, // Include constructors in registry
|
|
442
|
+
});
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
Values defined in previous example are defaults, per-class overrides available in mixin, decorators, and HOFs.
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## Registry
|
|
450
|
+
|
|
451
|
+
The registry ensures **unique labels** and supports central class management for ops as serialization.
|
|
452
|
+
|
|
453
|
+
- **Access:** `const registry = getActiveRegistry();` – Returns current `SigilRegistry` or `null`.
|
|
454
|
+
- **Operations:** `has(label)`, `get(label)`, `listLabels()`, `register(label, ctor, opts?)`, `unregister(label)`, `clear()`, `size`.
|
|
455
|
+
- **Replacement:** `updateOptions({ registry: new SigilRegistry(myMap) }, merge?);` – Optionally merge existing entries.
|
|
456
|
+
- **Disable:** Set `registry: null` to skip all registry functions.
|
|
457
|
+
- **Global Storage:** Defaults to `globalThis[Symbol.for('__SIGIL_REGISTRY__')];` disable with `useGlobalRegistry: false` if single-bundle guaranteed.
|
|
458
|
+
- **Constructor Privacy:** Set `storeConstructor: false` globally or per-class to replace constructors with null in the map.
|
|
459
|
+
|
|
460
|
+
### Class typing in registry
|
|
461
|
+
|
|
462
|
+
Unfortunately concrete types of classes is not supported and all classes are stored as `ISigil` type. if you want concrete typing, you can wrap registry:
|
|
463
|
+
|
|
464
|
+
```ts
|
|
465
|
+
import { getActiveRegistry } from '@vicin/sigil';
|
|
466
|
+
import type { MySigilClass1 } from './file1';
|
|
467
|
+
import type { MySigilClass2 } from './file2';
|
|
468
|
+
|
|
469
|
+
interface MyClasses {
|
|
470
|
+
MySigilClass1: typeof MySigilClass1;
|
|
471
|
+
MySigilClass2: typeof MySigilClass2;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
export class MySigilRegistry {
|
|
475
|
+
listLabels(): (keyof MyClasses)[] {
|
|
476
|
+
return getActiveRegistry()?.listLabels();
|
|
477
|
+
}
|
|
478
|
+
has(label: string): boolean {
|
|
479
|
+
return getActiveRegistry()?.has(label);
|
|
480
|
+
}
|
|
481
|
+
get<L extends keyof MyClasses>(label: L): MyClasses[L] {
|
|
482
|
+
return getActiveRegistry()?.get(label) as any;
|
|
483
|
+
}
|
|
484
|
+
unregister(label: string): boolean {
|
|
485
|
+
return getActiveRegistry()?.unregister(label);
|
|
486
|
+
}
|
|
487
|
+
clear(): void {
|
|
488
|
+
getActiveRegistry()?.clear();
|
|
489
|
+
}
|
|
490
|
+
replaceRegistry(newRegistry: Map<string, ISigil> | null): void {
|
|
491
|
+
getActiveRegistry()?.replaceRegistry(newRegistry);
|
|
492
|
+
}
|
|
493
|
+
get size(): number {
|
|
494
|
+
return getActiveRegistry()?.size;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
Now you have fully typed central class registry!
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
## Security guidance
|
|
504
|
+
|
|
505
|
+
- **Recommended for Untrusted Environments:** `updateOptions({ storeConstructor: false });` – Prevents constructors from being stored in the registry map (labels remain, but constructors are `null`).
|
|
506
|
+
|
|
507
|
+
- **Trusted Environments:** Enable full registry for centralization (default behavior).
|
|
508
|
+
|
|
509
|
+
- **Per-Class Control:** Use `{ storeConstructor: false }` for sensitive classes in decorator or HOF function.
|
|
510
|
+
|
|
511
|
+
Always remember, Registry is metadata-only; avoid for sensitive data. Global access possible if enabled.
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
## Minimal mode
|
|
516
|
+
|
|
517
|
+
`updateOptions({ autofillLabels: true, storeConstructor: false });` – Enables background operation without explicit labels or storage:
|
|
518
|
+
|
|
519
|
+
```ts
|
|
520
|
+
import { Sigil, updateOptions } from '@vicin/sigil';
|
|
521
|
+
|
|
522
|
+
// run at the start of the app
|
|
523
|
+
updateOptions({ autofillLabels: true, storeConstructor: false });
|
|
524
|
+
|
|
525
|
+
// No decorators or HOF needed to use 'isOfType' ('instanceof' replacement)
|
|
526
|
+
class A extends Sigil {}
|
|
527
|
+
class B extends A {}
|
|
528
|
+
class C extends B {}
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
## Troubleshooting & FAQ
|
|
534
|
+
|
|
535
|
+
- **Dev Extension Errors:** Add labels or enable autofillLabels.
|
|
536
|
+
- **Anonymous Class Errors:** Export untyped bases.
|
|
537
|
+
- **Selective Labeling:** Use `autofillLabels: true` or empty `@WithSigil()` for auto-generation.
|
|
538
|
+
- **Registry Inspection:** `getActiveRegistry()?.listLabels()`.
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
## Deprecated API
|
|
543
|
+
|
|
544
|
+
### REGISTRY
|
|
545
|
+
|
|
546
|
+
`Sigil` has moved from static reference registry to dynamic access and updates, now devs can create `SigilRegistry` class and pass it to `SigilOptions` to be used by the library internals. however change is done gracefully and `REGISTRY` is still supported with no change in behavior but it's **marked with `deprecated` and will be removed in v2.0.0**.
|
|
547
|
+
|
|
548
|
+
```ts
|
|
549
|
+
import { REGISTRY, getActiveRegistry } from '@vicin/sigil';
|
|
550
|
+
|
|
551
|
+
// from:
|
|
552
|
+
const present = REGISTRY.has('label');
|
|
553
|
+
// to:
|
|
554
|
+
const registry = getActiveRegistry();
|
|
555
|
+
const present = registry ? registry.has('label') : false;
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
```ts
|
|
559
|
+
import { REGISTRY, updateOptions, SigilRegistry } from '@vicin/sigil';
|
|
560
|
+
|
|
561
|
+
// from:
|
|
562
|
+
const newRegistry = new Map();
|
|
563
|
+
REGISTRY.replaceRegistry(newRegistry);
|
|
564
|
+
// to
|
|
565
|
+
const newRegistry = new SigilRegistry(); // can pass external map to constructor, this map will hold all classes
|
|
566
|
+
updateOptions({ registry: newRegistry });
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
### typed
|
|
570
|
+
|
|
571
|
+
Obsolete; mixins now handle typing natively. **marked with `deprecated` and will be removed in v2.0.0**
|
|
572
|
+
|
|
573
|
+
---
|
|
574
|
+
|
|
575
|
+
## Phantom
|
|
576
|
+
|
|
577
|
+
`Phantom` is another lightweight TypeScript library I created for achieving **nominal typing** on primitives and objects through type-only metadata. It solves the problem of structural typing in TypeScript allowing accidental misuse of identical shapes (e.g., confusing `UserId` and `PostId` as both strings) by enabling compile-time distinctions with features like **brands**, **constrained identities**, **variants for states**, **additive traits**, and **reversible transformations**. This makes it ideal for domain-driven design (DDD) without runtime overhead.
|
|
578
|
+
|
|
579
|
+
`Phantom` works seamlessly in conjunction with `Sigil`, use `Sigil` for nominal identity on classes (runtime-safe checks across bundles), and `Phantom` for primitives/objects. Together, they provide **end-to-end type safety**: e.g., a Sigil-branded `User` class could hold a Phantom-branded `UserId` string property, enforcing domain boundaries at both compile and runtime.
|
|
580
|
+
|
|
581
|
+
- **GitHub: [@phantom](https://github.com/ZiadTaha62/phantom)**
|
|
582
|
+
- **NPM: [@phantom](https://www.npmjs.com/package/@vicin/phantom)**
|
|
583
|
+
|
|
584
|
+
---
|
|
585
|
+
|
|
586
|
+
## Contributing
|
|
587
|
+
|
|
588
|
+
Any contributions you make are **greatly appreciated**.
|
|
589
|
+
|
|
590
|
+
Please see our [CONTRIBUTING.md](./CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
|
|
591
|
+
|
|
592
|
+
### Reporting bugs
|
|
593
|
+
|
|
594
|
+
If you encounter a bug:
|
|
595
|
+
|
|
596
|
+
- 1. Check existing issues first
|
|
597
|
+
- 2. Open a new issue with:
|
|
598
|
+
- Minimal reproduction
|
|
599
|
+
- Expected vs actual behavior
|
|
600
|
+
- Environment (Node, TS version)
|
|
601
|
+
|
|
602
|
+
Bug reports help improve Sigil — thank you! 🙏
|
|
603
|
+
|
|
604
|
+
## License
|
|
605
|
+
|
|
606
|
+
Distributed under the MIT License. See `LICENSE` for more information.
|
|
607
|
+
|
|
608
|
+
---
|
|
609
|
+
|
|
610
|
+
## Author
|
|
611
|
+
|
|
612
|
+
Built with ❤️ by **Ziad Taha**.
|
|
613
|
+
|
|
614
|
+
- **GitHub: [@ZiadTaha62](https://github.com/ZiadTaha62)**
|
|
615
|
+
- **NPM: [@ziadtaha62](https://www.npmjs.com/~ziadtaha62)**
|
|
616
|
+
- **Vicin: [@vicin](https://www.npmjs.com/org/vicin)**
|