@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/CHANGELOG.md +7 -249
- package/CONTRIBUTING.md +24 -0
- package/LICENSE +3 -1
- package/README.md +140 -189
- package/dist/index.cjs +298 -0
- package/dist/index.cjs.map +1 -0
- package/dist/{index.d.mts → index.d.cts} +67 -242
- package/dist/index.d.ts +67 -242
- package/dist/index.global.js +116 -207
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +115 -218
- package/dist/index.js.map +1 -1
- package/package.json +43 -59
- package/dist/index.mjs +0 -374
- package/dist/index.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
# Sigil
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@vicin/sigil) [](https://www.npmjs.com/package/@vicin/sigil) [](LICENSE)  [](https://www.npmjs.com/package/@vicin/sigil) [](https://www.npmjs.com/package/@vicin/sigil) [](LICENSE)  [](https://github.com/ZiadTaha62/vicin-packages/actions/workflows/ci.yml) 
|
|
4
4
|
|
|
5
|
-
> - 🎉
|
|
5
|
+
> - 🎉 v4.0.0 is out! Happy coding! 😄💻
|
|
6
6
|
> - 📄 **Changelog:** [CHANGELOG.md](./CHANGELOG.md)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
**Sigil** — bulletproof class identity for large TypeScript projects.
|
|
9
9
|
|
|
10
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
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
|
|
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
|
-
- **
|
|
150
|
-
- **
|
|
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
|
|
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.
|
|
168
|
-
if (User.
|
|
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.
|
|
232
|
-
console.log(Admin.
|
|
233
|
-
console.log(User.
|
|
234
|
-
console.log(User.
|
|
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.
|
|
238
|
-
console.log(Admin.
|
|
239
|
-
console.log(User.
|
|
240
|
-
console.log(User.
|
|
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.
|
|
244
|
-
console.log(user.
|
|
245
|
-
console.log(admin.
|
|
246
|
-
console.log(user.
|
|
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.
|
|
258
|
-
console.log(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
|
-
####
|
|
301
|
+
#### Attach sigil to parent after child
|
|
318
302
|
|
|
319
303
|
```ts
|
|
320
|
-
|
|
321
|
-
class
|
|
322
|
-
```
|
|
304
|
+
class Parent extends Sigil {}
|
|
305
|
+
class Child extends Parent {}
|
|
323
306
|
|
|
324
|
-
|
|
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
|
-
- `
|
|
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
|
-
- `
|
|
381
|
-
- `updateSigilOptions(opts)`: change global runtime options of `Sigil` library (e.g., `
|
|
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
|
-
- `
|
|
392
|
-
- `
|
|
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
|
-
- `
|
|
397
|
-
- `
|
|
398
|
-
- `
|
|
399
|
-
- `
|
|
400
|
-
- `
|
|
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
|
-
##
|
|
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
|
-
|
|
398
|
+
### Attach sigil to parent after child
|
|
450
399
|
|
|
451
|
-
|
|
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
|
-
|
|
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
|
-
|
|
464
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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/
|
|
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
|
-
|
|
533
|
-
|
|
465
|
+
pnpm install
|
|
466
|
+
pnpm run bench --filter @vicin/sigil
|
|
534
467
|
```
|
|
535
468
|
|
|
536
469
|
### 1. Runtime Type Checking
|
|
537
470
|
|
|
538
|
-
|
|
|
539
|
-
|
|
|
540
|
-
|
|
|
541
|
-
|
|
|
542
|
-
|
|
|
543
|
-
|
|
|
544
|
-
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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
|
-
|
|
|
552
|
-
|
|
|
553
|
-
|
|
|
554
|
-
| Empty Sigil
|
|
555
|
-
| Small (
|
|
556
|
-
| Large (
|
|
557
|
-
|
|
|
558
|
-
|
|
|
559
|
-
|
|
|
560
|
-
|
|
|
561
|
-
|
|
|
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
|
-
> -
|
|
566
|
-
>
|
|
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
|
|
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/
|
|
540
|
+
To verify bundle size fetch source code from [github](https://github.com/ZiadTaha62/vicin-packages) then:
|
|
579
541
|
|
|
580
542
|
```bash
|
|
581
|
-
|
|
582
|
-
|
|
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 |
|
|
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/
|
|
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
|
-
|
|
617
|
-
|
|
567
|
+
pnpm install
|
|
568
|
+
pnpm run test --filter @vicin/sigil
|
|
618
569
|
```
|
|
619
570
|
|
|
620
571
|
---
|