@fictjs/runtime 0.0.2

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.
Files changed (51) hide show
  1. package/README.md +17 -0
  2. package/dist/index.cjs +4224 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +1572 -0
  5. package/dist/index.d.ts +1572 -0
  6. package/dist/index.dev.js +4240 -0
  7. package/dist/index.dev.js.map +1 -0
  8. package/dist/index.js +4133 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/jsx-dev-runtime.cjs +44 -0
  11. package/dist/jsx-dev-runtime.cjs.map +1 -0
  12. package/dist/jsx-dev-runtime.js +14 -0
  13. package/dist/jsx-dev-runtime.js.map +1 -0
  14. package/dist/jsx-runtime.cjs +44 -0
  15. package/dist/jsx-runtime.cjs.map +1 -0
  16. package/dist/jsx-runtime.js +14 -0
  17. package/dist/jsx-runtime.js.map +1 -0
  18. package/dist/slim.cjs +3384 -0
  19. package/dist/slim.cjs.map +1 -0
  20. package/dist/slim.d.cts +475 -0
  21. package/dist/slim.d.ts +475 -0
  22. package/dist/slim.js +3335 -0
  23. package/dist/slim.js.map +1 -0
  24. package/package.json +68 -0
  25. package/src/binding.ts +2127 -0
  26. package/src/constants.ts +456 -0
  27. package/src/cycle-guard.ts +134 -0
  28. package/src/devtools.ts +17 -0
  29. package/src/dom.ts +683 -0
  30. package/src/effect.ts +83 -0
  31. package/src/error-boundary.ts +118 -0
  32. package/src/hooks.ts +72 -0
  33. package/src/index.ts +184 -0
  34. package/src/jsx-dev-runtime.ts +2 -0
  35. package/src/jsx-runtime.ts +2 -0
  36. package/src/jsx.ts +786 -0
  37. package/src/lifecycle.ts +273 -0
  38. package/src/list-helpers.ts +619 -0
  39. package/src/memo.ts +14 -0
  40. package/src/node-ops.ts +185 -0
  41. package/src/props.ts +212 -0
  42. package/src/reconcile.ts +151 -0
  43. package/src/ref.ts +25 -0
  44. package/src/scheduler.ts +12 -0
  45. package/src/signal.ts +1278 -0
  46. package/src/slim.ts +68 -0
  47. package/src/store.ts +210 -0
  48. package/src/suspense.ts +187 -0
  49. package/src/transition.ts +128 -0
  50. package/src/types.ts +172 -0
  51. package/src/versioned-signal.ts +58 -0
