@intent-framework/core 0.1.0-alpha.7 → 0.1.0-alpha.9
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/dist/index.d.ts +1 -1
- package/dist/index.js +133 -27
- package/dist/resource.d.ts +9 -1
- package/dist/screen.d.ts +2 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ 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,24 @@ 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
|
+
inFlightPromise: null
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const entries = /* @__PURE__ */ new Map();
|
|
56
|
+
let _activeKey = DEFAULT_KEY;
|
|
57
|
+
if (!hasKey) entries.set(DEFAULT_KEY, createEntry());
|
|
43
58
|
let currentStatus = "idle";
|
|
44
59
|
let currentValue = void 0;
|
|
45
60
|
let currentError = void 0;
|
|
@@ -47,10 +62,74 @@ function createResourceNode(id, name, loader, autoLoad = true) {
|
|
|
47
62
|
let lastContext = void 0;
|
|
48
63
|
const notify = () => statusSignal.set(statusSignal.get() + 1);
|
|
49
64
|
const staleNotify = () => staleSignal.set(staleSignal.get() + 1);
|
|
65
|
+
const shouldDeduplicate = cache ? cache.deduplicate !== false : false;
|
|
50
66
|
let _ready;
|
|
51
67
|
let _pending;
|
|
52
68
|
let _failed;
|
|
53
69
|
let _stale;
|
|
70
|
+
function getActiveEntry() {
|
|
71
|
+
let entry = entries.get(_activeKey);
|
|
72
|
+
if (!entry) {
|
|
73
|
+
entry = createEntry();
|
|
74
|
+
entries.set(_activeKey, entry);
|
|
75
|
+
}
|
|
76
|
+
return entry;
|
|
77
|
+
}
|
|
78
|
+
function syncFromEntry(entry) {
|
|
79
|
+
currentStatus = entry.status;
|
|
80
|
+
currentValue = entry.value;
|
|
81
|
+
currentError = entry.error;
|
|
82
|
+
if (currentStale !== entry.stale) {
|
|
83
|
+
currentStale = entry.stale;
|
|
84
|
+
staleNotify();
|
|
85
|
+
}
|
|
86
|
+
notify();
|
|
87
|
+
}
|
|
88
|
+
function syncFromActiveEntry() {
|
|
89
|
+
syncFromEntry(getActiveEntry());
|
|
90
|
+
}
|
|
91
|
+
function encodeResourceKey(key) {
|
|
92
|
+
if (Array.isArray(key)) return ["array", key.map(encodeResourceKey)];
|
|
93
|
+
if (key === null) return ["null"];
|
|
94
|
+
if (key === void 0) return ["undefined"];
|
|
95
|
+
if (typeof key === "string") return ["string", key];
|
|
96
|
+
if (typeof key === "boolean") return ["boolean", key];
|
|
97
|
+
if (typeof key === "number") {
|
|
98
|
+
if (Number.isNaN(key)) return ["number", "NaN"];
|
|
99
|
+
if (key === Infinity) return ["number", "Infinity"];
|
|
100
|
+
if (key === -Infinity) return ["number", "-Infinity"];
|
|
101
|
+
if (Object.is(key, -0)) return ["number", "-0"];
|
|
102
|
+
return ["number", key];
|
|
103
|
+
}
|
|
104
|
+
const exhaustive = key;
|
|
105
|
+
return exhaustive;
|
|
106
|
+
}
|
|
107
|
+
function normalizeKey(key) {
|
|
108
|
+
return JSON.stringify(encodeResourceKey(key));
|
|
109
|
+
}
|
|
110
|
+
function resolveKey(ctx) {
|
|
111
|
+
if (!hasKey) return DEFAULT_KEY;
|
|
112
|
+
const context = ctx ?? lastContext ?? {};
|
|
113
|
+
return normalizeKey(cache.key(context));
|
|
114
|
+
}
|
|
115
|
+
function _clearEntryStaleTimer(entry) {
|
|
116
|
+
if (entry.staleTimer != null) {
|
|
117
|
+
clearTimeout(entry.staleTimer);
|
|
118
|
+
entry.staleTimer = null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function _startEntryStaleTimer(entry) {
|
|
122
|
+
_clearEntryStaleTimer(entry);
|
|
123
|
+
if (cache?.staleTime != null && isFinite(cache.staleTime)) entry.staleTimer = setTimeout(() => {
|
|
124
|
+
if (!entry.stale) {
|
|
125
|
+
entry.stale = true;
|
|
126
|
+
if (entry === getActiveEntry()) {
|
|
127
|
+
currentStale = true;
|
|
128
|
+
staleNotify();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}, cache.staleTime);
|
|
132
|
+
}
|
|
54
133
|
function getReady() {
|
|
55
134
|
if (!_ready) _ready = createCondition(() => currentStatus === "ready", (notify$1) => statusSignal.subscribe(() => notify$1()));
|
|
56
135
|
return _ready;
|
|
@@ -67,34 +146,58 @@ function createResourceNode(id, name, loader, autoLoad = true) {
|
|
|
67
146
|
if (!_stale) _stale = createCondition(() => currentStale, (notify$1) => staleSignal.subscribe(() => notify$1()));
|
|
68
147
|
return _stale;
|
|
69
148
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
149
|
+
function executeLoad(context) {
|
|
150
|
+
const key = resolveKey(context);
|
|
151
|
+
_activeKey = key;
|
|
152
|
+
let entry = entries.get(key);
|
|
153
|
+
if (!entry) {
|
|
154
|
+
entry = createEntry();
|
|
155
|
+
entries.set(key, entry);
|
|
156
|
+
}
|
|
157
|
+
if (shouldDeduplicate && entry.inFlightPromise) return entry.inFlightPromise;
|
|
77
158
|
if (context !== void 0) lastContext = context;
|
|
78
159
|
const loadContext = context ?? lastContext ?? {};
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
160
|
+
entry.stale = false;
|
|
161
|
+
entry.status = "pending";
|
|
162
|
+
entry.value = void 0;
|
|
163
|
+
entry.error = void 0;
|
|
164
|
+
syncFromActiveEntry();
|
|
165
|
+
const promise = (async () => {
|
|
166
|
+
try {
|
|
167
|
+
const result = await Promise.resolve(loader(loadContext));
|
|
168
|
+
entry.value = result;
|
|
169
|
+
entry.status = "ready";
|
|
170
|
+
entry.stale = false;
|
|
171
|
+
if (entry === getActiveEntry()) syncFromEntry(entry);
|
|
172
|
+
staleNotify();
|
|
173
|
+
_startEntryStaleTimer(entry);
|
|
174
|
+
} catch (e) {
|
|
175
|
+
entry.error = e;
|
|
176
|
+
entry.status = "failed";
|
|
177
|
+
entry.stale = false;
|
|
178
|
+
if (entry === getActiveEntry()) syncFromEntry(entry);
|
|
179
|
+
staleNotify();
|
|
180
|
+
} finally {
|
|
181
|
+
entry.inFlightPromise = null;
|
|
182
|
+
}
|
|
183
|
+
})();
|
|
184
|
+
entry.inFlightPromise = promise;
|
|
185
|
+
return promise;
|
|
93
186
|
}
|
|
94
187
|
function invalidate() {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
188
|
+
const entry = getActiveEntry();
|
|
189
|
+
if (!entry.stale) {
|
|
190
|
+
entry.stale = true;
|
|
191
|
+
if (entry === getActiveEntry()) {
|
|
192
|
+
currentStale = true;
|
|
193
|
+
staleNotify();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function dispose() {
|
|
198
|
+
for (const entry of entries.values()) {
|
|
199
|
+
_clearEntryStaleTimer(entry);
|
|
200
|
+
entry.inFlightPromise = null;
|
|
98
201
|
}
|
|
99
202
|
}
|
|
100
203
|
const node = {
|
|
@@ -127,7 +230,8 @@ function createResourceNode(id, name, loader, autoLoad = true) {
|
|
|
127
230
|
invalidate,
|
|
128
231
|
subscribe(fn) {
|
|
129
232
|
return statusSignal.subscribe(fn);
|
|
130
|
-
}
|
|
233
|
+
},
|
|
234
|
+
dispose
|
|
131
235
|
};
|
|
132
236
|
return node;
|
|
133
237
|
}
|
|
@@ -683,6 +787,7 @@ function screen(name, fn) {
|
|
|
683
787
|
name: n,
|
|
684
788
|
autoLoad: config.autoLoad ?? true,
|
|
685
789
|
loader: config.load,
|
|
790
|
+
cache: config.cache,
|
|
686
791
|
ref
|
|
687
792
|
});
|
|
688
793
|
return ref;
|
|
@@ -907,7 +1012,7 @@ var ScreenRuntime = class {
|
|
|
907
1012
|
this._started = true;
|
|
908
1013
|
const nodeMap = /* @__PURE__ */ new Map();
|
|
909
1014
|
for (const config of this._screen.resourceConfigs) {
|
|
910
|
-
const node = createResourceNode(config.id, config.name, config.loader, false);
|
|
1015
|
+
const node = createResourceNode(config.id, config.name, config.loader, false, config.cache);
|
|
911
1016
|
this._resourceNodes.push(node);
|
|
912
1017
|
nodeMap.set(config.id, node);
|
|
913
1018
|
}
|
|
@@ -929,6 +1034,7 @@ var ScreenRuntime = class {
|
|
|
929
1034
|
this._disposed = true;
|
|
930
1035
|
for (const unsub of this._unsubscribers) unsub();
|
|
931
1036
|
this._unsubscribers = [];
|
|
1037
|
+
for (const node of this._resourceNodes) node.dispose();
|
|
932
1038
|
for (const config of this._screen.resourceConfigs) if (config.ref && this._resourceNodeMap) {
|
|
933
1039
|
const node = this._resourceNodeMap.get(config.id);
|
|
934
1040
|
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,24 @@ 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
|
+
deduplicate?: boolean;
|
|
29
|
+
};
|
|
23
30
|
export type ResourceConfig<TValue = unknown, TServices extends object = DefaultScreenServices> = {
|
|
24
31
|
id: string;
|
|
25
32
|
name: string;
|
|
26
33
|
autoLoad: boolean;
|
|
27
34
|
loader: ResourceLoader<TValue, TServices>;
|
|
35
|
+
cache?: ResourceCacheOptions<TServices>;
|
|
28
36
|
ref?: ResourceRef<TValue, TServices>;
|
|
29
37
|
};
|
|
30
38
|
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>;
|
|
39
|
+
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
40
|
export declare class ResourceRef<TValue, TServices extends object = DefaultScreenServices> {
|
|
33
41
|
readonly id: string;
|
|
34
42
|
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> = {
|