@uicast/core 0.0.1-beta.2 → 0.0.1
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/CHANGELOG.md +3 -13
- package/README.md +120 -1
- package/dist/build-elements-by-key.d.ts +13 -0
- package/dist/build-elements-by-key.js +39 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +4 -0
- package/dist/create-component-definition.d.ts +26 -0
- package/dist/create-component-definition.js +74 -0
- package/dist/entry-error.d.ts +41 -31
- package/dist/entry-error.js +49 -20
- package/dist/entry-shape.d.ts +7 -0
- package/dist/entry-shape.js +88 -0
- package/dist/expr/evaluate.d.ts +4 -6
- package/dist/expr/evaluate.js +36 -46
- package/dist/expr/extract-deps.d.ts +3 -1
- package/dist/expr/extract-deps.js +20 -20
- package/dist/expr/plan-step-waves.d.ts +8 -0
- package/dist/expr/plan-step-waves.js +45 -0
- package/dist/index.d.ts +7 -8
- package/dist/index.js +5 -9
- package/dist/internal.d.ts +12 -0
- package/dist/internal.js +13 -0
- package/dist/json-schema.d.ts +58 -0
- package/dist/json-schema.js +73 -0
- package/dist/prompt/format.d.ts +5 -0
- package/dist/prompt/format.js +22 -0
- package/dist/prompt/get-common-instructions-partial-prompt.d.ts +20 -10
- package/dist/prompt/get-common-instructions-partial-prompt.js +11 -9
- package/dist/prompt/get-components-partial-prompt.d.ts +20 -28
- package/dist/prompt/get-components-partial-prompt.js +122 -123
- package/dist/prompt/get-edit-request-prompt.d.ts +16 -16
- package/dist/prompt/get-edit-request-prompt.js +11 -18
- package/dist/prompt/get-error-recovery-prompt.d.ts +25 -14
- package/dist/prompt/get-error-recovery-prompt.js +17 -14
- package/dist/prompt/get-expressions-partial-prompt.d.ts +19 -20
- package/dist/prompt/get-expressions-partial-prompt.js +11 -21
- package/dist/prompt/get-functions-partial-prompt.d.ts +16 -16
- package/dist/prompt/get-functions-partial-prompt.js +52 -40
- package/dist/prompt/get-scope-partial-prompt.d.ts +17 -17
- package/dist/prompt/get-scope-partial-prompt.js +21 -29
- package/dist/prompt/index.js +0 -2
- package/dist/prompt/json-schema-to-ts.d.ts +6 -0
- package/dist/prompt/json-schema-to-ts.js +146 -0
- package/dist/prompt/md/EXPRESSIONS.json +1 -1
- package/dist/prompt/md/INSTRUCTIONS.json +1 -1
- package/dist/prompt/shared-types.d.ts +5 -0
- package/dist/prompt/shared-types.js +82 -0
- package/dist/scope/create-proxy-scope.d.ts +52 -17
- package/dist/scope/create-proxy-scope.js +280 -86
- package/dist/scope/parse-scope.d.ts +2 -20
- package/dist/scope/parse-scope.js +10 -35
- package/dist/scope/parse-set-address.d.ts +16 -0
- package/dist/scope/parse-set-address.js +52 -0
- package/dist/stream-json-lines.d.ts +14 -0
- package/dist/stream-json-lines.js +59 -0
- package/dist/types.d.ts +81 -46
- package/dist/types.js +6 -9
- package/dist/url-policy.d.ts +45 -0
- package/dist/url-policy.js +252 -0
- package/package.json +18 -16
- package/dist/def/create-component-definition.d.ts +0 -9
- package/dist/def/create-component-definition.js +0 -10
- package/dist/expr/analyze.d.ts +0 -5
- package/dist/expr/analyze.js +0 -189
- package/dist/expr/ast-utils.d.ts +0 -4
- package/dist/expr/ast-utils.js +0 -23
- package/dist/expr/globals.d.ts +0 -3
- package/dist/expr/globals.js +0 -111
- package/dist/expr/safer-eval.d.ts +0 -21
- package/dist/expr/safer-eval.js +0 -170
- package/dist/expr/validate.d.ts +0 -7
- package/dist/expr/validate.js +0 -109
- package/dist/prompt-utils/json-schema-to-ts.d.ts +0 -73
- package/dist/prompt-utils/json-schema-to-ts.js +0 -199
- package/dist/stream/stream-json-lines.d.ts +0 -1
- package/dist/stream/stream-json-lines.js +0 -94
- package/dist/utils/utils.d.ts +0 -13
- package/dist/utils/utils.js +0 -58
|
@@ -1,20 +1,55 @@
|
|
|
1
|
-
type EventHandler<T = unknown> = (payload: T) => void;
|
|
2
|
-
interface ChangePayload<T = unknown> {
|
|
3
|
-
path: string;
|
|
4
|
-
value: T;
|
|
5
|
-
oldValue: T;
|
|
6
|
-
}
|
|
7
1
|
interface Emitter {
|
|
8
|
-
on
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
on(field: string, handler: () => void): () => void;
|
|
3
|
+
emit(field: string): void;
|
|
4
|
+
readonly emits: number;
|
|
11
5
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
6
|
+
type SetOptions = {
|
|
7
|
+
default?: boolean;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* A scope: its fields as plain properties, plus `$$emitter` and `$$set`. A write to a field emits it; a write inside
|
|
11
|
+
* a field's value changes plain data and emits nothing.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const root: ReactiveProxy = createProxyScope();
|
|
15
|
+
* const off = root.$$emitter.on("user", () => console.log(root.user));
|
|
16
|
+
*/
|
|
17
|
+
type ReactiveProxy<T extends object = Record<string, unknown>> = T & {
|
|
18
|
+
/** Field subscriptions, e.g. `scope.$$emitter.on("user", handler)`, which returns the unsubscribe function. */
|
|
19
|
+
$$emitter: Pick<Emitter, "on">;
|
|
20
|
+
/** Writes a field, like assignment. `{ default: true }` writes only while it is undefined (first writer wins). */
|
|
21
|
+
$$set: (field: string, value: unknown, options?: SetOptions) => void;
|
|
22
|
+
};
|
|
23
|
+
declare const countEmits: (scope: ReactiveProxy) => number;
|
|
24
|
+
/**
|
|
25
|
+
* Creates a reactive scope: a shallow proxy over `bag`. A write to a field emits it, so every element reading that
|
|
26
|
+
* field re-renders.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* const userCtx = createProxyScope({ name: "Hopper", plan: "pro" });
|
|
30
|
+
* const init: InitFn = ({ scopes }) => { scopes.userCtx = userCtx; }; // read as scopes.userCtx.name
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* userCtx.plan = "free"; // emits "plan"
|
|
34
|
+
* userCtx.$$set("plan", "pro", { default: true }); // no-op: "plan" is set
|
|
35
|
+
*/
|
|
36
|
+
declare function createProxyScope<T extends object>(bag?: T): ReactiveProxy<T>;
|
|
37
|
+
type ForwardTarget = {
|
|
38
|
+
scope: ReactiveProxy;
|
|
39
|
+
field: string;
|
|
40
|
+
};
|
|
41
|
+
type RowScope = {
|
|
42
|
+
proxy: ReactiveProxy<Record<string, unknown>>;
|
|
43
|
+
retarget(element: unknown): boolean;
|
|
44
|
+
see(scopes: Record<string, ReactiveProxy>, sources: readonly string[]): void;
|
|
45
|
+
detach(): void;
|
|
46
|
+
};
|
|
47
|
+
declare const unwrapRows: (value: unknown) => unknown;
|
|
48
|
+
declare const getForwardTargets: (proxy: ReactiveProxy) => ForwardTarget[];
|
|
49
|
+
declare function createRowScope(): RowScope;
|
|
50
|
+
type RowState = {
|
|
51
|
+
proxy: ReactiveProxy<Record<string, unknown>>;
|
|
52
|
+
retarget(element: unknown, index: number, id: string | number): void;
|
|
18
53
|
};
|
|
19
|
-
declare function
|
|
20
|
-
export { createProxyScope,
|
|
54
|
+
declare function createRowState(): RowState;
|
|
55
|
+
export { createProxyScope, createRowScope, createRowState, countEmits, getForwardTargets, unwrapRows, type ReactiveProxy, type RowScope, type RowState, };
|
|
@@ -1,101 +1,295 @@
|
|
|
1
|
+
import { EntryError } from "../entry-error.js";
|
|
2
|
+
import { PROTOTYPE_KEYS, ROW_FIELDS } from "./parse-set-address.js";
|
|
1
3
|
function createEmitter() {
|
|
2
4
|
const events = new Map();
|
|
5
|
+
let emits = 0;
|
|
3
6
|
return {
|
|
4
|
-
|
|
5
|
-
|
|
7
|
+
get emits() {
|
|
8
|
+
return emits;
|
|
9
|
+
},
|
|
10
|
+
on(field, handler) {
|
|
11
|
+
const handlers = events.get(field);
|
|
6
12
|
if (handlers)
|
|
7
13
|
handlers.add(handler);
|
|
8
14
|
else
|
|
9
|
-
events.set(
|
|
10
|
-
return () =>
|
|
11
|
-
},
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
}
|
|
15
|
+
events.set(field, new Set([handler]));
|
|
16
|
+
return () => events.get(field)?.delete(handler);
|
|
17
|
+
},
|
|
18
|
+
emit(field) {
|
|
19
|
+
emits++;
|
|
20
|
+
for (const fn of events.get(field) ?? [])
|
|
21
|
+
fn();
|
|
22
|
+
for (const fn of events.get("*") ?? [])
|
|
23
|
+
fn();
|
|
33
24
|
},
|
|
34
25
|
};
|
|
35
26
|
}
|
|
36
|
-
|
|
27
|
+
const isObject = (value) => value !== null && typeof value === "object";
|
|
28
|
+
// Every scope's emitter is `createEmitter`'s, so the count is there.
|
|
29
|
+
const countEmits = (scope) => scope.$$emitter.emits;
|
|
30
|
+
function assertField(field) {
|
|
31
|
+
if (PROTOTYPE_KEYS.has(field)) {
|
|
32
|
+
throw new EntryError(`Cannot set "${field}": it reaches the prototype chain.`, {
|
|
33
|
+
reason: "guardrail-violation",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
if (field.includes(".")) {
|
|
37
|
+
throw new EntryError(`Cannot set "${field}": a scope field has no dots. Write the whole field.`, {
|
|
38
|
+
reason: "guardrail-violation",
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Creates a reactive scope: a shallow proxy over `bag`. A write to a field emits it, so every element reading that
|
|
44
|
+
* field re-renders.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* const userCtx = createProxyScope({ name: "Hopper", plan: "pro" });
|
|
48
|
+
* const init: InitFn = ({ scopes }) => { scopes.userCtx = userCtx; }; // read as scopes.userCtx.name
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* userCtx.plan = "free"; // emits "plan"
|
|
52
|
+
* userCtx.$$set("plan", "pro", { default: true }); // no-op: "plan" is set
|
|
53
|
+
*/
|
|
54
|
+
function createProxyScope(bag = {}) {
|
|
37
55
|
const emitter = createEmitter();
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
56
|
+
const fields = bag;
|
|
57
|
+
const write = (field, value, options) => {
|
|
58
|
+
assertField(field);
|
|
59
|
+
const oldValue = fields[field];
|
|
60
|
+
if (options?.default && oldValue !== undefined)
|
|
61
|
+
return;
|
|
62
|
+
if (Object.is(oldValue, value))
|
|
63
|
+
return;
|
|
64
|
+
fields[field] = value;
|
|
65
|
+
emitter.emit(field);
|
|
66
|
+
};
|
|
67
|
+
return new Proxy(bag, {
|
|
68
|
+
get(target, prop, receiver) {
|
|
69
|
+
if (prop === "$$emitter")
|
|
70
|
+
return emitter;
|
|
71
|
+
if (prop === "$$set")
|
|
72
|
+
return write;
|
|
73
|
+
return Reflect.get(target, prop, receiver);
|
|
74
|
+
},
|
|
75
|
+
set(target, prop, value) {
|
|
76
|
+
if (typeof prop !== "string")
|
|
77
|
+
return Reflect.set(target, prop, value);
|
|
78
|
+
write(prop, value);
|
|
79
|
+
return true;
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
// Deeper than any result the evaluator lets out.
|
|
84
|
+
const MAX_DATA_DEPTH = 256;
|
|
85
|
+
// `value` with each part `swap` changes replaced, down to `depth` levels, copying what holds it; the same value when
|
|
86
|
+
// nothing changes.
|
|
87
|
+
function rebuild(value, swap, depth) {
|
|
88
|
+
const swapped = swap(value);
|
|
89
|
+
if (swapped !== value || depth === 0 || !isObject(value))
|
|
90
|
+
return swapped;
|
|
91
|
+
if (Array.isArray(value)) {
|
|
92
|
+
let out = null;
|
|
93
|
+
for (let i = 0; i < value.length; i++) {
|
|
94
|
+
const next = rebuild(value[i], swap, depth - 1);
|
|
95
|
+
if (next === value[i])
|
|
96
|
+
continue;
|
|
97
|
+
out ??= [...value];
|
|
98
|
+
out[i] = next;
|
|
99
|
+
}
|
|
100
|
+
return out ?? value;
|
|
101
|
+
}
|
|
102
|
+
let out = null;
|
|
103
|
+
for (const [key, child] of Object.entries(value)) {
|
|
104
|
+
const next = rebuild(child, swap, depth - 1);
|
|
105
|
+
if (next === child)
|
|
106
|
+
continue;
|
|
107
|
+
out ??= { ...value };
|
|
108
|
+
out[key] = next;
|
|
109
|
+
}
|
|
110
|
+
return out ?? value;
|
|
111
|
+
}
|
|
112
|
+
// How many levels below `value` the item first appears, or -1. Level by level, each object once, so a cycle ends.
|
|
113
|
+
function levelOf(value, item) {
|
|
114
|
+
if (value === item)
|
|
115
|
+
return 0;
|
|
116
|
+
const seen = new WeakSet();
|
|
117
|
+
let level = [value];
|
|
118
|
+
for (let depth = 1; level.length > 0; depth++) {
|
|
119
|
+
const next = [];
|
|
120
|
+
for (const node of level) {
|
|
121
|
+
if (!isObject(node) || seen.has(node))
|
|
122
|
+
continue;
|
|
123
|
+
seen.add(node);
|
|
124
|
+
for (const child of Array.isArray(node) ? node : Object.values(node)) {
|
|
125
|
+
if (child === item)
|
|
126
|
+
return depth;
|
|
127
|
+
if (isObject(child))
|
|
128
|
+
next.push(child);
|
|
46
129
|
}
|
|
47
|
-
current = current[key];
|
|
48
130
|
}
|
|
49
|
-
|
|
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;
|
|
131
|
+
level = next;
|
|
56
132
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
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;
|
|
133
|
+
return -1;
|
|
134
|
+
}
|
|
135
|
+
// Follows `path` down own keys, then replaces the item wherever it first appears below.
|
|
136
|
+
function replaceAt(value, path, from, to) {
|
|
137
|
+
if (path.length === 0) {
|
|
138
|
+
const depth = levelOf(value, from);
|
|
139
|
+
return depth === -1 ? value : rebuild(value, (part) => (part === from ? to : part), depth);
|
|
97
140
|
}
|
|
98
|
-
const
|
|
99
|
-
|
|
141
|
+
const [key, ...rest] = path;
|
|
142
|
+
if (!isObject(value) || !Object.hasOwn(value, key))
|
|
143
|
+
return value;
|
|
144
|
+
const next = replaceAt(value[key], rest, from, to);
|
|
145
|
+
if (next === value[key])
|
|
146
|
+
return value;
|
|
147
|
+
return Array.isArray(value) ? Object.assign([...value], { [key]: next }) : { ...value, [key]: next };
|
|
148
|
+
}
|
|
149
|
+
const forwardTargetsByProxy = new WeakMap();
|
|
150
|
+
// A row window's current item.
|
|
151
|
+
const itemOfWindow = new WeakMap();
|
|
152
|
+
// Every row window in `value` swapped for the item it shows now: a result holds the data, not a live view that a
|
|
153
|
+
// later write changes behind the memo.
|
|
154
|
+
const unwrapRows = (value) => rebuild(value, (part) => (isObject(part) ? (itemOfWindow.get(part)?.() ?? part) : part), MAX_DATA_DEPTH);
|
|
155
|
+
// The fields a row's write replaces: those its list's `each` reads. Empty for a root-kind scope.
|
|
156
|
+
const getForwardTargets = (proxy) => forwardTargetsByProxy.get(proxy)?.() ?? [];
|
|
157
|
+
const ENGINE_DESCRIPTOR = { writable: false, enumerable: false, configurable: true };
|
|
158
|
+
// A write never changes the item: it puts a copy in the fields `each` reads. A field of an outer row is that row's
|
|
159
|
+
// write, so a nested edit climbs to the outermost array.
|
|
160
|
+
function createRowScope() {
|
|
161
|
+
const emitter = createEmitter();
|
|
162
|
+
let element;
|
|
163
|
+
let visible = {};
|
|
164
|
+
let reads = [];
|
|
165
|
+
let detached = false;
|
|
166
|
+
// "scopes.root.data.items" → root's `data` field, then `items` below it. A bare scope names no field.
|
|
167
|
+
const sources = () => {
|
|
168
|
+
const out = [];
|
|
169
|
+
for (const read of reads) {
|
|
170
|
+
const [name, field, ...path] = read.split(".").slice(1);
|
|
171
|
+
const scope = Object.hasOwn(visible, name) ? visible[name] : undefined;
|
|
172
|
+
if (scope && field !== undefined && Object.hasOwn(scope, field))
|
|
173
|
+
out.push({ scope, field, path });
|
|
174
|
+
}
|
|
175
|
+
return out;
|
|
176
|
+
};
|
|
177
|
+
const write = (field, value, options) => {
|
|
178
|
+
assertField(field);
|
|
179
|
+
if (!isObject(element) || Array.isArray(element)) {
|
|
180
|
+
const kind = element === null ? "null" : Array.isArray(element) ? "an array" : `a ${typeof element}`;
|
|
181
|
+
throw new EntryError(`Cannot set "${field}": this row holds ${kind}, not an object. Replace it through the array it came from.`, { reason: "guardrail-violation" });
|
|
182
|
+
}
|
|
183
|
+
const oldValue = element[field];
|
|
184
|
+
if (options?.default && oldValue !== undefined)
|
|
185
|
+
return;
|
|
186
|
+
if (Object.is(oldValue, value))
|
|
187
|
+
return;
|
|
188
|
+
const copy = { ...element, [field]: value };
|
|
189
|
+
// Two sources may share a field (`data.a`, `data.b`), so each builds on the last.
|
|
190
|
+
const next = new Map();
|
|
191
|
+
for (const { scope, field: name, path } of detached ? [] : sources()) {
|
|
192
|
+
const pending = next.get(scope);
|
|
193
|
+
const current = pending?.has(name) ? pending.get(name) : scope[name];
|
|
194
|
+
const replaced = replaceAt(current, path, element, copy);
|
|
195
|
+
if (replaced !== current)
|
|
196
|
+
next.set(scope, (pending ?? new Map()).set(name, replaced));
|
|
197
|
+
}
|
|
198
|
+
if (next.size === 0) {
|
|
199
|
+
throw new EntryError(`Cannot set "${field}": this item is in no field "each" reads — the row was removed, or "each" built a new object. Edit the array it came from.`, { reason: "unknown-reference" });
|
|
200
|
+
}
|
|
201
|
+
for (const [scope, fields] of next)
|
|
202
|
+
for (const [name, replaced] of fields)
|
|
203
|
+
scope.$$set(name, replaced);
|
|
204
|
+
element = copy;
|
|
205
|
+
emitter.emit(field);
|
|
206
|
+
};
|
|
207
|
+
const proxy = new Proxy({}, {
|
|
208
|
+
get(_, prop) {
|
|
209
|
+
if (prop === "$$emitter")
|
|
210
|
+
return emitter;
|
|
211
|
+
if (prop === "$$set")
|
|
212
|
+
return write;
|
|
213
|
+
if (typeof prop !== "string")
|
|
214
|
+
return undefined;
|
|
215
|
+
return isObject(element) ? element[prop] : undefined;
|
|
216
|
+
},
|
|
217
|
+
has(_, prop) {
|
|
218
|
+
return typeof prop === "string" && isObject(element) && Object.hasOwn(element, prop);
|
|
219
|
+
},
|
|
220
|
+
ownKeys() {
|
|
221
|
+
return isObject(element) ? Reflect.ownKeys(element) : [];
|
|
222
|
+
},
|
|
223
|
+
getOwnPropertyDescriptor(_, prop) {
|
|
224
|
+
if (typeof prop !== "string" || !isObject(element))
|
|
225
|
+
return undefined;
|
|
226
|
+
const desc = Object.getOwnPropertyDescriptor(element, prop);
|
|
227
|
+
// The proxy target has no such property, so it must be reported configurable.
|
|
228
|
+
return desc ? { ...desc, configurable: true } : undefined;
|
|
229
|
+
},
|
|
230
|
+
set(_, prop, value) {
|
|
231
|
+
if (typeof prop !== "string")
|
|
232
|
+
return false;
|
|
233
|
+
write(prop, value);
|
|
234
|
+
return true;
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
forwardTargetsByProxy.set(proxy, () => sources().map(({ scope, field }) => ({ scope, field })));
|
|
238
|
+
itemOfWindow.set(proxy, () => element);
|
|
239
|
+
return {
|
|
240
|
+
proxy,
|
|
241
|
+
retarget(nextElement) {
|
|
242
|
+
const moved = nextElement !== element;
|
|
243
|
+
element = nextElement;
|
|
244
|
+
return moved;
|
|
245
|
+
},
|
|
246
|
+
see(scopes, eachReads) {
|
|
247
|
+
visible = scopes;
|
|
248
|
+
reads = eachReads;
|
|
249
|
+
},
|
|
250
|
+
detach() {
|
|
251
|
+
detached = true;
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
// `scopes.$<as>`: the runtime's read-only `index`, `id` and `value`, then whatever the row's steps set.
|
|
256
|
+
function createRowState() {
|
|
257
|
+
const scope = createProxyScope(Object.create(null));
|
|
258
|
+
let runtime = {};
|
|
259
|
+
const write = (field, value, options) => {
|
|
260
|
+
if (ROW_FIELDS.has(field)) {
|
|
261
|
+
throw new EntryError(`Cannot set "${field}": the runtime owns it.`, { reason: "guardrail-violation" });
|
|
262
|
+
}
|
|
263
|
+
scope.$$set(field, value, options);
|
|
264
|
+
};
|
|
265
|
+
const proxy = new Proxy(scope, {
|
|
266
|
+
get(target, prop, receiver) {
|
|
267
|
+
if (prop === "$$set")
|
|
268
|
+
return write;
|
|
269
|
+
if (typeof prop === "string" && ROW_FIELDS.has(prop))
|
|
270
|
+
return runtime[prop];
|
|
271
|
+
return Reflect.get(target, prop, receiver);
|
|
272
|
+
},
|
|
273
|
+
has(target, prop) {
|
|
274
|
+
return (typeof prop === "string" && ROW_FIELDS.has(prop)) || Reflect.has(target, prop);
|
|
275
|
+
},
|
|
276
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
277
|
+
if (typeof prop === "string" && ROW_FIELDS.has(prop))
|
|
278
|
+
return { ...ENGINE_DESCRIPTOR, value: runtime[prop] };
|
|
279
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
280
|
+
},
|
|
281
|
+
set(_, prop, value) {
|
|
282
|
+
if (typeof prop !== "string")
|
|
283
|
+
return false;
|
|
284
|
+
write(prop, value);
|
|
285
|
+
return true;
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
return {
|
|
289
|
+
proxy,
|
|
290
|
+
retarget(element, index, id) {
|
|
291
|
+
runtime = { index, id, value: isObject(element) ? undefined : element };
|
|
292
|
+
},
|
|
293
|
+
};
|
|
100
294
|
}
|
|
101
|
-
export { createProxyScope,
|
|
295
|
+
export { createProxyScope, createRowScope, createRowState, countEmits, getForwardTargets, unwrapRows, };
|
|
@@ -1,20 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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 declare const parseScope: (key: string, prefix?: string) => [string, string];
|
|
1
|
+
export declare const parseScope: (key: string) => [string, string];
|
|
2
|
+
export declare const depKey: (path: string) => string;
|
|
@@ -1,36 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
}
|
|
1
|
+
const PREFIX = "scopes.";
|
|
2
|
+
// "scopes.root.user.name" → ["root", "user.name"]
|
|
3
|
+
export const parseScope = (key) => {
|
|
4
|
+
const dot = key.indexOf(".", PREFIX.length);
|
|
5
|
+
return [key.slice(PREFIX.length, dot), key.slice(dot + 1)];
|
|
6
|
+
};
|
|
7
|
+
// "scopes.root.user.name" → "scopes.root.user"; a bare scope reads every field: "scopes.root.*".
|
|
8
|
+
export const depKey = (path) => {
|
|
9
|
+
const [scope, field] = path.slice(PREFIX.length).split(".");
|
|
10
|
+
return field ? `scopes.${scope}.${field}` : `scopes.${scope}.*`;
|
|
36
11
|
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { EntryError } from "../entry-error.js";
|
|
2
|
+
import type { ComponentEntry } from "../types.js";
|
|
3
|
+
export declare const PROTOTYPE_KEYS: Set<string>;
|
|
4
|
+
export declare const ROW_FIELDS: Set<string>;
|
|
5
|
+
type SetAddressFault = {
|
|
6
|
+
kind: "shape" | "reserved" | "prototype";
|
|
7
|
+
segment?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function findSetAddressFault(address: string): SetAddressFault | null;
|
|
10
|
+
export declare function setAddressError(address: string, fault: SetAddressFault, elementKey?: string): EntryError;
|
|
11
|
+
export declare function parseSetAddress(address: string, elementKey?: string): {
|
|
12
|
+
scope: string;
|
|
13
|
+
field: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function entrySetAddressError(entry: Pick<ComponentEntry, "key" | "seed" | "callbacks">): EntryError | null;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { EntryError } from "../entry-error.js";
|
|
2
|
+
const ADDRESS = /^scopes\.([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)$/;
|
|
3
|
+
export const PROTOTYPE_KEYS = new Set([
|
|
4
|
+
"__proto__",
|
|
5
|
+
"constructor",
|
|
6
|
+
"prototype",
|
|
7
|
+
"__defineGetter__",
|
|
8
|
+
"__defineSetter__",
|
|
9
|
+
"__lookupGetter__",
|
|
10
|
+
"__lookupSetter__",
|
|
11
|
+
]);
|
|
12
|
+
// A row's `$<as>` scope: readable, never written.
|
|
13
|
+
export const ROW_FIELDS = new Set(["index", "id", "value"]);
|
|
14
|
+
export function findSetAddressFault(address) {
|
|
15
|
+
const match = ADDRESS.exec(address);
|
|
16
|
+
if (!match)
|
|
17
|
+
return { kind: "shape" };
|
|
18
|
+
const [, scope, field] = match;
|
|
19
|
+
const prototype = [scope, field].find((segment) => PROTOTYPE_KEYS.has(segment));
|
|
20
|
+
if (prototype)
|
|
21
|
+
return { kind: "prototype", segment: prototype };
|
|
22
|
+
return scope.startsWith("$") && ROW_FIELDS.has(field) ? { kind: "reserved", segment: field } : null;
|
|
23
|
+
}
|
|
24
|
+
// The message names the fix — the recovery prompt forwards it verbatim.
|
|
25
|
+
export function setAddressError(address, fault, elementKey) {
|
|
26
|
+
const messages = {
|
|
27
|
+
shape: `"set": "${address}" is not an address. A set names one field, "scopes.<scope>.<field>"; to change part of a field, write the whole field.`,
|
|
28
|
+
reserved: `"set": "${address}" writes "${fault.segment}", which the runtime owns (a row's index, id and value are read-only).`,
|
|
29
|
+
prototype: `"set": "${address}" writes through "${fault.segment}", which reaches the prototype chain.`,
|
|
30
|
+
};
|
|
31
|
+
return new EntryError(messages[fault.kind], { reason: "guardrail-violation", elementKey });
|
|
32
|
+
}
|
|
33
|
+
export function parseSetAddress(address, elementKey) {
|
|
34
|
+
const fault = findSetAddressFault(address);
|
|
35
|
+
if (fault)
|
|
36
|
+
throw setAddressError(address, fault, elementKey);
|
|
37
|
+
const [, scope, field] = ADDRESS.exec(address);
|
|
38
|
+
return { scope, field };
|
|
39
|
+
}
|
|
40
|
+
// The first bad `set` in the entry's seed and callbacks, as the error it raises.
|
|
41
|
+
export function entrySetAddressError(entry) {
|
|
42
|
+
for (const steps of [entry.seed ?? [], ...Object.values(entry.callbacks ?? {})]) {
|
|
43
|
+
for (const { set } of steps) {
|
|
44
|
+
if (set === undefined)
|
|
45
|
+
continue;
|
|
46
|
+
const fault = findSetAddressFault(set);
|
|
47
|
+
if (fault)
|
|
48
|
+
return setAddressError(set, fault, entry.key);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Yields each line of a byte or text stream that parses as JSON. Skips blank and non-JSON lines: code fences, prose,
|
|
3
|
+
* a cut-off last line.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* for await (const value of streamJsonLines(result.textStream)) {
|
|
7
|
+
* if (isComponentEntry(value)) await saveEntry(value);
|
|
8
|
+
* }
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* if (!res.body) throw new Error(`Generation failed (${res.status})`);
|
|
12
|
+
* const entries = streamJsonLines<ComponentEntry>(res.body);
|
|
13
|
+
*/
|
|
14
|
+
export declare function streamJsonLines<T = unknown>(source: ReadableStream<Uint8Array | string> | AsyncIterable<string | Uint8Array>): AsyncGenerator<T>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Yields each line of a byte or text stream that parses as JSON. Skips blank and non-JSON lines: code fences, prose,
|
|
3
|
+
* a cut-off last line.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* for await (const value of streamJsonLines(result.textStream)) {
|
|
7
|
+
* if (isComponentEntry(value)) await saveEntry(value);
|
|
8
|
+
* }
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* if (!res.body) throw new Error(`Generation failed (${res.status})`);
|
|
12
|
+
* const entries = streamJsonLines<ComponentEntry>(res.body);
|
|
13
|
+
*/
|
|
14
|
+
export async function* streamJsonLines(source) {
|
|
15
|
+
const decoder = new TextDecoder();
|
|
16
|
+
let buffer = "";
|
|
17
|
+
for await (const chunk of toAsyncIterable(source)) {
|
|
18
|
+
buffer += typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
|
|
19
|
+
let nl = buffer.indexOf("\n");
|
|
20
|
+
while (nl !== -1) {
|
|
21
|
+
yield* parseLine(buffer.slice(0, nl));
|
|
22
|
+
buffer = buffer.slice(nl + 1);
|
|
23
|
+
nl = buffer.indexOf("\n");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
yield* parseLine(buffer + decoder.decode());
|
|
27
|
+
}
|
|
28
|
+
function* parseLine(raw) {
|
|
29
|
+
const line = raw.trim();
|
|
30
|
+
if (!line)
|
|
31
|
+
return;
|
|
32
|
+
try {
|
|
33
|
+
yield JSON.parse(line);
|
|
34
|
+
}
|
|
35
|
+
catch { }
|
|
36
|
+
}
|
|
37
|
+
function toAsyncIterable(source) {
|
|
38
|
+
if (Symbol.asyncIterator in source) {
|
|
39
|
+
return source;
|
|
40
|
+
}
|
|
41
|
+
// Not every browser's ReadableStream is async-iterable.
|
|
42
|
+
const stream = source;
|
|
43
|
+
return {
|
|
44
|
+
async *[Symbol.asyncIterator]() {
|
|
45
|
+
const reader = stream.getReader();
|
|
46
|
+
try {
|
|
47
|
+
for (;;) {
|
|
48
|
+
const { done, value } = await reader.read();
|
|
49
|
+
if (done)
|
|
50
|
+
return;
|
|
51
|
+
yield value;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
reader.releaseLock();
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|