@vicin/sigil 1.1.1 → 1.2.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/README.md CHANGED
@@ -1,6 +1,6 @@
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 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.
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 its own unique label and symbol.
4
4
 
5
5
  > **Key ideas:**
6
6
  >
@@ -9,11 +9,21 @@
9
9
  > - **Inheritance-aware identity**: lineages and sets let you test for subtype/supertype relationships.
10
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
- **Note: You should read these parts before implementing `Sigil` in you code:**
12
+ **Note: You should read these parts before implementing `Sigil` in your code:**
13
13
 
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).
14
+ - **Security note:** By default, `Sigil` stores constructor references in a global registry. While this does not expose private instance data, it allows any module to retrieve a class constructor via its label. If you have sensitive classes that should not be accessible globally, update your options:
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 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.
16
+ ```ts
17
+ @WithSigil("label", { storeConstructor: false })
18
+ ```
19
+
20
+ See the [Registry](#registry) section for more details.
21
+
22
+ - **Performance & Hot-Paths note:** `Sigil` attaches minimal metadata to instances, which is negligible for 99% of use cases. While `.isOfType()` is optimized, it is inherently slower than the native `instanceof` operator. For extreme hot-path code where every microsecond counts, stick to native checks—but keep in mind you'll lose Sigil's cross-bundle reliability.
23
+
24
+ - **HOF & Private Constructors note:** Due to a known TypeScript limitation with class expressions, using HOF helpers (like `withSigilTyped`) on classes with private constructors still allow those classes to be extended in the type system. If strict constructor encapsulation is a priority, please review the [Private Constructors](#private-constructors) guide.
25
+
26
+ - **Simplified instanceof Replacement:** `Sigil` relies on unique labels to identify classes. If your primary goal is simply to fix `instanceof` across bundles without the overhead of nominal typing or a registry, you can jump straight to our [Minimal Setup Guide](#i-dont-care-about-nominal-types-or-central-registry-i-just-want-a-runtime-replacement-of-instanceof).
17
27
 
18
28
  ---
19
29
 
@@ -24,16 +34,17 @@
24
34
  - [Install](#install)
25
35
  - [Basic usage (mixin / base class)](#basic-usage-mixin--base-class)
26
36
  - [Decorator style](#decorator-style)
27
- - [HOF helpers (recommended)](#hof-helpers-recommended)
28
- - [Typed helpers (compile-time branding)](#typed-helpers-compile-time-branding)
37
+ - [HOF helpers](#hof-higher-order-function-helpers)
38
+ - [Minimal “first-run” example](#minimal-first-run-example)
29
39
  - [Migration](#migration)
40
+ - [Limitations & guarantees](#limitations--guarantees)
30
41
  - [Core concepts](#core-concepts)
31
- - [Typing](#typing)
42
+ - [Nominal typing](#nominal-typing)
32
43
  - [API reference](#api-reference)
33
44
  - [Options & configuration](#options--configuration)
34
45
  - [Registry](#registry)
46
+ - [Security guidance](#security-guidance)
35
47
  - [Troubleshooting & FAQ](#troubleshooting--faq)
36
- - [Best practices](#best-practices)
37
48
  - [Deprecated API](#deprecated-api)
38
49
  - [Phantom](#phantom)
39
50
  - [Contributing](#contributing)
@@ -52,7 +63,7 @@
52
63
 
53
64
  - Easy to use: decorator (`@WithSigil`), mixin (`Sigilify`), and HOF (Higher order function) helpers (`withSigil`, `withSigilTyped`, `typed`).
54
65
 
55
- - **Global registry** to centralize classes (query any class by it's label in run-time) and guard against duplicate labels.
66
+ - **Global registry** to centralize classes (query any class by its label in run-time) and guard against duplicate labels.
56
67
 
57
68
  - Minimal runtime overhead in production (DEV checks can be toggled off).
58
69
 
@@ -70,7 +81,7 @@ yarn add @vicin/sigil
70
81
  pnpm add @vicin/sigil
71
82
  ```
72
83
 
73
- **Requirements**: TypeScript 5.0+ (for stage-3 decorators) and Node.js 18+ recommended.
84
+ **Requirements**: TypeScript 5.0+ (for stage-3 decorators) and Node.js 18+ recommended. however HOF can be used in older TypeScript versions.
74
85
 
75
86
  ### Basic usage (mixin / base class)
76
87
 
@@ -101,7 +112,7 @@ class User extends Sigil {}
101
112
 
102
113
  > 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.
103
114
 
104
- ### HOF (Higher-Order Function) helpers - recommended -
115
+ ### HOF (Higher-Order Function) helpers
105
116
 
106
117
  HOFs work well in many build setups and are idempotent-safe for HMR flows.
107
118
 
@@ -115,24 +126,27 @@ const user = new User();
115
126
  console.log(User.SigilLabel); // "@myorg/mypkg.User"
116
127
  ```
117
128
 
118
- ### Typed helpers (compile-time branding)
119
-
120
- If you want TypeScript to treat identities nominally (so `UserId` !== `PostId` despite identical shape), use the typed helpers.
129
+ ### Minimal “first-run” example
121
130
 
122
131
  ```ts
123
- import { Sigil, withSigilTyped, GetInstance } from '@vicin/sigil';
132
+ import { Sigil, withSigil } from '@vicin/sigil';
124
133
 
125
- class _User extends Sigil {}
126
- const User = withSigilTyped(_User, '@myorg/mypkg.User');
127
- type User = GetInstance<typeof User>;
134
+ class _User extends Sigil {
135
+ constructor(public name: string) {
136
+ super();
137
+ }
138
+ }
139
+ export const User = withSigil(_User, '@myorg/mypkg.User');
128
140
 
129
- // Now `User` carries a compile-time brand that prevents accidental assignment
130
- // to other labelled instances with the same structure.
141
+ const u = new User('alice');
142
+
143
+ console.log(User.SigilLabel); // "@myorg/mypkg.User"
144
+ console.log(User.isOfType(u)); // true
131
145
  ```
132
146
 
133
147
  ### Migration
134
148
 
135
- Migration old code into `Sigil` can be done seamlessly with this set-up:
149
+ Migrating old code into `Sigil` can be done seamlessly with this set-up:
136
150
 
137
151
  1. Set `SigilOptions.autofillLabels` to `true` at the start of the app so no errors are thrown in the migration stage:
138
152
 
@@ -141,7 +155,7 @@ import { updateOptions } from '@vicin/sigil';
141
155
  updateOptions({ autofillLabels: true });
142
156
  ```
143
157
 
144
- 2. Make you base classes extends `Sigil`:
158
+ 2. Make your base classes extends `Sigil`:
145
159
 
146
160
  ```ts
147
161
  import { Sigil } from '@vicin/sigil';
@@ -151,11 +165,51 @@ class MyBaseClass {} // original
151
165
  class MyBaseClass extends Sigil {} // <-- add 'extends Sigil' here
152
166
  ```
153
167
 
154
- Just like this, your entire classes are siglized and you can start using `.isOfType()` as a replacement of `instanceof` in cross bundle checks.
168
+ Just like this, your entire classes are sigilized and you can start using `.isOfType()` as a replacement of `instanceof` in cross bundle checks.
155
169
  But there is more to add to your system, which will be discussed in the [Core concepts](#core-concepts).
156
170
 
157
171
  ---
158
172
 
173
+ ## Limitations & guarantees
174
+
175
+ This section states clearly what `Sigil` provides and what it does **not** provide.
176
+
177
+ ### What Sigil guarantees
178
+
179
+ **1. Stable label → symbol mapping within the same JS global symbol registry.**
180
+
181
+ - If two bundles share the same **global symbol registry** (the thing `Symbol.for(...)` uses), then `Symbol.for(label)` is identical across them — enabling cross-bundle identity checks when both sides use the same label string.
182
+
183
+ **2. Reliable runtime identity (when used as intended).**
184
+
185
+ - When classes are sigilified and their labels are used consistently, `.isOfType()` and the `SigilTypeSet` checks produce stable results across bundles/Hot Module Replacement flows that share the same runtime/global symbol registry.
186
+
187
+ **3. Optional central registry for discovery & serialization helpers.**
188
+
189
+ - If enabled, the registry centralizes labels (and optionally constructor references), which can be used for label-based serialization or runtime lookups within a trusted runtime.
190
+
191
+ **4. Nominal typing that is inheritance-aware**
192
+
193
+ - With couple extra lines of code you can have nominally typed classes.
194
+
195
+ ### What Sigil does not guarantee
196
+
197
+ **2. It is not for across isolated JS realms.**
198
+
199
+ Examples of isolated realms where Sigil may not work as expected:
200
+
201
+ - iframe with a different global context that does not share the same window (and therefore a different symbol registry).
202
+ - Workers or processes that do not share the same `globalThis` / symbol registry.
203
+ - Cross-origin frames where symbols are not shared.
204
+
205
+ In such cases you must provide a bridging/serialization protocol that maps labels to constructors on each side. however `Sigil` if used as intended makes serialization protocol much easier as each class will have a unique label.
206
+
207
+ **3. Not a security or access-control mechanism.**
208
+
209
+ Presence of a constructor or label in the registry is discoverable (unless you purposely set `storeConstructor: false`). Do **not** use `Sigil` as an authorization or secrets mechanism.
210
+
211
+ ---
212
+
159
213
  ## Core concepts
160
214
 
161
215
  ### Terminology
@@ -216,12 +270,11 @@ The goal is simple:
216
270
 
217
271
  #### Problem A — `instanceof` is unreliable
218
272
 
219
- **Sigil’s solution (runtime chain).**
220
- 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`).
273
+ 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) or by extending the `Sigil` base class. Sigilify augments a plain JS class with the metadata and helper methods Sigil needs (for example, `isOfType`).
221
274
 
222
275
  Basic patterns:
223
276
 
224
- Mixin / factory:
277
+ **Mixin:**
225
278
 
226
279
  ```ts
227
280
  import { Sigilify } from '@vicin/sigil';
@@ -229,27 +282,27 @@ import { Sigilify } from '@vicin/sigil';
229
282
  const MyClass = Sigilify(class {}, 'MyClass');
230
283
  ```
231
284
 
232
- Direct base-class extend:
285
+ **Direct base-class extend:**
233
286
 
234
287
  ```ts
235
288
  import { Sigil, WithSigil } from '@vicin/sigil';
236
289
 
290
+ @WithSigil('MyClass')
237
291
  class MyClass extends Sigil {}
238
292
  ```
239
293
 
240
- **Enforced contract.**
241
- 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.
294
+ 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.
242
295
 
243
- Decorator style:
296
+ **Decorator:**
244
297
 
245
298
  ```ts
246
299
  import { Sigil, WithSigil } from '@vicin/sigil';
247
300
 
248
- @WithSigil('MyClass') // <-- Note `@WithSigil` used here cause it extended alreay sigilized class (Sigil). Error is thrown without it.
301
+ @WithSigil('MyClass')
249
302
  class MyClass extends Sigil {}
250
303
  ```
251
304
 
252
- HOF (preferred for many workflows):
305
+ **HOF:**
253
306
 
254
307
  ```ts
255
308
  import { Sigil, withSigil } from '@vicin/sigil';
@@ -258,17 +311,22 @@ class _MyClass extends Sigil {}
258
311
  const MyClass = withSigil(_MyClass, 'MyClass');
259
312
  ```
260
313
 
261
- Recommendation:
314
+ #### Problem B — Branding can get messy
262
315
 
263
- - Use the decorator approach for a minimal, runtime-only fix for `instanceof`.
264
- - Use the HOFs when you want better ergonomics or plan to opt into typed branding later.
316
+ Runtime metadata alone does not change TypeScript types. To get compile-time nominal typing (so `UserId` `PostId` even with the same shape), Sigil provides two patterns:
265
317
 
266
- #### Problem B — manual branding
318
+ **Decorator with brand field:**
267
319
 
268
- **Sigil’s solution (typed chain).**
269
- 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.
320
+ ```ts
321
+ import { Sigil, WithSigil, UpdateSigilBrand } from '@vicin/sigil';
270
322
 
271
- Example using a typed HOF:
323
+ @WithSigil('User')
324
+ class User extends Sigil {
325
+ declare __SIGIL_BRAND__: UpdateSigilBrand<'User', Sigil>; // <-- inject type
326
+ }
327
+ ```
328
+
329
+ **HOF with `_Class` / `Class`:**
272
330
 
273
331
  ```ts
274
332
  import { Sigil, withSigilTyped, GetInstance } from '@vicin/sigil';
@@ -281,76 +339,47 @@ const User = withSigilTyped(_User, 'User');
281
339
  type User = GetInstance<typeof User>;
282
340
  ```
283
341
 
284
- With the typed HOF:
285
-
286
- - The emitted runtime class still has sigil metadata (symbols, lineage).
287
- - The TypeScript type for the class is narrowed to the `UserId` label at compile time. Assignments between different labels become type errors.
288
-
289
- ---
290
-
291
- #### Why there are '\_Class' and 'Class'
292
-
293
- The typed approach requires redefinition of public class, so you have:
294
-
295
- - **Untyped class:** `_User` — regular class code used for inheritance and implementation.
296
- - **Typed class:** `User` — the result of the typed HOF; this is the sigil-aware, branded class used by the rest of your codebase.
297
-
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.
299
-
300
- Example of approach for class chain:
342
+ Typings are lineage aware as well:
301
343
 
302
344
  ```ts
303
345
  import { Sigil, withSigilTyped, GetInstance } from '@vicin/sigil';
304
346
 
305
- // Untyped base classes used for implementation:
306
- class _User extends Sigil {}
347
+ class _A extends Sigil {}
348
+ const A = withSigilTyped(_A, 'A');
349
+ type A = GetInstance<typeof A>;
307
350
 
308
- const User = withSigilTyped(_User, 'User');
309
- type User = GetInstance<typeof User>;
310
-
311
- class _Admin extends User {}
351
+ class _B extends A {}
352
+ const B = withSigilTyped(_B, 'B');
353
+ type B = GetInstance<typeof B>;
312
354
 
313
- const Admin = withSigilTyped(_Admin, 'Admin');
314
- type Admin = GetInstance<typeof Admin>;
315
-
316
- // Type relationships:
317
- type test1 = User extends Admin ? true : false; // false
318
- type test2 = Admin extends User ? true : false; // true
355
+ type test1 = A extends B ? true : false; // false
356
+ type test2 = B extends A ? true : false; // true
319
357
  ```
320
358
 
321
- This demonstrates:
322
-
323
- - `Admin` is recognized as a subtype of `User` (both at runtime and in types) if it was created via the appropriate typed helpers.
324
-
325
- ---
326
-
327
359
  #### SigilLabel & SigilType
328
360
 
329
361
  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.
330
362
 
331
363
  ---
332
364
 
333
- ## Typing
365
+ ## Nominal typing
334
366
 
335
367
  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.
368
+ First we have two patterns, **HOF with `_Class` / `Class`** and **Decorators with brand field**:
336
369
 
337
- ---
338
-
339
- ### Typed vs Untyped classes
340
-
341
- The update of Sigil brand types happens via HOF that are defined below actual class definition.
370
+ ### HOF with `_Class` / `Class`
342
371
 
343
- Example:
372
+ The update of `Sigil` brand types happens via HOF that are defined below actual class definition:
344
373
 
345
374
  ```ts
346
- import { Sigil, withSigilTyped } from '@vicin/sigil';
375
+ import { Sigil, withSigilTyped, GetInstance } from '@vicin/sigil';
347
376
 
348
377
  class _X extends Sigil {
349
378
  // All logic for class
350
379
  }
351
380
 
352
381
  export const X = withSigilTyped(_X, 'Label.X'); // <-- Pass class with label to uniquely identify it from other classes
353
- export type X = InstanceType<typeof X>; // alias to instance to avoid InstanceType<typeof X> everywhere
382
+ export type X = GetInstance<typeof X>;
354
383
  ```
355
384
 
356
385
  In other parts of the code:
@@ -363,17 +392,14 @@ class _Y extends X {
363
392
  }
364
393
 
365
394
  export const Y = withSigilTyped(_Y, 'Label.Y');
366
- export type Y = InstanceType<typeof Y>;
395
+ export type Y = GetInstance<typeof Y>;
367
396
  ```
368
397
 
369
398
  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.
370
399
 
371
- ---
372
-
373
- ### `InstanceType<>` vs `GetInstance<>`
400
+ #### `InstanceType<>` vs `GetInstance<>`
374
401
 
375
- 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`.
376
- So alternative in introduced which is `GetInstance`.
402
+ 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`.
377
403
 
378
404
  ```ts
379
405
  import { Sigil, withSigilTyped, GetInstance } from '@vicin/sigil';
@@ -381,14 +407,12 @@ import { Sigil, withSigilTyped, GetInstance } from '@vicin/sigil';
381
407
  class _X extends Sigil {}
382
408
 
383
409
  export const X = withSigilTyped(_X, 'Label.X');
384
- export type X = GetInstance<typeof X>; // <-- Just replace 'InstanceType' here with 'GetInstance'
410
+ export type X = GetInstance<typeof X>; // <-- works with 'private' and 'protected' constructors as well
385
411
  ```
386
412
 
387
- Internally `GetInstance` is just `T extends { prototype: infer R }` with appending new `__SIGIL_BRAND__` to it.
388
-
389
- ---
413
+ Internally `GetInstance` is just `T extends { prototype: infer R }`.
390
414
 
391
- ### Generic propagation
415
+ #### Generic propagation
392
416
 
393
417
  One of the downsides of defining typed class at the bottom is that we need to redefine generics as well in the type.
394
418
 
@@ -404,9 +428,7 @@ export const X = withSigilTyped(_X, 'Label.X');
404
428
  export type X<G> = GetInstance<typeof X<G>>; // <-- Generics re-defined here, just copy-paste and pass them
405
429
  ```
406
430
 
407
- ---
408
-
409
- ### Anonymous classes
431
+ #### Anonymous classes
410
432
 
411
433
  You may see error: `Property 'x' of exported anonymous class type may not be private or protected.`, although this is rare to occur.
412
434
  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.
@@ -420,6 +442,61 @@ export const X = withSigilTyped(_X, 'Label.X');
420
442
  export type X = GetInstance<typeof X>;
421
443
  ```
422
444
 
445
+ #### Private constructors
446
+
447
+ The only limitation in HOF approach is **extending private constructors**:
448
+
449
+ ```ts
450
+ import { Sigil, withSigilTyped, GetInstance } from '@vicin/sigil';
451
+ class _X extends Sigil {
452
+ private constructor() {}
453
+ }
454
+ const X = withSigilTyped(_X, 'X');
455
+ type X = GetInstance<typeof X>;
456
+
457
+ class _Y extends X {} // <-- This is allowed!
458
+ const Y = withSigilTyped(_Y, 'Y');
459
+ type Y = GetInstance<typeof Y>;
460
+
461
+ const y = new Y(); // <-- Type here is any
462
+ ```
463
+
464
+ Unfortunately this is a limitation in typescript and i couldn't find any solution to adress it.
465
+
466
+ ---
467
+
468
+ ### Decorators with brand field
469
+
470
+ The update of `Sigil` brand type happens directly by overriding `__SIGIL_BRAND__` field:
471
+
472
+ ```ts
473
+ import { Sigil, WithSigil, UpdateSigilBrand } from '@vicin/sigil';
474
+
475
+ @WithSigil('X')
476
+ class X extends Sigil {
477
+ declare __SIGIL_BRAND__: UpdateSigilBrand<'X', Sigil>;
478
+ }
479
+
480
+ @WithSigil('Y')
481
+ class Y extends X {
482
+ declare __SIGIL_BRAND__: UpdateSigilBrand<'Y', Sigil>;
483
+ }
484
+ ```
485
+
486
+ As you see no `_Class`/`Class` pattern, no `private constructor` issue and no type hacks, but our branding logic now lives in class body.
487
+
488
+ To be more explicit and prevent mismatch between run-time and compile-time labels we can:
489
+
490
+ ```ts
491
+ import { Sigil, WithSigil, UpdateSigilBrand } from '@vicin/sigil';
492
+
493
+ const label = 'X';
494
+ @WithSigil(label)
495
+ class X extends Sigil {
496
+ declare __SIGIL_BRAND__: UpdateSigilBrand<typeof label, Sigil>;
497
+ }
498
+ ```
499
+
423
500
  ---
424
501
 
425
502
  ## API reference
@@ -433,7 +510,7 @@ Top-level exports from the library:
433
510
  ```ts
434
511
  export { Sigil, SigilError } from './classes';
435
512
  export { WithSigil } from './decorator';
436
- export { typed, withSigil, withSigilTyped } from './enhancers';
513
+ export { withSigil, withSigilTyped } from './enhancers';
437
514
  export {
438
515
  isDecorated,
439
516
  isInheritanceChecked,
@@ -451,12 +528,11 @@ export {
451
528
  } from './options';
452
529
  export type {
453
530
  ISigil,
454
- ISigilInstance,
455
- ISigilStatic,
456
531
  TypedSigil,
457
532
  GetInstance,
458
533
  SigilBrandOf,
459
534
  SigilOptions,
535
+ UpdateSigilBrand,
460
536
  } from './types';
461
537
  ```
462
538
 
@@ -468,13 +544,12 @@ export type {
468
544
  - `Sigilify(Base, label?, opts?)`: mixin function that returns a new constructor with sigil types and instance helpers.
469
545
  - `withSigil(Class, label?, opts?)`: HOF that validates and decorates an existing class constructor.
470
546
  - `withSigilTyped(Class, label?, opts?)`: like `withSigil` but narrows the TypeScript type to include brands.
471
- - `typed(Class, label?, parent?)`: type-only narrowing helper (no runtime mutation) — asserts runtime label in DEV.
472
547
  - `isSigilCtor(value)`: `true` if `value` is a sigil constructor.
473
548
  - `isSigilInstance(value)`: `true` if `value` is an instance of a sigil constructor.
474
549
  - `SigilRegistry`: `Sigil` Registy class used to centralize classes across app.
475
550
  - `getActiveRegistry`: Getter of active registry being used by `Sigil`.
476
551
  - `updateOptions(opts, mergeRegistries?)`: change global runtime options before sigil decoration (e.g., `autofillLabels`, `devMarker`, etc.).
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`
552
+ - `DEFAULT_LABEL_REGEX`: regex that ensures structure of `@scope/package.ClassName` to all labels, it's advised to use it as your `SigilOptions.labelValidation`
478
553
 
479
554
  ### Instance & static helpers provided by Sigilified constructors
480
555
 
@@ -510,29 +585,20 @@ updateOptions({
510
585
  autofillLabels: false, // auto-generate labels for subclasses that would otherwise inherit
511
586
  skipLabelInheritanceCheck: false, // skip DEV-only inheritance checks -- ALMOST NEVER WANT TO SET THIS TO TRUE, Use 'autofillLabels: true' instead. --
512
587
  labelValidation: null, // or a RegExp / function to validate labels
513
- devMarker: process.env.NODE_ENV !== 'production', // boolean used to block dev only checks in non-dev enviroments
588
+ devMarker: process.env.NODE_ENV !== 'production', // boolean used to block dev only checks in non-dev environments
514
589
  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.
590
+ useGlobalRegistry: true, // append registry into 'globalThis' to ensure single source in the runtime in cross bundles.
516
591
  storeConstructor: true, // store reference of the constructor in registry
517
592
  });
518
593
  ```
519
594
 
520
595
  Global options can be overridden per class by `opts` field in decorator and HOF.
521
596
 
522
- **Notes**:
523
-
524
- - It's advised to use `updateOptions({ labelValidation: DEFAULT_LABEL_REGEX })` at app entry point to validate labels against `@scope/package.ClassName` structure.
525
- - `devMarker` drives DEV-only checks — when `false`, many runtime validations are no-ops (useful for production builds).
526
- - `autofillLabels` is useful for some HMR/test setups where you prefer not to throw on collisions and want autogenerated labels.
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.
530
-
531
597
  ---
532
598
 
533
599
  ## Registry
534
600
 
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`.
601
+ `Sigil`'s default options require developers to label every class, that allows central class registry that stores a reference for every class keyed by its label, also it prevent two classes in the codebase from having the same `SigilLabel`.
536
602
 
537
603
  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
604
 
@@ -584,8 +650,8 @@ import { updateOptions } from '@vicin/sigil';
584
650
  updateOptions({ useGlobalRegistry: false });
585
651
  ```
586
652
 
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:
653
+ Before applying this change, for registry to function normally you should ensure that `Sigil` is not bundles twice in your app.
654
+ 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:
589
655
 
590
656
  ```ts
591
657
  import { updateOptions } from '@vicin/sigil';
@@ -606,7 +672,7 @@ Pick whatever pattern you like!
606
672
 
607
673
  ### Class typing in registry
608
674
 
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:
675
+ 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:
610
676
 
611
677
  ```ts
612
678
  import { getActiveRegistry } from '@vicin/sigil';
@@ -670,7 +736,48 @@ Z.isOfType(new X()); // true
670
736
  Y.isOfType(new Y()); // false
671
737
  ```
672
738
 
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`.
739
+ 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`.
740
+
741
+ ---
742
+
743
+ ## Security guidance
744
+
745
+ ### Recommended defaults & quick rules
746
+
747
+ - Default recommendation for public/shared runtimes (web pages, untrusted workers, serverless):
748
+
749
+ ```ts
750
+ updateOptions({ useGlobalRegistry: false, storeConstructor: false });
751
+ ```
752
+
753
+ This prevents constructors from being put on `globalThis` and prevents constructors from being stored in the registry map (labels remain, but constructors are `null`).
754
+
755
+ - For private server runtimes (single controlled Node process) where a central registry is desired:
756
+
757
+ ```ts
758
+ updateOptions({ useGlobalRegistry: true, storeConstructor: true });
759
+ ```
760
+
761
+ Only enable this if you control all bundles and trust the runtime environment.
762
+
763
+ ### Per-class sensitivity control
764
+
765
+ 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:
766
+
767
+ ```ts
768
+ class _Sensitive extends Sigil {}
769
+ export const Sensitive = withSigil(_Sensitive, '@myorg/internal.Sensitive', {
770
+ storeConstructor: false, // label kept, constructor not stored
771
+ });
772
+ ```
773
+
774
+ This keeps the declarative identity but avoids exposing the constructor reference in the registry.
775
+
776
+ ### Short warnings (do not rely on Sigil for)
777
+
778
+ - **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.
779
+
780
+ - **Third-party code can access the registry if `useGlobalRegistry: true`** — only enable that in fully trusted runtimes.
674
781
 
675
782
  ---
676
783
 
@@ -688,13 +795,9 @@ A: Use `@WithSigil("@your/label")`, or wrap the subclass with `withSigil` / `wit
688
795
 
689
796
  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.
690
797
 
691
- **Q: I need nominal types in TypeScript. Which helper do I use?**
692
-
693
- 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).
694
-
695
798
  **Q: How do I inspect currently registered labels?**
696
799
 
697
- A: Use `getActiveRegistry()?.list()` to get an array of registered labels.
800
+ A: Use `getActiveRegistry()?.listLabels()` to get an array of registered labels.
698
801
 
699
802
  **Q: What if i want to omit labeling in some classes while enforce others?**
700
803
 
@@ -702,15 +805,6 @@ A: You can set `SigilOptions.autofillLabels` to `true`. or if you more strict en
702
805
 
703
806
  ---
704
807
 
705
- ## Best practices
706
-
707
- - Prefer `withSigil`/`withSigilTyped` for predictable, explicit decoration.
708
- - Keep labels globally unique and descriptive (including scope and package like `@myorg/mypkg.ClassName`).
709
- - Use typed helpers for domain-level identities (IDs, tokens, domain types) so the compiler helps you avoid mistakes.
710
- - Run with `devMarker` enabled during local development / CI to catch label collisions early.
711
-
712
- ---
713
-
714
808
  ## Deprecated API
715
809
 
716
810
  ### REGISTRY
@@ -739,6 +833,10 @@ const newRegistry = new SigilRegistry(); // can pass external map to constructor
739
833
  updateOptions({ registry: newRegistry });
740
834
  ```
741
835
 
836
+ ### typed
837
+
838
+ Typed was added to add types to output from `Sigilify` mixin, but now mixin do this by default.
839
+
742
840
  ---
743
841
 
744
842
  ## Phantom