@briancray/belte 0.6.0 → 0.7.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/package.json
CHANGED
package/src/lib/browser/cache.ts
CHANGED
|
@@ -63,10 +63,12 @@ export function cache<Args, Return>(
|
|
|
63
63
|
/*
|
|
64
64
|
Tag an existing entry with this call's scope so a later
|
|
65
65
|
cache.invalidate({ scope }) reaches entries hydrated from the SSR
|
|
66
|
-
snapshot (which carry a value but no scope) without a refetch.
|
|
66
|
+
snapshot (which carry a value but no scope) without a refetch. Merge
|
|
67
|
+
rather than replace so a read tagging one group can't drop tags a
|
|
68
|
+
different read site already added.
|
|
67
69
|
*/
|
|
68
70
|
if (existing && options?.scope !== undefined) {
|
|
69
|
-
existing.scope = options.scope
|
|
71
|
+
existing.scope = mergeScopes(existing.scope, options.scope)
|
|
70
72
|
}
|
|
71
73
|
/*
|
|
72
74
|
Snapshot warm path: hydration pre-decoded the SSR body onto the
|
|
@@ -117,7 +119,7 @@ function invokeWithCache<Args>(
|
|
|
117
119
|
request,
|
|
118
120
|
ttl,
|
|
119
121
|
expiresAt: undefined as number | undefined,
|
|
120
|
-
scope: options?.scope,
|
|
122
|
+
scope: options?.scope === undefined ? undefined : toScopeSet(options.scope),
|
|
121
123
|
}
|
|
122
124
|
store.entries.set(key, entry)
|
|
123
125
|
function deleteIfCurrent() {
|
|
@@ -163,10 +165,11 @@ function shareable(promise: Promise<Response>): Promise<Response> {
|
|
|
163
165
|
Three call shapes:
|
|
164
166
|
invalidate() → drop everything
|
|
165
167
|
invalidate(fn) → drop one function's calls (method+url prefix)
|
|
166
|
-
invalidate({ key?, scope? }) → drop one entry by key and/or
|
|
168
|
+
invalidate({ key?, scope? }) → drop one entry by key and/or tagged groups
|
|
167
169
|
A selector with both fields drops the union; an empty or unmatched selector
|
|
168
170
|
is a no-op. `key` accepts the same string/array/object the cache() `key`
|
|
169
|
-
option does and is canonicalised the same way.
|
|
171
|
+
option does and is canonicalised the same way. `scope` accepts one tag or an
|
|
172
|
+
array; an entry is dropped when its tag set shares any tag with the request.
|
|
170
173
|
*/
|
|
171
174
|
function invalidate<Args, Return>(
|
|
172
175
|
arg?: AnyRemote<Args, Return> | Pick<CacheOptions, 'key' | 'scope'>,
|
|
@@ -195,11 +198,15 @@ function invalidate<Args, Return>(
|
|
|
195
198
|
}
|
|
196
199
|
const target = arg.key !== undefined ? canonicalKey(arg.key) : undefined
|
|
197
200
|
const byKey = target !== undefined && store.entries.has(target) ? [target] : []
|
|
201
|
+
const requestedScopes = arg.scope === undefined ? undefined : toScopeSet(arg.scope)
|
|
198
202
|
const byScope =
|
|
199
|
-
|
|
203
|
+
requestedScopes === undefined
|
|
200
204
|
? []
|
|
201
205
|
: Array.from(store.entries.values())
|
|
202
|
-
.filter(
|
|
206
|
+
.filter(
|
|
207
|
+
(entry) =>
|
|
208
|
+
entry.scope !== undefined && intersects(entry.scope, requestedScopes),
|
|
209
|
+
)
|
|
203
210
|
.map((entry) => entry.key)
|
|
204
211
|
/* emit() dedupes via a Set, so a key matching both criteria is harmless. */
|
|
205
212
|
const affected = [...byKey, ...byScope]
|
|
@@ -227,6 +234,21 @@ function canonicalKey(value: CacheOptions['key']): string {
|
|
|
227
234
|
return canonicalJson(value)
|
|
228
235
|
}
|
|
229
236
|
|
|
237
|
+
/* Normalizes a scope option (one tag or many) to a Set for O(1) membership. */
|
|
238
|
+
function toScopeSet(scope: string | string[]): Set<string> {
|
|
239
|
+
return new Set(typeof scope === 'string' ? [scope] : scope)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* Folds new tags into an entry's existing set without duplicating them. */
|
|
243
|
+
function mergeScopes(existing: Set<string> | undefined, incoming: string | string[]): Set<string> {
|
|
244
|
+
return new Set([...(existing ?? []), ...toScopeSet(incoming)])
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/* True when an entry's tags and the requested tags overlap on any tag. */
|
|
248
|
+
function intersects(entryScopes: Set<string>, requestedScopes: Set<string>): boolean {
|
|
249
|
+
return Array.from(requestedScopes).some((scope) => entryScopes.has(scope))
|
|
250
|
+
}
|
|
251
|
+
|
|
230
252
|
/*
|
|
231
253
|
Detail is a Set so each subscriber's `has(key)` check is O(1) regardless of
|
|
232
254
|
how many keys a single invalidate touches.
|
|
@@ -12,8 +12,9 @@ snapshot body is pre-decoded synchronously so the first client render can
|
|
|
12
12
|
read it without a microtask hop and byte-match the SSR DOM. Live fetches
|
|
13
13
|
leave it undefined and take the async decode path.
|
|
14
14
|
|
|
15
|
-
`scope`
|
|
16
|
-
`cache.invalidate({ scope })` can drop every entry sharing
|
|
15
|
+
`scope` holds the cache() call's scope tags as a Set so
|
|
16
|
+
`cache.invalidate({ scope })` can drop every entry sharing any tag with O(1)
|
|
17
|
+
membership; a re-read merges new tags in rather than replacing them.
|
|
17
18
|
*/
|
|
18
19
|
export type CacheEntry = {
|
|
19
20
|
key: string
|
|
@@ -22,5 +23,5 @@ export type CacheEntry = {
|
|
|
22
23
|
ttl: number | undefined
|
|
23
24
|
expiresAt: number | undefined
|
|
24
25
|
value?: unknown
|
|
25
|
-
scope?: string
|
|
26
|
+
scope?: Set<string>
|
|
26
27
|
}
|
|
@@ -3,11 +3,12 @@ Options for cache(). `key` overrides the auto-derived WeakMap key — useful
|
|
|
3
3
|
when sharing entries across calls or stripping noisy args. `ttl` is the
|
|
4
4
|
milliseconds-past-resolve that the entry stays live: omitted = forever, 0 =
|
|
5
5
|
dedupe only (entry dropped once the promise settles), any other number = TTL.
|
|
6
|
-
`scope` is
|
|
7
|
-
`cache.invalidate({ scope })` drops them
|
|
6
|
+
`scope` is one or more free-form tags grouping unrelated calls so one
|
|
7
|
+
`cache.invalidate({ scope })` drops every entry sharing any of them — pass an
|
|
8
|
+
array when a call belongs to multiple invalidation groups.
|
|
8
9
|
*/
|
|
9
10
|
export type CacheOptions = {
|
|
10
11
|
key?: string | unknown[] | Record<string, unknown>
|
|
11
12
|
ttl?: number
|
|
12
|
-
scope?: string
|
|
13
|
+
scope?: string | string[]
|
|
13
14
|
}
|