@mmstack/resource 19.6.6 → 19.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/README.md +6 -6
- package/fesm2022/mmstack-resource.mjs +438 -356
- package/fesm2022/mmstack-resource.mjs.map +1 -1
- package/lib/mutation-resource.d.ts +13 -5
- package/lib/util/cache/cache.d.ts +33 -2
- package/lib/util/cache/persistence.d.ts +1 -1
- package/lib/util/hash-request.d.ts +24 -1
- package/package.json +1 -1
|
@@ -97,14 +97,17 @@ export type MutationResourceOptions<TResult, TRaw = TResult, TMutation = TResult
|
|
|
97
97
|
* Cache entries to invalidate after a SUCCESSFUL mutation — the declarative
|
|
98
98
|
* alternative to calling `injectQueryCache().invalidatePrefix(...)` in `onSuccess`.
|
|
99
99
|
*
|
|
100
|
-
* Each string is a URL prefix matched against
|
|
101
|
-
*
|
|
102
|
-
* plus subpaths like `/api/posts/123` — and all `varyHeaders`
|
|
100
|
+
* Each string is a URL prefix matched against the request URL of every cached
|
|
101
|
+
* entry, regardless of HTTP method: `'/api/posts'` invalidates `/api/posts` with
|
|
102
|
+
* any query params, plus subpaths like `/api/posts/123` — and all `varyHeaders`
|
|
103
|
+
* variants of each — across GET/HEAD/OPTIONS/POST or whatever methods you cache.
|
|
103
104
|
* Note that plain prefix matching also catches sibling paths sharing the prefix
|
|
104
105
|
* (`/api/posts-archive`); pass `'/api/posts/'` or the exact URL to narrow.
|
|
105
106
|
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
107
|
+
* Keys built by a custom `cache.hash` that merely *prepends* a namespace (e.g. a
|
|
108
|
+
* tenant/`sub` for per-user persistent caches) are still matched — the URL is
|
|
109
|
+
* recovered structurally. Keys that abandon the auto shape entirely need an
|
|
110
|
+
* a custom invalidateMatcher (or manual `injectQueryCache().invalidateWhere`).
|
|
108
111
|
*
|
|
109
112
|
* The function form receives the mutation result and the mutated value:
|
|
110
113
|
* ```ts
|
|
@@ -112,6 +115,11 @@ export type MutationResourceOptions<TResult, TRaw = TResult, TMutation = TResult
|
|
|
112
115
|
* ```
|
|
113
116
|
*/
|
|
114
117
|
invalidates?: string[] | ((value: NoInfer<TResult>, mutation: NoInfer<TMutation>) => string[]);
|
|
118
|
+
/**
|
|
119
|
+
* override for how {@link MutationResourceOptions.invalidates} URL
|
|
120
|
+
* prefixes map onto cache keys — given a prefix, return a key predicate.
|
|
121
|
+
*/
|
|
122
|
+
invalidateMatcher?: (urlPrefix: string) => (key: string) => boolean;
|
|
115
123
|
equal?: ValueEqualityFn<TMutation>;
|
|
116
124
|
};
|
|
117
125
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HttpResponse } from '@angular/common/http';
|
|
2
|
-
import { Injector, type Provider, type Signal } from '@angular/core';
|
|
3
|
-
import { CacheDB } from './persistence';
|
|
2
|
+
import { type Injector, type Provider, type Signal } from '@angular/core';
|
|
3
|
+
import { type CacheDB } from './persistence';
|
|
4
4
|
/**
|
|
5
5
|
* Options for configuring the Least Recently Used (LRU) cache cleanup strategy.
|
|
6
6
|
* @internal
|
|
@@ -72,6 +72,8 @@ export declare class Cache<T> {
|
|
|
72
72
|
private hydrated;
|
|
73
73
|
/** Keys invalidated while hydration was still in flight — must not be resurrected by it. */
|
|
74
74
|
private readonly hydrationTombstones;
|
|
75
|
+
/** Dev-only: ensures the "foreign keys, no matcher" hint in invalidateUrlPrefix fires at most once. */
|
|
76
|
+
private warnedForeignKeys;
|
|
75
77
|
private readonly hitCount;
|
|
76
78
|
private readonly missCount;
|
|
77
79
|
/**
|
|
@@ -181,6 +183,35 @@ export declare class Cache<T> {
|
|
|
181
183
|
* cache.invalidatePrefix('GET https://api.example.com/posts');
|
|
182
184
|
*/
|
|
183
185
|
invalidatePrefix(prefix: string): number;
|
|
186
|
+
/**
|
|
187
|
+
* Invalidates every cache entry whose *request URL* starts with `urlPrefix`,
|
|
188
|
+
* regardless of HTTP method. This is the engine behind `mutationResource`'s
|
|
189
|
+
* `invalidates` option: `'/api/posts'` clears `/api/posts` with any query
|
|
190
|
+
* params, subpaths like `/api/posts/123`, and all `varyHeaders` variants —
|
|
191
|
+
* across GET/HEAD/OPTIONS/POST or any other cached method. Returns the number
|
|
192
|
+
* of entries removed.
|
|
193
|
+
*
|
|
194
|
+
* Unlike {@link invalidatePrefix} (which matches the raw key from its start),
|
|
195
|
+
* this extracts the URL field from the auto-generated key shape, so it is not
|
|
196
|
+
* fooled by the leading method token nor by a namespace a custom `cache.hash`
|
|
197
|
+
* prepends (e.g. `tenant:…`). Plain prefix matching still catches siblings
|
|
198
|
+
* sharing the prefix (`/api/posts-archive`) — pass `'/api/posts/'` to narrow.
|
|
199
|
+
*
|
|
200
|
+
* Keys produced by a custom `hash` that don't follow the auto shape won't be
|
|
201
|
+
* matched by the default; pass `match` to describe how a URL prefix maps onto
|
|
202
|
+
* your key format. In dev mode, if a default-matcher call removes nothing and
|
|
203
|
+
* every cached key is foreign-shaped, this logs a one-time hint pointing at the
|
|
204
|
+
* `match` escape hatch (a likely sign of a custom `hash` with no matcher wired up).
|
|
205
|
+
*
|
|
206
|
+
* @param urlPrefix - URL prefix to match.
|
|
207
|
+
* @param match - Optional custom matcher: given the prefix, returns a key predicate.
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* cache.invalidateUrlPrefix('/api/posts');
|
|
211
|
+
* // custom key scheme:
|
|
212
|
+
* cache.invalidateUrlPrefix('/api/posts', (p) => (k) => k.includes(`|url=${p}`));
|
|
213
|
+
*/
|
|
214
|
+
invalidateUrlPrefix(urlPrefix: string, match?: (urlPrefix: string) => (key: string) => boolean): number;
|
|
184
215
|
/**
|
|
185
216
|
* Invalidates every cache entry whose key matches the predicate. Use for
|
|
186
217
|
* arbitrary bulk invalidation that doesn't fit prefix matching (e.g.
|
|
@@ -7,11 +7,34 @@ type HashableRequest = {
|
|
|
7
7
|
body?: unknown;
|
|
8
8
|
headers?: HttpResourceRequest['headers'] | HttpRequest<unknown>['headers'];
|
|
9
9
|
};
|
|
10
|
+
/**
|
|
11
|
+
* Top-level field separator for auto-generated cache keys. ASCII Unit Separator
|
|
12
|
+
* (`\x1f`) is deliberately content-rare: it never occurs in HTTP method tokens,
|
|
13
|
+
* URLs, or `encodeURIComponent`/digest output (params, vary headers, body hash),
|
|
14
|
+
* so the structural layout stays unambiguous even when a custom `cache.hash`
|
|
15
|
+
* *prepends* a namespace with ordinary chars (e.g. `tenant:${hashRequest(req)}`).
|
|
16
|
+
* Survives `JSON.stringify` (IndexedDB persistence) and `structuredClone`
|
|
17
|
+
* (cross-tab broadcast) intact.
|
|
18
|
+
*/
|
|
19
|
+
export declare const KEY_DELIMITER = "\u001F";
|
|
20
|
+
/**
|
|
21
|
+
* Recovers the URL portion of an auto-generated cache key — the segment between
|
|
22
|
+
* the 1st and 2nd {@link KEY_DELIMITER} (the key shape is
|
|
23
|
+
* `method␟url␟responseType[␟…]`). Returns `null` when the key has no delimiter
|
|
24
|
+
* (e.g. produced by a custom `hash` that doesn't follow this shape).
|
|
25
|
+
*
|
|
26
|
+
* A namespace prepended with non-delimiter chars collapses into segment 0
|
|
27
|
+
* (`tenant:GET`), so the URL remains segment 1 — method-agnostic and
|
|
28
|
+
* namespacing-tolerant by construction.
|
|
29
|
+
*/
|
|
30
|
+
export declare function extractUrlFromKey(key: string): string | null;
|
|
10
31
|
/**
|
|
11
32
|
* Builds a stable cache/dedupe key from an HTTP request shape (accepts both
|
|
12
33
|
* `HttpRequest` and `HttpResourceRequest`).
|
|
13
34
|
*
|
|
14
|
-
* Key composition: `${method}
|
|
35
|
+
* Key composition: `${method}␟${url}␟${responseType}[␟${params}][␟${body}][␟${vary}]`,
|
|
36
|
+
* where `␟` is {@link KEY_DELIMITER} (ASCII Unit Separator) — a content-rare top-level
|
|
37
|
+
* separator. Sub-fields inside `params`/`vary` keep their own `&`/`=` delimiters.
|
|
15
38
|
* - `method` defaults to `'GET'`, `responseType` to `'json'` (Angular defaults).
|
|
16
39
|
* - Query params are sorted alphabetically and URL-encoded for stability.
|
|
17
40
|
* - Body hashing handles `File`/`Blob`/`FormData`/`URLSearchParams`/`ArrayBuffer`
|