fusee-framework 2.7.0 → 2.7.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.
- package/README.md +98 -26
- package/bin/create-fusee-linux +0 -0
- package/bin/create-fusee-mac-arm64 +0 -0
- package/bin/create-fusee-mac-intel +0 -0
- package/bin/create-fusee-win.exe +0 -0
- package/framework/auto-import-preset.js +20 -12
- package/framework/auto-imports.d.ts +68 -39
- package/framework/core/compiler.js +310 -310
- package/framework/core/component-core.js +445 -0
- package/framework/core/component.js +11 -440
- package/framework/core/composable.js +29 -29
- package/framework/core/h.js +119 -44
- package/framework/core/jsx-helpers.js +11 -2
- package/framework/core/jsx-runtime.js +73 -73
- package/framework/core/rust-compiler/pkg/fusee_compiler_bg.wasm +0 -0
- package/framework/core/store.js +333 -333
- package/framework/globals.js +42 -0
- package/framework/index.js +22 -55
- package/framework/router/file-router.js +184 -184
- package/framework/server/actions.js +183 -183
- package/framework/server/generate-manifest.js +3 -3
- package/framework/types/component.d.ts +97 -86
- package/framework/types/index.d.ts +118 -117
- package/package.json +83 -80
- package/framework/core/rust-compiler/Cargo.toml +0 -31
- package/framework/core/rust-compiler/README.md +0 -69
- package/framework/core/rust-compiler/pkg/README.md +0 -69
- package/framework/core/rust-compiler/pkg/fusee_compiler.d.ts +0 -6
- package/framework/core/rust-compiler/pkg/fusee_compiler.js +0 -9
- package/framework/core/rust-compiler/pkg/fusee_compiler_bg.js +0 -137
- package/framework/core/rust-compiler/pkg/fusee_compiler_bg.wasm.d.ts +0 -11
- package/framework/core/rust-compiler/pkg/package.json +0 -18
- package/framework/core/rust-compiler/src/ast.rs +0 -274
- package/framework/core/rust-compiler/src/errors.rs +0 -204
- package/framework/core/rust-compiler/src/generator.rs +0 -583
- package/framework/core/rust-compiler/src/lexer.rs +0 -414
- package/framework/core/rust-compiler/src/lib.rs +0 -87
- package/framework/core/rust-compiler/src/main_compiler.rs +0 -165
- package/framework/core/rust-compiler/src/parser.rs +0 -516
- package/framework/core/rust-compiler/src/ssr_generator.rs +0 -688
- package/framework/core/rust-compiler/src/transformer.rs +0 -562
- package/framework/core/rust-compiler/src/utils.rs +0 -10
- package/framework/engine-go/actions.go +0 -112
- package/framework/engine-go/cmd/server/main.go +0 -233
- package/framework/engine-go/comet/comet.go +0 -41
- package/framework/engine-go/comet/fragment.go +0 -51
- package/framework/engine-go/dispatcher.go +0 -120
- package/framework/engine-go/elements.go +0 -248
- package/framework/engine-go/evalexpr/evalexpr.go +0 -733
- package/framework/engine-go/go.mod +0 -3
- package/framework/engine-go/hydration.go +0 -43
- package/framework/engine-go/load-helpers.go +0 -29
- package/framework/engine-go/manifest.go +0 -41
- package/framework/engine-go/reactive.go +0 -123
- package/framework/engine-go/render.go +0 -635
- package/framework/engine-go/reqcontext.go +0 -48
- package/framework/engine-go/router.go +0 -85
- package/framework/engine-go/rustast/rustast.go +0 -317
- package/framework/engine-go/ssr.go +0 -103
- package/framework/engine-go/static-assets.go +0 -139
- package/framework/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
package/framework/core/store.js
CHANGED
|
@@ -1,333 +1,333 @@
|
|
|
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
|
-
/** Registers a plugin that receives each newly initialized store. */
|
|
16
|
-
export function registerStorePlugin(plugin) {
|
|
17
|
-
if (typeof plugin === 'function') {
|
|
18
|
-
storePlugins.push(plugin)
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** Resolves a nested store hook while detecting circular initialization. */
|
|
23
|
-
export function useNestedStore(useStoreFn) {
|
|
24
|
-
if (typeof useStoreFn !== 'function' || !useStoreFn.$id) {
|
|
25
|
-
console.error('[framework] useNestedStore requires a valid store hook (defineStore result)')
|
|
26
|
-
throw new Error('Invalid store hook provided to useNestedStore')
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const targetId = useStoreFn.$id
|
|
30
|
-
|
|
31
|
-
if (initializationStack.has(targetId)) {
|
|
32
|
-
const chain = Array.from(initializationStack).concat(targetId).join(' -> ')
|
|
33
|
-
console.error(`[framework] Circular dependency detected: ${chain}`)
|
|
34
|
-
throw new Error(`Circular store dependency detected: ${chain}`)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return useStoreFn()
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const isDev = typeof import.meta.env !== 'undefined' ? !!import.meta.env.DEV : true
|
|
41
|
-
if (isDev && typeof window !== 'undefined') {
|
|
42
|
-
window.__FRAMEWORK_STORES__ = storesRegistry
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function defineStore(id, setup) {
|
|
46
|
-
if (typeof id !== 'string' || !id.trim()) {
|
|
47
|
-
console.error('[framework] defineStore requires a valid string ID.')
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Creates the store once, then returns the cached store instance. */
|
|
51
|
-
function useStore() {
|
|
52
|
-
let store = storesRegistry.get(id)
|
|
53
|
-
|
|
54
|
-
if (!store) {
|
|
55
|
-
const prevInstance = getCurrentInstance()
|
|
56
|
-
|
|
57
|
-
setCurrentInstance(null)
|
|
58
|
-
initializationStack.add(id)
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
store = setup()
|
|
62
|
-
|
|
63
|
-
if (store instanceof Promise) {
|
|
64
|
-
console.error(`[framework] Store "${id}" cannot be async.`)
|
|
65
|
-
throw new Error(`Async store "${id}" is not supported.`)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
} catch (err) {
|
|
69
|
-
console.error(`[framework] Error initializing store "${id}":`, err)
|
|
70
|
-
throw err
|
|
71
|
-
} finally {
|
|
72
|
-
initializationStack.delete(id)
|
|
73
|
-
setCurrentInstance(prevInstance)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const initialState = {}
|
|
77
|
-
const signalKeys = []
|
|
78
|
-
const getterKeys = []
|
|
79
|
-
for (const key in store) {
|
|
80
|
-
if (typeof store[key] === 'function' && store[key].isSignal) {
|
|
81
|
-
if (store[key].readonly) {
|
|
82
|
-
getterKeys.push(key)
|
|
83
|
-
} else {
|
|
84
|
-
initialState[key] = store[key]()
|
|
85
|
-
signalKeys.push(key)
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Subscription management
|
|
91
|
-
const subscriptions = new Map()
|
|
92
|
-
let subscriptionId = 0
|
|
93
|
-
let currentMutation = null
|
|
94
|
-
|
|
95
|
-
/** Publishes the pending mutation to synchronous and queued subscribers. */
|
|
96
|
-
function notifySubscribers() {
|
|
97
|
-
if (!currentMutation) return
|
|
98
|
-
const mutation = currentMutation
|
|
99
|
-
currentMutation = null
|
|
100
|
-
|
|
101
|
-
const state = {}
|
|
102
|
-
for (const key of signalKeys) {
|
|
103
|
-
state[key] = store[key]()
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
for (const [subId, sub] of subscriptions) {
|
|
107
|
-
if (sub.flush === 'sync') {
|
|
108
|
-
sub.callback(mutation, state)
|
|
109
|
-
} else {
|
|
110
|
-
// Default: post (after all batched updates)
|
|
111
|
-
queueMicrotask(() => {
|
|
112
|
-
if (subscriptions.has(subId)) {
|
|
113
|
-
sub.callback(mutation, state)
|
|
114
|
-
}
|
|
115
|
-
})
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/** Wraps a writable signal so direct writes produce store mutations. */
|
|
121
|
-
function wrapSignal(key, originalSignal) {
|
|
122
|
-
const wrapped = function(newValue) {
|
|
123
|
-
if (arguments.length === 0) {
|
|
124
|
-
return originalSignal()
|
|
125
|
-
} else {
|
|
126
|
-
const result = originalSignal(newValue)
|
|
127
|
-
if (!currentMutation) {
|
|
128
|
-
currentMutation = {
|
|
129
|
-
type: MutationType.DIRECT,
|
|
130
|
-
storeId: id,
|
|
131
|
-
payload: { [key]: newValue }
|
|
132
|
-
}
|
|
133
|
-
notifySubscribers()
|
|
134
|
-
}
|
|
135
|
-
return result
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
wrapped.isSignal = true
|
|
139
|
-
for (const method of ['map', 'filter', 'slice', 'concat', 'flat', 'flatMap',
|
|
140
|
-
'find', 'findLast', 'findIndex', 'findLastIndex',
|
|
141
|
-
'indexOf', 'lastIndexOf', 'includes', 'every', 'some',
|
|
142
|
-
'reduce', 'at', 'join', 'push', 'pop', 'shift', 'unshift',
|
|
143
|
-
'splice', 'remove', 'clear', 'sort', 'reverse']) {
|
|
144
|
-
if (originalSignal[method]) {
|
|
145
|
-
wrapped[method] = originalSignal[method]
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return wrapped
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/** Wraps a computed signal as a read-only store getter. */
|
|
152
|
-
function wrapGetter(key, originalGetter) {
|
|
153
|
-
const wrapped = function(newValue) {
|
|
154
|
-
if (arguments.length === 0) {
|
|
155
|
-
return originalGetter()
|
|
156
|
-
} else {
|
|
157
|
-
console.warn(`[framework] Getter "${key}" is read-only`)
|
|
158
|
-
return originalGetter()
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
wrapped.isSignal = true
|
|
162
|
-
wrapped.readonly = true
|
|
163
|
-
for (const method of ['map', 'filter', 'slice', 'concat', 'flat', 'flatMap',
|
|
164
|
-
'find', 'findLast', 'findIndex', 'findLastIndex',
|
|
165
|
-
'indexOf', 'lastIndexOf', 'includes', 'every', 'some',
|
|
166
|
-
'reduce', 'at', 'join']) {
|
|
167
|
-
if (originalGetter[method]) {
|
|
168
|
-
wrapped[method] = originalGetter[method]
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return wrapped
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const wrappedSignals = {}
|
|
175
|
-
for (const key of signalKeys) {
|
|
176
|
-
wrappedSignals[key] = wrapSignal(key, store[key])
|
|
177
|
-
store[key] = wrappedSignals[key]
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
for (const key of getterKeys) {
|
|
181
|
-
store[key] = wrapGetter(key, store[key])
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
Object.defineProperties(store, {
|
|
185
|
-
id: {
|
|
186
|
-
value: id,
|
|
187
|
-
enumerable: false
|
|
188
|
-
},
|
|
189
|
-
type: {
|
|
190
|
-
value: 'store',
|
|
191
|
-
enumerable: false
|
|
192
|
-
},
|
|
193
|
-
patch: {
|
|
194
|
-
value: (arg) => {
|
|
195
|
-
const isObjectPatch = arg && typeof arg === 'object'
|
|
196
|
-
currentMutation = {
|
|
197
|
-
type: isObjectPatch ? MutationType.PATCH_OBJECT : MutationType.PATCH_FUNCTION,
|
|
198
|
-
storeId: id,
|
|
199
|
-
payload: isObjectPatch ? { ...arg } : undefined
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
batch(() => {
|
|
203
|
-
if (typeof arg === 'function') {
|
|
204
|
-
arg(store)
|
|
205
|
-
} else if (isObjectPatch) {
|
|
206
|
-
for (const key in arg) {
|
|
207
|
-
if (typeof store[key] === 'function' && store[key].isSignal && !store[key].readonly) {
|
|
208
|
-
const originalSignal = wrappedSignals[key]
|
|
209
|
-
originalSignal(arg[key])
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
notifySubscribers()
|
|
216
|
-
},
|
|
217
|
-
enumerable: false
|
|
218
|
-
},
|
|
219
|
-
reset: {
|
|
220
|
-
value: () => {
|
|
221
|
-
currentMutation = {
|
|
222
|
-
type: MutationType.PATCH_FUNCTION,
|
|
223
|
-
storeId: id,
|
|
224
|
-
payload: undefined
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
batch(() => {
|
|
228
|
-
for (const key in initialState) {
|
|
229
|
-
const originalSignal = wrappedSignals[key]
|
|
230
|
-
originalSignal(initialState[key])
|
|
231
|
-
}
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
notifySubscribers()
|
|
235
|
-
},
|
|
236
|
-
enumerable: false
|
|
237
|
-
},
|
|
238
|
-
subscribe: {
|
|
239
|
-
value: (callback, options = {}) => {
|
|
240
|
-
if (typeof callback !== 'function') {
|
|
241
|
-
console.warn('[framework] subscribe callback must be a function')
|
|
242
|
-
return () => {}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const subId = ++subscriptionId
|
|
246
|
-
const sub = {
|
|
247
|
-
callback,
|
|
248
|
-
flush: options.flush || 'post',
|
|
249
|
-
detached: options.detached || false
|
|
250
|
-
}
|
|
251
|
-
subscriptions.set(subId, sub)
|
|
252
|
-
|
|
253
|
-
if (!sub.detached) {
|
|
254
|
-
const instance = getCurrentInstance()
|
|
255
|
-
if (instance && instance._unmountHooks) {
|
|
256
|
-
instance._unmountHooks.push(() => {
|
|
257
|
-
subscriptions.delete(subId)
|
|
258
|
-
})
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
return () => {
|
|
263
|
-
subscriptions.delete(subId)
|
|
264
|
-
}
|
|
265
|
-
},
|
|
266
|
-
enumerable: false
|
|
267
|
-
}
|
|
268
|
-
})
|
|
269
|
-
|
|
270
|
-
for (const plugin of storePlugins) {
|
|
271
|
-
try {
|
|
272
|
-
plugin(store, id)
|
|
273
|
-
} catch (e) {
|
|
274
|
-
console.warn(`[framework] Store plugin error for "${id}":`, e)
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
storesRegistry.set(id, store)
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
return store
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
useStore.$id = id
|
|
285
|
-
|
|
286
|
-
return useStore
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/** Drops one cached store so its setup function runs again on next use. */
|
|
290
|
-
export function resetStore(id) {
|
|
291
|
-
storesRegistry.delete(id)
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/** Removes every registered store instance. */
|
|
295
|
-
export function clearStores() {
|
|
296
|
-
storesRegistry.clear()
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/** Returns all signal-backed properties from a store. */
|
|
300
|
-
export function storeToRefs(store) {
|
|
301
|
-
const refs = {}
|
|
302
|
-
for (const key in store) {
|
|
303
|
-
const value = store[key]
|
|
304
|
-
if (typeof value === 'function' && value.isSignal) {
|
|
305
|
-
refs[key] = value
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
return refs
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/** Returns only writable signal-backed state from a store. */
|
|
312
|
-
export function storeToState(store) {
|
|
313
|
-
const state = {}
|
|
314
|
-
for (const key in store) {
|
|
315
|
-
const value = store[key]
|
|
316
|
-
if (typeof value === 'function' && value.isSignal && !value.readonly) {
|
|
317
|
-
state[key] = value
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
return state
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/** Returns only read-only signal-backed getters from a store. */
|
|
324
|
-
export function storeToGetters(store) {
|
|
325
|
-
const getters = {}
|
|
326
|
-
for (const key in store) {
|
|
327
|
-
const value = store[key]
|
|
328
|
-
if (typeof value === 'function' && value.isSignal && value.readonly) {
|
|
329
|
-
getters[key] = value
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
return getters
|
|
333
|
-
}
|
|
1
|
+
import { getCurrentInstance, setCurrentInstance } from './component-core.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
|
+
/** Registers a plugin that receives each newly initialized store. */
|
|
16
|
+
export function registerStorePlugin(plugin) {
|
|
17
|
+
if (typeof plugin === 'function') {
|
|
18
|
+
storePlugins.push(plugin)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Resolves a nested store hook while detecting circular initialization. */
|
|
23
|
+
export function useNestedStore(useStoreFn) {
|
|
24
|
+
if (typeof useStoreFn !== 'function' || !useStoreFn.$id) {
|
|
25
|
+
console.error('[framework] useNestedStore requires a valid store hook (defineStore result)')
|
|
26
|
+
throw new Error('Invalid store hook provided to useNestedStore')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const targetId = useStoreFn.$id
|
|
30
|
+
|
|
31
|
+
if (initializationStack.has(targetId)) {
|
|
32
|
+
const chain = Array.from(initializationStack).concat(targetId).join(' -> ')
|
|
33
|
+
console.error(`[framework] Circular dependency detected: ${chain}`)
|
|
34
|
+
throw new Error(`Circular store dependency detected: ${chain}`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return useStoreFn()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const isDev = typeof import.meta.env !== 'undefined' ? !!import.meta.env.DEV : true
|
|
41
|
+
if (isDev && typeof window !== 'undefined') {
|
|
42
|
+
window.__FRAMEWORK_STORES__ = storesRegistry
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function defineStore(id, setup) {
|
|
46
|
+
if (typeof id !== 'string' || !id.trim()) {
|
|
47
|
+
console.error('[framework] defineStore requires a valid string ID.')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Creates the store once, then returns the cached store instance. */
|
|
51
|
+
function useStore() {
|
|
52
|
+
let store = storesRegistry.get(id)
|
|
53
|
+
|
|
54
|
+
if (!store) {
|
|
55
|
+
const prevInstance = getCurrentInstance()
|
|
56
|
+
|
|
57
|
+
setCurrentInstance(null)
|
|
58
|
+
initializationStack.add(id)
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
store = setup()
|
|
62
|
+
|
|
63
|
+
if (store instanceof Promise) {
|
|
64
|
+
console.error(`[framework] Store "${id}" cannot be async.`)
|
|
65
|
+
throw new Error(`Async store "${id}" is not supported.`)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.error(`[framework] Error initializing store "${id}":`, err)
|
|
70
|
+
throw err
|
|
71
|
+
} finally {
|
|
72
|
+
initializationStack.delete(id)
|
|
73
|
+
setCurrentInstance(prevInstance)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const initialState = {}
|
|
77
|
+
const signalKeys = []
|
|
78
|
+
const getterKeys = []
|
|
79
|
+
for (const key in store) {
|
|
80
|
+
if (typeof store[key] === 'function' && store[key].isSignal) {
|
|
81
|
+
if (store[key].readonly) {
|
|
82
|
+
getterKeys.push(key)
|
|
83
|
+
} else {
|
|
84
|
+
initialState[key] = store[key]()
|
|
85
|
+
signalKeys.push(key)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Subscription management
|
|
91
|
+
const subscriptions = new Map()
|
|
92
|
+
let subscriptionId = 0
|
|
93
|
+
let currentMutation = null
|
|
94
|
+
|
|
95
|
+
/** Publishes the pending mutation to synchronous and queued subscribers. */
|
|
96
|
+
function notifySubscribers() {
|
|
97
|
+
if (!currentMutation) return
|
|
98
|
+
const mutation = currentMutation
|
|
99
|
+
currentMutation = null
|
|
100
|
+
|
|
101
|
+
const state = {}
|
|
102
|
+
for (const key of signalKeys) {
|
|
103
|
+
state[key] = store[key]()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const [subId, sub] of subscriptions) {
|
|
107
|
+
if (sub.flush === 'sync') {
|
|
108
|
+
sub.callback(mutation, state)
|
|
109
|
+
} else {
|
|
110
|
+
// Default: post (after all batched updates)
|
|
111
|
+
queueMicrotask(() => {
|
|
112
|
+
if (subscriptions.has(subId)) {
|
|
113
|
+
sub.callback(mutation, state)
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Wraps a writable signal so direct writes produce store mutations. */
|
|
121
|
+
function wrapSignal(key, originalSignal) {
|
|
122
|
+
const wrapped = function(newValue) {
|
|
123
|
+
if (arguments.length === 0) {
|
|
124
|
+
return originalSignal()
|
|
125
|
+
} else {
|
|
126
|
+
const result = originalSignal(newValue)
|
|
127
|
+
if (!currentMutation) {
|
|
128
|
+
currentMutation = {
|
|
129
|
+
type: MutationType.DIRECT,
|
|
130
|
+
storeId: id,
|
|
131
|
+
payload: { [key]: newValue }
|
|
132
|
+
}
|
|
133
|
+
notifySubscribers()
|
|
134
|
+
}
|
|
135
|
+
return result
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
wrapped.isSignal = true
|
|
139
|
+
for (const method of ['map', 'filter', 'slice', 'concat', 'flat', 'flatMap',
|
|
140
|
+
'find', 'findLast', 'findIndex', 'findLastIndex',
|
|
141
|
+
'indexOf', 'lastIndexOf', 'includes', 'every', 'some',
|
|
142
|
+
'reduce', 'at', 'join', 'push', 'pop', 'shift', 'unshift',
|
|
143
|
+
'splice', 'remove', 'clear', 'sort', 'reverse']) {
|
|
144
|
+
if (originalSignal[method]) {
|
|
145
|
+
wrapped[method] = originalSignal[method]
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return wrapped
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Wraps a computed signal as a read-only store getter. */
|
|
152
|
+
function wrapGetter(key, originalGetter) {
|
|
153
|
+
const wrapped = function(newValue) {
|
|
154
|
+
if (arguments.length === 0) {
|
|
155
|
+
return originalGetter()
|
|
156
|
+
} else {
|
|
157
|
+
console.warn(`[framework] Getter "${key}" is read-only`)
|
|
158
|
+
return originalGetter()
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
wrapped.isSignal = true
|
|
162
|
+
wrapped.readonly = true
|
|
163
|
+
for (const method of ['map', 'filter', 'slice', 'concat', 'flat', 'flatMap',
|
|
164
|
+
'find', 'findLast', 'findIndex', 'findLastIndex',
|
|
165
|
+
'indexOf', 'lastIndexOf', 'includes', 'every', 'some',
|
|
166
|
+
'reduce', 'at', 'join']) {
|
|
167
|
+
if (originalGetter[method]) {
|
|
168
|
+
wrapped[method] = originalGetter[method]
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return wrapped
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const wrappedSignals = {}
|
|
175
|
+
for (const key of signalKeys) {
|
|
176
|
+
wrappedSignals[key] = wrapSignal(key, store[key])
|
|
177
|
+
store[key] = wrappedSignals[key]
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
for (const key of getterKeys) {
|
|
181
|
+
store[key] = wrapGetter(key, store[key])
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
Object.defineProperties(store, {
|
|
185
|
+
id: {
|
|
186
|
+
value: id,
|
|
187
|
+
enumerable: false
|
|
188
|
+
},
|
|
189
|
+
type: {
|
|
190
|
+
value: 'store',
|
|
191
|
+
enumerable: false
|
|
192
|
+
},
|
|
193
|
+
patch: {
|
|
194
|
+
value: (arg) => {
|
|
195
|
+
const isObjectPatch = arg && typeof arg === 'object'
|
|
196
|
+
currentMutation = {
|
|
197
|
+
type: isObjectPatch ? MutationType.PATCH_OBJECT : MutationType.PATCH_FUNCTION,
|
|
198
|
+
storeId: id,
|
|
199
|
+
payload: isObjectPatch ? { ...arg } : undefined
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
batch(() => {
|
|
203
|
+
if (typeof arg === 'function') {
|
|
204
|
+
arg(store)
|
|
205
|
+
} else if (isObjectPatch) {
|
|
206
|
+
for (const key in arg) {
|
|
207
|
+
if (typeof store[key] === 'function' && store[key].isSignal && !store[key].readonly) {
|
|
208
|
+
const originalSignal = wrappedSignals[key]
|
|
209
|
+
originalSignal(arg[key])
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
notifySubscribers()
|
|
216
|
+
},
|
|
217
|
+
enumerable: false
|
|
218
|
+
},
|
|
219
|
+
reset: {
|
|
220
|
+
value: () => {
|
|
221
|
+
currentMutation = {
|
|
222
|
+
type: MutationType.PATCH_FUNCTION,
|
|
223
|
+
storeId: id,
|
|
224
|
+
payload: undefined
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
batch(() => {
|
|
228
|
+
for (const key in initialState) {
|
|
229
|
+
const originalSignal = wrappedSignals[key]
|
|
230
|
+
originalSignal(initialState[key])
|
|
231
|
+
}
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
notifySubscribers()
|
|
235
|
+
},
|
|
236
|
+
enumerable: false
|
|
237
|
+
},
|
|
238
|
+
subscribe: {
|
|
239
|
+
value: (callback, options = {}) => {
|
|
240
|
+
if (typeof callback !== 'function') {
|
|
241
|
+
console.warn('[framework] subscribe callback must be a function')
|
|
242
|
+
return () => {}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const subId = ++subscriptionId
|
|
246
|
+
const sub = {
|
|
247
|
+
callback,
|
|
248
|
+
flush: options.flush || 'post',
|
|
249
|
+
detached: options.detached || false
|
|
250
|
+
}
|
|
251
|
+
subscriptions.set(subId, sub)
|
|
252
|
+
|
|
253
|
+
if (!sub.detached) {
|
|
254
|
+
const instance = getCurrentInstance()
|
|
255
|
+
if (instance && instance._unmountHooks) {
|
|
256
|
+
instance._unmountHooks.push(() => {
|
|
257
|
+
subscriptions.delete(subId)
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return () => {
|
|
263
|
+
subscriptions.delete(subId)
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
enumerable: false
|
|
267
|
+
}
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
for (const plugin of storePlugins) {
|
|
271
|
+
try {
|
|
272
|
+
plugin(store, id)
|
|
273
|
+
} catch (e) {
|
|
274
|
+
console.warn(`[framework] Store plugin error for "${id}":`, e)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
storesRegistry.set(id, store)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return store
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
useStore.$id = id
|
|
285
|
+
|
|
286
|
+
return useStore
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** Drops one cached store so its setup function runs again on next use. */
|
|
290
|
+
export function resetStore(id) {
|
|
291
|
+
storesRegistry.delete(id)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Removes every registered store instance. */
|
|
295
|
+
export function clearStores() {
|
|
296
|
+
storesRegistry.clear()
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Returns all signal-backed properties from a store. */
|
|
300
|
+
export function storeToRefs(store) {
|
|
301
|
+
const refs = {}
|
|
302
|
+
for (const key in store) {
|
|
303
|
+
const value = store[key]
|
|
304
|
+
if (typeof value === 'function' && value.isSignal) {
|
|
305
|
+
refs[key] = value
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return refs
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Returns only writable signal-backed state from a store. */
|
|
312
|
+
export function storeToState(store) {
|
|
313
|
+
const state = {}
|
|
314
|
+
for (const key in store) {
|
|
315
|
+
const value = store[key]
|
|
316
|
+
if (typeof value === 'function' && value.isSignal && !value.readonly) {
|
|
317
|
+
state[key] = value
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return state
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Returns only read-only signal-backed getters from a store. */
|
|
324
|
+
export function storeToGetters(store) {
|
|
325
|
+
const getters = {}
|
|
326
|
+
for (const key in store) {
|
|
327
|
+
const value = store[key]
|
|
328
|
+
if (typeof value === 'function' && value.isSignal && value.readonly) {
|
|
329
|
+
getters[key] = value
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return getters
|
|
333
|
+
}
|