@effect-app/vue 4.0.0-beta.307 → 4.0.0-beta.309
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/CHANGELOG.md +17 -0
- package/dist/atomQuery.d.ts +5 -1
- package/dist/atomQuery.d.ts.map +1 -1
- package/dist/atomQuery.js +24 -5
- package/dist/dependencyMetadata.d.ts +1 -0
- package/dist/dependencyMetadata.d.ts.map +1 -1
- package/dist/dependencyMetadata.js +2 -1
- package/dist/liveQueryInvalidation.d.ts +16 -0
- package/dist/liveQueryInvalidation.d.ts.map +1 -0
- package/dist/liveQueryInvalidation.js +115 -0
- package/dist/makeClient.js +1 -1
- package/dist/mutate.js +1 -1
- package/dist/query.d.ts +8 -2
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +13 -5
- package/package.json +2 -2
- package/src/atomQuery.ts +56 -27
- package/src/dependencyMetadata.ts +4 -0
- package/src/liveQueryInvalidation.ts +138 -0
- package/src/makeClient.ts +3 -3
- package/src/mutate.ts +2 -2
- package/src/query.ts +19 -5
- package/test/dist/liveQueryInvalidation.test.d.ts.map +1 -0
- package/test/liveQueryInvalidation.test.ts +108 -0
- package/tsconfig.json +6 -1
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { DataDependencies } from "effect-app/client"
|
|
2
|
+
import * as Hash from "effect/Hash"
|
|
3
|
+
|
|
4
|
+
export interface LiveQueryOptions {
|
|
5
|
+
/** Fixed maximum delay from the first matching write. Zero reacts immediately. */
|
|
6
|
+
readonly maxDelayMs?: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface LiveQueryInvalidationSource {
|
|
10
|
+
/** Resolve only after events received after this point can no longer be missed. */
|
|
11
|
+
readonly ready: () => Promise<void>
|
|
12
|
+
readonly subscribe: (
|
|
13
|
+
onWrites: (writes: DataDependencies.DataDependencies) => void,
|
|
14
|
+
onReset: () => void
|
|
15
|
+
) => () => void
|
|
16
|
+
readonly invalidate: (keys: ReadonlyArray<ReadonlyArray<unknown>>) => void
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface LiveQueryEntry {
|
|
20
|
+
readonly key: ReadonlyArray<unknown>
|
|
21
|
+
readonly reads: () => DataDependencies.DataDependencies
|
|
22
|
+
observers: number
|
|
23
|
+
readonly delays: Map<number, number>
|
|
24
|
+
timer: ReturnType<typeof setTimeout> | undefined
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const entries = new Map<number, LiveQueryEntry>()
|
|
28
|
+
let source: LiveQueryInvalidationSource | undefined
|
|
29
|
+
let unsubscribe: (() => void) | undefined
|
|
30
|
+
let discovering = 0
|
|
31
|
+
let bufferedWrites = DataDependencies.empty()
|
|
32
|
+
|
|
33
|
+
const invalidateEntry = (entry: LiveQueryEntry) => {
|
|
34
|
+
entry.timer = undefined
|
|
35
|
+
source?.invalidate([entry.key])
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const schedule = (entry: LiveQueryEntry) => {
|
|
39
|
+
if (entry.timer !== undefined) return
|
|
40
|
+
const maxDelayMs = Math.min(...entry.delays.keys())
|
|
41
|
+
if (maxDelayMs <= 0) return invalidateEntry(entry)
|
|
42
|
+
entry.timer = setTimeout(() => invalidateEntry(entry), maxDelayMs)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const dispatch = (writes: DataDependencies.DataDependencies) => {
|
|
46
|
+
if (!DataDependencies.isNonEmpty(writes)) return
|
|
47
|
+
for (const entry of entries.values()) {
|
|
48
|
+
if (DataDependencies.intersects(entry.reads(), writes)) schedule(entry)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const receive = (writes: DataDependencies.DataDependencies) => {
|
|
53
|
+
if (discovering > 0) {
|
|
54
|
+
bufferedWrites = DataDependencies.merge(bufferedWrites, writes)
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
dispatch(writes)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const reset = () => {
|
|
61
|
+
bufferedWrites = DataDependencies.empty()
|
|
62
|
+
for (const entry of entries.values()) {
|
|
63
|
+
if (DataDependencies.isNonEmpty(entry.reads())) schedule(entry)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const ensureSubscribed = () => {
|
|
68
|
+
if (unsubscribe === undefined && source !== undefined && entries.size > 0) {
|
|
69
|
+
unsubscribe = source.subscribe(receive, reset)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const configureLiveQueryInvalidation = (next: LiveQueryInvalidationSource) => {
|
|
74
|
+
unsubscribe?.()
|
|
75
|
+
unsubscribe = undefined
|
|
76
|
+
source = next
|
|
77
|
+
ensureSubscribed()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const registerLiveQuery = (
|
|
81
|
+
key: ReadonlyArray<unknown>,
|
|
82
|
+
reads: () => DataDependencies.DataDependencies,
|
|
83
|
+
options: LiveQueryOptions
|
|
84
|
+
) => {
|
|
85
|
+
const hash = Hash.hash(key)
|
|
86
|
+
const delay = options.maxDelayMs ?? 0
|
|
87
|
+
const current = entries.get(hash)
|
|
88
|
+
if (current !== undefined) {
|
|
89
|
+
current.observers++
|
|
90
|
+
current.delays.set(delay, (current.delays.get(delay) ?? 0) + 1)
|
|
91
|
+
} else {
|
|
92
|
+
entries.set(hash, {
|
|
93
|
+
key,
|
|
94
|
+
reads,
|
|
95
|
+
observers: 1,
|
|
96
|
+
delays: new Map([[delay, 1]]),
|
|
97
|
+
timer: undefined
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
ensureSubscribed()
|
|
101
|
+
|
|
102
|
+
return () => {
|
|
103
|
+
const entry = entries.get(hash)
|
|
104
|
+
if (entry === undefined) return
|
|
105
|
+
const delayObservers = entry.delays.get(delay) ?? 0
|
|
106
|
+
if (delayObservers <= 1) entry.delays.delete(delay)
|
|
107
|
+
else entry.delays.set(delay, delayObservers - 1)
|
|
108
|
+
if (--entry.observers > 0) return
|
|
109
|
+
if (entry.timer !== undefined) clearTimeout(entry.timer)
|
|
110
|
+
entries.delete(hash)
|
|
111
|
+
if (entries.size === 0) {
|
|
112
|
+
unsubscribe?.()
|
|
113
|
+
unsubscribe = undefined
|
|
114
|
+
bufferedWrites = DataDependencies.empty()
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const beginLiveQueryFetch = async (key: ReadonlyArray<unknown>) => {
|
|
120
|
+
if (!entries.has(Hash.hash(key)) || source === undefined) return false
|
|
121
|
+
discovering++
|
|
122
|
+
try {
|
|
123
|
+
await source.ready()
|
|
124
|
+
return true
|
|
125
|
+
} catch (error) {
|
|
126
|
+
discovering--
|
|
127
|
+
throw error
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export const endLiveQueryFetch = (wasLive: boolean) => {
|
|
132
|
+
if (!wasLive) return
|
|
133
|
+
discovering = Math.max(0, discovering - 1)
|
|
134
|
+
if (discovering > 0 || !DataDependencies.isNonEmpty(bufferedWrites)) return
|
|
135
|
+
const writes = bufferedWrites
|
|
136
|
+
bufferedWrites = DataDependencies.empty()
|
|
137
|
+
dispatch(writes)
|
|
138
|
+
}
|
package/src/makeClient.ts
CHANGED
|
@@ -1031,7 +1031,7 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
1031
1031
|
fromRequestConfig(
|
|
1032
1032
|
defaultKey,
|
|
1033
1033
|
queryResources as never,
|
|
1034
|
-
input
|
|
1034
|
+
input,
|
|
1035
1035
|
output as never
|
|
1036
1036
|
) as InvalidationEntry[])
|
|
1037
1037
|
: undefined
|
|
@@ -1111,7 +1111,7 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
1111
1111
|
fromRequestConfig(
|
|
1112
1112
|
defaultKey,
|
|
1113
1113
|
queryResources as never,
|
|
1114
|
-
input
|
|
1114
|
+
input,
|
|
1115
1115
|
output as never
|
|
1116
1116
|
) as InvalidationEntry[])
|
|
1117
1117
|
: undefined
|
|
@@ -1142,7 +1142,7 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
1142
1142
|
fromRequestConfig(
|
|
1143
1143
|
defaultKey,
|
|
1144
1144
|
queryResources as never,
|
|
1145
|
-
input
|
|
1145
|
+
input,
|
|
1146
1146
|
output as never
|
|
1147
1147
|
) as InvalidationEntry[])
|
|
1148
1148
|
: undefined
|
package/src/mutate.ts
CHANGED
|
@@ -212,7 +212,7 @@ export const asResult = <Args extends readonly any[], A, E, R>(
|
|
|
212
212
|
))
|
|
213
213
|
)
|
|
214
214
|
|
|
215
|
-
return tuple(computed(() => state.value), act)
|
|
215
|
+
return tuple(computed(() => state.value), act)
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
/**
|
|
@@ -259,7 +259,7 @@ export const asStreamResult = <Args extends readonly any[], A, E, R>(
|
|
|
259
259
|
|
|
260
260
|
const act = (...args: Args) => runStream(handler(...args))
|
|
261
261
|
|
|
262
|
-
return tuple(computed(() => state.value), act)
|
|
262
|
+
return tuple(computed(() => state.value), act)
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
const buildInvalidateCache = <RInvalidator>(
|
package/src/query.ts
CHANGED
|
@@ -16,8 +16,9 @@ import * as Exit from "effect/Exit"
|
|
|
16
16
|
import * as Stream from "effect/Stream"
|
|
17
17
|
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
|
|
18
18
|
import * as Atom from "effect/unstable/reactivity/Atom"
|
|
19
|
-
import { computed, type ComputedRef, effectScope, type MaybeRefOrGetter, onBeforeUnmount, onMounted, onScopeDispose,
|
|
20
|
-
import { type AtomClientRuntime, type AtomQueryOptions, awaitAtomResult, buildQueryFamily, buildStreamQueryFamily, disabledQueryAtom, isStaleResult, refreshAtomWithCurrentSpan, staleTimeMsOf, withQueryOptions } from "./atomQuery.ts"
|
|
19
|
+
import { computed, type ComputedRef, effectScope, type MaybeRefOrGetter, onBeforeUnmount, onMounted, onScopeDispose, ref, toValue, type WatchSource } from "vue"
|
|
20
|
+
import { type AtomClientRuntime, type AtomQueryOptions, awaitAtomResult, buildQueryFamily, buildStreamQueryFamily, disabledQueryAtom, isStaleResult, queryKeyForAtom, refreshAtomWithCurrentSpan, staleTimeMsOf, withQueryOptions } from "./atomQuery.ts"
|
|
21
|
+
import type { LiveQueryOptions } from "./liveQueryInvalidation.ts"
|
|
21
22
|
import { latestDefined } from "./suspense.ts"
|
|
22
23
|
|
|
23
24
|
// --- minimal local types (replacing the former @tanstack/vue-query type imports) ---
|
|
@@ -201,6 +202,7 @@ export interface CustomUseQueryOptions<
|
|
|
201
202
|
readonly structuralSharing?: boolean
|
|
202
203
|
/** poll: re-fetch every N ms (tanstack refetchInterval) */
|
|
203
204
|
readonly refetchInterval?: number
|
|
205
|
+
readonly live?: boolean | LiveQueryOptions
|
|
204
206
|
readonly select?: (data: TQueryFnData) => TData
|
|
205
207
|
/** accepted for source compatibility; not used by the atom engine */
|
|
206
208
|
readonly retry?: boolean | number
|
|
@@ -253,6 +255,7 @@ export interface AtomQueryNewOptions<TQueryFnData = unknown, TData = TQueryFnDat
|
|
|
253
255
|
readonly structuralSharing?: boolean
|
|
254
256
|
readonly refreshEvery?: number
|
|
255
257
|
readonly refetchInterval?: number
|
|
258
|
+
readonly live?: boolean | LiveQueryOptions
|
|
256
259
|
readonly select?: (data: TQueryFnData) => TData
|
|
257
260
|
}
|
|
258
261
|
|
|
@@ -263,6 +266,7 @@ export interface AtomStreamQueryOptions {
|
|
|
263
266
|
readonly revalidateOnFocus?: boolean
|
|
264
267
|
readonly refetchOnWindowFocus?: boolean
|
|
265
268
|
readonly refreshEvery?: number
|
|
269
|
+
readonly live?: boolean | LiveQueryOptions
|
|
266
270
|
readonly refetchInterval?: number
|
|
267
271
|
}
|
|
268
272
|
|
|
@@ -276,7 +280,8 @@ export interface AtomStreamQueryOptions {
|
|
|
276
280
|
* option is set. Neither value is written to the atom cache.
|
|
277
281
|
*/
|
|
278
282
|
export const withDataFallback = <A, E>(
|
|
279
|
-
|
|
283
|
+
// Structural read so both Ref and Readonly<Ref> from useAtomValue are accepted.
|
|
284
|
+
rawResult: { readonly value: AsyncResult.AsyncResult<A, E> },
|
|
280
285
|
options?: unknown
|
|
281
286
|
): ComputedRef<AsyncResult.AsyncResult<A, E>> => {
|
|
282
287
|
const opts = options as
|
|
@@ -285,6 +290,8 @@ export const withDataFallback = <A, E>(
|
|
|
285
290
|
const initialData = opts?.initialData
|
|
286
291
|
const placeholderData = opts?.placeholderData
|
|
287
292
|
if (initialData === undefined && placeholderData === undefined) {
|
|
293
|
+
// Identity when no fallback options. Callers that need a real ComputedRef
|
|
294
|
+
// (e.g. useAtomValue's Readonly<Ref>) wrap before calling this helper.
|
|
288
295
|
return rawResult as ComputedRef<AsyncResult.AsyncResult<A, E>>
|
|
289
296
|
}
|
|
290
297
|
|
|
@@ -319,6 +326,7 @@ const normalizeQueryOptions = (options?: {
|
|
|
319
326
|
readonly structuralSharing?: boolean
|
|
320
327
|
readonly refetchInterval?: number
|
|
321
328
|
readonly refreshEvery?: number
|
|
329
|
+
readonly live?: boolean | LiveQueryOptions
|
|
322
330
|
}): AtomQueryOptions => {
|
|
323
331
|
const out: {
|
|
324
332
|
staleTime?: number
|
|
@@ -326,6 +334,7 @@ const normalizeQueryOptions = (options?: {
|
|
|
326
334
|
revalidateOnFocus?: boolean
|
|
327
335
|
structuralSharing?: boolean
|
|
328
336
|
refetchInterval?: number
|
|
337
|
+
live?: boolean | LiveQueryOptions
|
|
329
338
|
} = {}
|
|
330
339
|
if (options?.staleTime !== undefined) out.staleTime = options.staleTime
|
|
331
340
|
const gcTime = options?.idleTTL ?? options?.gcTime
|
|
@@ -335,6 +344,7 @@ const normalizeQueryOptions = (options?: {
|
|
|
335
344
|
if (options?.structuralSharing !== undefined) out.structuralSharing = options.structuralSharing
|
|
336
345
|
const refetchInterval = options?.refreshEvery ?? options?.refetchInterval
|
|
337
346
|
if (refetchInterval !== undefined) out.refetchInterval = refetchInterval
|
|
347
|
+
if (options?.live !== undefined) out.live = options.live
|
|
338
348
|
return out
|
|
339
349
|
}
|
|
340
350
|
|
|
@@ -510,8 +520,10 @@ const observedAtom = <A, E>(
|
|
|
510
520
|
readonly structuralSharing?: boolean
|
|
511
521
|
readonly refetchInterval?: number
|
|
512
522
|
readonly refreshEvery?: number
|
|
523
|
+
readonly live?: boolean | LiveQueryOptions
|
|
513
524
|
}
|
|
514
|
-
): Atom.Atom<AsyncResult.AsyncResult<A, E>> =>
|
|
525
|
+
): Atom.Atom<AsyncResult.AsyncResult<A, E>> =>
|
|
526
|
+
withQueryOptions(atom, normalizeQueryOptions(options), queryKeyForAtom(atom))
|
|
515
527
|
|
|
516
528
|
const observedStreamAtom = <A, E>(
|
|
517
529
|
atom: Atom.Writable<Atom.PullResult<A, E>, void>,
|
|
@@ -549,7 +561,9 @@ const makeQueryView = <I, A, E, TData>(
|
|
|
549
561
|
const [req, enabledRef] = optionValue<I>(arg, options)
|
|
550
562
|
const family = getQueryFamily(atomRt, q)
|
|
551
563
|
const atomRef = computed(() => enabledRef.value ? observedAtom(family(req.value), options) : disabledQueryAtom)
|
|
552
|
-
|
|
564
|
+
// useAtomValue returns Readonly<Ref>, not ComputedRef — re-wrap before withDataFallback / QueryView.
|
|
565
|
+
const rawAtomResult = useAtomValue(() => atomRef.value)
|
|
566
|
+
const rawResult = computed(() => rawAtomResult.value)
|
|
553
567
|
// `initialData` / `placeholderData` display fallback (old `.query()` API). Applied only while the
|
|
554
568
|
// result is still Initial; Failures/Success pass through unmasked. Neither is written to cache
|
|
555
569
|
// (the base atom stays Initial, so the mount-staleness check still triggers the real fetch). The
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"liveQueryInvalidation.test.d.ts","sourceRoot":"","sources":["../liveQueryInvalidation.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { DataDependencies } from "effect-app/client"
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from "vitest"
|
|
3
|
+
import { beginLiveQueryFetch, configureLiveQueryInvalidation, endLiveQueryFetch, registerLiveQuery } from "../src/liveQueryInvalidation.js"
|
|
4
|
+
|
|
5
|
+
const repo = DataDependencies.repo("Inventory", ["item-1"])
|
|
6
|
+
const otherId = DataDependencies.repo("Inventory", ["item-2"])
|
|
7
|
+
|
|
8
|
+
describe("live query invalidation", () => {
|
|
9
|
+
const cleanups: Array<() => void> = []
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
while (cleanups.length > 0) cleanups.pop()?.()
|
|
13
|
+
vi.useRealTimers()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it("invalidates only live queries whose recorded reads intersect", () => {
|
|
17
|
+
const invalidations: Array<ReadonlyArray<ReadonlyArray<unknown>>> = []
|
|
18
|
+
let receive = (_writes: DataDependencies.DataDependencies) => {}
|
|
19
|
+
configureLiveQueryInvalidation({
|
|
20
|
+
ready: () => Promise.resolve(),
|
|
21
|
+
subscribe: (onWrites) => {
|
|
22
|
+
receive = onWrites
|
|
23
|
+
return () => {}
|
|
24
|
+
},
|
|
25
|
+
invalidate: (keys) => invalidations.push(keys)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const key = ["$Inventory", "List", undefined]
|
|
29
|
+
cleanups.push(registerLiveQuery(key, () => new Set([repo]), {}))
|
|
30
|
+
receive(new Set([otherId]))
|
|
31
|
+
expect(invalidations).toEqual([])
|
|
32
|
+
|
|
33
|
+
receive(new Set([repo]))
|
|
34
|
+
expect(invalidations).toEqual([[key]])
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it("replays writes received while the initial query discovers its dependencies", async () => {
|
|
38
|
+
const invalidations: Array<ReadonlyArray<ReadonlyArray<unknown>>> = []
|
|
39
|
+
let receive = (_writes: DataDependencies.DataDependencies) => {}
|
|
40
|
+
let open = () => {}
|
|
41
|
+
const ready = new Promise<void>((resolve) => open = resolve)
|
|
42
|
+
configureLiveQueryInvalidation({
|
|
43
|
+
ready: () => ready,
|
|
44
|
+
subscribe: (onWrites) => {
|
|
45
|
+
receive = onWrites
|
|
46
|
+
return () => {}
|
|
47
|
+
},
|
|
48
|
+
invalidate: (keys) => invalidations.push(keys)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const key = ["$Inventory", "Get", { id: "item-1" }]
|
|
52
|
+
let reads = DataDependencies.empty()
|
|
53
|
+
cleanups.push(registerLiveQuery(key, () => reads, {}))
|
|
54
|
+
|
|
55
|
+
const starting = beginLiveQueryFetch(key)
|
|
56
|
+
receive(new Set([repo]))
|
|
57
|
+
open()
|
|
58
|
+
const wasLive = await starting
|
|
59
|
+
reads = new Set([repo])
|
|
60
|
+
endLiveQueryFetch(wasLive)
|
|
61
|
+
|
|
62
|
+
expect(invalidations).toEqual([[key]])
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it("coalesces matching writes using the query's maximum delay", () => {
|
|
66
|
+
vi.useFakeTimers()
|
|
67
|
+
const invalidations: Array<ReadonlyArray<ReadonlyArray<unknown>>> = []
|
|
68
|
+
let receive = (_writes: DataDependencies.DataDependencies) => {}
|
|
69
|
+
configureLiveQueryInvalidation({
|
|
70
|
+
ready: () => Promise.resolve(),
|
|
71
|
+
subscribe: (onWrites) => {
|
|
72
|
+
receive = onWrites
|
|
73
|
+
return () => {}
|
|
74
|
+
},
|
|
75
|
+
invalidate: (keys) => invalidations.push(keys)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const key = ["$Inventory", "List", undefined]
|
|
79
|
+
cleanups.push(registerLiveQuery(key, () => new Set([repo]), { maxDelayMs: 2_000 }))
|
|
80
|
+
receive(new Set([repo]))
|
|
81
|
+
receive(new Set([repo]))
|
|
82
|
+
vi.advanceTimersByTime(1_999)
|
|
83
|
+
expect(invalidations).toEqual([])
|
|
84
|
+
vi.advanceTimersByTime(1)
|
|
85
|
+
expect(invalidations).toEqual([[key]])
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it("fails safe by invalidating every live query when the source resets", () => {
|
|
89
|
+
const invalidations: Array<ReadonlyArray<ReadonlyArray<unknown>>> = []
|
|
90
|
+
let reset = () => {}
|
|
91
|
+
configureLiveQueryInvalidation({
|
|
92
|
+
ready: () => Promise.resolve(),
|
|
93
|
+
subscribe: (_onWrites, onReset) => {
|
|
94
|
+
reset = onReset
|
|
95
|
+
return () => {}
|
|
96
|
+
},
|
|
97
|
+
invalidate: (keys) => invalidations.push(keys)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
const first = ["$Inventory", "List", undefined]
|
|
101
|
+
const second = ["$Orders", "List", undefined]
|
|
102
|
+
cleanups.push(registerLiveQuery(first, () => new Set([repo]), {}))
|
|
103
|
+
cleanups.push(registerLiveQuery(second, () => new Set([otherId]), {}))
|
|
104
|
+
reset()
|
|
105
|
+
|
|
106
|
+
expect(invalidations).toEqual([[first], [second]])
|
|
107
|
+
})
|
|
108
|
+
})
|
package/tsconfig.json
CHANGED