@rlabs-inc/signals 0.1.0 → 0.2.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/README.md +43 -13
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +103 -0
- package/dist/index.mjs +103 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Ultra-lightweight fine-grained reactivity for TypeScript**
|
|
4
4
|
|
|
5
|
-
A standalone reactive primitives library inspired by Svelte 5 runes, Solid.js, and Vue reactivity. Zero dependencies. ~
|
|
5
|
+
A standalone reactive primitives library inspired by Svelte 5 runes, Solid.js, and Vue reactivity. Zero dependencies. ~800 lines of pure TypeScript.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
@@ -25,7 +25,7 @@ npm install @rlabs-inc/signals
|
|
|
25
25
|
## Quick Start
|
|
26
26
|
|
|
27
27
|
```typescript
|
|
28
|
-
import { signal, effect, derived
|
|
28
|
+
import { signal, effect, derived } from '@rlabs-inc/signals'
|
|
29
29
|
|
|
30
30
|
// Simple signal
|
|
31
31
|
const count = signal(0)
|
|
@@ -43,8 +43,8 @@ count.value = 1
|
|
|
43
43
|
const doubled = derived(() => count.value * 2)
|
|
44
44
|
console.log(doubled.value) // 2
|
|
45
45
|
|
|
46
|
-
// Deep reactivity
|
|
47
|
-
const user =
|
|
46
|
+
// Deep reactivity - signal() is deeply reactive for objects/arrays!
|
|
47
|
+
const user = signal({
|
|
48
48
|
name: 'John',
|
|
49
49
|
address: {
|
|
50
50
|
city: 'NYC'
|
|
@@ -52,28 +52,47 @@ const user = state({
|
|
|
52
52
|
})
|
|
53
53
|
|
|
54
54
|
effect(() => {
|
|
55
|
-
console.log('City:', user.address.city)
|
|
55
|
+
console.log('City:', user.value.address.city)
|
|
56
56
|
})
|
|
57
57
|
// Logs: "City: NYC"
|
|
58
58
|
|
|
59
|
-
user.address.city = 'LA'
|
|
60
|
-
// Logs: "City: LA"
|
|
59
|
+
user.value.address.city = 'LA'
|
|
60
|
+
// Logs: "City: LA" - mutations at any depth trigger effects!
|
|
61
|
+
|
|
62
|
+
// Even deeply nested arrays work
|
|
63
|
+
const data = signal({ items: [[1, 2], [3, 4]] })
|
|
64
|
+
data.value.items[0][1] = 99 // Triggers effects!
|
|
61
65
|
```
|
|
62
66
|
|
|
63
67
|
## API Reference
|
|
64
68
|
|
|
65
69
|
### signal(initial, options?)
|
|
66
70
|
|
|
67
|
-
Create a reactive signal that holds a value.
|
|
71
|
+
Create a reactive signal that holds a value. **For objects and arrays, signals are deeply reactive** - mutations at any depth trigger effects.
|
|
68
72
|
|
|
69
73
|
```typescript
|
|
74
|
+
// Primitives
|
|
70
75
|
const count = signal(0)
|
|
71
76
|
console.log(count.value) // 0
|
|
72
|
-
|
|
73
77
|
count.value = 5
|
|
74
78
|
console.log(count.value) // 5
|
|
75
79
|
|
|
76
|
-
//
|
|
80
|
+
// Objects - deeply reactive!
|
|
81
|
+
const user = signal({ name: 'John', address: { city: 'NYC' } })
|
|
82
|
+
user.value.address.city = 'LA' // Triggers effects!
|
|
83
|
+
|
|
84
|
+
// Arrays - deeply reactive!
|
|
85
|
+
const matrix = signal([[1, 2], [3, 4]])
|
|
86
|
+
matrix.value[0][1] = 99 // Triggers effects!
|
|
87
|
+
matrix.value.push([5, 6]) // Triggers effects!
|
|
88
|
+
|
|
89
|
+
// Arbitrarily deep nesting works
|
|
90
|
+
const complex = signal({
|
|
91
|
+
level1: [{ level2: [[{ level3: 'value' }]] }]
|
|
92
|
+
})
|
|
93
|
+
complex.value.level1[0].level2[0][0].level3 = 'mutated' // Triggers!
|
|
94
|
+
|
|
95
|
+
// With custom equality (for full replacement comparison)
|
|
77
96
|
const obj = signal({ x: 1 }, {
|
|
78
97
|
equals: (a, b) => a.x === b.x
|
|
79
98
|
})
|
|
@@ -119,9 +138,10 @@ const tripled = derived.by(() => count.value * 3)
|
|
|
119
138
|
|
|
120
139
|
### state(initial)
|
|
121
140
|
|
|
122
|
-
|
|
141
|
+
Convenience function for deeply reactive objects **without `.value`**. Use when you want direct property access instead of going through `.value`.
|
|
123
142
|
|
|
124
143
|
```typescript
|
|
144
|
+
// state() - direct access, no .value needed
|
|
125
145
|
const app = state({
|
|
126
146
|
user: {
|
|
127
147
|
name: 'John',
|
|
@@ -133,13 +153,21 @@ const app = state({
|
|
|
133
153
|
})
|
|
134
154
|
|
|
135
155
|
effect(() => {
|
|
136
|
-
console.log(app.user.preferences.theme)
|
|
156
|
+
console.log(app.user.preferences.theme) // No .value!
|
|
137
157
|
})
|
|
138
158
|
|
|
139
159
|
app.user.preferences.theme = 'light' // Triggers effect
|
|
140
160
|
app.items.push(4) // Arrays are reactive too
|
|
161
|
+
|
|
162
|
+
// Equivalent using signal():
|
|
163
|
+
const app2 = signal({ ... })
|
|
164
|
+
app2.value.user.preferences.theme = 'light' // Same, but with .value
|
|
141
165
|
```
|
|
142
166
|
|
|
167
|
+
**When to use which:**
|
|
168
|
+
- `signal()` - Unified API, always use `.value` (recommended)
|
|
169
|
+
- `state()` - When you want direct property access without `.value`
|
|
170
|
+
|
|
143
171
|
### batch(fn)
|
|
144
172
|
|
|
145
173
|
Batch multiple updates into a single reaction cycle.
|
|
@@ -278,7 +306,8 @@ scope.stop() // Disposes all effects
|
|
|
278
306
|
|
|
279
307
|
| Svelte 5 | @rlabs-inc/signals |
|
|
280
308
|
|----------|-------------------|
|
|
281
|
-
| `$state(value)` | `signal(value)`
|
|
309
|
+
| `$state(value)` | `signal(value)` - deeply reactive for objects/arrays |
|
|
310
|
+
| `$state(obj)` direct access | `state(obj)` - no `.value` needed |
|
|
282
311
|
| `$derived(expr)` | `derived(() => expr)` |
|
|
283
312
|
| `$derived.by(fn)` | `derived.by(fn)` or `derived(fn)` |
|
|
284
313
|
| `$effect(fn)` | `effect(fn)` |
|
|
@@ -289,6 +318,7 @@ scope.stop() // Disposes all effects
|
|
|
289
318
|
- No compiler needed - works with plain TypeScript
|
|
290
319
|
- Use `.value` to read/write signals (like Vue/Solid)
|
|
291
320
|
- No Happy DOM or browser environment required
|
|
321
|
+
- `signal()` is deeply reactive out of the box - arrays of arrays of objects just work
|
|
292
322
|
|
|
293
323
|
## Why?
|
|
294
324
|
|
package/dist/index.d.ts
CHANGED
|
@@ -27,12 +27,22 @@ export declare const shallowEquals: <T>(a: T, b: T) => boolean;
|
|
|
27
27
|
/**
|
|
28
28
|
* Create a reactive signal
|
|
29
29
|
*
|
|
30
|
+
* For objects and arrays, the signal is deeply reactive - mutations at any
|
|
31
|
+
* depth will trigger effects.
|
|
32
|
+
*
|
|
30
33
|
* @example
|
|
31
34
|
* ```ts
|
|
32
35
|
* const count = signal(0)
|
|
33
36
|
* console.log(count.value) // 0
|
|
34
37
|
* count.value = 1
|
|
35
38
|
* console.log(count.value) // 1
|
|
39
|
+
*
|
|
40
|
+
* // Deep reactivity for objects/arrays
|
|
41
|
+
* const user = signal({ name: 'John', address: { city: 'NYC' } })
|
|
42
|
+
* user.value.address.city = 'LA' // Triggers effects!
|
|
43
|
+
*
|
|
44
|
+
* const items = signal([[1, 2], [3, 4]])
|
|
45
|
+
* items.value[0][1] = 99 // Triggers effects!
|
|
36
46
|
* ```
|
|
37
47
|
*/
|
|
38
48
|
export declare function signal<T>(initial: T, options?: SignalOptions<T>): WritableSignal<T>;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,2CAA2C;AAC3C,MAAM,MAAM,SAAS,GAAG,MAAM,IAAI,CAAA;AAElC,gDAAgD;AAChD,MAAM,MAAM,QAAQ,GAAG,MAAM,IAAI,GAAG,SAAS,CAAA;AAE7C,6CAA6C;AAC7C,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,OAAO,CAAA;AAEnD,2CAA2C;AAC3C,MAAM,WAAW,MAAM,CAAC,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;CAClB;AAED,iCAAiC;AACjC,MAAM,WAAW,cAAc,CAAC,CAAC,CAAE,SAAQ,MAAM,CAAC,CAAC,CAAC;IAClD,KAAK,EAAE,CAAC,CAAA;CACT;AAED,4CAA4C;AAC5C,MAAM,WAAW,OAAO,CAAC,CAAC,CAAE,SAAQ,MAAM,CAAC,CAAC,CAAC;IAC3C,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;CAClB;AAED,oCAAoC;AACpC,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAA;CACvB;AAqDD,6CAA6C;AAC7C,eAAO,MAAM,aAAa,GAAI,CAAC,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC,KAAG,OAA0B,CAAA;AAExE,0CAA0C;AAC1C,eAAO,MAAM,aAAa,GAAI,CAAC,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC,KAAG,OAe7C,CAAA;AAMD
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -79,14 +79,22 @@ function signal(initial, options) {
|
|
|
79
79
|
reactions: new Set,
|
|
80
80
|
equals: options?.equals ?? defaultEquals
|
|
81
81
|
};
|
|
82
|
+
let proxyCache = null;
|
|
82
83
|
return {
|
|
83
84
|
get value() {
|
|
84
85
|
track(internal);
|
|
86
|
+
if (internal.v !== null && typeof internal.v === "object") {
|
|
87
|
+
if (!proxyCache) {
|
|
88
|
+
proxyCache = createDeepReactiveForSignal(internal.v, internal);
|
|
89
|
+
}
|
|
90
|
+
return proxyCache;
|
|
91
|
+
}
|
|
85
92
|
return internal.v;
|
|
86
93
|
},
|
|
87
94
|
set value(newValue) {
|
|
88
95
|
if (!internal.equals(internal.v, newValue)) {
|
|
89
96
|
internal.v = newValue;
|
|
97
|
+
proxyCache = null;
|
|
90
98
|
trigger(internal);
|
|
91
99
|
}
|
|
92
100
|
}
|
|
@@ -329,6 +337,101 @@ function createDeepReactive(target) {
|
|
|
329
337
|
return proxy;
|
|
330
338
|
}
|
|
331
339
|
var proxyToRaw = new WeakMap;
|
|
340
|
+
var signalProxyCache = new WeakMap;
|
|
341
|
+
function createDeepReactiveForSignal(target, parentSignal) {
|
|
342
|
+
const existingProxy = signalProxyCache.get(target);
|
|
343
|
+
if (existingProxy)
|
|
344
|
+
return existingProxy;
|
|
345
|
+
const propSignals = new Map;
|
|
346
|
+
const getPropSignal = (prop) => {
|
|
347
|
+
let sig = propSignals.get(prop);
|
|
348
|
+
if (!sig) {
|
|
349
|
+
sig = {
|
|
350
|
+
v: target[prop],
|
|
351
|
+
reactions: new Set,
|
|
352
|
+
equals: defaultEquals
|
|
353
|
+
};
|
|
354
|
+
propSignals.set(prop, sig);
|
|
355
|
+
}
|
|
356
|
+
return sig;
|
|
357
|
+
};
|
|
358
|
+
const internal = {
|
|
359
|
+
v: target,
|
|
360
|
+
reactions: new Set,
|
|
361
|
+
equals: defaultEquals
|
|
362
|
+
};
|
|
363
|
+
const proxy = new Proxy(target, {
|
|
364
|
+
get(target2, prop, receiver) {
|
|
365
|
+
if (prop === REACTIVE_MARKER)
|
|
366
|
+
return true;
|
|
367
|
+
const value = Reflect.get(target2, prop, receiver);
|
|
368
|
+
if (Array.isArray(target2) && typeof value === "function") {
|
|
369
|
+
track(internal);
|
|
370
|
+
return value.bind(proxy);
|
|
371
|
+
}
|
|
372
|
+
const sig = getPropSignal(prop);
|
|
373
|
+
track(sig);
|
|
374
|
+
if (value !== null && typeof value === "object") {
|
|
375
|
+
const proto = Object.getPrototypeOf(value);
|
|
376
|
+
if (proto === Object.prototype || proto === Array.prototype || proto === null) {
|
|
377
|
+
if (!value[REACTIVE_MARKER]) {
|
|
378
|
+
return createDeepReactiveForSignal(value, parentSignal);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return value;
|
|
383
|
+
},
|
|
384
|
+
set(target2, prop, value, receiver) {
|
|
385
|
+
const oldValue = target2[prop];
|
|
386
|
+
const rawValue = value?.[REACTIVE_MARKER] ? proxyToRaw.get(value) ?? value : value;
|
|
387
|
+
if (Object.is(oldValue, rawValue))
|
|
388
|
+
return true;
|
|
389
|
+
const result = Reflect.set(target2, prop, rawValue, receiver);
|
|
390
|
+
if (result) {
|
|
391
|
+
batch(() => {
|
|
392
|
+
const sig = getPropSignal(prop);
|
|
393
|
+
sig.v = rawValue;
|
|
394
|
+
trigger(sig);
|
|
395
|
+
trigger(parentSignal);
|
|
396
|
+
if (Array.isArray(target2)) {
|
|
397
|
+
internal.v = target2;
|
|
398
|
+
trigger(internal);
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
return result;
|
|
403
|
+
},
|
|
404
|
+
deleteProperty(target2, prop) {
|
|
405
|
+
const hadKey = prop in target2;
|
|
406
|
+
const result = Reflect.deleteProperty(target2, prop);
|
|
407
|
+
if (result && hadKey) {
|
|
408
|
+
batch(() => {
|
|
409
|
+
const sig = propSignals.get(prop);
|
|
410
|
+
if (sig) {
|
|
411
|
+
sig.v = undefined;
|
|
412
|
+
trigger(sig);
|
|
413
|
+
}
|
|
414
|
+
trigger(parentSignal);
|
|
415
|
+
trigger(internal);
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
return result;
|
|
419
|
+
},
|
|
420
|
+
has(target2, prop) {
|
|
421
|
+
if (prop === REACTIVE_MARKER)
|
|
422
|
+
return true;
|
|
423
|
+
track(internal);
|
|
424
|
+
return Reflect.has(target2, prop);
|
|
425
|
+
},
|
|
426
|
+
ownKeys(target2) {
|
|
427
|
+
track(internal);
|
|
428
|
+
return Reflect.ownKeys(target2);
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
signalProxyCache.set(target, proxy);
|
|
432
|
+
proxyToRaw.set(proxy, target);
|
|
433
|
+
return proxy;
|
|
434
|
+
}
|
|
332
435
|
function toRaw(proxy) {
|
|
333
436
|
if (proxy && typeof proxy === "object" && proxy[REACTIVE_MARKER]) {
|
|
334
437
|
return proxyToRaw.get(proxy) ?? proxy;
|
package/dist/index.mjs
CHANGED
|
@@ -30,14 +30,22 @@ function signal(initial, options) {
|
|
|
30
30
|
reactions: new Set,
|
|
31
31
|
equals: options?.equals ?? defaultEquals
|
|
32
32
|
};
|
|
33
|
+
let proxyCache = null;
|
|
33
34
|
return {
|
|
34
35
|
get value() {
|
|
35
36
|
track(internal);
|
|
37
|
+
if (internal.v !== null && typeof internal.v === "object") {
|
|
38
|
+
if (!proxyCache) {
|
|
39
|
+
proxyCache = createDeepReactiveForSignal(internal.v, internal);
|
|
40
|
+
}
|
|
41
|
+
return proxyCache;
|
|
42
|
+
}
|
|
36
43
|
return internal.v;
|
|
37
44
|
},
|
|
38
45
|
set value(newValue) {
|
|
39
46
|
if (!internal.equals(internal.v, newValue)) {
|
|
40
47
|
internal.v = newValue;
|
|
48
|
+
proxyCache = null;
|
|
41
49
|
trigger(internal);
|
|
42
50
|
}
|
|
43
51
|
}
|
|
@@ -280,6 +288,101 @@ function createDeepReactive(target) {
|
|
|
280
288
|
return proxy;
|
|
281
289
|
}
|
|
282
290
|
var proxyToRaw = new WeakMap;
|
|
291
|
+
var signalProxyCache = new WeakMap;
|
|
292
|
+
function createDeepReactiveForSignal(target, parentSignal) {
|
|
293
|
+
const existingProxy = signalProxyCache.get(target);
|
|
294
|
+
if (existingProxy)
|
|
295
|
+
return existingProxy;
|
|
296
|
+
const propSignals = new Map;
|
|
297
|
+
const getPropSignal = (prop) => {
|
|
298
|
+
let sig = propSignals.get(prop);
|
|
299
|
+
if (!sig) {
|
|
300
|
+
sig = {
|
|
301
|
+
v: target[prop],
|
|
302
|
+
reactions: new Set,
|
|
303
|
+
equals: defaultEquals
|
|
304
|
+
};
|
|
305
|
+
propSignals.set(prop, sig);
|
|
306
|
+
}
|
|
307
|
+
return sig;
|
|
308
|
+
};
|
|
309
|
+
const internal = {
|
|
310
|
+
v: target,
|
|
311
|
+
reactions: new Set,
|
|
312
|
+
equals: defaultEquals
|
|
313
|
+
};
|
|
314
|
+
const proxy = new Proxy(target, {
|
|
315
|
+
get(target2, prop, receiver) {
|
|
316
|
+
if (prop === REACTIVE_MARKER)
|
|
317
|
+
return true;
|
|
318
|
+
const value = Reflect.get(target2, prop, receiver);
|
|
319
|
+
if (Array.isArray(target2) && typeof value === "function") {
|
|
320
|
+
track(internal);
|
|
321
|
+
return value.bind(proxy);
|
|
322
|
+
}
|
|
323
|
+
const sig = getPropSignal(prop);
|
|
324
|
+
track(sig);
|
|
325
|
+
if (value !== null && typeof value === "object") {
|
|
326
|
+
const proto = Object.getPrototypeOf(value);
|
|
327
|
+
if (proto === Object.prototype || proto === Array.prototype || proto === null) {
|
|
328
|
+
if (!value[REACTIVE_MARKER]) {
|
|
329
|
+
return createDeepReactiveForSignal(value, parentSignal);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return value;
|
|
334
|
+
},
|
|
335
|
+
set(target2, prop, value, receiver) {
|
|
336
|
+
const oldValue = target2[prop];
|
|
337
|
+
const rawValue = value?.[REACTIVE_MARKER] ? proxyToRaw.get(value) ?? value : value;
|
|
338
|
+
if (Object.is(oldValue, rawValue))
|
|
339
|
+
return true;
|
|
340
|
+
const result = Reflect.set(target2, prop, rawValue, receiver);
|
|
341
|
+
if (result) {
|
|
342
|
+
batch(() => {
|
|
343
|
+
const sig = getPropSignal(prop);
|
|
344
|
+
sig.v = rawValue;
|
|
345
|
+
trigger(sig);
|
|
346
|
+
trigger(parentSignal);
|
|
347
|
+
if (Array.isArray(target2)) {
|
|
348
|
+
internal.v = target2;
|
|
349
|
+
trigger(internal);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
return result;
|
|
354
|
+
},
|
|
355
|
+
deleteProperty(target2, prop) {
|
|
356
|
+
const hadKey = prop in target2;
|
|
357
|
+
const result = Reflect.deleteProperty(target2, prop);
|
|
358
|
+
if (result && hadKey) {
|
|
359
|
+
batch(() => {
|
|
360
|
+
const sig = propSignals.get(prop);
|
|
361
|
+
if (sig) {
|
|
362
|
+
sig.v = undefined;
|
|
363
|
+
trigger(sig);
|
|
364
|
+
}
|
|
365
|
+
trigger(parentSignal);
|
|
366
|
+
trigger(internal);
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
return result;
|
|
370
|
+
},
|
|
371
|
+
has(target2, prop) {
|
|
372
|
+
if (prop === REACTIVE_MARKER)
|
|
373
|
+
return true;
|
|
374
|
+
track(internal);
|
|
375
|
+
return Reflect.has(target2, prop);
|
|
376
|
+
},
|
|
377
|
+
ownKeys(target2) {
|
|
378
|
+
track(internal);
|
|
379
|
+
return Reflect.ownKeys(target2);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
signalProxyCache.set(target, proxy);
|
|
383
|
+
proxyToRaw.set(proxy, target);
|
|
384
|
+
return proxy;
|
|
385
|
+
}
|
|
283
386
|
function toRaw(proxy) {
|
|
284
387
|
if (proxy && typeof proxy === "object" && proxy[REACTIVE_MARKER]) {
|
|
285
388
|
return proxyToRaw.get(proxy) ?? proxy;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rlabs-inc/signals",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Ultra-lightweight fine-grained reactivity for TypeScript. Signals, effects, derived values, and reactive collections with deep reactivity support.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|