@intent-framework/core 0.1.0-alpha.1 → 0.1.0-alpha.10
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 +67 -0
- package/dist/graph.d.ts +5 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +204 -29
- package/dist/resource.d.ts +10 -1
- package/dist/screen.d.ts +2 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# @intent-framework/core
|
|
2
|
+
|
|
3
|
+
Platformless semantic graph and runtime for Intent applications.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm add @intent-framework/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install @intent-framework/core
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## What it provides
|
|
16
|
+
|
|
17
|
+
- `screen()` — define a semantic interaction space
|
|
18
|
+
- `$.state.text()` / `$.state.boolean()` / `$.state.choice()` — reactive state
|
|
19
|
+
- `$.ask()` — user-facing question with validation
|
|
20
|
+
- `$.act()` — executable action with conditions, lifecycle, and feedback
|
|
21
|
+
- `$.resource()` — async state with load/reload lifecycle
|
|
22
|
+
- `$.surface()` — named containment surface
|
|
23
|
+
- `createScreenRuntime()` — runtime that owns screen state and resources
|
|
24
|
+
- `inspectScreen()` — semantic graph snapshot with diagnostics
|
|
25
|
+
- Condition and signal primitives
|
|
26
|
+
|
|
27
|
+
## Minimal example
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { screen, inspectScreen } from "@intent-framework/core"
|
|
31
|
+
|
|
32
|
+
const InviteMember = screen("InviteMember", $ => {
|
|
33
|
+
const email = $.state.text("email")
|
|
34
|
+
|
|
35
|
+
const emailAsk = $.ask("Email", email)
|
|
36
|
+
.required("Email is required")
|
|
37
|
+
.validate(value => value.includes("@") ? true : "Enter a valid email")
|
|
38
|
+
|
|
39
|
+
const invite = $.act("Invite member")
|
|
40
|
+
.primary()
|
|
41
|
+
.when(emailAsk.valid, "Enter a valid email first")
|
|
42
|
+
.does(() => {
|
|
43
|
+
console.log("invite", email.value)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
$.surface("main").contains(emailAsk, invite)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const graph = inspectScreen(InviteMember)
|
|
50
|
+
console.log(graph.diagnostics)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Where this fits
|
|
54
|
+
|
|
55
|
+
Core defines the product graph. It has no DOM, React, Node, or framework dependencies. Renderers (`@intent-framework/dom`), the router (`@intent-framework/router`), and testing (`@intent-framework/testing`) all build on core.
|
|
56
|
+
|
|
57
|
+
## Learn more
|
|
58
|
+
|
|
59
|
+
- [Root README](../../README.md) — project overview and philosophy
|
|
60
|
+
- [Quickstart](../../docs/Quickstart.md) — step-by-step guide
|
|
61
|
+
- [Inspect Screen and Diagnostics Guide](../../docs/Inspect-Screen.md) — graph inspection and diagnostics
|
|
62
|
+
- [Resources Guide](../../docs/Resources.md) — resource lifecycle and runtime scoping
|
|
63
|
+
- [MVP Checkpoint](../../docs/MVP-Checkpoint.md) — current implementation boundaries
|
|
64
|
+
|
|
65
|
+
## Status
|
|
66
|
+
|
|
67
|
+
Experimental alpha. APIs may change. Not recommended for production use.
|
package/dist/graph.d.ts
CHANGED
|
@@ -2,12 +2,17 @@ import type { ScreenDefinition } from "./screen.js";
|
|
|
2
2
|
import type { DefaultScreenServices } from "./act.js";
|
|
3
3
|
import type { AnyResourceNode } from "./resource.js";
|
|
4
4
|
export type DiagnosticSeverity = "info" | "warning" | "error";
|
|
5
|
+
export type FlowDiagnosticMeta = {
|
|
6
|
+
flowNodeId: string;
|
|
7
|
+
flowSemanticNodeId?: string;
|
|
8
|
+
};
|
|
5
9
|
export type GraphDiagnostic = {
|
|
6
10
|
severity: DiagnosticSeverity;
|
|
7
11
|
code: string;
|
|
8
12
|
message: string;
|
|
9
13
|
nodeId?: string;
|
|
10
14
|
semanticNodeId?: string;
|
|
15
|
+
flow?: FlowDiagnosticMeta;
|
|
11
16
|
};
|
|
12
17
|
export type InspectedScreen = {
|
|
13
18
|
name: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
export { screen } from "./screen.js";
|
|
2
2
|
export type { ScreenDefinition, ScreenBuilder } from "./screen.js";
|
|
3
3
|
export { inspectScreen } from "./graph.js";
|
|
4
|
-
export type { InspectedScreen, GraphDiagnostic, DiagnosticSeverity } from "./graph.js";
|
|
4
|
+
export type { InspectedScreen, GraphDiagnostic, DiagnosticSeverity, FlowDiagnosticMeta } from "./graph.js";
|
|
5
5
|
export type { TextState, BooleanState, ChoiceState } from "./state.js";
|
|
6
6
|
export type { AskNode, AnyAskNode, AskKind, AskBuilder } from "./ask.js";
|
|
7
7
|
export type { ActNode, ActCondition, ActStatus, FeedbackConfig, ActBuilder, NavigationService, ActionExecutionContext, DefaultScreenServices } from "./act.js";
|
|
8
8
|
export type { FlowNode, FlowStep, FlowBuilder } from "./flow.js";
|
|
9
9
|
export type { SurfaceNode, SurfaceBuilder } from "./surface.js";
|
|
10
|
-
export type { ResourceNode, ResourceConfig, ResourceLoadContext, ResourceStatus, AnyResourceNode } from "./resource.js";
|
|
10
|
+
export type { ResourceNode, ResourceConfig, ResourceCacheOptions, ResourceLoadContext, ResourceStatus, ResourceKey, AnyResourceNode } from "./resource.js";
|
|
11
11
|
export { ResourceRef, createResourceNode } from "./resource.js";
|
|
12
12
|
export { createScreenRuntime } from "./runtime.js";
|
|
13
13
|
export type { ScreenRuntime } from "./runtime.js";
|
package/dist/index.js
CHANGED
|
@@ -37,9 +37,25 @@ function signal(initial) {
|
|
|
37
37
|
|
|
38
38
|
//#endregion
|
|
39
39
|
//#region src/resource.ts
|
|
40
|
-
function createResourceNode(id, name, loader, autoLoad = true) {
|
|
40
|
+
function createResourceNode(id, name, loader, autoLoad = true, cache) {
|
|
41
41
|
const statusSignal = signal(0);
|
|
42
42
|
const staleSignal = signal(0);
|
|
43
|
+
const hasKey = typeof cache?.key === "function";
|
|
44
|
+
const DEFAULT_KEY = "";
|
|
45
|
+
function createEntry() {
|
|
46
|
+
return {
|
|
47
|
+
value: void 0,
|
|
48
|
+
status: "idle",
|
|
49
|
+
error: void 0,
|
|
50
|
+
stale: false,
|
|
51
|
+
staleTimer: null,
|
|
52
|
+
cacheTimer: null,
|
|
53
|
+
inFlightPromise: null
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const entries = /* @__PURE__ */ new Map();
|
|
57
|
+
let _activeKey = DEFAULT_KEY;
|
|
58
|
+
if (!hasKey) entries.set(DEFAULT_KEY, createEntry());
|
|
43
59
|
let currentStatus = "idle";
|
|
44
60
|
let currentValue = void 0;
|
|
45
61
|
let currentError = void 0;
|
|
@@ -47,10 +63,94 @@ function createResourceNode(id, name, loader, autoLoad = true) {
|
|
|
47
63
|
let lastContext = void 0;
|
|
48
64
|
const notify = () => statusSignal.set(statusSignal.get() + 1);
|
|
49
65
|
const staleNotify = () => staleSignal.set(staleSignal.get() + 1);
|
|
66
|
+
const shouldDeduplicate = cache ? cache.deduplicate !== false : false;
|
|
50
67
|
let _ready;
|
|
51
68
|
let _pending;
|
|
52
69
|
let _failed;
|
|
53
70
|
let _stale;
|
|
71
|
+
function getActiveEntry() {
|
|
72
|
+
let entry = entries.get(_activeKey);
|
|
73
|
+
if (!entry) {
|
|
74
|
+
entry = createEntry();
|
|
75
|
+
entries.set(_activeKey, entry);
|
|
76
|
+
}
|
|
77
|
+
return entry;
|
|
78
|
+
}
|
|
79
|
+
function syncFromEntry(entry) {
|
|
80
|
+
currentStatus = entry.status;
|
|
81
|
+
currentValue = entry.value;
|
|
82
|
+
currentError = entry.error;
|
|
83
|
+
if (currentStale !== entry.stale) {
|
|
84
|
+
currentStale = entry.stale;
|
|
85
|
+
staleNotify();
|
|
86
|
+
}
|
|
87
|
+
notify();
|
|
88
|
+
}
|
|
89
|
+
function syncFromActiveEntry() {
|
|
90
|
+
syncFromEntry(getActiveEntry());
|
|
91
|
+
}
|
|
92
|
+
function encodeResourceKey(key) {
|
|
93
|
+
if (Array.isArray(key)) return ["array", key.map(encodeResourceKey)];
|
|
94
|
+
if (key === null) return ["null"];
|
|
95
|
+
if (key === void 0) return ["undefined"];
|
|
96
|
+
if (typeof key === "string") return ["string", key];
|
|
97
|
+
if (typeof key === "boolean") return ["boolean", key];
|
|
98
|
+
if (typeof key === "number") {
|
|
99
|
+
if (Number.isNaN(key)) return ["number", "NaN"];
|
|
100
|
+
if (key === Infinity) return ["number", "Infinity"];
|
|
101
|
+
if (key === -Infinity) return ["number", "-Infinity"];
|
|
102
|
+
if (Object.is(key, -0)) return ["number", "-0"];
|
|
103
|
+
return ["number", key];
|
|
104
|
+
}
|
|
105
|
+
const exhaustive = key;
|
|
106
|
+
return exhaustive;
|
|
107
|
+
}
|
|
108
|
+
function normalizeKey(key) {
|
|
109
|
+
return JSON.stringify(encodeResourceKey(key));
|
|
110
|
+
}
|
|
111
|
+
function resolveKey(ctx) {
|
|
112
|
+
if (!hasKey) return DEFAULT_KEY;
|
|
113
|
+
const context = ctx ?? lastContext ?? {};
|
|
114
|
+
return normalizeKey(cache.key(context));
|
|
115
|
+
}
|
|
116
|
+
function _clearEntryStaleTimer(entry) {
|
|
117
|
+
if (entry.staleTimer != null) {
|
|
118
|
+
clearTimeout(entry.staleTimer);
|
|
119
|
+
entry.staleTimer = null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function _startEntryStaleTimer(entry, key) {
|
|
123
|
+
_clearEntryStaleTimer(entry);
|
|
124
|
+
if (cache?.staleTime != null && isFinite(cache.staleTime)) entry.staleTimer = setTimeout(() => {
|
|
125
|
+
if (!entry.stale) {
|
|
126
|
+
entry.stale = true;
|
|
127
|
+
_startEntryCacheTimer(entry, key);
|
|
128
|
+
if (entry === getActiveEntry()) {
|
|
129
|
+
currentStale = true;
|
|
130
|
+
staleNotify();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}, cache.staleTime);
|
|
134
|
+
}
|
|
135
|
+
function _clearEntryCacheTimer(entry) {
|
|
136
|
+
if (entry.cacheTimer != null) {
|
|
137
|
+
clearTimeout(entry.cacheTimer);
|
|
138
|
+
entry.cacheTimer = null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function _startEntryCacheTimer(entry, key) {
|
|
142
|
+
_clearEntryCacheTimer(entry);
|
|
143
|
+
if (cache?.cacheTime != null && isFinite(cache.cacheTime)) entry.cacheTimer = setTimeout(() => {
|
|
144
|
+
if (entry === getActiveEntry()) {
|
|
145
|
+
entry.value = void 0;
|
|
146
|
+
entry.error = void 0;
|
|
147
|
+
entry.status = "idle";
|
|
148
|
+
entry.stale = false;
|
|
149
|
+
_clearEntryCacheTimer(entry);
|
|
150
|
+
syncFromActiveEntry();
|
|
151
|
+
} else entries.delete(key);
|
|
152
|
+
}, cache.cacheTime);
|
|
153
|
+
}
|
|
54
154
|
function getReady() {
|
|
55
155
|
if (!_ready) _ready = createCondition(() => currentStatus === "ready", (notify$1) => statusSignal.subscribe(() => notify$1()));
|
|
56
156
|
return _ready;
|
|
@@ -67,34 +167,68 @@ function createResourceNode(id, name, loader, autoLoad = true) {
|
|
|
67
167
|
if (!_stale) _stale = createCondition(() => currentStale, (notify$1) => staleSignal.subscribe(() => notify$1()));
|
|
68
168
|
return _stale;
|
|
69
169
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
170
|
+
function executeLoad(context) {
|
|
171
|
+
const key = resolveKey(context);
|
|
172
|
+
const prevActiveKey = _activeKey;
|
|
173
|
+
_activeKey = key;
|
|
174
|
+
let entry = entries.get(key);
|
|
175
|
+
if (!entry) {
|
|
176
|
+
entry = createEntry();
|
|
177
|
+
entries.set(key, entry);
|
|
178
|
+
}
|
|
179
|
+
_clearEntryCacheTimer(entry);
|
|
180
|
+
if (shouldDeduplicate && entry.inFlightPromise) {
|
|
181
|
+
if (key !== prevActiveKey) syncFromEntry(entry);
|
|
182
|
+
return entry.inFlightPromise;
|
|
183
|
+
}
|
|
77
184
|
if (context !== void 0) lastContext = context;
|
|
78
185
|
const loadContext = context ?? lastContext ?? {};
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
186
|
+
entry.stale = false;
|
|
187
|
+
entry.status = "pending";
|
|
188
|
+
entry.value = void 0;
|
|
189
|
+
entry.error = void 0;
|
|
190
|
+
syncFromActiveEntry();
|
|
191
|
+
const promise = (async () => {
|
|
192
|
+
try {
|
|
193
|
+
const result = await Promise.resolve(loader(loadContext));
|
|
194
|
+
if (entries.get(key) !== entry) return;
|
|
195
|
+
entry.value = result;
|
|
196
|
+
entry.status = "ready";
|
|
197
|
+
entry.stale = false;
|
|
198
|
+
_clearEntryCacheTimer(entry);
|
|
199
|
+
if (entry === getActiveEntry()) syncFromEntry(entry);
|
|
200
|
+
staleNotify();
|
|
201
|
+
_startEntryStaleTimer(entry, key);
|
|
202
|
+
} catch (e) {
|
|
203
|
+
if (entries.get(key) !== entry) return;
|
|
204
|
+
entry.error = e;
|
|
205
|
+
entry.status = "failed";
|
|
206
|
+
entry.stale = false;
|
|
207
|
+
if (entry === getActiveEntry()) syncFromEntry(entry);
|
|
208
|
+
staleNotify();
|
|
209
|
+
} finally {
|
|
210
|
+
entry.inFlightPromise = null;
|
|
211
|
+
}
|
|
212
|
+
})();
|
|
213
|
+
entry.inFlightPromise = promise;
|
|
214
|
+
return promise;
|
|
93
215
|
}
|
|
94
216
|
function invalidate() {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
217
|
+
const entry = getActiveEntry();
|
|
218
|
+
if (!entry.stale) {
|
|
219
|
+
entry.stale = true;
|
|
220
|
+
_startEntryCacheTimer(entry, _activeKey);
|
|
221
|
+
if (entry === getActiveEntry()) {
|
|
222
|
+
currentStale = true;
|
|
223
|
+
staleNotify();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function dispose() {
|
|
228
|
+
for (const entry of entries.values()) {
|
|
229
|
+
_clearEntryStaleTimer(entry);
|
|
230
|
+
_clearEntryCacheTimer(entry);
|
|
231
|
+
entry.inFlightPromise = null;
|
|
98
232
|
}
|
|
99
233
|
}
|
|
100
234
|
const node = {
|
|
@@ -127,7 +261,8 @@ function createResourceNode(id, name, loader, autoLoad = true) {
|
|
|
127
261
|
invalidate,
|
|
128
262
|
subscribe(fn) {
|
|
129
263
|
return statusSignal.subscribe(fn);
|
|
130
|
-
}
|
|
264
|
+
},
|
|
265
|
+
dispose
|
|
131
266
|
};
|
|
132
267
|
return node;
|
|
133
268
|
}
|
|
@@ -683,6 +818,7 @@ function screen(name, fn) {
|
|
|
683
818
|
name: n,
|
|
684
819
|
autoLoad: config.autoLoad ?? true,
|
|
685
820
|
loader: config.load,
|
|
821
|
+
cache: config.cache,
|
|
686
822
|
ref
|
|
687
823
|
});
|
|
688
824
|
return ref;
|
|
@@ -761,6 +897,38 @@ function computeDiagnostics(screenDef) {
|
|
|
761
897
|
message: "Action is defined but not included in any surface.",
|
|
762
898
|
nodeId: act.id
|
|
763
899
|
});
|
|
900
|
+
if (screenDef.flows.length > 0) {
|
|
901
|
+
const flowNodeIds = /* @__PURE__ */ new Set();
|
|
902
|
+
for (const flow of screenDef.flows) for (const step of flow.steps) flowNodeIds.add(step.node.id);
|
|
903
|
+
for (const flow of screenDef.flows) for (const step of flow.steps) if (!surfacedNodeIds.has(step.node.id)) diagnostics.push({
|
|
904
|
+
severity: "warning",
|
|
905
|
+
code: "flow-step-not-surfaced",
|
|
906
|
+
message: `"${step.node.label}" is a flow step but not included in any surface.`,
|
|
907
|
+
nodeId: step.node.id,
|
|
908
|
+
flow: { flowNodeId: flow.id }
|
|
909
|
+
});
|
|
910
|
+
for (const flow of screenDef.flows) if (flow.steps.length > 0) {
|
|
911
|
+
const hasSurfacedStep = flow.steps.some((step) => surfacedNodeIds.has(step.node.id));
|
|
912
|
+
if (!hasSurfacedStep) diagnostics.push({
|
|
913
|
+
severity: "warning",
|
|
914
|
+
code: "orphaned-flow",
|
|
915
|
+
message: `"${flow.name}" has no surfaced steps.`,
|
|
916
|
+
flow: { flowNodeId: flow.id }
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
for (const ask of screenDef.asks) if (surfacedNodeIds.has(ask.id) && !flowNodeIds.has(ask.id)) diagnostics.push({
|
|
920
|
+
severity: "info",
|
|
921
|
+
code: "surfaced-node-not-in-any-flow",
|
|
922
|
+
message: `"${ask.label}" is surfaced but not referenced in any flow.`,
|
|
923
|
+
nodeId: ask.id
|
|
924
|
+
});
|
|
925
|
+
for (const act of screenDef.acts) if (surfacedNodeIds.has(act.id) && !flowNodeIds.has(act.id)) diagnostics.push({
|
|
926
|
+
severity: "info",
|
|
927
|
+
code: "surfaced-node-not-in-any-flow",
|
|
928
|
+
message: `"${act.label}" is surfaced but not referenced in any flow.`,
|
|
929
|
+
nodeId: act.id
|
|
930
|
+
});
|
|
931
|
+
}
|
|
764
932
|
return diagnostics;
|
|
765
933
|
}
|
|
766
934
|
function inspectScreen(screenDef, runtimeResources) {
|
|
@@ -773,9 +941,15 @@ function inspectScreen(screenDef, runtimeResources) {
|
|
|
773
941
|
const idToSemantic = /* @__PURE__ */ new Map();
|
|
774
942
|
for (const a of screenDef.asks) idToSemantic.set(a.id, askIds(a.label));
|
|
775
943
|
for (const a of screenDef.acts) idToSemantic.set(a.id, actIds(a.label));
|
|
944
|
+
const idToFlowSemantic = /* @__PURE__ */ new Map();
|
|
945
|
+
for (const f of screenDef.flows) idToFlowSemantic.set(f.id, flowIds(f.name));
|
|
776
946
|
const augmentedDiagnostics = diagnostics.map((d) => ({
|
|
777
947
|
...d,
|
|
778
|
-
semanticNodeId: d.nodeId ? idToSemantic.get(d.nodeId) : void 0
|
|
948
|
+
semanticNodeId: d.nodeId ? idToSemantic.get(d.nodeId) : void 0,
|
|
949
|
+
flow: d.flow ? {
|
|
950
|
+
...d.flow,
|
|
951
|
+
flowSemanticNodeId: idToFlowSemantic.get(d.flow.flowNodeId)
|
|
952
|
+
} : void 0
|
|
779
953
|
}));
|
|
780
954
|
return {
|
|
781
955
|
name: screenDef.name,
|
|
@@ -803,7 +977,7 @@ function inspectScreen(screenDef, runtimeResources) {
|
|
|
803
977
|
})),
|
|
804
978
|
flows: screenDef.flows.map((f) => ({
|
|
805
979
|
id: f.id,
|
|
806
|
-
semanticId:
|
|
980
|
+
semanticId: idToFlowSemantic.get(f.id),
|
|
807
981
|
name: f.name,
|
|
808
982
|
stepCount: f.steps.length
|
|
809
983
|
})),
|
|
@@ -869,7 +1043,7 @@ var ScreenRuntime = class {
|
|
|
869
1043
|
this._started = true;
|
|
870
1044
|
const nodeMap = /* @__PURE__ */ new Map();
|
|
871
1045
|
for (const config of this._screen.resourceConfigs) {
|
|
872
|
-
const node = createResourceNode(config.id, config.name, config.loader, false);
|
|
1046
|
+
const node = createResourceNode(config.id, config.name, config.loader, false, config.cache);
|
|
873
1047
|
this._resourceNodes.push(node);
|
|
874
1048
|
nodeMap.set(config.id, node);
|
|
875
1049
|
}
|
|
@@ -891,6 +1065,7 @@ var ScreenRuntime = class {
|
|
|
891
1065
|
this._disposed = true;
|
|
892
1066
|
for (const unsub of this._unsubscribers) unsub();
|
|
893
1067
|
this._unsubscribers = [];
|
|
1068
|
+
for (const node of this._resourceNodes) node.dispose();
|
|
894
1069
|
for (const config of this._screen.resourceConfigs) if (config.ref && this._resourceNodeMap) {
|
|
895
1070
|
const node = this._resourceNodeMap.get(config.id);
|
|
896
1071
|
if (node) config.ref._disconnect(node);
|
package/dist/resource.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Condition } from "./signal.js";
|
|
2
2
|
import type { ActionExecutionContext, DefaultScreenServices } from "./act.js";
|
|
3
3
|
export type ResourceStatus = "idle" | "pending" | "ready" | "failed";
|
|
4
|
+
export type ResourceKey = string | number | boolean | null | undefined | ResourceKey[];
|
|
4
5
|
export type ResourceLoadContext<TServices extends object = DefaultScreenServices> = ActionExecutionContext<TServices>;
|
|
5
6
|
type ResourceLoader<TValue, TServices extends object> = (() => TValue | Promise<TValue>) | ((context: ResourceLoadContext<TServices>) => TValue | Promise<TValue>);
|
|
6
7
|
export type ResourceNode<TValue, TServices extends object = DefaultScreenServices> = {
|
|
@@ -18,17 +19,25 @@ export type ResourceNode<TValue, TServices extends object = DefaultScreenService
|
|
|
18
19
|
reload: (context?: ResourceLoadContext<TServices>) => Promise<void>;
|
|
19
20
|
invalidate: () => void;
|
|
20
21
|
subscribe: (fn: () => void) => () => void;
|
|
22
|
+
dispose: () => void;
|
|
21
23
|
};
|
|
22
24
|
export type AnyResourceNode = ResourceNode<unknown, any>;
|
|
25
|
+
export type ResourceCacheOptions<TServices extends object = DefaultScreenServices> = {
|
|
26
|
+
key?: (context: ResourceLoadContext<TServices>) => ResourceKey;
|
|
27
|
+
staleTime?: number;
|
|
28
|
+
cacheTime?: number;
|
|
29
|
+
deduplicate?: boolean;
|
|
30
|
+
};
|
|
23
31
|
export type ResourceConfig<TValue = unknown, TServices extends object = DefaultScreenServices> = {
|
|
24
32
|
id: string;
|
|
25
33
|
name: string;
|
|
26
34
|
autoLoad: boolean;
|
|
27
35
|
loader: ResourceLoader<TValue, TServices>;
|
|
36
|
+
cache?: ResourceCacheOptions<TServices>;
|
|
28
37
|
ref?: ResourceRef<TValue, TServices>;
|
|
29
38
|
};
|
|
30
39
|
export declare function createResourceConfig<TValue, TServices extends object = DefaultScreenServices>(id: string, name: string, loader: ResourceLoader<TValue, TServices>, autoLoad?: boolean): ResourceConfig<TValue, TServices>;
|
|
31
|
-
export declare function createResourceNode<TValue, TServices extends object = DefaultScreenServices>(id: string, name: string, loader: ResourceLoader<TValue, TServices>, autoLoad?: boolean): ResourceNode<TValue, TServices>;
|
|
40
|
+
export declare function createResourceNode<TValue, TServices extends object = DefaultScreenServices>(id: string, name: string, loader: ResourceLoader<TValue, TServices>, autoLoad?: boolean, cache?: ResourceCacheOptions<TServices>): ResourceNode<TValue, TServices>;
|
|
32
41
|
export declare class ResourceRef<TValue, TServices extends object = DefaultScreenServices> {
|
|
33
42
|
readonly id: string;
|
|
34
43
|
readonly name: string;
|
package/dist/screen.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { AnyAskNode } from "./ask.js";
|
|
|
2
2
|
import type { ActNode, DefaultScreenServices } from "./act.js";
|
|
3
3
|
import type { FlowNode } from "./flow.js";
|
|
4
4
|
import type { SurfaceNode } from "./surface.js";
|
|
5
|
-
import type { ResourceConfig, ResourceLoadContext } from "./resource.js";
|
|
5
|
+
import type { ResourceCacheOptions, ResourceConfig, ResourceLoadContext } from "./resource.js";
|
|
6
6
|
import { ResourceRef } from "./resource.js";
|
|
7
7
|
import { type TextState, type BooleanState, type ChoiceState } from "./state.js";
|
|
8
8
|
import { AskBuilder } from "./ask.js";
|
|
@@ -31,6 +31,7 @@ export type ScreenBuilder<TServices extends object = DefaultScreenServices> = {
|
|
|
31
31
|
resource: <T>(name: string, config: {
|
|
32
32
|
load: (() => Promise<T>) | ((context: ResourceLoadContext<TServices>) => Promise<T>);
|
|
33
33
|
autoLoad?: boolean;
|
|
34
|
+
cache?: ResourceCacheOptions<TServices>;
|
|
34
35
|
}) => ResourceRef<T, TServices>;
|
|
35
36
|
};
|
|
36
37
|
export type ScreenDefinition<TServices extends object = DefaultScreenServices> = {
|