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