@rlabs-inc/signals 0.1.0 → 1.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/README.md +240 -170
- package/dist/collections/date.d.ts +67 -0
- package/dist/collections/date.d.ts.map +1 -0
- package/dist/collections/map.d.ts +37 -0
- package/dist/collections/map.d.ts.map +1 -0
- package/dist/collections/set.d.ts +36 -0
- package/dist/collections/set.d.ts.map +1 -0
- package/dist/core/constants.d.ts +47 -0
- package/dist/core/constants.d.ts.map +1 -0
- package/dist/core/globals.d.ts +45 -0
- package/dist/core/globals.d.ts.map +1 -0
- package/dist/core/types.d.ts +76 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/deep/proxy.d.ts +19 -0
- package/dist/deep/proxy.d.ts.map +1 -0
- package/dist/index.d.ts +14 -267
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1241 -512
- package/dist/index.mjs +1240 -513
- package/dist/primitives/derived.d.ts +45 -0
- package/dist/primitives/derived.d.ts.map +1 -0
- package/dist/primitives/effect.d.ts +41 -0
- package/dist/primitives/effect.d.ts.map +1 -0
- package/dist/primitives/signal.d.ts +54 -0
- package/dist/primitives/signal.d.ts.map +1 -0
- package/dist/reactivity/batching.d.ts +56 -0
- package/dist/reactivity/batching.d.ts.map +1 -0
- package/dist/reactivity/equality.d.ts +36 -0
- package/dist/reactivity/equality.d.ts.map +1 -0
- package/dist/reactivity/scheduling.d.ts +26 -0
- package/dist/reactivity/scheduling.d.ts.map +1 -0
- package/dist/reactivity/tracking.d.ts +47 -0
- package/dist/reactivity/tracking.d.ts.map +1 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
# @rlabs-inc/signals
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Production-grade fine-grained reactivity for TypeScript.**
|
|
4
4
|
|
|
5
|
-
A standalone
|
|
5
|
+
A complete standalone mirror of Svelte 5's reactivity system. No compiler needed, no DOM, works anywhere - Bun, Node, Deno, or browser.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- **Fine-
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
9
|
+
- **True Fine-Grained Reactivity** - Changes to deeply nested properties only trigger effects that read that exact path
|
|
10
|
+
- **Per-Property Tracking** - Proxy-based deep reactivity with lazy signal creation per property
|
|
11
|
+
- **Three-State Dirty Tracking** - Efficient CLEAN/MAYBE_DIRTY/DIRTY propagation
|
|
12
|
+
- **Automatic Cleanup** - Effects clean up when disposed, no memory leaks
|
|
13
|
+
- **Batching** - Group updates to prevent redundant effect runs
|
|
14
|
+
- **Self-Referencing Effects** - Effects can write to their own dependencies
|
|
15
|
+
- **Infinite Loop Protection** - Throws after 1000 iterations to catch bugs
|
|
16
|
+
- **Reactive Collections** - ReactiveMap, ReactiveSet, ReactiveDate
|
|
17
|
+
- **TypeScript Native** - Full type safety with generics
|
|
16
18
|
|
|
17
19
|
## Installation
|
|
18
20
|
|
|
@@ -25,69 +27,84 @@ npm install @rlabs-inc/signals
|
|
|
25
27
|
## Quick Start
|
|
26
28
|
|
|
27
29
|
```typescript
|
|
28
|
-
import { signal,
|
|
30
|
+
import { signal, derived, effect, flushSync } from '@rlabs-inc/signals'
|
|
29
31
|
|
|
30
|
-
//
|
|
32
|
+
// Create a signal
|
|
31
33
|
const count = signal(0)
|
|
32
34
|
|
|
33
|
-
//
|
|
35
|
+
// Create a derived value
|
|
36
|
+
const doubled = derived(() => count.value * 2)
|
|
37
|
+
|
|
38
|
+
// Create an effect
|
|
34
39
|
effect(() => {
|
|
35
|
-
console.log(
|
|
40
|
+
console.log(`Count: ${count.value}, Doubled: ${doubled.value}`)
|
|
36
41
|
})
|
|
37
|
-
// Logs: "Count: 0"
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
// Logs: "Count:
|
|
43
|
+
// Flush to run effects synchronously
|
|
44
|
+
flushSync() // Logs: "Count: 0, Doubled: 0"
|
|
41
45
|
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
// Update the signal
|
|
47
|
+
count.value = 5
|
|
48
|
+
flushSync() // Logs: "Count: 5, Doubled: 10"
|
|
49
|
+
```
|
|
45
50
|
|
|
46
|
-
|
|
47
|
-
const user = state({
|
|
48
|
-
name: 'John',
|
|
49
|
-
address: {
|
|
50
|
-
city: 'NYC'
|
|
51
|
-
}
|
|
52
|
-
})
|
|
51
|
+
## API Reference
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
### Signals
|
|
54
|
+
|
|
55
|
+
#### `signal<T>(initialValue: T, options?): WritableSignal<T>`
|
|
56
|
+
|
|
57
|
+
Create a reactive value with `.value` getter/setter.
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
```typescript
|
|
60
|
+
const name = signal('John')
|
|
61
|
+
console.log(name.value) // 'John'
|
|
62
|
+
name.value = 'Jane' // Triggers effects
|
|
61
63
|
```
|
|
62
64
|
|
|
63
|
-
|
|
65
|
+
**Options:**
|
|
66
|
+
- `equals?: (a: T, b: T) => boolean` - Custom equality function
|
|
64
67
|
|
|
65
|
-
|
|
68
|
+
#### `state<T extends object>(initialValue: T): T`
|
|
66
69
|
|
|
67
|
-
Create a reactive
|
|
70
|
+
Create a deeply reactive object. No `.value` needed - access properties directly.
|
|
68
71
|
|
|
69
72
|
```typescript
|
|
70
|
-
const
|
|
71
|
-
|
|
73
|
+
const user = state({ name: 'John', address: { city: 'NYC' } })
|
|
74
|
+
user.name = 'Jane' // Reactive
|
|
75
|
+
user.address.city = 'LA' // Also reactive, deeply
|
|
76
|
+
```
|
|
72
77
|
|
|
73
|
-
|
|
74
|
-
console.log(count.value) // 5
|
|
78
|
+
### Derived Values
|
|
75
79
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
#### `derived<T>(fn: () => T): DerivedSignal<T>`
|
|
81
|
+
|
|
82
|
+
Create a computed value that automatically updates when dependencies change.
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
const firstName = signal('John')
|
|
86
|
+
const lastName = signal('Doe')
|
|
87
|
+
|
|
88
|
+
const fullName = derived(() => `${firstName.value} ${lastName.value}`)
|
|
89
|
+
console.log(fullName.value) // 'John Doe'
|
|
80
90
|
```
|
|
81
91
|
|
|
82
|
-
|
|
92
|
+
Deriveds are:
|
|
93
|
+
- **Lazy** - Only computed when read
|
|
94
|
+
- **Cached** - Value is memoized until dependencies change
|
|
95
|
+
- **Pure** - Cannot write to signals inside (throws error)
|
|
96
|
+
|
|
97
|
+
### Effects
|
|
83
98
|
|
|
84
|
-
|
|
99
|
+
#### `effect(fn: () => void | CleanupFn): DisposeFn`
|
|
100
|
+
|
|
101
|
+
Create a side effect that re-runs when dependencies change.
|
|
85
102
|
|
|
86
103
|
```typescript
|
|
87
|
-
const
|
|
104
|
+
const count = signal(0)
|
|
88
105
|
|
|
89
106
|
const dispose = effect(() => {
|
|
90
|
-
console.log('
|
|
107
|
+
console.log('Count is:', count.value)
|
|
91
108
|
|
|
92
109
|
// Optional cleanup function
|
|
93
110
|
return () => {
|
|
@@ -95,213 +112,266 @@ const dispose = effect(() => {
|
|
|
95
112
|
}
|
|
96
113
|
})
|
|
97
114
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
dispose() // Stop the effect
|
|
115
|
+
// Stop the effect
|
|
116
|
+
dispose()
|
|
101
117
|
```
|
|
102
118
|
|
|
103
|
-
|
|
119
|
+
#### `effect.root(fn: () => T): DisposeFn`
|
|
104
120
|
|
|
105
|
-
Create
|
|
121
|
+
Create an effect scope that can contain nested effects.
|
|
106
122
|
|
|
107
123
|
```typescript
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
count.value = 10
|
|
114
|
-
console.log(doubled.value) // 20
|
|
124
|
+
const dispose = effect.root(() => {
|
|
125
|
+
effect(() => { /* ... */ })
|
|
126
|
+
effect(() => { /* ... */ })
|
|
127
|
+
})
|
|
115
128
|
|
|
116
|
-
//
|
|
117
|
-
|
|
129
|
+
// Disposes all nested effects
|
|
130
|
+
dispose()
|
|
118
131
|
```
|
|
119
132
|
|
|
120
|
-
|
|
133
|
+
#### `effect.pre(fn: () => void): DisposeFn`
|
|
121
134
|
|
|
122
|
-
Create
|
|
135
|
+
Create an effect that runs synchronously (like `$effect.pre` in Svelte).
|
|
123
136
|
|
|
124
137
|
```typescript
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
name: 'John',
|
|
128
|
-
preferences: {
|
|
129
|
-
theme: 'dark'
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
items: [1, 2, 3]
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
effect(() => {
|
|
136
|
-
console.log(app.user.preferences.theme)
|
|
138
|
+
effect.pre(() => {
|
|
139
|
+
// Runs immediately, no flushSync needed
|
|
137
140
|
})
|
|
138
|
-
|
|
139
|
-
app.user.preferences.theme = 'light' // Triggers effect
|
|
140
|
-
app.items.push(4) // Arrays are reactive too
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
-
###
|
|
143
|
+
### Batching & Scheduling
|
|
144
|
+
|
|
145
|
+
#### `batch(fn: () => T): T`
|
|
144
146
|
|
|
145
|
-
Batch multiple updates into a single
|
|
147
|
+
Batch multiple signal updates into a single effect run.
|
|
146
148
|
|
|
147
149
|
```typescript
|
|
148
150
|
const a = signal(1)
|
|
149
151
|
const b = signal(2)
|
|
150
152
|
|
|
151
|
-
effect(() =>
|
|
152
|
-
console.log('Sum:', a.value + b.value)
|
|
153
|
-
})
|
|
154
|
-
// Logs: "Sum: 3"
|
|
153
|
+
effect(() => console.log(a.value + b.value))
|
|
155
154
|
|
|
156
155
|
batch(() => {
|
|
157
156
|
a.value = 10
|
|
158
157
|
b.value = 20
|
|
159
158
|
})
|
|
160
|
-
//
|
|
159
|
+
// Effect runs once with final values, not twice
|
|
161
160
|
```
|
|
162
161
|
|
|
163
|
-
|
|
162
|
+
#### `flushSync<T>(fn?: () => T): T | undefined`
|
|
164
163
|
|
|
165
|
-
|
|
164
|
+
Synchronously flush all pending effects.
|
|
166
165
|
|
|
167
166
|
```typescript
|
|
168
|
-
|
|
169
|
-
|
|
167
|
+
count.value = 5
|
|
168
|
+
flushSync() // Effects run NOW, not on next microtask
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
#### `tick(): Promise<void>`
|
|
172
|
+
|
|
173
|
+
Wait for the next update cycle.
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
count.value = 5
|
|
177
|
+
await tick() // Effects have run
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Utilities
|
|
181
|
+
|
|
182
|
+
#### `untrack<T>(fn: () => T): T`
|
|
170
183
|
|
|
184
|
+
Read signals without creating dependencies.
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
171
187
|
effect(() => {
|
|
172
|
-
|
|
173
|
-
untrack(() =>
|
|
174
|
-
console.log('b:', b.value) // Does NOT create dependency
|
|
175
|
-
})
|
|
188
|
+
const a = count.value // Creates dependency
|
|
189
|
+
const b = untrack(() => other.value) // No dependency
|
|
176
190
|
})
|
|
177
|
-
|
|
178
|
-
b.value = 100 // Effect does NOT re-run
|
|
179
|
-
a.value = 100 // Effect re-runs
|
|
180
191
|
```
|
|
181
192
|
|
|
182
|
-
|
|
193
|
+
#### `peek<T>(signal: Source<T>): T`
|
|
194
|
+
|
|
195
|
+
Read a signal's value without tracking (low-level).
|
|
196
|
+
|
|
197
|
+
### Deep Reactivity
|
|
198
|
+
|
|
199
|
+
#### `proxy<T extends object>(value: T): T`
|
|
183
200
|
|
|
184
|
-
|
|
201
|
+
Create a deeply reactive proxy (used internally by `state()`).
|
|
185
202
|
|
|
186
203
|
```typescript
|
|
187
|
-
const
|
|
204
|
+
const obj = proxy({ a: { b: { c: 1 } } })
|
|
205
|
+
obj.a.b.c = 2 // Only triggers effects reading a.b.c
|
|
206
|
+
```
|
|
188
207
|
|
|
189
|
-
|
|
190
|
-
() => count.value,
|
|
191
|
-
(newValue, oldValue) => {
|
|
192
|
-
console.log(`Changed from ${oldValue} to ${newValue}`)
|
|
193
|
-
}
|
|
194
|
-
)
|
|
208
|
+
#### `toRaw<T>(value: T): T`
|
|
195
209
|
|
|
196
|
-
|
|
210
|
+
Get the original object from a proxy.
|
|
197
211
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
() => count.value,
|
|
201
|
-
(value) => console.log('Value:', value),
|
|
202
|
-
{ immediate: true }
|
|
203
|
-
)
|
|
204
|
-
// Logs immediately: "Value: 1"
|
|
212
|
+
```typescript
|
|
213
|
+
const raw = toRaw(user) // Original non-reactive object
|
|
205
214
|
```
|
|
206
215
|
|
|
207
|
-
|
|
216
|
+
#### `isReactive(value: unknown): boolean`
|
|
208
217
|
|
|
209
|
-
|
|
218
|
+
Check if a value is a reactive proxy.
|
|
219
|
+
|
|
220
|
+
### Reactive Collections
|
|
221
|
+
|
|
222
|
+
#### `ReactiveMap<K, V>`
|
|
223
|
+
|
|
224
|
+
A Map with per-key reactivity.
|
|
210
225
|
|
|
211
226
|
```typescript
|
|
212
|
-
const
|
|
227
|
+
const users = new ReactiveMap<string, User>()
|
|
213
228
|
|
|
214
229
|
effect(() => {
|
|
215
|
-
console.log(
|
|
230
|
+
console.log(users.get('john')) // Only re-runs when 'john' changes
|
|
216
231
|
})
|
|
217
232
|
|
|
218
|
-
|
|
233
|
+
users.set('jane', { name: 'Jane' }) // Doesn't trigger above effect
|
|
219
234
|
```
|
|
220
235
|
|
|
221
|
-
|
|
236
|
+
#### `ReactiveSet<T>`
|
|
222
237
|
|
|
223
|
-
A
|
|
238
|
+
A Set with per-item reactivity.
|
|
224
239
|
|
|
225
240
|
```typescript
|
|
226
|
-
const
|
|
241
|
+
const tags = new ReactiveSet<string>()
|
|
227
242
|
|
|
228
243
|
effect(() => {
|
|
229
|
-
console.log(
|
|
244
|
+
console.log(tags.has('important')) // Only re-runs when 'important' changes
|
|
230
245
|
})
|
|
231
|
-
|
|
232
|
-
set.add('item') // Triggers effect
|
|
233
246
|
```
|
|
234
247
|
|
|
235
|
-
|
|
248
|
+
#### `ReactiveDate`
|
|
236
249
|
|
|
237
|
-
|
|
250
|
+
A Date with reactive getters/setters.
|
|
238
251
|
|
|
239
252
|
```typescript
|
|
240
|
-
const
|
|
253
|
+
const date = new ReactiveDate()
|
|
241
254
|
|
|
242
255
|
effect(() => {
|
|
243
|
-
console.log(
|
|
244
|
-
console.log('Length:', items.length)
|
|
256
|
+
console.log(date.getHours()) // Re-runs when time changes
|
|
245
257
|
})
|
|
246
258
|
|
|
247
|
-
|
|
248
|
-
items.push(4) // Triggers effect
|
|
259
|
+
date.setHours(12) // Triggers effect
|
|
249
260
|
```
|
|
250
261
|
|
|
251
|
-
|
|
262
|
+
## Advanced Usage
|
|
263
|
+
|
|
264
|
+
### Self-Referencing Effects
|
|
265
|
+
|
|
266
|
+
Effects can write to signals they depend on:
|
|
252
267
|
|
|
253
268
|
```typescript
|
|
254
|
-
// Create read-only view
|
|
255
269
|
const count = signal(0)
|
|
256
|
-
const ro = readonly(count)
|
|
257
|
-
// ro.value = 5 // TypeScript error
|
|
258
270
|
|
|
259
|
-
|
|
260
|
-
|
|
271
|
+
effect(() => {
|
|
272
|
+
if (count.value < 10) {
|
|
273
|
+
count.value++ // Will re-run until count reaches 10
|
|
274
|
+
}
|
|
275
|
+
})
|
|
276
|
+
```
|
|
261
277
|
|
|
262
|
-
|
|
263
|
-
isReactive(someObject)
|
|
278
|
+
**Note:** Unguarded self-references throw after 1000 iterations.
|
|
264
279
|
|
|
265
|
-
|
|
266
|
-
const raw = toRaw(reactiveObject)
|
|
280
|
+
### Custom Equality
|
|
267
281
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
282
|
+
```typescript
|
|
283
|
+
import { signal, shallowEquals } from '@rlabs-inc/signals'
|
|
284
|
+
|
|
285
|
+
const obj = signal({ a: 1 }, { equals: shallowEquals })
|
|
286
|
+
obj.value = { a: 1 } // Won't trigger - shallowly equal
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Built-in equality functions:
|
|
290
|
+
- `equals` - Default, uses `Object.is`
|
|
291
|
+
- `safeEquals` - Handles NaN correctly
|
|
292
|
+
- `shallowEquals` - Shallow object comparison
|
|
293
|
+
- `neverEquals` - Always triggers (always false)
|
|
294
|
+
- `alwaysEquals` - Never triggers (always true)
|
|
295
|
+
|
|
296
|
+
### Low-Level API
|
|
297
|
+
|
|
298
|
+
For advanced use cases, you can access internal primitives:
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
import { source, get, set } from '@rlabs-inc/signals'
|
|
302
|
+
|
|
303
|
+
// Create a raw source (no .value wrapper)
|
|
304
|
+
const src = source(0)
|
|
305
|
+
|
|
306
|
+
// Read with tracking
|
|
307
|
+
const value = get(src)
|
|
308
|
+
|
|
309
|
+
// Write with notification
|
|
310
|
+
set(src, 10)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Error Handling
|
|
314
|
+
|
|
315
|
+
### "Cannot write to signals inside a derived"
|
|
316
|
+
|
|
317
|
+
Deriveds must be pure computations:
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
// BAD - will throw
|
|
321
|
+
const bad = derived(() => {
|
|
322
|
+
otherSignal.value = 10 // Throws!
|
|
323
|
+
return count.value
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
// GOOD - use effects for side effects
|
|
327
|
+
effect(() => {
|
|
328
|
+
if (count.value > 0) {
|
|
329
|
+
otherSignal.value = count.value * 2
|
|
330
|
+
}
|
|
273
331
|
})
|
|
274
|
-
scope.stop() // Disposes all effects
|
|
275
332
|
```
|
|
276
333
|
|
|
277
|
-
|
|
334
|
+
### "Maximum update depth exceeded"
|
|
278
335
|
|
|
279
|
-
|
|
280
|
-
|----------|-------------------|
|
|
281
|
-
| `$state(value)` | `signal(value)` or `state(obj)` |
|
|
282
|
-
| `$derived(expr)` | `derived(() => expr)` |
|
|
283
|
-
| `$derived.by(fn)` | `derived.by(fn)` or `derived(fn)` |
|
|
284
|
-
| `$effect(fn)` | `effect(fn)` |
|
|
285
|
-
| `SvelteMap` | `ReactiveMap` |
|
|
286
|
-
| `SvelteSet` | `ReactiveSet` |
|
|
336
|
+
Your effect is infinitely re-triggering itself:
|
|
287
337
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
338
|
+
```typescript
|
|
339
|
+
// BAD - infinite loop
|
|
340
|
+
effect(() => {
|
|
341
|
+
count.value = count.value + 1 // Always triggers itself
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
// GOOD - add a guard
|
|
345
|
+
effect(() => {
|
|
346
|
+
if (count.value < 100) {
|
|
347
|
+
count.value++
|
|
348
|
+
}
|
|
349
|
+
})
|
|
350
|
+
```
|
|
292
351
|
|
|
293
|
-
##
|
|
352
|
+
## Performance
|
|
294
353
|
|
|
295
|
-
|
|
296
|
-
1. We love Svelte 5's reactivity model
|
|
297
|
-
2. We wanted to use it outside Svelte components
|
|
298
|
-
3. We didn't want the overhead of Happy DOM for server-side usage
|
|
299
|
-
4. We needed a lightweight solution for libraries like [FatherStateDB](https://github.com/rlabs-inc/fatherstatedb)
|
|
354
|
+
This library is designed for performance:
|
|
300
355
|
|
|
301
|
-
|
|
356
|
+
- **Lazy evaluation** - Deriveds only compute when read
|
|
357
|
+
- **Version-based deduplication** - No duplicate dependency tracking
|
|
358
|
+
- **Linked list effect tree** - O(1) effect insertion/removal
|
|
359
|
+
- **Microtask batching** - Updates coalesce automatically
|
|
360
|
+
- **Per-property signals** - Fine-grained updates at any depth
|
|
302
361
|
|
|
303
|
-
|
|
362
|
+
## Comparison with Svelte 5
|
|
304
363
|
|
|
305
|
-
|
|
364
|
+
| Feature | Svelte 5 | @rlabs-inc/signals |
|
|
365
|
+
|---------|----------|-------------------|
|
|
366
|
+
| Compiler required | Yes | No |
|
|
367
|
+
| DOM integration | Yes | No |
|
|
368
|
+
| Fine-grained reactivity | Yes | Yes |
|
|
369
|
+
| Deep proxy reactivity | Yes | Yes |
|
|
370
|
+
| Batching | Yes | Yes |
|
|
371
|
+
| Effect cleanup | Yes | Yes |
|
|
372
|
+
| TypeScript | Yes | Yes |
|
|
373
|
+
| Runs in Node/Bun | Needs adapter | Native |
|
|
306
374
|
|
|
307
|
-
|
|
375
|
+
## License
|
|
376
|
+
|
|
377
|
+
MIT
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A reactive Date
|
|
3
|
+
*
|
|
4
|
+
* All getters are reactive - they track changes to the underlying time.
|
|
5
|
+
* All setters trigger updates.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const date = new ReactiveDate()
|
|
10
|
+
*
|
|
11
|
+
* effect(() => {
|
|
12
|
+
* console.log('Hours:', date.getHours())
|
|
13
|
+
* })
|
|
14
|
+
*
|
|
15
|
+
* date.setHours(12) // Triggers effect
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare class ReactiveDate extends Date {
|
|
19
|
+
#private;
|
|
20
|
+
constructor();
|
|
21
|
+
constructor(value: number | string | Date);
|
|
22
|
+
constructor(year: number, monthIndex: number, date?: number, hours?: number, minutes?: number, seconds?: number, ms?: number);
|
|
23
|
+
getTime(): number;
|
|
24
|
+
getFullYear(): number;
|
|
25
|
+
getMonth(): number;
|
|
26
|
+
getDate(): number;
|
|
27
|
+
getDay(): number;
|
|
28
|
+
getHours(): number;
|
|
29
|
+
getMinutes(): number;
|
|
30
|
+
getSeconds(): number;
|
|
31
|
+
getMilliseconds(): number;
|
|
32
|
+
getUTCFullYear(): number;
|
|
33
|
+
getUTCMonth(): number;
|
|
34
|
+
getUTCDate(): number;
|
|
35
|
+
getUTCDay(): number;
|
|
36
|
+
getUTCHours(): number;
|
|
37
|
+
getUTCMinutes(): number;
|
|
38
|
+
getUTCSeconds(): number;
|
|
39
|
+
getUTCMilliseconds(): number;
|
|
40
|
+
getTimezoneOffset(): number;
|
|
41
|
+
setTime(time: number): number;
|
|
42
|
+
setFullYear(year: number, month?: number, date?: number): number;
|
|
43
|
+
setMonth(month: number, date?: number): number;
|
|
44
|
+
setDate(date: number): number;
|
|
45
|
+
setHours(hours: number, min?: number, sec?: number, ms?: number): number;
|
|
46
|
+
setMinutes(min: number, sec?: number, ms?: number): number;
|
|
47
|
+
setSeconds(sec: number, ms?: number): number;
|
|
48
|
+
setMilliseconds(ms: number): number;
|
|
49
|
+
setUTCFullYear(year: number, month?: number, date?: number): number;
|
|
50
|
+
setUTCMonth(month: number, date?: number): number;
|
|
51
|
+
setUTCDate(date: number): number;
|
|
52
|
+
setUTCHours(hours: number, min?: number, sec?: number, ms?: number): number;
|
|
53
|
+
setUTCMinutes(min: number, sec?: number, ms?: number): number;
|
|
54
|
+
setUTCSeconds(sec: number, ms?: number): number;
|
|
55
|
+
setUTCMilliseconds(ms: number): number;
|
|
56
|
+
toString(): string;
|
|
57
|
+
toDateString(): string;
|
|
58
|
+
toTimeString(): string;
|
|
59
|
+
toISOString(): string;
|
|
60
|
+
toUTCString(): string;
|
|
61
|
+
toLocaleString(locales?: string | string[], options?: Intl.DateTimeFormatOptions): string;
|
|
62
|
+
toLocaleDateString(locales?: string | string[], options?: Intl.DateTimeFormatOptions): string;
|
|
63
|
+
toLocaleTimeString(locales?: string | string[], options?: Intl.DateTimeFormatOptions): string;
|
|
64
|
+
toJSON(): string;
|
|
65
|
+
valueOf(): number;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=date.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date.d.ts","sourceRoot":"","sources":["../../src/collections/date.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A reactive Map with per-key granularity
|
|
3
|
+
*
|
|
4
|
+
* Three levels of reactivity:
|
|
5
|
+
* 1. Per-key signals: map.get('key') only tracks that specific key
|
|
6
|
+
* 2. Version signal: Tracks structural changes (add/delete)
|
|
7
|
+
* 3. Size signal: Tracks map size changes
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* const users = new ReactiveMap<string, User>()
|
|
12
|
+
*
|
|
13
|
+
* effect(() => {
|
|
14
|
+
* // Only re-runs when 'alice' changes
|
|
15
|
+
* console.log(users.get('alice'))
|
|
16
|
+
* })
|
|
17
|
+
*
|
|
18
|
+
* users.set('bob', { name: 'Bob' }) // Doesn't trigger above effect
|
|
19
|
+
* users.set('alice', { name: 'Alice Updated' }) // Triggers effect
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare class ReactiveMap<K, V> extends Map<K, V> {
|
|
23
|
+
#private;
|
|
24
|
+
constructor(entries?: Iterable<readonly [K, V]> | null);
|
|
25
|
+
get size(): number;
|
|
26
|
+
has(key: K): boolean;
|
|
27
|
+
get(key: K): V | undefined;
|
|
28
|
+
set(key: K, value: V): this;
|
|
29
|
+
delete(key: K): boolean;
|
|
30
|
+
clear(): void;
|
|
31
|
+
keys(): MapIterator<K>;
|
|
32
|
+
values(): MapIterator<V>;
|
|
33
|
+
entries(): MapIterator<[K, V]>;
|
|
34
|
+
forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: unknown): void;
|
|
35
|
+
[Symbol.iterator](): MapIterator<[K, V]>;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"map.d.ts","sourceRoot":"","sources":["../../src/collections/map.ts"],"names":[],"mappings":"AAcA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,WAAW,CAAC,CAAC,EAAE,CAAC,CAAE,SAAQ,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;;gBAUlC,OAAO,CAAC,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI;IA4BtD,IAAI,IAAI,IAAI,MAAM,CAGjB;IAMD,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAwBpB,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS;IA0B1B,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAsC3B,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAwBvB,KAAK,IAAI,IAAI;IAmBb,IAAI,IAAI,WAAW,CAAC,CAAC,CAAC;IAKtB,MAAM,IAAI,WAAW,CAAC,CAAC,CAAC;IAKxB,OAAO,IAAI,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAK9B,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI;IAKxF,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;CAGzC"}
|