@uicast/core 0.0.1-beta.1 → 0.0.1-beta.2

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.
Files changed (72) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/{src/def/create-component-definition.ts → dist/def/create-component-definition.d.ts} +2 -14
  3. package/dist/def/create-component-definition.js +10 -0
  4. package/dist/entry-error.d.ts +47 -0
  5. package/dist/entry-error.js +56 -0
  6. package/dist/expr/analyze.d.ts +5 -0
  7. package/dist/expr/analyze.js +189 -0
  8. package/dist/expr/ast-utils.d.ts +4 -0
  9. package/dist/expr/ast-utils.js +23 -0
  10. package/dist/expr/evaluate.d.ts +7 -0
  11. package/dist/expr/evaluate.js +64 -0
  12. package/dist/expr/extract-deps.d.ts +2 -0
  13. package/dist/expr/extract-deps.js +30 -0
  14. package/dist/expr/globals.d.ts +3 -0
  15. package/dist/expr/globals.js +111 -0
  16. package/dist/expr/safer-eval.d.ts +21 -0
  17. package/dist/expr/safer-eval.js +170 -0
  18. package/dist/expr/validate.d.ts +7 -0
  19. package/dist/expr/validate.js +109 -0
  20. package/dist/index.d.ts +9 -0
  21. package/dist/index.js +10 -0
  22. package/{src/prompt/get-common-instructions-partial-prompt.ts → dist/prompt/get-common-instructions-partial-prompt.d.ts} +1 -8
  23. package/dist/prompt/get-common-instructions-partial-prompt.js +14 -0
  24. package/dist/prompt/get-components-partial-prompt.d.ts +35 -0
  25. package/dist/prompt/get-components-partial-prompt.js +134 -0
  26. package/dist/prompt/get-edit-request-prompt.d.ts +23 -0
  27. package/dist/prompt/get-edit-request-prompt.js +22 -0
  28. package/{src/prompt/get-error-recovery-prompt.ts → dist/prompt/get-error-recovery-prompt.d.ts} +7 -20
  29. package/dist/prompt/get-error-recovery-prompt.js +23 -0
  30. package/{src/prompt/get-expressions-partial-prompt.ts → dist/prompt/get-expressions-partial-prompt.d.ts} +1 -10
  31. package/dist/prompt/get-expressions-partial-prompt.js +27 -0
  32. package/dist/prompt/get-functions-partial-prompt.d.ts +22 -0
  33. package/{src/prompt/get-functions-partial-prompt.ts → dist/prompt/get-functions-partial-prompt.js} +18 -34
  34. package/dist/prompt/get-scope-partial-prompt.d.ts +24 -0
  35. package/{src/prompt/get-scope-partial-prompt.ts → dist/prompt/get-scope-partial-prompt.js} +15 -34
  36. package/dist/prompt/index.d.ts +7 -0
  37. package/dist/prompt/index.js +9 -0
  38. package/{src → dist}/prompt/md/EXPRESSIONS.json +1 -1
  39. package/dist/prompt/md/INSTRUCTIONS.json +1 -0
  40. package/dist/prompt-utils/json-schema-to-ts.d.ts +73 -0
  41. package/dist/prompt-utils/json-schema-to-ts.js +199 -0
  42. package/dist/scope/create-proxy-scope.d.ts +20 -0
  43. package/dist/scope/create-proxy-scope.js +101 -0
  44. package/{src/scope/parse-scope.ts → dist/scope/parse-scope.d.ts} +1 -17
  45. package/dist/scope/parse-scope.js +36 -0
  46. package/dist/stream/stream-json-lines.d.ts +1 -0
  47. package/dist/stream/stream-json-lines.js +94 -0
  48. package/dist/types.d.ts +89 -0
  49. package/dist/types.js +22 -0
  50. package/dist/utils/utils.d.ts +13 -0
  51. package/dist/utils/utils.js +58 -0
  52. package/package.json +13 -6
  53. package/src/entry-error.ts +0 -104
  54. package/src/expr/analyze.ts +0 -190
  55. package/src/expr/ast-utils.ts +0 -28
  56. package/src/expr/evaluate.ts +0 -74
  57. package/src/expr/extract-deps.ts +0 -32
  58. package/src/expr/globals.ts +0 -118
  59. package/src/expr/safer-eval.ts +0 -233
  60. package/src/expr/validate.ts +0 -149
  61. package/src/index.ts +0 -28
  62. package/src/prompt/get-components-partial-prompt.ts +0 -153
  63. package/src/prompt/get-edit-request-prompt.ts +0 -37
  64. package/src/prompt/index.ts +0 -32
  65. package/src/prompt/md/EXPRESSIONS.md +0 -29
  66. package/src/prompt/md/INSTRUCTIONS.json +0 -1
  67. package/src/prompt/md/INSTRUCTIONS.md +0 -172
  68. package/src/prompt-utils/json-schema-to-ts.ts +0 -225
  69. package/src/scope/create-proxy-scope.ts +0 -131
  70. package/src/stream/stream-json-lines.ts +0 -56
  71. package/src/types.ts +0 -124
  72. package/src/utils/utils.ts +0 -64
