From 5d2105d83a509fc901bda8128dc86c6c3bbc4329 Mon Sep 17 00:00:00 2001 From: Mahyar Date: Sat, 27 Jun 2026 11:05:21 +0330 Subject: [PATCH] =?UTF-8?q?docs:=20update=20for=20alpha.9=20=E2=80=94=20ca?= =?UTF-8?q?che.key=20docs=20and=20resource-lifecycle=20example?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 ++-- docs/Quickstart.md | 6 +- docs/agent/RELEASE-STATE.md | 8 +-- .../src/ResourceDemo.test.ts | 59 ++++++++++++++++++- .../resource-lifecycle/src/ResourceDemo.ts | 29 +++++++++ 5 files changed, 98 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 9ea3cfd..a671364 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The current repository proves the core shape: - Typed router - Runtime-scoped resources - Resource reload and invalidation -- Resource cache phase 1 (staleTime, deduplicate) +- Resource cache phase 2 (staleTime, deduplicate, cache.key, ResourceKey) - Independent executable actions - Keyboard Enter default action - Accessible Enter hints @@ -40,17 +40,17 @@ It is not production-ready yet. The goal right now is to keep the foundation sma Intent is available as experimental alpha packages on npm: ```sh -pnpm add @intent-framework/core@0.1.0-alpha.8 @intent-framework/dom@0.1.0-alpha.8 @intent-framework/router@0.1.0-alpha.8 @intent-framework/testing@0.1.0-alpha.8 +pnpm add @intent-framework/core@0.1.0-alpha.9 @intent-framework/dom@0.1.0-alpha.9 @intent-framework/router@0.1.0-alpha.9 @intent-framework/testing@0.1.0-alpha.9 ``` ```sh -npm install @intent-framework/core@0.1.0-alpha.8 @intent-framework/dom@0.1.0-alpha.8 @intent-framework/router@0.1.0-alpha.8 @intent-framework/testing@0.1.0-alpha.8 +npm install @intent-framework/core@0.1.0-alpha.9 @intent-framework/dom@0.1.0-alpha.9 @intent-framework/router@0.1.0-alpha.9 @intent-framework/testing@0.1.0-alpha.9 ``` The quickstart pins the current alpha version so examples match the published APIs. ```txt -Current alpha: v0.1.0-alpha.8 +Current alpha: v0.1.0-alpha.9 First public alpha: v0.1.0-alpha.0 GitHub Releases: https://github.com/intent-framework/intent/releases ``` @@ -347,7 +347,7 @@ Current limitations include: * No native renderer yet * No SSR story yet * No backend persistence yet -* Resource cache is phase 1 (staleTime, deduplicate); cache.key, cacheTime, swr, and cross-navigation cache are future +* Resource cache is phase 2 (staleTime, deduplicate, cache.key); cacheTime, swr, and cross-navigation cache are future * No DevTools package yet * Automated version PR workflow and manual publish workflow are set up (Changesets configured) * Demo side panels use manual DOM diff --git a/docs/Quickstart.md b/docs/Quickstart.md index d8f13cf..7be9b94 100644 --- a/docs/Quickstart.md +++ b/docs/Quickstart.md @@ -7,16 +7,16 @@ This guide shows the fastest path from zero to a semantic screen using published ## 1. Install ```sh -pnpm add @intent-framework/core@0.1.0-alpha.8 @intent-framework/dom@0.1.0-alpha.8 @intent-framework/testing@0.1.0-alpha.8 +pnpm add @intent-framework/core@0.1.0-alpha.9 @intent-framework/dom@0.1.0-alpha.9 @intent-framework/testing@0.1.0-alpha.9 ``` Or with npm: ```sh -npm install @intent-framework/core@0.1.0-alpha.8 @intent-framework/dom@0.1.0-alpha.8 @intent-framework/testing@0.1.0-alpha.8 +npm install @intent-framework/core@0.1.0-alpha.9 @intent-framework/dom@0.1.0-alpha.9 @intent-framework/testing@0.1.0-alpha.9 ``` -The quickstart pins `0.1.0-alpha.8` so the examples match the APIs shown below. +The quickstart pins `0.1.0-alpha.9` so the examples match the APIs shown below. You also need `typescript` and `vitest` for type checking and tests. diff --git a/docs/agent/RELEASE-STATE.md b/docs/agent/RELEASE-STATE.md index a060c77..9122e1f 100644 --- a/docs/agent/RELEASE-STATE.md +++ b/docs/agent/RELEASE-STATE.md @@ -4,10 +4,10 @@ | Package | Version | Published | Notes | |---------|---------|-----------|-------| -| `@intent-framework/core` | `0.1.0-alpha.1` | Yes | alpha prerelease | -| `@intent-framework/dom` | `0.1.0-alpha.2` | Yes | alpha prerelease | -| `@intent-framework/router` | `0.1.0-alpha.1` | Yes | alpha prerelease | -| `@intent-framework/testing` | `0.1.0-alpha.1` | Yes | alpha prerelease | +| `@intent-framework/core` | `0.1.0-alpha.9` | Yes | alpha prerelease | +| `@intent-framework/dom` | `0.1.0-alpha.9` | Yes | alpha prerelease | +| `@intent-framework/router` | `0.1.0-alpha.9` | Yes | alpha prerelease | +| `@intent-framework/testing` | `0.1.0-alpha.9` | Yes | alpha prerelease | | `@intent-framework/server` | `0.1.0` | No | private workspace package | `@intent-framework/server` must remain `"private": true`. Never publish it. diff --git a/examples/resource-lifecycle/src/ResourceDemo.test.ts b/examples/resource-lifecycle/src/ResourceDemo.test.ts index 39487c9..4f6da16 100644 --- a/examples/resource-lifecycle/src/ResourceDemo.test.ts +++ b/examples/resource-lifecycle/src/ResourceDemo.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect } from "vitest" import { testScreen } from "@intent-framework/testing" import { createScreenRuntime, inspectScreen } from "@intent-framework/core" -import { ResourceDemo, teamLoadCount, cachedTeamLoadCount, dedupeReportLoadCount } from "./ResourceDemo.js" +import { ResourceDemo, teamLoadCount, cachedTeamLoadCount, dedupeReportLoadCount, keyedTeamLoadCount } from "./ResourceDemo.js" const testServices = { route: { name: "demo", path: "/:id", params: { id: "team_1" } }, @@ -116,12 +116,67 @@ describe("ResourceDemo", () => { }, { services: testServices as any }) }) + it("cache.key derives resource value from route context", async () => { + const runtime = createScreenRuntime(ResourceDemo, { services: testServices as any }) + await runtime.start() + const keyed = runtime.resources.find(r => r.name === "keyedTeam")! + expect(keyed.status).toBe("ready") + expect((keyed.value as any)?.id).toBe("team_1") + expect((keyed.value as any)?.name).toBe("Team-team_1") + runtime.dispose() + }) + + it("cache.key: different keys are independent", async () => { + const runtime = createScreenRuntime(ResourceDemo, { services: testServices as any }) + await runtime.start() + const keyed = runtime.resources.find(r => r.name === "keyedTeam")! + const before = keyedTeamLoadCount + // Load with a different key + await keyed.load({ route: { name: "demo", path: "/:id", params: { id: "team_b" } } }) + expect(keyed.status).toBe("ready") + expect((keyed.value as any)?.id).toBe("team_b") + expect((keyed.value as any)?.name).toBe("Team-team_b") + expect(keyedTeamLoadCount).toBe(before + 1) + runtime.dispose() + }) + + it("cache.key: switching active key updates visible value/status", async () => { + const runtime = createScreenRuntime(ResourceDemo, { services: testServices as any }) + await runtime.start() + const keyed = runtime.resources.find(r => r.name === "keyedTeam")! + // Initially keyed by route param "team_1" + expect((keyed.value as any)?.id).toBe("team_1") + // Switch to team_b + await keyed.load({ route: { name: "demo", path: "/:id", params: { id: "team_b" } } }) + expect(keyed.status).toBe("ready") + expect((keyed.value as any)?.id).toBe("team_b") + // Switch back to team_1 + await keyed.load({ route: { name: "demo", path: "/:id", params: { id: "team_1" } } }) + expect(keyed.status).toBe("ready") + expect((keyed.value as any)?.id).toBe("team_1") + runtime.dispose() + }) + + it("cache.key: no-arg reload reuses last active key", async () => { + const runtime = createScreenRuntime(ResourceDemo, { services: testServices as any }) + await runtime.start() + const keyed = runtime.resources.find(r => r.name === "keyedTeam")! + // Load team_b — changes active key + await keyed.load({ route: { name: "demo", path: "/:id", params: { id: "team_b" } } }) + const afterFirst = keyedTeamLoadCount + // no-arg reload uses lastContext → reloads team_b + await keyed.reload() + expect(keyedTeamLoadCount).toBe(afterFirst + 1) + expect((keyed.value as any)?.id).toBe("team_b") + runtime.dispose() + }) + it("inspectScreen reports resources with status/stale/error", async () => { const runtime = createScreenRuntime(ResourceDemo, { services: testServices as any }) await runtime.start() const graph = runtime.graph const resources = graph.resources - expect(resources).toHaveLength(5) + expect(resources).toHaveLength(6) const teamRes = resources.find(r => r.name === "team")! expect(teamRes.status).toBe("ready") expect(teamRes.hasValue).toBe(true) diff --git a/examples/resource-lifecycle/src/ResourceDemo.ts b/examples/resource-lifecycle/src/ResourceDemo.ts index 79f609c..bdb724f 100644 --- a/examples/resource-lifecycle/src/ResourceDemo.ts +++ b/examples/resource-lifecycle/src/ResourceDemo.ts @@ -5,6 +5,7 @@ export let teamLoadCount = 0 export let auditLogLoadCount = 0 export let cachedTeamLoadCount = 0 export let dedupeReportLoadCount = 0 +export let keyedTeamLoadCount = 0 export type Team = { id: string @@ -63,6 +64,19 @@ export const ResourceDemo = screen("Resource Demo", $ => { cache: { deduplicate: true }, }) + const keyedTeam = $.resource("keyedTeam", { + load: async ({ route }) => { + keyedTeamLoadCount++ + return { + id: route.params.id, + name: `Team-${route.params.id}`, + members: 3, + version: keyedTeamLoadCount, + } satisfies Team + }, + cache: { key: ({ route }) => route.params.id }, + }) + const reloadTeam = $.act("Reload team") .does(async () => { await team.reload() @@ -98,6 +112,19 @@ export const ResourceDemo = screen("Resource Demo", $ => { await dedupeReport.load() }) + const loadKeyedTeamB = $.act("Load keyed team (team_b)") + .does(async ({ navigate }) => { + await keyedTeam.load({ + route: { name: "demo", path: "/:id", params: { id: "team_b" } }, + navigate, + }) + }) + + const reloadKeyedTeam = $.act("Reload keyed team") + .does(async () => { + await keyedTeam.reload() + }) + $.surface("main").contains( reloadTeam, invalidateTeam, @@ -106,5 +133,7 @@ export const ResourceDemo = screen("Resource Demo", $ => { loadAuditLog, reloadCachedTeam, loadDedupeReport, + loadKeyedTeamB, + reloadKeyedTeam, ) })