fict 0.1.0 → 0.2.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.
- package/dist/{chunk-5AWNWCDT.js → chunk-T5BGEI6S.js} +15 -2
- package/dist/chunk-T5BGEI6S.js.map +1 -0
- package/dist/index.cjs +13 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/plus.cjs +60 -12
- package/dist/plus.cjs.map +1 -1
- package/dist/plus.d.cts +213 -13
- package/dist/plus.d.ts +213 -13
- package/dist/plus.js +48 -13
- package/dist/plus.js.map +1 -1
- package/dist/slim.cjs +14 -0
- package/dist/slim.cjs.map +1 -0
- package/dist/slim.d.cts +21 -0
- package/dist/slim.d.ts +21 -0
- package/dist/slim.js +11 -0
- package/dist/slim.js.map +1 -0
- package/dist/store-UpSYx4_N.d.cts +44 -0
- package/dist/store-UpSYx4_N.d.ts +44 -0
- package/package.json +8 -3
- package/src/lazy.ts +140 -15
- package/src/resource.ts +180 -8
- package/src/slim.ts +25 -0
- package/src/store.ts +79 -4
- package/dist/chunk-5AWNWCDT.js.map +0 -1
- package/dist/store-BmwKJIj6.d.cts +0 -3
- package/dist/store-BmwKJIj6.d.ts +0 -3
package/src/resource.ts
CHANGED
|
@@ -1,42 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Async data fetching with caching and Suspense support.
|
|
3
|
+
*
|
|
4
|
+
* The `resource` function creates a reactive data fetcher that:
|
|
5
|
+
* - Automatically cancels in-flight requests when args change
|
|
6
|
+
* - Supports Suspense for loading states
|
|
7
|
+
* - Provides caching with TTL and stale-while-revalidate
|
|
8
|
+
* - Handles errors gracefully
|
|
9
|
+
*/
|
|
10
|
+
|
|
1
11
|
import { createEffect, onCleanup, createSuspenseToken } from '@fictjs/runtime'
|
|
2
12
|
import { createSignal } from '@fictjs/runtime/advanced'
|
|
3
13
|
|
|
14
|
+
/**
|
|
15
|
+
* The result of reading a resource.
|
|
16
|
+
*
|
|
17
|
+
* @typeParam T - The type of data returned by the fetcher
|
|
18
|
+
*/
|
|
4
19
|
export interface ResourceResult<T> {
|
|
5
|
-
data
|
|
6
|
-
|
|
7
|
-
|
|
20
|
+
/** The fetched data, or undefined if not yet loaded or on error */
|
|
21
|
+
readonly data: T | undefined
|
|
22
|
+
/** Whether the resource is currently loading (initial fetch or refetch) */
|
|
23
|
+
readonly loading: boolean
|
|
24
|
+
/**
|
|
25
|
+
* Any error that occurred during fetching.
|
|
26
|
+
* Type is unknown since errors can be any value in JavaScript.
|
|
27
|
+
*/
|
|
28
|
+
readonly error: unknown
|
|
29
|
+
/** Manually trigger a refetch of the resource */
|
|
8
30
|
refresh: () => void
|
|
9
31
|
}
|
|
10
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Cache configuration options for a resource.
|
|
35
|
+
*/
|
|
11
36
|
export interface ResourceCacheOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Caching mode:
|
|
39
|
+
* - `'memory'`: Cache responses in memory (default)
|
|
40
|
+
* - `'none'`: No caching, always refetch
|
|
41
|
+
* @default 'memory'
|
|
42
|
+
*/
|
|
12
43
|
mode?: 'memory' | 'none'
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Time-to-live in milliseconds before cached data is considered stale.
|
|
47
|
+
* @default Infinity
|
|
48
|
+
*/
|
|
13
49
|
ttlMs?: number
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* If true, return stale cached data immediately while refetching in background.
|
|
53
|
+
* @default false
|
|
54
|
+
*/
|
|
14
55
|
staleWhileRevalidate?: boolean
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* If true, cache error responses as well.
|
|
59
|
+
* @default false
|
|
60
|
+
*/
|
|
15
61
|
cacheErrors?: boolean
|
|
16
62
|
}
|
|
17
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Configuration options for creating a resource.
|
|
66
|
+
*
|
|
67
|
+
* @typeParam T - The type of data returned by the fetcher
|
|
68
|
+
* @typeParam Args - The type of arguments passed to the fetcher
|
|
69
|
+
*/
|
|
18
70
|
export interface ResourceOptions<T, Args> {
|
|
19
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Custom cache key. Can be a static value or a function that computes
|
|
73
|
+
* the key from the args. If not provided, args are used as the key.
|
|
74
|
+
*/
|
|
75
|
+
key?: unknown | ((args: Args) => unknown)
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The fetcher function that performs the async data retrieval.
|
|
79
|
+
* Receives an AbortController signal for cancellation support.
|
|
80
|
+
*/
|
|
20
81
|
fetch: (ctx: { signal: AbortSignal }, args: Args) => Promise<T>
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* If true, the resource will throw a Suspense token while loading,
|
|
85
|
+
* enabling React-like Suspense boundaries.
|
|
86
|
+
* @default false
|
|
87
|
+
*/
|
|
21
88
|
suspense?: boolean
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Cache configuration options.
|
|
92
|
+
*/
|
|
22
93
|
cache?: ResourceCacheOptions
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* A value or reactive getter that, when changed, resets the resource.
|
|
97
|
+
* Useful for clearing cache when certain conditions change.
|
|
98
|
+
*/
|
|
23
99
|
reset?: unknown | (() => unknown)
|
|
24
100
|
}
|
|
25
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Return type of the resource factory.
|
|
104
|
+
*
|
|
105
|
+
* @typeParam T - The type of data returned by the fetcher
|
|
106
|
+
* @typeParam Args - The type of arguments passed to the fetcher
|
|
107
|
+
*/
|
|
108
|
+
export interface Resource<T, Args> {
|
|
109
|
+
/**
|
|
110
|
+
* Read the resource data, triggering a fetch if needed.
|
|
111
|
+
* Can accept static args or a reactive getter.
|
|
112
|
+
*
|
|
113
|
+
* @param argsAccessor - Arguments or a getter returning arguments
|
|
114
|
+
*/
|
|
115
|
+
read(argsAccessor: (() => Args) | Args): ResourceResult<T>
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Invalidate cached data, causing the next read to refetch.
|
|
119
|
+
*
|
|
120
|
+
* @param key - Optional specific key to invalidate. If omitted, invalidates all.
|
|
121
|
+
*/
|
|
122
|
+
invalidate(key?: unknown): void
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Prefetch data without reading it. Useful for eager loading.
|
|
126
|
+
*
|
|
127
|
+
* @param args - Arguments to pass to the fetcher
|
|
128
|
+
* @param keyOverride - Optional cache key override
|
|
129
|
+
*/
|
|
130
|
+
prefetch(args: Args, keyOverride?: unknown): void
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Resource status values for tracking fetch lifecycle.
|
|
135
|
+
* @internal
|
|
136
|
+
*/
|
|
137
|
+
export type ResourceStatus = 'idle' | 'pending' | 'success' | 'error'
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Internal cache entry for a resource.
|
|
141
|
+
* Tracks the reactive state and metadata for a single cached fetch.
|
|
142
|
+
*
|
|
143
|
+
* @typeParam T - The type of data returned by the fetcher
|
|
144
|
+
* @typeParam Args - The type of arguments passed to the fetcher
|
|
145
|
+
* @internal
|
|
146
|
+
*/
|
|
26
147
|
interface ResourceEntry<T, Args> {
|
|
148
|
+
/** Reactive signal for the fetched data */
|
|
27
149
|
data: ReturnType<typeof createSignal<T | undefined>>
|
|
150
|
+
/** Reactive signal for loading state */
|
|
28
151
|
loading: ReturnType<typeof createSignal<boolean>>
|
|
152
|
+
/** Reactive signal for error state */
|
|
29
153
|
error: ReturnType<typeof createSignal<unknown>>
|
|
154
|
+
/** Version counter for invalidation */
|
|
30
155
|
version: ReturnType<typeof createSignal<number>>
|
|
156
|
+
/** Suspense token when using suspense mode */
|
|
31
157
|
pendingToken: ReturnType<typeof createSuspenseToken> | null
|
|
158
|
+
/** Last used arguments for change detection */
|
|
32
159
|
lastArgs: Args | undefined
|
|
160
|
+
/** Last seen version for change detection */
|
|
33
161
|
lastVersion: number
|
|
162
|
+
/** Last reset token value for change detection */
|
|
34
163
|
lastReset: unknown
|
|
164
|
+
/** Whether we have a valid cached value */
|
|
35
165
|
hasValue: boolean
|
|
36
|
-
|
|
166
|
+
/** Current fetch status */
|
|
167
|
+
status: ResourceStatus
|
|
168
|
+
/** Generation counter to handle race conditions */
|
|
37
169
|
generation: number
|
|
170
|
+
/** Timestamp when the cached value expires */
|
|
38
171
|
expiresAt: number | undefined
|
|
172
|
+
/** Currently in-flight fetch promise */
|
|
39
173
|
inFlight: Promise<void> | undefined
|
|
174
|
+
/** AbortController for cancelling in-flight requests */
|
|
40
175
|
controller: AbortController | undefined
|
|
41
176
|
}
|
|
42
177
|
|
|
@@ -48,15 +183,52 @@ const defaultCacheOptions: Required<ResourceCacheOptions> = {
|
|
|
48
183
|
}
|
|
49
184
|
|
|
50
185
|
/**
|
|
51
|
-
*
|
|
186
|
+
* Create a reactive async data resource.
|
|
187
|
+
*
|
|
188
|
+
* Resources handle async data fetching with automatic caching, cancellation,
|
|
189
|
+
* and optional Suspense integration.
|
|
190
|
+
*
|
|
191
|
+
* @param optionsOrFetcher - A fetcher function or full configuration object
|
|
192
|
+
* @returns A resource factory with read, invalidate, and prefetch methods
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```tsx
|
|
196
|
+
* import { resource } from 'fict'
|
|
197
|
+
*
|
|
198
|
+
* // Simple fetcher
|
|
199
|
+
* const userResource = resource(
|
|
200
|
+
* ({ signal }, userId: string) =>
|
|
201
|
+
* fetch(`/api/users/${userId}`, { signal }).then(r => r.json())
|
|
202
|
+
* )
|
|
203
|
+
*
|
|
204
|
+
* // With full options
|
|
205
|
+
* const postsResource = resource({
|
|
206
|
+
* fetch: ({ signal }, userId: string) =>
|
|
207
|
+
* fetch(`/api/users/${userId}/posts`, { signal }).then(r => r.json()),
|
|
208
|
+
* suspense: true,
|
|
209
|
+
* cache: {
|
|
210
|
+
* ttlMs: 60_000,
|
|
211
|
+
* staleWhileRevalidate: true,
|
|
212
|
+
* },
|
|
213
|
+
* })
|
|
214
|
+
*
|
|
215
|
+
* // Usage in component
|
|
216
|
+
* function UserProfile({ userId }: { userId: string }) {
|
|
217
|
+
* const { data, loading, error, refresh } = userResource.read(() => userId)
|
|
218
|
+
*
|
|
219
|
+
* if (loading) return <Spinner />
|
|
220
|
+
* if (error) return <ErrorMessage error={error} />
|
|
221
|
+
* return <div>{data.name}</div>
|
|
222
|
+
* }
|
|
223
|
+
* ```
|
|
52
224
|
*
|
|
53
|
-
* @
|
|
225
|
+
* @public
|
|
54
226
|
*/
|
|
55
227
|
export function resource<T, Args = void>(
|
|
56
228
|
optionsOrFetcher:
|
|
57
229
|
| ((ctx: { signal: AbortSignal }, args: Args) => Promise<T>)
|
|
58
230
|
| ResourceOptions<T, Args>,
|
|
59
|
-
) {
|
|
231
|
+
): Resource<T, Args> {
|
|
60
232
|
const fetcher = typeof optionsOrFetcher === 'function' ? optionsOrFetcher : optionsOrFetcher.fetch
|
|
61
233
|
const useSuspense = typeof optionsOrFetcher === 'object' && !!optionsOrFetcher.suspense
|
|
62
234
|
const cacheOptions: ResourceCacheOptions =
|
package/src/slim.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Fict Slim entrypoint
|
|
3
|
+
*
|
|
4
|
+
* Exposes compiler macros only. Intended for users who want the smallest
|
|
5
|
+
* runtime surface and rely on the compiler to erase macro calls.
|
|
6
|
+
*
|
|
7
|
+
* @public
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Compiler macro for reactive state.
|
|
13
|
+
* This is transformed at compile time and should never be called at runtime.
|
|
14
|
+
*/
|
|
15
|
+
export function $state<T>(_initialValue: T): T {
|
|
16
|
+
throw new Error('$state() is a compiler macro and should be transformed at compile time')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Compiler macro for reactive effects.
|
|
21
|
+
* This is transformed at compile time and should never be called at runtime.
|
|
22
|
+
*/
|
|
23
|
+
export function $effect(_fn: () => void | (() => void)): void {
|
|
24
|
+
throw new Error('$effect() is a compiler macro and should be transformed at compile time')
|
|
25
|
+
}
|
package/src/store.ts
CHANGED
|
@@ -1,16 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Deep reactive store implementation for Fict.
|
|
3
|
+
*
|
|
4
|
+
* $store creates a deeply reactive proxy that tracks property access at the path level.
|
|
5
|
+
* Unlike $state (which is shallow), $store allows direct mutation of nested properties.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const user = $store({ name: 'Alice', address: { city: 'London' } })
|
|
10
|
+
* user.address.city = 'Paris' // Fine-grained reactive update
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
|
|
1
14
|
import { createSignal, type Signal } from '@fictjs/runtime/advanced'
|
|
2
15
|
|
|
16
|
+
/** Function type for bound methods */
|
|
3
17
|
type AnyFn = (...args: unknown[]) => unknown
|
|
18
|
+
|
|
19
|
+
/** Cache entry for bound methods to preserve identity */
|
|
4
20
|
interface BoundMethodEntry {
|
|
5
21
|
ref: AnyFn
|
|
6
22
|
bound: AnyFn
|
|
7
23
|
}
|
|
8
24
|
|
|
25
|
+
/** Type for objects with indexable properties */
|
|
26
|
+
type IndexableObject = Record<string | symbol, unknown>
|
|
27
|
+
|
|
28
|
+
/** Cache of proxied objects to avoid duplicate proxies */
|
|
9
29
|
const PROXY_CACHE = new WeakMap<object, unknown>()
|
|
30
|
+
|
|
31
|
+
/** Cache of signals per object property */
|
|
10
32
|
const SIGNAL_CACHE = new WeakMap<object, Record<string | symbol, Signal<unknown>>>()
|
|
33
|
+
|
|
34
|
+
/** Cache of bound methods to preserve function identity across reads */
|
|
11
35
|
const BOUND_METHOD_CACHE = new WeakMap<object, Map<string | symbol, BoundMethodEntry>>()
|
|
36
|
+
|
|
37
|
+
/** Special key for tracking iteration (Object.keys, for-in, etc.) */
|
|
12
38
|
const ITERATE_KEY = Symbol('iterate')
|
|
13
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Get or create a signal for a specific property on a target object.
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
14
44
|
function getSignal(target: object, prop: string | symbol): Signal<unknown> {
|
|
15
45
|
let signals = SIGNAL_CACHE.get(target)
|
|
16
46
|
if (!signals) {
|
|
@@ -18,13 +48,17 @@ function getSignal(target: object, prop: string | symbol): Signal<unknown> {
|
|
|
18
48
|
SIGNAL_CACHE.set(target, signals)
|
|
19
49
|
}
|
|
20
50
|
if (!signals[prop]) {
|
|
21
|
-
const initial = prop === ITERATE_KEY ? 0 : (target as
|
|
51
|
+
const initial = prop === ITERATE_KEY ? 0 : (target as IndexableObject)[prop]
|
|
22
52
|
signals[prop] = createSignal(initial)
|
|
23
53
|
}
|
|
24
54
|
return signals[prop]
|
|
25
55
|
}
|
|
26
56
|
|
|
27
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Trigger iteration signal to notify consumers that keys have changed.
|
|
59
|
+
* @internal
|
|
60
|
+
*/
|
|
61
|
+
function triggerIteration(target: object): void {
|
|
28
62
|
const signals = SIGNAL_CACHE.get(target)
|
|
29
63
|
if (signals && signals[ITERATE_KEY]) {
|
|
30
64
|
const current = signals[ITERATE_KEY]() as number
|
|
@@ -32,6 +66,35 @@ function triggerIteration(target: object) {
|
|
|
32
66
|
}
|
|
33
67
|
}
|
|
34
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Create a deep reactive store using Proxy.
|
|
71
|
+
*
|
|
72
|
+
* Unlike `$state` (which is shallow and compiler-transformed), `$store` provides:
|
|
73
|
+
* - **Deep reactivity**: Nested objects are automatically wrapped in proxies
|
|
74
|
+
* - **Direct mutation**: Modify properties directly without spread operators
|
|
75
|
+
* - **Path-level tracking**: Only components reading changed paths re-render
|
|
76
|
+
*
|
|
77
|
+
* @param initialValue - The initial object to make reactive
|
|
78
|
+
* @returns A reactive proxy of the object
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```tsx
|
|
82
|
+
* import { $store } from 'fict'
|
|
83
|
+
*
|
|
84
|
+
* const form = $store({
|
|
85
|
+
* user: { name: '', email: '' },
|
|
86
|
+
* settings: { theme: 'light' }
|
|
87
|
+
* })
|
|
88
|
+
*
|
|
89
|
+
* // Direct mutation works
|
|
90
|
+
* form.user.name = 'Alice'
|
|
91
|
+
*
|
|
92
|
+
* // In JSX - only updates when form.user.name changes
|
|
93
|
+
* <input value={form.user.name} />
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* @public
|
|
97
|
+
*/
|
|
35
98
|
export function $store<T extends object>(initialValue: T): T {
|
|
36
99
|
if (typeof initialValue !== 'object' || initialValue === null) {
|
|
37
100
|
return initialValue
|
|
@@ -82,6 +145,7 @@ export function $store<T extends object>(initialValue: T): T {
|
|
|
82
145
|
},
|
|
83
146
|
|
|
84
147
|
set(target, prop, newValue, receiver) {
|
|
148
|
+
const oldLength = Array.isArray(target) && prop === 'length' ? target.length : undefined
|
|
85
149
|
const oldValue = Reflect.get(target, prop, receiver)
|
|
86
150
|
const hadKey = Object.prototype.hasOwnProperty.call(target, prop)
|
|
87
151
|
|
|
@@ -114,13 +178,24 @@ export function $store<T extends object>(initialValue: T): T {
|
|
|
114
178
|
if (Array.isArray(target) && prop !== 'length') {
|
|
115
179
|
const signals = SIGNAL_CACHE.get(target)
|
|
116
180
|
if (signals && signals.length) {
|
|
117
|
-
signals.length(
|
|
181
|
+
signals.length(target.length)
|
|
118
182
|
}
|
|
119
183
|
}
|
|
120
184
|
|
|
121
185
|
// If it's an array and length changed implicitly, we might need to handle it.
|
|
122
|
-
// But usually 'length' is set explicitly or handled by the runtime.
|
|
123
186
|
if (Array.isArray(target) && prop === 'length') {
|
|
187
|
+
const nextLength = target.length
|
|
188
|
+
if (typeof oldLength === 'number' && nextLength < oldLength) {
|
|
189
|
+
const signals = SIGNAL_CACHE.get(target)
|
|
190
|
+
if (signals) {
|
|
191
|
+
for (let i = nextLength; i < oldLength; i += 1) {
|
|
192
|
+
const key = String(i)
|
|
193
|
+
if (signals[key]) {
|
|
194
|
+
signals[key](undefined)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
124
199
|
triggerIteration(target)
|
|
125
200
|
}
|
|
126
201
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/store.ts"],"names":["signals"],"mappings":"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","file":"chunk-5AWNWCDT.js","sourcesContent":["import { createSignal, type Signal } from '@fictjs/runtime/advanced'\n\ntype AnyFn = (...args: unknown[]) => unknown\ninterface BoundMethodEntry {\n ref: AnyFn\n bound: AnyFn\n}\n\nconst PROXY_CACHE = new WeakMap<object, unknown>()\nconst SIGNAL_CACHE = new WeakMap<object, Record<string | symbol, Signal<unknown>>>()\nconst BOUND_METHOD_CACHE = new WeakMap<object, Map<string | symbol, BoundMethodEntry>>()\nconst ITERATE_KEY = Symbol('iterate')\n\nfunction getSignal(target: object, prop: string | symbol): Signal<unknown> {\n let signals = SIGNAL_CACHE.get(target)\n if (!signals) {\n signals = {}\n SIGNAL_CACHE.set(target, signals)\n }\n if (!signals[prop]) {\n const initial = prop === ITERATE_KEY ? 0 : (target as Record<string | symbol, unknown>)[prop]\n signals[prop] = createSignal(initial)\n }\n return signals[prop]\n}\n\nfunction triggerIteration(target: object) {\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals[ITERATE_KEY]) {\n const current = signals[ITERATE_KEY]() as number\n signals[ITERATE_KEY](current + 1)\n }\n}\n\nexport function $store<T extends object>(initialValue: T): T {\n if (typeof initialValue !== 'object' || initialValue === null) {\n return initialValue\n }\n\n if (PROXY_CACHE.has(initialValue)) {\n return PROXY_CACHE.get(initialValue) as T\n }\n\n const proxy = new Proxy(initialValue, {\n get(target, prop, receiver) {\n // Always touch the signal so reference changes to this property are tracked,\n // even if the value is an object we proxy further.\n const signal = getSignal(target, prop)\n const trackedValue = signal()\n\n const currentValue = Reflect.get(target, prop, receiver ?? proxy)\n if (currentValue !== trackedValue) {\n // If the value has changed (e.g. via direct mutation of the underlying object not via proxy),\n // we update the signal to keep it in sync.\n // Note: This is a bit of a heuristic. Ideally all mutations go through proxy.\n signal(currentValue)\n }\n\n if (typeof currentValue === 'function') {\n let boundMethods = BOUND_METHOD_CACHE.get(target)\n if (!boundMethods) {\n boundMethods = new Map()\n BOUND_METHOD_CACHE.set(target, boundMethods)\n }\n const cached = boundMethods.get(prop)\n if (cached && cached.ref === currentValue) {\n return cached.bound\n }\n\n const bound = (currentValue as AnyFn).bind(receiver ?? proxy)\n boundMethods.set(prop, { ref: currentValue as AnyFn, bound })\n return bound\n }\n\n // If the value is an object/array, we recursively wrap it in a store\n if (typeof currentValue === 'object' && currentValue !== null) {\n return $store(currentValue as Record<string, unknown>)\n }\n\n // For primitives (and functions), we return the signal value (which tracks the read)\n return currentValue\n },\n\n set(target, prop, newValue, receiver) {\n const oldValue = Reflect.get(target, prop, receiver)\n const hadKey = Object.prototype.hasOwnProperty.call(target, prop)\n\n // If value hasn't changed, do nothing\n if (oldValue === newValue && hadKey) {\n return true\n }\n\n const result = Reflect.set(target, prop, newValue, receiver)\n\n // IMPORTANT: Clear bound method cache BEFORE updating the signal\n const boundMethods = BOUND_METHOD_CACHE.get(target)\n if (boundMethods && boundMethods.has(prop)) {\n boundMethods.delete(prop)\n }\n\n // Update the signal if it exists\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals[prop]) {\n signals[prop](newValue)\n }\n\n // If new property, trigger iteration update\n if (!hadKey) {\n triggerIteration(target)\n }\n\n // Ensure array length subscribers are notified even if the native push/pop\n // doesn't trigger a separate set trap for \"length\" (defensive).\n if (Array.isArray(target) && prop !== 'length') {\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals.length) {\n signals.length((target as unknown as { length: number }).length)\n }\n }\n\n // If it's an array and length changed implicitly, we might need to handle it.\n // But usually 'length' is set explicitly or handled by the runtime.\n if (Array.isArray(target) && prop === 'length') {\n triggerIteration(target)\n }\n\n return result\n },\n\n deleteProperty(target, prop) {\n const hadKey = Object.prototype.hasOwnProperty.call(target, prop)\n const result = Reflect.deleteProperty(target, prop)\n\n if (result && hadKey) {\n const signals = SIGNAL_CACHE.get(target)\n if (signals && signals[prop]) {\n signals[prop](undefined)\n }\n\n // Clear bound method cache\n const boundMethods = BOUND_METHOD_CACHE.get(target)\n if (boundMethods && boundMethods.has(prop)) {\n boundMethods.delete(prop)\n }\n\n triggerIteration(target)\n }\n\n return result\n },\n\n ownKeys(target) {\n getSignal(target, ITERATE_KEY)()\n return Reflect.ownKeys(target)\n },\n\n has(target, prop) {\n getSignal(target, prop)()\n return Reflect.has(target, prop)\n },\n })\n\n PROXY_CACHE.set(initialValue, proxy)\n return proxy\n}\n"]}
|
package/dist/store-BmwKJIj6.d.ts
DELETED