@mmstack/resource 22.1.6 → 22.2.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmstack/resource",
3
- "version": "22.1.6",
3
+ "version": "22.2.0",
4
4
  "keywords": [
5
5
  "angular",
6
6
  "signals",
@@ -80,6 +80,8 @@ declare class Cache<T> {
80
80
  private hydrated;
81
81
  /** Keys invalidated while hydration was still in flight — must not be resurrected by it. */
82
82
  private readonly hydrationTombstones;
83
+ /** Dev-only: ensures the "foreign keys, no matcher" hint in invalidateUrlPrefix fires at most once. */
84
+ private warnedForeignKeys;
83
85
  private readonly hitCount;
84
86
  private readonly missCount;
85
87
  /**
@@ -189,6 +191,35 @@ declare class Cache<T> {
189
191
  * cache.invalidatePrefix('GET https://api.example.com/posts');
190
192
  */
191
193
  invalidatePrefix(prefix: string): number;
194
+ /**
195
+ * Invalidates every cache entry whose *request URL* starts with `urlPrefix`,
196
+ * regardless of HTTP method. This is the engine behind `mutationResource`'s
197
+ * `invalidates` option: `'/api/posts'` clears `/api/posts` with any query
198
+ * params, subpaths like `/api/posts/123`, and all `varyHeaders` variants —
199
+ * across GET/HEAD/OPTIONS/POST or any other cached method. Returns the number
200
+ * of entries removed.
201
+ *
202
+ * Unlike {@link invalidatePrefix} (which matches the raw key from its start),
203
+ * this extracts the URL field from the auto-generated key shape, so it is not
204
+ * fooled by the leading method token nor by a namespace a custom `cache.hash`
205
+ * prepends (e.g. `tenant:…`). Plain prefix matching still catches siblings
206
+ * sharing the prefix (`/api/posts-archive`) — pass `'/api/posts/'` to narrow.
207
+ *
208
+ * Keys produced by a custom `hash` that don't follow the auto shape won't be
209
+ * matched by the default; pass `match` to describe how a URL prefix maps onto
210
+ * your key format. In dev mode, if a default-matcher call removes nothing and
211
+ * every cached key is foreign-shaped, this logs a one-time hint pointing at the
212
+ * `match` escape hatch (a likely sign of a custom `hash` with no matcher wired up).
213
+ *
214
+ * @param urlPrefix - URL prefix to match.
215
+ * @param match - Optional custom matcher: given the prefix, returns a key predicate.
216
+ *
217
+ * @example
218
+ * cache.invalidateUrlPrefix('/api/posts');
219
+ * // custom key scheme:
220
+ * cache.invalidateUrlPrefix('/api/posts', (p) => (k) => k.includes(`|url=${p}`));
221
+ */
222
+ invalidateUrlPrefix(urlPrefix: string, match?: (urlPrefix: string) => (key: string) => boolean): number;
192
223
  /**
193
224
  * Invalidates every cache entry whose key matches the predicate. Use for
194
225
  * arbitrary bulk invalidation that doesn't fit prefix matching (e.g.
@@ -530,7 +561,9 @@ type HashableRequest = {
530
561
  * Builds a stable cache/dedupe key from an HTTP request shape (accepts both
531
562
  * `HttpRequest` and `HttpResourceRequest`).
532
563
  *
533
- * Key composition: `${method}:${url}:${responseType}[:${params}][:${body}][:${vary}]`
564
+ * Key composition: `${method}␟${url}␟${responseType}[␟${params}][␟${body}][␟${vary}]`,
565
+ * where `␟` is {@link KEY_DELIMITER} (ASCII Unit Separator) — a content-rare top-level
566
+ * separator. Sub-fields inside `params`/`vary` keep their own `&`/`=` delimiters.
534
567
  * - `method` defaults to `'GET'`, `responseType` to `'json'` (Angular defaults).
535
568
  * - Query params are sorted alphabetically and URL-encoded for stability.
536
569
  * - Body hashing handles `File`/`Blob`/`FormData`/`URLSearchParams`/`ArrayBuffer`
@@ -1144,14 +1177,17 @@ type MutationResourceOptions<TResult, TRaw = TResult, TMutation = TResult, TCTX
1144
1177
  * Cache entries to invalidate after a SUCCESSFUL mutation — the declarative
1145
1178
  * alternative to calling `injectQueryCache().invalidatePrefix(...)` in `onSuccess`.
1146
1179
  *
1147
- * Each string is a URL prefix matched against auto-generated `GET` cache keys
1148
- * (`GET:${url}:...`): `'/api/posts'` invalidates `/api/posts` with any query params,
1149
- * plus subpaths like `/api/posts/123` — and all `varyHeaders` variants of each.
1180
+ * Each string is a URL prefix matched against the request URL of every cached
1181
+ * entry, regardless of HTTP method: `'/api/posts'` invalidates `/api/posts` with
1182
+ * any query params, plus subpaths like `/api/posts/123` — and all `varyHeaders`
1183
+ * variants of each — across GET/HEAD/OPTIONS/POST or whatever methods you cache.
1150
1184
  * Note that plain prefix matching also catches sibling paths sharing the prefix
1151
1185
  * (`/api/posts-archive`); pass `'/api/posts/'` or the exact URL to narrow.
1152
1186
  *
1153
- * Entries keyed by a custom `hash` function follow that function's shape, not the
1154
- * auto-key shape invalidate those manually via `injectQueryCache().invalidateWhere`.
1187
+ * Keys built by a custom `cache.hash` that merely *prepends* a namespace (e.g. a
1188
+ * tenant/`sub` for per-user persistent caches) are still matched the URL is
1189
+ * recovered structurally. Keys that abandon the auto shape entirely need an
1190
+ * a custom invalidateMatcher (or manual `injectQueryCache().invalidateWhere`).
1155
1191
  *
1156
1192
  * The function form receives the mutation result and the mutated value:
1157
1193
  * ```ts
@@ -1159,6 +1195,11 @@ type MutationResourceOptions<TResult, TRaw = TResult, TMutation = TResult, TCTX
1159
1195
  * ```
1160
1196
  */
1161
1197
  invalidates?: string[] | ((value: NoInfer<TResult>, mutation: NoInfer<TMutation>) => string[]);
1198
+ /**
1199
+ * override for how {@link MutationResourceOptions.invalidates} URL
1200
+ * prefixes map onto cache keys — given a prefix, return a key predicate.
1201
+ */
1202
+ invalidateMatcher?: (urlPrefix: string) => (key: string) => boolean;
1162
1203
  equal?: ValueEqualityFn<TMutation>;
1163
1204
  };
1164
1205
  /**