fusee-framework 1.8.0 → 1.9.1

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 (109) hide show
  1. package/README.md +64 -3
  2. package/app/benchmark/vite.config.js +23 -0
  3. package/auto-imports.d.ts +4 -0
  4. package/bin/create-fusee-linux +0 -0
  5. package/bin/create-fusee-mac-arm64 +0 -0
  6. package/bin/create-fusee-mac-intel +0 -0
  7. package/bin/create-fusee-win.exe +0 -0
  8. package/bin/create-fusee.exe +0 -0
  9. package/create-fusee-go/internal/assets/embed/framework/auto-import-preset.js +15 -9
  10. package/create-fusee-go/internal/assets/embed/framework/core/async-context.js +27 -0
  11. package/create-fusee-go/internal/assets/embed/framework/core/compiler/ast.js +167 -0
  12. package/create-fusee-go/internal/assets/embed/framework/core/compiler/errors-list.js +47 -0
  13. package/create-fusee-go/internal/assets/embed/framework/core/compiler/errors.js +107 -0
  14. package/create-fusee-go/internal/assets/embed/framework/core/compiler/generator.js +363 -0
  15. package/create-fusee-go/internal/assets/embed/framework/core/compiler/lexer.js +347 -0
  16. package/create-fusee-go/internal/assets/embed/framework/core/compiler/main-compiler.js +115 -0
  17. package/create-fusee-go/internal/assets/embed/framework/core/compiler/parser.js +414 -0
  18. package/create-fusee-go/internal/assets/embed/framework/core/compiler/plugins/actions-plugin.js +263 -0
  19. package/create-fusee-go/internal/assets/embed/framework/core/compiler/plugins/compiler-plugin.d.ts +5 -0
  20. package/create-fusee-go/internal/assets/embed/framework/core/compiler/plugins/compiler-plugin.js +45 -0
  21. package/create-fusee-go/internal/assets/embed/framework/core/compiler/plugins/file-router-plugin.js +301 -0
  22. package/create-fusee-go/internal/assets/embed/framework/core/compiler/plugins/router-plugin.js +196 -0
  23. package/create-fusee-go/internal/assets/embed/framework/core/compiler/transformer.js +398 -0
  24. package/create-fusee-go/internal/assets/embed/framework/core/component.js +61 -23
  25. package/create-fusee-go/internal/assets/embed/framework/core/di.js +187 -0
  26. package/create-fusee-go/internal/assets/embed/framework/core/directives.js +40 -96
  27. package/create-fusee-go/internal/assets/embed/framework/core/evaluator.js +1 -1
  28. package/create-fusee-go/internal/assets/embed/framework/core/h.js +430 -0
  29. package/create-fusee-go/internal/assets/embed/framework/core/hydration.js +83 -0
  30. package/create-fusee-go/internal/assets/embed/framework/core/reconcile.js +151 -0
  31. package/create-fusee-go/internal/assets/embed/framework/core/signal.js +269 -0
  32. package/create-fusee-go/internal/assets/embed/framework/core/store.js +322 -81
  33. package/create-fusee-go/internal/assets/embed/framework/index.js +5 -3
  34. package/create-fusee-go/internal/assets/embed/framework/router/file-router.js +1 -1
  35. package/create-fusee-go/internal/assets/embed/framework/router/router.js +113 -21
  36. package/create-fusee-go/internal/assets/embed/framework/server/actions.js +173 -0
  37. package/create-fusee-go/internal/assets/embed/framework/server/actions.server.js +88 -0
  38. package/create-fusee-go/internal/assets/embed/framework/server/index.js +18 -0
  39. package/create-fusee-go/internal/assets/embed/framework/types/actions.d.ts +50 -0
  40. package/create-fusee-go/internal/assets/embed/framework/types/actions.server.d.ts +36 -0
  41. package/create-fusee-go/internal/assets/embed/framework/types/compiler/ast.d.ts +166 -0
  42. package/create-fusee-go/internal/assets/embed/framework/types/compiler/errors-codes.d.ts +23 -0
  43. package/create-fusee-go/internal/assets/embed/framework/types/compiler/errors.d.ts +43 -0
  44. package/create-fusee-go/internal/assets/embed/framework/types/compiler/generator.d.ts +8 -0
  45. package/create-fusee-go/internal/assets/embed/framework/types/compiler/h.d.ts +46 -0
  46. package/create-fusee-go/internal/assets/embed/framework/types/compiler/lexer.d.ts +27 -0
  47. package/create-fusee-go/internal/assets/embed/framework/types/compiler/main-compiler.d.ts +60 -0
  48. package/create-fusee-go/internal/assets/embed/framework/types/compiler/parser.d.ts +4 -0
  49. package/create-fusee-go/internal/assets/embed/framework/types/compiler/plugins/actions.d.ts +30 -0
  50. package/create-fusee-go/internal/assets/embed/framework/types/compiler/plugins/compiler-plugin.d.ts +5 -0
  51. package/create-fusee-go/internal/assets/embed/framework/types/compiler/plugins/file-router-plugins.d.ts +32 -0
  52. package/create-fusee-go/internal/assets/embed/framework/types/compiler/plugins/router-plugin.d.ts +24 -0
  53. package/create-fusee-go/internal/assets/embed/framework/types/compiler/transformer.d.ts +28 -0
  54. package/create-fusee-go/internal/assets/embed/framework/types/component.d.ts +89 -88
  55. package/create-fusee-go/internal/assets/embed/framework/types/di.d.ts +43 -0
  56. package/create-fusee-go/internal/assets/embed/framework/types/hydration.d.ts +30 -0
  57. package/create-fusee-go/internal/assets/embed/framework/types/index.d.ts +38 -22
  58. package/create-fusee-go/internal/assets/embed/framework/types/reconcile.d.ts +7 -0
  59. package/create-fusee-go/internal/assets/embed/framework/types/router.d.ts +11 -1
  60. package/create-fusee-go/internal/assets/embed/framework/types/server.d.ts +11 -0
  61. package/create-fusee-go/internal/assets/embed/framework/types/signal.d.ts +142 -79
  62. package/create-fusee-go/internal/assets/embed/framework/types/store.d.ts +66 -8
  63. package/framework/auto-import-preset.js +3 -1
  64. package/framework/core/async-context.js +27 -0
  65. package/framework/core/compiler/ast.js +167 -0
  66. package/framework/core/compiler/errors-list.js +47 -0
  67. package/framework/core/compiler/errors.js +107 -0
  68. package/framework/core/compiler/generator.js +363 -0
  69. package/framework/core/compiler/lexer.js +347 -0
  70. package/framework/core/compiler/main-compiler.js +115 -0
  71. package/framework/core/compiler/parser.js +414 -0
  72. package/framework/core/compiler/plugins/actions-plugin.js +263 -0
  73. package/framework/core/compiler/plugins/compiler-plugin.d.ts +5 -0
  74. package/framework/core/compiler/plugins/compiler-plugin.js +45 -0
  75. package/framework/core/compiler/plugins/file-router-plugin.js +301 -0
  76. package/framework/core/compiler/plugins/router-plugin.js +196 -0
  77. package/framework/core/compiler/transformer.js +398 -0
  78. package/framework/core/component.js +34 -10
  79. package/framework/core/evaluator.js +1 -1
  80. package/framework/core/h.js +430 -0
  81. package/framework/core/hydration.js +83 -0
  82. package/framework/core/signal.js +17 -15
  83. package/framework/server/actions.js +173 -0
  84. package/framework/server/actions.server.js +88 -0
  85. package/framework/server/index.js +18 -0
  86. package/framework/types/actions.d.ts +50 -0
  87. package/framework/types/actions.server.d.ts +36 -0
  88. package/framework/types/compiler/ast.d.ts +166 -0
  89. package/framework/types/compiler/errors-codes.d.ts +23 -0
  90. package/framework/types/compiler/errors.d.ts +43 -0
  91. package/framework/types/compiler/generator.d.ts +8 -0
  92. package/framework/types/compiler/h.d.ts +46 -0
  93. package/framework/types/compiler/lexer.d.ts +27 -0
  94. package/framework/types/compiler/main-compiler.d.ts +60 -0
  95. package/framework/types/compiler/parser.d.ts +4 -0
  96. package/framework/types/compiler/plugins/actions.d.ts +30 -0
  97. package/framework/types/compiler/plugins/compiler-plugin.d.ts +5 -0
  98. package/framework/types/compiler/plugins/file-router-plugins.d.ts +32 -0
  99. package/framework/types/compiler/plugins/router-plugin.d.ts +24 -0
  100. package/framework/types/compiler/transformer.d.ts +28 -0
  101. package/framework/types/hydration.d.ts +30 -0
  102. package/framework/types/index.d.ts +6 -0
  103. package/framework/types/server.d.ts +11 -0
  104. package/framework/types/signal.d.ts +2 -7
  105. package/ghid_go_fusee_integrat_go1_26.txt +276 -0
  106. package/ghid_rust_fusee_rust_1_95_0.txt +1118 -0
  107. package/package.json +44 -36
  108. package/vite.config.js +65 -53
  109. package/create-fusee-go/internal/assets/embed/framework/bin/create-fusee.js +0 -199
