@rlabs-inc/signals 0.2.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 +238 -198
- 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 -277
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1233 -607
- package/dist/index.mjs +1232 -608
- 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,88 +27,84 @@ npm install @rlabs-inc/signals
|
|
|
25
27
|
## Quick Start
|
|
26
28
|
|
|
27
29
|
```typescript
|
|
28
|
-
import { signal, effect,
|
|
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 = signal({
|
|
48
|
-
name: 'John',
|
|
49
|
-
address: {
|
|
50
|
-
city: 'NYC'
|
|
51
|
-
}
|
|
52
|
-
})
|
|
51
|
+
## API Reference
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
console.log('City:', user.value.address.city)
|
|
56
|
-
})
|
|
57
|
-
// Logs: "City: NYC"
|
|
53
|
+
### Signals
|
|
58
54
|
|
|
59
|
-
|
|
60
|
-
// Logs: "City: LA" - mutations at any depth trigger effects!
|
|
55
|
+
#### `signal<T>(initialValue: T, options?): WritableSignal<T>`
|
|
61
56
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
Create a reactive value with `.value` getter/setter.
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
const name = signal('John')
|
|
61
|
+
console.log(name.value) // 'John'
|
|
62
|
+
name.value = 'Jane' // Triggers effects
|
|
65
63
|
```
|
|
66
64
|
|
|
67
|
-
|
|
65
|
+
**Options:**
|
|
66
|
+
- `equals?: (a: T, b: T) => boolean` - Custom equality function
|
|
68
67
|
|
|
69
|
-
|
|
68
|
+
#### `state<T extends object>(initialValue: T): T`
|
|
70
69
|
|
|
71
|
-
Create a reactive
|
|
70
|
+
Create a deeply reactive object. No `.value` needed - access properties directly.
|
|
72
71
|
|
|
73
72
|
```typescript
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
console.log(count.value) // 5
|
|
73
|
+
const user = state({ name: 'John', address: { city: 'NYC' } })
|
|
74
|
+
user.name = 'Jane' // Reactive
|
|
75
|
+
user.address.city = 'LA' // Also reactive, deeply
|
|
76
|
+
```
|
|
79
77
|
|
|
80
|
-
|
|
81
|
-
const user = signal({ name: 'John', address: { city: 'NYC' } })
|
|
82
|
-
user.value.address.city = 'LA' // Triggers effects!
|
|
78
|
+
### Derived Values
|
|
83
79
|
|
|
84
|
-
|
|
85
|
-
const matrix = signal([[1, 2], [3, 4]])
|
|
86
|
-
matrix.value[0][1] = 99 // Triggers effects!
|
|
87
|
-
matrix.value.push([5, 6]) // Triggers effects!
|
|
80
|
+
#### `derived<T>(fn: () => T): DerivedSignal<T>`
|
|
88
81
|
|
|
89
|
-
|
|
90
|
-
const complex = signal({
|
|
91
|
-
level1: [{ level2: [[{ level3: 'value' }]] }]
|
|
92
|
-
})
|
|
93
|
-
complex.value.level1[0].level2[0][0].level3 = 'mutated' // Triggers!
|
|
82
|
+
Create a computed value that automatically updates when dependencies change.
|
|
94
83
|
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
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'
|
|
99
90
|
```
|
|
100
91
|
|
|
101
|
-
|
|
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
|
|
102
98
|
|
|
103
|
-
|
|
99
|
+
#### `effect(fn: () => void | CleanupFn): DisposeFn`
|
|
100
|
+
|
|
101
|
+
Create a side effect that re-runs when dependencies change.
|
|
104
102
|
|
|
105
103
|
```typescript
|
|
106
|
-
const
|
|
104
|
+
const count = signal(0)
|
|
107
105
|
|
|
108
106
|
const dispose = effect(() => {
|
|
109
|
-
console.log('
|
|
107
|
+
console.log('Count is:', count.value)
|
|
110
108
|
|
|
111
109
|
// Optional cleanup function
|
|
112
110
|
return () => {
|
|
@@ -114,224 +112,266 @@ const dispose = effect(() => {
|
|
|
114
112
|
}
|
|
115
113
|
})
|
|
116
114
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
dispose() // Stop the effect
|
|
115
|
+
// Stop the effect
|
|
116
|
+
dispose()
|
|
120
117
|
```
|
|
121
118
|
|
|
122
|
-
|
|
119
|
+
#### `effect.root(fn: () => T): DisposeFn`
|
|
123
120
|
|
|
124
|
-
Create
|
|
121
|
+
Create an effect scope that can contain nested effects.
|
|
125
122
|
|
|
126
123
|
```typescript
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
count.value = 10
|
|
133
|
-
console.log(doubled.value) // 20
|
|
124
|
+
const dispose = effect.root(() => {
|
|
125
|
+
effect(() => { /* ... */ })
|
|
126
|
+
effect(() => { /* ... */ })
|
|
127
|
+
})
|
|
134
128
|
|
|
135
|
-
//
|
|
136
|
-
|
|
129
|
+
// Disposes all nested effects
|
|
130
|
+
dispose()
|
|
137
131
|
```
|
|
138
132
|
|
|
139
|
-
|
|
133
|
+
#### `effect.pre(fn: () => void): DisposeFn`
|
|
140
134
|
|
|
141
|
-
|
|
135
|
+
Create an effect that runs synchronously (like `$effect.pre` in Svelte).
|
|
142
136
|
|
|
143
137
|
```typescript
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
user: {
|
|
147
|
-
name: 'John',
|
|
148
|
-
preferences: {
|
|
149
|
-
theme: 'dark'
|
|
150
|
-
}
|
|
151
|
-
},
|
|
152
|
-
items: [1, 2, 3]
|
|
138
|
+
effect.pre(() => {
|
|
139
|
+
// Runs immediately, no flushSync needed
|
|
153
140
|
})
|
|
154
|
-
|
|
155
|
-
effect(() => {
|
|
156
|
-
console.log(app.user.preferences.theme) // No .value!
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
app.user.preferences.theme = 'light' // Triggers effect
|
|
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
|
|
165
141
|
```
|
|
166
142
|
|
|
167
|
-
|
|
168
|
-
- `signal()` - Unified API, always use `.value` (recommended)
|
|
169
|
-
- `state()` - When you want direct property access without `.value`
|
|
143
|
+
### Batching & Scheduling
|
|
170
144
|
|
|
171
|
-
|
|
145
|
+
#### `batch(fn: () => T): T`
|
|
172
146
|
|
|
173
|
-
Batch multiple updates into a single
|
|
147
|
+
Batch multiple signal updates into a single effect run.
|
|
174
148
|
|
|
175
149
|
```typescript
|
|
176
150
|
const a = signal(1)
|
|
177
151
|
const b = signal(2)
|
|
178
152
|
|
|
179
|
-
effect(() =>
|
|
180
|
-
console.log('Sum:', a.value + b.value)
|
|
181
|
-
})
|
|
182
|
-
// Logs: "Sum: 3"
|
|
153
|
+
effect(() => console.log(a.value + b.value))
|
|
183
154
|
|
|
184
155
|
batch(() => {
|
|
185
156
|
a.value = 10
|
|
186
157
|
b.value = 20
|
|
187
158
|
})
|
|
188
|
-
//
|
|
159
|
+
// Effect runs once with final values, not twice
|
|
189
160
|
```
|
|
190
161
|
|
|
191
|
-
|
|
162
|
+
#### `flushSync<T>(fn?: () => T): T | undefined`
|
|
192
163
|
|
|
193
|
-
|
|
164
|
+
Synchronously flush all pending effects.
|
|
194
165
|
|
|
195
166
|
```typescript
|
|
196
|
-
|
|
197
|
-
|
|
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
|
+
```
|
|
198
179
|
|
|
180
|
+
### Utilities
|
|
181
|
+
|
|
182
|
+
#### `untrack<T>(fn: () => T): T`
|
|
183
|
+
|
|
184
|
+
Read signals without creating dependencies.
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
199
187
|
effect(() => {
|
|
200
|
-
|
|
201
|
-
untrack(() =>
|
|
202
|
-
console.log('b:', b.value) // Does NOT create dependency
|
|
203
|
-
})
|
|
188
|
+
const a = count.value // Creates dependency
|
|
189
|
+
const b = untrack(() => other.value) // No dependency
|
|
204
190
|
})
|
|
205
|
-
|
|
206
|
-
b.value = 100 // Effect does NOT re-run
|
|
207
|
-
a.value = 100 // Effect re-runs
|
|
208
191
|
```
|
|
209
192
|
|
|
210
|
-
|
|
193
|
+
#### `peek<T>(signal: Source<T>): T`
|
|
211
194
|
|
|
212
|
-
|
|
195
|
+
Read a signal's value without tracking (low-level).
|
|
196
|
+
|
|
197
|
+
### Deep Reactivity
|
|
198
|
+
|
|
199
|
+
#### `proxy<T extends object>(value: T): T`
|
|
200
|
+
|
|
201
|
+
Create a deeply reactive proxy (used internally by `state()`).
|
|
213
202
|
|
|
214
203
|
```typescript
|
|
215
|
-
const
|
|
204
|
+
const obj = proxy({ a: { b: { c: 1 } } })
|
|
205
|
+
obj.a.b.c = 2 // Only triggers effects reading a.b.c
|
|
206
|
+
```
|
|
216
207
|
|
|
217
|
-
|
|
218
|
-
() => count.value,
|
|
219
|
-
(newValue, oldValue) => {
|
|
220
|
-
console.log(`Changed from ${oldValue} to ${newValue}`)
|
|
221
|
-
}
|
|
222
|
-
)
|
|
208
|
+
#### `toRaw<T>(value: T): T`
|
|
223
209
|
|
|
224
|
-
|
|
210
|
+
Get the original object from a proxy.
|
|
225
211
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
() => count.value,
|
|
229
|
-
(value) => console.log('Value:', value),
|
|
230
|
-
{ immediate: true }
|
|
231
|
-
)
|
|
232
|
-
// Logs immediately: "Value: 1"
|
|
212
|
+
```typescript
|
|
213
|
+
const raw = toRaw(user) // Original non-reactive object
|
|
233
214
|
```
|
|
234
215
|
|
|
235
|
-
|
|
216
|
+
#### `isReactive(value: unknown): boolean`
|
|
217
|
+
|
|
218
|
+
Check if a value is a reactive proxy.
|
|
236
219
|
|
|
237
|
-
|
|
220
|
+
### Reactive Collections
|
|
221
|
+
|
|
222
|
+
#### `ReactiveMap<K, V>`
|
|
223
|
+
|
|
224
|
+
A Map with per-key reactivity.
|
|
238
225
|
|
|
239
226
|
```typescript
|
|
240
|
-
const
|
|
227
|
+
const users = new ReactiveMap<string, User>()
|
|
241
228
|
|
|
242
229
|
effect(() => {
|
|
243
|
-
console.log(
|
|
230
|
+
console.log(users.get('john')) // Only re-runs when 'john' changes
|
|
244
231
|
})
|
|
245
232
|
|
|
246
|
-
|
|
233
|
+
users.set('jane', { name: 'Jane' }) // Doesn't trigger above effect
|
|
247
234
|
```
|
|
248
235
|
|
|
249
|
-
|
|
236
|
+
#### `ReactiveSet<T>`
|
|
250
237
|
|
|
251
|
-
A
|
|
238
|
+
A Set with per-item reactivity.
|
|
252
239
|
|
|
253
240
|
```typescript
|
|
254
|
-
const
|
|
241
|
+
const tags = new ReactiveSet<string>()
|
|
255
242
|
|
|
256
243
|
effect(() => {
|
|
257
|
-
console.log(
|
|
244
|
+
console.log(tags.has('important')) // Only re-runs when 'important' changes
|
|
258
245
|
})
|
|
259
|
-
|
|
260
|
-
set.add('item') // Triggers effect
|
|
261
246
|
```
|
|
262
247
|
|
|
263
|
-
|
|
248
|
+
#### `ReactiveDate`
|
|
264
249
|
|
|
265
|
-
|
|
250
|
+
A Date with reactive getters/setters.
|
|
266
251
|
|
|
267
252
|
```typescript
|
|
268
|
-
const
|
|
253
|
+
const date = new ReactiveDate()
|
|
269
254
|
|
|
270
255
|
effect(() => {
|
|
271
|
-
console.log(
|
|
272
|
-
console.log('Length:', items.length)
|
|
256
|
+
console.log(date.getHours()) // Re-runs when time changes
|
|
273
257
|
})
|
|
274
258
|
|
|
275
|
-
|
|
276
|
-
items.push(4) // Triggers effect
|
|
259
|
+
date.setHours(12) // Triggers effect
|
|
277
260
|
```
|
|
278
261
|
|
|
279
|
-
|
|
262
|
+
## Advanced Usage
|
|
263
|
+
|
|
264
|
+
### Self-Referencing Effects
|
|
265
|
+
|
|
266
|
+
Effects can write to signals they depend on:
|
|
280
267
|
|
|
281
268
|
```typescript
|
|
282
|
-
// Create read-only view
|
|
283
269
|
const count = signal(0)
|
|
284
|
-
const ro = readonly(count)
|
|
285
|
-
// ro.value = 5 // TypeScript error
|
|
286
270
|
|
|
287
|
-
|
|
288
|
-
|
|
271
|
+
effect(() => {
|
|
272
|
+
if (count.value < 10) {
|
|
273
|
+
count.value++ // Will re-run until count reaches 10
|
|
274
|
+
}
|
|
275
|
+
})
|
|
276
|
+
```
|
|
289
277
|
|
|
290
|
-
|
|
291
|
-
isReactive(someObject)
|
|
278
|
+
**Note:** Unguarded self-references throw after 1000 iterations.
|
|
292
279
|
|
|
293
|
-
|
|
294
|
-
const raw = toRaw(reactiveObject)
|
|
280
|
+
### Custom Equality
|
|
295
281
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
+
}
|
|
331
|
+
})
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### "Maximum update depth exceeded"
|
|
335
|
+
|
|
336
|
+
Your effect is infinitely re-triggering itself:
|
|
337
|
+
|
|
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
|
+
}
|
|
301
349
|
})
|
|
302
|
-
scope.stop() // Disposes all effects
|
|
303
350
|
```
|
|
304
351
|
|
|
352
|
+
## Performance
|
|
353
|
+
|
|
354
|
+
This library is designed for performance:
|
|
355
|
+
|
|
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
|
|
361
|
+
|
|
305
362
|
## Comparison with Svelte 5
|
|
306
363
|
|
|
307
|
-
| Svelte 5 | @rlabs-inc/signals |
|
|
308
|
-
|
|
309
|
-
|
|
|
310
|
-
|
|
|
311
|
-
|
|
|
312
|
-
|
|
|
313
|
-
|
|
|
314
|
-
|
|
|
315
|
-
|
|
|
316
|
-
|
|
317
|
-
**Key differences:**
|
|
318
|
-
- No compiler needed - works with plain TypeScript
|
|
319
|
-
- Use `.value` to read/write signals (like Vue/Solid)
|
|
320
|
-
- No Happy DOM or browser environment required
|
|
321
|
-
- `signal()` is deeply reactive out of the box - arrays of arrays of objects just work
|
|
322
|
-
|
|
323
|
-
## Why?
|
|
324
|
-
|
|
325
|
-
We built this because:
|
|
326
|
-
1. We love Svelte 5's reactivity model
|
|
327
|
-
2. We wanted to use it outside Svelte components
|
|
328
|
-
3. We didn't want the overhead of Happy DOM for server-side usage
|
|
329
|
-
4. We needed a lightweight solution for libraries like [FatherStateDB](https://github.com/rlabs-inc/fatherstatedb)
|
|
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 |
|
|
330
374
|
|
|
331
375
|
## License
|
|
332
376
|
|
|
333
377
|
MIT
|
|
334
|
-
|
|
335
|
-
---
|
|
336
|
-
|
|
337
|
-
Built with ❤️ by RLabs Inc.
|
|
@@ -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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"}
|