@vicin/sigil 3.3.0 → 4.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/README.md CHANGED
@@ -1,20 +1,31 @@
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) ![bundle size](https://img.shields.io/bundlephobia/minzip/@vicin/sigil)
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/vicin-packages/actions/workflows/ci.yml/badge.svg)](https://github.com/ZiadTaha62/vicin-packages/actions/workflows/ci.yml) ![bundle size](https://img.shields.io/bundlephobia/minzip/@vicin/sigil)
4
4
 
5
- > - 🎉 v3.0.0 is out! Happy coding! 😄💻
5
+ > - 🎉 v4.0.0 is out! Happy coding! 😄💻
6
6
  > - 📄 **Changelog:** [CHANGELOG.md](./CHANGELOG.md)
7
7
 
8
- `Sigil` gives you the power of **safe cross-bundle class instances checks** and **simple class nominal typing** if needed.
8
+ **Sigil** bulletproof class identity for large TypeScript projects.
9
9
 
10
- ## Features
10
+ Reliable `instanceof`-style checks that survive bundling, HMR, monorepos and realms + exact/subtype discrimination + lightweight nominal typing + and debug/log friendly labels/lineage.
11
11
 
12
- - **Drop-in `instanceof` replacement** that works across bundles, HMR and monorepos, Also add check for **exact class instance**
13
- - ✅ **Simple nominal typing** with just one line of code for each class (e.g., `UserId` vs. `PostId`)
14
- - **Tiny less than 1.6 KB minified and brotlied** measured using [size-limit](https://www.npmjs.com/package/size-limit)
15
- - **Performant as native instanceof** but with guaranteed checks
16
- - **Test coverage is 100%** to ensure that runtime remains consistent and predictable
17
- - **Safe with strict rules to ensure uniqueness of labels**, if duplicate label is passed error is thrown immediately
12
+ ## Why Sigil?
13
+
14
+ - **Works when `instanceof` breaks** (multi-bundle, HMR, separate realms)
15
+ - **Exact class match (`isExactInstance`)** not just "inherits from"
16
+ - **One-line nominal branding** (`declare [sigil]: ExtendSigil<…>`)
17
+ - **Human-readable class IDs in logs & debugging** (`SigilLabel`/`SigilLineage`)
18
+ - **Tiny (~0.9 kB brotli)**
19
+ - **Fast (near native `instanceof` speed)**
20
+ - **100% test coverage**
21
+
22
+ ## Limitations
23
+
24
+ - **Identity is explicit and dependent on passed labels**, If same label is passed to two different classes `Sigil` will treat them as one
25
+ - **Identity is updated with every new label passed only**, If you stopped passing labels to child classes their identity will stop at last passed label
26
+ - **`Sigil` is not built for security**, identity can be forged
27
+
28
+ Earlier versions of `Sigil` tried to solve these limitations, however it was not `100%` reliable, so we decided to make this package minimal and stable, while adding new features in future package `@vicin/sigil-extend`
18
29
 
19
30
  ---
20
31
 
@@ -24,7 +35,7 @@
24
35
  - [Install](#install)
25
36
  - [Basic usage](#basic-usage)
26
37
  - [Decorator pattern](#decorator-pattern)
27
- - [HOF pattern](#hof-higher-order-function-pattern)
38
+ - [Function pattern](#function-pattern)
28
39
  - [Migration](#migration)
29
40
  - [Core concepts](#core-concepts)
30
41
  - [Terminology](#terminology)
@@ -37,6 +48,7 @@
37
48
  - [Minimal mode](#minimal-mode)
38
49
  - [Strict mode](#strict-mode)
39
50
  - [Hot module reload](#hot-module-reload)
51
+ - [Edge cases](#edge-cases)
40
52
  - [Benchmarks](#benchmarks)
41
53
  - [Bundle Size](#bundle-size)
42
54
  - [Tests](#tests)
@@ -58,7 +70,9 @@ yarn add @vicin/sigil
58
70
  pnpm add @vicin/sigil
59
71
  ```
60
72
 
61
- Requires TypeScript 5.0+ for decorators; HOFs work on older versions. Node.js 18+ recommended.
73
+ Requires TypeScript 5.0+ for decorators; attach functions work on older versions. Node.js 18+ recommended.
74
+
75
+ > No tsconfig changes are needed as we use **Stage 3 decorators** which are supported by default in TypeScript 5.0+
62
76
 
63
77
  ### Basic usage
64
78
 
@@ -92,30 +106,28 @@ After opting into the `Sigil` contract, labels are passed to child classes to un
92
106
 
93
107
  ##### Decorator pattern
94
108
 
95
- Apply a label with the `@WithSigil` decorator:
109
+ Apply a label with the `@AttachSigil` decorator:
96
110
 
97
111
  ```ts
98
- import { Sigil, WithSigil } from '@vicin/sigil';
112
+ import { Sigil, AttachSigil } from '@vicin/sigil';
99
113
 
100
- @WithSigil('@myorg/mypkg.User')
114
+ @AttachSigil('@myorg/mypkg.User')
101
115
  class User extends Sigil {}
102
116
  ```
103
117
 
104
- ##### HOF (Higher-Order Function) pattern
118
+ ##### Function pattern
105
119
 
106
- Apply a label using `withSigil` HOF:
120
+ Apply a label using `attachSigil` function:
107
121
 
108
122
  ```ts
109
- import { Sigil, withSigil } from '@vicin/sigil';
123
+ import { Sigil, attachSigil } from '@vicin/sigil';
110
124
 
111
- class _User extends Sigil {}
112
- const User = withSigil(_User, '@myorg/mypkg.User');
125
+ class User extends Sigil {}
126
+ attachSigil(User, '@myorg/mypkg.User');
113
127
  ```
114
128
 
115
129
  ### Migration
116
130
 
117
- Migrating old code into `Sigil` can be done with extra couple lines of code only:
118
-
119
131
  1. Pass your base class to `Sigilify` mixin:
120
132
 
121
133
  ```ts
@@ -132,7 +144,7 @@ import { Sigil } from '@vicin/sigil';
132
144
  class MyBaseClass extends Sigil {} // <-- add 'extends Sigil' here
133
145
  ```
134
146
 
135
- Congratulations — you’ve opted into `Sigil` and you can start replacing `instanceof` with `isOfType()` / `isExactType()`, however there is more to add to your system, check [Core concepts](#core-concepts) for more.
147
+ Congratulations — you’ve opted into `Sigil`, now you can start giving your classes identity by using `attachSigil` or `AttachSigil` helpers.
136
148
 
137
149
  ---
138
150
 
@@ -141,16 +153,15 @@ Congratulations — you’ve opted into `Sigil` and you can start replacing `ins
141
153
  ### Terminology
142
154
 
143
155
  - **Label**: An identity (string) such as `@scope/pkg.ClassName`, must be unique for each `Sigil` class otherwise error is thrown.
144
- - **EffectiveLabel:** A human-readable (string) such as `@scope/pkg.ClassName`, if no label is passed it inherit the last defined label.
145
- - **isOfType**: Takes object argument and check if this object is an instance of calling class or it's children. Can be called from class instances as well.
146
- - **isExactType**: Takes object argument and check if this object is an instance of calling class only. Can be called from class instances as well.
156
+ - **isInstance**: Takes object argument and check if this object is an instance of calling class or it's children. Can be called from class instances as well.
157
+ - **isExactInstance**: Takes object argument and check if this object is an instance of calling class only. Can be called from class instances as well.
147
158
  - **[sigil]**: TypeScript symbol marker for nominal types.
148
159
 
149
160
  ---
150
161
 
151
162
  ### Purpose and Origins
152
163
 
153
- Sigil addresses issues in large monorepos, HMR:
164
+ Sigil addresses issues in large codebases and monorepos:
154
165
 
155
166
  - **Unreliable `instanceof`:** Bundling cause class redefinitions, breaking checks.
156
167
 
@@ -159,13 +170,13 @@ Sigil addresses issues in large monorepos, HMR:
159
170
  if (obj instanceof User) { ... }
160
171
 
161
172
  // With Sigil
162
- if (User.isOfType(obj)) { ... } // This still works even if User was bundled twice.
163
- if (User.isExactType(obj)) { ... } // Or check for exactly same constructor not its children
173
+ if (User.isInstance(obj)) { ... } // This still works even if User was bundled twice.
174
+ if (User.isExactInstance(obj)) { ... } // Or check for exactly same constructor not its children
164
175
  ```
165
176
 
166
- Also by utilizing unique passed labels it solve another problem in Domain-Driven Design (DDD):
177
+ Also by utilizing unique passed labels it solves another problem in Domain-Driven Design (DDD):
167
178
 
168
- - **Manual Branding Overhead:** Custom identifiers lead to boilerplate and maintenance issues, `Sigil` add reliable inheritance-aware nominal branding with just one line of code.
179
+ - **Manual Branding Overhead:** Custom identifiers lead to boilerplate and maintenance issues, `Sigil` adds reliable inheritance-aware nominal branding with just one line of code.
169
180
 
170
181
  ```ts
171
182
  import { sigil } from '@vicin/sigil';
@@ -178,46 +189,45 @@ type test1 = User extends Sigil ? true : false; // true
178
189
  type test2 = Sigil extends User ? true : false; // false
179
190
  ```
180
191
 
192
+ - **Need for stable class id**: Most large projects implement their own labels (e.g. to be used in logs)
193
+
181
194
  ### Implementation Mechanics
182
195
 
183
196
  - **Runtime Contract:** Established via extending `Sigil` or using `Sigilify` mixin.
184
- - **Update metadata:** With each new child, use decorators or HOF to attach run-time metadata and `ExtendSigil` to update nominal type.
197
+ - **Update metadata:** With each new child, use decorator (`AttachSigil`) or function (`attachSigil`) to attach run-time metadata, also use `ExtendSigil` on `[sigil]` field to update nominal type.
185
198
 
186
199
  ```ts
187
- import { Sigil, WithSigil, sigil, ExtendSigil } from '@vicin/sigil';
200
+ import { Sigil, AttachSigil, sigil, ExtendSigil } from '@vicin/sigil';
188
201
 
189
- @WithSigil('@scope/package.MyClass') // <-- Run-time values update
202
+ @AttachSigil('@scope/package.MyClass') // <-- Run-time values update
190
203
  class MyClass extends Sigil {
191
204
  declare [sigil]: ExtendSigil<'MyClass', Sigil>; // <-- compile-time type update
192
205
  }
193
206
  ```
194
207
 
195
- You can avoid decorators and use HOF if needed:
208
+ You can avoid decorators and use normal functions if needed:
196
209
 
197
210
  ```ts
198
- import { Sigil, withSigil, sigil, ExtendSigil } from '@vicin/sigil';
211
+ import { Sigil, attachSigil, sigil, ExtendSigil } from '@vicin/sigil';
199
212
 
200
- class _MyClass extends Sigil {
213
+ class MyClass extends Sigil {
201
214
  declare [sigil]: ExtendSigil<'MyClass', Sigil>;
202
215
  }
203
216
 
204
- const MyClass = withSigil(_MyClass, '@scope/package.MyClass');
205
- type MyClass = InstanceType<typeof MyClass>;
217
+ attachSigil(MyClass, '@scope/package.MyClass');
206
218
  ```
207
219
 
208
- Note that you can't use `InstanceType` on `private` or `protected` classes, however you can use `GetPrototype<T>` in such cases.
209
-
210
220
  ### Example
211
221
 
212
222
  ```ts
213
- import { Sigil, WithSigil } from '@vicin/sigil';
223
+ import { Sigil, AttachSigil } from '@vicin/sigil';
214
224
 
215
- @WithSigil('@myorg/User')
225
+ @AttachSigil('@myorg/User')
216
226
  class User extends Sigil {
217
227
  declare [sigil]: ExtendSigil<'User', Sigil>;
218
228
  }
219
229
 
220
- @WithSigil('@myorg/Admin')
230
+ @AttachSigil('@myorg/Admin')
221
231
  class Admin extends User {
222
232
  declare [sigil]: ExtendSigil<'Admin', User>;
223
233
  }
@@ -226,92 +236,60 @@ const admin = new Admin();
226
236
  const user = new User();
227
237
 
228
238
  // Instanceof like behavior
229
- console.log(Admin.isOfType(admin)); // true
230
- console.log(Admin.isOfType(user)); // false
231
- console.log(User.isOfType(admin)); // true
232
- console.log(User.isOfType(user)); // true
239
+ console.log(Admin.isInstance(admin)); // true
240
+ console.log(Admin.isInstance(user)); // false
241
+ console.log(User.isInstance(admin)); // true
242
+ console.log(User.isInstance(user)); // true
233
243
 
234
244
  // Exact checks
235
- console.log(Admin.isExactType(admin)); // true
236
- console.log(Admin.isExactType(user)); // false
237
- console.log(User.isExactType(user)); // true
238
- console.log(User.isExactType(admin)); // false (Admin is child indeed but this checks for user specifically)
245
+ console.log(Admin.isExactInstance(admin)); // true
246
+ console.log(Admin.isExactInstance(user)); // false
247
+ console.log(User.isExactInstance(user)); // true
248
+ console.log(User.isExactInstance(admin)); // false (Admin is child indeed but this checks for user specifically)
239
249
 
240
250
  // Can use checks from instances
241
- console.log(admin.isOfType(user)); // false
242
- console.log(user.isOfType(admin)); // true
243
- console.log(admin.isExactType(user)); // false
244
- console.log(user.isExactType(admin)); // false
251
+ console.log(admin.isInstance(user)); // false
252
+ console.log(user.isInstance(admin)); // true
253
+ console.log(admin.isExactInstance(user)); // false
254
+ console.log(user.isExactInstance(admin)); // false
245
255
 
246
256
  // Type checks are nominal
247
257
  type test1 = Admin extends User ? true : false; // true
248
258
  type test2 = User extends Admin ? true : false; // false
249
259
 
250
260
  // Passed label must be unique (enforced by Sigil) so can be used as stable Id for class
251
- // Also 'SigilLabelLineage' and 'SigilLabelSet' are useful for logging & debugging
261
+ // Also 'SigilLabelLineage' is useful for logging & debugging
252
262
  console.log(Admin.SigilLabel); // '@myorg/Admin'
253
- console.log(Admin.SigilEffectiveLabel); // '@myorg/Admin'
254
263
  console.log(Admin.SigilLabelLineage); // ['Sigil', '@myorg/User', '@myorg/Admin']
255
- console.log(Admin.SigilLabelSet); // Set(['Sigil', '@myorg/User', '@myorg/Admin'])
256
- console.log(admin.getSigilLabel()); // '@myorg/Admin'
257
- console.log(admin.getSigilEffectiveLabel()); // '@myorg/Admin'
258
- console.log(admin.getSigilLabelLineage()); // ['Sigil', '@myorg/User', '@myorg/Admin']
259
- console.log(admin.getSigilLabelSet()); // Set(['Sigil', '@myorg/User', '@myorg/Admin'])
264
+ console.log(admin.SigilLabel); // '@myorg/Admin'
265
+ console.log(admin.SigilLabelLineage); // ['Sigil', '@myorg/User', '@myorg/Admin']
260
266
  ```
261
267
 
262
268
  ### Errors & throws
263
269
 
264
270
  Run-time errors that can be thrown by `Sigil`:
265
271
 
266
- #### Double `Sigilify() / SigilifyAbstract()`
272
+ #### Double Sigilify
267
273
 
268
274
  ```ts
269
275
  class A {}
270
- const B = Sigilify(A, 'A');
271
- const C = Sigilify(B, 'B'); // Throws: [Sigil Error] Class 'Sigilified' with label 'A' is already sigilified
272
-
273
- abstract class AbsA {}
274
- const AbsB = SigilifyAbstract(AbsA, 'AbsA');
275
- const AbsC = SigilifyAbstract(AbsB, 'AbsB'); // Throws: [Sigil Error] Class 'Sigilified' with label 'AbsA' is already sigilified
276
- ```
277
-
278
- #### `@WithSigil() / withSigil()` on non-Sigil class
276
+ Sigilify(Sigilify(A, 'A'), 'B'); // Throws: [Sigil Error] Class 'Sigilified' with label 'A' is already sigilified
279
277
 
280
- ```ts
281
- @WithSigil('A')
282
- class A {} // Throws: [Sigil Error] 'WithSigil' decorator accept only Sigil classes but used on class 'A'
283
-
284
- withSigil(class A {}); // Throws: [Sigil Error] 'WithSigil' decorator accept only Sigil classes but used on class 'A'
285
- ```
286
-
287
- #### Double `@WithSigil() / withSigil()`
288
-
289
- ```ts
290
- @WithSigil('B')
291
- @WithSigil('A')
278
+ @AttachSigil('B')
279
+ @AttachSigil('A')
292
280
  class A extends Sigil {} // Throws: [Sigil Error] Class 'A' with label 'A' is already sigilified
293
281
 
294
- class _A extends Sigil {}
295
- withSigil(withSigil(_A, 'A'), 'B'); // Throws: [Sigil Error] Class 'A' with label 'A' is already sigilified
296
- ```
297
-
298
- #### No label is passed with `autofillLabels: false`
299
-
300
- ```ts
301
- updateSigilOptions({ autofillLabels: false });
302
-
303
282
  class A extends Sigil {}
304
- new A(); // Throws: [Sigil Error] Class 'A' is not sigilified, Make sure to sigilify all Sigil classes or set 'autofillLabels' to 'true'
283
+ attachSigil(attachSigil(A, 'A'), 'B'); // Throws: [Sigil Error] Class 'A' with label 'A' is already sigilified
305
284
  ```
306
285
 
307
- #### Same label is passed twice to `Sigil`
286
+ #### `@AttachSigil() / attachSigil()` on non-Sigil class
308
287
 
309
288
  ```ts
310
- @WithSigil('Label')
311
- class A extends Sigil {}
289
+ @AttachSigil('A') // Throws: [Sigil Error] 'AttachSigil' decorator accept only Sigil classes but used on class 'A'
290
+ class A {}
312
291
 
313
- @WithSigil('Label')
314
- class B extends Sigil {} // Throws: [Sigil Error] Passed label 'Label' to class 'B' is re-used, passed labels must be unique
292
+ attachSigil(class A {}); // Throws: [Sigil Error] 'AttachSigil' function accept only Sigil classes but used on class 'A'
315
293
  ```
316
294
 
317
295
  #### Invalid label format
@@ -319,23 +297,18 @@ class B extends Sigil {} // Throws: [Sigil Error] Passed label 'Label' to class
319
297
  ```ts
320
298
  updateSigilOptions({ labelValidation: RECOMMENDED_LABEL_REGEX });
321
299
 
322
- @WithSigil('InvalidLabel')
300
+ @AttachSigil('InvalidLabel')
323
301
  class A extends Sigil {} // Throws: [Sigil Error] Invalid Sigil label 'InvalidLabel'. Make sure that supplied label matches validation regex or function
324
302
  ```
325
303
 
326
- #### Using '@Sigil-auto' prefix
304
+ #### Attach sigil to parent after child
327
305
 
328
306
  ```ts
329
- @WithSigil('@Sigil-auto:label')
330
- class X extends Sigil {} // Throws: '@Sigil-auto' is a prefex reserved by the library
331
- ```
332
-
333
- #### Invalid options passed to `updateOptions`
307
+ class Parent extends Sigil {}
308
+ class Child extends Parent {}
334
309
 
335
- ```ts
336
- updateSigilOptions({ autofillLabels: {} as any }); // Throws: 'updateSigilOptions.autofillLabels' must be boolean
337
- updateSigilOptions({ labelValidation: 123 as any }); // Throws: 'updateSigilOptions.labelValidation' must be null, function or RegExp
338
- updateSigilOptions({ skipLabelUniquenessCheck: 'str' as any }); // Throws: 'updateSigilOptions.skipLabelUniquenessCheck' must be boolean
310
+ attachSigil(Child, 'Child');
311
+ attachSigil(Parent, 'Parent'); // Throws: [Sigil Error] Class 'Parent' with label 'Sigil' is already sigilified
339
312
  ```
340
313
 
341
314
  ---
@@ -353,24 +326,21 @@ updateSigilOptions({ skipLabelUniquenessCheck: 'str' as any }); // Throws: 'upda
353
326
  - `SigilError`
354
327
 
355
328
  - **Decorator:**
356
- - `WithSigil(label, opts?)`
329
+ - `AttachSigil(label, opts?)`
357
330
 
358
- - **HOFs:**
359
- - `withSigil(Class, label, opts?)`
331
+ - **Attach function:**
332
+ - `attachSigil(Class, label, opts?)`
360
333
 
361
334
  - **Helpers:**
362
335
  - `isSigilCtor(ctor)`
363
336
  - `isSigilInstance(inst)`
364
- - `getSigilLabels()`
337
+ - `hasOwnSigil(ctor)`
365
338
 
366
339
  - **Options:**
367
340
  - `updateSigilOptions(opts)`
368
341
  - `RECOMMENDED_LABEL_REGEX`
369
342
 
370
343
  - **Types:**
371
- - `ISigil<Label, ParentSigil?>`
372
- - `ISigilStatic<Label, ParentSigil?>`
373
- - `ISigilInstance<Label, ParentSigil?>`
374
344
  - `SigilOf<T>`
375
345
  - `ExtendSigil<Label, Parent>`
376
346
  - `GetPrototype<Class>`
@@ -382,12 +352,12 @@ updateSigilOptions({ skipLabelUniquenessCheck: 'str' as any }); // Throws: 'upda
382
352
  - `SigilError`: an `Error` class decorated with a `Sigil` so it can be identified at runtime.
383
353
  - `Sigilify(Base, label, opts?)`: mixin function that returns a new constructor with `Sigil` types and instance helpers.
384
354
  - `SigilifyAbstract(Base, label, opts?)`: Same as `Sigilify` but for abstract classes.
385
- - `WithSigil(label, opts?)`: class decorator that attaches `Sigil` metadata at declaration time.
386
- - `withSigil(Class, label, opts?)`: HOF that validates and decorates an existing class constructor.
355
+ - `AttachSigil(label, opts?)`: class decorator that attaches `Sigil` metadata at declaration time.
356
+ - `attachSigil(Class, label, opts?)`: function that validates and decorates an existing class constructor.
387
357
  - `isSigilCtor(value)`: `true` if `value` is a `Sigil` constructor.
388
358
  - `isSigilInstance(value)`: `true` if `value` is an instance of a `Sigil` constructor.
389
- - `getSigilLabels()`: Get `Sigil` labels registered.
390
- - `updateSigilOptions(opts)`: change global runtime options of `Sigil` library (e.g., `autofillLabels`).
359
+ - `hasOwnSigil(ctor)`: `true` if new sigil label is attached to `ctor`
360
+ - `updateSigilOptions(opts)`: change global runtime options of `Sigil` library (e.g., `labelValidation`).
391
361
  - `RECOMMENDED_LABEL_REGEX`: regex that ensures structure of `@scope/package.ClassName` to all labels, it's advised to use it as your `SigilOptions.labelValidation`
392
362
 
393
363
  ### Instance & static helpers provided by Sigilified constructors
@@ -395,20 +365,18 @@ updateSigilOptions({ skipLabelUniquenessCheck: 'str' as any }); // Throws: 'upda
395
365
  When a constructor is sigilified it will expose the following **static** getters/methods:
396
366
 
397
367
  - `SigilLabel` — the identity label string.
398
- - `SigilEffectiveLabel` — the human label string.
399
368
  - `SigilLabelLineage` — readonly array of labels representing parent → child for debugging.
400
- - `SigilLabelSet` — readonly `Set<string>` of sigil labels for debugging.
401
- - `isOfType(other)` — check if other is an instance of this constructor or its children.
402
- - `isExactType(other) `— check if other is an instance exactly this constructor.
369
+ - `hasOwnSigil` — check if new sigil label is attached to this class.
370
+ - `isInstance(other)` — check if other is an instance of this constructor or its children.
371
+ - `isExactInstance(other) `— check if other is an instance exactly this constructor.
403
372
 
404
373
  Instances of sigilified classes expose instance helpers:
405
374
 
406
- - `getSigilLabel()` — returns the identity label.
407
- - `getSigilEffectiveLabel()` — returns the human label.
408
- - `getSigilLabelLineage()` — returns lineage array.
409
- - `getSigilLabelSet()` — returns readonly Set.
410
- - `isOfType(other)` check if other is an instance of the same class or its children as this.
411
- - `isExactType(other) `— check if other is an instance exactly the same constructor.
375
+ - `SigilLabel` — the identity label string.
376
+ - `SigilLabelLineage` — readonly array of labels representing parent → child for debugging.
377
+ - `hasOwnSigil` — check if new sigil label is attached to this class.
378
+ - `isInstance(other)` — check if other is an instance of the same class or its children as this.
379
+ - `isExactInstance(other) `— check if other is an instance exactly the same constructor.
412
380
 
413
381
  ---
414
382
 
@@ -420,64 +388,72 @@ Customize behavior globally at startup:
420
388
  import { updateSigilOptions } from '@vicin/sigil';
421
389
 
422
390
  updateSigilOptions({
423
- autofillLabels: true, // Automatically label unlabeled subclasses
424
391
  labelValidation: null, // Function or regex, Enforce label format
425
- skipLabelUniquenessCheck: false, // Skip uniqueness check for labels, should be used in HMR set-ups only
426
392
  });
427
393
  ```
428
394
 
429
- Values defined in previous example are defaults, per-class overrides available in mixin, decorators, and HOFs.
395
+ Values defined in previous example are defaults, per-class overrides available in mixin and attach function / decorator.
430
396
 
431
397
  ---
432
398
 
433
- ## Minimal mode
399
+ ## Edge cases
400
+
401
+ ### Attach sigil to parent after child
434
402
 
435
- By default `Sigil` works with minimal mode, You can ignore all decorators and HOFs and just make base class extend `Sigil`:
403
+ When attaching `Sigil` labels to classes order from parent -> child must be respected
436
404
 
437
405
  ```ts
438
- import { Sigil, updateSigilOptions } from '@vicin/sigil';
406
+ class Parent extends Sigil {}
407
+ class Child extends Parent {}
439
408
 
440
- // No decorators or HOF needed to use 'isOfType' ('instanceof' replacement)
441
- class A extends Sigil {}
442
- class B extends A {}
443
- class C extends B {}
409
+ attachSigil(Child, 'Child');
410
+ attachSigil(Parent, 'Parent'); // Throws: [Sigil Error] Class 'Parent' with label 'Sigil' is already sigilified
444
411
  ```
445
412
 
446
- ## Strict mode
413
+ ### Accessing Sigil `metadata` before running `attachSigil` function
447
414
 
448
- 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:
415
+ If you didn't make sure that `attachSigil` runs right after class declaration and used one of `Sigil` methods this will occur:
449
416
 
450
417
  ```ts
451
- import { updateSigilOptions } from '@vicin/sigil';
418
+ class A extends Sigil {}
452
419
 
453
- updateSigilOptions({ autofillLabels: false });
454
- ```
420
+ console.log(A.SigilLabel); // returns label of the parent (Sigil in our case)
455
421
 
456
- Now if you forgot to pass a label error is thrown at the moment you create class instance or use any of `Sigil` methods.
422
+ attachSigil(A, 'A');
457
423
 
458
- ---
459
-
460
- ## Hot module reload
424
+ console.log(A.SigilLabel); // A
425
+ ```
461
426
 
462
- HMR can cause class re-definitions, which will throw in default `Sigil` set-up as same label will be passed multiple times triggering duplicate label error.
463
- To avoid this you can set global options to skip label uniqueness check at the start of app:
427
+ To avoid this bug entirely you can use the return of `attachSigil` in your code so you are enforced to respect order:
464
428
 
465
429
  ```ts
466
- import { updateSigilOptions } from '@vicin/sigil';
430
+ class _A extends Sigil {}
431
+
432
+ const A = attachSigil(_A, 'A');
433
+ type A = InstanceType<typeof A>;
467
434
 
468
- updateSigilOptions({ skipLabelUniquenessCheck: true });
435
+ console.log(A.SigilLabel); // A
469
436
  ```
470
437
 
471
- But this can cause bugs if same label is used for two different classes as checks are disables globally.
472
- If you need more strict mode you can pass this options to the re-loaded class only:
438
+ Note that you can't use `InstanceType` on `private` or `protected` classes, however you can use `GetPrototype<T>` in such cases.
439
+
440
+ #### Static blocks & IIFE static initializer
441
+
442
+ Stage 3 decorators (`AttachSigil`) and function (`attachSigil`) runs after IIFE and static blocks, so accessing `Sigil` metadata inside them should be avoided
473
443
 
474
444
  ```ts
475
- @WithSigil('HmrClassLabel', { skipLabelUniquenessCheck: true })
476
- class HmrClass extends Sigil {}
445
+ @AttachSigil('A')
446
+ class A extends Sigil {
447
+ static IIFE = (() => {
448
+ const label = A.SigilLabel; // returns label of the parent (Sigil in our case)
449
+ })();
450
+
451
+ static {
452
+ const label = this.SigilLabel; // returns label of the parent (Sigil in our case)
453
+ }
454
+ }
477
455
  ```
478
456
 
479
- With this approach `skipLabelUniquenessCheck` affects only `HmrClass`, and if `HmrClassLabel` or any other label is re-used error is thrown immediately
480
-
481
457
  ---
482
458
 
483
459
  ## Benchmarks
@@ -486,68 +462,95 @@ Sigil is built for **real-world performance**. Below are the latest micro-benchm
486
462
 
487
463
  **Running Tests**
488
464
 
489
- To run benchmarks on your machine fetch source code from [github](https://github.com/ZiadTaha62/sigil) then:
465
+ To run benchmarks on your machine fetch source code from [github](https://github.com/ZiadTaha62/vicin-packages) then:
490
466
 
491
467
  ```bash
492
- npm install
493
- npm run bench
468
+ pnpm install
469
+ pnpm run bench --filter @vicin/sigil
494
470
  ```
495
471
 
496
472
  ### 1. Runtime Type Checking
497
473
 
498
- | Depth | `instanceof` (per op) | `isOfType` (ctor) | `isOfType` (instance) | `isExactType` (ctor) | `isExactType` (instance) |
499
- | ----- | --------------------- | ----------------- | --------------------- | -------------------- | ------------------------ |
500
- | 0 | 0.000010 ms | 0.000025 ms | **0.000010 ms** | 0.000027 ms | 0.000012 ms |
501
- | 3 | 0.000032 ms | 0.000045 ms | **0.000027 ms** | 0.000038 ms | **0.000025 ms** |
502
- | 5 | 0.000034 ms | 0.000046 ms | **0.000028 ms** | 0.000037 ms | **0.000026 ms** |
503
- | 10 | 0.000044 ms | 0.000045 ms | **0.000029 ms** | 0.000038 ms | **0.000027 ms** |
504
- | 15 | 0.000058 ms | 0.000063 ms | **0.000051 ms** | 0.000069 ms | **0.000053 ms** |
505
-
506
- > **Key takeaway**:
507
- > `isOfType` & `isExactType` has **practically the same performance as native `instanceof`**, slightly **slower** on static calls and slightly **faster** on the instance side.
474
+ | Test Name | Ops/sec (hz) | Mean (ms) | p99 (ms) | RME | Samples |
475
+ | -------------------------- | ------------ | --------- | -------- | ------ | --------- |
476
+ | **Depth 0** | | | | | |
477
+ | instanceof | 11,986,628 | 0.0001 | 0.0002 | ±0.58% | 5,993,314 |
478
+ | isInstance (Ctor) | 8,848,040 | 0.0001 | 0.0003 | ±0.63% | 4,424,020 |
479
+ | isInstance (instance) | 9,246,878 | 0.0001 | 0.0002 | ±0.86% | 4,623,440 |
480
+ | isExactInstance (Ctor) | 5,114,916 | 0.0002 | 0.0003 | ±0.44% | 2,557,459 |
481
+ | isExactInstance (instance) | 5,553,370 | 0.0002 | 0.0003 | ±0.41% | 2,776,686 |
482
+ | **Depth 5** | | | | | |
483
+ | instanceof | 8,399,150 | 0.0001 | 0.0002 | ±0.48% | 4,199,651 |
484
+ | isInstance (Ctor) | 7,385,890 | 0.0001 | 0.0003 | ±2.01% | 3,692,945 |
485
+ | isInstance (instance) | 7,862,474 | 0.0001 | 0.0003 | ±0.77% | 3,931,238 |
486
+ | isExactInstance (Ctor) | 4,433,700 | 0.0002 | 0.0004 | ±0.64% | 2,216,851 |
487
+ | isExactInstance (instance) | 4,898,498 | 0.0002 | 0.0004 | ±0.34% | 2,449,249 |
488
+ | **Depth 10** | | | | | |
489
+ | instanceof | 7,144,486 | 0.0001 | 0.0003 | ±0.63% | 3,572,243 |
490
+ | isInstance (Ctor) | 7,165,920 | 0.0001 | 0.0003 | ±0.49% | 3,582,960 |
491
+ | isInstance (instance) | 7,834,056 | 0.0001 | 0.0003 | ±1.16% | 3,917,029 |
492
+ | isExactInstance (Ctor) | 4,292,578 | 0.0002 | 0.0004 | ±0.34% | 2,146,290 |
493
+ | isExactInstance (instance) | 5,020,923 | 0.0002 | 0.0004 | ±0.28% | 2,510,462 |
494
+ | **Depth 15** | | | | | |
495
+ | instanceof | 7,116,266 | 0.0001 | 0.0002 | ±0.38% | 3,558,134 |
496
+ | isInstance (Ctor) | 6,308,498 | 0.0002 | 0.0003 | ±0.41% | 3,154,249 |
497
+ | isInstance (instance) | 6,403,126 | 0.0002 | 0.0003 | ±0.70% | 3,201,564 |
498
+ | isExactInstance (Ctor) | 3,678,280 | 0.0003 | 0.0005 | ±0.37% | 1,839,141 |
499
+ | isExactInstance (instance) | 3,753,618 | 0.0003 | 0.0005 | ±0.39% | 1,876,810 |
500
+
501
+ > **Key takeaway**:
502
+ >
503
+ > `isInstance` Efficiency: Demonstrates high parity with native instanceof, maintaining a minimal performance delta (approx. 15% overhead).
504
+ > `isExactInstance` Cost-Benefit: While introducing a ~2x overhead compared to native operations, it remains performant in absolute terms. The throughput cost is a deliberate trade-off for the increased precision required for exact matching.
508
505
 
509
506
  ### 2. Class Definition & Instance Creation
510
507
 
511
- | Scenario | Definition (per class) | Instantiation (per instance) |
512
- | ------------------------------- | ---------------------- | ---------------------------- |
513
- | Empty plain class | 0.0122 ms | 0.00019 ms |
514
- | Empty Sigil class | 0.0672 ms | 0.00059 ms |
515
- | Small (5 props + 3 methods) | 0.0172 ms | 0.00327 ms |
516
- | Large (15 props + 10 methods) | 0.0212 ms | 0.00922 ms |
517
- | Large Sigil | 0.0780 ms | 0.01177 ms |
518
- | Extended chain depth 5 plain | 0.0897 ms | 0.01809 ms |
519
- | Extended chain depth 5 Sigil | 0.3978 ms | 0.02020 ms |
520
- | Extended chain depth 10 – plain | 0.2042 ms | 0.05759 ms |
521
- | Extended chain depth 10 Sigil | 0.8127 ms | 0.06675 ms |
508
+ | Test Name | Ops/sec (hz) | Mean (ms) | p99 (ms) | RME | Samples |
509
+ | ------------------------------------- | ----------------- | ---------------- | ---------------- | ------- | --------- |
510
+ | **Definition (Module Load Time)** | | | | | |
511
+ | Define Empty (Plain vs Sigil) | 122,640 vs 13,154 | 0.0082 vs 0.0760 | 0.0206 vs 0.1413 | ±11.36% | 6,578 |
512
+ | Define Small (Plain vs Sigil) | 75,363 vs 12,003 | 0.0133 vs 0.0833 | 0.0267 vs 0.1596 | ±5.98% | 6,002 |
513
+ | Define Large (Plain vs Sigil) | 53,392 vs 11,978 | 0.0187 vs 0.0835 | 0.0352 vs 0.1471 | ±6.13% | 5,990 |
514
+ | Define Depth 3 (Plain vs Sigil) | 22,802 vs 5,205 | 0.0439 vs 0.1921 | 0.0850 vs 0.3139 | ±14.68% | 2,603 |
515
+ | Define Depth 5 (Plain vs Sigil) | 12,003 vs 3,369 | 0.0833 vs 0.2968 | 0.1864 vs 0.5488 | ±7.31% | 1,685 |
516
+ | Define Depth 10 (Plain vs Sigil) | 5,477 vs 1,758 | 0.1826 vs 0.5685 | 0.3402 vs 1.1148 | ±9.62% | 880 |
517
+ | **Instantiation (Runtime/Hot Path)** | | | | | |
518
+ | Instantiate Empty (Plain vs Sigil) | 12.0M vs 11.9M | 0.0001 vs 0.0001 | 0.0002 vs 0.0002 | ±0.76% | 5,999,555 |
519
+ | Instantiate Small (Plain vs Sigil) | 12.5M vs 11.4M | 0.0001 vs 0.0001 | 0.0001 vs 0.0002 | ±0.89% | 5,739,986 |
520
+ | Instantiate Large (Plain vs Sigil) | 12.4M vs 12.4M | 0.0001 vs 0.0001 | 0.0002 vs 0.0001 | ±0.74% | 6,205,306 |
521
+ | Instantiate Depth 3 (Plain vs Sigil) | 12.0M vs 12.1M | 0.0001 vs 0.0001 | 0.0002 vs 0.0002 | ±1.01% | 6,092,517 |
522
+ | Instantiate Depth 5 (Plain vs Sigil) | 9.09M vs 8.54M | 0.0001 vs 0.0001 | 0.0002 vs 0.0003 | ±2.24% | 4,272,328 |
523
+ | Instantiate Depth 10 (Plain vs Sigil) | 2.87M vs 1.72M | 0.0003 vs 0.0006 | 0.0008 vs 0.0011 | ±0.75% | 862,564 |
522
524
 
523
525
  > **Key takeaways**:
524
526
  >
525
- > - Class definition is a **one-time cost** at module load time. Even at depth 10 the cost stays well under 1 ms per class.
526
- > - Instance creation adds a small fixed overhead of ~0.4–0.6 µs per object, which becomes completely negligible as your classes grow in size and complexity.
527
+ > Front-Loaded Definition Overhead: Sigil introduces main overhead during the class definition phase (roughly 6x to 10x slower than plain classes). This is a one-time cost per class, typically occurring during module evaluation or registry setup.
528
+ >
529
+ > Runtime Performance Parity: Once the class is defined, Sigil achieves near-native instantiation throughput. For standard object creation, the delta is negligible, ensuring that the library does not bottleneck high-frequency allocation patterns.
530
+ >
531
+ > Scale Stability: The overhead of Sigil remains constant regardless of the number of properties or methods added (Small vs. Large). The definition speed for a "Small Sigil" and a "Large Sigil" is nearly identical (~12k hz), suggesting that the setup logic is O(1) relative to class member count.
527
532
 
528
533
  ---
529
534
 
530
535
  ## Bundle Size
531
536
 
532
- **Less than 1.6 KB (1.54 KB)** (minified + Brotli, including all dependencies)
537
+ **Less than 1 KB (908 B)** (minified + Brotli, including all dependencies)
533
538
 
534
539
  This makes Sigil one of the smallest full-featured solutions for nominal typing + reliable runtime identity.
535
540
 
536
541
  **Running Tests**
537
542
 
538
- To verify bundle size fetch source code from [github](https://github.com/ZiadTaha62/sigil) then:
543
+ To verify bundle size fetch source code from [github](https://github.com/ZiadTaha62/vicin-packages) then:
539
544
 
540
545
  ```bash
541
- npm install
542
- npm run size
546
+ pnpm install
547
+ pnpm run size --filter @vicin/sigil
543
548
  ```
544
549
 
545
550
  ---
546
551
 
547
552
  ## Tests
548
553
 
549
- Reliability is a core pillar of `Sigil`. The library is backed by a comprehensive suite of unit tests that cover everything from basic mixins to lazy evaluation.
550
-
551
554
  **Coverage Status**
552
555
 
553
556
  We maintain **100%** test coverage across the entire codebase to ensure that runtime metadata remains consistent and predictable.
@@ -559,21 +562,13 @@ We maintain **100%** test coverage across the entire codebase to ensure that run
559
562
  | Funcs | 100% |
560
563
  | Lines | 100% |
561
564
 
562
- **Key Test Areas**
563
-
564
- - **Mixins, Decorators & HOFs:** Validating `Sigilify`, `WithSigil` and `withSigil` behaviors.
565
- - **Sigil methods:** Ensuring `Sigil` class methods (e.g. `SigilLabel`, `getSigilLabel`) work as expected.
566
- - **Lazy Evaluation:** Ensuring metadata is attached before being accessed via `Sigil` methods.
567
- - **Lineage:** Verifying that `isOfType` and `isExactType` work across complex inheritance chains.
568
- - **Error Handling:** Strict validation for all errors and throws.
569
-
570
565
  **Running Tests**
571
566
 
572
- To run the test suite locally and generate a coverage report, fetch source code from [github](https://github.com/ZiadTaha62/sigil) then:
567
+ To run the test suite locally and generate a coverage report, fetch source code from [github](https://github.com/ZiadTaha62/vicin-packages) then:
573
568
 
574
569
  ```bash
575
- npm install
576
- npm run test:unit
570
+ pnpm install
571
+ pnpm run test --filter @vicin/sigil
577
572
  ```
578
573
 
579
574
  ---