package/src/signal.ts ADDED
@@ -0,0 +1,1278 @@
1
+ import { beginFlushGuard, beforeEffectRunGuard, endFlushGuard } from './cycle-guard'
2
+ import { getDevtoolsHook } from './devtools'
3
+ import { registerRootCleanup } from './lifecycle'
4
+
5
+ // ============================================================================
6
+ // Type Definitions
7
+ // ============================================================================
8
+
9
+ /**
10
+ * Reactive node that can be either a signal, computed, effect, or effect scope
11
+ */
12
+ export type ReactiveNode =
13
+ | SignalNode<unknown>
14
+ | ComputedNode<unknown>
15
+ | EffectNode
16
+ | EffectScopeNode
17
+ | SubscriberNode
18
+
19
+ /**
20
+ * Link between a dependency and a subscriber in the reactive graph
21
+ */
22
+ export interface Link {
23
+ /** Version/cycle when this link was created */
24
+ version: number
25
+ /** The dependency being tracked */
26
+ dep: ReactiveNode
27
+ /** The subscriber tracking this dependency */
28
+ sub: ReactiveNode
29
+ /** Previous dependency link in the subscriber's dependency list */
30
+ prevDep: Link | undefined
31
+ /** Next dependency link in the subscriber's dependency list */
32
+ nextDep: Link | undefined
33
+ /** Previous subscriber link in the dependency's subscriber list */
34
+ prevSub: Link | undefined
35
+ /** Next subscriber link in the dependency's subscriber list */
36
+ nextSub: Link | undefined
37
+ }
38
+
39
+ /**
40
+ * Stack frame for traversing the reactive graph
41
+ */
42
+ export interface StackFrame {
43
+ /** The link value at this stack level */
44
+ value: Link | undefined
45
+ /** Previous stack frame */
46
+ prev: StackFrame | undefined
47
+ }
48
+
49
+ /**
50
+ * Base interface for all reactive nodes
51
+ */
52
+ export interface BaseNode {
53
+ /** First subscriber link */
54
+ subs: Link | undefined
55
+ /** Last subscriber link */
56
+ subsTail: Link | undefined
57
+ /** Reactive flags (Mutable, Watching, Running, etc.) */
58
+ flags: number
59
+ }
60
+
61
+ /**
62
+ * Signal node - mutable reactive value
63
+ */
64
+ export interface SignalNode<T = unknown> extends BaseNode {
65
+ /** Current committed value */
66
+ currentValue: T
67
+ /** Pending value to be committed */
68
+ pendingValue: T
69
+ /** Signals don't have dependencies */
70
+ deps?: undefined
71
+ depsTail?: undefined
72
+ getter?: undefined
73
+ }
74
+
75
+ /**
76
+ * Computed node - derived reactive value
77
+ */
78
+ export interface ComputedNode<T = unknown> extends BaseNode {
79
+ /** Current computed value */
80
+ value: T
81
+ /** First dependency link */
82
+ deps: Link | undefined
83
+ /** Last dependency link */
84
+ depsTail: Link | undefined
85
+ /** Getter function to compute the value */
86
+ getter: (oldValue: T | undefined) => T
87
+ }
88
+
89
+ /**
90
+ * Effect node - side effect that runs when dependencies change
91
+ */
92
+ export interface EffectNode extends BaseNode {
93
+ /** Effect function to execute */
94
+ fn: () => void
95
+ /** First dependency link */
96
+ deps: Link | undefined
97
+ /** Last dependency link */
98
+ depsTail: Link | undefined
99
+ }
100
+
101
+ /**
102
+ * Effect scope node - manages multiple effects
103
+ */
104
+ export interface EffectScopeNode extends BaseNode {
105
+ /** First dependency link */
106
+ deps: Link | undefined
107
+ /** Last dependency link */
108
+ depsTail: Link | undefined
109
+ }
110
+
111
+ /**
112
+ * Subscriber node used in trigger
113
+ */
114
+ export interface SubscriberNode {
115
+ /** First dependency link */
116
+ deps: Link | undefined
117
+ /** Last dependency link */
118
+ depsTail: Link | undefined
119
+ /** Reactive flags */
120
+ flags: number
121
+ subs?: undefined
122
+ subsTail?: undefined
123
+ }
124
+
125
+ /**
126
+ * Signal accessor - function to get/set signal value
127
+ */
128
+ export interface SignalAccessor<T> {
129
+ (): T
130
+ (value: T): void
131
+ }
132
+
133
+ /**
134
+ * Computed accessor - function to get computed value
135
+ */
136
+ export type ComputedAccessor<T> = () => T
137
+
138
+ /**
139
+ * Effect disposer - function to dispose an effect
140
+ */
141
+ export type EffectDisposer = () => void
142
+
143
+ /**
144
+ * Effect scope disposer - function to dispose an effect scope
145
+ */
146
+ export type EffectScopeDisposer = () => void
147
+
148
+ /**
149
+ * Options for creating a custom reactive system
150
+ */
151
+ export interface ReactiveSystemOptions {
152
+ /** Update function for reactive nodes */
153
+ update: (node: ReactiveNode) => boolean
154
+ /** Notify function when a subscriber needs to be notified */
155
+ notify: (sub: ReactiveNode) => void
156
+ /** Callback when a dependency becomes unwatched */
157
+ unwatched: (dep: ReactiveNode) => void
158
+ }
159
+
160
+ /**
161
+ * Custom reactive system methods
162
+ */
163
+ export interface ReactiveSystem {
164
+ /** Link a dependency to a subscriber */
165
+ link: typeof link
166
+ /** Unlink a dependency from a subscriber */
167
+ unlink: (lnk: Link, sub?: ReactiveNode) => Link | undefined
168
+ /** Propagate changes through the reactive graph */
169
+ propagate: (firstLink: Link) => void
170
+ /** Check if a node is dirty */
171
+ checkDirty: (firstLink: Link, sub: ReactiveNode) => boolean
172
+ /** Shallow propagate changes */
173
+ shallowPropagate: (firstLink: Link) => void
174
+ }
175
+
176
+ // ============================================================================
177
+ // Flags
178
+ // ============================================================================
179
+ const Mutable = 1
180
+ const Watching = 2
181
+ const Running = 4
182
+ const Recursed = 8
183
+ const Dirty = 16
184
+ const Pending = 32
185
+ // Pre-computed combinations
186
+ const MutableDirty = 17
187
+ const MutablePending = 33
188
+ const MutableRunning = 5
189
+ const WatchingRunning = 6
190
+ // Global state
191
+ let cycle = 0
192
+ let batchDepth = 0
193
+ let activeSub: ReactiveNode | undefined
194
+ let flushScheduled = false
195
+ // Dual-priority queue for scheduler
196
+ const highPriorityQueue: EffectNode[] = []
197
+ const lowPriorityQueue: EffectNode[] = []
198
+ let isInTransition = false
199
+ const enqueueMicrotask =
200
+ typeof queueMicrotask === 'function'
201
+ ? queueMicrotask
202
+ : (fn: () => void) => {
203
+ Promise.resolve().then(fn)
204
+ }
205
+ export const ReactiveFlags = {
206
+ None: 0,
207
+ Mutable,
208
+ Watching,
209
+ RecursedCheck: Running,
210
+ Recursed,
211
+ Dirty,
212
+ Pending,
213
+ }
214
+ // ============================================================================
215
+ // createReactiveSystem - Support for custom systems
216
+ // ============================================================================
217
+ /**
218
+ * Create a custom reactive system with custom update, notify, and unwatched handlers
219
+ * @param options - Reactive system options
220
+ * @returns Custom reactive system methods
221
+ */
222
+ export function createReactiveSystem({
223
+ update,
224
+ notify: notifyFn,
225
+ unwatched: unwatchedFn,
226
+ }: ReactiveSystemOptions): ReactiveSystem {
227
+ function customPropagate(firstLink: Link): void {
228
+ let link = firstLink
229
+ let next = link.nextSub
230
+ let stack: StackFrame | undefined
231
+
232
+ top: for (;;) {
233
+ const sub = link.sub
234
+ let flags = sub.flags
235
+
236
+ if (!(flags & 60)) {
237
+ sub.flags = flags | Pending
238
+ } else if (!(flags & 12)) {
239
+ flags = 0
240
+ } else if (!(flags & Running)) {
241
+ sub.flags = (flags & ~Recursed) | Pending
242
+ } else if (!(flags & 48)) {
243
+ let vlink = sub.depsTail
244
+ let valid = false
245
+ while (vlink !== undefined) {
246
+ if (vlink === link) {
247
+ valid = true
248
+ break
249
+ }
250
+ vlink = vlink.prevDep
251
+ }
252
+ if (valid) {
253
+ sub.flags = flags | 40
254
+ flags &= Mutable
255
+ } else {
256
+ flags = 0
257
+ }
258
+ } else {
259
+ flags = 0
260
+ }
261
+
262
+ if (flags & Watching) notifyFn(sub)
263
+
264
+ if (flags & Mutable) {
265
+ const subSubs = sub.subs
266
+ if (subSubs !== undefined) {
267
+ const nextSub = subSubs.nextSub
268
+ if (nextSub !== undefined) {
269
+ stack = { value: next, prev: stack }
270
+ next = nextSub
271
+ }
272
+ link = subSubs
273
+ continue
274
+ }
275
+ }
276
+
277
+ if (next !== undefined) {
278
+ link = next
279
+ next = link.nextSub
280
+ continue
281
+ }
282
+
283
+ while (stack !== undefined) {
284
+ link = stack.value!
285
+ stack = stack.prev
286
+ if (link !== undefined) {
287
+ next = link.nextSub
288
+ continue top
289
+ }
290
+ }
291
+ break
292
+ }
293
+ }
294
+ function customCheckDirty(firstLink: Link, sub: ReactiveNode): boolean {
295
+ let link = firstLink
296
+ let stack: StackFrame | undefined
297
+ let checkDepth = 0
298
+ let dirty = false
299
+
300
+ top: for (;;) {
301
+ const dep = link.dep
302
+ const depFlags = dep.flags
303
+
304
+ if (sub.flags & Dirty) {
305
+ dirty = true
306
+ } else if ((depFlags & MutableDirty) === MutableDirty) {
307
+ if (update(dep)) {
308
+ const subs = dep.subs
309
+ if (subs !== undefined && subs.nextSub !== undefined) {
310
+ customShallowPropagate(subs)
311
+ }
312
+ dirty = true
313
+ }
314
+ } else if ((depFlags & MutablePending) === MutablePending) {
315
+ if (link.nextSub !== undefined || link.prevSub !== undefined) {
316
+ stack = { value: link, prev: stack }
317
+ }
318
+ link = dep.deps!
319
+ sub = dep
320
+ ++checkDepth
321
+ continue
322
+ }
323
+
324
+ if (!dirty) {
325
+ const nextDep = link.nextDep
326
+ if (nextDep !== undefined) {
327
+ link = nextDep
328
+ continue
329
+ }
330
+ }
331
+
332
+ while (checkDepth-- > 0) {
333
+ const firstSub = sub.subs!
334
+ const hasMultipleSubs = firstSub.nextSub !== undefined
335
+
336
+ if (hasMultipleSubs) {
337
+ link = stack!.value!
338
+ stack = stack!.prev
339
+ } else link = firstSub
340
+
341
+ if (dirty) {
342
+ if (update(sub)) {
343
+ if (hasMultipleSubs) customShallowPropagate(firstSub)
344
+ sub = link.sub
345
+ continue
346
+ }
347
+ dirty = false
348
+ } else {
349
+ sub.flags &= ~Pending
350
+ }
351
+
352
+ sub = link.sub
353
+ const nextDep = link.nextDep
354
+ if (nextDep !== undefined) {
355
+ link = nextDep
356
+ continue top
357
+ }
358
+ }
359
+
360
+ return dirty
361
+ }
362
+ }
363
+ function customShallowPropagate(firstLink: Link): void {
364
+ let link: Link | undefined = firstLink
365
+ do {
366
+ const sub = link.sub
367
+ const flags = sub.flags
368
+ if ((flags & 48) === Pending) {
369
+ sub.flags = flags | Dirty
370
+ if ((flags & 6) === Watching) notifyFn(sub)
371
+ }
372
+ link = link.nextSub
373
+ } while (link !== undefined)
374
+ }
375
+ function customUnlink(lnk: Link, sub: ReactiveNode = lnk.sub): Link | undefined {
376
+ const dep = lnk.dep
377
+ const prevDep = lnk.prevDep
378
+ const nextDep = lnk.nextDep
379
+ const nextSub = lnk.nextSub
380
+ const prevSub = lnk.prevSub
381
+
382
+ if (nextDep !== undefined) nextDep.prevDep = prevDep
383
+ else sub.depsTail = prevDep
384
+ if (prevDep !== undefined) prevDep.nextDep = nextDep
385
+ else sub.deps = nextDep
386
+
387
+ if (nextSub !== undefined) nextSub.prevSub = prevSub
388
+ else dep.subsTail = prevSub
389
+ if (prevSub !== undefined) prevSub.nextSub = nextSub
390
+ else if ((dep.subs = nextSub) === undefined) unwatchedFn(dep)
391
+
392
+ return nextDep
393
+ }
394
+ return {
395
+ link,
396
+ unlink: customUnlink,
397
+ propagate: customPropagate,
398
+ checkDirty: customCheckDirty,
399
+ shallowPropagate: customShallowPropagate,
400
+ }
401
+ }
402
+ // ============================================================================
403
+ // Core functions
404
+ // ============================================================================
405
+ /**
406
+ * Create a link between a dependency and a subscriber
407
+ * @param dep - The dependency node
408
+ * @param sub - The subscriber node
409
+ * @param version - The cycle version
410
+ */
411
+ function link(dep: ReactiveNode, sub: ReactiveNode, version: number): void {
412
+ const prevDep = sub.depsTail
413
+ if (prevDep !== undefined && prevDep.dep === dep) return
414
+
415
+ const nextDep = prevDep !== undefined ? prevDep.nextDep : sub.deps
416
+ if (nextDep !== undefined && nextDep.dep === dep) {
417
+ nextDep.version = version
418
+ sub.depsTail = nextDep
419
+ return
420
+ }
421
+
422
+ const prevSub = dep.subsTail
423
+ if (prevSub !== undefined && prevSub.version === version && prevSub.sub === sub) return
424
+
425
+ const newLink = { version, dep, sub, prevDep, nextDep, prevSub, nextSub: undefined }
426
+ sub.depsTail = newLink
427
+ dep.subsTail = newLink
428
+
429
+ if (nextDep !== undefined) nextDep.prevDep = newLink
430
+ if (prevDep !== undefined) prevDep.nextDep = newLink
431
+ else sub.deps = newLink
432
+ if (prevSub !== undefined) prevSub.nextSub = newLink
433
+ else dep.subs = newLink
434
+ }
435
+ /**
436
+ * Remove a link between a dependency and a subscriber
437
+ * @param lnk - The link to remove
438
+ * @param sub - The subscriber node (defaults to lnk.sub)
439
+ * @returns The next dependency link
440
+ */
441
+ function unlink(lnk: Link, sub: ReactiveNode = lnk.sub): Link | undefined {
442
+ const dep = lnk.dep
443
+ const prevDep = lnk.prevDep
444
+ const nextDep = lnk.nextDep
445
+ const nextSub = lnk.nextSub
446
+ const prevSub = lnk.prevSub
447
+
448
+ if (nextDep !== undefined) nextDep.prevDep = prevDep
449
+ else sub.depsTail = prevDep
450
+ if (prevDep !== undefined) prevDep.nextDep = nextDep
451
+ else sub.deps = nextDep
452
+
453
+ if (nextSub !== undefined) nextSub.prevSub = prevSub
454
+ else dep.subsTail = prevSub
455
+ if (prevSub !== undefined) prevSub.nextSub = nextSub
456
+ else if ((dep.subs = nextSub) === undefined) unwatched(dep)
457
+
458
+ return nextDep
459
+ }
460
+ /**
461
+ * Handle when a dependency becomes unwatched
462
+ * @param dep - The dependency node
463
+ */
464
+ function unwatched(dep: ReactiveNode): void {
465
+ if (!(dep.flags & Mutable)) {
466
+ disposeNode(dep)
467
+ } else if ('getter' in dep && dep.getter !== undefined) {
468
+ dep.depsTail = undefined
469
+ dep.flags = MutableDirty
470
+ purgeDeps(dep)
471
+ }
472
+ }
473
+ /**
474
+ * Propagate changes through the reactive graph
475
+ * @param firstLink - The first link to propagate from
476
+ */
477
+ function propagate(firstLink: Link): void {
478
+ let link = firstLink
479
+ let next = link.nextSub
480
+ let stack: StackFrame | undefined
481
+
482
+ top: for (;;) {
483
+ const sub = link.sub
484
+ let flags = sub.flags
485
+
486
+ if (!(flags & 60)) {
487
+ sub.flags = flags | Pending
488
+ } else if (!(flags & 12)) {
489
+ flags = 0
490
+ } else if (!(flags & Running)) {
491
+ sub.flags = (flags & ~Recursed) | Pending
492
+ } else if (!(flags & 48)) {
493
+ let vlink = sub.depsTail
494
+ let valid = false
495
+ while (vlink !== undefined) {
496
+ if (vlink === link) {
497
+ valid = true
498
+ break
499
+ }
500
+ vlink = vlink.prevDep
501
+ }
502
+ if (valid) {
503
+ sub.flags = flags | 40
504
+ flags &= Mutable
505
+ } else {
506
+ flags = 0
507
+ }
508
+ } else {
509
+ flags = 0
510
+ }
511
+
512
+ if (flags & Watching) notify(sub)
513
+
514
+ if (flags & Mutable) {
515
+ const subSubs = sub.subs
516
+ if (subSubs !== undefined) {
517
+ const nextSub = subSubs.nextSub
518
+ if (nextSub !== undefined) {
519
+ stack = { value: next, prev: stack }
520
+ next = nextSub
521
+ }
522
+ link = subSubs
523
+ continue
524
+ }
525
+ }
526
+
527
+ if (next !== undefined) {
528
+ link = next
529
+ next = link.nextSub
530
+ continue
531
+ }
532
+
533
+ while (stack !== undefined) {
534
+ link = stack.value!
535
+ stack = stack.prev
536
+ if (link !== undefined) {
537
+ next = link.nextSub
538
+ continue top
539
+ }
540
+ }
541
+ break
542
+ }
543
+ }
544
+ /**
545
+ * Check if a node is dirty by traversing its dependencies
546
+ * @param firstLink - The first link to check
547
+ * @param sub - The subscriber node
548
+ * @returns True if the node is dirty
549
+ */
550
+ function checkDirty(firstLink: Link, sub: ReactiveNode): boolean {
551
+ let link = firstLink
552
+ let stack: StackFrame | undefined
553
+ let checkDepth = 0
554
+ let dirty = false
555
+
556
+ top: for (;;) {
557
+ const dep = link.dep
558
+ const depFlags = dep.flags
559
+
560
+ if (sub.flags & Dirty) {
561
+ dirty = true
562
+ } else if ((depFlags & MutableDirty) === MutableDirty) {
563
+ if (update(dep)) {
564
+ const subs = dep.subs
565
+ if (subs !== undefined && subs.nextSub !== undefined) shallowPropagate(subs)
566
+ dirty = true
567
+ }
568
+ } else if ((depFlags & MutablePending) === MutablePending) {
569
+ if (link.nextSub !== undefined || link.prevSub !== undefined) {
570
+ stack = { value: link, prev: stack }
571
+ }
572
+ link = dep.deps!
573
+ sub = dep
574
+ ++checkDepth
575
+ continue
576
+ }
577
+
578
+ if (!dirty) {
579
+ const nextDep = link.nextDep
580
+ if (nextDep !== undefined) {
581
+ link = nextDep
582
+ continue
583
+ }
584
+ }
585
+
586
+ while (checkDepth-- > 0) {
587
+ const firstSub = sub.subs!
588
+ const hasMultipleSubs = firstSub.nextSub !== undefined
589
+
590
+ if (hasMultipleSubs) {
591
+ link = stack!.value!
592
+ stack = stack!.prev
593
+ } else {
594
+ link = firstSub
595
+ }
596
+
597
+ if (dirty) {
598
+ if (update(sub)) {
599
+ if (hasMultipleSubs) shallowPropagate(firstSub)
600
+ sub = link.sub
601
+ continue
602
+ }
603
+ dirty = false
604
+ } else {
605
+ sub.flags &= ~Pending
606
+ }
607
+
608
+ sub = link.sub
609
+ const nextDep = link.nextDep
610
+ if (nextDep !== undefined) {
611
+ link = nextDep
612
+ continue top
613
+ }
614
+ }
615
+
616
+ return dirty
617
+ }
618
+ }
619
+ /**
620
+ * Shallow propagate changes without traversing deeply
621
+ * @param firstLink - The first link to propagate from
622
+ */
623
+ function shallowPropagate(firstLink: Link): void {
624
+ let link: Link | undefined = firstLink
625
+ do {
626
+ const sub = link.sub
627
+ const flags = sub.flags
628
+ if ((flags & 48) === Pending) {
629
+ sub.flags = flags | Dirty
630
+ if ((flags & 6) === Watching) notify(sub)
631
+ }
632
+ link = link.nextSub
633
+ } while (link !== undefined)
634
+ }
635
+ /**
636
+ * Update a reactive node (signal or computed)
637
+ * @param node - The node to update
638
+ * @returns True if the value changed
639
+ */
640
+ function update(node: ReactiveNode): boolean {
641
+ return 'getter' in node && node.getter !== undefined
642
+ ? updateComputed(node as ComputedNode)
643
+ : updateSignal(node as SignalNode)
644
+ }
645
+ /**
646
+ * Notify an effect and add it to the queue
647
+ * @param effect - The effect to notify
648
+ */
649
+ function notify(effect: ReactiveNode): void {
650
+ effect.flags &= ~Watching
651
+ const effects: EffectNode[] = []
652
+
653
+ for (;;) {
654
+ effects.push(effect as EffectNode)
655
+ const nextLink = effect.subs
656
+ if (nextLink === undefined) break
657
+ effect = nextLink.sub
658
+ if (effect === undefined || !(effect.flags & Watching)) break
659
+ effect.flags &= ~Watching
660
+ }
661
+
662
+ // Route effects to appropriate queue based on transition context
663
+ const targetQueue = isInTransition ? lowPriorityQueue : highPriorityQueue
664
+ for (let i = effects.length - 1; i >= 0; i--) {
665
+ targetQueue.push(effects[i]!)
666
+ }
667
+ }
668
+ /**
669
+ * Purge all dependencies from a subscriber
670
+ * @param sub - The subscriber node
671
+ */
672
+ function purgeDeps(sub: ReactiveNode): void {
673
+ const depsTail = sub.depsTail
674
+ let dep = depsTail !== undefined ? depsTail.nextDep : sub.deps
675
+ while (dep !== undefined) dep = unlink(dep, sub)
676
+ }
677
+ /**
678
+ * Dispose a reactive node
679
+ * @param node - The node to dispose
680
+ */
681
+ function disposeNode(node: ReactiveNode): void {
682
+ node.depsTail = undefined
683
+ node.flags = 0
684
+ purgeDeps(node)
685
+ const sub = node.subs
686
+ if (sub !== undefined) unlink(sub, node)
687
+ }
688
+ /**
689
+ * Update a signal node
690
+ * @param s - The signal node
691
+ * @returns True if the value changed
692
+ */
693
+ function updateSignal(s: SignalNode): boolean {
694
+ s.flags = Mutable
695
+ const current = s.currentValue
696
+ const pending = s.pendingValue
697
+ if (current !== pending) {
698
+ s.currentValue = pending
699
+ return true
700
+ }
701
+ return false
702
+ }
703
+ /**
704
+ * Update a computed node
705
+ * @param c - The computed node
706
+ * @returns True if the value changed
707
+ */
708
+ function updateComputed<T>(c: ComputedNode<T>): boolean {
709
+ ++cycle
710
+ const oldValue = c.value
711
+ c.depsTail = undefined
712
+ c.flags = MutableRunning
713
+ const prevSub = activeSub
714
+ activeSub = c
715
+
716
+ try {
717
+ const newValue = c.getter(oldValue)
718
+ activeSub = prevSub
719
+ c.flags &= ~Running
720
+ purgeDeps(c)
721
+ if (oldValue !== newValue) {
722
+ c.value = newValue
723
+ return true
724
+ }
725
+ return false
726
+ } catch (e) {
727
+ activeSub = prevSub
728
+ c.flags &= ~Running
729
+ throw e
730
+ }
731
+ }
732
+ /**
733
+ * Run an effect
734
+ * @param e - The effect node
735
+ */
736
+ function runEffect(e: EffectNode): void {
737
+ const flags = e.flags
738
+ if (flags & Dirty || (flags & Pending && e.deps && checkDirty(e.deps, e))) {
739
+ ++cycle
740
+ effectRunDevtools(e)
741
+ e.depsTail = undefined
742
+ e.flags = WatchingRunning
743
+ const prevSub = activeSub
744
+ activeSub = e
745
+ try {
746
+ e.fn()
747
+ activeSub = prevSub
748
+ e.flags = Watching
749
+ purgeDeps(e)
750
+ } catch (err) {
751
+ activeSub = prevSub
752
+ e.flags = Watching
753
+ throw err
754
+ }
755
+ } else {
756
+ e.flags = Watching
757
+ }
758
+ }
759
+ /**
760
+ * Schedule a flush in a microtask to coalesce synchronous writes
761
+ */
762
+ export function scheduleFlush(): void {
763
+ const hasWork = highPriorityQueue.length > 0 || lowPriorityQueue.length > 0
764
+ if (flushScheduled || !hasWork) return
765
+ if (batchDepth > 0) return
766
+ flushScheduled = true
767
+ enqueueMicrotask(() => {
768
+ flush()
769
+ })
770
+ }
771
+ /**
772
+ * Flush all queued effects with priority-based scheduling
773
+ * High priority effects execute first; low priority can be interrupted
774
+ */
775
+ function flush(): void {
776
+ beginFlushGuard()
777
+ if (batchDepth > 0) {
778
+ // If batching is active, defer until the batch completes
779
+ scheduleFlush()
780
+ endFlushGuard()
781
+ return
782
+ }
783
+ const hasWork = highPriorityQueue.length > 0 || lowPriorityQueue.length > 0
784
+ if (!hasWork) {
785
+ flushScheduled = false
786
+ endFlushGuard()
787
+ return
788
+ }
789
+ flushScheduled = false
790
+
791
+ // 1. Process all high-priority effects first
792
+ let highIndex = 0
793
+ while (highIndex < highPriorityQueue.length) {
794
+ const e = highPriorityQueue[highIndex]!
795
+ if (!beforeEffectRunGuard()) {
796
+ if (highIndex > 0) {
797
+ highPriorityQueue.copyWithin(0, highIndex)
798
+ highPriorityQueue.length -= highIndex
799
+ }
800
+ endFlushGuard()
801
+ return
802
+ }
803
+ highIndex++
804
+ runEffect(e)
805
+ }
806
+ highPriorityQueue.length = 0
807
+
808
+ // 2. Process low-priority effects, interruptible by high priority
809
+ let lowIndex = 0
810
+ while (lowIndex < lowPriorityQueue.length) {
811
+ // Check if high priority work arrived during low priority execution
812
+ if (highPriorityQueue.length > 0) {
813
+ if (lowIndex > 0) {
814
+ lowPriorityQueue.copyWithin(0, lowIndex)
815
+ lowPriorityQueue.length -= lowIndex
816
+ }
817
+ scheduleFlush()
818
+ endFlushGuard()
819
+ return
820
+ }
821
+ const e = lowPriorityQueue[lowIndex]!
822
+ if (!beforeEffectRunGuard()) {
823
+ if (lowIndex > 0) {
824
+ lowPriorityQueue.copyWithin(0, lowIndex)
825
+ lowPriorityQueue.length -= lowIndex
826
+ }
827
+ endFlushGuard()
828
+ return
829
+ }
830
+ lowIndex++
831
+ runEffect(e)
832
+ }
833
+ lowPriorityQueue.length = 0
834
+
835
+ endFlushGuard()
836
+ }
837
+ // ============================================================================
838
+ // Signal - Inline optimized version
839
+ // ============================================================================
840
+ /**
841
+ * Create a reactive signal
842
+ * @param initialValue - The initial value
843
+ * @returns A signal accessor function
844
+ */
845
+ export function signal<T>(initialValue: T): SignalAccessor<T> {
846
+ const s = {
847
+ currentValue: initialValue,
848
+ pendingValue: initialValue,
849
+ subs: undefined,
850
+ subsTail: undefined,
851
+ flags: Mutable,
852
+ __id: undefined as number | undefined,
853
+ }
854
+ registerSignalDevtools(initialValue, s)
855
+ return signalOper.bind(s) as SignalAccessor<T>
856
+ }
857
+ function signalOper<T>(this: SignalNode<T>, value?: T): T | void {
858
+ if (arguments.length > 0) {
859
+ if (this.pendingValue !== value) {
860
+ this.pendingValue = value as T
861
+ this.flags = MutableDirty
862
+ updateSignalDevtools(this, value)
863
+ const subs = this.subs
864
+ if (subs !== undefined) {
865
+ propagate(subs)
866
+ if (!batchDepth) scheduleFlush()
867
+ }
868
+ }
869
+ return
870
+ }
871
+
872
+ const flags = this.flags
873
+ if (flags & Dirty) {
874
+ if (updateSignal(this)) {
875
+ const subs = this.subs
876
+ if (subs !== undefined) shallowPropagate(subs)
877
+ }
878
+ }
879
+
880
+ let sub = activeSub
881
+ while (sub !== undefined) {
882
+ if (sub.flags & 3) {
883
+ link(this, sub, cycle)
884
+ break
885
+ }
886
+ const subSubs = sub.subs
887
+ sub = subSubs !== undefined ? subSubs.sub : undefined
888
+ }
889
+
890
+ return this.currentValue
891
+ }
892
+ // ============================================================================
893
+ // Computed
894
+ // ============================================================================
895
+ /**
896
+ * Create a computed reactive value
897
+ * @param getter - The getter function
898
+ * @returns A computed accessor function
899
+ */
900
+ export function computed<T>(getter: (oldValue?: T) => T): ComputedAccessor<T> {
901
+ const c: ComputedNode<T> = {
902
+ value: undefined as unknown as T,
903
+ subs: undefined,
904
+ subsTail: undefined,
905
+ deps: undefined,
906
+ depsTail: undefined,
907
+ flags: 0,
908
+ getter,
909
+ }
910
+ const bound = (computedOper as (this: ComputedNode<T>) => T).bind(c)
911
+ return bound as ComputedAccessor<T>
912
+ }
913
+ function computedOper<T>(this: ComputedNode<T>): T {
914
+ const flags = this.flags
915
+
916
+ if (flags & Dirty) {
917
+ if (updateComputed(this)) {
918
+ const subs = this.subs
919
+ if (subs !== undefined) shallowPropagate(subs)
920
+ }
921
+ } else if (flags & Pending) {
922
+ if (this.deps && checkDirty(this.deps, this)) {
923
+ if (updateComputed(this)) {
924
+ const subs = this.subs
925
+ if (subs !== undefined) shallowPropagate(subs)
926
+ }
927
+ } else {
928
+ this.flags = flags & ~Pending
929
+ }
930
+ } else if (!flags) {
931
+ this.flags = MutableRunning
932
+ const prevSub = setActiveSub(this)
933
+ try {
934
+ this.value = this.getter(undefined)
935
+ } finally {
936
+ setActiveSub(prevSub)
937
+ this.flags &= ~Running
938
+ }
939
+ }
940
+
941
+ if (activeSub !== undefined) link(this, activeSub, cycle)
942
+ return this.value
943
+ }
944
+ // ============================================================================
945
+ // Effect
946
+ // ============================================================================
947
+ /**
948
+ * Create a reactive effect
949
+ * @param fn - The effect function
950
+ * @returns An effect disposer function
951
+ */
952
+ export function effect(fn: () => void): EffectDisposer {
953
+ const e = {
954
+ fn,
955
+ subs: undefined,
956
+ subsTail: undefined,
957
+ deps: undefined,
958
+ depsTail: undefined,
959
+ flags: WatchingRunning,
960
+ __id: undefined as number | undefined,
961
+ }
962
+
963
+ registerEffectDevtools(e)
964
+
965
+ const prevSub = activeSub
966
+ if (prevSub !== undefined) link(e, prevSub, 0)
967
+ activeSub = e
968
+
969
+ try {
970
+ effectRunDevtools(e)
971
+ fn()
972
+ } finally {
973
+ activeSub = prevSub
974
+ e.flags &= ~Running
975
+ }
976
+
977
+ return effectOper.bind(e) as EffectDisposer
978
+ }
979
+ function effectOper(this: EffectNode): void {
980
+ disposeNode(this)
981
+ }
982
+ // ============================================================================
983
+ // Effect Scope
984
+ // ============================================================================
985
+ /**
986
+ * Create a reactive effect scope
987
+ * @param fn - The scope function
988
+ * @returns An effect scope disposer function
989
+ */
990
+ export function effectScope(fn: () => void): EffectScopeDisposer {
991
+ const e = { deps: undefined, depsTail: undefined, subs: undefined, subsTail: undefined, flags: 0 }
992
+
993
+ const prevSub = activeSub
994
+ if (prevSub !== undefined) link(e, prevSub, 0)
995
+ activeSub = e
996
+
997
+ try {
998
+ fn()
999
+ } finally {
1000
+ activeSub = prevSub
1001
+ }
1002
+
1003
+ return effectScopeOper.bind(e) as EffectScopeDisposer
1004
+ }
1005
+ function effectScopeOper(this: EffectScopeNode): void {
1006
+ disposeNode(this)
1007
+ }
1008
+ // ============================================================================
1009
+ // Trigger
1010
+ // ============================================================================
1011
+ /**
1012
+ * Trigger a reactive computation without creating a persistent subscription
1013
+ * @param fn - The function to run
1014
+ */
1015
+ export function trigger(fn: () => void): void {
1016
+ const sub: SubscriberNode = { deps: undefined, depsTail: undefined, flags: Watching }
1017
+ const prevSub = activeSub
1018
+ activeSub = sub as ReactiveNode
1019
+
1020
+ try {
1021
+ fn()
1022
+ } finally {
1023
+ activeSub = prevSub
1024
+ let lnk = sub.deps
1025
+ while (lnk !== undefined) {
1026
+ const dep = lnk.dep
1027
+ lnk = unlink(lnk, sub)
1028
+ const subs = dep.subs
1029
+ if (subs !== undefined) {
1030
+ sub.flags = 0
1031
+ propagate(subs)
1032
+ shallowPropagate(subs)
1033
+ }
1034
+ }
1035
+ if (!batchDepth) scheduleFlush()
1036
+ }
1037
+ }
1038
+ // ============================================================================
1039
+ // Batch processing & Utility API
1040
+ // ============================================================================
1041
+ /**
1042
+ * Start a batch of updates
1043
+ */
1044
+ export function startBatch(): void {
1045
+ ++batchDepth
1046
+ }
1047
+ /**
1048
+ * End a batch of updates and flush effects
1049
+ */
1050
+ export function endBatch(): void {
1051
+ if (--batchDepth === 0) flush()
1052
+ }
1053
+ /**
1054
+ * Execute a function in a batch
1055
+ * @param fn - The function to execute
1056
+ * @returns The return value of the function
1057
+ */
1058
+ export function batch<T>(fn: () => T): T {
1059
+ ++batchDepth
1060
+ try {
1061
+ return fn()
1062
+ } finally {
1063
+ if (--batchDepth === 0) flush()
1064
+ }
1065
+ }
1066
+ /**
1067
+ * Get the current active subscriber
1068
+ * @returns The active subscriber or undefined
1069
+ */
1070
+ export function getActiveSub(): ReactiveNode | undefined {
1071
+ return activeSub
1072
+ }
1073
+ /**
1074
+ * Set the active subscriber
1075
+ * @param sub - The new active subscriber
1076
+ * @returns The previous active subscriber
1077
+ */
1078
+ export function setActiveSub(sub: ReactiveNode | undefined): ReactiveNode | undefined {
1079
+ const prev = activeSub
1080
+ activeSub = sub
1081
+ return prev
1082
+ }
1083
+ /**
1084
+ * Get the current batch depth
1085
+ * @returns The current batch depth
1086
+ */
1087
+ export function getBatchDepth(): number {
1088
+ return batchDepth
1089
+ }
1090
+ /**
1091
+ * Execute a function without tracking dependencies
1092
+ * @param fn - The function to execute
1093
+ * @returns The return value of the function
1094
+ */
1095
+ export function untrack<T>(fn: () => T): T {
1096
+ const prev = activeSub
1097
+ activeSub = undefined
1098
+ try {
1099
+ return fn()
1100
+ } finally {
1101
+ activeSub = prev
1102
+ }
1103
+ }
1104
+ /**
1105
+ * Peek at a reactive value without tracking it as a dependency
1106
+ * @param accessor - The accessor function
1107
+ * @returns The value
1108
+ */
1109
+ export function peek<T>(accessor: () => T): T {
1110
+ return untrack(accessor)
1111
+ }
1112
+ // Type detection - Fixed: using Function.name
1113
+ /**
1114
+ * Check if a function is a signal accessor
1115
+ * @param fn - The function to check
1116
+ * @returns True if the function is a signal accessor
1117
+ */
1118
+ export function isSignal(fn: unknown): fn is SignalAccessor<unknown> {
1119
+ return typeof fn === 'function' && fn.name === 'bound signalOper'
1120
+ }
1121
+ /**
1122
+ * Check if a function is a computed accessor
1123
+ * @param fn - The function to check
1124
+ * @returns True if the function is a computed accessor
1125
+ */
1126
+ export function isComputed(fn: unknown): fn is ComputedAccessor<unknown> {
1127
+ return typeof fn === 'function' && fn.name === 'bound computedOper'
1128
+ }
1129
+ /**
1130
+ * Check if a function is an effect disposer
1131
+ * @param fn - The function to check
1132
+ * @returns True if the function is an effect disposer
1133
+ */
1134
+ export function isEffect(fn: unknown): fn is EffectDisposer {
1135
+ return typeof fn === 'function' && fn.name === 'bound effectOper'
1136
+ }
1137
+ /**
1138
+ * Check if a function is an effect scope disposer
1139
+ * @param fn - The function to check
1140
+ * @returns True if the function is an effect scope disposer
1141
+ */
1142
+ export function isEffectScope(fn: unknown): fn is EffectScopeDisposer {
1143
+ return typeof fn === 'function' && fn.name === 'bound effectScopeOper'
1144
+ }
1145
+ // ============================================================================
1146
+ // Transition Context (for priority scheduling)
1147
+ // ============================================================================
1148
+ /**
1149
+ * Set the transition context
1150
+ * @param value - Whether we're inside a transition
1151
+ * @returns The previous transition context value
1152
+ */
1153
+ export function setTransitionContext(value: boolean): boolean {
1154
+ const prev = isInTransition
1155
+ isInTransition = value
1156
+ return prev
1157
+ }
1158
+ /**
1159
+ * Get the current transition context
1160
+ * @returns True if currently inside a transition
1161
+ */
1162
+ export function getTransitionContext(): boolean {
1163
+ return isInTransition
1164
+ }
1165
+ // Export aliases for API compatibility
1166
+ export { signal as createSignal }
1167
+ export type { SignalAccessor as Signal }
1168
+
1169
+ export { flush, link, unlink, propagate, checkDirty, shallowPropagate }
1170
+ export default {
1171
+ signal,
1172
+ computed,
1173
+ effect,
1174
+ effectScope,
1175
+ trigger,
1176
+ batch,
1177
+ startBatch,
1178
+ endBatch,
1179
+ flush,
1180
+ untrack,
1181
+ peek,
1182
+ isSignal,
1183
+ isComputed,
1184
+ isEffect,
1185
+ isEffectScope,
1186
+ getActiveSub,
1187
+ setActiveSub,
1188
+ getBatchDepth,
1189
+ link,
1190
+ unlink,
1191
+ propagate,
1192
+ checkDirty,
1193
+ shallowPropagate,
1194
+ createReactiveSystem,
1195
+ ReactiveFlags,
1196
+ }
1197
+ export const $state = signal as <T>(value: T) => T
1198
+
1199
+ let devtoolsSignalId = 0
1200
+ let devtoolsEffectId = 0
1201
+
1202
+ interface DevtoolsIdentifiable {
1203
+ __id?: number
1204
+ }
1205
+
1206
+ function registerSignalDevtools(value: unknown, node: SignalNode): number | undefined {
1207
+ const hook = getDevtoolsHook()
1208
+ if (!hook) return undefined
1209
+ const id = ++devtoolsSignalId
1210
+ hook.registerSignal(id, value)
1211
+ ;(node as SignalNode & DevtoolsIdentifiable).__id = id
1212
+ return id
1213
+ }
1214
+
1215
+ function updateSignalDevtools(node: SignalNode, value: unknown): void {
1216
+ const hook = getDevtoolsHook()
1217
+ if (!hook) return
1218
+ const id = (node as SignalNode & DevtoolsIdentifiable).__id
1219
+ if (id) hook.updateSignal(id, value)
1220
+ }
1221
+
1222
+ function registerEffectDevtools(node: EffectNode): number | undefined {
1223
+ const hook = getDevtoolsHook()
1224
+ if (!hook) return undefined
1225
+ const id = ++devtoolsEffectId
1226
+ hook.registerEffect(id)
1227
+ ;(node as EffectNode & DevtoolsIdentifiable).__id = id
1228
+ return id
1229
+ }
1230
+
1231
+ function effectRunDevtools(node: EffectNode): void {
1232
+ const hook = getDevtoolsHook()
1233
+ if (!hook) return
1234
+ const id = (node as EffectNode & DevtoolsIdentifiable).__id
1235
+ if (id) hook.effectRun(id)
1236
+ }
1237
+
1238
+ // ============================================================================
1239
+ // Selector
1240
+ // ============================================================================
1241
+ /**
1242
+ * Create a selector signal that efficiently updates only when the selected key matches.
1243
+ * Useful for large lists where only one item is selected.
1244
+ *
1245
+ * @param source - The source signal returning the current key
1246
+ * @param equalityFn - Optional equality function
1247
+ * @returns A selector function that takes a key and returns a boolean signal accessor
1248
+ */
1249
+ export function createSelector<T>(
1250
+ source: () => T,
1251
+ equalityFn: (a: T, b: T) => boolean = (a, b) => a === b,
1252
+ ): (key: T) => boolean {
1253
+ let current = source()
1254
+ const observers = new Map<T, SignalAccessor<boolean>>()
1255
+
1256
+ effect(() => {
1257
+ const next = source()
1258
+ if (equalityFn(current, next)) return
1259
+
1260
+ const prevSig = observers.get(current)
1261
+ if (prevSig) prevSig(false)
1262
+
1263
+ const nextSig = observers.get(next)
1264
+ if (nextSig) nextSig(true)
1265
+
1266
+ current = next
1267
+ })
1268
+
1269
+ return (key: T) => {
1270
+ let sig = observers.get(key)
1271
+ if (!sig) {
1272
+ sig = signal(equalityFn(key, current))
1273
+ observers.set(key, sig)
1274
+ registerRootCleanup(() => observers.delete(key))
1275
+ }
1276
+ return sig()
1277
+ }
1278
+ }