@vicin/sigil 2.2.0 β†’ 3.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.
package/CHANGELOG.md CHANGED
@@ -2,7 +2,27 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## [2.1.1] - 2026-02-23
5
+ ## [3.0.0] - 2026-02-24
6
+
7
+ ### Added
8
+
9
+ - `sigil` symbol used for type-only nominal branding of classes
10
+
11
+ ### Changed
12
+
13
+ - `__SIGIL_BRAND__` is classes replaced with `sigil` symbol
14
+
15
+ ### Removed
16
+
17
+ - `withSigilTyped` is removed as library moved into manual branding
18
+
19
+ ### Breaking changes
20
+
21
+ - `withSigilTyped` is removed as library moved into manual branding
22
+ - `SigilBrandOf` is renamed to `SigilOf`
23
+ - `UpdateSigilBrand` is renamed to `ExtendSigil`
24
+
25
+ ## [2.2.1] - 2026-02-23
6
26
 
7
27
  ### Added
8
28
 
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Sigil
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/@vicin/sigil.svg)](https://www.npmjs.com/package/@vicin/sigil) [![npm downloads](https://img.shields.io/npm/dm/@vicin/sigil.svg)](https://www.npmjs.com/package/@vicin/sigil) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) ![TypeScript](https://img.shields.io/badge/TypeScript-5.0%2B-blue) [![Build](https://github.com/ZiadTaha62/sigil/actions/workflows/ci.yml/badge.svg)](https://github.com/ZiadTaha62/sigil/actions/workflows/ci.yml)
3
+ [![npm version](https://img.shields.io/npm/v/@vicin/sigil.svg)](https://www.npmjs.com/package/@vicin/sigil) [![npm downloads](https://img.shields.io/npm/dm/@vicin/sigil.svg)](https://www.npmjs.com/package/@vicin/sigil) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) ![TypeScript](https://img.shields.io/badge/TypeScript-5.0%2B-blue) [![Build](https://github.com/ZiadTaha62/sigil/actions/workflows/ci.yml/badge.svg)](https://github.com/ZiadTaha62/sigil/actions/workflows/ci.yml) ![bundle size](https://img.shields.io/bundlephobia/minzip/@vicin/sigil)
4
4
 
5
- > - πŸŽ‰ v2.0.0 is out! Happy coding! πŸ˜„πŸ’»
5
+ > - πŸŽ‰ v3.0.0 is out! Happy coding! πŸ˜„πŸ’»
6
6
  > - πŸ“„ **Changelog:** [CHANGELOG.md](./CHANGELOG.md)
7
7
 
8
8
  `Sigil` replaces `instanceof` across bundles, enforces nominal class identity, and makes inheritance-aware runtime type checks reliable in large TypeScript systems. It organizes class identities across your codebase and gives you the power of **nominal typing** and **safe cross-bundle class checks** where each class constructor is stored under a unique label.
@@ -15,14 +15,17 @@
15
15
 
16
16
  ## Important Notes Before Using
17
17
 
18
- - **Explicit class identity:** `Sigil` uses passed class label to identify classes, which means that dev is responsible for uniqueness of classes by passing unique labels.
19
- - **Performance:** Minimal overhead, but `.isOfType()` is slightly slower than native `instanceof`. Avoid in ultra-hot paths.
20
- - **Private Constructors:** HOF pattern allows extending private constructors in types (TypeScript limitation).
18
+ - **Explicit class identity:** `Sigil` uses passed class label to identify classes, which means that the developer is responsible for uniqueness of classes by passing unique labels.
19
+ - **Performance:** Minimal overhead, `.isOfType()` is slightly slower than native `instanceof`. Avoid in ultra-hot paths.
21
20
  - **Simple instanceof Fix:** If you just need runtime checks without extras, see the [minimal mode](#minimal-mode).
22
21
 
23
- ## Why Registry is dropped
22
+ ## Features
24
23
 
25
- In v2 we simplified the architecture to remove global registries and reduce complexity across packages.
24
+ - βœ… **Drop-in `instanceof` replacement** that works across bundles, HMR, and monorepos
25
+ - βœ… **True nominal typing** with zero runtime cost
26
+ - βœ… **Inheritance-aware** checks (`isOfType` knows about subclasses)
27
+ - βœ… Four flexible usage patterns: `extends Sigil`, mixin, decorator, HOF
28
+ - βœ… Full TypeScript 5.0+ support + excellent JSDoc
26
29
 
27
30
  ---
28
31
 
@@ -33,24 +36,16 @@ In v2 we simplified the architecture to remove global registries and reduce comp
33
36
  - [Basic usage](#basic-usage)
34
37
  - [Decorator pattern](#decorator-pattern)
35
38
  - [HOF pattern](#hof-higher-order-function-pattern)
36
- - [Minimal β€œfirst-run” example](#minimal-first-run-example)
37
39
  - [Migration](#migration)
38
- - [Limitations & guarantees](#limitations--guarantees)
39
- - [What Sigil guarantees](#what-sigil-guarantees)
40
- - [What Sigil does not guarantee](#what-sigil-does-not-guarantee)
41
40
  - [Core concepts](#core-concepts)
42
41
  - [Terminology](#terminology)
43
42
  - [Purpose and Origins](#purpose-and-origins)
44
43
  - [Implementation Mechanics](#implementation-mechanics)
45
- - [Nominal typing patterns](#nominal-typing-patterns)
46
- - [HOF pattern](#1-hof-pattern-_classclass)
47
- - [Decorator pattern](#2-decorator-pattern)
44
+ - [Inheritance example](#inheritance-example)
48
45
  - [API reference](#api-reference)
49
46
  - [Options & configuration](#options--configuration)
50
47
  - [Minimal mode](#minimal-mode)
51
48
  - [Strict mode](#strict-mode)
52
- - [## Which pattern should I use?](#which-pattern-should-i-use)
53
- - [Troubleshooting & FAQ](#troubleshooting--faq)
54
49
  - [Contributing](#contributing)
55
50
  - [License](#license)
56
51
  - [Author](#author)
@@ -92,10 +87,8 @@ If your class is marked with `abstract`:
92
87
  ```ts
93
88
  import { Sigil, SigilifyAbstract } from '@vicin/sigil';
94
89
 
95
- // Using the pre-sigilified base class:
96
90
  abstract class User extends Sigil {}
97
91
 
98
- // Or use Sigilify when you want an ad-hoc class:
99
92
  const MyClass = SigilifyAbstract(abstract class {}, '@myorg/mypkg.MyClass');
100
93
  ```
101
94
 
@@ -118,7 +111,7 @@ class User extends Sigil {}
118
111
 
119
112
  ##### HOF (Higher-Order Function) pattern
120
113
 
121
- Apply a label using HOF as `withSigil` or `withSigilTyped`:
114
+ Apply a label using `withSigil` HOF:
122
115
 
123
116
  ```ts
124
117
  import { Sigil, withSigil } from '@vicin/sigil';
@@ -130,26 +123,6 @@ const user = new User();
130
123
  console.log(User.SigilLabel); // "@myorg/mypkg.User"
131
124
  ```
132
125
 
133
- > Note: When extending an already sigilified class (for example `Sigil`), you must decorate the subclass or use the HOF helpers in DEV mode unless you configured the library otherwise.
134
-
135
- ### Minimal β€œfirst-run” example
136
-
137
- ```ts
138
- import { Sigil, withSigil } from '@vicin/sigil';
139
-
140
- class _User extends Sigil {
141
- constructor(public name: string) {
142
- super();
143
- }
144
- }
145
- export const User = withSigil(_User, '@myorg/mypkg.User');
146
-
147
- const u = new User('alice');
148
-
149
- console.log(User.SigilLabel); // "@myorg/mypkg.User"
150
- console.log(User.isOfType(u)); // true
151
- ```
152
-
153
126
  ### Migration
154
127
 
155
128
  Migrating old code into `Sigil` can be done with extra couple lines of code only:
@@ -174,33 +147,15 @@ Congratulations β€” you’ve opted into `Sigil` and you can start replacing `ins
174
147
 
175
148
  ---
176
149
 
177
- ## Limitations & guarantees
178
-
179
- This section states clearly what `Sigil` provides and what it does **not** provide.
180
-
181
- ### What Sigil guarantees
182
-
183
- **1. Reliable runtime identity (when used as intended).**
184
-
185
- **2. Nominal typing that is inheritance-aware**
186
-
187
- ### What Sigil does not guarantee
188
-
189
- **1. Doesn't work across isolated realms (e.g., iframes, workers) without custom bridging.**
190
-
191
- **2. Not for security/access control β€” constructors can be discoverable.**
192
-
193
- ---
194
-
195
150
  ## Core concepts
196
151
 
197
152
  ### Terminology
198
153
 
199
154
  - **Label**: An identity (string) such as `@scope/pkg.ClassName`, but can be random string (e.g. `@Sigil.auto-dq62ib6jnvmmlfbjhxh2937h`) if no label passed.
200
- - **EffictiveLabel:** A human-readable (string) such as `@scope/pkg.ClassName`, if no label is passed it inherit the last defined label.
155
+ - **EffectiveLabel:** A human-readable (string) such as `@scope/pkg.ClassName`, if no label is passed it inherit the last defined label.
201
156
  - **Label lineage**: Array of labels for ancestry.
202
157
  - **Label set**: Set of labels for fast checks.
203
- - **Brand**: TypeScript marker (`__SIGIL_BRAND__`) for nominal types.
158
+ - **[sigil]**: TypeScript symbol marker for nominal types.
204
159
 
205
160
  ---
206
161
 
@@ -218,141 +173,74 @@ if (obj instanceof User) { ... }
218
173
  if (User.isOfType(obj)) { ... } // This still works even if User was bundled twice.
219
174
  ```
220
175
 
221
- - **Manual Branding Overhead:** Custom identifiers lead to boilerplate and maintenance issues.
222
-
223
- `Sigil` abstracts these into a **centralized system**, making identity management **explicit** and **error-resistant** if defined the right way.
224
-
225
- ### Implementation Mechanics
226
-
227
- - **Runtime Contract:** Established via extending `Sigil` or using `Sigilify` mixin.
228
- - **Update metadata:** With each new child, HOF or decorators are used to attach metadata and update nominal type.
229
- - **Accessors & Type guards:** Classes expose `SigilLabel`; instances provide `getSigilLabel()` for querying unique identifier label. also when typed it hold nominal identity used to prevent subtle bugs.
176
+ - **Manual Branding Overhead:** Custom identifiers lead to boilerplate and maintenance issues, `Sigil` add reliable inheritance-aware nominal branding with just one line of code.
230
177
 
231
178
  ```ts
232
- import { Sigil, withSigilTyped, GetInstance } from '@vicin/sigil';
179
+ import { sigil } from '@vicin/sigil';
233
180
 
234
- // Runtime contract
235
- class _MyClass extends Sigil {}
236
-
237
- // Update metadata (append new label)
238
- const MyClass = withSigilTyped(_MyClass, '@scope/package.MyClass');
239
- type MyClass = GetInstance<typeof MyClass>;
181
+ class User extends Sigil {
182
+ declare [sigil]: ExtendSigil<'User', Sigil>; // <-- Update nominal brand with this line
183
+ }
240
184
 
241
- // Accessors & Type guards
242
- console.log(MyClass.SigilLabel); // '@scope/package.MyClass'
243
- console.log(new MyClass().getSigilLabel()); // '@scope/package.MyClass'
244
- console.log(MyClass.isOfType(new MyClass())); // true
245
- function x(c: MyClass) {} // Only instances of 'MyClass' can be passed
185
+ type test1 = User extends Sigil ? true : false; // true
186
+ type test2 = Sigil extends User ? true : false; // false
246
187
  ```
247
188
 
248
- ---
249
-
250
- ## Nominal typing patterns
251
-
252
- In this part we will discuss conventions to avoid any type errors and have nominal typing with just extra few definition lines.
253
- We have two patterns, **HOF pattern (`_Class`/`Class`)** and **Decorator pattern**:
189
+ `Sigil` abstracts these into a **centralized system**, making identity management **explicit** and **error-resistant** if defined the right way.
254
190
 
255
- ### 1. HOF pattern (`_Class`/`Class`)
191
+ ### Implementation Mechanics
256
192
 
257
- Define implementation in an untyped class, then wrap for typing:
193
+ - **Runtime Contract:** Established via extending `Sigil` or using `Sigilify` mixin.
194
+ - **Update metadata:** With each new child, use decorators or HOF to attach run-time metadata and `ExtendSigil` to update nominal type.
258
195
 
259
196
  ```ts
260
- import { Sigil, withSigilTyped, GetInstance } from '@vicin/sigil';
197
+ import { Sigil, WithSigil, sigil, ExtendSigil } from '@vicin/sigil';
261
198
 
262
- class _X extends Sigil {
263
- // Class logic here
199
+ @WithSigil('@scope/package.MyClass') // <-- Run-time values update
200
+ class MyClass extends Sigil {
201
+ declare [sigil]: ExtendSigil<'@scope/package.MyClass', Sigil>; // <-- compile-time type update
264
202
  }
265
- export const X = withSigilTyped(_X, 'Label.X');
266
- export type X = GetInstance<typeof X>;
267
203
  ```
268
204
 
269
- #### `InstanceType<>` vs `GetInstance<>`
270
-
271
- 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`.
205
+ You can avoid decorators and use HOF but they are slightly more verbose:
272
206
 
273
207
  ```ts
274
- export type X = GetInstance<typeof X>; // <-- works with 'private' and 'protected' constructors as well
275
- ```
276
-
277
- Internally `GetInstance` is just `T extends { prototype: infer R }`.
278
-
279
- #### Generic propagation
280
-
281
- ```ts
282
- class _X<G> extends Sigil {}
283
- export const X = withSigilTyped(_X, 'Label.X');
284
- export type X<G> = GetInstance<typeof X<G>>; // <-- Redeclare generics here
208
+ import { Sigil, withSigil, sigil, ExtendSigil } from '@vicin/sigil';
285
209
 
286
- class _Y<G> extends X<G> {} // and so on...
287
- ```
288
-
289
- #### Anonymous classes
290
-
291
- You may see error: `Property 'x' of exported anonymous class type may not be private or protected.`, although this is rare to occur.
292
- This comes from the fact that all typed classes are `anonymous class` as they are return of HOF. to avoid these error entirely all you need is exporting the untyped classes even if they are un-used as a good convention.
293
-
294
- ```ts
295
- export class _X extends Sigil {} // <-- Just add 'export' here
296
- export const X = withSigilTyped(_X, 'Label.X');
297
- export type X = GetInstance<typeof X>;
298
- ```
299
-
300
- #### Private constructors
301
-
302
- The only limitation in HOF approach is **extending private constructors**:
303
-
304
- ```ts
305
- import { Sigil, withSigilTyped, GetInstance } from '@vicin/sigil';
306
- class _X extends Sigil {
307
- private constructor() {}
210
+ class _MyClass extends Sigil {
211
+ declare [sigil]: ExtendSigil<'@scope/package.MyClass', Sigil>;
308
212
  }
309
- const X = withSigilTyped(_X, 'X');
310
- type X = GetInstance<typeof X>;
311
213
 
312
- class _Y extends X {} // <-- This is allowed!
313
- const Y = withSigilTyped(_Y, 'Y');
314
- type Y = GetInstance<typeof Y>;
315
-
316
- const y = new Y(); // <-- Type here is any
214
+ const MyClass = withSigil(_MyClass, '@scope/package.MyClass');
215
+ type MyClass = InstanceType<typeof MyClass>;
317
216
  ```
318
217
 
319
- This is a known TypeScript limitation.
320
-
321
- ---
322
-
323
- ### 2. Decorator pattern
218
+ Note that you can't use `InstanceType` on `private` or `protected` classes.
324
219
 
325
- Inject brand directly in class body:
220
+ ### Inheritance example
326
221
 
327
222
  ```ts
328
- import { Sigil, WithSigil, UpdateSigilBrand } from '@vicin/sigil';
223
+ import { Sigil, WithSigil } from '@vicin/sigil';
329
224
 
330
- @WithSigil('X')
331
- class X extends Sigil {
332
- declare __SIGIL_BRAND__: UpdateSigilBrand<'X', Sigil>;
225
+ @WithSigil('@myorg/User')
226
+ class User extends Sigil {
227
+ declare [sigil]: ExtendSigil<'@myorg/User', Sigil>;
333
228
  }
334
229
 
335
- @WithSigil('Y')
336
- class Y extends X {
337
- declare __SIGIL_BRAND__: UpdateSigilBrand<'Y', X>;
230
+ @WithSigil('@myorg/Admin')
231
+ class Admin extends User {
232
+ declare [sigil]: ExtendSigil<'@myorg/Admin', User>;
338
233
  }
339
- ```
340
-
341
- No `_Class`/`Class` pattern, no `private constructor` issue, no type hacks and only one extra line, but our branding logic now lives in class body.
342
-
343
- #### Label Consistency
344
-
345
- Use typeof label for compile-time matching:
346
234
 
347
- ```ts
348
- import { Sigil, WithSigil, UpdateSigilBrand } from '@vicin/sigil';
235
+ const admin = new Admin();
236
+ const user = new User();
349
237
 
350
- const label = 'X';
238
+ console.log(Admin.isOfType(admin)); // true
239
+ console.log(Admin.isOfType(user)); // false
240
+ console.log(User.isOfType(admin)); // true
351
241
 
352
- @WithSigil(label)
353
- class X extends Sigil {
354
- declare __SIGIL_BRAND__: UpdateSigilBrand<typeof label, Sigil>;
355
- }
242
+ type test1 = Admin extends User ? true : false; // true
243
+ type test2 = User extends Admin ? true : false; // false
356
244
  ```
357
245
 
358
246
  ---
@@ -374,7 +262,6 @@ class X extends Sigil {
374
262
 
375
263
  - **HOFs:**
376
264
  - `withSigil(Class, label?, opts?)`
377
- - `withSigilTyped(Class, label?, opts?)`
378
265
 
379
266
  - **Helpers:**
380
267
  - `isSigilCtor(ctor)`
@@ -392,20 +279,17 @@ class X extends Sigil {
392
279
  - `ISigil<Label, ParentSigil?>`
393
280
  - `ISigilStatic<Label, ParentSigil?>`
394
281
  - `ISigilInstance<Label, ParentSigil?>`
395
- - `SigilBrandOf<T>`
396
- - `TypedSigil<SigilClass, Label>`
397
- - `GetInstance<T>`
398
- - `UpdateSigilBrand<Label, Base>`
282
+ - `SigilOf<T>`
283
+ - `ExtendSigil<Label, Parent>`
399
284
  - `SigilOptions`
400
285
 
401
286
  ### Key helpers (runtime)
402
287
 
403
288
  - `Sigil`: a minimal sigilified base class you can extend from.
404
289
  - `SigilError`: an `Error` class decorated with a `Sigil` so it can be identified at runtime.
405
- - `WithSigil(label)`: class decorator that attaches `Sigil` metadata at declaration time.
406
290
  - `Sigilify(Base, label?, opts?)`: mixin function that returns a new constructor with `Sigil` types and instance helpers.
291
+ - `WithSigil(label)`: class decorator that attaches `Sigil` metadata at declaration time.
407
292
  - `withSigil(Class, label?, opts?)`: HOF that validates and decorates an existing class constructor.
408
- - `withSigilTyped(Class, label?, opts?)`: like `withSigil` but narrows the TypeScript type to include brands.
409
293
  - `isSigilCtor(value)`: `true` if `value` is a `Sigil` constructor.
410
294
  - `isSigilInstance(value)`: `true` if `value` is an instance of a `Sigil` constructor.
411
295
  - `updateSigilOptions(opts)`: change global runtime options before `Sigil` decoration (e.g., `autofillLabels`).
@@ -467,7 +351,7 @@ class C extends B {}
467
351
 
468
352
  ## Strict mode
469
353
 
470
- If you want to inforce passing a label to every class defined in your codebase, you can set `autofillLabels` to `false` at the start of app:
354
+ If you want to enforce passing a label to every class defined in your codebase, you can set `autofillLabels` to `false` at the start of app:
471
355
 
472
356
  ```ts
473
357
  import { updateSigilOptions } from '@vicin/sigil';
@@ -479,23 +363,6 @@ Now if you forgot to pass a label error is thrown.
479
363
 
480
364
  ---
481
365
 
482
- ## Which pattern should I use?
483
-
484
- - Want simplest setup? β†’ Extend `Sigil`
485
- - Want full nominal typing? β†’ Use HOF pattern
486
- - Want clean class bodies? β†’ Use HOF
487
- - Want fewer wrapper exports? β†’ Use Decorators
488
-
489
- ---
490
-
491
- ## Troubleshooting & FAQ
492
-
493
- - **Dev Extension Errors:** Add labels or enable autofillLabels.
494
- - **Anonymous Class Errors:** Export untyped bases.
495
- - **Selective Labeling:** Use `autofillLabels: true` or empty `@WithSigil()` for auto-generation.
496
-
497
- ---
498
-
499
366
  ## Contributing
500
367
 
501
368
  Any contributions you make are **greatly appreciated**.