@vicin/sigil 1.0.0 → 1.1.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/LICENSE +1 -1
- package/README.md +210 -57
- package/dist/index.d.mts +777 -0
- package/dist/index.d.ts +777 -3
- package/dist/index.js +701 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +684 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +12 -4
- package/dist/core/classes.d.ts +0 -48
- package/dist/core/classes.d.ts.map +0 -1
- package/dist/core/classes.js +0 -18
- package/dist/core/classes.js.map +0 -1
- package/dist/core/decorator.d.ts +0 -28
- package/dist/core/decorator.d.ts.map +0 -1
- package/dist/core/decorator.js +0 -48
- package/dist/core/decorator.js.map +0 -1
- package/dist/core/enhancers.d.ts +0 -58
- package/dist/core/enhancers.d.ts.map +0 -1
- package/dist/core/enhancers.js +0 -101
- package/dist/core/enhancers.js.map +0 -1
- package/dist/core/helpers.d.ts +0 -192
- package/dist/core/helpers.d.ts.map +0 -1
- package/dist/core/helpers.js +0 -349
- package/dist/core/helpers.js.map +0 -1
- package/dist/core/index.d.ts +0 -9
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js +0 -8
- package/dist/core/index.js.map +0 -1
- package/dist/core/mixin.d.ts +0 -115
- package/dist/core/mixin.d.ts.map +0 -1
- package/dist/core/mixin.js +0 -209
- package/dist/core/mixin.js.map +0 -1
- package/dist/core/options.d.ts +0 -74
- package/dist/core/options.d.ts.map +0 -1
- package/dist/core/options.js +0 -39
- package/dist/core/options.js.map +0 -1
- package/dist/core/registry.d.ts +0 -104
- package/dist/core/registry.d.ts.map +0 -1
- package/dist/core/registry.js +0 -174
- package/dist/core/registry.js.map +0 -1
- package/dist/core/symbols.d.ts +0 -96
- package/dist/core/symbols.d.ts.map +0 -1
- package/dist/core/symbols.js +0 -96
- package/dist/core/symbols.js.map +0 -1
- package/dist/core/types.d.ts +0 -169
- package/dist/core/types.d.ts.map +0 -1
- package/dist/core/types.js +0 -2
- package/dist/core/types.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/utils/index.d.ts +0 -2
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -2
- package/dist/utils/index.js.map +0 -1
package/LICENSE
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Copyright (c)
|
|
1
|
+
Copyright (c) 2026 Ziad Taha and contributors
|
|
2
2
|
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
4
|
|
package/README.md
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# Sigil
|
|
2
2
|
|
|
3
|
-
`Sigil` is a lightweight TypeScript library for creating **nominal identity classes** with compile-time branding and reliable runtime type checks. It
|
|
3
|
+
`Sigil` is a lightweight TypeScript library for creating **nominal identity classes** with compile-time branding and reliable runtime type checks. It organizes classes across your code and gives you power of **nominal typing**, **safe class checks across bundles** and **centralized registry** where reference to every class constructor is stored and enforced to have it's own unique label and symbol.
|
|
4
4
|
|
|
5
5
|
> **Key ideas:**
|
|
6
6
|
>
|
|
7
7
|
> - **Compile-time nominal typing** via type brands so two structurally-identical types can remain distinct.
|
|
8
8
|
> - **Reliable runtime guards** using `Symbol.for(...)` and lineage sets instead of `instanceof`.
|
|
9
9
|
> - **Inheritance-aware identity**: lineages and sets let you test for subtype/supertype relationships.
|
|
10
|
-
> - **Centralized class registry**: every class have
|
|
10
|
+
> - **Centralized class registry**: every class have its own unique label and symbol that can be used as an id throughout the codebase.
|
|
11
11
|
|
|
12
12
|
**Note: You should read these parts before implementing `Sigil` in you code:**
|
|
13
13
|
|
|
14
|
-
- **Security note:** `Sigil`
|
|
14
|
+
- **Security note:** By default, `Sigil` stores constructor references in the global registry. While it doesn't expose private instance data, it does mean any module can get constructor of the class. if you have sensitive classes that you want to be unaccessable outside it's module update global or per class options (e.g. `updateOptions({ storeConstructor: false })` or `@WithSigil("label", { storeConstructor: false })`). read more [Registery](#registry).
|
|
15
15
|
|
|
16
|
-
- **Performance note:** `Sigil` attaches couple methods to every sigilized class instance, this is negligible in almost all cases, also `.isOfType()` although being reliable and performance optimized but it still
|
|
16
|
+
- **Performance note:** `Sigil` attaches couple methods to every sigilized class instance, this is negligible in almost all cases, also `.isOfType()` although being reliable and performance optimized but it still less performant that native `instanceof` checks, so if you want maximum performance in cases like hot-path code it is not advised to use `Sigil` as it's built for consistency and maintainability mainly at the cost of minimal performance overhead.
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
@@ -34,17 +34,26 @@
|
|
|
34
34
|
- [Registry](#registry)
|
|
35
35
|
- [Troubleshooting & FAQ](#troubleshooting--faq)
|
|
36
36
|
- [Best practices](#best-practices)
|
|
37
|
+
- [Deprecated API](#deprecated-api)
|
|
37
38
|
- [Phantom](#phantom)
|
|
39
|
+
- [Contributing](#contributing)
|
|
40
|
+
- [License](#license)
|
|
41
|
+
- [Author](#author)
|
|
38
42
|
|
|
39
43
|
---
|
|
40
44
|
|
|
41
45
|
## Features
|
|
42
46
|
|
|
43
47
|
- Attach **stable runtime identity** to classes using `Symbol.for(label)`.
|
|
48
|
+
|
|
44
49
|
- **Type-level branding** so distinct domain identities are enforced by TypeScript.
|
|
50
|
+
|
|
45
51
|
- **Lineage tracking** (arrays + sets of symbols) for O(1) and O(n) checks.
|
|
52
|
+
|
|
46
53
|
- Easy to use: decorator (`@WithSigil`), mixin (`Sigilify`), and HOF (Higher order function) helpers (`withSigil`, `withSigilTyped`, `typed`).
|
|
54
|
+
|
|
47
55
|
- **Global registry** to centralize classes (query any class by it's label in run-time) and guard against duplicate labels.
|
|
56
|
+
|
|
48
57
|
- Minimal runtime overhead in production (DEV checks can be toggled off).
|
|
49
58
|
|
|
50
59
|
---
|
|
@@ -54,11 +63,11 @@
|
|
|
54
63
|
### Install
|
|
55
64
|
|
|
56
65
|
```bash
|
|
57
|
-
npm install sigil
|
|
66
|
+
npm install @vicin/sigil
|
|
58
67
|
# or
|
|
59
|
-
yarn add sigil
|
|
68
|
+
yarn add @vicin/sigil
|
|
60
69
|
# or
|
|
61
|
-
pnpm add sigil
|
|
70
|
+
pnpm add @vicin/sigil
|
|
62
71
|
```
|
|
63
72
|
|
|
64
73
|
**Requirements**: TypeScript 5.0+ (for stage-3 decorators) and Node.js 18+ recommended.
|
|
@@ -68,7 +77,7 @@ pnpm add sigil
|
|
|
68
77
|
Use the `Sigil` base class or the `Sigilify` mixin to opt a class into the Sigil runtime contract.
|
|
69
78
|
|
|
70
79
|
```ts
|
|
71
|
-
import { Sigil, Sigilify } from 'sigil';
|
|
80
|
+
import { Sigil, Sigilify } from '@vicin/sigil';
|
|
72
81
|
|
|
73
82
|
// Using the pre-sigilified base class:
|
|
74
83
|
class User extends Sigil {}
|
|
@@ -84,7 +93,7 @@ This adds runtime metadata to the constructor and allows you to use runtime help
|
|
|
84
93
|
Apply a label with the `@WithSigil` decorator. This is handy for small classes or when you prefer decorator syntax.
|
|
85
94
|
|
|
86
95
|
```ts
|
|
87
|
-
import { Sigil, WithSigil } from 'sigil';
|
|
96
|
+
import { Sigil, WithSigil } from '@vicin/sigil';
|
|
88
97
|
|
|
89
98
|
@WithSigil('@myorg/mypkg.User')
|
|
90
99
|
class User extends Sigil {}
|
|
@@ -97,7 +106,7 @@ class User extends Sigil {}
|
|
|
97
106
|
HOFs work well in many build setups and are idempotent-safe for HMR flows.
|
|
98
107
|
|
|
99
108
|
```ts
|
|
100
|
-
import { Sigil, withSigil } from 'sigil';
|
|
109
|
+
import { Sigil, withSigil } from '@vicin/sigil';
|
|
101
110
|
|
|
102
111
|
class _User extends Sigil {}
|
|
103
112
|
const User = withSigil(_User, '@myorg/mypkg.User');
|
|
@@ -111,7 +120,7 @@ console.log(User.SigilLabel); // "@myorg/mypkg.User"
|
|
|
111
120
|
If you want TypeScript to treat identities nominally (so `UserId` !== `PostId` despite identical shape), use the typed helpers.
|
|
112
121
|
|
|
113
122
|
```ts
|
|
114
|
-
import { Sigil, withSigilTyped, GetInstance } from 'sigil';
|
|
123
|
+
import { Sigil, withSigilTyped, GetInstance } from '@vicin/sigil';
|
|
115
124
|
|
|
116
125
|
class _User extends Sigil {}
|
|
117
126
|
const User = withSigilTyped(_User, '@myorg/mypkg.User');
|
|
@@ -128,14 +137,14 @@ Migration old code into `Sigil` can be done seamlessly with this set-up:
|
|
|
128
137
|
1. Set `SigilOptions.autofillLabels` to `true` at the start of the app so no errors are thrown in the migration stage:
|
|
129
138
|
|
|
130
139
|
```ts
|
|
131
|
-
import { updateOptions } from 'sigil';
|
|
140
|
+
import { updateOptions } from '@vicin/sigil';
|
|
132
141
|
updateOptions({ autofillLabels: true });
|
|
133
142
|
```
|
|
134
143
|
|
|
135
144
|
2. Make you base classes extends `Sigil`:
|
|
136
145
|
|
|
137
146
|
```ts
|
|
138
|
-
import { Sigil } from 'sigil';
|
|
147
|
+
import { Sigil } from '@vicin/sigil';
|
|
139
148
|
|
|
140
149
|
class MyBaseClass {} // original
|
|
141
150
|
|
|
@@ -160,7 +169,7 @@ But there is more to add to your system, which will be discussed in the [Core co
|
|
|
160
169
|
|
|
161
170
|
---
|
|
162
171
|
|
|
163
|
-
###
|
|
172
|
+
### Why `Sigil` exists
|
|
164
173
|
|
|
165
174
|
`Sigil` was born out of real-world friction in a large **monorepo** built with **Domain-Driven Design (DDD)**.
|
|
166
175
|
|
|
@@ -215,7 +224,7 @@ Basic patterns:
|
|
|
215
224
|
Mixin / factory:
|
|
216
225
|
|
|
217
226
|
```ts
|
|
218
|
-
import { Sigilify } from 'sigil';
|
|
227
|
+
import { Sigilify } from '@vicin/sigil';
|
|
219
228
|
|
|
220
229
|
const MyClass = Sigilify(class {}, 'MyClass');
|
|
221
230
|
```
|
|
@@ -223,7 +232,7 @@ const MyClass = Sigilify(class {}, 'MyClass');
|
|
|
223
232
|
Direct base-class extend:
|
|
224
233
|
|
|
225
234
|
```ts
|
|
226
|
-
import { Sigil, WithSigil } from 'sigil';
|
|
235
|
+
import { Sigil, WithSigil } from '@vicin/sigil';
|
|
227
236
|
|
|
228
237
|
class MyClass extends Sigil {}
|
|
229
238
|
```
|
|
@@ -234,7 +243,7 @@ Once you opt into the runtime contract, Sigil enforces consistency: in DEV mode,
|
|
|
234
243
|
Decorator style:
|
|
235
244
|
|
|
236
245
|
```ts
|
|
237
|
-
import { Sigil, WithSigil } from 'sigil';
|
|
246
|
+
import { Sigil, WithSigil } from '@vicin/sigil';
|
|
238
247
|
|
|
239
248
|
@WithSigil('MyClass') // <-- Note `@WithSigil` used here cause it extended alreay sigilized class (Sigil). Error is thrown without it.
|
|
240
249
|
class MyClass extends Sigil {}
|
|
@@ -243,7 +252,7 @@ class MyClass extends Sigil {}
|
|
|
243
252
|
HOF (preferred for many workflows):
|
|
244
253
|
|
|
245
254
|
```ts
|
|
246
|
-
import { Sigil, withSigil } from 'sigil';
|
|
255
|
+
import { Sigil, withSigil } from '@vicin/sigil';
|
|
247
256
|
|
|
248
257
|
class _MyClass extends Sigil {}
|
|
249
258
|
const MyClass = withSigil(_MyClass, 'MyClass');
|
|
@@ -262,7 +271,7 @@ Runtime metadata alone does not change TypeScript types. To get compile-time nom
|
|
|
262
271
|
Example using a typed HOF:
|
|
263
272
|
|
|
264
273
|
```ts
|
|
265
|
-
import { Sigil, withSigilTyped, GetInstance } from 'sigil';
|
|
274
|
+
import { Sigil, withSigilTyped, GetInstance } from '@vicin/sigil';
|
|
266
275
|
|
|
267
276
|
// Untyped (runtime) base you extend as normal TS class code:
|
|
268
277
|
class _User extends Sigil {}
|
|
@@ -288,10 +297,10 @@ The typed approach requires redefinition of public class, so you have:
|
|
|
288
297
|
|
|
289
298
|
This separation is necessary as typescript decorators doesn't affect type system. so to reflect type update the class should be passed to HOF.
|
|
290
299
|
|
|
291
|
-
Example of
|
|
300
|
+
Example of approach for class chain:
|
|
292
301
|
|
|
293
302
|
```ts
|
|
294
|
-
import { Sigil, withSigilTyped, GetInstance } from 'sigil';
|
|
303
|
+
import { Sigil, withSigilTyped, GetInstance } from '@vicin/sigil';
|
|
295
304
|
|
|
296
305
|
// Untyped base classes used for implementation:
|
|
297
306
|
class _User extends Sigil {}
|
|
@@ -334,7 +343,7 @@ The update of Sigil brand types happens via HOF that are defined below actual cl
|
|
|
334
343
|
Example:
|
|
335
344
|
|
|
336
345
|
```ts
|
|
337
|
-
import { Sigil, withSigilTyped } from 'sigil';
|
|
346
|
+
import { Sigil, withSigilTyped } from '@vicin/sigil';
|
|
338
347
|
|
|
339
348
|
class _X extends Sigil {
|
|
340
349
|
// All logic for class
|
|
@@ -367,7 +376,7 @@ Earlier example used `InstanceType<>` to get instance of the class. It works wel
|
|
|
367
376
|
So alternative in introduced which is `GetInstance`.
|
|
368
377
|
|
|
369
378
|
```ts
|
|
370
|
-
import { Sigil, withSigilTyped, GetInstance } from 'sigil';
|
|
379
|
+
import { Sigil, withSigilTyped, GetInstance } from '@vicin/sigil';
|
|
371
380
|
|
|
372
381
|
class _X extends Sigil {}
|
|
373
382
|
|
|
@@ -386,7 +395,7 @@ One of the downsides of defining typed class at the bottom is that we need to re
|
|
|
386
395
|
Example of generic propagation:
|
|
387
396
|
|
|
388
397
|
```ts
|
|
389
|
-
import { Sigil, withSigilTyped, GetInstance } from 'sigil';
|
|
398
|
+
import { Sigil, withSigilTyped, GetInstance } from '@vicin/sigil';
|
|
390
399
|
|
|
391
400
|
// Untyped base classes used for implementation:
|
|
392
401
|
class _X<G> extends Sigil {}
|
|
@@ -400,10 +409,10 @@ export type X<G> = GetInstance<typeof X<G>>; // <-- Generics re-defined here, ju
|
|
|
400
409
|
### Anonymous classes
|
|
401
410
|
|
|
402
411
|
You may see error: `Property 'x' of exported anonymous class type may not be private or protected.`, although this is rare to occur.
|
|
403
|
-
This comes from the fact that all typed classes are `anonymous class` as they are return of HOF and ts compiler struggle to type them safely. to avoid these error
|
|
412
|
+
This comes from the fact that all typed classes are `anonymous class` as they are return of HOF and ts compiler struggle to type them safely. to avoid these error entirely all you need is exporting the untyped classes even if they are un-used as a good convention.
|
|
404
413
|
|
|
405
414
|
```ts
|
|
406
|
-
import { Sigil, withSigilTyped, GetInstance } from 'sigil';
|
|
415
|
+
import { Sigil, withSigilTyped, GetInstance } from '@vicin/sigil';
|
|
407
416
|
|
|
408
417
|
export class _X extends Sigil {} // <-- Just add 'export' here
|
|
409
418
|
|
|
@@ -434,8 +443,12 @@ export {
|
|
|
434
443
|
isSigilInstance,
|
|
435
444
|
} from './helpers';
|
|
436
445
|
export { Sigilify } from './mixin';
|
|
437
|
-
export {
|
|
438
|
-
|
|
446
|
+
export {
|
|
447
|
+
updateOptions,
|
|
448
|
+
SigilRegistry,
|
|
449
|
+
getActiveRegistry,
|
|
450
|
+
DEFAULT_LABEL_REGEX,
|
|
451
|
+
} from './options';
|
|
439
452
|
export type {
|
|
440
453
|
ISigil,
|
|
441
454
|
ISigilInstance,
|
|
@@ -443,6 +456,7 @@ export type {
|
|
|
443
456
|
TypedSigil,
|
|
444
457
|
GetInstance,
|
|
445
458
|
SigilBrandOf,
|
|
459
|
+
SigilOptions,
|
|
446
460
|
} from './types';
|
|
447
461
|
```
|
|
448
462
|
|
|
@@ -457,8 +471,9 @@ export type {
|
|
|
457
471
|
- `typed(Class, label?, parent?)`: type-only narrowing helper (no runtime mutation) — asserts runtime label in DEV.
|
|
458
472
|
- `isSigilCtor(value)`: `true` if `value` is a sigil constructor.
|
|
459
473
|
- `isSigilInstance(value)`: `true` if `value` is an instance of a sigil constructor.
|
|
460
|
-
- `
|
|
461
|
-
- `
|
|
474
|
+
- `SigilRegistry`: `Sigil` Registy class used to centralize classes across app.
|
|
475
|
+
- `getActiveRegistry`: Getter of active registry being used by `Sigil`.
|
|
476
|
+
- `updateOptions(opts, mergeRegistries?)`: change global runtime options before sigil decoration (e.g., `autofillLabels`, `devMarker`, etc.).
|
|
462
477
|
- `DEFAULT_LABEL_REGEX`: regex that insures structure of `@scope/package.ClassName` to all labels, it's advised to use it as your `SigilOptions.labelValidation`
|
|
463
478
|
|
|
464
479
|
### Instance & static helpers provided by Sigilified constructors
|
|
@@ -484,25 +499,34 @@ Instances of sigilified classes expose instance helpers:
|
|
|
484
499
|
|
|
485
500
|
## Options & configuration
|
|
486
501
|
|
|
487
|
-
Sigil exposes a small set of runtime options that control DEV behavior. These can be modified at app startup via `updateOptions(...)
|
|
502
|
+
Sigil exposes a small set of runtime options that control registry and DEV behavior. These can be modified at app startup via `updateOptions(...)` to set global options:
|
|
488
503
|
|
|
489
504
|
```ts
|
|
490
|
-
import { updateOptions } from 'sigil';
|
|
505
|
+
import { updateOptions, SigilRegistry } from '@vicin/sigil';
|
|
506
|
+
|
|
507
|
+
// Values defined in this example are defaults:
|
|
491
508
|
|
|
492
509
|
updateOptions({
|
|
493
510
|
autofillLabels: false, // auto-generate labels for subclasses that would otherwise inherit
|
|
494
511
|
skipLabelInheritanceCheck: false, // skip DEV-only inheritance checks -- ALMOST NEVER WANT TO SET THIS TO TRUE, Use 'autofillLabels: true' instead. --
|
|
495
512
|
labelValidation: null, // or a RegExp / function to validate labels
|
|
496
513
|
devMarker: process.env.NODE_ENV !== 'production', // boolean used to block dev only checks in non-dev enviroments
|
|
514
|
+
registry: new SigilRegistry(), // setting active registry used by 'Sigil'
|
|
515
|
+
useGlobalRegistry: true, // append registry into 'globalThis' to insure single source in the runtime in cross bundles.
|
|
516
|
+
storeConstructor: true, // store reference of the constructor in registry
|
|
497
517
|
});
|
|
498
518
|
```
|
|
499
519
|
|
|
520
|
+
Global options can be overridden per class by `opts` field in decorator and HOF.
|
|
521
|
+
|
|
500
522
|
**Notes**:
|
|
501
523
|
|
|
502
|
-
- It's advised to use `updateOptions({ labelValidation: DEFAULT_LABEL_REGEX })` at app entry to validate labels against `@scope/package.ClassName` structure.
|
|
524
|
+
- It's advised to use `updateOptions({ labelValidation: DEFAULT_LABEL_REGEX })` at app entry point to validate labels against `@scope/package.ClassName` structure.
|
|
503
525
|
- `devMarker` drives DEV-only checks — when `false`, many runtime validations are no-ops (useful for production builds).
|
|
504
526
|
- `autofillLabels` is useful for some HMR/test setups where you prefer not to throw on collisions and want autogenerated labels.
|
|
505
527
|
- `skipLabelInheritanceCheck = true` can result on subtle bugs if enabled, so avoid setting it to true.
|
|
528
|
+
- When `SigilOptions.registry` is updated, old registry entries is merged and registered into new registry, to disable this behavrio pass `false` to `mergeRegistries` (`updateOptions({ registry: newRegistry }, false)`)
|
|
529
|
+
- `useGlobalRegistry` makes Sigil registry a central manager of classes and reliable way to enforce single label usage, so avoid setting it to `false` except if you have a strong reason. if you want to avoid making class constructor accessible via `globalThis` use `storeConstructor = true` instead.
|
|
506
530
|
|
|
507
531
|
---
|
|
508
532
|
|
|
@@ -510,28 +534,82 @@ updateOptions({
|
|
|
510
534
|
|
|
511
535
|
`Sigil` with default options forces devs to `SigilLabel` every class defined, that allows central class registery that store a reference for every class keyed by its label, also it prevent two classes in the codebase from having the same `SigilLabel`.
|
|
512
536
|
|
|
513
|
-
This is mainly useful in large codebases or frameworks where they need central registry or if you need class transport across API, workers, etc... where you can use `SigilLabel` reliably to serialize class identity. to interact with registry `Sigil` exposes `
|
|
537
|
+
This is mainly useful in large codebases or frameworks where they need central registry or if you need class transport across API, workers, etc... where you can use `SigilLabel` reliably to serialize class identity. to interact with registry `Sigil` exposes `getActiveRegistry` and `SigilRegistry` class. also you can update registry related options with `updateOptions`.
|
|
538
|
+
|
|
539
|
+
By default, registry is stored in `globalThis` under `Symbol.for(__SIGIL_REGISTRY__)` so one instance is used across runtime even with multiple bundles, but this also exposes that map anywhere in the code, see [globalThis and security](#globalthis-and-security).
|
|
540
|
+
|
|
541
|
+
### Get registry
|
|
514
542
|
|
|
515
|
-
|
|
543
|
+
You can interact with registry using `getActiveRegistry`, this function returns registry currently in use:
|
|
516
544
|
|
|
517
|
-
|
|
545
|
+
```ts
|
|
546
|
+
import { getActiveRegistry } from '@vicin/sigil';
|
|
547
|
+
const registry = getActiveRegistry();
|
|
548
|
+
if (registry) console.log(registry.listLabels()); // check for presence as it can be 'null' if 'updateOptions({ registry: null })' is used
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### Replace registry
|
|
552
|
+
|
|
553
|
+
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`:
|
|
554
|
+
|
|
555
|
+
```ts
|
|
556
|
+
import { SigilRegistry, updateOptions } from '@vicin/sigil';
|
|
557
|
+
|
|
558
|
+
const myMap = new Map();
|
|
559
|
+
const myRegistry = new SigilRegistry(myMap);
|
|
560
|
+
updateOptions({ registry: myRegistry });
|
|
518
561
|
|
|
519
|
-
|
|
562
|
+
// Now 'Sigil' register new labels and constructors to 'myMap'.
|
|
563
|
+
```
|
|
520
564
|
|
|
521
|
-
|
|
565
|
+
By default `Sigil` will merge old registry map into `myMap`, to prevent this behavior:
|
|
522
566
|
|
|
523
|
-
|
|
567
|
+
```ts
|
|
568
|
+
updateOptions({ registry: myRegistry }, false); // <-- add false here
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
Also you can set registry to `null`, but this is not advised as it disable all registry operations entirely:
|
|
572
|
+
|
|
573
|
+
```ts
|
|
574
|
+
import { updateOptions } from '@vicin/sigil';
|
|
575
|
+
updateOptions({ registry: null }); // No label checks and registry map is freed from memory
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### globalThis and security
|
|
579
|
+
|
|
580
|
+
By default registry is stored in `globalThis`. to disable this behavior you can:
|
|
581
|
+
|
|
582
|
+
```ts
|
|
583
|
+
import { updateOptions } from '@vicin/sigil';
|
|
584
|
+
updateOptions({ useGlobalRegistry: false });
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
Before applying this change, for registry to function normally you should insure that `Sigil` is not bundles twice in your app.
|
|
588
|
+
however if you can't insure that only bundle of `Sigil` is used and don't want class constructors to be accessible globally do this:
|
|
589
|
+
|
|
590
|
+
```ts
|
|
591
|
+
import { updateOptions } from '@vicin/sigil';
|
|
592
|
+
updateOptions({ storeConstructor: false });
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
Now registry only stores label of this classes and all class constructors are in the map are replaced with `null`.
|
|
596
|
+
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:
|
|
597
|
+
|
|
598
|
+
```ts
|
|
599
|
+
import { withSigil, Sigil } from '@vicin/sigil';
|
|
600
|
+
|
|
601
|
+
class _X extends Sigil {}
|
|
602
|
+
const X = withSigil(_X, 'X', { storeConstructor: false });
|
|
603
|
+
```
|
|
524
604
|
|
|
525
|
-
|
|
526
|
-
- 2. Attach this new map to `globalThis`.
|
|
527
|
-
- 3. Store every new class defined in the passed map.
|
|
605
|
+
Pick whatever pattern you like!
|
|
528
606
|
|
|
529
|
-
### Class typing
|
|
607
|
+
### Class typing in registry
|
|
530
608
|
|
|
531
609
|
Unfortunately concrete types of classes is not supported and all classes are stored as `ISigil` type. if you want concrete typings you can wrap registery:
|
|
532
610
|
|
|
533
611
|
```ts
|
|
534
|
-
import {
|
|
612
|
+
import { getActiveRegistry } from '@vicin/sigil';
|
|
535
613
|
import { MySigilClass1 } from './file1';
|
|
536
614
|
import { MySigilClass2 } from './file2';
|
|
537
615
|
|
|
@@ -542,25 +620,25 @@ interface MyClasses {
|
|
|
542
620
|
|
|
543
621
|
class MySigilRegistry {
|
|
544
622
|
listLabels(): (keyof MyClasses)[] {
|
|
545
|
-
return
|
|
623
|
+
return getActiveRegistry()?.listLabels();
|
|
546
624
|
}
|
|
547
625
|
has(label: string): boolean {
|
|
548
|
-
return
|
|
626
|
+
return getActiveRegistry()?.has(label);
|
|
549
627
|
}
|
|
550
628
|
get<L extends keyof MyClasses>(label: L): MyClasses[L] {
|
|
551
|
-
return
|
|
629
|
+
return getActiveRegistry()?.get(label) as any;
|
|
552
630
|
}
|
|
553
631
|
unregister(label: string): boolean {
|
|
554
|
-
return
|
|
632
|
+
return getActiveRegistry()?.unregister(label);
|
|
555
633
|
}
|
|
556
634
|
clear(): void {
|
|
557
|
-
|
|
635
|
+
getActiveRegistry()?.clear();
|
|
558
636
|
}
|
|
559
637
|
replaceRegistry(newRegistry: Map<string, ISigil> | null): void {
|
|
560
|
-
|
|
638
|
+
getActiveRegistry()?.replaceRegistry(newRegistry);
|
|
561
639
|
}
|
|
562
640
|
get size(): number {
|
|
563
|
-
return
|
|
641
|
+
return getActiveRegistry()?.size;
|
|
564
642
|
}
|
|
565
643
|
}
|
|
566
644
|
|
|
@@ -569,10 +647,30 @@ export const MY_SIGIL_REGISTRY = new MySigilRegistry();
|
|
|
569
647
|
|
|
570
648
|
Now you have fully typed central class registry!
|
|
571
649
|
|
|
572
|
-
###
|
|
650
|
+
### I don't care about nominal types or central registry, i just want a runtime replacement of 'instanceof'
|
|
573
651
|
|
|
574
|
-
|
|
575
|
-
|
|
652
|
+
You can run this at the start of your app:
|
|
653
|
+
|
|
654
|
+
```ts
|
|
655
|
+
import { updateOptions } from '@vicin/sigil';
|
|
656
|
+
updateOptions({ autofillLabels: true, storeConstructor: false });
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
now you can omit all `HOF`, `Decorators` and make `Sigil` work in the background:
|
|
660
|
+
|
|
661
|
+
```ts
|
|
662
|
+
import { Sigil } from '@vicin/sigil';
|
|
663
|
+
|
|
664
|
+
class X extends Sigil {}
|
|
665
|
+
class Y extends X {}
|
|
666
|
+
class Z extends Y {}
|
|
667
|
+
|
|
668
|
+
Z.isOfType(new Y()); // true
|
|
669
|
+
Z.isOfType(new X()); // true
|
|
670
|
+
Y.isOfType(new Y()); // false
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
No class constructors are stored globally and no code overhead, moreover if you can insure that `Sigil` is not bundles twice you can disable `useGlobalRegistry` and no trace of sigil in `globalThis`.
|
|
576
674
|
|
|
577
675
|
---
|
|
578
676
|
|
|
@@ -588,7 +686,7 @@ A: Use `@WithSigil("@your/label")`, or wrap the subclass with `withSigil` / `wit
|
|
|
588
686
|
|
|
589
687
|
**Q: I got this error: 'Property 'x' of exported anonymous class type may not be private or protected.', How to fix it?**
|
|
590
688
|
|
|
591
|
-
A: This error
|
|
689
|
+
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.
|
|
592
690
|
|
|
593
691
|
**Q: I need nominal types in TypeScript. Which helper do I use?**
|
|
594
692
|
|
|
@@ -596,9 +694,9 @@ A: Use `withSigilTyped` to both attach runtime metadata and apply compile-time b
|
|
|
596
694
|
|
|
597
695
|
**Q: How do I inspect currently registered labels?**
|
|
598
696
|
|
|
599
|
-
A: Use `
|
|
697
|
+
A: Use `getActiveRegistry()?.list()` to get an array of registered labels.
|
|
600
698
|
|
|
601
|
-
**Q: What if i want to omit labeling in some classes while
|
|
699
|
+
**Q: What if i want to omit labeling in some classes while enforce others?**
|
|
602
700
|
|
|
603
701
|
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.
|
|
604
702
|
|
|
@@ -613,8 +711,63 @@ A: You can set `SigilOptions.autofillLabels` to `true`. or if you more strict en
|
|
|
613
711
|
|
|
614
712
|
---
|
|
615
713
|
|
|
714
|
+
## Deprecated API
|
|
715
|
+
|
|
716
|
+
### REGISTRY
|
|
717
|
+
|
|
718
|
+
`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**.
|
|
719
|
+
|
|
720
|
+
```ts
|
|
721
|
+
import { REGISTRY, getActiveRegistry } from '@vicin/sigil';
|
|
722
|
+
|
|
723
|
+
// from:
|
|
724
|
+
const present = REGISTRY.has('label');
|
|
725
|
+
|
|
726
|
+
// to:
|
|
727
|
+
const present = getActiveRegistry()?.has('label'); // Active registy can be 'null' if 'SigilOptions.registy' is set to null so we used the '?' mark
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
```ts
|
|
731
|
+
import { REGISTRY, updateOptions, SigilRegistry } from '@vicin/sigil';
|
|
732
|
+
|
|
733
|
+
// from:
|
|
734
|
+
const newRegistry = new Map();
|
|
735
|
+
REGISTRY.replaceRegistry(newRegistry);
|
|
736
|
+
|
|
737
|
+
// to
|
|
738
|
+
const newRegistry = new SigilRegistry(); // can pass external map to constructor if needed.
|
|
739
|
+
updateOptions({ registry: newRegistry });
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
---
|
|
743
|
+
|
|
616
744
|
## Phantom
|
|
617
745
|
|
|
618
746
|
`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.
|
|
619
747
|
|
|
620
748
|
`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.
|
|
749
|
+
|
|
750
|
+
- **GitHub: [@phantom](https://github.com/ZiadTaha62/phantom)**
|
|
751
|
+
- **NPM: [@phantom](https://www.npmjs.com/package/@vicin/phantom)**
|
|
752
|
+
|
|
753
|
+
---
|
|
754
|
+
|
|
755
|
+
## Contributing
|
|
756
|
+
|
|
757
|
+
Any contributions you make are **greatly appreciated**.
|
|
758
|
+
|
|
759
|
+
Please see our [CONTRIBUTING.md](./CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
|
|
760
|
+
|
|
761
|
+
## License
|
|
762
|
+
|
|
763
|
+
Distributed under the MIT License. See `LICENSE` for more information.
|
|
764
|
+
|
|
765
|
+
---
|
|
766
|
+
|
|
767
|
+
## Author
|
|
768
|
+
|
|
769
|
+
Built with ❤️ by **Ziad Taha**.
|
|
770
|
+
|
|
771
|
+
- **GitHub: [@ZiadTaha62](https://github.com/ZiadTaha62)**
|
|
772
|
+
- **NPM: [@ziadtaha62](https://www.npmjs.com/~ziadtaha62)**
|
|
773
|
+
- **Vicin: [@vicin](https://www.npmjs.com/org/vicin)**
|