Skip to content

Commit d57413e

Browse files
authored
docs: alpha.8 docs and resource-lifecycle cache example
docs: alpha.8 docs and resource-lifecycle cache example
2 parents 014dd16 + b2bde22 commit d57413e

6 files changed

Lines changed: 118 additions & 23 deletions

File tree

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ The current repository proves the core shape:
2323
- Typed router
2424
- Runtime-scoped resources
2525
- Resource reload and invalidation
26+
- Resource cache phase 1 (staleTime, deduplicate)
2627
- Independent executable actions
2728
- Keyboard Enter default action
2829
- Accessible Enter hints
@@ -39,17 +40,17 @@ It is not production-ready yet. The goal right now is to keep the foundation sma
3940
Intent is available as experimental alpha packages on npm:
4041

4142
```sh
42-
pnpm add @intent-framework/core@0.1.0-alpha.7 @intent-framework/dom@0.1.0-alpha.7 @intent-framework/router@0.1.0-alpha.7 @intent-framework/testing@0.1.0-alpha.7
43+
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
4344
```
4445

4546
```sh
46-
npm install @intent-framework/core@0.1.0-alpha.7 @intent-framework/dom@0.1.0-alpha.7 @intent-framework/router@0.1.0-alpha.7 @intent-framework/testing@0.1.0-alpha.7
47+
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
4748
```
4849

4950
The quickstart pins the current alpha version so examples match the published APIs.
5051

5152
```txt
52-
Current alpha: v0.1.0-alpha.7
53+
Current alpha: v0.1.0-alpha.8
5354
First public alpha: v0.1.0-alpha.0
5455
GitHub Releases: https://github.com/intent-framework/intent/releases
5556
```
@@ -346,7 +347,7 @@ Current limitations include:
346347
* No native renderer yet
347348
* No SSR story yet
348349
* No backend persistence yet
349-
* No real resource cache policy yet
350+
* Resource cache is phase 1 (staleTime, deduplicate); cache.key, cacheTime, swr, and cross-navigation cache are future
350351
* No DevTools package yet
351352
* Automated version PR workflow and manual publish workflow are set up (Changesets configured)
352353
* Demo side panels use manual DOM

docs/Quickstart.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@ This guide shows the fastest path from zero to a semantic screen using published
77
## 1. Install
88

99
```sh
10-
pnpm add @intent-framework/core@0.1.0-alpha.7 @intent-framework/dom@0.1.0-alpha.7 @intent-framework/testing@0.1.0-alpha.7
10+
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
1111
```
1212

1313
Or with npm:
1414

1515
```sh
16-
npm install @intent-framework/core@0.1.0-alpha.7 @intent-framework/dom@0.1.0-alpha.7 @intent-framework/testing@0.1.0-alpha.7
16+
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
1717
```
1818

19-
The quickstart pins `0.1.0-alpha.7` so the examples match the APIs shown below.
19+
The quickstart pins `0.1.0-alpha.8` so the examples match the APIs shown below.
2020

2121
You also need `typescript` and `vitest` for type checking and tests.
2222

docs/Release-Readiness.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Status
44

5-
Seven public alpha releases have been published: `0.1.0-alpha.0` through `0.1.0-alpha.7`.
5+
Eight public alpha releases have been published: `0.1.0-alpha.0` through `0.1.0-alpha.8`.
66

77
Five packages are under the `@intent-framework/*` scope, four of which are published to npm. The server package `@intent-framework/server` remains private and unpublished.
88

@@ -29,7 +29,7 @@ Packages under `examples/` (e.g., `web-basic`) are private and must not be publi
2929

3030
| Field | Value | Status |
3131
|---|---|---|
32-
| Current version | `0.1.0-alpha.7` | Acceptable for alpha |
32+
| Current version | `0.1.0-alpha.8` | Acceptable for alpha |
3333
| `main` | `./dist/index.js` | Exists after build |
3434
| `module` | `./dist/index.js` | Exists after build |
3535
| `types` | `./dist/index.d.ts` | Exists after build |
@@ -47,7 +47,7 @@ Packages under `examples/` (e.g., `web-basic`) are private and must not be publi
4747

4848
| Field | Value | Status |
4949
|---|---|---|
50-
| Current version | `0.1.0-alpha.7` | Acceptable for alpha |
50+
| Current version | `0.1.0-alpha.8` | Acceptable for alpha |
5151
| `main` | `./dist/index.js` | Exists after build |
5252
| `module` | `./dist/index.js` | Exists after build |
5353
| `types` | `./dist/index.d.ts` | Exists after build |
@@ -63,7 +63,7 @@ Packages under `examples/` (e.g., `web-basic`) are private and must not be publi
6363

6464
| Field | Value | Status |
6565
|---|---|---|
66-
| Current version | `0.1.0-alpha.7` | Acceptable for alpha |
66+
| Current version | `0.1.0-alpha.8` | Acceptable for alpha |
6767
| `main` | `./dist/index.js` | Exists after build |
6868
| `module` | `./dist/index.js` | Exists after build |
6969
| `types` | `./dist/index.d.ts` | Exists after build |
@@ -79,7 +79,7 @@ Packages under `examples/` (e.g., `web-basic`) are private and must not be publi
7979

