@vicin/sigil 3.2.1 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -1
- package/README.md +140 -73
- package/dist/index.d.mts +66 -58
- package/dist/index.d.ts +66 -58
- package/dist/index.global.js +105 -235
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +105 -235
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +104 -236
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [3.4.0] - 2026-02-28
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Updated internal logic to handle edge-cases
|
|
10
|
+
- Edge cases part added to tests
|
|
11
|
+
- Updated README.md
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `AttachSigil` / `attachSigil` which are `WithSigil` / `withSigil` renamed for clarity
|
|
16
|
+
|
|
17
|
+
### Deprecated
|
|
18
|
+
|
|
19
|
+
- `WithSigil` / `withSigil` renamed for clarity. old names will be removed in v4
|
|
20
|
+
- `SigilLabelSet` / `getSigilLabelSet` methods to minimize api surface and bundle size as they are redundant (internall 'new Set(this.SigilLabelLineage)' only). will be removed in v4
|
|
21
|
+
|
|
22
|
+
## [3.3.0] - 2026-02-27
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- Label registry now stores user defined labels only, and `getSigilLabels` has no `includeAuto` argument any more
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- Options `skipLabelUniquenessCheck` is added to avoid false-positive HMR throws
|
|
31
|
+
|
|
5
32
|
## [3.2.1] - 2026-02-27
|
|
6
33
|
|
|
7
34
|
### Changed
|
|
@@ -17,7 +44,7 @@ All notable changes to this project will be documented in this file.
|
|
|
17
44
|
|
|
18
45
|
### Deprecated
|
|
19
46
|
|
|
20
|
-
- `DEFAULT_LABEL_REGEX` is deprecated
|
|
47
|
+
- `DEFAULT_LABEL_REGEX` is deprecated use `RECOMMENDED_LABEL_REGEX` instead, will be removed in v4
|
|
21
48
|
|
|
22
49
|
## [3.1.4] - 2026-02-26
|
|
23
50
|
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
## Features
|
|
11
11
|
|
|
12
|
-
- ✅ **Drop-in `instanceof` replacement** that works across bundles and monorepos,
|
|
12
|
+
- ✅ **Drop-in `instanceof` replacement** that works across bundles, HMR and monorepos, and adds an **exact-class-instance check**
|
|
13
13
|
- ✅ **Simple nominal typing** with just one line of code for each class (e.g., `UserId` vs. `PostId`)
|
|
14
14
|
- ✅ **Tiny less than 1.6 KB minified and brotlied** measured using [size-limit](https://www.npmjs.com/package/size-limit)
|
|
15
15
|
- ✅ **Performant as native instanceof** but with guaranteed checks
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
- [Install](#install)
|
|
25
25
|
- [Basic usage](#basic-usage)
|
|
26
26
|
- [Decorator pattern](#decorator-pattern)
|
|
27
|
-
- [
|
|
27
|
+
- [Function pattern](#function-pattern)
|
|
28
28
|
- [Migration](#migration)
|
|
29
29
|
- [Core concepts](#core-concepts)
|
|
30
30
|
- [Terminology](#terminology)
|
|
@@ -36,6 +36,8 @@
|
|
|
36
36
|
- [Options & configuration](#options--configuration)
|
|
37
37
|
- [Minimal mode](#minimal-mode)
|
|
38
38
|
- [Strict mode](#strict-mode)
|
|
39
|
+
- [Hot module reload](#hot-module-reload)
|
|
40
|
+
- [Edge cases](#edge-cases)
|
|
39
41
|
- [Benchmarks](#benchmarks)
|
|
40
42
|
- [Bundle Size](#bundle-size)
|
|
41
43
|
- [Tests](#tests)
|
|
@@ -57,7 +59,9 @@ yarn add @vicin/sigil
|
|
|
57
59
|
pnpm add @vicin/sigil
|
|
58
60
|
```
|
|
59
61
|
|
|
60
|
-
Requires TypeScript 5.0+ for decorators;
|
|
62
|
+
Requires TypeScript 5.0+ for decorators; attach functions work on older versions. Node.js 18+ recommended.
|
|
63
|
+
|
|
64
|
+
> No tsconfig changes are needed as we use **Stage 3 decorators** which are supported by default in TypeScript 5.0+
|
|
61
65
|
|
|
62
66
|
### Basic usage
|
|
63
67
|
|
|
@@ -91,26 +95,28 @@ After opting into the `Sigil` contract, labels are passed to child classes to un
|
|
|
91
95
|
|
|
92
96
|
##### Decorator pattern
|
|
93
97
|
|
|
94
|
-
Apply a label with the `@
|
|
98
|
+
Apply a label with the `@AttachSigil` decorator:
|
|
95
99
|
|
|
96
100
|
```ts
|
|
97
|
-
import { Sigil,
|
|
101
|
+
import { Sigil, AttachSigil } from '@vicin/sigil';
|
|
98
102
|
|
|
99
|
-
@
|
|
103
|
+
@AttachSigil('@myorg/mypkg.User')
|
|
100
104
|
class User extends Sigil {}
|
|
101
105
|
```
|
|
102
106
|
|
|
103
|
-
#####
|
|
107
|
+
##### Function pattern
|
|
104
108
|
|
|
105
|
-
Apply a label using `
|
|
109
|
+
Apply a label using `attachSigil` function:
|
|
106
110
|
|
|
107
111
|
```ts
|
|
108
|
-
import { Sigil,
|
|
112
|
+
import { Sigil, attachSigil } from '@vicin/sigil';
|
|
109
113
|
|
|
110
|
-
class
|
|
111
|
-
|
|
114
|
+
class User extends Sigil {}
|
|
115
|
+
attachSigil(User, '@myorg/mypkg.User');
|
|
112
116
|
```
|
|
113
117
|
|
|
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
|
+
|
|
114
120
|
### Migration
|
|
115
121
|
|
|
116
122
|
Migrating old code into `Sigil` can be done with extra couple lines of code only:
|
|
@@ -149,12 +155,12 @@ Congratulations — you’ve opted into `Sigil` and you can start replacing `ins
|
|
|
149
155
|
|
|
150
156
|
### Purpose and Origins
|
|
151
157
|
|
|
152
|
-
Sigil addresses issues in large monorepos
|
|
158
|
+
Sigil addresses issues in large monorepos, HMR:
|
|
153
159
|
|
|
154
160
|
- **Unreliable `instanceof`:** Bundling cause class redefinitions, breaking checks.
|
|
155
161
|
|
|
156
162
|
```ts
|
|
157
|
-
// Can be broken in monorepo
|
|
163
|
+
// Can be broken in monorepo or HMR set-ups
|
|
158
164
|
if (obj instanceof User) { ... }
|
|
159
165
|
|
|
160
166
|
// With Sigil
|
|
@@ -162,7 +168,9 @@ if (User.isOfType(obj)) { ... } // This still works even if User was bundled twi
|
|
|
162
168
|
if (User.isExactType(obj)) { ... } // Or check for exactly same constructor not its children
|
|
163
169
|
```
|
|
164
170
|
|
|
165
|
-
|
|
171
|
+
Also by utilizing unique passed labels it solves another problem in Domain-Driven Design (DDD):
|
|
172
|
+
|
|
173
|
+
- **Manual Branding Overhead:** Custom identifiers lead to boilerplate and maintenance issues, `Sigil` adds reliable inheritance-aware nominal branding with just one line of code.
|
|
166
174
|
|
|
167
175
|
```ts
|
|
168
176
|
import { sigil } from '@vicin/sigil';
|
|
@@ -175,48 +183,43 @@ type test1 = User extends Sigil ? true : false; // true
|
|
|
175
183
|
type test2 = Sigil extends User ? true : false; // false
|
|
176
184
|
```
|
|
177
185
|
|
|
178
|
-
`Sigil` makes identity management **explicit** and **error-resistant** if defined the right way.
|
|
179
|
-
|
|
180
186
|
### Implementation Mechanics
|
|
181
187
|
|
|
182
188
|
- **Runtime Contract:** Established via extending `Sigil` or using `Sigilify` mixin.
|
|
183
|
-
- **Update metadata:** With each new child, use
|
|
189
|
+
- **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.
|
|
184
190
|
|
|
185
191
|
```ts
|
|
186
|
-
import { Sigil,
|
|
192
|
+
import { Sigil, AttachSigil, sigil, ExtendSigil } from '@vicin/sigil';
|
|
187
193
|
|
|
188
|
-
@
|
|
194
|
+
@AttachSigil('@scope/package.MyClass') // <-- Run-time values update
|
|
189
195
|
class MyClass extends Sigil {
|
|
190
196
|
declare [sigil]: ExtendSigil<'MyClass', Sigil>; // <-- compile-time type update
|
|
191
197
|
}
|
|
192
198
|
```
|
|
193
199
|
|
|
194
|
-
You can avoid decorators and use
|
|
200
|
+
You can avoid decorators and use normal functions if needed:
|
|
195
201
|
|
|
196
202
|
```ts
|
|
197
|
-
import { Sigil,
|
|
203
|
+
import { Sigil, attachSigil, sigil, ExtendSigil } from '@vicin/sigil';
|
|
198
204
|
|
|
199
|
-
class
|
|
205
|
+
class MyClass extends Sigil {
|
|
200
206
|
declare [sigil]: ExtendSigil<'MyClass', Sigil>;
|
|
201
207
|
}
|
|
202
208
|
|
|
203
|
-
|
|
204
|
-
type MyClass = InstanceType<typeof MyClass>;
|
|
209
|
+
attachSigil(MyClass, '@scope/package.MyClass');
|
|
205
210
|
```
|
|
206
211
|
|
|
207
|
-
Note that you can't use `InstanceType` on `private` or `protected` classes, however you can use `GetPrototype<T>` in such cases.
|
|
208
|
-
|
|
209
212
|
### Example
|
|
210
213
|
|
|
211
214
|
```ts
|
|
212
|
-
import { Sigil,
|
|
215
|
+
import { Sigil, AttachSigil } from '@vicin/sigil';
|
|
213
216
|
|
|
214
|
-
@
|
|
217
|
+
@AttachSigil('@myorg/User')
|
|
215
218
|
class User extends Sigil {
|
|
216
219
|
declare [sigil]: ExtendSigil<'User', Sigil>;
|
|
217
220
|
}
|
|
218
221
|
|
|
219
|
-
@
|
|
222
|
+
@AttachSigil('@myorg/Admin')
|
|
220
223
|
class Admin extends User {
|
|
221
224
|
declare [sigil]: ExtendSigil<'Admin', User>;
|
|
222
225
|
}
|
|
@@ -247,51 +250,40 @@ type test1 = Admin extends User ? true : false; // true
|
|
|
247
250
|
type test2 = User extends Admin ? true : false; // false
|
|
248
251
|
|
|
249
252
|
// Passed label must be unique (enforced by Sigil) so can be used as stable Id for class
|
|
250
|
-
// Also 'SigilLabelLineage'
|
|
253
|
+
// Also 'SigilLabelLineage' is useful for logging & debugging
|
|
251
254
|
console.log(Admin.SigilLabel); // '@myorg/Admin'
|
|
252
255
|
console.log(Admin.SigilEffectiveLabel); // '@myorg/Admin'
|
|
253
256
|
console.log(Admin.SigilLabelLineage); // ['Sigil', '@myorg/User', '@myorg/Admin']
|
|
254
|
-
console.log(Admin.SigilLabelSet); // Set(['Sigil', '@myorg/User', '@myorg/Admin'])
|
|
255
257
|
console.log(admin.getSigilLabel()); // '@myorg/Admin'
|
|
256
258
|
console.log(admin.getSigilEffectiveLabel()); // '@myorg/Admin'
|
|
257
259
|
console.log(admin.getSigilLabelLineage()); // ['Sigil', '@myorg/User', '@myorg/Admin']
|
|
258
|
-
console.log(admin.getSigilLabelSet()); // Set(['Sigil', '@myorg/User', '@myorg/Admin'])
|
|
259
260
|
```
|
|
260
261
|
|
|
261
262
|
### Errors & throws
|
|
262
263
|
|
|
263
264
|
Run-time errors that can be thrown by `Sigil`:
|
|
264
265
|
|
|
265
|
-
#### Double
|
|
266
|
+
#### Double Sigilify
|
|
266
267
|
|
|
267
268
|
```ts
|
|
268
269
|
class A {}
|
|
269
|
-
|
|
270
|
-
const C = Sigilify(B, 'B'); // Throws: [Sigil Error] Class 'Sigilified' with label 'A' is already sigilified
|
|
270
|
+
Sigilify(Sigilify(A, 'A'), 'B'); // Throws: [Sigil Error] Class 'Sigilified' with label 'A' is already sigilified
|
|
271
271
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
#### `@WithSigil() / withSigil()` on non-Sigil class
|
|
278
|
-
|
|
279
|
-
```ts
|
|
280
|
-
@WithSigil('A')
|
|
281
|
-
class A {} // Throws: [Sigil Error] 'WithSigil' decorator accept only Sigil classes but used on class 'A'
|
|
272
|
+
@AttachSigil('B')
|
|
273
|
+
@AttachSigil('A')
|
|
274
|
+
class A extends Sigil {} // Throws: [Sigil Error] Class 'A' with label 'A' is already sigilified
|
|
282
275
|
|
|
283
|
-
|
|
276
|
+
class A extends Sigil {}
|
|
277
|
+
attachSigil(attachSigil(A, 'A'), 'B'); // Throws: [Sigil Error] Class 'A' with label 'A' is already sigilified
|
|
284
278
|
```
|
|
285
279
|
|
|
286
|
-
####
|
|
280
|
+
#### `@AttachSigil() / attachSigil()` on non-Sigil class
|
|
287
281
|
|
|
288
282
|
```ts
|
|
289
|
-
@
|
|
290
|
-
|
|
291
|
-
class A extends Sigil {} // Throws: [Sigil Error] Class 'A' with label 'A' is already sigilified
|
|
283
|
+
@AttachSigil('A') // Throws: [Sigil Error] 'AttachSigil' decorator accept only Sigil classes but used on class 'A'
|
|
284
|
+
class A {}
|
|
292
285
|
|
|
293
|
-
class
|
|
294
|
-
withSigil(withSigil(_A, 'A'), 'B'); // Throws: [Sigil Error] Class 'A' with label 'A' is already sigilified
|
|
286
|
+
attachSigil(class A {}); // Throws: [Sigil Error] 'AttachSigil' function accept only Sigil classes but used on class 'A'
|
|
295
287
|
```
|
|
296
288
|
|
|
297
289
|
#### No label is passed with `autofillLabels: false`
|
|
@@ -306,10 +298,10 @@ new A(); // Throws: [Sigil Error] Class 'A' is not sigilified, Make sure to sigi
|
|
|
306
298
|
#### Same label is passed twice to `Sigil`
|
|
307
299
|
|
|
308
300
|
```ts
|
|
309
|
-
@
|
|
301
|
+
@AttachSigil('Label')
|
|
310
302
|
class A extends Sigil {}
|
|
311
303
|
|
|
312
|
-
@
|
|
304
|
+
@AttachSigil('Label')
|
|
313
305
|
class B extends Sigil {} // Throws: [Sigil Error] Passed label 'Label' to class 'B' is re-used, passed labels must be unique
|
|
314
306
|
```
|
|
315
307
|
|
|
@@ -318,15 +310,15 @@ class B extends Sigil {} // Throws: [Sigil Error] Passed label 'Label' to class
|
|
|
318
310
|
```ts
|
|
319
311
|
updateSigilOptions({ labelValidation: RECOMMENDED_LABEL_REGEX });
|
|
320
312
|
|
|
321
|
-
@
|
|
313
|
+
@AttachSigil('InvalidLabel')
|
|
322
314
|
class A extends Sigil {} // Throws: [Sigil Error] Invalid Sigil label 'InvalidLabel'. Make sure that supplied label matches validation regex or function
|
|
323
315
|
```
|
|
324
316
|
|
|
325
317
|
#### Using '@Sigil-auto' prefix
|
|
326
318
|
|
|
327
319
|
```ts
|
|
328
|
-
@
|
|
329
|
-
class X extends Sigil {} // Throws: '@Sigil-auto' is a
|
|
320
|
+
@AttachSigil('@Sigil-auto:label')
|
|
321
|
+
class X extends Sigil {} // Throws: '@Sigil-auto' is a prefix reserved by the library
|
|
330
322
|
```
|
|
331
323
|
|
|
332
324
|
#### Invalid options passed to `updateOptions`
|
|
@@ -334,6 +326,7 @@ class X extends Sigil {} // Throws: '@Sigil-auto' is a prefex reserved by the li
|
|
|
334
326
|
```ts
|
|
335
327
|
updateSigilOptions({ autofillLabels: {} as any }); // Throws: 'updateSigilOptions.autofillLabels' must be boolean
|
|
336
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
|
|
337
330
|
```
|
|
338
331
|
|
|
339
332
|
---
|
|
@@ -351,15 +344,15 @@ updateSigilOptions({ labelValidation: 123 as any }); // Throws: 'updateSigilOpti
|
|
|
351
344
|
- `SigilError`
|
|
352
345
|
|
|
353
346
|
- **Decorator:**
|
|
354
|
-
- `
|
|
347
|
+
- `AttachSigil(label, opts?)`
|
|
355
348
|
|
|
356
|
-
- **
|
|
357
|
-
- `
|
|
349
|
+
- **Attach function:**
|
|
350
|
+
- `attachSigil(Class, label, opts?)`
|
|
358
351
|
|
|
359
352
|
- **Helpers:**
|
|
360
353
|
- `isSigilCtor(ctor)`
|
|
361
354
|
- `isSigilInstance(inst)`
|
|
362
|
-
- `getSigilLabels(
|
|
355
|
+
- `getSigilLabels()`
|
|
363
356
|
|
|
364
357
|
- **Options:**
|
|
365
358
|
- `updateSigilOptions(opts)`
|
|
@@ -380,11 +373,11 @@ updateSigilOptions({ labelValidation: 123 as any }); // Throws: 'updateSigilOpti
|
|
|
380
373
|
- `SigilError`: an `Error` class decorated with a `Sigil` so it can be identified at runtime.
|
|
381
374
|
- `Sigilify(Base, label, opts?)`: mixin function that returns a new constructor with `Sigil` types and instance helpers.
|
|
382
375
|
- `SigilifyAbstract(Base, label, opts?)`: Same as `Sigilify` but for abstract classes.
|
|
383
|
-
- `
|
|
384
|
-
- `
|
|
376
|
+
- `AttachSigil(label, opts?)`: class decorator that attaches `Sigil` metadata at declaration time.
|
|
377
|
+
- `attachSigil(Class, label, opts?)`: function that validates and decorates an existing class constructor.
|
|
385
378
|
- `isSigilCtor(value)`: `true` if `value` is a `Sigil` constructor.
|
|
386
379
|
- `isSigilInstance(value)`: `true` if `value` is an instance of a `Sigil` constructor.
|
|
387
|
-
- `getSigilLabels(
|
|
380
|
+
- `getSigilLabels()`: Get `Sigil` labels registered.
|
|
388
381
|
- `updateSigilOptions(opts)`: change global runtime options of `Sigil` library (e.g., `autofillLabels`).
|
|
389
382
|
- `RECOMMENDED_LABEL_REGEX`: regex that ensures structure of `@scope/package.ClassName` to all labels, it's advised to use it as your `SigilOptions.labelValidation`
|
|
390
383
|
|
|
@@ -395,7 +388,6 @@ When a constructor is sigilified it will expose the following **static** getters
|
|
|
395
388
|
- `SigilLabel` — the identity label string.
|
|
396
389
|
- `SigilEffectiveLabel` — the human label string.
|
|
397
390
|
- `SigilLabelLineage` — readonly array of labels representing parent → child for debugging.
|
|
398
|
-
- `SigilLabelSet` — readonly `Set<string>` of sigil labels for debugging.
|
|
399
391
|
- `isOfType(other)` — check if other is an instance of this constructor or its children.
|
|
400
392
|
- `isExactType(other) `— check if other is an instance exactly this constructor.
|
|
401
393
|
|
|
@@ -404,7 +396,6 @@ Instances of sigilified classes expose instance helpers:
|
|
|
404
396
|
- `getSigilLabel()` — returns the identity label.
|
|
405
397
|
- `getSigilEffectiveLabel()` — returns the human label.
|
|
406
398
|
- `getSigilLabelLineage()` — returns lineage array.
|
|
407
|
-
- `getSigilLabelSet()` — returns readonly Set.
|
|
408
399
|
- `isOfType(other)` — check if other is an instance of the same class or its children as this.
|
|
409
400
|
- `isExactType(other) `— check if other is an instance exactly the same constructor.
|
|
410
401
|
|
|
@@ -420,21 +411,22 @@ import { updateSigilOptions } from '@vicin/sigil';
|
|
|
420
411
|
updateSigilOptions({
|
|
421
412
|
autofillLabels: true, // Automatically label unlabeled subclasses
|
|
422
413
|
labelValidation: null, // Function or regex, Enforce label format
|
|
414
|
+
skipLabelUniquenessCheck: false, // Skip uniqueness check for labels, should be used in HMR set-ups only
|
|
423
415
|
});
|
|
424
416
|
```
|
|
425
417
|
|
|
426
|
-
Values defined in previous example are defaults, per-class overrides available in mixin
|
|
418
|
+
Values defined in previous example are defaults, per-class overrides available in mixin and attach function / decorator.
|
|
427
419
|
|
|
428
420
|
---
|
|
429
421
|
|
|
430
422
|
## Minimal mode
|
|
431
423
|
|
|
432
|
-
You can ignore all decorators and
|
|
424
|
+
By default `Sigil` works with minimal mode, You can ignore all decorators and functions and just make base class extend `Sigil`:
|
|
433
425
|
|
|
434
426
|
```ts
|
|
435
427
|
import { Sigil, updateSigilOptions } from '@vicin/sigil';
|
|
436
428
|
|
|
437
|
-
// No decorators or
|
|
429
|
+
// No decorators or functions needed to use 'isOfType' ('instanceof' replacement)
|
|
438
430
|
class A extends Sigil {}
|
|
439
431
|
class B extends A {}
|
|
440
432
|
class C extends B {}
|
|
@@ -454,6 +446,80 @@ Now if you forgot to pass a label error is thrown at the moment you create class
|
|
|
454
446
|
|
|
455
447
|
---
|
|
456
448
|
|
|
449
|
+
## Hot module reload
|
|
450
|
+
|
|
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:
|
|
453
|
+
|
|
454
|
+
```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:
|
|
462
|
+
|
|
463
|
+
```ts
|
|
464
|
+
@AttachSigil('HmrClassLabel', { skipLabelUniquenessCheck: true })
|
|
465
|
+
class HmrClass extends Sigil {}
|
|
466
|
+
```
|
|
467
|
+
|
|
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
|
|
475
|
+
|
|
476
|
+
If you didn't make sure that `attachSigil` runs right after class declaration and used one of `Sigil` methods this will occur:
|
|
477
|
+
|
|
478
|
+
```ts
|
|
479
|
+
class A extends Sigil {}
|
|
480
|
+
|
|
481
|
+
console.log(A.SigilLabel); // returns auto-generated label (e.g. @Sigil-auto:A:6:a3f15bhl) or throws in strict mode
|
|
482
|
+
|
|
483
|
+
attachSigil(A, 'A');
|
|
484
|
+
|
|
485
|
+
console.log(A.SigilLabel); // A
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
To avoid this bug entirely you can use the return of `attachSigil` in your code so you are enforced to respect order:
|
|
489
|
+
|
|
490
|
+
```ts
|
|
491
|
+
class _A extends Sigil {}
|
|
492
|
+
|
|
493
|
+
const A = attachSigil(_A, 'A');
|
|
494
|
+
type A = InstanceType<typeof A>;
|
|
495
|
+
|
|
496
|
+
console.log(A.SigilLabel); // A
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
Note that you can't use `InstanceType` on `private` or `protected` classes, however you can use `GetPrototype<T>` in such cases.
|
|
500
|
+
|
|
501
|
+
#### Static blocks & IIFE static initializer
|
|
502
|
+
|
|
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:
|
|
504
|
+
|
|
505
|
+
```ts
|
|
506
|
+
class A extends Sigil {
|
|
507
|
+
static IIFE = (() => {
|
|
508
|
+
const label = A.SigilLabel; // returns auto-generated label (e.g. @Sigil-auto:A:6:a3f15bhl) or throws in strict mode
|
|
509
|
+
})();
|
|
510
|
+
|
|
511
|
+
static {
|
|
512
|
+
const label = this.SigilLabel; // returns auto-generated label (e.g. @Sigil-auto:A:6:a3f15bhl) or throws in strict mode
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
attachSigil(A, 'A');
|
|
517
|
+
```
|
|
518
|
+
|
|
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
|
+
---
|
|
522
|
+
|
|
457
523
|
## Benchmarks
|
|
458
524
|
|
|
459
525
|
Sigil is built for **real-world performance**. Below are the latest micro-benchmark results (run on **Node.js v20.12.0**).
|
|
@@ -503,7 +569,7 @@ npm run bench
|
|
|
503
569
|
|
|
504
570
|
## Bundle Size
|
|
505
571
|
|
|
506
|
-
**Less than 1.6 KB (1.
|
|
572
|
+
**Less than 1.6 KB (1.52 KB)** (minified + Brotli, including all dependencies)
|
|
507
573
|
|
|
508
574
|
This makes Sigil one of the smallest full-featured solutions for nominal typing + reliable runtime identity.
|
|
509
575
|
|
|
@@ -520,7 +586,7 @@ npm run size
|
|
|
520
586
|
|
|
521
587
|
## Tests
|
|
522
588
|
|
|
523
|
-
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
|
|
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.
|
|
524
590
|
|
|
525
591
|
**Coverage Status**
|
|
526
592
|
|
|
@@ -535,11 +601,12 @@ We maintain **100%** test coverage across the entire codebase to ensure that run
|
|
|
535
601
|
|
|
536
602
|
**Key Test Areas**
|
|
537
603
|
|
|
538
|
-
- **Mixins,
|
|
604
|
+
- **Mixins, Attach function & decorator:** Validating `Sigilify`, `AttachSigil` and `attachSigil` behaviors.
|
|
539
605
|
- **Sigil methods:** Ensuring `Sigil` class methods (e.g. `SigilLabel`, `getSigilLabel`) work as expected.
|
|
540
|
-
- **Lazy Evaluation:** Ensuring metadata is attached before being accessed via `Sigil` methods.
|
|
606
|
+
- **Lazy Evaluation:** Ensuring metadata is attached before being accessed via `Sigil` methods even when no attach function or decorator is used.
|
|
541
607
|
- **Lineage:** Verifying that `isOfType` and `isExactType` work across complex inheritance chains.
|
|
542
608
|
- **Error Handling:** Strict validation for all errors and throws.
|
|
609
|
+
- **Edge cases**: Known edge cases.
|
|
543
610
|
|
|
544
611
|
**Running Tests**
|
|
545
612
|
|