@signal-kernel/core 0.1.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/LICENSE +21 -0
- package/README.md +331 -0
- package/dist/index.cjs +296 -0
- package/dist/index.d.cts +29 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +265 -0
- package/package.json +37 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 LucianoLee
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# `@signal-kernel/core`
|
|
2
|
+
### A minimal, deterministic, fine-grained reactivity engine.
|
|
3
|
+
|
|
4
|
+
`@signal-kernel/core` provides the foundational primitives for building reactive systems:
|
|
5
|
+
signals, computed values, effects, a deterministic scheduler, and a dependency graph.
|
|
6
|
+
|
|
7
|
+
This package is framework-agnostic and has no DOM or framework dependencies, making it suitable for:
|
|
8
|
+
- UI frameworks (via adapters)
|
|
9
|
+
- Server-side reactive pipelines
|
|
10
|
+
- Async dataflow runtimes
|
|
11
|
+
- State machines & reactive graphs
|
|
12
|
+
- Compiler-driven reactivity transforms
|
|
13
|
+
|
|
14
|
+
It is the core runtime powering the broader Signal Kernel ecosystem.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# Installation
|
|
19
|
+
```bash
|
|
20
|
+
npm install @signal-kernel/core
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Overview
|
|
26
|
+
`@signal-kernel/core` implements a modern fine-grained reactive graph, consisting of:
|
|
27
|
+
|
|
28
|
+
1. **`signal()`**
|
|
29
|
+
Mutable state unit, with dependency tracking and change propagation.
|
|
30
|
+
2. **`computed()`**
|
|
31
|
+
Lazy, memoized derivations with automatic dependency tracking.
|
|
32
|
+
3. **`createEffect()`**
|
|
33
|
+
Reactive side-effects with cleanup, disposal, and deterministic scheduling.
|
|
34
|
+
4. **`batch()`**
|
|
35
|
+
Coalesces multiple updates into a single scheduler flush.
|
|
36
|
+
5. **Deterministic Scheduler**
|
|
37
|
+
A two-phase flush model that first stabilizes computed nodes, then executes effects.
|
|
38
|
+
|
|
39
|
+
All primitives are minimal yet expressive enough to build a complete reactivity system or compose higher-level runtimes (e.g., async pipelines).
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
# Public API
|
|
44
|
+
```ts
|
|
45
|
+
export { signal } from "./signal.js";
|
|
46
|
+
export { computed } from "./computed.js";
|
|
47
|
+
export { createEffect, onCleanup } from "./effect.js";
|
|
48
|
+
export { batch } from "./scheduler.js";
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
*(Internal APIs such as `atomic()`, `transaction()`, and `flushSync()` are intentionally not exported at this stage.)*
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
---------------------------------------------------------
|
|
55
|
+
# 1. `signal()`
|
|
56
|
+
---------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
### Signature
|
|
59
|
+
```ts
|
|
60
|
+
const { get, set, subscribe, peek } = signal<T>(initial, equals?);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Behavior
|
|
64
|
+
`get()`
|
|
65
|
+
- Returns the current value.
|
|
66
|
+
- If called within an active observer, registers a dependency via track().
|
|
67
|
+
|
|
68
|
+
`set(next)`
|
|
69
|
+
- Updates the stored value.
|
|
70
|
+
- Uses a customizable equality comparator to prevent unnecessary propagation.
|
|
71
|
+
- Notifies downstream:
|
|
72
|
+
- computed nodes → marked stale
|
|
73
|
+
- effects → scheduled for execution
|
|
74
|
+
|
|
75
|
+
`peek()`
|
|
76
|
+
- Reads value without tracking dependencies.
|
|
77
|
+
|
|
78
|
+
`subscribe(node)`
|
|
79
|
+
Manually links an observer to the signal.
|
|
80
|
+
Useful for framework adapters (React/Vue/Solid) that manage their own lifecycle.
|
|
81
|
+
|
|
82
|
+
**Key properties**
|
|
83
|
+
- Signals **cannot depend** on others (enforced by runtime errors).
|
|
84
|
+
- They are the leaf nodes of the reactive graph.
|
|
85
|
+
- Changes propagate deterministically through the scheduler.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
---------------------------------------------------------
|
|
90
|
+
# 2. `computed()`
|
|
91
|
+
---------------------------------------------------------
|
|
92
|
+
### Signature
|
|
93
|
+
```ts
|
|
94
|
+
const { get, peek, dispose } = computed<T>(fn, equals?);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Behavior
|
|
98
|
+
##### Lazy evaluation
|
|
99
|
+
Computed values only evaluate when `get()` is called.
|
|
100
|
+
If the node is stale—or has never computed before—it runs its `fn()` and updates dependencies.
|
|
101
|
+
|
|
102
|
+
##### Automatic dependency tracking
|
|
103
|
+
During execution, all accessed signals/computeds are recorded via `track()`.
|
|
104
|
+
|
|
105
|
+
##### Memoization & equality
|
|
106
|
+
A computed node only updates its value when:
|
|
107
|
+
```rs
|
|
108
|
+
!equals(prev, next)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
##### Stale propagation
|
|
112
|
+
When an upstream signal or computed changes:
|
|
113
|
+
```ts
|
|
114
|
+
computed.stale = true;
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Downstream effects are scheduled, but computation still occurs lazily.
|
|
118
|
+
|
|
119
|
+
##### Cycle detection
|
|
120
|
+
If a computed re-enters itself:
|
|
121
|
+
```ts
|
|
122
|
+
throw new Error("Cycle detected in computed");
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`dispose()`
|
|
126
|
+
Fully removes the node from the graph:
|
|
127
|
+
- Unlinks all deps and subs
|
|
128
|
+
- Clears cached value
|
|
129
|
+
- Leaves the graph consistent
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
---------------------------------------------------------
|
|
134
|
+
# 3. `createEffect()`
|
|
135
|
+
---------------------------------------------------------
|
|
136
|
+
Effects are the imperative “side-effect” layer of the reactive system.
|
|
137
|
+
|
|
138
|
+
### Signature
|
|
139
|
+
```ts
|
|
140
|
+
const dispose = createEffect(() => {
|
|
141
|
+
// ...
|
|
142
|
+
return () => cleanup();
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Behavior
|
|
147
|
+
|
|
148
|
+
##### Automatic dependency tracking
|
|
149
|
+
During effect execution, reads from signals or computed values create graph edges via `track()`.
|
|
150
|
+
|
|
151
|
+
##### Cleanup handling
|
|
152
|
+
Before each re-run:
|
|
153
|
+
1. All previous cleanups run
|
|
154
|
+
2. All previous dependencies are removed
|
|
155
|
+
3. The effect function re-executes and re-establishes its dependencies
|
|
156
|
+
|
|
157
|
+
##### Disposal
|
|
158
|
+
Calling `dispose()`:
|
|
159
|
+
- Runs remaining cleanups
|
|
160
|
+
- Deletes the effect from the registry
|
|
161
|
+
- Removes all graph links
|
|
162
|
+
- Prevents future scheduling
|
|
163
|
+
|
|
164
|
+
##### Integration with scheduler
|
|
165
|
+
Effects are not run synchronously—they are **scheduled**.
|
|
166
|
+
This ensures deterministic ordering and batching behavior.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
---------------------------------------------------------
|
|
171
|
+
# 4. Scheduler
|
|
172
|
+
---------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
Your runtime's scheduler is a defining feature.
|
|
175
|
+
It implements a two-phase deterministic flush, ensuring stable, predictable updates.
|
|
176
|
+
|
|
177
|
+
### Two-phase flush model
|
|
178
|
+
```mermaid
|
|
179
|
+
flowchart TD
|
|
180
|
+
A["Began flushJobsTwoPhase()"] --> B{computeQ and effectQ are empty?}
|
|
181
|
+
B -- Yes --> Z[return]
|
|
182
|
+
B -- No --> C[scheduled = false]
|
|
183
|
+
|
|
184
|
+
C --> D{computeQ is empty?}
|
|
185
|
+
D -- No --> E[deal with one computeQ]
|
|
186
|
+
E --> F["iterate job.run()<br/>Maybe enqueue a new computed/effect"]
|
|
187
|
+
F --> D
|
|
188
|
+
D -- Yes --> G[Deal with Phase B]
|
|
189
|
+
|
|
190
|
+
G --> H{effectQ is empty?}
|
|
191
|
+
H -- No --> I["Follow priority sort effectQ"]
|
|
192
|
+
I --> J["iterate effect.run()<br/>Maybe enqueue a new computed/effect"]
|
|
193
|
+
J --> K{"Have a new task?(computeQ or effectQ)"}
|
|
194
|
+
K -- Yes --> C
|
|
195
|
+
K -- No --> Z
|
|
196
|
+
|
|
197
|
+
H -- Yes --> Z
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Properties
|
|
202
|
+
- Computed nodes always stabilize before effects run.
|
|
203
|
+
- Effects run in priority order.
|
|
204
|
+
- No interleaving of computed and effect execution.
|
|
205
|
+
- Infinite loops guarded by a safety counter.
|
|
206
|
+
- Batching defers flush until the outermost batch completes.
|
|
207
|
+
|
|
208
|
+
##### `batch()`
|
|
209
|
+
```ts
|
|
210
|
+
batch(() => {
|
|
211
|
+
setA(1);
|
|
212
|
+
setB(2);
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
Updates are collapsed; effects execute once.
|
|
216
|
+
|
|
217
|
+
##### Internal APIs (not exported)
|
|
218
|
+
- `atomic()`
|
|
219
|
+
- `transaction()`
|
|
220
|
+
- `flushSync()`
|
|
221
|
+
|
|
222
|
+
These provide:
|
|
223
|
+
- nested transaction support
|
|
224
|
+
- rollback semantics
|
|
225
|
+
- temporary scheduler muting
|
|
226
|
+
- consistent reconciling of reactive state
|
|
227
|
+
|
|
228
|
+
They are intentionally excluded from the public API until higher-level packages require them.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
---------------------------------------------------------
|
|
233
|
+
# 5. Dependency Graph
|
|
234
|
+
---------------------------------------------------------
|
|
235
|
+
From `graph.ts`, each reactive node has:
|
|
236
|
+
```ts
|
|
237
|
+
kind: "signal" | "computed" | "effect"
|
|
238
|
+
deps: Set<Node>
|
|
239
|
+
subs: Set<Node>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
`track(dep)`
|
|
243
|
+
Creates a directional edge:
|
|
244
|
+
observer → dep
|
|
245
|
+
|
|
246
|
+
`withObserver(observer, fn)`
|
|
247
|
+
Temporarily sets the active observer during execution.
|
|
248
|
+
|
|
249
|
+
`unlink(from, to)`
|
|
250
|
+
Removes a dependency edge; used by effects and computed nodes on re-run.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
---------------------------------------------------------
|
|
255
|
+
# 6. Internal Architecture Diagram
|
|
256
|
+
---------------------------------------------------------
|
|
257
|
+
```mermaid
|
|
258
|
+
flowchart TD
|
|
259
|
+
subgraph Core["Signal Kernel Core (Reactive Graph)"]
|
|
260
|
+
S["signal()"]
|
|
261
|
+
C["computed()"]
|
|
262
|
+
E["createEffect()"]
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
subgraph Scheduler["Scheduler (two-phase flush)"]
|
|
266
|
+
PA["Phase A:<br/>recompute all stale computed"]
|
|
267
|
+
PB["Phase B:<br/>run effects (by priority)"]
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
%% Signal update path
|
|
271
|
+
S -- "set()" --> MS["markStale(computed)"]
|
|
272
|
+
MS --> C
|
|
273
|
+
|
|
274
|
+
%% Computed recomputation
|
|
275
|
+
C -- "get() / stale" --> RC["recompute()"]
|
|
276
|
+
RC --> C
|
|
277
|
+
|
|
278
|
+
%% Effects depend on signals / computed
|
|
279
|
+
S -- "track()" --> E
|
|
280
|
+
C -- "track()" --> E
|
|
281
|
+
|
|
282
|
+
%% Scheduling
|
|
283
|
+
E -- "scheduleJob()" --> PA
|
|
284
|
+
C -. "stale" .-> PA
|
|
285
|
+
|
|
286
|
+
%% Two-phase loop
|
|
287
|
+
PA --> C
|
|
288
|
+
PA --> PB
|
|
289
|
+
PB --> E
|
|
290
|
+
|
|
291
|
+
%% New work can re-enter the loop
|
|
292
|
+
PB -. "new stale computed / effects" .-> PA
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
---------------------------------------------------------
|
|
298
|
+
# 7. Why This Runtime?
|
|
299
|
+
---------------------------------------------------------
|
|
300
|
+
Compared to other fine-grained reactive engines, Signal Kernel Core offers:
|
|
301
|
+
### Deterministic scheduling
|
|
302
|
+
Computed nodes always settle before effects.
|
|
303
|
+
|
|
304
|
+
### Lazy derivations
|
|
305
|
+
Computed values are only evaluated when needed.
|
|
306
|
+
|
|
307
|
+
### Cycle detection
|
|
308
|
+
Prevents accidental recursive dependencies.
|
|
309
|
+
|
|
310
|
+
### Zero framework assumptions
|
|
311
|
+
No VDOM, no compiler, no DOM APIs.
|
|
312
|
+
|
|
313
|
+
### Adapter-friendly design
|
|
314
|
+
`subscribe()`, `onCleanup()`, and effect disposal make framework integration clean.
|
|
315
|
+
|
|
316
|
+
### Extensibility
|
|
317
|
+
The internal architecture supports advanced features such as async pipelines, transactions, and server-side reactive graphs.
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
---------------------------------------------------------
|
|
322
|
+
# 8. Summary
|
|
323
|
+
---------------------------------------------------------
|
|
324
|
+
`@signal-kernel/core` provides a minimal yet robust foundation for building reactive systems.
|
|
325
|
+
Its combination of:
|
|
326
|
+
- signals
|
|
327
|
+
- lazy computed values
|
|
328
|
+
- deterministic effects
|
|
329
|
+
- a two-phase scheduler
|
|
330
|
+
- an explicit dependency graph
|
|
331
|
+
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
batch: () => batch,
|
|
24
|
+
computed: () => computed,
|
|
25
|
+
createEffect: () => createEffect,
|
|
26
|
+
onCleanup: () => onCleanup,
|
|
27
|
+
signal: () => signal
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(index_exports);
|
|
30
|
+
|
|
31
|
+
// src/graph.ts
|
|
32
|
+
function link(from, to) {
|
|
33
|
+
if (from.kind === "signal") throw new Error("Signal nodes cannot depend on others");
|
|
34
|
+
if (from.deps.has(to)) return;
|
|
35
|
+
from.deps.add(to);
|
|
36
|
+
to.subs.add(from);
|
|
37
|
+
}
|
|
38
|
+
function unlink(from, to) {
|
|
39
|
+
from.deps.delete(to);
|
|
40
|
+
to.subs.delete(from);
|
|
41
|
+
}
|
|
42
|
+
var currentObserver = null;
|
|
43
|
+
function withObserver(obs, fn) {
|
|
44
|
+
const prev = currentObserver;
|
|
45
|
+
currentObserver = obs;
|
|
46
|
+
try {
|
|
47
|
+
return fn();
|
|
48
|
+
} finally {
|
|
49
|
+
currentObserver = prev;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function track(dep) {
|
|
53
|
+
if (!currentObserver) return;
|
|
54
|
+
link(currentObserver, dep);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/registry.ts
|
|
58
|
+
var EffectSlot = /* @__PURE__ */ Symbol("EffectSlot");
|
|
59
|
+
var SymbolRegistry = {
|
|
60
|
+
get(node) {
|
|
61
|
+
return node[EffectSlot];
|
|
62
|
+
},
|
|
63
|
+
set(node, inst) {
|
|
64
|
+
Object.defineProperty(node, EffectSlot, {
|
|
65
|
+
value: inst,
|
|
66
|
+
enumerable: false,
|
|
67
|
+
configurable: true
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
delete(node) {
|
|
71
|
+
Reflect.deleteProperty(node, EffectSlot);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// src/computed.ts
|
|
76
|
+
var defaultEquals = Object.is;
|
|
77
|
+
function markStale(node) {
|
|
78
|
+
if (node.kind !== "computed") return;
|
|
79
|
+
const c = node;
|
|
80
|
+
if (c.stale) return;
|
|
81
|
+
c.stale = true;
|
|
82
|
+
for (const sub of node.subs) {
|
|
83
|
+
if (sub.kind === "computed") {
|
|
84
|
+
markStale(sub);
|
|
85
|
+
} else if (sub.kind === "effect") {
|
|
86
|
+
SymbolRegistry.get(sub)?.schedule();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function computed(fn, equals = defaultEquals) {
|
|
91
|
+
const node = {
|
|
92
|
+
kind: "computed",
|
|
93
|
+
deps: /* @__PURE__ */ new Set(),
|
|
94
|
+
subs: /* @__PURE__ */ new Set(),
|
|
95
|
+
value: void 0,
|
|
96
|
+
stale: true,
|
|
97
|
+
equals,
|
|
98
|
+
computing: false,
|
|
99
|
+
hasValue: false
|
|
100
|
+
};
|
|
101
|
+
function recompute() {
|
|
102
|
+
if (node.computing) throw new Error("Cycle detected in computed");
|
|
103
|
+
node.computing = true;
|
|
104
|
+
for (const d of [...node.deps]) unlink(node, d);
|
|
105
|
+
const next = withObserver(node, fn);
|
|
106
|
+
if (!node.hasValue || !node.equals(node.value, next)) {
|
|
107
|
+
node.value = next;
|
|
108
|
+
node.hasValue = true;
|
|
109
|
+
}
|
|
110
|
+
node.stale = false;
|
|
111
|
+
node.computing = false;
|
|
112
|
+
}
|
|
113
|
+
const get = () => {
|
|
114
|
+
track(node);
|
|
115
|
+
if (node.stale || !node.hasValue) recompute();
|
|
116
|
+
return node.value;
|
|
117
|
+
};
|
|
118
|
+
const peek = () => node.value;
|
|
119
|
+
const dispose = () => {
|
|
120
|
+
for (const d of [...node.deps]) unlink(node, d);
|
|
121
|
+
for (const s of [...node.subs]) unlink(s, node);
|
|
122
|
+
node.deps.clear();
|
|
123
|
+
node.subs.clear();
|
|
124
|
+
node.stale = true;
|
|
125
|
+
node.hasValue = false;
|
|
126
|
+
};
|
|
127
|
+
return { get, peek, dispose };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/scheduler.ts
|
|
131
|
+
var computeQ = /* @__PURE__ */ new Set();
|
|
132
|
+
var effectQ = /* @__PURE__ */ new Set();
|
|
133
|
+
var scheduled = false;
|
|
134
|
+
var batchDepth = 0;
|
|
135
|
+
var atomicDepth = 0;
|
|
136
|
+
var atomicLogs = [];
|
|
137
|
+
var muted = 0;
|
|
138
|
+
function scheduleJob(job) {
|
|
139
|
+
const j = job;
|
|
140
|
+
if (j.disposed) return;
|
|
141
|
+
if (muted > 0) return;
|
|
142
|
+
const kind = j.kind ?? "effect";
|
|
143
|
+
if (kind === "computed") computeQ.add(j);
|
|
144
|
+
else effectQ.add(j);
|
|
145
|
+
if (!scheduled && batchDepth === 0) {
|
|
146
|
+
scheduled = true;
|
|
147
|
+
queueMicrotask(flushJobsTwoPhase);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function batch(fn) {
|
|
151
|
+
batchDepth++;
|
|
152
|
+
try {
|
|
153
|
+
return fn();
|
|
154
|
+
} finally {
|
|
155
|
+
batchDepth--;
|
|
156
|
+
if (batchDepth === 0) flushJobsTwoPhase();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function inAtomic() {
|
|
160
|
+
return atomicDepth > 0;
|
|
161
|
+
}
|
|
162
|
+
function recordAtomicWrite(node, prevValue) {
|
|
163
|
+
const log = atomicLogs[atomicLogs.length - 1];
|
|
164
|
+
if (!log) return;
|
|
165
|
+
if (!log.has(node)) log.set(node, prevValue);
|
|
166
|
+
}
|
|
167
|
+
function flushJobsTwoPhase() {
|
|
168
|
+
scheduled = false;
|
|
169
|
+
let guard = 0;
|
|
170
|
+
while (computeQ.size > 0 || effectQ.size > 0) {
|
|
171
|
+
if (++guard > 1e4) throw new Error("Infinite update loop");
|
|
172
|
+
while (computeQ.size > 0) {
|
|
173
|
+
const batch2 = Array.from(computeQ);
|
|
174
|
+
computeQ.clear();
|
|
175
|
+
for (const job of batch2) {
|
|
176
|
+
job.kind = "computed";
|
|
177
|
+
job.run();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (effectQ.size > 0) {
|
|
181
|
+
const batch2 = Array.from(effectQ).sort(
|
|
182
|
+
(a, b) => (a.priority ?? 0) - (b.priority ?? 0)
|
|
183
|
+
);
|
|
184
|
+
effectQ.clear();
|
|
185
|
+
for (const job of batch2) {
|
|
186
|
+
job.kind = "effect";
|
|
187
|
+
job.run();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/signal.ts
|
|
194
|
+
var defaultEquals2 = Object.is;
|
|
195
|
+
function signal(initial, equals = defaultEquals2) {
|
|
196
|
+
const node = {
|
|
197
|
+
kind: "signal",
|
|
198
|
+
deps: /* @__PURE__ */ new Set(),
|
|
199
|
+
subs: /* @__PURE__ */ new Set(),
|
|
200
|
+
value: initial,
|
|
201
|
+
equals
|
|
202
|
+
};
|
|
203
|
+
const get = () => {
|
|
204
|
+
track(node);
|
|
205
|
+
return node.value;
|
|
206
|
+
};
|
|
207
|
+
const set = (next) => {
|
|
208
|
+
const prev = node.value;
|
|
209
|
+
const nxtVal = typeof next === "function" ? next(node.value) : next;
|
|
210
|
+
if (node.equals(node.value, nxtVal)) return;
|
|
211
|
+
if (inAtomic()) recordAtomicWrite(node, prev);
|
|
212
|
+
node.value = nxtVal;
|
|
213
|
+
if (node.subs.size === 0) return;
|
|
214
|
+
for (const sub of node.subs) {
|
|
215
|
+
if (sub.kind === "effect") {
|
|
216
|
+
SymbolRegistry.get(sub)?.schedule();
|
|
217
|
+
} else if (sub.kind === "computed") {
|
|
218
|
+
markStale(sub);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
const subscribe = (observer) => {
|
|
223
|
+
if (observer.kind === "signal") {
|
|
224
|
+
throw new Error("A signal cannot subscribe to another node");
|
|
225
|
+
}
|
|
226
|
+
link(observer, node);
|
|
227
|
+
return () => unlink(observer, node);
|
|
228
|
+
};
|
|
229
|
+
return { get, set, subscribe, peek: () => node.value };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/effect.ts
|
|
233
|
+
function drainCleanups(list, onError) {
|
|
234
|
+
for (let i = list.length - 1; i >= 0; i--) {
|
|
235
|
+
const cb = list[i];
|
|
236
|
+
try {
|
|
237
|
+
cb?.();
|
|
238
|
+
} catch (e) {
|
|
239
|
+
onError?.(e);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
list.length = 0;
|
|
243
|
+
}
|
|
244
|
+
var activeEffect = null;
|
|
245
|
+
function onCleanup(cb) {
|
|
246
|
+
if (activeEffect) activeEffect.cleanups.push(cb);
|
|
247
|
+
}
|
|
248
|
+
var EffectInstance = class {
|
|
249
|
+
constructor(fn) {
|
|
250
|
+
this.fn = fn;
|
|
251
|
+
this.node = {
|
|
252
|
+
kind: "effect",
|
|
253
|
+
deps: /* @__PURE__ */ new Set(),
|
|
254
|
+
subs: /* @__PURE__ */ new Set()
|
|
255
|
+
};
|
|
256
|
+
this.cleanups = [];
|
|
257
|
+
this.disposed = false;
|
|
258
|
+
SymbolRegistry.set(this.node, this);
|
|
259
|
+
}
|
|
260
|
+
run() {
|
|
261
|
+
if (this.disposed) return;
|
|
262
|
+
drainCleanups(this.cleanups);
|
|
263
|
+
for (const dep of [...this.node.deps]) unlink(this.node, dep);
|
|
264
|
+
activeEffect = this;
|
|
265
|
+
try {
|
|
266
|
+
const ret = withObserver(this.node, this.fn);
|
|
267
|
+
if (typeof ret === "function") this.cleanups.push(ret);
|
|
268
|
+
} finally {
|
|
269
|
+
activeEffect = null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
schedule() {
|
|
273
|
+
scheduleJob(this);
|
|
274
|
+
}
|
|
275
|
+
dispose() {
|
|
276
|
+
if (this.disposed) return;
|
|
277
|
+
this.disposed = true;
|
|
278
|
+
drainCleanups(this.cleanups);
|
|
279
|
+
for (const dep of [...this.node.deps]) unlink(this.node, dep);
|
|
280
|
+
this.node.deps.clear();
|
|
281
|
+
SymbolRegistry.delete(this.node);
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
function createEffect(fn) {
|
|
285
|
+
const inst = new EffectInstance(fn);
|
|
286
|
+
inst.run();
|
|
287
|
+
return () => inst.dispose();
|
|
288
|
+
}
|
|
289
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
290
|
+
0 && (module.exports = {
|
|
291
|
+
batch,
|
|
292
|
+
computed,
|
|
293
|
+
createEffect,
|
|
294
|
+
onCleanup,
|
|
295
|
+
signal
|
|
296
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
type Kind = 'signal' | 'computed' | 'effect';
|
|
2
|
+
interface Node {
|
|
3
|
+
kind: Kind;
|
|
4
|
+
deps: Set<Node>;
|
|
5
|
+
subs: Set<Node>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
type Comparator$1<T> = (a: T, b: T) => boolean;
|
|
9
|
+
declare function signal<T>(initial: T, equals?: Comparator$1<T>): {
|
|
10
|
+
get: () => T;
|
|
11
|
+
set: (next: T | ((prev: T) => T)) => void;
|
|
12
|
+
subscribe: (observer: Node) => () => void;
|
|
13
|
+
peek: () => T;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type Comparator<T> = (a: T, b: T) => boolean;
|
|
17
|
+
declare function computed<T>(fn: () => T, equals?: Comparator<T>): {
|
|
18
|
+
get: () => T;
|
|
19
|
+
peek: () => T;
|
|
20
|
+
dispose: () => void;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type Cleanup = () => void;
|
|
24
|
+
declare function onCleanup(cb: Cleanup): void;
|
|
25
|
+
declare function createEffect(fn: () => void | Cleanup): () => void;
|
|
26
|
+
|
|
27
|
+
declare function batch<T>(fn: () => T): T;
|
|
28
|
+
|
|
29
|
+
export { batch, computed, createEffect, onCleanup, signal };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
type Kind = 'signal' | 'computed' | 'effect';
|
|
2
|
+
interface Node {
|
|
3
|
+
kind: Kind;
|
|
4
|
+
deps: Set<Node>;
|
|
5
|
+
subs: Set<Node>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
type Comparator$1<T> = (a: T, b: T) => boolean;
|
|
9
|
+
declare function signal<T>(initial: T, equals?: Comparator$1<T>): {
|
|
10
|
+
get: () => T;
|
|
11
|
+
set: (next: T | ((prev: T) => T)) => void;
|
|
12
|
+
subscribe: (observer: Node) => () => void;
|
|
13
|
+
peek: () => T;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type Comparator<T> = (a: T, b: T) => boolean;
|
|
17
|
+
declare function computed<T>(fn: () => T, equals?: Comparator<T>): {
|
|
18
|
+
get: () => T;
|
|
19
|
+
peek: () => T;
|
|
20
|
+
dispose: () => void;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type Cleanup = () => void;
|
|
24
|
+
declare function onCleanup(cb: Cleanup): void;
|
|
25
|
+
declare function createEffect(fn: () => void | Cleanup): () => void;
|
|
26
|
+
|
|
27
|
+
declare function batch<T>(fn: () => T): T;
|
|
28
|
+
|
|
29
|
+
export { batch, computed, createEffect, onCleanup, signal };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// src/graph.ts
|
|
2
|
+
function link(from, to) {
|
|
3
|
+
if (from.kind === "signal") throw new Error("Signal nodes cannot depend on others");
|
|
4
|
+
if (from.deps.has(to)) return;
|
|
5
|
+
from.deps.add(to);
|
|
6
|
+
to.subs.add(from);
|
|
7
|
+
}
|
|
8
|
+
function unlink(from, to) {
|
|
9
|
+
from.deps.delete(to);
|
|
10
|
+
to.subs.delete(from);
|
|
11
|
+
}
|
|
12
|
+
var currentObserver = null;
|
|
13
|
+
function withObserver(obs, fn) {
|
|
14
|
+
const prev = currentObserver;
|
|
15
|
+
currentObserver = obs;
|
|
16
|
+
try {
|
|
17
|
+
return fn();
|
|
18
|
+
} finally {
|
|
19
|
+
currentObserver = prev;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function track(dep) {
|
|
23
|
+
if (!currentObserver) return;
|
|
24
|
+
link(currentObserver, dep);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/registry.ts
|
|
28
|
+
var EffectSlot = /* @__PURE__ */ Symbol("EffectSlot");
|
|
29
|
+
var SymbolRegistry = {
|
|
30
|
+
get(node) {
|
|
31
|
+
return node[EffectSlot];
|
|
32
|
+
},
|
|
33
|
+
set(node, inst) {
|
|
34
|
+
Object.defineProperty(node, EffectSlot, {
|
|
35
|
+
value: inst,
|
|
36
|
+
enumerable: false,
|
|
37
|
+
configurable: true
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
delete(node) {
|
|
41
|
+
Reflect.deleteProperty(node, EffectSlot);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// src/computed.ts
|
|
46
|
+
var defaultEquals = Object.is;
|
|
47
|
+
function markStale(node) {
|
|
48
|
+
if (node.kind !== "computed") return;
|
|
49
|
+
const c = node;
|
|
50
|
+
if (c.stale) return;
|
|
51
|
+
c.stale = true;
|
|
52
|
+
for (const sub of node.subs) {
|
|
53
|
+
if (sub.kind === "computed") {
|
|
54
|
+
markStale(sub);
|
|
55
|
+
} else if (sub.kind === "effect") {
|
|
56
|
+
SymbolRegistry.get(sub)?.schedule();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function computed(fn, equals = defaultEquals) {
|
|
61
|
+
const node = {
|
|
62
|
+
kind: "computed",
|
|
63
|
+
deps: /* @__PURE__ */ new Set(),
|
|
64
|
+
subs: /* @__PURE__ */ new Set(),
|
|
65
|
+
value: void 0,
|
|
66
|
+
stale: true,
|
|
67
|
+
equals,
|
|
68
|
+
computing: false,
|
|
69
|
+
hasValue: false
|
|
70
|
+
};
|
|
71
|
+
function recompute() {
|
|
72
|
+
if (node.computing) throw new Error("Cycle detected in computed");
|
|
73
|
+
node.computing = true;
|
|
74
|
+
for (const d of [...node.deps]) unlink(node, d);
|
|
75
|
+
const next = withObserver(node, fn);
|
|
76
|
+
if (!node.hasValue || !node.equals(node.value, next)) {
|
|
77
|
+
node.value = next;
|
|
78
|
+
node.hasValue = true;
|
|
79
|
+
}
|
|
80
|
+
node.stale = false;
|
|
81
|
+
node.computing = false;
|
|
82
|
+
}
|
|
83
|
+
const get = () => {
|
|
84
|
+
track(node);
|
|
85
|
+
if (node.stale || !node.hasValue) recompute();
|
|
86
|
+
return node.value;
|
|
87
|
+
};
|
|
88
|
+
const peek = () => node.value;
|
|
89
|
+
const dispose = () => {
|
|
90
|
+
for (const d of [...node.deps]) unlink(node, d);
|
|
91
|
+
for (const s of [...node.subs]) unlink(s, node);
|
|
92
|
+
node.deps.clear();
|
|
93
|
+
node.subs.clear();
|
|
94
|
+
node.stale = true;
|
|
95
|
+
node.hasValue = false;
|
|
96
|
+
};
|
|
97
|
+
return { get, peek, dispose };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/scheduler.ts
|
|
101
|
+
var computeQ = /* @__PURE__ */ new Set();
|
|
102
|
+
var effectQ = /* @__PURE__ */ new Set();
|
|
103
|
+
var scheduled = false;
|
|
104
|
+
var batchDepth = 0;
|
|
105
|
+
var atomicDepth = 0;
|
|
106
|
+
var atomicLogs = [];
|
|
107
|
+
var muted = 0;
|
|
108
|
+
function scheduleJob(job) {
|
|
109
|
+
const j = job;
|
|
110
|
+
if (j.disposed) return;
|
|
111
|
+
if (muted > 0) return;
|
|
112
|
+
const kind = j.kind ?? "effect";
|
|
113
|
+
if (kind === "computed") computeQ.add(j);
|
|
114
|
+
else effectQ.add(j);
|
|
115
|
+
if (!scheduled && batchDepth === 0) {
|
|
116
|
+
scheduled = true;
|
|
117
|
+
queueMicrotask(flushJobsTwoPhase);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function batch(fn) {
|
|
121
|
+
batchDepth++;
|
|
122
|
+
try {
|
|
123
|
+
return fn();
|
|
124
|
+
} finally {
|
|
125
|
+
batchDepth--;
|
|
126
|
+
if (batchDepth === 0) flushJobsTwoPhase();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function inAtomic() {
|
|
130
|
+
return atomicDepth > 0;
|
|
131
|
+
}
|
|
132
|
+
function recordAtomicWrite(node, prevValue) {
|
|
133
|
+
const log = atomicLogs[atomicLogs.length - 1];
|
|
134
|
+
if (!log) return;
|
|
135
|
+
if (!log.has(node)) log.set(node, prevValue);
|
|
136
|
+
}
|
|
137
|
+
function flushJobsTwoPhase() {
|
|
138
|
+
scheduled = false;
|
|
139
|
+
let guard = 0;
|
|
140
|
+
while (computeQ.size > 0 || effectQ.size > 0) {
|
|
141
|
+
if (++guard > 1e4) throw new Error("Infinite update loop");
|
|
142
|
+
while (computeQ.size > 0) {
|
|
143
|
+
const batch2 = Array.from(computeQ);
|
|
144
|
+
computeQ.clear();
|
|
145
|
+
for (const job of batch2) {
|
|
146
|
+
job.kind = "computed";
|
|
147
|
+
job.run();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (effectQ.size > 0) {
|
|
151
|
+
const batch2 = Array.from(effectQ).sort(
|
|
152
|
+
(a, b) => (a.priority ?? 0) - (b.priority ?? 0)
|
|
153
|
+
);
|
|
154
|
+
effectQ.clear();
|
|
155
|
+
for (const job of batch2) {
|
|
156
|
+
job.kind = "effect";
|
|
157
|
+
job.run();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/signal.ts
|
|
164
|
+
var defaultEquals2 = Object.is;
|
|
165
|
+
function signal(initial, equals = defaultEquals2) {
|
|
166
|
+
const node = {
|
|
167
|
+
kind: "signal",
|
|
168
|
+
deps: /* @__PURE__ */ new Set(),
|
|
169
|
+
subs: /* @__PURE__ */ new Set(),
|
|
170
|
+
value: initial,
|
|
171
|
+
equals
|
|
172
|
+
};
|
|
173
|
+
const get = () => {
|
|
174
|
+
track(node);
|
|
175
|
+
return node.value;
|
|
176
|
+
};
|
|
177
|
+
const set = (next) => {
|
|
178
|
+
const prev = node.value;
|
|
179
|
+
const nxtVal = typeof next === "function" ? next(node.value) : next;
|
|
180
|
+
if (node.equals(node.value, nxtVal)) return;
|
|
181
|
+
if (inAtomic()) recordAtomicWrite(node, prev);
|
|
182
|
+
node.value = nxtVal;
|
|
183
|
+
if (node.subs.size === 0) return;
|
|
184
|
+
for (const sub of node.subs) {
|
|
185
|
+
if (sub.kind === "effect") {
|
|
186
|
+
SymbolRegistry.get(sub)?.schedule();
|
|
187
|
+
} else if (sub.kind === "computed") {
|
|
188
|
+
markStale(sub);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
const subscribe = (observer) => {
|
|
193
|
+
if (observer.kind === "signal") {
|
|
194
|
+
throw new Error("A signal cannot subscribe to another node");
|
|
195
|
+
}
|
|
196
|
+
link(observer, node);
|
|
197
|
+
return () => unlink(observer, node);
|
|
198
|
+
};
|
|
199
|
+
return { get, set, subscribe, peek: () => node.value };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/effect.ts
|
|
203
|
+
function drainCleanups(list, onError) {
|
|
204
|
+
for (let i = list.length - 1; i >= 0; i--) {
|
|
205
|
+
const cb = list[i];
|
|
206
|
+
try {
|
|
207
|
+
cb?.();
|
|
208
|
+
} catch (e) {
|
|
209
|
+
onError?.(e);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
list.length = 0;
|
|
213
|
+
}
|
|
214
|
+
var activeEffect = null;
|
|
215
|
+
function onCleanup(cb) {
|
|
216
|
+
if (activeEffect) activeEffect.cleanups.push(cb);
|
|
217
|
+
}
|
|
218
|
+
var EffectInstance = class {
|
|
219
|
+
constructor(fn) {
|
|
220
|
+
this.fn = fn;
|
|
221
|
+
this.node = {
|
|
222
|
+
kind: "effect",
|
|
223
|
+
deps: /* @__PURE__ */ new Set(),
|
|
224
|
+
subs: /* @__PURE__ */ new Set()
|
|
225
|
+
};
|
|
226
|
+
this.cleanups = [];
|
|
227
|
+
this.disposed = false;
|
|
228
|
+
SymbolRegistry.set(this.node, this);
|
|
229
|
+
}
|
|
230
|
+
run() {
|
|
231
|
+
if (this.disposed) return;
|
|
232
|
+
drainCleanups(this.cleanups);
|
|
233
|
+
for (const dep of [...this.node.deps]) unlink(this.node, dep);
|
|
234
|
+
activeEffect = this;
|
|
235
|
+
try {
|
|
236
|
+
const ret = withObserver(this.node, this.fn);
|
|
237
|
+
if (typeof ret === "function") this.cleanups.push(ret);
|
|
238
|
+
} finally {
|
|
239
|
+
activeEffect = null;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
schedule() {
|
|
243
|
+
scheduleJob(this);
|
|
244
|
+
}
|
|
245
|
+
dispose() {
|
|
246
|
+
if (this.disposed) return;
|
|
247
|
+
this.disposed = true;
|
|
248
|
+
drainCleanups(this.cleanups);
|
|
249
|
+
for (const dep of [...this.node.deps]) unlink(this.node, dep);
|
|
250
|
+
this.node.deps.clear();
|
|
251
|
+
SymbolRegistry.delete(this.node);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
function createEffect(fn) {
|
|
255
|
+
const inst = new EffectInstance(fn);
|
|
256
|
+
inst.run();
|
|
257
|
+
return () => inst.dispose();
|
|
258
|
+
}
|
|
259
|
+
export {
|
|
260
|
+
batch,
|
|
261
|
+
computed,
|
|
262
|
+
createEffect,
|
|
263
|
+
onCleanup,
|
|
264
|
+
signal
|
|
265
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@signal-kernel/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^22.0.0",
|
|
24
|
+
"@typescript-eslint/eslint-plugin": "^8.48.1",
|
|
25
|
+
"@typescript-eslint/parser": "^8.48.1",
|
|
26
|
+
"eslint": "^9.39.1",
|
|
27
|
+
"tsup": "^8.0.0",
|
|
28
|
+
"typescript": "^5.6.0",
|
|
29
|
+
"vitest": "^2.0.0"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --tsconfig tsconfig.dts.json",
|
|
33
|
+
"lint": "eslint src --ext .ts",
|
|
34
|
+
"typecheck": "tsc --noEmit",
|
|
35
|
+
"test": "vitest run"
|
|
36
|
+
}
|
|
37
|
+
}
|