@@ -0,0 +1,101 @@
1
+ function createEmitter() {
2
+ const events = new Map();
3
+ return {
4
+ on(type, handler) {
5
+ const handlers = events.get(type);
6
+ if (handlers)
7
+ handlers.add(handler);
8
+ else
9
+ events.set(type, new Set([handler]));
10
+ return () => { var _a; return (_a = events.get(type)) === null || _a === void 0 ? void 0 : _a.delete(handler); };
11
+ },
12
+ off(type, handler) {
13
+ var _a;
14
+ (_a = events.get(type)) === null || _a === void 0 ? void 0 : _a.delete(handler);
15
+ },
16
+ emit(type, payload) {
17
+ var _a;
18
+ (_a = events.get(type)) === null || _a === void 0 ? void 0 : _a.forEach((fn) => {
19
+ fn(payload);
20
+ });
21
+ // Writing a path replaces everything beneath it, so subscribers of
22
+ // deeper paths must wake too: a write to `products` wakes a reader of
23
+ // `products.length`. The reverse direction stays exact — a write to
24
+ // `rows.0.name` does not wake a reader of `rows`.
25
+ const prefix = `${type}.`;
26
+ for (const [key, handlers] of events) {
27
+ if (key.startsWith(prefix)) {
28
+ handlers.forEach((fn) => {
29
+ fn(payload);
30
+ });
31
+ }
32
+ }
33
+ },
34
+ };
35
+ }
36
+ function createProxyScope(target = {}) {
37
+ const emitter = createEmitter();
38
+ const proxyCache = new WeakMap();
39
+ function set(path, value, options) {
40
+ const keys = path.split(".");
41
+ let current = proxy;
42
+ for (let i = 0; i < keys.length - 1; i++) {
43
+ const key = keys[i];
44
+ if (current[key] === undefined || current[key] === null) {
45
+ current[key] = {};
46
+ }
47
+ current = current[key];
48
+ }
49
+ const lastKey = keys[keys.length - 1];
50
+ // `default: true` → init-if-absent (first-writer-wins); never clobber an
51
+ // existing value. Used by `seed` so two components seeding the same
52
+ // path don't overwrite each other, and user edits survive remounts.
53
+ if ((options === null || options === void 0 ? void 0 : options.default) && current[lastKey] !== undefined)
54
+ return;
55
+ current[lastKey] = value;
56
+ }
57
+ function wrap(obj, path = []) {
58
+ if (obj === null || typeof obj !== "object")
59
+ return obj;
60
+ if (proxyCache.has(obj))
61
+ return proxyCache.get(obj);
62
+ const proxy = new Proxy(obj, {
63
+ get(target, prop, receiver) {
64
+ if (path.length === 0) {
65
+ if (prop === "$emitter")
66
+ return emitter;
67
+ if (prop === "$set")
68
+ return set;
69
+ }
70
+ const value = Reflect.get(target, prop, receiver);
71
+ // Proxy invariant: for non-configurable, non-writable properties
72
+ // the trap MUST return the exact target value.
73
+ if (value !== null && typeof value === "object") {
74
+ const desc = Object.getOwnPropertyDescriptor(target, prop);
75
+ if (desc && !desc.configurable && !desc.writable) {
76
+ return value;
77
+ }
78
+ }
79
+ return wrap(value, path.concat(prop));
80
+ },
81
+ set(target, prop, value, receiver) {
82
+ const oldValue = target[prop];
83
+ const result = Reflect.set(target, prop, value, receiver);
84
+ if (oldValue !== value) {
85
+ const fullPath = path.concat(prop).join(".");
86
+ emitter.emit(fullPath, {
87
+ path: fullPath,
88
+ value,
89
+ oldValue,
90
+ });
91
+ }
92
+ return result;
93
+ },
94
+ });
95
+ proxyCache.set(obj, proxy);
96
+ return proxy;
97
+ }
98
+ const proxy = wrap(target);
99
+ return proxy;
100
+ }
101
+ export { createProxyScope, createEmitter, };
@@ -17,20 +17,4 @@
17
17
  * produces `scopes.X.Y` read strings, and the renderer hands each to
