You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* chore: add changeset for resource cache key
* feat(core): resource cache phase 2 - cache.key
Adds ResourceKey type and cache.key option for resource cache keying.
- cache.key derives a resource entry key from the current load context
- One ResourceNode holds multiple internal entries, one per key
- Each entry independently tracks value, error, status, stale flag,
staleTime timer, and in-flight promise
- ResourceRef proxies the active key entry
- cache.staleTime timers are per key
- cache.deduplicate deduplicates per active key
- Resources without cache.key behave exactly as before
Reverts .changeset/pre.json to exclude the new changeset from the
prerelease consumed list (entries in pre.json changesets are treated
as already consumed; the source of truth is the .md file itself).
* docs: replace placeholder PR number with actual PR #123 in proposal doc
* fix: replace JSON.stringify with type-tagged key serializer, fix docs API mismatch
- Replace JSON.stringify normalizeKey with encodeResourceKey (type-tagged)
- Tagged approach preserves null vs undefined vs 'null' vs 'undefined',
NaN, Infinity, -Infinity, -0, and nested array distinctions
- Fix docs/Resources.md: key() -> cache.key() nested API
- Fix proposal doc examples to match nested API
- Add 8 new tests for key serialization edge cases
- Update PR body to remove .changeset/pre.json references
staleTime: 5_000, // ms (optional, default: Infinity)
264
+
deduplicate: true, // boolean (optional, default: true when cache is set)
264
265
},
265
266
})
266
267
```
267
268
269
+
### cache.key (Phase 2)
270
+
271
+
`cache.key` is an optional function that derives a cache key from the resource's load context. When set, the resource holds multiple internal entries — one per unique key — each with its own:
272
+
273
+
- value
274
+
- error
275
+
- status (idle/pending/ready/failed)
276
+
- stale flag
277
+
- staleTime timer
278
+
- in-flight promise (for deduplication)
279
+
280
+
The `ResourceRef` always reflects the **active key** entry — the key from the most recent `load()` or `reload()` call.
- Keys are normalized via a type-tagged serializer for stable map lookups — preserving distinctions between `null`, `undefined`, `"null"`, `"undefined"`, `0`, `-0`, `NaN`, `Infinity`, `-Infinity`, and nested arrays.
307
+
- Equivalent array content (e.g. `["a", "b"]`) maps to the same entry.
308
+
-`no-arg reload()` uses the last context, therefore reloads the last active key.
309
+
-`invalidate()` marks only the active entry stale.
310
+
-`cache.deduplicate` deduplicates per active key.
311
+
-`cache.staleTime` timers are per key.
312
+
- Resources without `cache.key` behave exactly as before (single-entry behavior).
313
+
314
+
Scope limitations (Phase 2):
315
+
316
+
-**Single-runtime only.** Keyed entries live in one `ResourceNode` within one `ScreenRuntime`.
317
+
-**No `cacheTime`.** Entries persist for the node's lifetime. No time-based eviction.
318
+
-**No SWR.** Stale data must be explicitly reloaded.
319
+
-**No cross-navigation cache.** Disposing the runtime clears all entries.
320
+
-**No dependency-tracked keys.** Key is derived from context at load time; automatically reacting to context changes is future work.
321
+
268
322
### staleTime
269
323
270
324
`cache.staleTime` is an optional number in milliseconds. After a successful `load()` or `reload()`, a timer starts. When it fires, the resource transitions to stale automatically:
@@ -279,7 +333,8 @@ Behavior:
279
333
-**Timer resets** on every successful `load()` or `reload()`.
280
334
-**`invalidate()`** always marks stale immediately, regardless of `staleTime`.
281
335
-**Failed loads** do not start a stale timer.
282
-
-**`dispose()`** clears the stale timer and prevents late notifications.
336
+
-**`dispose()`** clears all stale timers and prevents late notifications.
337
+
-**With `cache.key`**, each key has an independent stale timer.
283
338
284
339
### deduplicate
285
340
@@ -293,23 +348,25 @@ When `false`, each call runs independently (preserving existing behavior).
293
348
294
349
When `cache` is not set at all, deduplication is disabled to preserve backward compatibility. When `cache` is set, `deduplicate` defaults to `true`.
295
350
351
+
**With `cache.key`:** Deduplication is per-key. Concurrent `load()` calls with the same key share one promise. Different keys each invoke the loader independently.
352
+
296
353
### Future cache options
297
354
298
355
The following cache options remain as future work and are **not yet supported**:
299
356
300
-
-**`cache.key`** — cache key function for parameterized resources
301
357
-**`cacheTime`** — retention period for stale values before eviction
### Phase 2 — `cache.key` (recommended next slice)
437
+
### Phase 2 — `cache.key` (implemented)
436
438
437
439
**Scope:** `cache.key` only, scoped to one runtime and one `ResourceNode`.
438
440
@@ -722,7 +724,7 @@ When no `key` option is provided:
722
724
#### Migration
723
725
724
726
- **No migration required** for existing resources — the `key` option is opt-in.
725
-
- Users who want parameterized resources add `key: (ctx) =>ctx.route.params.id` to their resource config.
727
+
- Users who want parameterized resources add `cache: { key: (ctx) =>ctx.route.params.id }` to their resource config.
726
728
- Users who had workarounds (e.g., creating separate resources for each parameter) can consolidate into a single keyed resource.
727
729
- `deduplicate` defaults to `true` when `cache` is set (already the case from Phase 1).
728
730
@@ -736,10 +738,10 @@ When no `key` option is provided:
736
738
737
739
#### Open Questions (Deferred from Phase 2)
738
740
739
-
- **Key equality function** — should we use `JSON.stringify` (fast, no deps) or a deep equality utility (more correct for complex keys like arrays/objects)? Recommendation: use `JSON.stringify` for Phase 2. Array keys are supported by the type but should be used sparingly.
741
+
- **Key equality function** — should we use `JSON.stringify` (fast, no deps) or a deep equality utility (more correct for complex keys like arrays/objects)? Recommendation: use `JSON.stringify`with a type-tagged encoder for Phase 2 (see `encodeResourceKey`). This preserves distinctions between `null`, `undefined`, `NaN`, `Infinity`, `-0`, and nested arrays. Array keys are supported by the type but should be used sparingly.
740
742
- **Active key change without explicit load** — should the key be reactive (automatically reload when route params change)? This is dependency-tracked keys (Phase 6+). Phase 2 requires an explicit `load()`/`reload()` call to change the active key.
741
743
- **Entry eviction policy** — without `cacheTime`, entries accumulate in the map indefinitely. For Phase 2 this is acceptable (the node is disposed when the runtime is disposed). Future phases should add LRU or time-based eviction.
742
-
- **`cache.key` as a top-level config property vs nested under `cache`** — the existing convention nests under `cache`. Phase 2 follows this convention for consistency. This can be revisited if the team prefers flat.
744
+
- **`cache.key` as a top-level config property vs nested under `cache`** — the existing convention nests under `cache`. Phase 2 follows this convention for consistency. The nested API is the approved design.
743
745
- **Key type validation** — `ResourceKey` allows `string|number|boolean|null|undefined|ResourceKey[]`. Should we restrict further (e.g., disallow arrays in Phase 2)? Recommendation: keep the union type but document that string keys are preferred.
744
746
745
747
---
@@ -861,7 +863,12 @@ No changeset is needed for this proposal — it is design-only.
0 commit comments