@vicin/sigil 3.4.0 → 4.0.1

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, and adds an **exact-class-instance check**
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 (~1 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
 
@@ -34,9 +45,6 @@
34
45
  - [Errors & throws](#errors--throws)
35
46
  - [API reference](#api-reference)
36
47
  - [Options & configuration](#options--configuration)
37
- - [Minimal mode](#minimal-mode)
38
- - [Strict mode](#strict-mode)
39
- - [Hot module reload](#hot-module-reload)
40
48
  - [Edge cases](#edge-cases)
41
49
  - [Benchmarks](#benchmarks)
42
50
  - [Bundle Size](#bundle-size)
@@ -115,12 +123,8 @@ class User extends Sigil {}
115
123
  attachSigil(User, '@myorg/mypkg.User');
116
124
  ```
117
125
 
118
- > Note: Function pattern is susceptible to some [Edge case subtle pitfalls](#edge-cases) if not used appropriately, so we advise to use decorator pattern
119
-
120
126
  ### Migration
121
127
 
122
- Migrating old code into `Sigil` can be done with extra couple lines of code only:
123
-
124
128
  1. Pass your base class to `Sigilify` mixin:
125
129
 
126
130
  ```ts
@@ -137,7 +141,7 @@ import { Sigil } from '@vicin/sigil';
137
141
  class MyBaseClass extends Sigil {} // <-- add 'extends Sigil' here
138
142
  ```
139
143
 
140
- 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.
144
+ Congratulations — you’ve opted into `Sigil`, now you can start giving your classes identity by using `attachSigil` or `AttachSigil` helpers.
141
145
 
142
146
  ---
143
147
 
@@ -146,16 +150,15 @@ Congratulations — you’ve opted into `Sigil` and you can start replacing `ins
146
150
  ### Terminology
147
151
 
148
152
  - **Label**: An identity (string) such as `@scope/pkg.ClassName`, must be unique for each `Sigil` class otherwise error is thrown.
149
- - **EffectiveLabel:** A human-readable (string) such as `@scope/pkg.ClassName`, if no label is passed it inherit the last defined label.
150
- - **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.
151
- - **isExactType**: Takes object argument and check if this object is an instance of calling class only. Can be called from class instances as well.
153
+ - **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.
154
+ - **isExactInstance**: Takes object argument and check if this object is an instance of calling class only. Can be called from class instances as well.
152
155
  - **[sigil]**: TypeScript symbol marker for nominal types.
153
156
 
154
157
  ---
155
158
 
156
159
  ### Purpose and Origins
157
160
 
158
- Sigil addresses issues in large monorepos, HMR:
161
+ Sigil addresses issues in large codebases and monorepos:
159
162
 
160
163
  - **Unreliable `instanceof`:** Bundling cause class redefinitions, breaking checks.
161
164
 
@@ -164,8 +167,8 @@ Sigil addresses issues in large monorepos, HMR:
164
167
  if (obj instanceof User) { ... }
165
168
 
166
169
  // With Sigil
167
- if (User.isOfType(obj)) { ... } // This still works even if User was bundled twice.
168
- if (User.isExactType(obj)) { ... } // Or check for exactly same constructor not its children
170
+ if (User.isInstance(obj)) { ... } // This still works even if User was bundled twice.
171
+ if (User.isExactInstance(obj)) { ... } // Or check for exactly same constructor not its children
169
172
  ```
170
173
 
171
174
  Also by utilizing unique passed labels it solves another problem in Domain-Driven Design (DDD):
@@ -183,6 +186,8 @@ type test1 = User extends Sigil ? true : false; // true
183
186
  type test2 = Sigil extends User ? true : false; // false
184
187
  ```
185
188
 
189
+ - **Need for stable class id**: Most large projects implement their own labels (e.g. to be used in logs)
190
+
186
191
  ### Implementation Mechanics
187
192
 
188
193
  - **Runtime Contract:** Established via extending `Sigil` or using `Sigilify` mixin.
@@ -228,22 +233,22 @@ const admin = new Admin();
228
233
  const user = new User();
229
234
 
230
235
  // Instanceof like behavior
231
- console.log(Admin.isOfType(admin)); // true
232
- console.log(Admin.isOfType(user)); // false
233
- console.log(User.isOfType(admin)); // true
234
- console.log(User.isOfType(user)); // true
236
+ console.log(Admin.isInstance(admin)); // true
237
+ console.log(Admin.isInstance(user)); // false
238
+ console.log(User.isInstance(admin)); // true
239
+ console.log(User.isInstance(user)); // true
235
240
 
236
241
  // Exact checks
237
- console.log(Admin.isExactType(admin)); // true
238
- console.log(Admin.isExactType(user)); // false
239
- console.log(User.isExactType(user)); // true
240
- console.log(User.isExactType(admin)); // false (Admin is child indeed but this checks for user specifically)
242
+ console.log(Admin.isExactInstance(admin)); // true
243
+ console.log(Admin.isExactInstance(user)); // false
244
+ console.log(User.isExactInstance(user)); // true
245
+ console.log(User.isExactInstance(admin)); // false (Admin is child indeed but this checks for user specifically)
241
246
 
242
247
  // Can use checks from instances
243
- console.log(admin.isOfType(user)); // false
244
- console.log(user.isOfType(admin)); // true
245
- console.log(admin.isExactType(user)); // false
246
- console.log(user.isExactType(admin)); // false
248
+ console.log(admin.isInstance(user)); // false
249
+ console.log(user.isInstance(admin)); // true
250
+ console.log(admin.isExactInstance(user)); // false
251
+ console.log(user.isExactInstance(admin)); // false
247
252
 
248
253
  // Type checks are nominal
249
254
  type test1 = Admin extends User ? true : false; // true
@@ -252,11 +257,9 @@ type test2 = User extends Admin ? true : false; // false
252
257
  // Passed label must be unique (enforced by Sigil) so can be used as stable Id for class
253
258
  // Also 'SigilLabelLineage' is useful for logging & debugging
254
259
  console.log(Admin.SigilLabel); // '@myorg/Admin'
255
- console.log(Admin.SigilEffectiveLabel); // '@myorg/Admin'
256
260
  console.log(Admin.SigilLabelLineage); // ['Sigil', '@myorg/User', '@myorg/Admin']
257
- console.log(admin.getSigilLabel()); // '@myorg/Admin'
258
- console.log(admin.getSigilEffectiveLabel()); // '@myorg/Admin'
259
- console.log(admin.getSigilLabelLineage()); // ['Sigil', '@myorg/User', '@myorg/Admin']
261
+ console.log(admin.SigilLabel); // '@myorg/Admin'
262
+ console.log(admin.SigilLabelLineage); // ['Sigil', '@myorg/User', '@myorg/Admin']
260
263
  ```
261
264
 
262
265
  ### Errors & throws
@@ -286,25 +289,6 @@ class A {}
286
289
  attachSigil(class A {}); // Throws: [Sigil Error] 'AttachSigil' function accept only Sigil classes but used on class 'A'
287
290
  ```
288
291
 
289
- #### No label is passed with `autofillLabels: false`
290
-
291
- ```ts
292
- updateSigilOptions({ autofillLabels: false });
293
-
294
- class A extends Sigil {}
295
- new A(); // Throws: [Sigil Error] Class 'A' is not sigilified, Make sure to sigilify all Sigil classes or set 'autofillLabels' to 'true'
296
- ```
297
-
298
- #### Same label is passed twice to `Sigil`
299
-
300
- ```ts
301
- @AttachSigil('Label')
302
- class A extends Sigil {}
303
-
304
- @AttachSigil('Label')
305
- class B extends Sigil {} // Throws: [Sigil Error] Passed label 'Label' to class 'B' is re-used, passed labels must be unique
306
- ```
307
-
308
292
  #### Invalid label format
309
293
 
310
294
  ```ts
@@ -314,19 +298,14 @@ updateSigilOptions({ labelValidation: RECOMMENDED_LABEL_REGEX });
314
298
  class A extends Sigil {} // Throws: [Sigil Error] Invalid Sigil label 'InvalidLabel'. Make sure that supplied label matches validation regex or function
315
299
  ```
316
300
 
317
- #### Using '@Sigil-auto' prefix
301
+ #### Attach sigil to parent after child
318
302
 
319
303
  ```ts
320
- @AttachSigil('@Sigil-auto:label')
321
- class X extends Sigil {} // Throws: '@Sigil-auto' is a prefix reserved by the library
322
- ```
304
+ class Parent extends Sigil {}
305
+ class Child extends Parent {}
323
306
 
324
- #### Invalid options passed to `updateOptions`
325
-
326
- ```ts
327
- updateSigilOptions({ autofillLabels: {} as any }); // Throws: 'updateSigilOptions.autofillLabels' must be boolean
328
- updateSigilOptions({ labelValidation: 123 as any }); // Throws: 'updateSigilOptions.labelValidation' must be null, function or RegExp
329
- updateSigilOptions({ skipLabelUniquenessCheck: 'str' as any }); // Throws: 'updateSigilOptions.skipLabelUniquenessCheck' must be boolean
307
+ attachSigil(Child, 'Child');
308
+ attachSigil(Parent, 'Parent'); // Throws: [Sigil Error] Class 'Parent' with label 'Sigil' is already sigilified
330
309
  ```
331
310
 
332
311
  ---
@@ -352,16 +331,13 @@ updateSigilOptions({ skipLabelUniquenessCheck: 'str' as any }); // Throws: 'upda
352
331
  - **Helpers:**
353
332
  - `isSigilCtor(ctor)`
354
333
  - `isSigilInstance(inst)`
355
- - `getSigilLabels()`
334
+ - `hasOwnSigil(ctor)`
356
335
 
357
336
  - **Options:**
358
337
  - `updateSigilOptions(opts)`
359
338
  - `RECOMMENDED_LABEL_REGEX`
360
339
 
361
340
  - **Types:**
362
- - `ISigil<Label, ParentSigil?>`
363
- - `ISigilStatic<Label, ParentSigil?>`
364
- - `ISigilInstance<Label, ParentSigil?>`
365
341
  - `SigilOf<T>`
366
342
  - `ExtendSigil<Label, Parent>`
367
343
  - `GetPrototype<Class>`
@@ -377,8 +353,8 @@ updateSigilOptions({ skipLabelUniquenessCheck: 'str' as any }); // Throws: 'upda
377
353
  - `attachSigil(Class, label, opts?)`: function that validates and decorates an existing class constructor.
378
354
  - `isSigilCtor(value)`: `true` if `value` is a `Sigil` constructor.
379
355
  - `isSigilInstance(value)`: `true` if `value` is an instance of a `Sigil` constructor.
380
- - `getSigilLabels()`: Get `Sigil` labels registered.
381
- - `updateSigilOptions(opts)`: change global runtime options of `Sigil` library (e.g., `autofillLabels`).
356
+ - `hasOwnSigil(ctor)`: `true` if new sigil label is attached to `ctor`
357
+ - `updateSigilOptions(opts)`: change global runtime options of `Sigil` library (e.g., `labelValidation`).
382
358
  - `RECOMMENDED_LABEL_REGEX`: regex that ensures structure of `@scope/package.ClassName` to all labels, it's advised to use it as your `SigilOptions.labelValidation`
383
359
 
384
360
  ### Instance & static helpers provided by Sigilified constructors
@@ -386,18 +362,18 @@ updateSigilOptions({ skipLabelUniquenessCheck: 'str' as any }); // Throws: 'upda
386
362
  When a constructor is sigilified it will expose the following **static** getters/methods:
387
363
 
388
364
  - `SigilLabel` — the identity label string.
389
- - `SigilEffectiveLabel` — the human label string.
390
365
  - `SigilLabelLineage` — readonly array of labels representing parent → child for debugging.
391
- - `isOfType(other)` — check if other is an instance of this constructor or its children.
392
- - `isExactType(other) `— check if other is an instance exactly this constructor.
366
+ - `hasOwnSigil` — check if new sigil label is attached to this class.
367
+ - `isInstance(other)` check if other is an instance of this constructor or its children.
368
+ - `isExactInstance(other) `— check if other is an instance exactly this constructor.
393
369
 
394
370
  Instances of sigilified classes expose instance helpers:
395
371
 
396
- - `getSigilLabel()` — returns the identity label.
397
- - `getSigilEffectiveLabel()` — returns the human label.
398
- - `getSigilLabelLineage()` — returns lineage array.
399
- - `isOfType(other)` — check if other is an instance of the same class or its children as this.
400
- - `isExactType(other) `— check if other is an instance exactly the same constructor.
372
+ - `SigilLabel` — the identity label string.
373
+ - `SigilLabelLineage` — readonly array of labels representing parent → child for debugging.
374
+ - `hasOwnSigil` — check if new sigil label is attached to this class.
375
+ - `isInstance(other)` — check if other is an instance of the same class or its children as this.
376
+ - `isExactInstance(other) `— check if other is an instance exactly the same constructor.
401
377
 
402
378
  ---
403
379
 
@@ -409,9 +385,7 @@ Customize behavior globally at startup:
409
385
  import { updateSigilOptions } from '@vicin/sigil';
410
386
 
411
387
  updateSigilOptions({
412
- autofillLabels: true, // Automatically label unlabeled subclasses
413
388
  labelValidation: null, // Function or regex, Enforce label format
414
- skipLabelUniquenessCheck: false, // Skip uniqueness check for labels, should be used in HMR set-ups only
415
389
  });
416
390
  ```
417
391
 
@@ -419,66 +393,28 @@ Values defined in previous example are defaults, per-class overrides available i
419
393
 
420
394
  ---
421
395
 
422
- ## Minimal mode
423
-
424
- By default `Sigil` works with minimal mode, You can ignore all decorators and functions and just make base class extend `Sigil`:
425
-
426
- ```ts
427
- import { Sigil, updateSigilOptions } from '@vicin/sigil';
428
-
429
- // No decorators or functions needed to use 'isOfType' ('instanceof' replacement)
430
- class A extends Sigil {}
431
- class B extends A {}
432
- class C extends B {}
433
- ```
434
-
435
- ## Strict mode
436
-
437
- 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:
438
-
439
- ```ts
440
- import { updateSigilOptions } from '@vicin/sigil';
441
-
442
- updateSigilOptions({ autofillLabels: false });
443
- ```
444
-
445
- Now if you forgot to pass a label error is thrown at the moment you create class instance or use any of `Sigil` methods.
446
-
447
- ---
396
+ ## Edge cases
448
397
 
449
- ## Hot module reload
398
+ ### Attach sigil to parent after child
450
399
 
451
- 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.
452
- To avoid this you can set global options to skip label uniqueness check at the start of app:
400
+ When attaching `Sigil` labels to classes order from parent -> child must be respected
453
401
 
454
402
  ```ts
455
- import { updateSigilOptions } from '@vicin/sigil';
456
-
457
- updateSigilOptions({ skipLabelUniquenessCheck: true });
458
- ```
459
-
460
- But this can cause unexpected behavior if same label is used for two different classes as checks are disabled globally.
461
- If you need more strict mode you can pass this options to the re-loaded class only:
403
+ class Parent extends Sigil {}
404
+ class Child extends Parent {}
462
405
 
463
- ```ts
464
- @AttachSigil('HmrClassLabel', { skipLabelUniquenessCheck: true })
465
- class HmrClass extends Sigil {}
406
+ attachSigil(Child, 'Child');
407
+ attachSigil(Parent, 'Parent'); // Throws: [Sigil Error] Class 'Parent' with label 'Sigil' is already sigilified
466
408
  ```
467
409
 
468
- With this approach `skipLabelUniquenessCheck` affects only `HmrClass`, and if `HmrClassLabel` or any other label is re-used error is thrown immediately
469
-
470
- ---
471
-
472
- ## Edge cases
473
-
474
- ### Accessing Sigil `metadata` before running 'attachSigil' function
410
+ ### Accessing Sigil `metadata` before running `attachSigil` function
475
411
 
476
412
  If you didn't make sure that `attachSigil` runs right after class declaration and used one of `Sigil` methods this will occur:
477
413
 
478
414
  ```ts
479
415
  class A extends Sigil {}
480
416
 
481
- console.log(A.SigilLabel); // returns auto-generated label (e.g. @Sigil-auto:A:6:a3f15bhl) or throws in strict mode
417
+ console.log(A.SigilLabel); // returns label of the parent (Sigil in our case)
482
418
 
483
419
  attachSigil(A, 'A');
484
420
 
@@ -500,24 +436,21 @@ Note that you can't use `InstanceType` on `private` or `protected` classes, howe
500
436
 
501
437
  #### Static blocks & IIFE static initializer
502
438
 
503
- Decorators ensure that metadata is appended before static blocks or IIFE static initializers, however `attachSigil` function runs after them so accessing label inside them will return auto-generated label or throw:
439
+ Stage 3 decorators (`AttachSigil`) and function (`attachSigil`) runs after IIFE and static blocks, so accessing `Sigil` metadata inside them should be avoided
504
440
 
505
441
  ```ts
442
+ @AttachSigil('A')
506
443
  class A extends Sigil {
507
444
  static IIFE = (() => {
508
- const label = A.SigilLabel; // returns auto-generated label (e.g. @Sigil-auto:A:6:a3f15bhl) or throws in strict mode
445
+ const label = A.SigilLabel; // returns label of the parent (Sigil in our case)
509
446
  })();
510
447
 
511
448
  static {
512
- const label = this.SigilLabel; // returns auto-generated label (e.g. @Sigil-auto:A:6:a3f15bhl) or throws in strict mode
449
+ const label = this.SigilLabel; // returns label of the parent (Sigil in our case)
513
450
  }
514
451
  }
515
-
516
- attachSigil(A, 'A');
517
452
  ```
518
453
 
519
- This behavior can't be avoided, so make sure not to call any `Sigil` method inside them or move to decorators (`@AttachSigil`)
520
-
521
454
  ---
522
455
 
523
456
  ## Benchmarks
@@ -526,95 +459,113 @@ Sigil is built for **real-world performance**. Below are the latest micro-benchm
526
459
 
527
460
  **Running Tests**
528
461
 
529
- To run benchmarks on your machine fetch source code from [github](https://github.com/ZiadTaha62/sigil) then:
462
+ To run benchmarks on your machine fetch source code from [github](https://github.com/ZiadTaha62/vicin-packages) then:
530
463
 
531
464
  ```bash
532
- npm install
533
- npm run bench
465
+ pnpm install
466
+ pnpm run bench --filter @vicin/sigil
534
467
  ```
535
468
 
536
469
  ### 1. Runtime Type Checking
537
470
 
538
- | Depth | `instanceof` (per op) | `isOfType` (ctor) | `isOfType` (instance) | `isExactType` (ctor) | `isExactType` (instance) |
539
- | ----- | --------------------- | ----------------- | --------------------- | -------------------- | ------------------------ |
540
- | 0 | 0.000010 ms | 0.000025 ms | **0.000010 ms** | 0.000027 ms | 0.000012 ms |
541
- | 3 | 0.000032 ms | 0.000045 ms | **0.000027 ms** | 0.000038 ms | **0.000025 ms** |
542
- | 5 | 0.000034 ms | 0.000046 ms | **0.000028 ms** | 0.000037 ms | **0.000026 ms** |
543
- | 10 | 0.000044 ms | 0.000045 ms | **0.000029 ms** | 0.000038 ms | **0.000027 ms** |
544
- | 15 | 0.000058 ms | 0.000063 ms | **0.000051 ms** | 0.000069 ms | **0.000053 ms** |
545
-
546
- > **Key takeaway**:
547
- > `isOfType` & `isExactType` has **practically the same performance as native `instanceof`**, slightly **slower** on static calls and slightly **faster** on the instance side.
471
+ | Test Name | Ops/sec (hz) | Mean (ms) | p99 (ms) | RME | Samples |
472
+ | -------------------------- | ------------ | --------- | -------- | ------ | --------- |
473
+ | **Depth 0** | | | | | |
474
+ | instanceof | 11,986,628 | 0.0001 | 0.0002 | ±0.58% | 5,993,314 |
475
+ | isInstance (Ctor) | 8,848,040 | 0.0001 | 0.0003 | ±0.63% | 4,424,020 |
476
+ | isInstance (instance) | 9,246,878 | 0.0001 | 0.0002 | ±0.86% | 4,623,440 |
477
+ | isExactInstance (Ctor) | 5,114,916 | 0.0002 | 0.0003 | ±0.44% | 2,557,459 |
478
+ | isExactInstance (instance) | 5,553,370 | 0.0002 | 0.0003 | ±0.41% | 2,776,686 |
479
+ | **Depth 5** | | | | | |
480
+ | instanceof | 8,399,150 | 0.0001 | 0.0002 | ±0.48% | 4,199,651 |
481
+ | isInstance (Ctor) | 7,385,890 | 0.0001 | 0.0003 | ±2.01% | 3,692,945 |
482
+ | isInstance (instance) | 7,862,474 | 0.0001 | 0.0003 | ±0.77% | 3,931,238 |
483
+ | isExactInstance (Ctor) | 4,433,700 | 0.0002 | 0.0004 | ±0.64% | 2,216,851 |
484
+ | isExactInstance (instance) | 4,898,498 | 0.0002 | 0.0004 | ±0.34% | 2,449,249 |
485
+ | **Depth 10** | | | | | |
486
+ | instanceof | 7,144,486 | 0.0001 | 0.0003 | ±0.63% | 3,572,243 |
487
+ | isInstance (Ctor) | 7,165,920 | 0.0001 | 0.0003 | ±0.49% | 3,582,960 |
488
+ | isInstance (instance) | 7,834,056 | 0.0001 | 0.0003 | ±1.16% | 3,917,029 |
489
+ | isExactInstance (Ctor) | 4,292,578 | 0.0002 | 0.0004 | ±0.34% | 2,146,290 |
490
+ | isExactInstance (instance) | 5,020,923 | 0.0002 | 0.0004 | ±0.28% | 2,510,462 |
491
+ | **Depth 15** | | | | | |
492
+ | instanceof | 7,116,266 | 0.0001 | 0.0002 | ±0.38% | 3,558,134 |
493
+ | isInstance (Ctor) | 6,308,498 | 0.0002 | 0.0003 | ±0.41% | 3,154,249 |
494
+ | isInstance (instance) | 6,403,126 | 0.0002 | 0.0003 | ±0.70% | 3,201,564 |
495
+ | isExactInstance (Ctor) | 3,678,280 | 0.0003 | 0.0005 | ±0.37% | 1,839,141 |
496
+ | isExactInstance (instance) | 3,753,618 | 0.0003 | 0.0005 | ±0.39% | 1,876,810 |
497
+
498
+ > **Key takeaway**:
499
+ >
500
+ > `isInstance` Efficiency: Demonstrates high parity with native instanceof, maintaining a minimal performance delta (approx. 15% overhead).
501
+ > `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.
548
502
 
549
503
  ### 2. Class Definition & Instance Creation
550
504
 
551
- | Scenario | Definition (per class) | Instantiation (per instance) |
552
- | ------------------------------- | ---------------------- | ---------------------------- |
553
- | Empty plain class | 0.0122 ms | 0.00019 ms |
554
- | Empty Sigil class | 0.0672 ms | 0.00059 ms |
555
- | Small (5 props + 3 methods) | 0.0172 ms | 0.00327 ms |
556
- | Large (15 props + 10 methods) | 0.0212 ms | 0.00922 ms |
557
- | Large Sigil | 0.0780 ms | 0.01177 ms |
558
- | Extended chain depth 5 plain | 0.0897 ms | 0.01809 ms |
559
- | Extended chain depth 5 Sigil | 0.3978 ms | 0.02020 ms |
560
- | Extended chain depth 10 – plain | 0.2042 ms | 0.05759 ms |
561
- | Extended chain depth 10 Sigil | 0.8127 ms | 0.06675 ms |
505
+ | Test Name | Ops/sec (hz) | Mean (ms) | p99 (ms) | RME | Samples |
506
+ | ------------------------------------- | ----------------- | ---------------- | ---------------- | ------- | --------- |
507
+ | **Definition (Module Load Time)** | | | | | |
508
+ | Define Empty (Plain vs Sigil) | 122,640 vs 13,154 | 0.0082 vs 0.0760 | 0.0206 vs 0.1413 | ±11.36% | 6,578 |
509
+ | Define Small (Plain vs Sigil) | 75,363 vs 12,003 | 0.0133 vs 0.0833 | 0.0267 vs 0.1596 | ±5.98% | 6,002 |
510
+ | Define Large (Plain vs Sigil) | 53,392 vs 11,978 | 0.0187 vs 0.0835 | 0.0352 vs 0.1471 | ±6.13% | 5,990 |
511
+ | 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 |
512
+ | 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 |
513
+ | Define Depth 10 (Plain vs Sigil) | 5,477 vs 1,758 | 0.1826 vs 0.5685 | 0.3402 vs 1.1148 | ±9.62% | 880 |
514
+ | **Instantiation (Runtime/Hot Path)** | | | | | |
515
+ | 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 |
516
+ | 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 |
517
+ | 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 |
518
+ | 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 |
519
+ | 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 |
520
+ | 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 |
562
521
 
563
522
  > **Key takeaways**:
564
523
  >
565
- > - Class definition is a **one-time cost** at module load time. Even at depth 10 the cost stays well under 1 ms per class.
566
- > - 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.
524
+ > 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.
525
+ >
526
+ > 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.
527
+ >
528
+ > 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.
567
529
 
568
530
  ---
569
531
 
570
532
  ## Bundle Size
571
533
 
572
- **Less than 1.6 KB (1.52 KB)** (minified + Brotli, including all dependencies)
534
+ **Less than 1 kB (997 B)** (minified + Brotli, including all dependencies)
573
535
 
574
536
  This makes Sigil one of the smallest full-featured solutions for nominal typing + reliable runtime identity.
575
537
 
576
538
  **Running Tests**
577
539
 
578
- To verify bundle size fetch source code from [github](https://github.com/ZiadTaha62/sigil) then:
540
+ To verify bundle size fetch source code from [github](https://github.com/ZiadTaha62/vicin-packages) then:
579
541
 
580
542
  ```bash
581
- npm install
582
- npm run size
543
+ pnpm install
544
+ pnpm run size --filter @vicin/sigil
583
545
  ```
584
546
 
585
547
  ---
586
548
 
587
549
  ## Tests
588
550
 
589
- Reliability is a core pillar of `Sigil`. The library is backed by a comprehensive suite of unit tests ( 71 total tests ) that cover everything from basic mixins to edge cases.
590
-
591
551
  **Coverage Status**
592
552
 
593
553
  We maintain **100%** test coverage across the entire codebase to ensure that runtime metadata remains consistent and predictable.
594
554
 
595
- | Metric | Score |
596
- | ------ | ----- |
597
- | Stmts | 100% |
598
- | Branch | 100% |
599
- | Funcs | 100% |
600
- | Lines | 100% |
601
-
602
- **Key Test Areas**
603
-
604
- - **Mixins, Attach function & decorator:** Validating `Sigilify`, `AttachSigil` and `attachSigil` behaviors.
605
- - **Sigil methods:** Ensuring `Sigil` class methods (e.g. `SigilLabel`, `getSigilLabel`) work as expected.
606
- - **Lazy Evaluation:** Ensuring metadata is attached before being accessed via `Sigil` methods even when no attach function or decorator is used.
607
- - **Lineage:** Verifying that `isOfType` and `isExactType` work across complex inheritance chains.
608
- - **Error Handling:** Strict validation for all errors and throws.
609
- - **Edge cases**: Known edge cases.
555
+ | Metric | Score |
556
+ | ------ | ------ |
557
+ | Stmts | 96.96% |
558
+ | Branch | 100% |
559
+ | Funcs | 100% |
560
+ | Lines | 100% |
610
561
 
611
562
  **Running Tests**
612
563
 
613
- To run the test suite locally and generate a coverage report, fetch source code from [github](https://github.com/ZiadTaha62/sigil) then:
564
+ To run the test suite locally and generate a coverage report, fetch source code from [github](https://github.com/ZiadTaha62/vicin-packages) then:
614
565
 
615
566
  ```bash
616
- npm install
617
- npm run test:unit
567
+ pnpm install
568
+ pnpm run test --filter @vicin/sigil
618
569
  ```
619
570
 
620
571
  ---