18
18
  * `parseScope(...)` to route an `$emitter.on(targetPath, …)` subscription.
19
19
  */
20
- export const parseScope = (key: string, prefix = "scopes") => {
21
- // Strip the "<prefix>." namespace token if present.
22
- const scopesPrefix = `${prefix}.`;
23
- const normalizedKey = key.startsWith(scopesPrefix)
24
- ? key.slice(scopesPrefix.length)
25
- : key;
26
-
27
- const dotIndex = normalizedKey.indexOf(".");
28
- if (dotIndex === -1) {
29
- throw new Error(`Invalid scope key: ${key}`);
30
- } else {
31
- return [
32
- normalizedKey.slice(0, dotIndex),
33
- normalizedKey.slice(dotIndex + 1),
34
- ] as [string, string];
35
- }
36
- };
20
+ export declare const parseScope: (key: string, prefix?: string) => [string, string];
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Split a scope key into `[scopeName, leafPath]`.
3
+ *
4
+ * Strips the leading namespace token (default `"scopes"`) if present, then
5
+ * splits on the first dot: the head is the named scope, the tail is the path
6
+ * *within* that scope.
7
+ *
8
+ * parseScope("scopes.root.user.name") → ["root", "user.name"]
9
+ * parseScope("root.count") → ["root", "count"] (prefix optional)
10
+ *
11
+ * `prefix` (default `"scopes"`) is the leading namespace identifier the dotted
12
+ * key is written against; it builds the `scopesPrefix` (`prefix + "."`) that
13
+ * gets stripped. Pass a different value when keys are written against another
14
+ * root identifier.
15
+ *
16
+ * This is the scope-layer counterpart to `createProxyScope`: `extractDeps`
17
+ * produces `scopes.X.Y` read strings, and the renderer hands each to
18
+ * `parseScope(...)` to route an `$emitter.on(targetPath, …)` subscription.
19
+ */
20
+ export const parseScope = (key, prefix = "scopes") => {
21
+ // Strip the "<prefix>." namespace token if present.
22
+ const scopesPrefix = `${prefix}.`;
23
+ const normalizedKey = key.startsWith(scopesPrefix)
24
+ ? key.slice(scopesPrefix.length)
25
+ : key;
26
+ const dotIndex = normalizedKey.indexOf(".");
27
+ if (dotIndex === -1) {
28
+ throw new Error(`Invalid scope key: ${key}`);
29
+ }
30
+ else {
31
+ return [
32
+ normalizedKey.slice(0, dotIndex),
33
+ normalizedKey.slice(dotIndex + 1),
34
+ ];
35
+ }
36
+ };
@@ -0,0 +1 @@
1
+ export declare function streamJsonLines<T = unknown>(source: ReadableStream<Uint8Array | string> | AsyncIterable<string | Uint8Array>): AsyncGenerator<T>;
@@ -0,0 +1,94 @@
1
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
2
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
3
+ var m = o[Symbol.asyncIterator], i;
4
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
5
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
6
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
7
+ };
8
+ var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
9
+ var __asyncDelegator = (this && this.__asyncDelegator) || function (o) {
10
+ var i, p;
11
+ return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i;
12
+ function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }
13
+ };
14
+ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
15
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
16
+ var g = generator.apply(thisArg, _arguments || []), i, q = [];
17
+ return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
18
+ function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
19
+ function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
20
+ function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
21
+ function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
22
+ function fulfill(value) { resume("next", value); }
23
+ function reject(value) { resume("throw", value); }
24
+ function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
25
+ };
26
+ // Yields every parseable JSON value from a byte or string stream, skipping
27
+ // blank/non-JSON lines (code fences, model prose, partial lines). Narrow the
28
+ // element type via <T>; shape-checking is the caller's concern.
29
+ export function streamJsonLines(source) {
30
+ return __asyncGenerator(this, arguments, function* streamJsonLines_1() {
31
+ var _a, e_1, _b, _c;
32
+ const decoder = new TextDecoder();
33
+ let buffer = "";
34
+ try {
35
+ for (var _d = true, _e = __asyncValues(toAsyncIterable(source)), _f; _f = yield __await(_e.next()), _a = _f.done, !_a; _d = true) {
36
+ _c = _f.value;
37
+ _d = false;
38
+ const chunk = _c;
39
+ buffer += typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
40
+ let nl = buffer.indexOf("\n");
41
+ while (nl !== -1) {
42
+ yield __await(yield* __asyncDelegator(__asyncValues(parseLine(buffer.slice(0, nl)))));
43
+ buffer = buffer.slice(nl + 1);
44
+ nl = buffer.indexOf("\n");
45
+ }
46
+ }
47
+ }
48
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
49
+ finally {
50
+ try {
51
+ if (!_d && !_a && (_b = _e.return)) yield __await(_b.call(_e));
52
+ }
53
+ finally { if (e_1) throw e_1.error; }
54
+ }
55
+ yield __await(yield* __asyncDelegator(__asyncValues(parseLine(buffer + decoder.decode()))));
56
+ });
57
+ }
58
+ function* parseLine(raw) {
59
+ const line = raw.trim();
60
+ if (!line)
61
+ return;
62
+ try {
63
+ yield JSON.parse(line);
64
+ }
65
+ catch (_a) {
66
+ // non-JSON noise (fences, prose, partial) — drop it
67
+ }
68
+ }
69
+ function toAsyncIterable(source) {
70
+ if (Symbol.asyncIterator in source) {
71
+ return source;
72
+ }
73
+ // Browser ReadableStream isn't async-iterable — pull it through a reader.
74
+ const stream = source;
75
+ return {
76
+ [Symbol.asyncIterator]() {
77
+ return __asyncGenerator(this, arguments, function* _a() {
78
+ const reader = stream.getReader();
79
+ try {
80
+ for (;;) {
81
+ const { done, value } = yield __await(reader.read());
82
+ if (done)
83
+ return yield __await(void 0);
84
+ if (value !== undefined)
85
+ yield yield __await(value);
86
+ }
87
+ }
88
+ finally {
89
+ reader.releaseLock();
90
+ }
91
+ });
92
+ },
93
+ };
94
+ }
@@ -0,0 +1,89 @@
1
+ import type { StandardJSONSchemaV1, StandardSchemaV1 } from "@standard-schema/spec";
2
+ export type Expression = string;
3
+ export type ScopePath = string;
4
+ export type ValueSource = {
5
+ expr: Expression;
6
+ } | {
7
+ literal: unknown;
8
+ };
9
+ export type ValueSourceAssignment = {
10
+ set: ScopePath;
11
+ } & ValueSource;
12
+ export type ConfirmableValueSourceAssignment = {
13
+ confirm?: string;
14
+ } & ValueSourceAssignment;
15
+ /**
16
+ * The uicast entry model. One line of a uicast document is a
17
+ * `ComponentEntry`. A `ComponentListEntry` is an entry that repeats: it carries
18
+ * `each` (the array to iterate) plus `as` and an optional `keyBy`.
19
+ *
20
+ * There is no separate "plain entry" type and no
21
+ * `ComponentEntry | ComponentListEntry` union: the list fields (`each`, `as`,
22
+ * `keyBy`) live on `ComponentEntry` as optional, so one type describes every
23
+ * line and a heterogeneous `ComponentEntry[]` can hold list lines directly.
24
+ * `ComponentListEntry` is the same shape with `each`/`as` required — narrow
25
+ * `ComponentEntry` → `ComponentListEntry` with `isComponentListEntry` (presence
26
+ * of `each`).
27
+ */
28
+ export interface ComponentEntry {
29
+ key: string;
30
+ component: string;
31
+ props?: ValueSource;
32
+ seed?: ValueSourceAssignment[];
33
+ hidden?: Expression;
34
+ callbacks?: Record<string, ConfirmableValueSourceAssignment[]>;
35
+ children?: string[];
36
+ each?: Expression;
37
+ as?: string;
38
+ keyBy?: (string & {}) | "_index" | "_item";
39
+ }
40
+ /**
41
+ * A list entry repeats — the same shape as `ComponentEntry` with the iteration
42
+ * fields required. `each` is the structural marker (its presence is what makes
43
+ * an entry a list); `as` names the per-item scope.
44
+ */
45
+ export interface ComponentListEntry extends ComponentEntry {
46
+ each: Expression;
47
+ as: string;
48
+ }
49
+ /**
50
+ * Narrow a `ComponentEntry` to a `ComponentListEntry`. List-ness is the
51
+ * presence of `each` — there is no `kind` discriminator, exactly like root-ness
52
+ * (derived from an entry being referenced by no other entry's `children`).
53
+ */
54
+ export declare function isComponentListEntry(entry: ComponentEntry): entry is ComponentListEntry;
55
+ /**
56
+ * Narrow an untrusted parsed value to a `ComponentEntry` — the shape guard for
57
+ * model-emitted lines (`streamJsonLines` output, fence bodies, stored JSONL).
58
+ */
59
+ export declare function isComponentEntry(value: unknown): value is ComponentEntry;
60
+ /**
61
+ * An interface that combines StandardJSONSchema and StandardSchema.
62
+ * */
63
+ export type CombinedSpec<Input = unknown, Output = Input> = StandardSchemaV1<Input, Output> & StandardJSONSchemaV1<Input, Output>;
64
+ /**
65
+ * The partner module the LLM reads for a component: a props spec plus optional
66
+ * callback specs (its `description` + `props` schema are serialized into the
67
+ * prompt). Parameterized over the concrete `props` / `callbacks` specs so an
68
+ * implementation can recover their exact types — `createComponentImplementation`
69
+ * infers `TProps` / `TCallbacks` straight off the `def` it's handed to type its
70
+ * `render` callback. Both params default to the widened base, so a bare
71
+ * `ComponentDefinition` is the heterogeneous form used in collections
72
+ * (`ComponentDefinition[]`, the `allDefinitions` registry, the prompt
73
+ * serializer). The `createComponentDefinition` factory (in
74
+ * `def/create-component-definition.ts`) is the value-side constructor for this.
75
+ */
76
+ export type ComponentDefinition<TProps extends CombinedSpec = CombinedSpec, TCallbacks extends Record<string, CombinedSpec> = Record<string, CombinedSpec>> = {
77
+ name: string;
78
+ description: string;
79
+ props: TProps;
80
+ callbacks?: TCallbacks;
81
+ /**
82
+ * Host-only component. Registered in the renderer registry so entries
83
+ * referencing it mount correctly, but filtered out of the LLM-facing
84
+ * prompt list in `getComponentsPartialPrompt`. Use for internal infrastructure
85
+ * (e.g. RootFragment — the synthetic wrapper used by `Renderer`'s `init`
86
+ * prop machinery) that the LLM should never emit.
87
+ */
88
+ hidden?: boolean;
89
+ };
package/dist/types.js ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Narrow a `ComponentEntry` to a `ComponentListEntry`. List-ness is the
3
+ * presence of `each` — there is no `kind` discriminator, exactly like root-ness
4
+ * (derived from an entry being referenced by no other entry's `children`).
5
+ */
6
+ export function isComponentListEntry(entry) {
7
+ return entry.each !== undefined;
8
+ }
9
+ /**
10
+ * Narrow an untrusted parsed value to a `ComponentEntry` — the shape guard for
11
+ * model-emitted lines (`streamJsonLines` output, fence bodies, stored JSONL).
12
+ */
13
+ export function isComponentEntry(value) {
14
+ if (!value || typeof value !== "object")
15
+ return false;
16
+ const entry = value;
17
+ return (typeof entry.key === "string" &&
18
+ typeof entry.component === "string" &&
19
+ // The least-trusted input path: `children` as a string (a plausible LLM
20
+ // slip) would be iterated char-by-char downstream into phantom child slots.
21
+ (entry.children === undefined || Array.isArray(entry.children)));
22
+ }
@@ -0,0 +1,13 @@
1
+ import type { ComponentEntry } from "../types.js";
2
+ /**
3
+ * Build an elements-by-key map from the NDJSON lines array.
4
+ *
5
+ * When an entry with a duplicate key is encountered (i.e. the LLM re-emits a
6
+ * entry to correct a mistake), the old subtree is removed before inserting
7
+ * the replacement — except old nodes the new `children` array still
8
+ * references (directly or through a kept child), which survive with their
9
+ * own subtrees. This lets the LLM restructure a parent by re-emitting just
10
+ * that entry, keeping existing children by reference, without regenerating
11
+ * the entire tree.
12
+ */
13
+ export declare function buildElementsById(lines: ComponentEntry[]): Record<string, ComponentEntry>;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Collect all descendant IDs of a given entry (not including the entry itself)
3
+ * by walking its children array recursively.
4
+ */
5
+ function collectDescendantIds(id, map) {
6
+ const result = new Set();
7
+ const stack = [id];
8
+ while (stack.length > 0) {
9
+ const current = stack.pop();
10
+ const entry = map[current];
11
+ if (entry === null || entry === void 0 ? void 0 : entry.children) {
12
+ for (const childId of entry.children) {
13
+ if (!result.has(childId)) {
14
+ result.add(childId);
15
+ stack.push(childId);
16
+ }
17
+ }
18
+ }
19
+ }
20
+ return result;
21
+ }
22
+ /**
23
+ * Build an elements-by-key map from the NDJSON lines array.
24
+ *
25
+ * When an entry with a duplicate key is encountered (i.e. the LLM re-emits a
26
+ * entry to correct a mistake), the old subtree is removed before inserting
27
+ * the replacement — except old nodes the new `children` array still
28
+ * references (directly or through a kept child), which survive with their
29
+ * own subtrees. This lets the LLM restructure a parent by re-emitting just
30
+ * that entry, keeping existing children by reference, without regenerating
31
+ * the entire tree.
32
+ */
33
+ export function buildElementsById(lines) {
34
+ var _a;
35
+ const map = {};
36
+ for (const line of lines) {
37
+ if (map[line.key]) {
38
+ const oldDescendants = collectDescendantIds(line.key, map);
39
+ const kept = new Set();
40
+ const stack = [...((_a = line.children) !== null && _a !== void 0 ? _a : [])];
41
+ while (stack.length > 0) {
42
+ const id = stack.pop();
43
+ if (kept.has(id) || !oldDescendants.has(id))
44
+ continue;
45
+ kept.add(id);
46
+ const entry = map[id];
47
+ if (entry === null || entry === void 0 ? void 0 : entry.children)
48
+ stack.push(...entry.children);
49
+ }
50
+ for (const descId of oldDescendants) {
51
+ if (!kept.has(descId))
52
+ delete map[descId];
53
+ }
54
+ }
55
+ map[line.key] = line;
56
+ }
57
+ return map;
58
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uicast/core",
3
- "version": "0.0.1-beta.1",
3
+ "version": "0.0.1-beta.2",
4
4
  "description": "LLM-generated UIs from a stream of JSONLines — the framework-agnostic uicast engine: entry format, sandboxed expressions, reactive scopes, and prompt builders.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -20,16 +20,23 @@
20
20
  ],
21
21
  "type": "module",
22
22
  "exports": {
23
- ".": "./src/index.ts",
24
- "./prompt": "./src/prompt/index.ts"
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "default": "./dist/index.js"
26
+ },
27
+ "./prompt": {
28
+ "types": "./dist/prompt/index.d.ts",
29
+ "default": "./dist/prompt/index.js"
30
+ }
25
31
  },
