@vicin/sigil 1.0.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.
Files changed (51) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +620 -0
  3. package/dist/core/classes.d.ts +48 -0
  4. package/dist/core/classes.d.ts.map +1 -0
  5. package/dist/core/classes.js +18 -0
  6. package/dist/core/classes.js.map +1 -0
  7. package/dist/core/decorator.d.ts +28 -0
  8. package/dist/core/decorator.d.ts.map +1 -0
  9. package/dist/core/decorator.js +48 -0
  10. package/dist/core/decorator.js.map +1 -0
  11. package/dist/core/enhancers.d.ts +58 -0
  12. package/dist/core/enhancers.d.ts.map +1 -0
  13. package/dist/core/enhancers.js +101 -0
  14. package/dist/core/enhancers.js.map +1 -0
  15. package/dist/core/helpers.d.ts +192 -0
  16. package/dist/core/helpers.d.ts.map +1 -0
  17. package/dist/core/helpers.js +349 -0
  18. package/dist/core/helpers.js.map +1 -0
  19. package/dist/core/index.d.ts +9 -0
  20. package/dist/core/index.d.ts.map +1 -0
  21. package/dist/core/index.js +8 -0
  22. package/dist/core/index.js.map +1 -0
  23. package/dist/core/mixin.d.ts +115 -0
  24. package/dist/core/mixin.d.ts.map +1 -0
  25. package/dist/core/mixin.js +209 -0
  26. package/dist/core/mixin.js.map +1 -0
  27. package/dist/core/options.d.ts +74 -0
  28. package/dist/core/options.d.ts.map +1 -0
  29. package/dist/core/options.js +39 -0
  30. package/dist/core/options.js.map +1 -0
  31. package/dist/core/registry.d.ts +104 -0
  32. package/dist/core/registry.d.ts.map +1 -0
  33. package/dist/core/registry.js +174 -0
  34. package/dist/core/registry.js.map +1 -0
  35. package/dist/core/symbols.d.ts +96 -0
  36. package/dist/core/symbols.d.ts.map +1 -0
  37. package/dist/core/symbols.js +96 -0
  38. package/dist/core/symbols.js.map +1 -0
  39. package/dist/core/types.d.ts +169 -0
  40. package/dist/core/types.d.ts.map +1 -0
  41. package/dist/core/types.js +2 -0
  42. package/dist/core/types.js.map +1 -0
  43. package/dist/index.d.ts +3 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +3 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/utils/index.d.ts +2 -0
  48. package/dist/utils/index.d.ts.map +1 -0
  49. package/dist/utils/index.js +2 -0
  50. package/dist/utils/index.js.map +1 -0
  51. package/package.json +57 -0
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) GitHub, Inc. and contributors
2
+
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
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,620 @@
1
+ # Sigil
2
+
3
+ `Sigil` is a lightweight TypeScript library for creating **nominal identity classes** with compile-time branding and reliable runtime type checks. It organize 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
+
5
+ > **Key ideas:**
6
+ >
7
+ > - **Compile-time nominal typing** via type brands so two structurally-identical types can remain distinct.
8
+ > - **Reliable runtime guards** using `Symbol.for(...)` and lineage sets instead of `instanceof`.
9
+ > - **Inheritance-aware identity**: lineages and sets let you test for subtype/supertype relationships.
10
+ > - **Centralized class registry**: every class have it's own unique label and symbol that can be used as an id throughout the codebase.
11
+
12
+ **Note: You should read these parts before implementing `Sigil` in you code:**
13
+
14
+ - **Security note:** `Sigil` exposes a global registry of all sigilized classes, this means that all sigilized classes can be accessed anywhere in the code. so **avoid** siglizing security sensitive classes or any class that you want to make it unaccessable outside it's module. read more [Registery](#registry).
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 marginally 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
+
18
+ ---
19
+
20
+ ## Table of contents
21
+
22
+ - [Features](#features)
23
+ - [Quick start](#quick-start)
24
+ - [Install](#install)
25
+ - [Basic usage (mixin / base class)](#basic-usage-mixin--base-class)
26
+ - [Decorator style](#decorator-style)
27
+ - [HOF helpers (recommended)](#hof-helpers-recommended)
28
+ - [Typed helpers (compile-time branding)](#typed-helpers-compile-time-branding)
29
+ - [Migration](#migration)
30
+ - [Core concepts](#core-concepts)
31
+ - [Typing](#typing)
32
+ - [API reference](#api-reference)
33
+ - [Options & configuration](#options--configuration)
34
+ - [Registry](#registry)
35
+ - [Troubleshooting & FAQ](#troubleshooting--faq)
36
+ - [Best practices](#best-practices)
37
+ - [Phantom](#phantom)
38
+
39
+ ---
40
+
41
+ ## Features
42
+
43
+ - Attach **stable runtime identity** to classes using `Symbol.for(label)`.
44
+ - **Type-level branding** so distinct domain identities are enforced by TypeScript.
45
+ - **Lineage tracking** (arrays + sets of symbols) for O(1) and O(n) checks.
46
+ - Easy to use: decorator (`@WithSigil`), mixin (`Sigilify`), and HOF (Higher order function) helpers (`withSigil`, `withSigilTyped`, `typed`).
47
+ - **Global registry** to centralize classes (query any class by it's label in run-time) and guard against duplicate labels.
48
+ - Minimal runtime overhead in production (DEV checks can be toggled off).
49
+
50
+ ---
51
+
52
+ ## Quick start
53
+
54
+ ### Install
55
+
56
+ ```bash
57
+ npm install sigil
58
+ # or
59
+ yarn add sigil
60
+ # or
61
+ pnpm add sigil
62
+ ```
63
+
64
+ **Requirements**: TypeScript 5.0+ (for stage-3 decorators) and Node.js 18+ recommended.
65
+
66
+ ### Basic usage (mixin / base class)
67
+
68
+ Use the `Sigil` base class or the `Sigilify` mixin to opt a class into the Sigil runtime contract.
69
+
70
+ ```ts
71
+ import { Sigil, Sigilify } from 'sigil';
72
+
73
+ // Using the pre-sigilified base class:
74
+ class User extends Sigil {}
75
+
76
+ // Or use Sigilify when you want an ad-hoc class:
77
+ const MyClass = Sigilify(class {}, '@myorg/mypkg.MyClass');
78
+ ```
79
+
80
+ This adds runtime metadata to the constructor and allows you to use runtime helpers (see API).
81
+
82
+ ### Decorator style
83
+
84
+ Apply a label with the `@WithSigil` decorator. This is handy for small classes or when you prefer decorator syntax.
85
+
86
+ ```ts
87
+ import { Sigil, WithSigil } from 'sigil';
88
+
89
+ @WithSigil('@myorg/mypkg.User')
90
+ class User extends Sigil {}
91
+ ```
92
+
93
+ > Note: When extending an already sigilized class (for example `Sigil`), you must decorate the subclass or use the HOF helpers in DEV mode unless you configured the library otherwise.
94
+
95
+ ### HOF (Higher-Order Function) helpers - recommended -
96
+
97
+ HOFs work well in many build setups and are idempotent-safe for HMR flows.
98
+
99
+ ```ts
100
+ import { Sigil, withSigil } from 'sigil';
101
+
102
+ class _User extends Sigil {}
103
+ const User = withSigil(_User, '@myorg/mypkg.User');
104
+
105
+ const user = new User();
106
+ console.log(User.SigilLabel); // "@myorg/mypkg.User"
107
+ ```
108
+
109
+ ### Typed helpers (compile-time branding)
110
+
111
+ If you want TypeScript to treat identities nominally (so `UserId` !== `PostId` despite identical shape), use the typed helpers.
112
+
113
+ ```ts
114
+ import { Sigil, withSigilTyped, GetInstance } from 'sigil';
115
+
116
+ class _User extends Sigil {}
117
+ const User = withSigilTyped(_User, '@myorg/mypkg.User');
118
+ type User = GetInstance<typeof User>;
119
+
120
+ // Now `User` carries a compile-time brand that prevents accidental assignment
121
+ // to other labelled instances with the same structure.
122
+ ```
123
+
124
+ ### Migration
125
+
126
+ Migration old code into `Sigil` can be done seamlessly with this set-up:
127
+
128
+ 1. Set `SigilOptions.autofillLabels` to `true` at the start of the app so no errors are thrown in the migration stage:
129
+
130
+ ```ts
131
+ import { updateOptions } from 'sigil';
132
+ updateOptions({ autofillLabels: true });
133
+ ```
134
+
135
+ 2. Make you base classes extends `Sigil`:
136
+
137
+ ```ts
138
+ import { Sigil } from 'sigil';
139
+
140
+ class MyBaseClass {} // original
141
+
142
+ class MyBaseClass extends Sigil {} // <-- add 'extends Sigil' here
143
+ ```
144
+
145
+ Just like this, your entire classes are siglized and you can start using `.isOfType()` as a replacement of `instanceof` in cross bundle checks.
146
+ But there is more to add to your system, which will be discussed in the [Core concepts](#core-concepts).
147
+
148
+ ---
149
+
150
+ ## Core concepts
151
+
152
+ ### Terminology
153
+
154
+ - **Label**: A human-readable identity (string) such as `@scope/pkg.ClassName`.
155
+ - **SigilType (symbol)**: `Symbol.for(label)` — stable across realms that share the global registry.
156
+ - **Type lineage**: An array `[parentSymbol, childSymbol]` used for strict ancestry checks.
157
+ - **Type set**: A `Set<symbol>` built from the lineage for O(1) membership checks.
158
+ - **Brand**: A compile-time-only TypeScript marker carried on instances so the type system treats labelled classes nominally.
159
+ - **Registry**: A global Map of registered `Sigil` classes keyed by there labels.
160
+
161
+ ---
162
+
163
+ ### Whe `Sigil` exists
164
+
165
+ `Sigil` was born out of real-world friction in a large **monorepo** built with **Domain-Driven Design (DDD)**.
166
+
167
+ #### The monorepo (`instanceof`) problem
168
+
169
+ The first issue surfaced with `instanceof`.
170
+ In modern JavaScript setups—monorepos, multiple bundles, HMR, transpiled builds—the same class can be defined more than once at runtime. When that happens, `instanceof` becomes unreliable:
171
+
172
+ - Objects created in one bundle fail `instanceof` checks in another
173
+ - Hot reloads can silently break identity checks
174
+ - Runtime behavior diverges from what the type system suggests
175
+
176
+ This made instanceof unsuitable as a foundation for domain identity.
177
+
178
+ #### The DDD (`Manual branding`) problem
179
+
180
+ We started embedding custom identifiers directly into class to achieve nominal typing.
181
+ While this worked conceptually, it quickly became problematic:
182
+
183
+ - Every class needed boilerplate fields or symbols
184
+ - Type guards had to be hand-written and maintained
185
+ - Inheritance required extra care to preserve identity
186
+
187
+ The intent of the domain model was obscured by repetitive code, What started as a workaround became verbose, fragile, and hard to enforce consistently.
188
+
189
+ #### A better abstraction
190
+
191
+ Sigil is the result of abstracting that pattern into a **first-class identity system**:
192
+
193
+ - **Nominal identity at compile time**, without structural leakage
194
+ - **Reliable runtime type checks**, without instanceof
195
+ - **Inheritance-aware identity**, with lineage tracking
196
+ - **Minimal runtime overhead**, with DEV-only safeguards
197
+
198
+ Instead of embedding identity logic inside every class, Sigil centralizes it, enforces it, and makes it explicit.
199
+
200
+ The goal is simple:
201
+
202
+ - **Make domain identity correct by default, and hard to get wrong.**
203
+
204
+ ---
205
+
206
+ ### How Sigil solves the problems
207
+
208
+ #### Problem A — `instanceof` is unreliable
209
+
210
+ **Sigil’s solution (runtime chain).**
211
+ To make runtime identity reliable across bundles, HMR, and transpiled code, `Sigil` explicitly attaches identity metadata and tracks inheritance lineage on classes. That tracking starts with a contract created by `Sigilify()` (a mixin / class factory) or by extending the `Sigil` base class. Sigilify augments a plain JS class with the metadata and helper methods Sigil needs (for example, `isOfType`).
212
+
213
+ Basic patterns:
214
+
215
+ Mixin / factory:
216
+
217
+ ```ts
218
+ import { Sigilify } from 'sigil';
219
+
220
+ const MyClass = Sigilify(class {}, 'MyClass');
221
+ ```
222
+
223
+ Direct base-class extend:
224
+
225
+ ```ts
226
+ import { Sigil, WithSigil } from 'sigil';
227
+
228
+ class MyClass extends Sigil {}
229
+ ```
230
+
231
+ **Enforced contract.**
232
+ Once you opt into the runtime contract, Sigil enforces consistency: in DEV mode, extending a sigil-aware class without using decorator `@WithSigil` or a provided HOF (e.g. `withSigil` or `withSigilTyped`) will throw a helpful error. If you prefer a laxer setup, Sigil can be configured to auto-label or disable the strict enforcement.
233
+
234
+ Decorator style:
235
+
236
+ ```ts
237
+ import { Sigil, WithSigil } from 'sigil';
238
+
239
+ @WithSigil('MyClass') // <-- Note `@WithSigil` used here cause it extended alreay sigilized class (Sigil). Error is thrown without it.
240
+ class MyClass extends Sigil {}
241
+ ```
242
+
243
+ HOF (preferred for many workflows):
244
+
245
+ ```ts
246
+ import { Sigil, withSigil } from 'sigil';
247
+
248
+ class _MyClass extends Sigil {}
249
+ const MyClass = withSigil(_MyClass, 'MyClass');
250
+ ```
251
+
252
+ Recommendation:
253
+
254
+ - Use the decorator approach for a minimal, runtime-only fix for `instanceof`.
255
+ - Use the HOFs when you want better ergonomics or plan to opt into typed branding later.
256
+
257
+ #### Problem B — manual branding
258
+
259
+ **Sigil’s solution (typed chain).**
260
+ Runtime metadata alone does not change TypeScript types. To get compile-time nominal typing (so `UserId` ≠ `PostId` even with the same shape), Sigil provides typed HOFs that produce TypeScript-branded classes while preserving runtime metadata.
261
+
262
+ Example using a typed HOF:
263
+
264
+ ```ts
265
+ import { Sigil, withSigilTyped, GetInstance } from 'sigil';
266
+
267
+ // Untyped (runtime) base you extend as normal TS class code:
268
+ class _User extends Sigil {}
269
+
270
+ // Create a fully typed & runtime-safe class:
271
+ const User = withSigilTyped(_User, 'User');
272
+ type User = GetInstance<typeof User>;
273
+ ```
274
+
275
+ With the typed HOF:
276
+
277
+ - The emitted runtime class still has sigil metadata (symbols, lineage).
278
+ - The TypeScript type for the class is narrowed to the `UserId` label at compile time. Assignments between different labels become type errors.
279
+
280
+ ---
281
+
282
+ #### Why there are '\_Class' and 'Class'
283
+
284
+ The typed approach requires redefinition of public class, so you have:
285
+
286
+ - **Untyped class:** `_User` — regular class code used for inheritance and implementation.
287
+ - **Typed class:** `User` — the result of the typed HOF; this is the sigil-aware, branded class used by the rest of your codebase.
288
+
289
+ 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
+
291
+ Example of appraoch for class chain:
292
+
293
+ ```ts
294
+ import { Sigil, withSigilTyped, GetInstance } from 'sigil';
295
+
296
+ // Untyped base classes used for implementation:
297
+ class _User extends Sigil {}
298
+
299
+ const User = withSigilTyped(_User, 'User');
300
+ type User = GetInstance<typeof User>;
301
+
302
+ class _Admin extends User {}
303
+
304
+ const Admin = withSigilTyped(_Admin, 'Admin');
305
+ type Admin = GetInstance<typeof Admin>;
306
+
307
+ // Type relationships:
308
+ type test1 = User extends Admin ? true : false; // false
309
+ type test2 = Admin extends User ? true : false; // true
310
+ ```
311
+
312
+ This demonstrates:
313
+
314
+ - `Admin` is recognized as a subtype of `User` (both at runtime and in types) if it was created via the appropriate typed helpers.
315
+
316
+ ---
317
+
318
+ #### SigilLabel & SigilType
319
+
320
+ If library is used with default options, `SigilLabel` & `SigilType` are **100% unique** for each class, which make them perfect replacement of manual labeling across your code that comes shipped with Sigil by default. you can access them in class constructor directly of via `getSigilLabel()` and `getSigilType()` in instances.
321
+
322
+ ---
323
+
324
+ ## Typing
325
+
326
+ In this part we will discuss conventions to avoid any type errors and have normal developing experience with just extra few definition lines at the bottom of the file.
327
+
328
+ ---
329
+
330
+ ### Typed vs Untyped classes
331
+
332
+ The update of Sigil brand types happens via HOF that are defined below actual class definition.
333
+
334
+ Example:
335
+
336
+ ```ts
337
+ import { Sigil, withSigilTyped } from 'sigil';
338
+
339
+ class _X extends Sigil {
340
+ // All logic for class
341
+ }
342
+
343
+ export const X = withSigilTyped(_X, 'Label.X'); // <-- Pass class with label to uniquely identify it from other classes
344
+ export type X = InstanceType<typeof X>; // alias to instance to avoid InstanceType<typeof X> everywhere
345
+ ```
346
+
347
+ In other parts of the code:
348
+
349
+ ```ts
350
+ import { X } from './example.ts';
351
+
352
+ class _Y extends X {
353
+ // All logic as earlier
354
+ }
355
+
356
+ export const Y = withSigilTyped(_Y, 'Label.Y');
357
+ export type Y = InstanceType<typeof Y>;
358
+ ```
359
+
360
+ So as we have seen nominal identity is introduced with few lines only below each class. and the bulk code where logic lives is untouched.
361
+
362
+ ---
363
+
364
+ ### `InstanceType<>` vs `GetInstance<>`
365
+
366
+ Earlier example used `InstanceType<>` to get instance of the class. It works well until class constructor is `protected` or `private` which cause it to return `any`.
367
+ So alternative in introduced which is `GetInstance`.
368
+
369
+ ```ts
370
+ import { Sigil, withSigilTyped, GetInstance } from 'sigil';
371
+
372
+ class _X extends Sigil {}
373
+
374
+ export const X = withSigilTyped(_X, 'Label.X');
375
+ export type X = GetInstance<typeof X>; // <-- Just replace 'InstanceType' here with 'GetInstance'
376
+ ```
377
+
378
+ Internally `GetInstance` is just `T extends { prototype: infer R }` with appending new `__SIGIL_BRAND__` to it.
379
+
380
+ ---
381
+
382
+ ### Generic propagation
383
+
384
+ One of the downsides of defining typed class at the bottom is that we need to redefine generics as well in the type.
385
+
386
+ Example of generic propagation:
387
+
388
+ ```ts
389
+ import { Sigil, withSigilTyped, GetInstance } from 'sigil';
390
+
391
+ // Untyped base classes used for implementation:
392
+ class _X<G> extends Sigil {}
393
+
394
+ export const X = withSigilTyped(_X, 'Label.X');
395
+ export type X<G> = GetInstance<typeof X<G>>; // <-- Generics re-defined here, just copy-paste and pass them
396
+ ```
397
+
398
+ ---
399
+
400
+ ### Anonymous classes
401
+
402
+ 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 entirly all you need is exporting the untyped classes even if they are un-used as a good convention.
404
+
405
+ ```ts
406
+ import { Sigil, withSigilTyped, GetInstance } from 'sigil';
407
+
408
+ export class _X extends Sigil {} // <-- Just add 'export' here
409
+
410
+ export const X = withSigilTyped(_X, 'Label.X');
411
+ export type X = GetInstance<typeof X>;
412
+ ```
413
+
414
+ ---
415
+
416
+ ## API reference
417
+
418
+ > The runtime API is intentionally small and focused. Types are exported for consumers that want to interact with compile-time helpers.
419
+
420
+ ### Exports
421
+
422
+ Top-level exports from the library:
423
+
424
+ ```ts
425
+ export { Sigil, SigilError } from './classes';
426
+ export { WithSigil } from './decorator';
427
+ export { typed, withSigil, withSigilTyped } from './enhancers';
428
+ export {
429
+ isDecorated,
430
+ isInheritanceChecked,
431
+ isSigilBaseCtor,
432
+ isSigilBaseInstance,
433
+ isSigilCtor,
434
+ isSigilInstance,
435
+ } from './helpers';
436
+ export { Sigilify } from './mixin';
437
+ export { updateOptions, type SigilOptions } from './options';
438
+ export { REGISTRY } from './registry';
439
+ export type {
440
+ ISigil,
441
+ ISigilInstance,
442
+ ISigilStatic,
443
+ TypedSigil,
444
+ GetInstance,
445
+ SigilBrandOf,
446
+ } from './types';
447
+ ```
448
+
449
+ ### Key helpers (runtime)
450
+
451
+ - `Sigil`: a minimal sigilified base class you can extend from.
452
+ - `SigilError`: an `Error` class decorated with a sigil so it can be identified at runtime.
453
+ - `WithSigil(label)`: class decorator that attaches sigil metadata at declaration time.
454
+ - `Sigilify(Base, label?, opts?)`: mixin function that returns a new constructor with sigil types and instance helpers.
455
+ - `withSigil(Class, label?, opts?)`: HOF that validates and decorates an existing class constructor.
456
+ - `withSigilTyped(Class, label?, opts?)`: like `withSigil` but narrows the TypeScript type to include brands.
457
+ - `typed(Class, label?, parent?)`: type-only narrowing helper (no runtime mutation) — asserts runtime label in DEV.
458
+ - `isSigilCtor(value)`: `true` if `value` is a sigil constructor.
459
+ - `isSigilInstance(value)`: `true` if `value` is an instance of a sigil constructor.
460
+ - `REGISTRY`: singleton wrapper around global `Sigil` registry.
461
+ - `updateOptions(opts)`: change global runtime options before sigil decoration (e.g., `autofillLabels`, `devMarker`, etc.).
462
+ - `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
+
464
+ ### Instance & static helpers provided by Sigilified constructors
465
+
466
+ When a constructor is decorated/sigilified it will expose the following **static** getters/methods:
467
+
468
+ - `SigilLabel` — the human label string.
469
+ - `SigilType` — the runtime symbol for the label.
470
+ - `SigilTypeLineage` — readonly array of symbols representing parent → child.
471
+ - `SigilTypeSet` — readonly `Set<symbol>` for O(1) checks.
472
+ - `isSigilified(obj)` — runtime predicate that delegates to `isSigilInstance`.
473
+ - `isOfType(other)` — O(1) membership test using `other`'s `__TYPE_SET__`.
474
+ - `isOfTypeStrict(other)` — strict lineage comparison element-by-element.
475
+
476
+ Instances of sigilified classes expose instance helpers:
477
+
478
+ - `getSigilLabel()` — returns the human label.
479
+ - `getSigilType()` — runtime symbol.
480
+ - `getSigilTypeLineage()` — returns lineage array.
481
+ - `getSigilTypeSet()` — returns readonly Set.
482
+
483
+ ---
484
+
485
+ ## Options & configuration
486
+
487
+ Sigil exposes a small set of runtime options that control DEV behavior. These can be modified at app startup via `updateOptions(...)`.
488
+
489
+ ```ts
490
+ import { updateOptions } from 'sigil';
491
+
492
+ updateOptions({
493
+ autofillLabels: false, // auto-generate labels for subclasses that would otherwise inherit
494
+ skipLabelInheritanceCheck: false, // skip DEV-only inheritance checks -- ALMOST NEVER WANT TO SET THIS TO TRUE, Use 'autofillLabels: true' instead. --
495
+ labelValidation: null, // or a RegExp / function to validate labels
496
+ devMarker: process.env.NODE_ENV !== 'production', // boolean used to block dev only checks in non-dev enviroments
497
+ });
498
+ ```
499
+
500
+ **Notes**:
501
+
502
+ - It's advised to use `updateOptions({ labelValidation: DEFAULT_LABEL_REGEX })` at app entry to validate labels against `@scope/package.ClassName` structure.
503
+ - `devMarker` drives DEV-only checks — when `false`, many runtime validations are no-ops (useful for production builds).
504
+ - `autofillLabels` is useful for some HMR/test setups where you prefer not to throw on collisions and want autogenerated labels.
505
+ - `skipLabelInheritanceCheck = true` can result on subtle bugs if enabled, so avoid setting it to true.
506
+
507
+ ---
508
+
509
+ ## Registry
510
+
511
+ `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
+
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 `REGISTRY` class instance.
514
+
515
+ Registry is stored in `globalThis` under `Symbol.for(__SIGIL_REGISTRY__)` so there is single source of truth across the runtime, but this also exposes that map anywhere in the code.
516
+
517
+ **Note:**
518
+
519
+ - If you intentionally want to disable registry checks (for certain test environments), call `REGISTRY.replaceRegistry(null)` to opt out. although you should not do this in most cases.
520
+
521
+ ### Registry map
522
+
523
+ `Sigil` registry uses `Map<string, ISigil>` where `string` is `SigilLabel` and `ISigil` is reference to class constructor. If you want to pass your own external `Map` -**although this is not advised except if you have a strong reason**- you can use `REGISTRY.replaceRegistry` and `Sigil` will:
524
+
525
+ - 1. Transfere all old classes to the new passed map.
526
+ - 2. Attach this new map to `globalThis`.
527
+ - 3. Store every new class defined in the passed map.
528
+
529
+ ### Class typing
530
+
531
+ 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
+
533
+ ```ts
534
+ import { REGISTRY } from 'sigil';
535
+ import { MySigilClass1 } from './file1';
536
+ import { MySigilClass2 } from './file2';
537
+
538
+ interface MyClasses {
539
+ MySigilClass1: typeof MySigilClass1;
540
+ MySigilClass2: typeof MySigilClass2;
541
+ }
542
+
543
+ class MySigilRegistry {
544
+ listLabels(): (keyof MyClasses)[] {
545
+ return REGISTRY.listLabels();
546
+ }
547
+ has(label: string): boolean {
548
+ return REGISTRY.has(label);
549
+ }
550
+ get<L extends keyof MyClasses>(label: L): MyClasses[L] {
551
+ return REGISTRY.get(label) as any;
552
+ }
553
+ unregister(label: string): boolean {
554
+ return REGISTRY.unregister();
555
+ }
556
+ clear(): void {
557
+ REGISTRY.clear();
558
+ }
559
+ replaceRegistry(newRegistry: Map<string, ISigil> | null): void {
560
+ REGISTRY.replaceRegistry(newRegistry);
561
+ }
562
+ get size(): number {
563
+ return REGISTRY.size;
564
+ }
565
+ }
566
+
567
+ export const MY_SIGIL_REGISTRY = new MySigilRegistry();
568
+ ```
569
+
570
+ Now you have fully typed central class registry!
571
+
572
+ ### Hot module relodes
573
+
574
+ - Sigil keeps a global registry (backed by `Symbol.for("@Sigil.__SIGIL_REGISTRY__")` on `globalThis`) so label uniqueness checks survive module reloads.
575
+ - In development with HMR, duplicate class definitions are common; Sigil prints friendly warnings in DEV rather than throwing to keep the feedback loop fast.
576
+
577
+ ---
578
+
579
+ ## Troubleshooting & FAQ
580
+
581
+ **Q: My `instanceof` checks fail across bundles — will Sigil fix this?**
582
+
583
+ 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.
584
+
585
+ **Q: I accidentally extended a sigilized class without decorating the subclass; I see an error in DEV. How should I fix it?**
586
+
587
+ 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.
588
+
589
+ **Q: I got this error: 'Property 'x' of exported anonymous class type may not be private or protected.', How to fix it?**
590
+
591
+ A: This error come's 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 it 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
+
593
+ **Q: I need nominal types in TypeScript. Which helper do I use?**
594
+
595
+ A: Use `withSigilTyped` to both attach runtime metadata and apply compile-time brands. If runtime metadata already exists and you only want to narrow types, use `typed(...)` (which is type-only but asserts the runtime label in DEV).
596
+
597
+ **Q: How do I inspect currently registered labels?**
598
+
599
+ A: Use `REGISTRY.list()` to get an array of registered labels.
600
+
601
+ **Q: What if i want to omit labeling in some classes while inforce others?**
602
+
603
+ 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
+
605
+ ---
606
+
607
+ ## Best practices
608
+
609
+ - Prefer `withSigil`/`withSigilTyped` for predictable, explicit decoration.
610
+ - Keep labels globally unique and descriptive (including scope and package like `@myorg/mypkg.ClassName`).
611
+ - Use typed helpers for domain-level identities (IDs, tokens, domain types) so the compiler helps you avoid mistakes.
612
+ - Run with `devMarker` enabled during local development / CI to catch label collisions early.
613
+
614
+ ---
615
+
616
+ ## Phantom
617
+
618
+ `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
+
620
+ `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.
@@ -0,0 +1,48 @@
1
+ /**
2
+ * A minimal root Sigil class used by the library as a base identity.
3
+ *
4
+ * This is produced by `Sigilify` and can serve as a basic sentinel/base
5
+ * class for other sigil classes or for debugging/inspection.
6
+ */
7
+ export declare const Sigil: {
8
+ new (...args: any[]): {
9
+ readonly __SIGIL_BRAND__: Record<string, true>;
10
+ getSigilLabel(): string;
11
+ getSigilType(): symbol;
12
+ getSigilTypeLineage(): readonly symbol[];
13
+ getSigilTypeSet(): Readonly<Set<symbol>>;
14
+ };
15
+ readonly __SIGIL_BRAND__: Record<string, true>;
16
+ get SigilLabel(): string;
17
+ get SigilType(): symbol;
18
+ get SigilTypeLineage(): readonly symbol[];
19
+ get SigilTypeSet(): Readonly<Set<symbol>>;
20
+ isSigilified(obj: unknown): obj is import("./types").ISigil;
21
+ isOfType<T extends import("./types").ISigil>(this: T, other: unknown): other is InstanceType<T>;
22
+ isOfTypeStrict<T extends import("./types").ISigil>(this: T, other: unknown): other is InstanceType<T>;
23
+ };
24
+ /**
25
+ * A sigil variant of the built-in `Error` constructor used by the library
26
+ * to represent Sigil-specific errors.
27
+ *
28
+ * Use `SigilError` when you want an Error type that is identifiable via sigil
29
+ * runtime checks (e.g. `SigilError.isOfType(someError)`).
30
+ */
31
+ export declare const SigilError: {
32
+ new (...args: any[]): {
33
+ readonly __SIGIL_BRAND__: Record<string, true>;
34
+ getSigilLabel(): string;
35
+ getSigilType(): symbol;
36
+ getSigilTypeLineage(): readonly symbol[];
37
+ getSigilTypeSet(): Readonly<Set<symbol>>;
38
+ };
39
+ readonly __SIGIL_BRAND__: Record<string, true>;
40
+ get SigilLabel(): string;
41
+ get SigilType(): symbol;
42
+ get SigilTypeLineage(): readonly symbol[];
43
+ get SigilTypeSet(): Readonly<Set<symbol>>;
44
+ isSigilified(obj: unknown): obj is import("./types").ISigil;
45
+ isOfType<T extends import("./types").ISigil>(this: T, other: unknown): other is InstanceType<T>;
46
+ isOfTypeStrict<T extends import("./types").ISigil>(this: T, other: unknown): other is InstanceType<T>;
47
+ };
48
+ //# sourceMappingURL=classes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classes.d.ts","sourceRoot":"","sources":["../../src/core/classes.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;CAA8B,CAAC;AAEjD;;;;;;GAMG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;CAAgC,CAAC"}