@ulearning/ai-generative-frame 0.0.0-stage → 1.0.0-next.0
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/LICENSE +21 -0
- package/README.md +233 -2
- package/dist/assistant-ui/entry.d.ts +34 -0
- package/dist/assistant-ui/entry.d.ts.map +1 -0
- package/dist/assistant-ui/entry.js +50 -0
- package/dist/assistant-ui/entry.js.map +1 -0
- package/dist/assistant-ui/extension.d.ts +18 -0
- package/dist/assistant-ui/extension.d.ts.map +1 -0
- package/dist/assistant-ui/extension.js +0 -0
- package/dist/assistant-ui/history.d.ts +15 -0
- package/dist/assistant-ui/history.d.ts.map +1 -0
- package/dist/assistant-ui/history.js +39 -0
- package/dist/assistant-ui/history.js.map +1 -0
- package/dist/assistant-ui/modelContext.d.ts +45 -0
- package/dist/assistant-ui/modelContext.d.ts.map +1 -0
- package/dist/assistant-ui/modelContext.js +88 -0
- package/dist/assistant-ui/modelContext.js.map +1 -0
- package/dist/assistant-ui/snapshotSaver.d.ts +11 -0
- package/dist/assistant-ui/snapshotSaver.d.ts.map +1 -0
- package/dist/assistant-ui/snapshotSaver.js +83 -0
- package/dist/assistant-ui/snapshotSaver.js.map +1 -0
- package/dist/assistant-ui/tool-calls.d.ts +21 -0
- package/dist/assistant-ui/tool-calls.d.ts.map +1 -0
- package/dist/assistant-ui/tool-calls.js +65 -0
- package/dist/assistant-ui/tool-calls.js.map +1 -0
- package/dist/assistant-ui/toolkit.d.ts +96 -0
- package/dist/assistant-ui/toolkit.d.ts.map +1 -0
- package/dist/assistant-ui/toolkit.js +456 -0
- package/dist/assistant-ui/toolkit.js.map +1 -0
- package/dist/assistant-ui/useSharedContext.d.ts +14 -0
- package/dist/assistant-ui/useSharedContext.d.ts.map +1 -0
- package/dist/assistant-ui/useSharedContext.js +27 -0
- package/dist/assistant-ui/useSharedContext.js.map +1 -0
- package/dist/assistant-ui.d.ts +3 -0
- package/dist/assistant-ui.d.ts.map +1 -0
- package/dist/assistant-ui.js +1 -0
- package/dist/bootstrap.d.ts +23 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +78 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/csp.d.ts +25 -0
- package/dist/csp.d.ts.map +1 -0
- package/dist/csp.js +78 -0
- package/dist/csp.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/json-schema.d.ts +70 -0
- package/dist/json-schema.d.ts.map +1 -0
- package/dist/json-schema.js +163 -0
- package/dist/json-schema.js.map +1 -0
- package/dist/preview.d.ts +30 -0
- package/dist/preview.d.ts.map +1 -0
- package/dist/preview.js +60 -0
- package/dist/preview.js.map +1 -0
- package/dist/prompts/guidance.d.ts +41 -0
- package/dist/prompts/guidance.d.ts.map +1 -0
- package/dist/prompts/guidance.js +268 -0
- package/dist/prompts/guidance.js.map +1 -0
- package/dist/prompts.d.ts +2 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +1 -0
- package/dist/protocol.d.ts +114 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +49 -0
- package/dist/protocol.js.map +1 -0
- package/dist/repair/repair.d.ts +72 -0
- package/dist/repair/repair.d.ts.map +1 -0
- package/dist/repair/repair.js +103 -0
- package/dist/repair/repair.js.map +1 -0
- package/dist/repair.d.ts +2 -0
- package/dist/repair.d.ts.map +1 -0
- package/dist/repair.js +1 -0
- package/dist/rpc.d.ts +60 -0
- package/dist/rpc.d.ts.map +1 -0
- package/dist/rpc.js +127 -0
- package/dist/rpc.js.map +1 -0
- package/dist/runtime/diagnostics.d.ts +17 -0
- package/dist/runtime/diagnostics.d.ts.map +1 -0
- package/dist/runtime/diagnostics.js +128 -0
- package/dist/runtime/diagnostics.js.map +1 -0
- package/dist/runtime/generated.d.ts +2 -0
- package/dist/runtime/generated.d.ts.map +1 -0
- package/dist/runtime/generated.js +5 -0
- package/dist/runtime/generated.js.map +1 -0
- package/dist/runtime/index.d.ts +7 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +264 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/morph.d.ts +51 -0
- package/dist/runtime/morph.d.ts.map +1 -0
- package/dist/runtime/morph.js +251 -0
- package/dist/runtime/morph.js.map +1 -0
- package/dist/runtime/screenshot.d.ts +23 -0
- package/dist/runtime/screenshot.d.ts.map +1 -0
- package/dist/runtime/screenshot.js +188 -0
- package/dist/runtime/screenshot.js.map +1 -0
- package/dist/runtime/size.d.ts +10 -0
- package/dist/runtime/size.d.ts.map +1 -0
- package/dist/runtime/size.js +21 -0
- package/dist/runtime/size.js.map +1 -0
- package/dist/runtime/storage.d.ts +4 -0
- package/dist/runtime/storage.d.ts.map +1 -0
- package/dist/runtime/storage.js +38 -0
- package/dist/runtime/storage.js.map +1 -0
- package/dist/spec/actions.d.ts +40 -0
- package/dist/spec/actions.d.ts.map +1 -0
- package/dist/spec/actions.js +53 -0
- package/dist/spec/actions.js.map +1 -0
- package/dist/spec/assistant-ui/thread.d.ts +66 -0
- package/dist/spec/assistant-ui/thread.d.ts.map +1 -0
- package/dist/spec/assistant-ui/thread.js +252 -0
- package/dist/spec/assistant-ui/thread.js.map +1 -0
- package/dist/spec/assistant-ui/toolkit.d.ts +33 -0
- package/dist/spec/assistant-ui/toolkit.d.ts.map +1 -0
- package/dist/spec/assistant-ui/toolkit.js +142 -0
- package/dist/spec/assistant-ui/toolkit.js.map +1 -0
- package/dist/spec/assistant-ui/web.d.ts +6 -0
- package/dist/spec/assistant-ui/web.d.ts.map +1 -0
- package/dist/spec/assistant-ui/web.js +48 -0
- package/dist/spec/assistant-ui/web.js.map +1 -0
- package/dist/spec/assistant-ui.d.ts +3 -0
- package/dist/spec/assistant-ui.d.ts.map +1 -0
- package/dist/spec/assistant-ui.js +1 -0
- package/dist/spec/calc.d.ts +8 -0
- package/dist/spec/calc.d.ts.map +1 -0
- package/dist/spec/calc.js +498 -0
- package/dist/spec/calc.js.map +1 -0
- package/dist/spec/catalog.d.ts +52 -0
- package/dist/spec/catalog.d.ts.map +1 -0
- package/dist/spec/catalog.js +104 -0
- package/dist/spec/catalog.js.map +1 -0
- package/dist/spec/expressions.d.ts +45 -0
- package/dist/spec/expressions.d.ts.map +1 -0
- package/dist/spec/expressions.js +199 -0
- package/dist/spec/expressions.js.map +1 -0
- package/dist/spec/patch.d.ts +20 -0
- package/dist/spec/patch.d.ts.map +1 -0
- package/dist/spec/patch.js +77 -0
- package/dist/spec/patch.js.map +1 -0
- package/dist/spec/pending.d.ts +15 -0
- package/dist/spec/pending.d.ts.map +1 -0
- package/dist/spec/pending.js +33 -0
- package/dist/spec/pending.js.map +1 -0
- package/dist/spec/pointer.d.ts +17 -0
- package/dist/spec/pointer.d.ts.map +1 -0
- package/dist/spec/pointer.js +88 -0
- package/dist/spec/pointer.js.map +1 -0
- package/dist/spec/prompt.d.ts +29 -0
- package/dist/spec/prompt.d.ts.map +1 -0
- package/dist/spec/prompt.js +145 -0
- package/dist/spec/prompt.js.map +1 -0
- package/dist/spec/render-spec.d.ts +50 -0
- package/dist/spec/render-spec.d.ts.map +1 -0
- package/dist/spec/render-spec.js +104 -0
- package/dist/spec/render-spec.js.map +1 -0
- package/dist/spec/resolve.d.ts +39 -0
- package/dist/spec/resolve.d.ts.map +1 -0
- package/dist/spec/resolve.js +51 -0
- package/dist/spec/resolve.js.map +1 -0
- package/dist/spec/resolved.d.ts +3 -0
- package/dist/spec/resolved.d.ts.map +1 -0
- package/dist/spec/resolved.js +36 -0
- package/dist/spec/resolved.js.map +1 -0
- package/dist/spec/state.d.ts +34 -0
- package/dist/spec/state.d.ts.map +1 -0
- package/dist/spec/state.js +111 -0
- package/dist/spec/state.js.map +1 -0
- package/dist/spec/stream.d.ts +50 -0
- package/dist/spec/stream.d.ts.map +1 -0
- package/dist/spec/stream.js +168 -0
- package/dist/spec/stream.js.map +1 -0
- package/dist/spec/tools.d.ts +3 -0
- package/dist/spec/tools.d.ts.map +1 -0
- package/dist/spec/tools.js +3 -0
- package/dist/spec/tree.d.ts +21 -0
- package/dist/spec/tree.d.ts.map +1 -0
- package/dist/spec/tree.js +27 -0
- package/dist/spec/tree.js.map +1 -0
- package/dist/spec/types.d.ts +66 -0
- package/dist/spec/types.d.ts.map +1 -0
- package/dist/spec/types.js +9 -0
- package/dist/spec/types.js.map +1 -0
- package/dist/spec/validate.d.ts +32 -0
- package/dist/spec/validate.d.ts.map +1 -0
- package/dist/spec/validate.js +264 -0
- package/dist/spec/validate.js.map +1 -0
- package/dist/spec/vue/SpecRenderer.d.ts +43 -0
- package/dist/spec/vue/SpecRenderer.d.ts.map +1 -0
- package/dist/spec/vue/SpecRenderer.js +357 -0
- package/dist/spec/vue/SpecRenderer.js.map +1 -0
- package/dist/spec/vue/useSpecStream.d.ts +19 -0
- package/dist/spec/vue/useSpecStream.d.ts.map +1 -0
- package/dist/spec/vue/useSpecStream.js +115 -0
- package/dist/spec/vue/useSpecStream.js.map +1 -0
- package/dist/spec/vue.d.ts +3 -0
- package/dist/spec/vue.d.ts.map +1 -0
- package/dist/spec/vue.js +3 -0
- package/dist/spec.d.ts +12 -0
- package/dist/spec.d.ts.map +1 -0
- package/dist/spec.js +8 -0
- package/dist/theme.d.ts +38 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +370 -0
- package/dist/theme.js.map +1 -0
- package/dist/tools/define.d.ts +43 -0
- package/dist/tools/define.d.ts.map +1 -0
- package/dist/tools/define.js +31 -0
- package/dist/tools/define.js.map +1 -0
- package/dist/tools/edits.d.ts +19 -0
- package/dist/tools/edits.d.ts.map +1 -0
- package/dist/tools/edits.js +55 -0
- package/dist/tools/edits.js.map +1 -0
- package/dist/tools/registry.d.ts +19 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +33 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/tools.d.ts +88 -0
- package/dist/tools/tools.d.ts.map +1 -0
- package/dist/tools/tools.js +235 -0
- package/dist/tools/tools.js.map +1 -0
- package/dist/tools.d.ts +5 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +5 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/dist/vue/Widget.d.ts +16 -0
- package/dist/vue/Widget.d.ts.map +1 -0
- package/dist/vue/Widget.js +58 -0
- package/dist/vue/Widget.js.map +1 -0
- package/dist/vue/sync.d.ts +19 -0
- package/dist/vue/sync.d.ts.map +1 -0
- package/dist/vue/sync.js +31 -0
- package/dist/vue/sync.js.map +1 -0
- package/dist/vue/themeStore.d.ts +23 -0
- package/dist/vue/themeStore.d.ts.map +1 -0
- package/dist/vue/themeStore.js +115 -0
- package/dist/vue/themeStore.js.map +1 -0
- package/dist/vue/useThemeTokens.d.ts +6 -0
- package/dist/vue/useThemeTokens.d.ts.map +1 -0
- package/dist/vue/useThemeTokens.js +49 -0
- package/dist/vue/useThemeTokens.js.map +1 -0
- package/dist/vue/useWidget.d.ts +10 -0
- package/dist/vue/useWidget.d.ts.map +1 -0
- package/dist/vue/useWidget.js +81 -0
- package/dist/vue/useWidget.js.map +1 -0
- package/dist/vue.d.ts +5 -0
- package/dist/vue.d.ts.map +1 -0
- package/dist/vue.js +4 -0
- package/dist/widget.d.ts +129 -0
- package/dist/widget.d.ts.map +1 -0
- package/dist/widget.js +655 -0
- package/dist/widget.js.map +1 -0
- package/package.json +125 -3
- package/src/assistant-ui/entry.ts +124 -0
- package/src/assistant-ui/extension.ts +19 -0
- package/src/assistant-ui/history.ts +63 -0
- package/src/assistant-ui/modelContext.ts +162 -0
- package/src/assistant-ui/snapshotSaver.ts +116 -0
- package/src/assistant-ui/tool-calls.ts +115 -0
- package/src/assistant-ui/toolkit.tsx +747 -0
- package/src/assistant-ui/useSharedContext.ts +40 -0
- package/src/assistant-ui.ts +14 -0
- package/src/bootstrap.ts +112 -0
- package/src/csp.ts +96 -0
- package/src/index.ts +44 -0
- package/src/json-schema.ts +353 -0
- package/src/preview.ts +120 -0
- package/src/prompts/guidance.ts +369 -0
- package/src/prompts.ts +8 -0
- package/src/protocol.ts +141 -0
- package/src/repair/repair.ts +202 -0
- package/src/repair.ts +11 -0
- package/src/rpc.ts +228 -0
- package/src/runtime/diagnostics.ts +165 -0
- package/src/runtime/generated.ts +2 -0
- package/src/runtime/index.ts +343 -0
- package/src/runtime/morph.ts +352 -0
- package/src/runtime/screenshot.ts +288 -0
- package/src/runtime/size.ts +17 -0
- package/src/runtime/storage.ts +49 -0
- package/src/spec/actions.ts +111 -0
- package/src/spec/assistant-ui/thread.ts +381 -0
- package/src/spec/assistant-ui/toolkit.tsx +295 -0
- package/src/spec/assistant-ui/web.tsx +63 -0
- package/src/spec/assistant-ui.ts +2 -0
- package/src/spec/calc.ts +598 -0
- package/src/spec/catalog.ts +196 -0
- package/src/spec/expressions.ts +301 -0
- package/src/spec/patch.ts +112 -0
- package/src/spec/pending.ts +36 -0
- package/src/spec/pointer.ts +170 -0
- package/src/spec/prompt.ts +243 -0
- package/src/spec/render-spec.ts +144 -0
- package/src/spec/resolve.ts +123 -0
- package/src/spec/resolved.ts +53 -0
- package/src/spec/state.ts +193 -0
- package/src/spec/stream.ts +230 -0
- package/src/spec/tools.ts +17 -0
- package/src/spec/tree.ts +37 -0
- package/src/spec/types.ts +51 -0
- package/src/spec/validate.ts +429 -0
- package/src/spec/vue/SpecRenderer.tsx +544 -0
- package/src/spec/vue/__tests__/harness.ts +50 -0
- package/src/spec/vue/useSpecStream.ts +162 -0
- package/src/spec/vue.ts +13 -0
- package/src/spec.ts +55 -0
- package/src/theme.ts +423 -0
- package/src/tools/define.ts +65 -0
- package/src/tools/edits.ts +73 -0
- package/src/tools/registry.ts +44 -0
- package/src/tools/tools.ts +361 -0
- package/src/tools.ts +32 -0
- package/src/version.ts +4 -0
- package/src/vue/Widget.tsx +74 -0
- package/src/vue/sync.ts +36 -0
- package/src/vue/themeStore.ts +157 -0
- package/src/vue/useThemeTokens.ts +71 -0
- package/src/vue/useWidget.ts +117 -0
- package/src/vue.ts +8 -0
- package/src/widget.ts +1080 -0
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
import { getAtPointer, parsePointer } from "./pointer.js";
|
|
2
|
+
// src/spec/calc.ts
|
|
3
|
+
const isNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
4
|
+
const normalize = (value) => value === void 0 || typeof value === "number" && !Number.isFinite(value) ? null : value;
|
|
5
|
+
const pointerTokens = (path) => {
|
|
6
|
+
try {
|
|
7
|
+
return parsePointer(path && !path.startsWith("/") ? `/${path}` : path);
|
|
8
|
+
} catch {
|
|
9
|
+
return [];
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
const readPath = (base, path) => getAtPointer(base, pointerTokens(path));
|
|
13
|
+
const readReference = (path, ctx) => {
|
|
14
|
+
if (path === "$index") return ctx.index;
|
|
15
|
+
if (path === "$item") return ctx.item;
|
|
16
|
+
if (path.startsWith("$item/")) return readPath(ctx.item, path.slice(5));
|
|
17
|
+
if (path === "$event") return ctx.event;
|
|
18
|
+
if (path.startsWith("$event/")) return readPath(ctx.event, path.slice(6));
|
|
19
|
+
return readPath(ctx.state, path);
|
|
20
|
+
};
|
|
21
|
+
const eachChild = (value, visit) => {
|
|
22
|
+
if (Array.isArray(value)) {
|
|
23
|
+
for (let index = 0; index < value.length; index++) if (Object.hasOwn(value, index) && !visit(value[index], String(index))) return false;
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
for (const key in value) if (Object.hasOwn(value, key) && !visit(value[key], key)) return false;
|
|
27
|
+
return true;
|
|
28
|
+
};
|
|
29
|
+
const visitAll = (value, step) => {
|
|
30
|
+
step();
|
|
31
|
+
if (typeof value === "object" && value !== null) eachChild(value, (child) => {
|
|
32
|
+
visitAll(child, step);
|
|
33
|
+
return true;
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
const formatText = (value, step) => {
|
|
37
|
+
if (value === null || value === void 0) return "";
|
|
38
|
+
if (typeof value !== "object") return String(value);
|
|
39
|
+
visitAll(value, step);
|
|
40
|
+
return JSON.stringify(value);
|
|
41
|
+
};
|
|
42
|
+
const fractionDigits = (value) => Math.max(0, Math.min(20, Math.trunc(value)));
|
|
43
|
+
const numeric = (fn) => (args) => args.every(isNumber) ? fn(...args) : null;
|
|
44
|
+
const aggregate = (operation, args, step) => {
|
|
45
|
+
const [list, field] = args;
|
|
46
|
+
if (!Array.isArray(list) || args.length > 2) return null;
|
|
47
|
+
if (args.length === 2 && typeof field !== "string") return null;
|
|
48
|
+
if (typeof field === "string" && field.length > 500) return null;
|
|
49
|
+
const tokens = typeof field === "string" ? pointerTokens(field) : void 0;
|
|
50
|
+
if (tokens && tokens.length > 32) return null;
|
|
51
|
+
let total = 0;
|
|
52
|
+
let count = 0;
|
|
53
|
+
let extreme = null;
|
|
54
|
+
for (const item of list) {
|
|
55
|
+
step();
|
|
56
|
+
const value = tokens ? getAtPointer(item, tokens) : item;
|
|
57
|
+
if (!isNumber(value)) continue;
|
|
58
|
+
total += value;
|
|
59
|
+
count++;
|
|
60
|
+
if (extreme === null) extreme = value;
|
|
61
|
+
else if (operation === "min") extreme = Math.min(extreme, value);
|
|
62
|
+
else if (operation === "max") extreme = Math.max(extreme, value);
|
|
63
|
+
}
|
|
64
|
+
if (operation === "sum") return total;
|
|
65
|
+
if (operation === "avg") return count ? total / count : null;
|
|
66
|
+
return extreme;
|
|
67
|
+
};
|
|
68
|
+
const formatters = /* @__PURE__ */ new Map();
|
|
69
|
+
const formatNumber = (value, options) => {
|
|
70
|
+
if (!isNumber(value)) return null;
|
|
71
|
+
const key = JSON.stringify(options);
|
|
72
|
+
let formatter = formatters.get(key);
|
|
73
|
+
if (!formatter) {
|
|
74
|
+
try {
|
|
75
|
+
formatter = new Intl.NumberFormat("en-US", options);
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
if (formatters.size >= 64) formatters.clear();
|
|
80
|
+
formatters.set(key, formatter);
|
|
81
|
+
}
|
|
82
|
+
return formatter.format(value);
|
|
83
|
+
};
|
|
84
|
+
const decimalFormat = (args, style) => {
|
|
85
|
+
const [value, digits] = args;
|
|
86
|
+
if (args.length === 1) return formatNumber(value, { style });
|
|
87
|
+
if (!isNumber(digits)) return null;
|
|
88
|
+
const precision = fractionDigits(digits);
|
|
89
|
+
return formatNumber(value, {
|
|
90
|
+
style,
|
|
91
|
+
minimumFractionDigits: precision,
|
|
92
|
+
maximumFractionDigits: precision
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
const functions = new Map([
|
|
96
|
+
["round", {
|
|
97
|
+
min: 1,
|
|
98
|
+
max: 2,
|
|
99
|
+
run: numeric((x, digits = 0) => {
|
|
100
|
+
const scale = 10 ** fractionDigits(digits);
|
|
101
|
+
return Math.round(x * scale) / scale;
|
|
102
|
+
})
|
|
103
|
+
}],
|
|
104
|
+
["floor", {
|
|
105
|
+
min: 1,
|
|
106
|
+
max: 1,
|
|
107
|
+
run: numeric(Math.floor)
|
|
108
|
+
}],
|
|
109
|
+
["ceil", {
|
|
110
|
+
min: 1,
|
|
111
|
+
max: 1,
|
|
112
|
+
run: numeric(Math.ceil)
|
|
113
|
+
}],
|
|
114
|
+
["abs", {
|
|
115
|
+
min: 1,
|
|
116
|
+
max: 1,
|
|
117
|
+
run: numeric(Math.abs)
|
|
118
|
+
}],
|
|
119
|
+
["clamp", {
|
|
120
|
+
min: 3,
|
|
121
|
+
max: 3,
|
|
122
|
+
run: numeric((x, low, high) => Math.min(Math.max(x, low), high))
|
|
123
|
+
}],
|
|
124
|
+
...["min", "max"].map((name) => [name, {
|
|
125
|
+
min: 1,
|
|
126
|
+
max: Infinity,
|
|
127
|
+
run: (args, step) => Array.isArray(args[0]) ? aggregate(name, args, step) : numeric(name === "min" ? Math.min : Math.max)(args, step)
|
|
128
|
+
}]),
|
|
129
|
+
["count", {
|
|
130
|
+
min: 1,
|
|
131
|
+
max: 1,
|
|
132
|
+
run: ([list]) => Array.isArray(list) ? list.length : null
|
|
133
|
+
}],
|
|
134
|
+
...["sum", "avg"].map((name) => [name, {
|
|
135
|
+
min: 1,
|
|
136
|
+
max: 2,
|
|
137
|
+
run: (args, step) => aggregate(name, args, step)
|
|
138
|
+
}]),
|
|
139
|
+
["number", {
|
|
140
|
+
min: 1,
|
|
141
|
+
max: 2,
|
|
142
|
+
run: (args) => decimalFormat(args, "decimal")
|
|
143
|
+
}],
|
|
144
|
+
["currency", {
|
|
145
|
+
min: 2,
|
|
146
|
+
max: 2,
|
|
147
|
+
run: ([value, code]) => typeof code === "string" ? formatNumber(value, {
|
|
148
|
+
style: "currency",
|
|
149
|
+
currency: code
|
|
150
|
+
}) : null
|
|
151
|
+
}],
|
|
152
|
+
["percent", {
|
|
153
|
+
min: 1,
|
|
154
|
+
max: 2,
|
|
155
|
+
run: (args) => decimalFormat(args, "percent")
|
|
156
|
+
}],
|
|
157
|
+
["time", {
|
|
158
|
+
min: 1,
|
|
159
|
+
max: 1,
|
|
160
|
+
run: ([value]) => {
|
|
161
|
+
if (!isNumber(value)) return null;
|
|
162
|
+
const minutes = (Math.round(value) % 1440 + 1440) % 1440;
|
|
163
|
+
const hour = Math.floor(minutes / 60);
|
|
164
|
+
return `${hour % 12 || 12}:${String(minutes % 60).padStart(2, "0")} ${hour < 12 ? "AM" : "PM"}`;
|
|
165
|
+
}
|
|
166
|
+
}],
|
|
167
|
+
["plural", {
|
|
168
|
+
min: 3,
|
|
169
|
+
max: 3,
|
|
170
|
+
run: ([n, one, other]) => n === 1 ? one : other
|
|
171
|
+
}]
|
|
172
|
+
]);
|
|
173
|
+
const tokenize = (formula) => {
|
|
174
|
+
const tokens = [];
|
|
175
|
+
let offset = 0;
|
|
176
|
+
while (offset < formula.length) {
|
|
177
|
+
const char = formula[offset];
|
|
178
|
+
if (/\s/.test(char)) offset++;
|
|
179
|
+
else if (char === "'" || char === "\"") {
|
|
180
|
+
offset++;
|
|
181
|
+
let value = "";
|
|
182
|
+
while (offset < formula.length && formula[offset] !== char) {
|
|
183
|
+
let next = formula[offset++];
|
|
184
|
+
if (next === "\\") {
|
|
185
|
+
next = formula[offset++] ?? "";
|
|
186
|
+
if (next !== "\\" && next !== "'" && next !== "\"") throw new Error("Invalid string escape");
|
|
187
|
+
}
|
|
188
|
+
value += next;
|
|
189
|
+
}
|
|
190
|
+
if (offset >= formula.length) throw new Error("Unterminated string");
|
|
191
|
+
offset++;
|
|
192
|
+
tokens.push({
|
|
193
|
+
kind: "literal",
|
|
194
|
+
value
|
|
195
|
+
});
|
|
196
|
+
} else if (char === "$" && formula[offset + 1] === "{") {
|
|
197
|
+
const end = formula.indexOf("}", offset + 2);
|
|
198
|
+
if (end === -1) throw new Error("Unterminated reference");
|
|
199
|
+
tokens.push({
|
|
200
|
+
kind: "reference",
|
|
201
|
+
text: formula.slice(offset + 2, end).trim()
|
|
202
|
+
});
|
|
203
|
+
offset = end + 1;
|
|
204
|
+
} else if (/[0-9]/.test(char)) {
|
|
205
|
+
const start = offset++;
|
|
206
|
+
while (/[0-9]/.test(formula[offset] ?? "")) offset++;
|
|
207
|
+
if (formula[offset] === "." && /[0-9]/.test(formula[offset + 1] ?? "")) {
|
|
208
|
+
offset++;
|
|
209
|
+
while (/[0-9]/.test(formula[offset] ?? "")) offset++;
|
|
210
|
+
}
|
|
211
|
+
tokens.push({
|
|
212
|
+
kind: "literal",
|
|
213
|
+
value: Number(formula.slice(start, offset))
|
|
214
|
+
});
|
|
215
|
+
} else if (/[a-zA-Z_]/.test(char)) {
|
|
216
|
+
const start = offset++;
|
|
217
|
+
while (/[a-zA-Z0-9_]/.test(formula[offset] ?? "")) offset++;
|
|
218
|
+
const name = formula.slice(start, offset);
|
|
219
|
+
if (name === "true" || name === "false" || name === "null") tokens.push({
|
|
220
|
+
kind: "literal",
|
|
221
|
+
value: name === "null" ? null : name === "true"
|
|
222
|
+
});
|
|
223
|
+
else tokens.push({
|
|
224
|
+
kind: "name",
|
|
225
|
+
text: name
|
|
226
|
+
});
|
|
227
|
+
} else {
|
|
228
|
+
const pair = formula.slice(offset, offset + 2);
|
|
229
|
+
if ([
|
|
230
|
+
"||",
|
|
231
|
+
"&&",
|
|
232
|
+
"==",
|
|
233
|
+
"!=",
|
|
234
|
+
"<=",
|
|
235
|
+
">="
|
|
236
|
+
].includes(pair)) {
|
|
237
|
+
tokens.push({
|
|
238
|
+
kind: "symbol",
|
|
239
|
+
text: pair
|
|
240
|
+
});
|
|
241
|
+
offset += 2;
|
|
242
|
+
} else if ("?:<>+-*/%!(),".includes(char)) {
|
|
243
|
+
tokens.push({
|
|
244
|
+
kind: "symbol",
|
|
245
|
+
text: char
|
|
246
|
+
});
|
|
247
|
+
offset++;
|
|
248
|
+
} else throw new Error(`Unexpected token "${char}"`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
tokens.push({ kind: "end" });
|
|
252
|
+
return tokens;
|
|
253
|
+
};
|
|
254
|
+
const precedence = [
|
|
255
|
+
["||"],
|
|
256
|
+
["&&"],
|
|
257
|
+
["==", "!="],
|
|
258
|
+
[
|
|
259
|
+
"<",
|
|
260
|
+
"<=",
|
|
261
|
+
">",
|
|
262
|
+
">="
|
|
263
|
+
],
|
|
264
|
+
["+", "-"],
|
|
265
|
+
[
|
|
266
|
+
"*",
|
|
267
|
+
"/",
|
|
268
|
+
"%"
|
|
269
|
+
]
|
|
270
|
+
];
|
|
271
|
+
const parse = (formula) => {
|
|
272
|
+
const tokens = tokenize(formula);
|
|
273
|
+
let position = 0;
|
|
274
|
+
const current = () => tokens[position];
|
|
275
|
+
const match = (symbol) => {
|
|
276
|
+
const token = current();
|
|
277
|
+
if (token.kind !== "symbol" || token.text !== symbol) return false;
|
|
278
|
+
position++;
|
|
279
|
+
return true;
|
|
280
|
+
};
|
|
281
|
+
const expect = (symbol) => {
|
|
282
|
+
if (!match(symbol)) throw new Error(`Expected "${symbol}"`);
|
|
283
|
+
};
|
|
284
|
+
const checkDepth = (depth) => {
|
|
285
|
+
if (depth > 32) throw new Error("Formula exceeds 32 levels of nesting");
|
|
286
|
+
};
|
|
287
|
+
const primary = (depth) => {
|
|
288
|
+
checkDepth(depth);
|
|
289
|
+
const token = current();
|
|
290
|
+
position++;
|
|
291
|
+
if (token.kind === "literal") return token;
|
|
292
|
+
if (token.kind === "reference") return {
|
|
293
|
+
kind: "reference",
|
|
294
|
+
path: token.text
|
|
295
|
+
};
|
|
296
|
+
if (token.kind === "symbol" && token.text === "(") {
|
|
297
|
+
const node = conditional(depth + 1);
|
|
298
|
+
expect(")");
|
|
299
|
+
return node;
|
|
300
|
+
}
|
|
301
|
+
if (token.kind === "name") {
|
|
302
|
+
const fn = functions.get(token.text);
|
|
303
|
+
if (!fn) throw new Error(`Unknown function "${token.text}"; known: ${[...functions.keys()].join(", ")}`);
|
|
304
|
+
checkDepth(depth + 1);
|
|
305
|
+
expect("(");
|
|
306
|
+
const args = [];
|
|
307
|
+
if (!match(")")) {
|
|
308
|
+
do
|
|
309
|
+
args.push(conditional(depth + 1));
|
|
310
|
+
while (match(","));
|
|
311
|
+
expect(")");
|
|
312
|
+
}
|
|
313
|
+
if (args.length < fn.min || args.length > fn.max) throw new Error(`Wrong argument count for "${token.text}"`);
|
|
314
|
+
return {
|
|
315
|
+
kind: "call",
|
|
316
|
+
name: token.text,
|
|
317
|
+
args
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
throw new Error("Expected a value");
|
|
321
|
+
};
|
|
322
|
+
const unary = (depth) => {
|
|
323
|
+
checkDepth(depth);
|
|
324
|
+
const token = current();
|
|
325
|
+
if (token.kind === "symbol" && (token.text === "-" || token.text === "!")) {
|
|
326
|
+
position++;
|
|
327
|
+
return {
|
|
328
|
+
kind: "unary",
|
|
329
|
+
operator: token.text,
|
|
330
|
+
operand: unary(depth + 1)
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
return primary(depth);
|
|
334
|
+
};
|
|
335
|
+
const binary = (level, depth) => {
|
|
336
|
+
const operators = precedence[level];
|
|
337
|
+
if (!operators) return unary(depth);
|
|
338
|
+
let left = binary(level + 1, depth);
|
|
339
|
+
for (;;) {
|
|
340
|
+
const token = current();
|
|
341
|
+
if (token.kind !== "symbol" || !operators.includes(token.text)) return left;
|
|
342
|
+
position++;
|
|
343
|
+
left = {
|
|
344
|
+
kind: "binary",
|
|
345
|
+
operator: token.text,
|
|
346
|
+
left,
|
|
347
|
+
right: binary(level + 1, depth)
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
const conditional = (depth) => {
|
|
352
|
+
const condition = binary(0, depth);
|
|
353
|
+
if (!match("?")) return condition;
|
|
354
|
+
const then = conditional(depth + 1);
|
|
355
|
+
expect(":");
|
|
356
|
+
return {
|
|
357
|
+
kind: "conditional",
|
|
358
|
+
condition,
|
|
359
|
+
then,
|
|
360
|
+
otherwise: conditional(depth + 1)
|
|
361
|
+
};
|
|
362
|
+
};
|
|
363
|
+
const node = conditional(0);
|
|
364
|
+
if (current().kind !== "end") throw new Error("Unexpected trailing token");
|
|
365
|
+
return node;
|
|
366
|
+
};
|
|
367
|
+
const parsedFormulas = /* @__PURE__ */ new Map();
|
|
368
|
+
const parsedFormula = (formula) => {
|
|
369
|
+
if (formula.length > 500) return "Formula exceeds 500 characters";
|
|
370
|
+
const cached = parsedFormulas.get(formula);
|
|
371
|
+
if (cached !== void 0) return cached;
|
|
372
|
+
let result;
|
|
373
|
+
try {
|
|
374
|
+
result = parse(formula);
|
|
375
|
+
} catch (error) {
|
|
376
|
+
result = error instanceof Error ? error.message : "Invalid formula";
|
|
377
|
+
}
|
|
378
|
+
if (parsedFormulas.size >= 500) parsedFormulas.clear();
|
|
379
|
+
parsedFormulas.set(formula, result);
|
|
380
|
+
return result;
|
|
381
|
+
};
|
|
382
|
+
const childCount = (value, step) => {
|
|
383
|
+
let count = 0;
|
|
384
|
+
eachChild(value, () => {
|
|
385
|
+
step();
|
|
386
|
+
count++;
|
|
387
|
+
return true;
|
|
388
|
+
});
|
|
389
|
+
return count;
|
|
390
|
+
};
|
|
391
|
+
const equal = (a, b, step) => {
|
|
392
|
+
step();
|
|
393
|
+
if (a === b) return true;
|
|
394
|
+
if (typeof a !== "object" || typeof b !== "object" || !a || !b) return false;
|
|
395
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
396
|
+
if (childCount(a, step) !== childCount(b, step)) return false;
|
|
397
|
+
return eachChild(a, (child, key) => equal(child, b[key], step));
|
|
398
|
+
};
|
|
399
|
+
const calculate = (operator, left, right, step) => {
|
|
400
|
+
if (operator === "==") return equal(left, right, step);
|
|
401
|
+
if (operator === "!=") return !equal(left, right, step);
|
|
402
|
+
if (operator === "+" && (typeof left === "string" || typeof right === "string")) return formatText(left, step) + formatText(right, step);
|
|
403
|
+
if ([
|
|
404
|
+
"<",
|
|
405
|
+
"<=",
|
|
406
|
+
">",
|
|
407
|
+
">="
|
|
408
|
+
].includes(operator)) {
|
|
409
|
+
if (!(typeof left === "number" && typeof right === "number") && !(typeof left === "string" && typeof right === "string")) return false;
|
|
410
|
+
if (operator === "<") return left < right;
|
|
411
|
+
if (operator === "<=") return left <= right;
|
|
412
|
+
if (operator === ">") return left > right;
|
|
413
|
+
return left >= right;
|
|
414
|
+
}
|
|
415
|
+
if (!isNumber(left) || !isNumber(right)) return null;
|
|
416
|
+
switch (operator) {
|
|
417
|
+
case "+": return left + right;
|
|
418
|
+
case "-": return left - right;
|
|
419
|
+
case "*": return left * right;
|
|
420
|
+
case "/": return right === 0 ? null : left / right;
|
|
421
|
+
case "%": return right === 0 ? null : left % right;
|
|
422
|
+
default: return null;
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
const evaluate = (node, ctx, step) => {
|
|
426
|
+
step();
|
|
427
|
+
const visit = (child) => normalize(evaluate(child, ctx, step));
|
|
428
|
+
switch (node.kind) {
|
|
429
|
+
case "literal": return node.value;
|
|
430
|
+
case "reference": return readReference(node.path, ctx);
|
|
431
|
+
case "unary": {
|
|
432
|
+
const value = visit(node.operand);
|
|
433
|
+
return node.operator === "!" ? !value : isNumber(value) ? -value : null;
|
|
434
|
+
}
|
|
435
|
+
case "conditional": return visit(node.condition) ? visit(node.then) : visit(node.otherwise);
|
|
436
|
+
case "call": return functions.get(node.name).run(node.args.map(visit), step);
|
|
437
|
+
case "binary": {
|
|
438
|
+
const left = visit(node.left);
|
|
439
|
+
if (node.operator === "&&") return Boolean(left) && Boolean(visit(node.right));
|
|
440
|
+
if (node.operator === "||") return Boolean(left) || Boolean(visit(node.right));
|
|
441
|
+
return calculate(node.operator, left, visit(node.right), step);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
/** Evaluates a bounded formula, returning null when it cannot run. */
|
|
446
|
+
function evaluateFormula(formula, ctx) {
|
|
447
|
+
try {
|
|
448
|
+
const node = parsedFormula(formula);
|
|
449
|
+
if (typeof node === "string") return null;
|
|
450
|
+
let steps = 0;
|
|
451
|
+
return normalize(evaluate(node, ctx, () => {
|
|
452
|
+
if (++steps > 1e4) throw new Error("Formula exceeds 10000 evaluation steps");
|
|
453
|
+
}));
|
|
454
|
+
} catch {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
/** The state paths a formula shows as values, leaving out reads under a condition and reads in the `?` branch of a condition on that same path, which only runs while it has a value. */
|
|
459
|
+
function formulaValueReads(formula) {
|
|
460
|
+
const node = parsedFormula(formula);
|
|
461
|
+
if (typeof node === "string") return [];
|
|
462
|
+
const reads = /* @__PURE__ */ new Set();
|
|
463
|
+
const visit = (current, condition, guarded) => {
|
|
464
|
+
switch (current.kind) {
|
|
465
|
+
case "literal": return;
|
|
466
|
+
case "reference":
|
|
467
|
+
if (!condition && !current.path.startsWith("$") && !guarded.has(current.path)) reads.add(current.path);
|
|
468
|
+
return;
|
|
469
|
+
case "unary":
|
|
470
|
+
visit(current.operand, condition || current.operator === "!", guarded);
|
|
471
|
+
return;
|
|
472
|
+
case "binary": {
|
|
473
|
+
const logical = current.operator === "&&" || current.operator === "||";
|
|
474
|
+
visit(current.left, condition || logical, guarded);
|
|
475
|
+
visit(current.right, condition || logical, guarded);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
case "conditional":
|
|
479
|
+
visit(current.condition, true, guarded);
|
|
480
|
+
visit(current.then, condition, current.condition.kind === "reference" ? /* @__PURE__ */ new Set([...guarded, current.condition.path]) : guarded);
|
|
481
|
+
visit(current.otherwise, condition, guarded);
|
|
482
|
+
return;
|
|
483
|
+
case "call":
|
|
484
|
+
for (const arg of current.args) visit(arg, condition, guarded);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
visit(node, false, /* @__PURE__ */ new Set());
|
|
489
|
+
return [...reads];
|
|
490
|
+
}
|
|
491
|
+
/** Why a formula cannot run, or undefined when it parses within the bounds. */
|
|
492
|
+
function formulaIssue(formula) {
|
|
493
|
+
const result = parsedFormula(formula);
|
|
494
|
+
return typeof result === "string" ? result : void 0;
|
|
495
|
+
}
|
|
496
|
+
export { evaluateFormula, formulaIssue, formulaValueReads };
|
|
497
|
+
|
|
498
|
+
//# sourceMappingURL=calc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calc.js","names":[],"sources":["../../src/spec/calc.ts"],"sourcesContent":["import type { ExpressionContext } from \"./expressions\";\nimport { getAtPointer, parsePointer } from \"./pointer\";\n\ntype Node =\n | { kind: \"literal\"; value: unknown }\n | { kind: \"reference\"; path: string }\n | { kind: \"unary\"; operator: string; operand: Node }\n | { kind: \"binary\"; operator: string; left: Node; right: Node }\n | { kind: \"conditional\"; condition: Node; then: Node; otherwise: Node }\n | { kind: \"call\"; name: string; args: Node[] };\n\ntype Token =\n | { kind: \"literal\"; value: unknown }\n | { kind: \"reference\" | \"name\" | \"symbol\"; text: string }\n | { kind: \"end\" };\n\ntype FormulaFunction = {\n min: number;\n max: number;\n run: (args: unknown[], step: () => void) => unknown;\n};\n\nconst isNumber = (value: unknown): value is number =>\n typeof value === \"number\" && Number.isFinite(value);\n\nconst normalize = (value: unknown): unknown =>\n value === undefined || (typeof value === \"number\" && !Number.isFinite(value))\n ? null\n : value;\n\nconst pointerTokens = (path: string) => {\n try {\n return parsePointer(path && !path.startsWith(\"/\") ? `/${path}` : path);\n } catch {\n return [];\n }\n};\n\nconst readPath = (base: unknown, path: string) =>\n getAtPointer(base, pointerTokens(path));\n\nconst readReference = (path: string, ctx: ExpressionContext) => {\n if (path === \"$index\") return ctx.index;\n if (path === \"$item\") return ctx.item;\n if (path.startsWith(\"$item/\")) return readPath(ctx.item, path.slice(5));\n if (path === \"$event\") return ctx.event;\n if (path.startsWith(\"$event/\")) return readPath(ctx.event, path.slice(6));\n return readPath(ctx.state, path);\n};\n\nconst eachChild = (\n value: object,\n visit: (child: unknown, key: string) => boolean,\n) => {\n if (Array.isArray(value)) {\n for (let index = 0; index < value.length; index++) {\n if (Object.hasOwn(value, index) && !visit(value[index], String(index))) {\n return false;\n }\n }\n return true;\n }\n for (const key in value) {\n if (\n Object.hasOwn(value, key) &&\n !visit((value as Record<string, unknown>)[key], key)\n ) {\n return false;\n }\n }\n return true;\n};\n\nconst visitAll = (value: unknown, step: () => void): void => {\n step();\n if (typeof value === \"object\" && value !== null) {\n eachChild(value, (child) => {\n visitAll(child, step);\n return true;\n });\n }\n};\n\nconst formatText = (value: unknown, step: () => void) => {\n if (value === null || value === undefined) return \"\";\n if (typeof value !== \"object\") return String(value);\n visitAll(value, step);\n return JSON.stringify(value);\n};\n\nconst fractionDigits = (value: number) =>\n Math.max(0, Math.min(20, Math.trunc(value)));\n\nconst numeric =\n (fn: (...args: number[]) => number): FormulaFunction[\"run\"] =>\n (args) =>\n args.every(isNumber) ? fn(...args) : null;\n\nconst aggregate = (\n operation: \"sum\" | \"avg\" | \"min\" | \"max\",\n args: unknown[],\n step: () => void,\n) => {\n const [list, field] = args;\n if (!Array.isArray(list) || args.length > 2) return null;\n if (args.length === 2 && typeof field !== \"string\") return null;\n if (typeof field === \"string\" && field.length > 500) return null;\n const tokens = typeof field === \"string\" ? pointerTokens(field) : undefined;\n if (tokens && tokens.length > 32) return null;\n let total = 0;\n let count = 0;\n let extreme: number | null = null;\n for (const item of list) {\n step();\n const value = tokens ? getAtPointer(item, tokens) : item;\n if (!isNumber(value)) continue;\n total += value;\n count++;\n if (extreme === null) extreme = value;\n else if (operation === \"min\") extreme = Math.min(extreme, value);\n else if (operation === \"max\") extreme = Math.max(extreme, value);\n }\n if (operation === \"sum\") return total;\n if (operation === \"avg\") return count ? total / count : null;\n return extreme;\n};\n\nconst formatters = new Map<string, Intl.NumberFormat>();\n\nconst formatNumber = (value: unknown, options: Intl.NumberFormatOptions) => {\n if (!isNumber(value)) return null;\n const key = JSON.stringify(options);\n let formatter = formatters.get(key);\n if (!formatter) {\n try {\n formatter = new Intl.NumberFormat(\"en-US\", options);\n } catch {\n return null;\n }\n if (formatters.size >= 64) formatters.clear();\n formatters.set(key, formatter);\n }\n return formatter.format(value);\n};\n\nconst decimalFormat = (args: unknown[], style: \"decimal\" | \"percent\") => {\n const [value, digits] = args;\n if (args.length === 1) return formatNumber(value, { style });\n if (!isNumber(digits)) return null;\n const precision = fractionDigits(digits);\n return formatNumber(value, {\n style,\n minimumFractionDigits: precision,\n maximumFractionDigits: precision,\n });\n};\n\nconst functions = new Map<string, FormulaFunction>([\n [\n \"round\",\n {\n min: 1,\n max: 2,\n run: numeric((x, digits = 0) => {\n const scale = 10 ** fractionDigits(digits);\n return Math.round(x * scale) / scale;\n }),\n },\n ],\n [\"floor\", { min: 1, max: 1, run: numeric(Math.floor) }],\n [\"ceil\", { min: 1, max: 1, run: numeric(Math.ceil) }],\n [\"abs\", { min: 1, max: 1, run: numeric(Math.abs) }],\n [\n \"clamp\",\n {\n min: 3,\n max: 3,\n run: numeric((x, low, high) => Math.min(Math.max(x, low), high)),\n },\n ],\n ...([\"min\", \"max\"] as const).map((name): [string, FormulaFunction] => [\n name,\n {\n min: 1,\n max: Infinity,\n run: (args, step) =>\n Array.isArray(args[0])\n ? aggregate(name, args, step)\n : numeric(name === \"min\" ? Math.min : Math.max)(args, step),\n },\n ]),\n [\n \"count\",\n {\n min: 1,\n max: 1,\n run: ([list]) => (Array.isArray(list) ? list.length : null),\n },\n ],\n ...([\"sum\", \"avg\"] as const).map((name): [string, FormulaFunction] => [\n name,\n {\n min: 1,\n max: 2,\n run: (args, step) => aggregate(name, args, step),\n },\n ]),\n [\"number\", { min: 1, max: 2, run: (args) => decimalFormat(args, \"decimal\") }],\n [\n \"currency\",\n {\n min: 2,\n max: 2,\n run: ([value, code]) =>\n typeof code === \"string\"\n ? formatNumber(value, { style: \"currency\", currency: code })\n : null,\n },\n ],\n [\n \"percent\",\n { min: 1, max: 2, run: (args) => decimalFormat(args, \"percent\") },\n ],\n [\n \"time\",\n {\n min: 1,\n max: 1,\n run: ([value]) => {\n if (!isNumber(value)) return null;\n const minutes = ((Math.round(value) % 1440) + 1440) % 1440;\n const hour = Math.floor(minutes / 60);\n return `${hour % 12 || 12}:${String(minutes % 60).padStart(2, \"0\")} ${hour < 12 ? \"AM\" : \"PM\"}`;\n },\n },\n ],\n [\n \"plural\",\n { min: 3, max: 3, run: ([n, one, other]) => (n === 1 ? one : other) },\n ],\n]);\n\nconst tokenize = (formula: string): Token[] => {\n const tokens: Token[] = [];\n let offset = 0;\n while (offset < formula.length) {\n const char = formula[offset]!;\n if (/\\s/.test(char)) {\n offset++;\n } else if (char === \"'\" || char === '\"') {\n offset++;\n let value = \"\";\n while (offset < formula.length && formula[offset] !== char) {\n let next = formula[offset++]!;\n if (next === \"\\\\\") {\n next = formula[offset++] ?? \"\";\n if (next !== \"\\\\\" && next !== \"'\" && next !== '\"') {\n throw new Error(\"Invalid string escape\");\n }\n }\n value += next;\n }\n if (offset >= formula.length) throw new Error(\"Unterminated string\");\n offset++;\n tokens.push({ kind: \"literal\", value });\n } else if (char === \"$\" && formula[offset + 1] === \"{\") {\n const end = formula.indexOf(\"}\", offset + 2);\n if (end === -1) throw new Error(\"Unterminated reference\");\n tokens.push({\n kind: \"reference\",\n text: formula.slice(offset + 2, end).trim(),\n });\n offset = end + 1;\n } else if (/[0-9]/.test(char)) {\n const start = offset++;\n while (/[0-9]/.test(formula[offset] ?? \"\")) offset++;\n if (formula[offset] === \".\" && /[0-9]/.test(formula[offset + 1] ?? \"\")) {\n offset++;\n while (/[0-9]/.test(formula[offset] ?? \"\")) offset++;\n }\n tokens.push({\n kind: \"literal\",\n value: Number(formula.slice(start, offset)),\n });\n } else if (/[a-zA-Z_]/.test(char)) {\n const start = offset++;\n while (/[a-zA-Z0-9_]/.test(formula[offset] ?? \"\")) offset++;\n const name = formula.slice(start, offset);\n if (name === \"true\" || name === \"false\" || name === \"null\") {\n tokens.push({\n kind: \"literal\",\n value: name === \"null\" ? null : name === \"true\",\n });\n } else tokens.push({ kind: \"name\", text: name });\n } else {\n const pair = formula.slice(offset, offset + 2);\n if ([\"||\", \"&&\", \"==\", \"!=\", \"<=\", \">=\"].includes(pair)) {\n tokens.push({ kind: \"symbol\", text: pair });\n offset += 2;\n } else if (\"?:<>+-*/%!(),\".includes(char)) {\n tokens.push({ kind: \"symbol\", text: char });\n offset++;\n } else throw new Error(`Unexpected token \"${char}\"`);\n }\n }\n tokens.push({ kind: \"end\" });\n return tokens;\n};\n\nconst precedence = [\n [\"||\"],\n [\"&&\"],\n [\"==\", \"!=\"],\n [\"<\", \"<=\", \">\", \">=\"],\n [\"+\", \"-\"],\n [\"*\", \"/\", \"%\"],\n];\n\nconst parse = (formula: string): Node => {\n const tokens = tokenize(formula);\n let position = 0;\n const current = () => tokens[position]!;\n const match = (symbol: string) => {\n const token = current();\n if (token.kind !== \"symbol\" || token.text !== symbol) return false;\n position++;\n return true;\n };\n const expect = (symbol: string) => {\n if (!match(symbol)) throw new Error(`Expected \"${symbol}\"`);\n };\n const checkDepth = (depth: number) => {\n if (depth > 32) throw new Error(\"Formula exceeds 32 levels of nesting\");\n };\n const primary = (depth: number): Node => {\n checkDepth(depth);\n const token = current();\n position++;\n if (token.kind === \"literal\") return token;\n if (token.kind === \"reference\")\n return { kind: \"reference\", path: token.text };\n if (token.kind === \"symbol\" && token.text === \"(\") {\n const node = conditional(depth + 1);\n expect(\")\");\n return node;\n }\n if (token.kind === \"name\") {\n const fn = functions.get(token.text);\n if (!fn) {\n throw new Error(\n `Unknown function \"${token.text}\"; known: ${[...functions.keys()].join(\", \")}`,\n );\n }\n checkDepth(depth + 1);\n expect(\"(\");\n const args: Node[] = [];\n if (!match(\")\")) {\n do {\n args.push(conditional(depth + 1));\n } while (match(\",\"));\n expect(\")\");\n }\n if (args.length < fn.min || args.length > fn.max) {\n throw new Error(`Wrong argument count for \"${token.text}\"`);\n }\n return { kind: \"call\", name: token.text, args };\n }\n throw new Error(\"Expected a value\");\n };\n const unary = (depth: number): Node => {\n checkDepth(depth);\n const token = current();\n if (token.kind === \"symbol\" && (token.text === \"-\" || token.text === \"!\")) {\n position++;\n return { kind: \"unary\", operator: token.text, operand: unary(depth + 1) };\n }\n return primary(depth);\n };\n const binary = (level: number, depth: number): Node => {\n const operators = precedence[level];\n if (!operators) return unary(depth);\n let left = binary(level + 1, depth);\n for (;;) {\n const token = current();\n if (token.kind !== \"symbol\" || !operators.includes(token.text))\n return left;\n position++;\n left = {\n kind: \"binary\",\n operator: token.text,\n left,\n right: binary(level + 1, depth),\n };\n }\n };\n const conditional = (depth: number): Node => {\n const condition = binary(0, depth);\n if (!match(\"?\")) return condition;\n const then = conditional(depth + 1);\n expect(\":\");\n return {\n kind: \"conditional\",\n condition,\n then,\n otherwise: conditional(depth + 1),\n };\n };\n const node = conditional(0);\n if (current().kind !== \"end\") throw new Error(\"Unexpected trailing token\");\n return node;\n};\n\nconst parsedFormulas = new Map<string, Node | string>();\n\nconst parsedFormula = (formula: string): Node | string => {\n if (formula.length > 500) return \"Formula exceeds 500 characters\";\n const cached = parsedFormulas.get(formula);\n if (cached !== undefined) return cached;\n let result: Node | string;\n try {\n result = parse(formula);\n } catch (error) {\n result = error instanceof Error ? error.message : \"Invalid formula\";\n }\n if (parsedFormulas.size >= 500) parsedFormulas.clear();\n parsedFormulas.set(formula, result);\n return result;\n};\n\nconst childCount = (value: object, step: () => void) => {\n let count = 0;\n eachChild(value, () => {\n step();\n count++;\n return true;\n });\n return count;\n};\n\nconst equal = (a: unknown, b: unknown, step: () => void): boolean => {\n step();\n if (a === b) return true;\n if (typeof a !== \"object\" || typeof b !== \"object\" || !a || !b) return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n if (childCount(a, step) !== childCount(b, step)) return false;\n return eachChild(a, (child, key) =>\n equal(child, (b as Record<string, unknown>)[key], step),\n );\n};\n\nconst calculate = (\n operator: string,\n left: unknown,\n right: unknown,\n step: () => void,\n): unknown => {\n if (operator === \"==\") return equal(left, right, step);\n if (operator === \"!=\") return !equal(left, right, step);\n if (\n operator === \"+\" &&\n (typeof left === \"string\" || typeof right === \"string\")\n ) {\n return formatText(left, step) + formatText(right, step);\n }\n if ([\"<\", \"<=\", \">\", \">=\"].includes(operator)) {\n if (\n !(typeof left === \"number\" && typeof right === \"number\") &&\n !(typeof left === \"string\" && typeof right === \"string\")\n )\n return false;\n if (operator === \"<\") return left < right;\n if (operator === \"<=\") return left <= right;\n if (operator === \">\") return left > right;\n return left >= right;\n }\n if (!isNumber(left) || !isNumber(right)) return null;\n switch (operator) {\n case \"+\":\n return left + right;\n case \"-\":\n return left - right;\n case \"*\":\n return left * right;\n case \"/\":\n return right === 0 ? null : left / right;\n case \"%\":\n return right === 0 ? null : left % right;\n default:\n return null;\n }\n};\n\nconst evaluate = (\n node: Node,\n ctx: ExpressionContext,\n step: () => void,\n): unknown => {\n step();\n const visit = (child: Node) => normalize(evaluate(child, ctx, step));\n switch (node.kind) {\n case \"literal\":\n return node.value;\n case \"reference\":\n return readReference(node.path, ctx);\n case \"unary\": {\n const value = visit(node.operand);\n return node.operator === \"!\" ? !value : isNumber(value) ? -value : null;\n }\n case \"conditional\":\n return visit(node.condition) ? visit(node.then) : visit(node.otherwise);\n case \"call\":\n return functions.get(node.name)!.run(node.args.map(visit), step);\n case \"binary\": {\n const left = visit(node.left);\n if (node.operator === \"&&\")\n return Boolean(left) && Boolean(visit(node.right));\n if (node.operator === \"||\")\n return Boolean(left) || Boolean(visit(node.right));\n return calculate(node.operator, left, visit(node.right), step);\n }\n }\n};\n\n/** Evaluates a bounded formula, returning null when it cannot run. */\nexport function evaluateFormula(\n formula: string,\n ctx: ExpressionContext,\n): unknown {\n try {\n const node = parsedFormula(formula);\n if (typeof node === \"string\") return null;\n let steps = 0;\n return normalize(\n evaluate(node, ctx, () => {\n if (++steps > 10_000)\n throw new Error(\"Formula exceeds 10000 evaluation steps\");\n }),\n );\n } catch {\n return null;\n }\n}\n\n/** The state paths a formula shows as values, leaving out reads under a condition and reads in the `?` branch of a condition on that same path, which only runs while it has a value. */\nexport function formulaValueReads(formula: string): string[] {\n const node = parsedFormula(formula);\n if (typeof node === \"string\") return [];\n const reads = new Set<string>();\n const visit = (\n current: Node,\n condition: boolean,\n guarded: ReadonlySet<string>,\n ): void => {\n switch (current.kind) {\n case \"literal\":\n return;\n case \"reference\":\n if (\n !condition &&\n !current.path.startsWith(\"$\") &&\n !guarded.has(current.path)\n )\n reads.add(current.path);\n return;\n case \"unary\":\n visit(current.operand, condition || current.operator === \"!\", guarded);\n return;\n case \"binary\": {\n const logical = current.operator === \"&&\" || current.operator === \"||\";\n visit(current.left, condition || logical, guarded);\n visit(current.right, condition || logical, guarded);\n return;\n }\n case \"conditional\":\n visit(current.condition, true, guarded);\n visit(\n current.then,\n condition,\n current.condition.kind === \"reference\"\n ? new Set([...guarded, current.condition.path])\n : guarded,\n );\n visit(current.otherwise, condition, guarded);\n return;\n case \"call\":\n for (const arg of current.args) visit(arg, condition, guarded);\n return;\n }\n };\n visit(node, false, new Set());\n return [...reads];\n}\n\n/** Why a formula cannot run, or undefined when it parses within the bounds. */\nexport function formulaIssue(formula: string): string | undefined {\n const result = parsedFormula(formula);\n return typeof result === \"string\" ? result : undefined;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type JsonSchema, type SchemaIssue, type StandardSchemaLike } from "../json-schema.js";
|
|
2
|
+
import { type SpecPromptOptions } from "./prompt.js";
|
|
3
|
+
export type PropsSchema = JsonSchema | StandardSchemaLike;
|
|
4
|
+
/** A component the model may use. Implementations are supplied per framework. */
|
|
5
|
+
export type ComponentDefinition = {
|
|
6
|
+
description: string;
|
|
7
|
+
/** JSON Schema for the props, or a Standard Schema (e.g. Zod 4) that can export one. */
|
|
8
|
+
props?: PropsSchema;
|
|
9
|
+
/**
|
|
10
|
+
* Child slots. `"default"` maps to `children`; other names map to
|
|
11
|
+
* `slots.<name>`. Components without slots take no children.
|
|
12
|
+
*/
|
|
13
|
+
slots?: readonly string[];
|
|
14
|
+
/** Events the component emits, which elements bind with `on`. */
|
|
15
|
+
events?: readonly string[];
|
|
16
|
+
};
|
|
17
|
+
export type ActionDefinition = {
|
|
18
|
+
description: string;
|
|
19
|
+
params?: PropsSchema;
|
|
20
|
+
};
|
|
21
|
+
export type CatalogDefinition<TComponents extends Record<string, ComponentDefinition> = Record<string, ComponentDefinition>, TActions extends Record<string, ActionDefinition> = Record<string, ActionDefinition>> = {
|
|
22
|
+
components: TComponents;
|
|
23
|
+
actions?: TActions;
|
|
24
|
+
};
|
|
25
|
+
export type Catalog<TComponents extends Record<string, ComponentDefinition> = Record<string, ComponentDefinition>, TActions extends Record<string, ActionDefinition> = Record<string, ActionDefinition>> = {
|
|
26
|
+
readonly components: TComponents;
|
|
27
|
+
readonly actions: TActions;
|
|
28
|
+
/** Component definition by name, or undefined. */
|
|
29
|
+
component(type: string): ComponentDefinition | undefined;
|
|
30
|
+
/** Action definition by name, including the built-in `setState`. */
|
|
31
|
+
action(name: string): ActionDefinition | undefined;
|
|
32
|
+
/** JSON Schema of a component's props, if it has one. */
|
|
33
|
+
propsSchema(type: string): JsonSchema | undefined;
|
|
34
|
+
/** Validates props, treating expression values as valid placeholders. */
|
|
35
|
+
/** JSON Schema of an action's params, if it has one. */
|
|
36
|
+
paramsSchema(action: string): JsonSchema | undefined;
|
|
37
|
+
validateProps(type: string, props: unknown): SchemaIssue[];
|
|
38
|
+
validateParams(action: string, params: unknown): SchemaIssue[];
|
|
39
|
+
/** Model guidance for this catalog: components, actions, protocol, expressions, rules. */
|
|
40
|
+
prompt(options?: SpecPromptOptions): string;
|
|
41
|
+
};
|
|
42
|
+
/** The built-in action every catalog accepts. */
|
|
43
|
+
export declare const SET_STATE_ACTION: ActionDefinition;
|
|
44
|
+
/**
|
|
45
|
+
* Declares the components and actions a model may use in a spec. Props are
|
|
46
|
+
* JSON Schema (or a Standard Schema exposing JSON Schema), so the catalog
|
|
47
|
+
* stays dependency-free and works as model guidance and runtime validation.
|
|
48
|
+
*/
|
|
49
|
+
export declare function defineCatalog<const TComponents extends Record<string, ComponentDefinition>, const TActions extends Record<string, ActionDefinition> = Record<never, ActionDefinition>>(definition: CatalogDefinition<TComponents, TActions>): Catalog<TComponents, TActions>;
|
|
50
|
+
/** One line describing a component's props for prompts and errors. */
|
|
51
|
+
export declare const describeProps: (catalog: Catalog, type: string) => string;
|
|
52
|
+
//# sourceMappingURL=catalog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","mappings":"AAAA,OAAO,EAIL,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACxB,MAAM,mBAAgB;AAEvB,OAAO,EAAmB,KAAK,iBAAiB,EAAE,MAAM,aAAU;AAElE,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,kBAAkB;AAEzD;AACA,MAAM,MAAM,mBAAmB,GAAG;IAChC,WAAW,EAAE,MAAM;IACnB;IACA,KAAK,CAAC,EAAE,WAAW;IACnB;;;;IAIA,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE;IACzB;IACA,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE;CAC3B;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,EAAE,MAAM;IACnB,MAAM,CAAC,EAAE,WAAW;CACrB;AAED,MAAM,MAAM,iBAAiB,CAC3B,WAAW,SAAS,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,GAAG,MAAM,CAC9D,MAAM,EACN,mBAAmB,CACpB,EACD,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,MAAM,CACxD,MAAM,EACN,gBAAgB,CACjB,IACC;IACF,UAAU,EAAE,WAAW;IACvB,OAAO,CAAC,EAAE,QAAQ;CACnB;AAED,MAAM,MAAM,OAAO,CACjB,WAAW,SAAS,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,GAAG,MAAM,CAC9D,MAAM,EACN,mBAAmB,CACpB,EACD,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,MAAM,CACxD,MAAM,EACN,gBAAgB,CACjB,IACC;IACF,QAAQ,CAAC,UAAU,EAAE,WAAW;IAChC,QAAQ,CAAC,OAAO,EAAE,QAAQ;IAC1B;IACA,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS;IACxD;IACA,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAClD;IACA,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IACjD;IACA;IACA,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IACpD,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,WAAW,EAAE;IAC1D,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,WAAW,EAAE;IAC9D;IACA,MAAM,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,MAAM;CAC5C;AAED;AACA,eAAO,MAAM,gBAAgB,EAAE,gBAU9B;AA0CD;;;;;AAKA,wBAAgB,aAAa,CAC3B,KAAK,CAAC,WAAW,SAAS,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,EAC7D,KAAK,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,MAAM,CAC9D,KAAK,EACL,gBAAgB,CACjB,EAED,UAAU,EAAE,iBAAiB,CAAC,WAAW,EAAE,QAAQ,CAAC,GACnD,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC;AAsDjC;AACA,eAAO,MAAM,aAAa,GAAI,SAAS,OAAO,EAAE,MAAM,MAAM,WACjB","names":[],"ignoreList":[],"sources":["../../src/spec/catalog.ts"],"sourcesContent":[null]}
|