26
32
  "files": [
27
- "src",
28
- "!src/**/test/**",
33
+ "dist",
29
34
  "LICENSE",
30
35
  "CHANGELOG.md"
31
36
  ],
32
37
  "scripts": {
38
+ "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
39
+ "prepublishOnly": "npm run build",
33
40
  "test": "vitest run",
34
41
  "test:watch": "vitest",
35
42
  "test:coverage": "vitest run --coverage",
@@ -39,7 +46,7 @@
39
46
  "dependencies": {
40
47
  "@standard-schema/spec": "^1.1.0",
41
48
  "acorn": "^8.16.0",
42
- "standard-tool": "^0.0.6"
49
+ "standard-tool": "^0.1.0"
43
50
  },
44
51
  "devDependencies": {
45
52
  "@vitest/coverage-v8": "^4.1.0",
@@ -1,104 +0,0 @@
1
- // The engine's classified error: every failure surfaced to the host — through
2
- // the error slot or the Renderer's `onError` — is an EntryError. Classification
3
- // happens at the throw site (by provenance: whose code raised it), never by
4
- // inspecting messages. Reason names state the verdict, not the location.
5
-
6
- export type EntryErrorReason =
7
- /** The expression doesn't parse. */
8
- | "expression-syntax"
9
- /** The expression uses syntax or reaches an API the sandbox blocks. */
10
- | "sandbox-violation"
11
- /** The expression references an identifier that doesn't exist — an unregistered function, a blocked-by-omission global, or an unknown scope path target. */
12
- | "unknown-reference"
13
- /** The element's `component` name has no implementation in the registry. */
14
- | "unknown-component"
15
- /** A list's `each` did not evaluate to an array, or a non-list element was rendered as a list. */
16
- | "invalid-list"
17
- /** The implementation's render threw, and the evaluated props FAIL the def's schema — the document sent a shape the contract forbids. */
18
- | "invalid-props"
19
- /** A host function rejected the arguments the document called it with (host functions throw this deliberately). */
20
- | "invalid-arguments"
21
- /** A host function threw or rejected while executing — server/host failure. */
22
- | "host-function"
23
- /** The host `init` callback threw or rejected. */
24
- | "host-init"
25
- /** The implementation's render threw on props its own def schema accepts — an implementation bug. */
26
- | "implementation"
27
- /** A valid expression threw at runtime (e.g. a property read through null) — the document wrote the path, but the host may have written the value. */
28
- | "expression-runtime"
29
- /** Escaped from an uninstrumented path — the defensive default. */
30
- | "unknown";
31
-
32
- export type EntryFault = "document" | "environment" | "unknown";
33
-
34
- // The single source of truth `fault` derives from — every reason maps to
35
- // exactly one fault; hosts pick a side by picking the reason.
36
- const FAULT_BY_REASON: Record<EntryErrorReason, EntryFault> = {
37
- "expression-syntax": "document",
38
- "sandbox-violation": "document",
39
- "unknown-reference": "document",
40
- "unknown-component": "document",
41
- "invalid-list": "document",
42
- "invalid-props": "document",
43
- "invalid-arguments": "document",
44
- "host-function": "environment",
45
- "host-init": "environment",
46
- implementation: "environment",
47
- "expression-runtime": "unknown",
48
- unknown: "unknown",
49
- };
50
-
51
- export class EntryError extends Error {
52
- readonly reason: EntryErrorReason;
53
- /** The element the failure belongs to; the error boundary fills it in when the throw site couldn't. */
54
- elementKey?: string;
55
- // Brand marker: with git-dep consumption two copies of core can coexist in
56
- // one bundle, where `instanceof` silently fails — `EntryError.is()` checks
57
- // this instead.
58
- readonly uicastEntryError = true;
59
-
60
- constructor(
61
- message: string,
62
- options: { reason: EntryErrorReason; elementKey?: string; cause?: unknown },
63
- ) {
64
- super(
65
- message,
66
- options.cause !== undefined ? { cause: options.cause } : undefined,
67
- );
68
- this.name = "EntryError";
69
- this.reason = options.reason;
70
- this.elementKey = options.elementKey;
71
- }
72
-
73
- /** Coarse verdict for recovery flows: `document` → ask the model to re-emit; `environment` → the host's problem; `unknown` → undecidable. */
74
- get fault(): EntryFault {
75
- return FAULT_BY_REASON[this.reason];
76
- }
77
-
78
- /** Cross-copy-safe `instanceof`. */
79
- static is(err: unknown): err is EntryError {
80
- return (
81
- typeof err === "object" &&
82
- err !== null &&
83
- (err as { uicastEntryError?: unknown }).uicastEntryError === true
84
- );
85
- }
86
-
87
- /**
88
- * Classify `err` as `reason`, keeping the original in `cause`. An error that
89
- * is already an EntryError passes through untouched (the deepest — most
90
- * precise — classification wins); a missing `elementKey` is filled in.
91
- */
92
- static wrap(
93
- err: unknown,
94
- reason: EntryErrorReason,
95
- elementKey?: string,
96
- ): EntryError {
97
- if (EntryError.is(err)) {
98
- if (elementKey && !err.elementKey) err.elementKey = elementKey;
99
- return err;
100
- }
101
- const message = err instanceof Error ? err.message : String(err);
102
- return new EntryError(message, { reason, elementKey, cause: err });
103
- }
104
- }