fusee-framework 1.7.0 → 1.9.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 +206 -139
- package/app/benchmark/benchmark.js +2 -2
- package/app/benchmark/vite.config.js +23 -0
- package/app/main.js +24 -3
- package/app/main.ts +7 -1
- package/app/pages/_layout.js +27 -0
- package/app/pages/async-work.js +65 -0
- package/app/pages/index.js +25 -5
- package/app/pages/index.ts +9 -0
- package/app/pages/router-example.js +24 -1
- package/app/pages/scroll-demo.ts +102 -0
- package/app/providers/ChildComponent.js +8 -2
- package/app/providers/ChildComponent.ts +47 -47
- package/app/providers/GrandchildComponent.js +19 -0
- package/app/providers/ParentComponent.js +5 -1
- package/app/routes/RouterExample.js +23 -1
- package/app/routes/TestResource.js +47 -0
- package/app/services.js +15 -0
- package/auto-imports.d.ts +12 -0
- package/framework/auto-import-preset.js +15 -9
- package/framework/core/async-context.js +27 -0
- package/framework/core/compiler/ast.js +167 -0
- package/framework/core/compiler/errors-list.js +47 -0
- package/framework/core/compiler/errors.js +107 -0
- package/framework/core/compiler/generator.js +363 -0
- package/framework/core/compiler/lexer.js +347 -0
- package/framework/core/compiler/main-compiler.js +115 -0
- package/framework/core/compiler/parser.js +414 -0
- package/framework/core/compiler/plugins/actions-plugin.js +263 -0
- package/framework/core/compiler/plugins/compiler-plugin.d.ts +5 -0
- package/framework/core/compiler/plugins/compiler-plugin.js +45 -0
- package/framework/core/compiler/plugins/file-router-plugin.js +301 -0
- package/framework/core/compiler/plugins/router-plugin.js +196 -0
- package/framework/core/compiler/transformer.js +398 -0
- package/framework/core/component.js +61 -23
- package/framework/core/di.js +187 -0
- package/framework/core/directives.js +40 -96
- package/framework/core/evaluator.js +1 -1
- package/framework/core/h.js +430 -0
- package/framework/core/hydration.js +83 -0
- package/framework/core/reconcile.js +151 -0
- package/framework/core/signal.js +269 -0
- package/framework/core/store.js +322 -81
- package/framework/index.js +5 -3
- package/framework/router/file-router.js +1 -1
- package/framework/router/router.js +113 -21
- package/framework/server/actions.js +173 -0
- package/framework/server/actions.server.js +88 -0
- package/framework/server/index.js +18 -0
- package/framework/types/actions.d.ts +50 -0
- package/framework/types/actions.server.d.ts +36 -0
- package/framework/types/compiler/ast.d.ts +166 -0
- package/framework/types/compiler/errors-codes.d.ts +23 -0
- package/framework/types/compiler/errors.d.ts +43 -0
- package/framework/types/compiler/generator.d.ts +8 -0
- package/framework/types/compiler/h.d.ts +46 -0
- package/framework/types/compiler/lexer.d.ts +27 -0
- package/framework/types/compiler/main-compiler.d.ts +60 -0
- package/framework/types/compiler/parser.d.ts +4 -0
- package/framework/types/compiler/plugins/actions.d.ts +30 -0
- package/framework/types/compiler/plugins/compiler-plugin.d.ts +5 -0
- package/framework/types/compiler/plugins/file-router-plugins.d.ts +32 -0
- package/framework/types/compiler/plugins/router-plugin.d.ts +24 -0
- package/framework/types/compiler/transformer.d.ts +28 -0
- package/framework/types/component.d.ts +89 -88
- package/framework/types/di.d.ts +43 -0
- package/framework/types/hydration.d.ts +30 -0
- package/framework/types/index.d.ts +38 -22
- package/framework/types/reconcile.d.ts +7 -0
- package/framework/types/router.d.ts +11 -1
- package/framework/types/server.d.ts +11 -0
- package/framework/types/signal.d.ts +142 -79
- package/framework/types/store.d.ts +66 -8
- package/ghid_go_fusee_integrat_go1_26.txt +276 -0
- package/ghid_rust_fusee_rust_1_95_0.txt +1118 -0
- package/package.json +44 -36
- package/vite.config.js +65 -47
package/framework/core/signal.js
CHANGED
|
@@ -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
|
+
}
|
package/framework/core/store.js
CHANGED
|
@@ -1,81 +1,322 @@
|
|
|
1
|
-
import { getCurrentInstance, setCurrentInstance } from './component.js'
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
+
}
|