@@ -1,3 +1,6 @@
1
+ import { getInFlightStore } from './async-context.js'
2
+ import { _registerResourceCache, getHydratedEntries } from './hydration.js'
3
+
1
4
  let currentEffect = null
2
5
  let currentOwner = null
3
6
  let onEffectCreated = null
@@ -28,6 +31,34 @@ export function batch(fn) {
28
31
  }
29
32
  }
30
33
 
34
+ const asyncJobQueueHigh = []
35
+ const asyncJobQueueLow = []
36
+ let asyncJobFlushScheduled = false
37
+
38
+ function flushAsyncJobs() {
39
+ asyncJobFlushScheduled = false
40
+
41
+ while (asyncJobQueueHigh.length || asyncJobQueueLow.length) {
42
+ const job = asyncJobQueueHigh.shift() || asyncJobQueueLow.shift()
43
+ try {
44
+ job()
45
+ } catch (e) {
46
+ console.warn('[framework] Async job error:', e)
47
+ }
48
+ }
49
+ }
50
+
51
+ export function scheduleAsyncJob(fn, opts = {}) {
52
+ const priority = opts.priority || 'high'
53
+ if (priority === 'low') asyncJobQueueLow.push(fn)
54
+ else asyncJobQueueHigh.push(fn)
55
+
56
+ if (!asyncJobFlushScheduled) {
57
+ asyncJobFlushScheduled = true
58
+ setTimeout(flushAsyncJobs, 0)
59
+ }
60
+ }
61
+
31
62
  function scheduleEffect(effectFn) {
32
63
  if (batchDepth > 0) {
33
64
  pendingEffects.add(effectFn)
@@ -403,6 +434,188 @@ function hasWatchChanged(newValue, oldValue, isMultiSource, equals) {
403
434
  return !equals(newValue, oldValue)
404
435
  }
405
436
 
437
+ export function resource(sourceOrFetcher, fetcherOrOptions, optionsObj) {
438
+ let source = null
439
+ let actualFetcher = null
440
+ let options = {}
441
+
442
+ if (arguments.length === 1) {
443
+ actualFetcher = sourceOrFetcher
444
+ } else if (arguments.length === 2) {
445
+ if (typeof fetcherOrOptions === 'function') {
446
+ source = sourceOrFetcher
447
+ actualFetcher = fetcherOrOptions
448
+ } else {
449
+ actualFetcher = sourceOrFetcher
450
+ options = fetcherOrOptions || {}
451
+ }
452
+ } else if (arguments.length >= 3) {
453
+ source = sourceOrFetcher
454
+ actualFetcher = fetcherOrOptions
455
+ options = optionsObj || {}
456
+ }
457
+
458
+ const data = signal(undefined)
459
+ const loading = signal(false)
460
+ const isFetching = signal(false)
461
+ const error = signal(undefined)
462
+
463
+ let currentPromiseId = 0
464
+ let currentPromise = null
465
+ const staleTime = typeof options.staleTime === 'number' ? options.staleTime : 0
466
+
467
+ const resourceKey = options.key !== undefined ? String(options.key) : (actualFetcher.name || 'anonymous')
468
+ const hydratedEntries = getHydratedEntries(resourceKey)
469
+ const cache = hydratedEntries ? new Map(hydratedEntries) : new Map()
470
+ _registerResourceCache(resourceKey, cache)
471
+
472
+ function serializeKey(input) {
473
+ if (input === undefined) return 'undefined'
474
+ if (input === null) return 'null'
475
+ if (typeof input === 'object') return JSON.stringify(input)
476
+ return String(input)
477
+ }
478
+
479
+ function makeScheduledPromise(runFetcher) {
480
+ try {
481
+ return Promise.resolve(runFetcher())
482
+ } catch (e) {
483
+ return Promise.reject(e)
484
+ }
485
+ }
486
+
487
+ async function load(input, force = false) {
488
+ const key = serializeKey(input)
489
+ const cached = force ? null : cache.get(key)
490
+ const isStale = !cached || (Date.now() - cached.updatedAt > staleTime)
491
+
492
+ if (cached) {
493
+ batch(() => {
494
+ data(cached.data)
495
+ error(undefined)
496
+ loading(false)
497
+ })
498
+ if (!isStale && !force) return
499
+ batch(() => { isFetching(true) })
500
+ } else {
501
+ batch(() => {
502
+ loading(true)
503
+ isFetching(true)
504
+ error(undefined)
505
+ })
506
+ }
507
+
508
+ const id = ++currentPromiseId
509
+
510
+ batch(() => {
511
+ isFetching(true)
512
+ })
513
+
514
+ let promise
515
+
516
+ const { byKey, byFetcher } = getInFlightStore()
517
+
518
+ if (options.key !== undefined) {
519
+ const dedupeKey = `key_${options.key}_${key}`
520
+ promise = byKey.get(dedupeKey)
521
+ if (!promise) {
522
+ promise = makeScheduledPromise(() => actualFetcher(input))
523
+ byKey.set(dedupeKey, promise)
524
+ promise.finally(() => {
525
+ if (byKey.get(dedupeKey) === promise) {
526
+ byKey.delete(dedupeKey)
527
+ }
528
+ }).catch(() => {})
529
+ }
530
+ } else {
531
+ let inFlightMap = byFetcher.get(actualFetcher)
532
+ if (!inFlightMap) {
533
+ inFlightMap = new Map()
534
+ byFetcher.set(actualFetcher, inFlightMap)
535
+ }
536
+ promise = inFlightMap.get(key)
537
+ if (!promise) {
538
+ promise = makeScheduledPromise(() => actualFetcher(input))
539
+ inFlightMap.set(key, promise)
540
+ promise.finally(() => {
541
+ if (inFlightMap.get(key) === promise) {
542
+ inFlightMap.delete(key)
543
+ }
544
+ }).catch(() => {})
545
+ }
546
+ }
547
+
548
+ currentPromise = promise
549
+
550
+ try {
551
+ const result = await promise
552
+
553
+ if (id !== currentPromiseId) return
554
+
555
+ cache.set(key, { data: result, updatedAt: Date.now() })
556
+
557
+ batch(() => {
558
+ data(result)
559
+ loading(false)
560
+ isFetching(false)
561
+ })
562
+
563
+ currentPromise = null
564
+ } catch (err) {
565
+ if (id !== currentPromiseId) return
566
+
567
+ batch(() => {
568
+ error(err)
569
+ loading(false)
570
+ isFetching(false)
571
+ })
572
+
573
+ currentPromise = null
574
+ }
575
+ }
576
+
577
+ if (source) {
578
+ effect(() => {
579
+ const input = typeof source === 'function' ? source() : source
580
+ if (input !== null && input !== false && input !== undefined) {
581
+ load(input)
582
+ }
583
+ })
584
+ } else {
585
+ load()
586
+ }
587
+
588
+ const accessor = () => data()
589
+ accessor.isSignal = true
590
+ accessor.loading = loading
591
+ accessor.isFetching = isFetching
592
+ accessor.error = error
593
+ accessor.read = () => {
594
+ if (loading() && currentPromise) {
595
+ throw currentPromise
596
+ }
597
+ const err = error()
598
+ if (err !== undefined) {
599
+ throw err
600
+ }
601
+ return data()
602
+ }
603
+
604
+ const mutate = (val) => {
605
+ const input = source ? (typeof source === 'function' ? source() : source) : undefined
606
+ const key = serializeKey(input)
607
+ cache.set(key, { data: val, updatedAt: Date.now() })
608
+ data(val)
609
+ }
610
+
611
+ const refetch = () => {
612
+ const input = source ? (typeof source === 'function' ? source() : source) : undefined
613
+ load(input, true)
614
+ }
615
+
616
+ return [accessor, { mutate, refetch }]
617
+ }
618
+
406
619
  function runWatchCleanup(cleanupRef) {
407
620
  if (typeof cleanupRef.fn === 'function') {
408
621
  const fn = cleanupRef.fn
@@ -410,3 +623,59 @@ function runWatchCleanup(cleanupRef) {
410
623
  untrack(() => fn())
411
624
  }
412
625
  }
626
+
627
+ export function createSuspense(renderFn, fallbackFn, options = {}) {
628
+ const out = signal(undefined)
629
+ const pending = signal(false)
630
+ const errSig = signal(undefined)
631
+
632
+ const retryTick = signal(0)
633
+ let token = 0
634
+
635
+ const dispose = effect(() => {
636
+ retryTick()
637
+ const myToken = ++token
638
+ let canceled = false
639
+ onCleanup(() => { canceled = true })
640
+
641
+ try {
642
+ pending(false)
643
+ errSig(undefined)
644
+ const v = renderFn()
645
+ out(v)
646
+ } catch (e) {
647
+ if (e instanceof Promise) {
648
+ pending(true)
649
+ errSig(undefined)
650
+ out(typeof fallbackFn === 'function' ? fallbackFn() : fallbackFn)
651
+
652
+ e.then(
653
+ () => {
654
+ if (canceled) return
655
+ if (myToken !== token) return
656
+ retryTick(retryTick() + 1)
657
+ },
658
+ (err) => {
659
+ if (canceled) return
660
+ if (myToken !== token) return
661
+ errSig(err)
662
+ pending(false)
663
+ if (options.onError) options.onError(err)
664
+ retryTick(retryTick() + 1)
665
+ }
666
+ )
667
+
668
+ return
669
+ }
670
+
671
+ errSig(e)
672
+ pending(false)
673
+ if (options.onError) options.onError(e)
674
+ }
675
+ })
676
+
677
+ out.pending = pending
678
+ out.error = errSig
679
+
680
+ return [out, dispose]
681
+ }
@@ -1,81 +1,322 @@
1
- import { getCurrentInstance, setCurrentInstance } from './component.js'
2
-
3
- const storesRegistry = new Map()
4
- const storePlugins = []
5
-
6
- export function registerStorePlugin(plugin) {
7
- if (typeof plugin === 'function') {
8
- storePlugins.push(plugin)
9
- }
10
- }
11
-
12
- const isDev = typeof import.meta.env !== 'undefined' ? !!import.meta.env.DEV : true
13
- if (isDev && typeof window !== 'undefined') {
14
- window.__FRAMEWORK_STORES__ = storesRegistry
15
- }
16
-
17
- export function defineStore(id, setup) {
18
- if (typeof id !== 'string' || !id.trim()) {
19
- console.error('[framework] defineStore requires a valid string ID.')
20
- }
21
-
22
- function useStore() {
23
- let store = storesRegistry.get(id)
24
-
25
- if (!store) {
26
- const prevInstance = getCurrentInstance()
27
-
28
- setCurrentInstance(null)
29
-
30
- try {
31
- store = setup()
32
-
33
- if (store instanceof Promise) {
34
- console.error(`[framework] Store "${id}" cannot be async.`)
35
- throw new Error(`Async store "${id}" is not supported.`)
36
- }
37
-
38
- } catch (err) {
39
- console.error(`[framework] Error initializing store "${id}":`, err)
40
- throw err
41
- } finally {
42
- setCurrentInstance(prevInstance)
43
- }
44
-
45
- Object.defineProperties(store, {
46
- $id: {
47
- value: id,
48
- enumerable: false
49
- },
50
- $type: {
51
- value: 'store',
52
- enumerable: false
53
- }
54
- })
55
-
56
- for (const plugin of storePlugins) {
57
- try {
58
- plugin(store, id)
59
- } catch (e) {
60
- console.warn(`[framework] Store plugin error for "${id}":`, e)
61
- }
62
- }
63
-
64
- storesRegistry.set(id, store)
65
- }
66
-
67
- return store
68
- }
69
-
70
- useStore.$id = id
71
-
72
- return useStore
73
- }
74
-
75
- export function resetStore(id) {
76
- storesRegistry.delete(id)
77
- }
78
-
79
- export function clearStores() {
80
- storesRegistry.clear()
81
- }
1
+ import { getCurrentInstance, setCurrentInstance } from './component.js'
2
+ import { batch, effect } from './signal.js'
3
+
4
+
5
+ const storesRegistry = new Map()
6
+ const storePlugins = []
7
+ const initializationStack = new Set()
8
+
9
+ export const MutationType = {
10
+ DIRECT: 'direct',
11
+ PATCH_OBJECT: 'patch object',
12
+ PATCH_FUNCTION: 'patch function'
13
+ }
14
+
15
+ export function registerStorePlugin(plugin) {
16
+ if (typeof plugin === 'function') {
17
+ storePlugins.push(plugin)
18
+ }
19
+ }
20
+
21
+ export function useNestedStore(useStoreFn) {
22
+ if (typeof useStoreFn !== 'function' || !useStoreFn.$id) {
23
+ console.error('[framework] useNestedStore requires a valid store hook (defineStore result)')
24
+ throw new Error('Invalid store hook provided to useNestedStore')
25
+ }
26
+
27
+ const targetId = useStoreFn.$id
28
+
29
+ if (initializationStack.has(targetId)) {
30
+ const chain = Array.from(initializationStack).concat(targetId).join(' -> ')
31
+ console.error(`[framework] Circular dependency detected: ${chain}`)
32
+ throw new Error(`Circular store dependency detected: ${chain}`)
33
+ }
34
+
35
+ return useStoreFn()
36
+ }
37
+
38
+ const isDev = typeof import.meta.env !== 'undefined' ? !!import.meta.env.DEV : true
39
+ if (isDev && typeof window !== 'undefined') {
40
+ window.__FRAMEWORK_STORES__ = storesRegistry
41
+ }
42
+
43
+ export function defineStore(id, setup) {
44
+ if (typeof id !== 'string' || !id.trim()) {
45
+ console.error('[framework] defineStore requires a valid string ID.')
46
+ }
47
+
48
+ function useStore() {
49
+ let store = storesRegistry.get(id)
50
+
51
+ if (!store) {
52
+ const prevInstance = getCurrentInstance()
53
+
54
+ setCurrentInstance(null)
55
+ initializationStack.add(id)
56
+
57
+ try {
58
+ store = setup()
59
+
60
+ if (store instanceof Promise) {
61
+ console.error(`[framework] Store "${id}" cannot be async.`)
62
+ throw new Error(`Async store "${id}" is not supported.`)
63
+ }
64
+
65
+ } catch (err) {
66
+ console.error(`[framework] Error initializing store "${id}":`, err)
67
+ throw err
68
+ } finally {
69
+ initializationStack.delete(id)
70
+ setCurrentInstance(prevInstance)
71
+ }
72
+
73
+ const initialState = {}
74
+ const signalKeys = []
75
+ const getterKeys = []
76
+ for (const key in store) {
77
+ if (typeof store[key] === 'function' && store[key].isSignal) {
78
+ if (store[key].readonly) {
79
+ getterKeys.push(key)
80
+ } else {
81
+ initialState[key] = store[key]()
82
+ signalKeys.push(key)
83
+ }
84
+ }
85
+ }
86
+
87
+ // Subscription management
88
+ const subscriptions = new Map()
89
+ let subscriptionId = 0
90
+ let currentMutation = null
91
+
92
+ function notifySubscribers() {
93
+ if (!currentMutation) return
94
+ const mutation = currentMutation
95
+ currentMutation = null
96
+
97
+ const state = {}
98
+ for (const key of signalKeys) {
99
+ state[key] = store[key]()
100
+ }
101
+
102
+ for (const [subId, sub] of subscriptions) {
103
+ if (sub.flush === 'sync') {
104
+ sub.callback(mutation, state)
105
+ } else {
106
+ // Default: post (after all batched updates)
107
+ queueMicrotask(() => {
108
+ if (subscriptions.has(subId)) {
109
+ sub.callback(mutation, state)
110
+ }
111
+ })
112
+ }
113
+ }
114
+ }
115
+
116
+ function wrapSignal(key, originalSignal) {
117
+ const wrapped = function(newValue) {
118
+ if (arguments.length === 0) {
119
+ return originalSignal()
120
+ } else {
121
+ const result = originalSignal(newValue)
122
+ if (!currentMutation) {
123
+ currentMutation = {
124
+ type: MutationType.DIRECT,
125
+ storeId: id,
126
+ payload: { [key]: newValue }
127
+ }
128
+ notifySubscribers()
129
+ }
130
+ return result
131
+ }
132
+ }
133
+ wrapped.isSignal = true
134
+ for (const method of ['map', 'filter', 'slice', 'concat', 'flat', 'flatMap',
135
+ 'find', 'findLast', 'findIndex', 'findLastIndex',
136
+ 'indexOf', 'lastIndexOf', 'includes', 'every', 'some',
137
+ 'reduce', 'at', 'join', 'push', 'pop', 'shift', 'unshift',
138
+ 'splice', 'remove', 'clear', 'sort', 'reverse']) {
139
+ if (originalSignal[method]) {
140
+ wrapped[method] = originalSignal[method]
141
+ }
142
+ }
143
+ return wrapped
144
+ }
145
+
146
+ function wrapGetter(key, originalGetter) {
147
+ const wrapped = function(newValue) {
148
+ if (arguments.length === 0) {
149
+ return originalGetter()
150
+ } else {
151
+ console.warn(`[framework] Getter "${key}" is read-only`)
152
+ return originalGetter()
153
+ }
154
+ }
155
+ wrapped.isSignal = true
156
+ wrapped.readonly = true
157
+ for (const method of ['map', 'filter', 'slice', 'concat', 'flat', 'flatMap',
158
+ 'find', 'findLast', 'findIndex', 'findLastIndex',
159
+ 'indexOf', 'lastIndexOf', 'includes', 'every', 'some',
160
+ 'reduce', 'at', 'join']) {
161
+ if (originalGetter[method]) {
162
+ wrapped[method] = originalGetter[method]
163
+ }
164
+ }
165
+ return wrapped
166
+ }
167
+
168
+ const wrappedSignals = {}
169
+ for (const key of signalKeys) {
170
+ wrappedSignals[key] = wrapSignal(key, store[key])
171
+ store[key] = wrappedSignals[key]
172
+ }
173
+
174
+ for (const key of getterKeys) {
175
+ store[key] = wrapGetter(key, store[key])
176
+ }
177
+
178
+ Object.defineProperties(store, {
179
+ id: {
180
+ value: id,
181
+ enumerable: false
182
+ },
183
+ type: {
184
+ value: 'store',
185
+ enumerable: false
186
+ },
187
+ patch: {
188
+ value: (arg) => {
189
+ const isObjectPatch = arg && typeof arg === 'object'
190
+ currentMutation = {
191
+ type: isObjectPatch ? MutationType.PATCH_OBJECT : MutationType.PATCH_FUNCTION,
192
+ storeId: id,
193
+ payload: isObjectPatch ? { ...arg } : undefined
194
+ }
195
+
196
+ batch(() => {
197
+ if (typeof arg === 'function') {
198
+ arg(store)
199
+ } else if (isObjectPatch) {
200
+ for (const key in arg) {
201
+ if (typeof store[key] === 'function' && store[key].isSignal && !store[key].readonly) {
202
+ const originalSignal = wrappedSignals[key]
203
+ originalSignal(arg[key])
204
+ }
205
+ }
206
+ }
207
+ })
208
+
209
+ notifySubscribers()
210
+ },
211
+ enumerable: false
212
+ },
213
+ reset: {
214
+ value: () => {
215
+ currentMutation = {
216
+ type: MutationType.PATCH_FUNCTION,
217
+ storeId: id,
218
+ payload: undefined
219
+ }
220
+
221
+ batch(() => {
222
+ for (const key in initialState) {
223
+ const originalSignal = wrappedSignals[key]
224
+ originalSignal(initialState[key])
225
+ }
226
+ })
227
+
228
+ notifySubscribers()
229
+ },
230
+ enumerable: false
231
+ },
232
+ subscribe: {
233
+ value: (callback, options = {}) => {
234
+ if (typeof callback !== 'function') {
235
+ console.warn('[framework] subscribe callback must be a function')
236
+ return () => {}
237
+ }
238
+
239
+ const subId = ++subscriptionId
240
+ const sub = {
241
+ callback,
242
+ flush: options.flush || 'post',
243
+ detached: options.detached || false
244
+ }
245
+ subscriptions.set(subId, sub)
246
+
247
+ if (!sub.detached) {
248
+ const instance = getCurrentInstance()
249
+ if (instance && instance._unmountHooks) {
250
+ instance._unmountHooks.push(() => {
251
+ subscriptions.delete(subId)
252
+ })
253
+ }
254
+ }
255
+
256
+ return () => {
257
+ subscriptions.delete(subId)
258
+ }
259
+ },
260
+ enumerable: false
261
+ }
262
+ })
263
+
264
+ for (const plugin of storePlugins) {
265
+ try {
266
+ plugin(store, id)
267
+ } catch (e) {
268
+ console.warn(`[framework] Store plugin error for "${id}":`, e)
269
+ }
270
+ }
271
+
272
+ storesRegistry.set(id, store)
273
+ }
274
+
275
+ return store
276
+ }
277
+
278
+ useStore.$id = id
279
+
280
+ return useStore
281
+ }
282
+
283
+ export function resetStore(id) {
284
+ storesRegistry.delete(id)
285
+ }
286
+
287
+ export function clearStores() {
288
+ storesRegistry.clear()
289
+ }
290
+
291
+ export function storeToRefs(store) {
292
+ const refs = {}
293
+ for (const key in store) {
294
+ const value = store[key]
295
+ if (typeof value === 'function' && value.isSignal) {
296
+ refs[key] = value
297
+ }
298
+ }
299
+ return refs
300
+ }
301
+
302
+ export function storeToState(store) {
303
+ const state = {}
304
+ for (const key in store) {
305
+ const value = store[key]
306
+ if (typeof value === 'function' && value.isSignal && !value.readonly) {
307
+ state[key] = value
308
+ }
309
+ }
310
+ return state
311
+ }
312
+
313
+ export function storeToGetters(store) {
314
+ const getters = {}
315
+ for (const key in store) {
316
+ const value = store[key]
317
+ if (typeof value === 'function' && value.isSignal && value.readonly) {
318
+ getters[key] = value
319
+ }
320
+ }
321
+ return getters
322
+ }