8080
| Field | Value | Status |
8181
|---|---|---|
82-
| Current version | `0.1.0-alpha.7` | Acceptable for alpha |
82+
| Current version | `0.1.0-alpha.8` | Acceptable for alpha |
8383
| `main` | `./dist/index.js` | Exists after build |
8484
| `module` | `./dist/index.js` | Exists after build |
8585
| `types` | `./dist/index.d.ts` | Exists after build |
@@ -214,7 +214,7 @@ All post-publish steps use the `NPM_TOKEN` / `NODE_AUTH_TOKEN` from the `npm` en
214214

215215
### Publish cadence
216216

217-
- Currently publishing **alpha releases** (`0.1.0-alpha.0` through `0.1.0-alpha.7` so far)
217+
- Currently publishing **alpha releases** (`0.1.0-alpha.0` through `0.1.0-alpha.8` so far)
218218
- Use `Changesets` pre-release mode for alpha/beta
219219
- Graduate to stable after API surface is settled and real-world usage begins
220220

@@ -273,20 +273,20 @@ Do not manually create GitHub Releases.
273273
- Manual Publish Alpha workflow (manual dispatch only)
274274
- [x] NPM_TOKEN secret added to GitHub repository secrets
275275
- [x] First alpha published (v0.1.0-alpha.0)
276-
- [x] Subsequent alpha releases published (v0.1.0-alpha.1 through v0.1.0-alpha.7)
276+
- [x] Subsequent alpha releases published (v0.1.0-alpha.1 through v0.1.0-alpha.8)
277277

278278
## Post-release verification
279279

280280
Published packages (latest alpha):
281281

282-
- `@intent-framework/core@0.1.0-alpha.7`
283-
- `@intent-framework/dom@0.1.0-alpha.7`
284-
- `@intent-framework/router@0.1.0-alpha.7`
285-
- `@intent-framework/testing@0.1.0-alpha.7`
282+
- `@intent-framework/core@0.1.0-alpha.8`
283+
- `@intent-framework/dom@0.1.0-alpha.8`
284+
- `@intent-framework/router@0.1.0-alpha.8`
285+
- `@intent-framework/testing@0.1.0-alpha.8`
286286

287287
Verified:
288288

289-
- npm install smoke test passed for alpha.0 through alpha.7
289+
- npm install smoke test passed for alpha.0 through alpha.8
290290
- `alpha` dist-tag points to the latest published alpha version (set by workflow post-publish step)
291291
- `latest` dist-tag points to the latest alpha (changeset behavior — publishes to `latest` when no regular release exists)
292292
- Git tags (`@intent-framework/pkg@version`) are present on remote (pushed by workflow post-publish step)

examples/resource-lifecycle/README.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Resource Lifecycle Example
22

3-
A focused demonstration of the full resource lifecycle in Intent — autoLoad, manual load, reload, invalidation, stale detection, failed state, and action-driven invalidation.
3+
A focused demonstration of the full resource lifecycle in Intent — autoLoad, manual load, reload, invalidation, stale detection, failed state, action-driven invalidation, and the cache options introduced in alpha.8.
44

55
## What it demonstrates
66

@@ -12,6 +12,8 @@ A focused demonstration of the full resource lifecycle in Intent — autoLoad, m
1212
- `.invalidates(resource)` on an action — marks resources stale on success
1313
- Resource "failed" status when a loader throws
1414
- Route-driven resource load context
15+
- `cache.staleTime` — resource transitions to stale automatically after a timeout (alpha.8)
16+
- `cache.deduplicate` — concurrent loads share the same in-flight promise (alpha.8)
1517

1618
## Run
1719

