fict 0.0.1 → 0.0.3
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/LICENSE +21 -0
- package/README.md +32 -0
- package/dist/index.cjs +14 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/jsx-dev-runtime.cjs +14 -0
- package/dist/jsx-dev-runtime.cjs.map +1 -0
- package/dist/jsx-dev-runtime.d.cts +1 -0
- package/dist/jsx-dev-runtime.d.ts +1 -0
- package/dist/jsx-dev-runtime.js +3 -0
- package/dist/jsx-dev-runtime.js.map +1 -0
- package/dist/jsx-runtime.cjs +14 -0
- package/dist/jsx-runtime.cjs.map +1 -0
- package/dist/jsx-runtime.d.cts +1 -0
- package/dist/jsx-runtime.d.ts +1 -0
- package/dist/jsx-runtime.js +3 -0
- package/dist/jsx-runtime.js.map +1 -0
- package/dist/plus.cjs +381 -0
- package/dist/plus.cjs.map +1 -0
- package/dist/plus.d.cts +50 -0
- package/dist/plus.d.ts +50 -0
- package/dist/plus.js +358 -0
- package/dist/plus.js.map +1 -0
- package/dist/slim.cjs +14 -0
- package/dist/slim.cjs.map +1 -0
- package/dist/slim.d.cts +1 -0
- package/dist/slim.d.ts +1 -0
- package/dist/slim.js +3 -0
- package/dist/slim.js.map +1 -0
- package/package.json +71 -8
- package/src/index.ts +1 -0
- package/src/jsx-dev-runtime.ts +1 -0
- package/src/jsx-runtime.ts +1 -0
- package/src/lazy.ts +48 -0
- package/src/plus.ts +10 -0
- package/src/resource.ts +293 -0
- package/src/slim.ts +2 -0
- package/src/store.ts +165 -0
package/src/resource.ts
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { createSignal, createEffect, onCleanup, createSuspenseToken } from '@fictjs/runtime'
|
|
2
|
+
|
|
3
|
+
export interface ResourceResult<T> {
|
|
4
|
+
data: T | undefined
|
|
5
|
+
loading: boolean
|
|
6
|
+
error: unknown
|
|
7
|
+
refresh: () => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ResourceCacheOptions {
|
|
11
|
+
mode?: 'memory' | 'none'
|
|
12
|
+
ttlMs?: number
|
|
13
|
+
staleWhileRevalidate?: boolean
|
|
14
|
+
cacheErrors?: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ResourceOptions<T, Args> {
|
|
18
|
+
key?: unknown
|
|
19
|
+
fetch: (ctx: { signal: AbortSignal }, args: Args) => Promise<T>
|
|
20
|
+
suspense?: boolean
|
|
21
|
+
cache?: ResourceCacheOptions
|
|
22
|
+
reset?: unknown | (() => unknown)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ResourceEntry<T, Args> {
|
|
26
|
+
data: ReturnType<typeof createSignal<T | undefined>>
|
|
27
|
+
loading: ReturnType<typeof createSignal<boolean>>
|
|
28
|
+
error: ReturnType<typeof createSignal<unknown>>
|
|
29
|
+
version: ReturnType<typeof createSignal<number>>
|
|
30
|
+
pendingToken: ReturnType<typeof createSuspenseToken> | null
|
|
31
|
+
lastArgs: Args | undefined
|
|
32
|
+
lastVersion: number
|
|
33
|
+
lastReset: unknown
|
|
34
|
+
hasValue: boolean
|
|
35
|
+
status: 'idle' | 'pending' | 'success' | 'error'
|
|
36
|
+
generation: number
|
|
37
|
+
expiresAt: number | undefined
|
|
38
|
+
inFlight: Promise<void> | undefined
|
|
39
|
+
controller: AbortController | undefined
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const defaultCacheOptions: Required<ResourceCacheOptions> = {
|
|
43
|
+
mode: 'memory',
|
|
44
|
+
ttlMs: Number.POSITIVE_INFINITY,
|
|
45
|
+
staleWhileRevalidate: false,
|
|
46
|
+
cacheErrors: false,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates a resource factory that can be read with arguments.
|
|
51
|
+
*
|
|
52
|
+
* @param optionsOrFetcher - Configuration object or fetcher function
|
|
53
|
+
*/
|
|
54
|
+
export function resource<T, Args = void>(
|
|
55
|
+
optionsOrFetcher:
|
|
56
|
+
| ((ctx: { signal: AbortSignal }, args: Args) => Promise<T>)
|
|
57
|
+
| ResourceOptions<T, Args>,
|
|
58
|
+
) {
|
|
59
|
+
const fetcher = typeof optionsOrFetcher === 'function' ? optionsOrFetcher : optionsOrFetcher.fetch
|
|
60
|
+
const useSuspense = typeof optionsOrFetcher === 'object' && !!optionsOrFetcher.suspense
|
|
61
|
+
const cacheOptions: ResourceCacheOptions =
|
|
62
|
+
typeof optionsOrFetcher === 'object' ? (optionsOrFetcher.cache ?? {}) : {}
|
|
63
|
+
const resolvedCacheOptions = { ...defaultCacheOptions, ...cacheOptions }
|
|
64
|
+
const cache = new Map<unknown, ResourceEntry<T, Args>>()
|
|
65
|
+
|
|
66
|
+
const readArgs = (argsAccessor: (() => Args) | Args): Args =>
|
|
67
|
+
typeof argsAccessor === 'function' ? (argsAccessor as () => Args)() : argsAccessor
|
|
68
|
+
|
|
69
|
+
const computeKey = (argsAccessor: (() => Args) | Args): unknown => {
|
|
70
|
+
const argsValue = readArgs(argsAccessor)
|
|
71
|
+
if (typeof optionsOrFetcher === 'object' && optionsOrFetcher.key !== undefined) {
|
|
72
|
+
const key = optionsOrFetcher.key
|
|
73
|
+
return typeof key === 'function' ? (key as (args: Args) => unknown)(argsValue) : key
|
|
74
|
+
}
|
|
75
|
+
return argsValue
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const readResetToken = (): unknown => {
|
|
79
|
+
if (typeof optionsOrFetcher !== 'object') return undefined
|
|
80
|
+
const reset = optionsOrFetcher.reset
|
|
81
|
+
if (typeof reset === 'function' && (reset as () => unknown).length === 0) {
|
|
82
|
+
return (reset as () => unknown)()
|
|
83
|
+
}
|
|
84
|
+
return reset
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const ensureEntry = (key: unknown): ResourceEntry<T, Args> => {
|
|
88
|
+
let state = cache.get(key)
|
|
89
|
+
if (!state) {
|
|
90
|
+
state = {
|
|
91
|
+
data: createSignal<T | undefined>(undefined),
|
|
92
|
+
loading: createSignal<boolean>(false),
|
|
93
|
+
error: createSignal<unknown>(undefined),
|
|
94
|
+
version: createSignal(0),
|
|
95
|
+
pendingToken: null,
|
|
96
|
+
lastArgs: undefined,
|
|
97
|
+
lastVersion: -1,
|
|
98
|
+
lastReset: undefined,
|
|
99
|
+
hasValue: false,
|
|
100
|
+
status: 'idle',
|
|
101
|
+
generation: 0,
|
|
102
|
+
expiresAt: undefined,
|
|
103
|
+
inFlight: undefined,
|
|
104
|
+
controller: undefined,
|
|
105
|
+
}
|
|
106
|
+
cache.set(key, state)
|
|
107
|
+
}
|
|
108
|
+
return state!
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const isExpired = (entry: ResourceEntry<T, Args>): boolean => {
|
|
112
|
+
if (resolvedCacheOptions.mode === 'none') return true
|
|
113
|
+
if (!Number.isFinite(resolvedCacheOptions.ttlMs)) return false
|
|
114
|
+
if (entry.expiresAt === undefined) return false
|
|
115
|
+
return entry.expiresAt < Date.now()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const markExpiry = (entry: ResourceEntry<T, Args>) => {
|
|
119
|
+
if (resolvedCacheOptions.mode === 'none') {
|
|
120
|
+
entry.expiresAt = Date.now() - 1
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
entry.expiresAt = Number.isFinite(resolvedCacheOptions.ttlMs)
|
|
124
|
+
? Date.now() + resolvedCacheOptions.ttlMs
|
|
125
|
+
: undefined
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const startFetch = (entry: ResourceEntry<T, Args>, key: unknown, args: Args) => {
|
|
129
|
+
entry.controller?.abort()
|
|
130
|
+
entry.inFlight = undefined
|
|
131
|
+
const controller = new AbortController()
|
|
132
|
+
entry.controller = controller
|
|
133
|
+
entry.status = 'pending'
|
|
134
|
+
entry.loading(true)
|
|
135
|
+
entry.error(undefined)
|
|
136
|
+
entry.generation += 1
|
|
137
|
+
const currentGen = entry.generation
|
|
138
|
+
|
|
139
|
+
const shouldSuspend = useSuspense && !entry.hasValue
|
|
140
|
+
entry.pendingToken = shouldSuspend ? createSuspenseToken() : null
|
|
141
|
+
|
|
142
|
+
const fetchPromise = fetcher({ signal: controller.signal }, args)
|
|
143
|
+
.then(res => {
|
|
144
|
+
if (controller.signal.aborted || entry.generation !== currentGen) return
|
|
145
|
+
entry.data(res)
|
|
146
|
+
entry.hasValue = true
|
|
147
|
+
entry.status = 'success'
|
|
148
|
+
entry.loading(false)
|
|
149
|
+
markExpiry(entry)
|
|
150
|
+
if (entry.pendingToken) {
|
|
151
|
+
entry.pendingToken.resolve()
|
|
152
|
+
entry.pendingToken = null
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
.catch(err => {
|
|
156
|
+
if (controller.signal.aborted || entry.generation !== currentGen) return
|
|
157
|
+
entry.error(err)
|
|
158
|
+
entry.status = 'error'
|
|
159
|
+
entry.loading(false)
|
|
160
|
+
if (resolvedCacheOptions.cacheErrors) {
|
|
161
|
+
markExpiry(entry)
|
|
162
|
+
} else {
|
|
163
|
+
entry.expiresAt = Date.now() - 1
|
|
164
|
+
entry.hasValue = false
|
|
165
|
+
}
|
|
166
|
+
if (entry.pendingToken) {
|
|
167
|
+
entry.pendingToken.reject(err)
|
|
168
|
+
entry.pendingToken = null
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
.finally(() => {
|
|
172
|
+
entry.inFlight = undefined
|
|
173
|
+
entry.controller = undefined
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
entry.inFlight = fetchPromise
|
|
177
|
+
|
|
178
|
+
onCleanup(() => {
|
|
179
|
+
if (resolvedCacheOptions.mode === 'none') {
|
|
180
|
+
controller.abort()
|
|
181
|
+
cache.delete(key)
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const invalidate = (key?: unknown) => {
|
|
187
|
+
if (key === undefined) {
|
|
188
|
+
cache.forEach(entry => {
|
|
189
|
+
entry.controller?.abort()
|
|
190
|
+
entry.version(entry.version() + 1)
|
|
191
|
+
entry.expiresAt = Date.now() - 1
|
|
192
|
+
})
|
|
193
|
+
cache.clear()
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
const entry = cache.get(key)
|
|
197
|
+
if (entry) {
|
|
198
|
+
entry.controller?.abort()
|
|
199
|
+
entry.version(entry.version() + 1)
|
|
200
|
+
entry.expiresAt = Date.now() - 1
|
|
201
|
+
cache.delete(key)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const prefetch = (args: Args, keyOverride?: unknown) => {
|
|
206
|
+
const key = keyOverride ?? computeKey(args)
|
|
207
|
+
const entry = ensureEntry(key)
|
|
208
|
+
const usableData = entry.hasValue && !isExpired(entry)
|
|
209
|
+
if (!usableData) {
|
|
210
|
+
entry.lastArgs = args
|
|
211
|
+
entry.lastVersion = entry.version()
|
|
212
|
+
startFetch(entry, key, args)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
read(argsAccessor: (() => Args) | Args): ResourceResult<T> {
|
|
218
|
+
const entryRef = createSignal<ResourceEntry<T, Args> | null>(null)
|
|
219
|
+
|
|
220
|
+
createEffect(() => {
|
|
221
|
+
const key = computeKey(argsAccessor)
|
|
222
|
+
const entry = ensureEntry(key)
|
|
223
|
+
entryRef(entry)
|
|
224
|
+
const args = readArgs(argsAccessor)
|
|
225
|
+
const currentVersion = entry.version()
|
|
226
|
+
const expired = isExpired(entry)
|
|
227
|
+
const argsChanged = entry.lastArgs !== args
|
|
228
|
+
const versionChanged = entry.lastVersion !== currentVersion
|
|
229
|
+
const resetToken = readResetToken()
|
|
230
|
+
const resetChanged = entry.lastReset !== resetToken
|
|
231
|
+
const shouldRefetch =
|
|
232
|
+
expired ||
|
|
233
|
+
argsChanged ||
|
|
234
|
+
versionChanged ||
|
|
235
|
+
resetChanged ||
|
|
236
|
+
(entry.status === 'error' && !resolvedCacheOptions.cacheErrors)
|
|
237
|
+
|
|
238
|
+
entry.lastArgs = args
|
|
239
|
+
entry.lastVersion = currentVersion
|
|
240
|
+
entry.lastReset = resetToken
|
|
241
|
+
|
|
242
|
+
if (shouldRefetch) {
|
|
243
|
+
if (entry.inFlight && (argsChanged || versionChanged)) {
|
|
244
|
+
entry.controller?.abort()
|
|
245
|
+
entry.inFlight = undefined
|
|
246
|
+
}
|
|
247
|
+
if (resetChanged) {
|
|
248
|
+
entry.hasValue = false
|
|
249
|
+
entry.expiresAt = Date.now() - 1
|
|
250
|
+
}
|
|
251
|
+
startFetch(entry, key, args as Args)
|
|
252
|
+
} else if (
|
|
253
|
+
entry.inFlight === undefined &&
|
|
254
|
+
resolvedCacheOptions.staleWhileRevalidate &&
|
|
255
|
+
expired &&
|
|
256
|
+
entry.hasValue
|
|
257
|
+
) {
|
|
258
|
+
// stale-while-revalidate: return stale data but refresh.
|
|
259
|
+
startFetch(entry, key, args as Args)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (resolvedCacheOptions.staleWhileRevalidate && entry.hasValue && expired) {
|
|
263
|
+
entry.loading(true)
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
get data() {
|
|
269
|
+
const entry = entryRef()
|
|
270
|
+
if (!entry) return undefined
|
|
271
|
+
if (useSuspense && entry.pendingToken) {
|
|
272
|
+
throw entry.pendingToken.token
|
|
273
|
+
}
|
|
274
|
+
return entry.data()
|
|
275
|
+
},
|
|
276
|
+
get loading() {
|
|
277
|
+
const entry = entryRef()
|
|
278
|
+
return entry ? entry.loading() : false
|
|
279
|
+
},
|
|
280
|
+
get error() {
|
|
281
|
+
const entry = entryRef()
|
|
282
|
+
return entry ? entry.error() : undefined
|
|
283
|
+
},
|
|
284
|
+
refresh: () => {
|
|
285
|
+
const entry = entryRef()
|
|
286
|
+
if (entry) entry.version(entry.version() + 1)
|
|
287
|
+
},
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
invalidate,
|
|
291
|
+
prefetch,
|
|
292
|
+
}
|
|
293
|
+
}
|
package/src/slim.ts
ADDED
package/src/store.ts
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { createSignal, type Signal } from '@fictjs/runtime'
|
|
2
|
+
|
|
3
|
+
type AnyFn = (...args: unknown[]) => unknown
|
|
4
|
+
interface BoundMethodEntry {
|
|
5
|
+
ref: AnyFn
|
|
6
|
+
bound: AnyFn
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const PROXY_CACHE = new WeakMap<object, unknown>()
|
|
10
|
+
const SIGNAL_CACHE = new WeakMap<object, Record<string | symbol, Signal<unknown>>>()
|
|
11
|
+
const BOUND_METHOD_CACHE = new WeakMap<object, Map<string | symbol, BoundMethodEntry>>()
|
|
12
|
+
const ITERATE_KEY = Symbol('iterate')
|
|
13
|
+
|
|
14
|
+
function getSignal(target: object, prop: string | symbol): Signal<unknown> {
|
|
15
|
+
let signals = SIGNAL_CACHE.get(target)
|
|
16
|
+
if (!signals) {
|
|
17
|
+
signals = {}
|
|
18
|
+
SIGNAL_CACHE.set(target, signals)
|
|
19
|
+
}
|
|
20
|
+
if (!signals[prop]) {
|
|
21
|
+
const initial = prop === ITERATE_KEY ? 0 : (target as Record<string | symbol, unknown>)[prop]
|
|
22
|
+
signals[prop] = createSignal(initial)
|
|
23
|
+
}
|
|
24
|
+
return signals[prop]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function triggerIteration(target: object) {
|
|
28
|
+
const signals = SIGNAL_CACHE.get(target)
|
|
29
|
+
if (signals && signals[ITERATE_KEY]) {
|
|
30
|
+
const current = signals[ITERATE_KEY]() as number
|
|
31
|
+
signals[ITERATE_KEY](current + 1)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function $store<T extends object>(initialValue: T): T {
|
|
36
|
+
if (typeof initialValue !== 'object' || initialValue === null) {
|
|
37
|
+
return initialValue
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (PROXY_CACHE.has(initialValue)) {
|
|
41
|
+
return PROXY_CACHE.get(initialValue) as T
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const proxy = new Proxy(initialValue, {
|
|
45
|
+
get(target, prop, receiver) {
|
|
46
|
+
// Always touch the signal so reference changes to this property are tracked,
|
|
47
|
+
// even if the value is an object we proxy further.
|
|
48
|
+
const signal = getSignal(target, prop)
|
|
49
|
+
const trackedValue = signal()
|
|
50
|
+
|
|
51
|
+
const currentValue = Reflect.get(target, prop, receiver ?? proxy)
|
|
52
|
+
if (currentValue !== trackedValue) {
|
|
53
|
+
// If the value has changed (e.g. via direct mutation of the underlying object not via proxy),
|
|
54
|
+
// we update the signal to keep it in sync.
|
|
55
|
+
// Note: This is a bit of a heuristic. Ideally all mutations go through proxy.
|
|
56
|
+
signal(currentValue)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (typeof currentValue === 'function') {
|
|
60
|
+
let boundMethods = BOUND_METHOD_CACHE.get(target)
|
|
61
|
+
if (!boundMethods) {
|
|
62
|
+
boundMethods = new Map()
|
|
63
|
+
BOUND_METHOD_CACHE.set(target, boundMethods)
|
|
64
|
+
}
|
|
65
|
+
const cached = boundMethods.get(prop)
|
|
66
|
+
if (cached && cached.ref === currentValue) {
|
|
67
|
+
return cached.bound
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const bound = (currentValue as AnyFn).bind(receiver ?? proxy)
|
|
71
|
+
boundMethods.set(prop, { ref: currentValue as AnyFn, bound })
|
|
72
|
+
return bound
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// If the value is an object/array, we recursively wrap it in a store
|
|
76
|
+
if (typeof currentValue === 'object' && currentValue !== null) {
|
|
77
|
+
return $store(currentValue as Record<string, unknown>)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// For primitives (and functions), we return the signal value (which tracks the read)
|
|
81
|
+
return currentValue
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
set(target, prop, newValue, receiver) {
|
|
85
|
+
const oldValue = Reflect.get(target, prop, receiver)
|
|
86
|
+
const hadKey = Object.prototype.hasOwnProperty.call(target, prop)
|
|
87
|
+
|
|
88
|
+
// If value hasn't changed, do nothing
|
|
89
|
+
if (oldValue === newValue && hadKey) {
|
|
90
|
+
return true
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const result = Reflect.set(target, prop, newValue, receiver)
|
|
94
|
+
|
|
95
|
+
// IMPORTANT: Clear bound method cache BEFORE updating the signal
|
|
96
|
+
const boundMethods = BOUND_METHOD_CACHE.get(target)
|
|
97
|
+
if (boundMethods && boundMethods.has(prop)) {
|
|
98
|
+
boundMethods.delete(prop)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Update the signal if it exists
|
|
102
|
+
const signals = SIGNAL_CACHE.get(target)
|
|
103
|
+
if (signals && signals[prop]) {
|
|
104
|
+
signals[prop](newValue)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// If new property, trigger iteration update
|
|
108
|
+
if (!hadKey) {
|
|
109
|
+
triggerIteration(target)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Ensure array length subscribers are notified even if the native push/pop
|
|
113
|
+
// doesn't trigger a separate set trap for "length" (defensive).
|
|
114
|
+
if (Array.isArray(target) && prop !== 'length') {
|
|
115
|
+
const signals = SIGNAL_CACHE.get(target)
|
|
116
|
+
if (signals && signals.length) {
|
|
117
|
+
signals.length((target as unknown as { length: number }).length)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 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
|
+
if (Array.isArray(target) && prop === 'length') {
|
|
124
|
+
triggerIteration(target)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return result
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
deleteProperty(target, prop) {
|
|
131
|
+
const hadKey = Object.prototype.hasOwnProperty.call(target, prop)
|
|
132
|
+
const result = Reflect.deleteProperty(target, prop)
|
|
133
|
+
|
|
134
|
+
if (result && hadKey) {
|
|
135
|
+
const signals = SIGNAL_CACHE.get(target)
|
|
136
|
+
if (signals && signals[prop]) {
|
|
137
|
+
signals[prop](undefined)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Clear bound method cache
|
|
141
|
+
const boundMethods = BOUND_METHOD_CACHE.get(target)
|
|
142
|
+
if (boundMethods && boundMethods.has(prop)) {
|
|
143
|
+
boundMethods.delete(prop)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
triggerIteration(target)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return result
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
ownKeys(target) {
|
|
153
|
+
getSignal(target, ITERATE_KEY)()
|
|
154
|
+
return Reflect.ownKeys(target)
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
has(target, prop) {
|
|
158
|
+
getSignal(target, prop)()
|
|
159
|
+
return Reflect.has(target, prop)
|
|
160
|
+
},
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
PROXY_CACHE.set(initialValue, proxy)
|
|
164
|
+
return proxy
|
|
165
|
+
}
|