@@ -28,11 +30,32 @@ Open the local URL printed by Vite. Use the browser console to inspect resource
2830
pnpm test
2931
```
3032

31-
Tests cover every resource status transition: autoLoad, idle, pending, ready, stale, failed, and action-driven invalidation.
33+
Tests cover every resource status transition: autoLoad, idle, pending, ready, stale, failed, action-driven invalidation, and the alpha.8 cache options.
3234

3335
## Inspect
3436

3537
- Watch the console log for resource load events and load counts
3638
- Each reload increments the team version counter
3739
- The "Broken save" action demonstrates a failed invalidation
40+
- `cache.staleTime` on the `cachedTeam` resource auto-transitions to stale after 50ms
41+
- `cache.deduplicate` on the `dedupeReport` resource shares concurrent load promises
3842
- Test file (`src/ResourceDemo.test.ts`) shows the full resource API surface via `@intent-framework/testing`
43+
44+
## Cache options (alpha.8)
45+
46+
```ts
47+
// staleTime — auto-stale after a timeout (ms)
48+
const cachedTeam = $.resource("cachedTeam", {
49+
load: async () => loadTeam(),
50+
cache: { staleTime: 50 },
51+
})
52+
53+
// deduplicate — share in-flight promise between concurrent calls
54+
const dedupeReport = $.resource("dedupeReport", {
55+
load: async () => loadReport(),
56+
autoLoad: false,
57+
cache: { deduplicate: true },
58+
})
59+
```
60+
61+
When no `cache` object is set, existing behavior is preserved (no deduplication, no time-based staleness).

examples/resource-lifecycle/src/ResourceDemo.test.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { describe, it, expect } from "vitest"
22
import { testScreen } from "@intent-framework/testing"
33
import { createScreenRuntime, inspectScreen } from "@intent-framework/core"
4-
import { ResourceDemo, teamLoadCount } from "./ResourceDemo.js"
4+
import { ResourceDemo, teamLoadCount, cachedTeamLoadCount, dedupeReportLoadCount } from "./ResourceDemo.js"
55

66
const testServices = {
77
route: { name: "demo", path: "/:id", params: { id: "team_1" } },
@@ -82,12 +82,46 @@ describe("ResourceDemo", () => {
8282
}, { services: testServices as any })
8383
})
8484

85+
it("cache.staleTime marks resource stale after timeout", async () => {
86+
await testScreen(ResourceDemo, async app => {
87+
const cached = app.resource("cachedTeam")
88+
expect(cached.status()).toBe("ready")
89+
expect(cached.stale()).toBe(false)
90+
await new Promise(r => setTimeout(r, 100))
91+
expect(cached.stale()).toBe(true)
92+
expect(cached.status()).toBe("ready")
93+
}, { services: testServices as any })
94+
})
95+
96+
it("reload resets staleTime timer and clears stale", async () => {
97+
await testScreen(ResourceDemo, async app => {
98+
const cached = app.resource("cachedTeam")
99+
await new Promise(r => setTimeout(r, 60))
100+
expect(cached.stale()).toBe(true)
101+
const before = cachedTeamLoadCount
102+
await cached.reload()
103+
expect(cachedTeamLoadCount).toBe(before + 1)
104+
expect(cached.stale()).toBe(false)
105+
}, { services: testServices as any })
106+
})
107+
108+
it("cache.deduplicate shares in-flight promise", async () => {
109+
await testScreen(ResourceDemo, async app => {
110+
const report = app.resource("dedupeReport")
111+
expect(report.status()).toBe("idle")
112+
const before = dedupeReportLoadCount
113+
await Promise.all([report.load(), report.load()])
114+
expect(dedupeReportLoadCount).toBe(before + 1)
115+
expect(report.status()).toBe("ready")
116+
}, { services: testServices as any })
117+
})
118+
85119
it("inspectScreen reports resources with status/stale/error", async () => {
86120
const runtime = createScreenRuntime(ResourceDemo, { services: testServices as any })
87121
await runtime.start()
88122
const graph = runtime.graph
89123
const resources = graph.resources
90-
expect(resources).toHaveLength(3)
124+
expect(resources).toHaveLength(5)
91125
const teamRes = resources.find(r => r.name === "team")!
92126
expect(teamRes.status).toBe("ready")
93127
expect(teamRes.hasValue).toBe(true)

examples/resource-lifecycle/src/ResourceDemo.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import type { AppServices } from "./types.js"
33

44
export let teamLoadCount = 0
55
export let auditLogLoadCount = 0
6+
export let cachedTeamLoadCount = 0
7+
export let dedupeReportLoadCount = 0
68

79
export type Team = {
810
id: string
@@ -38,6 +40,29 @@ export const ResourceDemo = screen<AppServices>("Resource Demo", $ => {
3840
},
3941
})
4042

43+
const cachedTeam = $.resource("cachedTeam", {
44+
load: async ({ route }) => {
45+
cachedTeamLoadCount++
46+
return {
47+
id: route.params.id,
48+
name: "Cached",
49+
members: 5,
50+
version: cachedTeamLoadCount,
51+
} satisfies Team
52+
},
53+
cache: { staleTime: 50 },
54+
})
55+
56+
const dedupeReport = $.resource("dedupeReport", {
57+
load: async () => {
58+
dedupeReportLoadCount++
59+
await new Promise(r => setTimeout(r, 20))
60+
return { summary: "ok" }
61+
},
62+
autoLoad: false,
63+
cache: { deduplicate: true },
64+
})
65+
4166
const reloadTeam = $.act("Reload team")
4267
.does(async () => {
4368
await team.reload()
@@ -63,11 +88,23 @@ export const ResourceDemo = screen<AppServices>("Resource Demo", $ => {
6388
await auditLog.load()
6489
})
6590

91+
const reloadCachedTeam = $.act("Reload cached team")
92+
.does(async () => {
93+
await cachedTeam.reload()
94+
})
95+
96+
const loadDedupeReport = $.act("Load dedupe report")
97+
.does(async () => {
98+
await dedupeReport.load()
99+
})
100+
66101
$.surface("main").contains(
67102
reloadTeam,
68103
invalidateTeam,
69104
saveTeam,
70105
brokenSave,
71106
loadAuditLog,
107+
reloadCachedTeam,
108+
loadDedupeReport,
72109
)
73110
})

0 commit comments

Comments
 (0)