@jigs.design/runtime 0.1.2 → 0.2.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/README.md +8 -11
- package/dist/index.d.ts +1 -5
- package/dist/index.js +1 -5
- package/dist/runtime-core.d.ts +1 -1
- package/dist/tree-store.d.ts +1 -2
- package/dist/tree-store.js +8 -8
- package/dist/validate.js +1 -1
- package/package.json +5 -2
- package/dist/parse.d.ts +0 -9
- package/dist/parse.js +0 -150
- package/dist/protocol.d.ts +0 -132
- package/dist/protocol.js +0 -4
- package/dist/types.d.ts +0 -168
- package/dist/types.js +0 -22
- package/dist/urls.d.ts +0 -14
- package/dist/urls.js +0 -59
package/README.md
CHANGED
|
@@ -1,29 +1,26 @@
|
|
|
1
1
|
# @jigs.design/runtime
|
|
2
2
|
|
|
3
|
-
The
|
|
3
|
+
The sandbox a program runs in: the frame document and its content security policy, the worker runtime, and the tree store the page draws from. The types it speaks are in `@jigs.design/protocol`, its only dependency. It imports neither the compiler nor the prompt.
|
|
4
4
|
|
|
5
|
-
`@jigs.design/
|
|
5
|
+
`@jigs.design/client` uses the tree store and the policy; `@jigs.design/server` serves the frame. An app rarely imports it directly.
|
|
6
6
|
|
|
7
7
|
## Entry points
|
|
8
8
|
|
|
9
|
-
| Entry | What it holds
|
|
10
|
-
| ---------------------------- |
|
|
11
|
-
| `@jigs.design/runtime` | `
|
|
12
|
-
| `@jigs.design/runtime/frame` | The sandbox frame document, its content security policy, the worker runtime, and `WATCHDOG_MS` and `HEARTBEAT_MS`. The server serves it
|
|
9
|
+
| Entry | What it holds |
|
|
10
|
+
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| `@jigs.design/runtime` | `createTreeStore` with its caps `MAX_NODES`, `MAX_OPS` and `MAX_TEXT`, the `TreeNode` and `TreeStore` types, and `frameSecurityPolicy` |
|
|
12
|
+
| `@jigs.design/runtime/frame` | The sandbox frame document, its content security policy, the worker runtime, and `WATCHDOG_MS` and `HEARTBEAT_MS`. The server serves it |
|
|
13
13
|
|
|
14
14
|
## How a program runs
|
|
15
15
|
|
|
16
|
-
The server serves the frame document from its route. The frame starts a worker and runs the compiled program there, with a watchdog that kills a program that stops answering. The worker sends tree ops to the page, where the tree store applies them, within the same caps on nodes, ops and text the worker keeps, and the view draws the tree. Functions never leave the worker; they cross as ids. The
|
|
16
|
+
The server serves the frame document from its route. The frame starts a worker and runs the compiled program there, with a watchdog that kills a program that stops answering. The worker sends tree ops to the page, where the tree store applies them, within the same caps on nodes, ops and text the worker keeps, and the view draws the tree. Functions never leave the worker; they cross as ids. The worker and the frame's runner ship as `Function.prototype.toString()` text, so `scripts/sandbox-check.ts` boots the built output after every build.
|
|
17
17
|
|
|
18
18
|
## Source
|
|
19
19
|
|
|
20
|
-
- `src/types.ts`: the shared types and `PROGRAM_FORMAT`.
|
|
21
|
-
- `src/protocol.ts`: messages between the page, the frame and the worker.
|
|
22
|
-
- `src/parse.ts`: checks for JSON arriving in the browser.
|
|
23
20
|
- `src/frame.ts`, `src/frame-policy.ts`: the frame document and its policy.
|
|
24
21
|
- `src/runtime-core.ts`: the runtime inside the worker.
|
|
25
22
|
- `src/tree-store.ts`: the tree the view draws.
|
|
26
|
-
- `src/
|
|
23
|
+
- `src/validate.ts`: prop checks against the program's schemas.
|
|
27
24
|
|
|
28
25
|
## Docs
|
|
29
26
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
1
|
import { frameSecurityPolicy } from "./frame-policy.js";
|
|
2
|
-
import { ChatBlock, ChatBlockStatus, ChildrenKind, ComponentSchema, Diagnostic, DiagnosticFix, Invalidation, JigsErrorCode, JigsErrorData, JsonValue, Message, NumberPropSchema, PROGRAM_FORMAT, ProcedureEntry, ProcedureKind, Program, PropSchema, SessionBody, SessionEvent, SessionOptions, Span, jigsErrorCodes } from "./types.js";
|
|
3
|
-
import { FrameMessage, HostMessage, MutationCall, MutationResult, Op, QueryRead, QueryResult, ROOT_ID, Row, RuntimeError, WireArg, WireValue, WorkerMessage } from "./protocol.js";
|
|
4
|
-
import { parseErrorData, parseInvalidations, parseProgram, parseSessionEvent } from "./parse.js";
|
|
5
2
|
import { MAX_NODES, MAX_OPS, MAX_TEXT, TreeNode, TreeStore, createTreeStore } from "./tree-store.js";
|
|
6
|
-
|
|
7
|
-
export { type ChatBlock, type ChatBlockStatus, type ChildrenKind, type ComponentSchema, type Diagnostic, type DiagnosticFix, type FrameMessage, type HostMessage, type Invalidation, type JigsErrorCode, type JigsErrorData, type JsonValue, MAX_NODES, MAX_OPS, MAX_TEXT, type Message, type MutationCall, type MutationResult, type NumberPropSchema, type Op, PROGRAM_FORMAT, type ProcedureEntry, type ProcedureKind, type Program, type PropSchema, type QueryRead, type QueryResult, ROOT_ID, type Row, type RuntimeError, type SessionBody, type SessionEvent, type SessionOptions, type Span, type TreeNode, type TreeStore, type WireArg, type WireValue, type WorkerMessage, createTreeStore, frameSecurityPolicy, isAllowedImage, isAllowedLink, isImageHostEntry, jigsErrorCodes, normalizeImageHost, parseErrorData, parseInvalidations, parseProgram, parseSessionEvent };
|
|
3
|
+
export { MAX_NODES, MAX_OPS, MAX_TEXT, type TreeNode, type TreeStore, createTreeStore, frameSecurityPolicy };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import { PROGRAM_FORMAT, jigsErrorCodes } from "./types.js";
|
|
2
|
-
import { ROOT_ID } from "./protocol.js";
|
|
3
1
|
import { frameSecurityPolicy } from "./frame-policy.js";
|
|
4
|
-
import { parseErrorData, parseInvalidations, parseProgram, parseSessionEvent } from "./parse.js";
|
|
5
|
-
import { isAllowedImage, isAllowedLink, isImageHostEntry, normalizeImageHost } from "./urls.js";
|
|
6
2
|
import { MAX_NODES, MAX_OPS, MAX_TEXT, createTreeStore } from "./tree-store.js";
|
|
7
|
-
export { MAX_NODES, MAX_OPS, MAX_TEXT,
|
|
3
|
+
export { MAX_NODES, MAX_OPS, MAX_TEXT, createTreeStore, frameSecurityPolicy };
|
package/dist/runtime-core.d.ts
CHANGED
package/dist/tree-store.d.ts
CHANGED
package/dist/tree-store.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import "./protocol.js";
|
|
2
1
|
import { acceptsProp } from "./validate.js";
|
|
2
|
+
import { ROOT_ID } from "@jigs.design/protocol";
|
|
3
3
|
//#region src/tree-store.ts
|
|
4
4
|
const ROOT_TYPE = "#root";
|
|
5
5
|
const TEXT_TYPE = "#text";
|
|
@@ -46,7 +46,7 @@ function isWellFormed(op) {
|
|
|
46
46
|
}
|
|
47
47
|
const rootNode = () => ({
|
|
48
48
|
children: [],
|
|
49
|
-
id:
|
|
49
|
+
id: ROOT_ID,
|
|
50
50
|
props: {},
|
|
51
51
|
text: "",
|
|
52
52
|
type: ROOT_TYPE
|
|
@@ -107,7 +107,7 @@ const MAX_WALK_STEPS = 64 * MAX_OPS;
|
|
|
107
107
|
* `useSyncExternalStore` snapshot.
|
|
108
108
|
*/
|
|
109
109
|
function createTreeStore() {
|
|
110
|
-
const nodes = /* @__PURE__ */ new Map([[
|
|
110
|
+
const nodes = /* @__PURE__ */ new Map([[ROOT_ID, rootNode()]]);
|
|
111
111
|
const parents = /* @__PURE__ */ new Map();
|
|
112
112
|
/** Children being rebuilt by the current `apply`, by parent. */
|
|
113
113
|
const drafts = /* @__PURE__ */ new Map();
|
|
@@ -216,7 +216,7 @@ function createTreeStore() {
|
|
|
216
216
|
reportTooMany();
|
|
217
217
|
for (let index = created.length - 1; index >= 0; index--) {
|
|
218
218
|
if (nodes.size <= 10001) break;
|
|
219
|
-
const id = created[index] ??
|
|
219
|
+
const id = created[index] ?? ROOT_ID;
|
|
220
220
|
if (nodes.has(id)) {
|
|
221
221
|
parents.delete(id);
|
|
222
222
|
removeSubtree(id);
|
|
@@ -230,7 +230,7 @@ function createTreeStore() {
|
|
|
230
230
|
return;
|
|
231
231
|
}
|
|
232
232
|
if (op.op === "create" || op.op === "createText") {
|
|
233
|
-
if (op.id ===
|
|
233
|
+
if (op.id === ROOT_ID || nodes.has(op.id)) {
|
|
234
234
|
report("The sandbox reused a node id; the operation was dropped");
|
|
235
235
|
return;
|
|
236
236
|
}
|
|
@@ -276,13 +276,13 @@ function createTreeStore() {
|
|
|
276
276
|
} : node);
|
|
277
277
|
return;
|
|
278
278
|
case "place":
|
|
279
|
-
if (!nodes.has(op.id) || !nodes.has(op.parent) || op.id ===
|
|
279
|
+
if (!nodes.has(op.id) || !nodes.has(op.parent) || op.id === ROOT_ID || isWithin(op.parent, op.id)) return;
|
|
280
280
|
detach(op.id);
|
|
281
281
|
if (!insert(draftOf(op.parent), op.id, op.index)) slow++;
|
|
282
282
|
parents.set(op.id, op.parent);
|
|
283
283
|
return;
|
|
284
284
|
case "remove":
|
|
285
|
-
if (op.id ===
|
|
285
|
+
if (op.id === ROOT_ID) return;
|
|
286
286
|
detach(op.id);
|
|
287
287
|
removeSubtree(op.id);
|
|
288
288
|
return;
|
|
@@ -320,7 +320,7 @@ function createTreeStore() {
|
|
|
320
320
|
nodes.clear();
|
|
321
321
|
parents.clear();
|
|
322
322
|
drafts.clear();
|
|
323
|
-
nodes.set(
|
|
323
|
+
nodes.set(ROOT_ID, rootNode());
|
|
324
324
|
issues = [];
|
|
325
325
|
notify();
|
|
326
326
|
},
|
package/dist/validate.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jigs.design/runtime",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "The
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "The sandbox: the frame document, the worker runtime that runs a compiled program, and the tree store the page draws from.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -27,5 +27,8 @@
|
|
|
27
27
|
},
|
|
28
28
|
"publishConfig": {
|
|
29
29
|
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@jigs.design/protocol": "0.2.0"
|
|
30
33
|
}
|
|
31
34
|
}
|
package/dist/parse.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { Invalidation, JigsErrorData, Program, SessionEvent } from "./types.js";
|
|
2
|
-
//#region src/parse.d.ts
|
|
3
|
-
export declare function parseErrorData(value: unknown): JigsErrorData | null;
|
|
4
|
-
/** The `invalidate` list of a mutation's reply. */
|
|
5
|
-
export declare function parseInvalidations(value: unknown): ReadonlyArray<Invalidation> | null;
|
|
6
|
-
export declare function parseProgram(value: unknown): Program | null;
|
|
7
|
-
/** One session event from its JSON text, or null when it is not one. */
|
|
8
|
-
export declare function parseSessionEvent(text: string): SessionEvent | null;
|
|
9
|
-
//#endregion
|
package/dist/parse.js
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { jigsErrorCodes } from "./types.js";
|
|
2
|
-
//#region src/parse.ts
|
|
3
|
-
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
|
-
const isString = (value) => typeof value === "string";
|
|
5
|
-
const isStringList = (value) => Array.isArray(value) && value.every(isString);
|
|
6
|
-
function recordOf(value, item) {
|
|
7
|
-
if (!isRecord(value)) return null;
|
|
8
|
-
const out = {};
|
|
9
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
10
|
-
const parsed = item(entry);
|
|
11
|
-
if (parsed === null) return null;
|
|
12
|
-
out[key] = parsed;
|
|
13
|
-
}
|
|
14
|
-
return out;
|
|
15
|
-
}
|
|
16
|
-
const isErrorCode = (value) => jigsErrorCodes.some((code) => code === value);
|
|
17
|
-
function parseErrorData(value) {
|
|
18
|
-
if (!isRecord(value) || !isErrorCode(value.code) || !isString(value.message)) return null;
|
|
19
|
-
return {
|
|
20
|
-
code: value.code,
|
|
21
|
-
message: value.message
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
function parseInvalidation(value) {
|
|
25
|
-
if (!isRecord(value) || !isString(value.name)) return null;
|
|
26
|
-
if (!("input" in value)) return { name: value.name };
|
|
27
|
-
return {
|
|
28
|
-
input: value.input,
|
|
29
|
-
name: value.name
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
/** The `invalidate` list of a mutation's reply. */
|
|
33
|
-
function parseInvalidations(value) {
|
|
34
|
-
if (!Array.isArray(value)) return null;
|
|
35
|
-
const out = [];
|
|
36
|
-
for (const entry of value) {
|
|
37
|
-
const parsed = parseInvalidation(entry);
|
|
38
|
-
if (parsed === null) return null;
|
|
39
|
-
out.push(parsed);
|
|
40
|
-
}
|
|
41
|
-
return out;
|
|
42
|
-
}
|
|
43
|
-
function parsePropSchema(value) {
|
|
44
|
-
if (!isRecord(value)) return null;
|
|
45
|
-
switch (value.kind) {
|
|
46
|
-
case "boolean":
|
|
47
|
-
case "handler":
|
|
48
|
-
case "image":
|
|
49
|
-
case "link":
|
|
50
|
-
case "list":
|
|
51
|
-
case "string": return { kind: value.kind };
|
|
52
|
-
case "enum": return isStringList(value.values) ? {
|
|
53
|
-
kind: "enum",
|
|
54
|
-
values: value.values
|
|
55
|
-
} : null;
|
|
56
|
-
case "number": {
|
|
57
|
-
const schema = { kind: "number" };
|
|
58
|
-
if (typeof value.min === "number") schema.min = value.min;
|
|
59
|
-
if (typeof value.max === "number") schema.max = value.max;
|
|
60
|
-
return schema;
|
|
61
|
-
}
|
|
62
|
-
default: return null;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
const isChildrenKind = (value) => value === "block" || value === "inline" || value === "none";
|
|
66
|
-
function parseComponentSchema(value) {
|
|
67
|
-
if (!isRecord(value) || !isChildrenKind(value.children)) return null;
|
|
68
|
-
const props = recordOf(value.props, parsePropSchema);
|
|
69
|
-
return props === null ? null : {
|
|
70
|
-
children: value.children,
|
|
71
|
-
props
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
function parseProcedure(value) {
|
|
75
|
-
if (!isRecord(value) || value.kind !== "query" && value.kind !== "mutation" && value.kind !== "action" || !isStringList(value.invalidates)) return null;
|
|
76
|
-
return {
|
|
77
|
-
invalidates: value.invalidates,
|
|
78
|
-
kind: value.kind
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
function parseProgram(value) {
|
|
82
|
-
if (!isRecord(value) || value.format !== 1 || !isString(value.code) || !isStringList(value.imageHosts)) return null;
|
|
83
|
-
const components = recordOf(value.components, parseComponentSchema);
|
|
84
|
-
const procedures = recordOf(value.procedures, parseProcedure);
|
|
85
|
-
const prose = recordOf(value.prose, (entry) => isString(entry) ? entry : null);
|
|
86
|
-
if (components === null || procedures === null || prose === null) return null;
|
|
87
|
-
return {
|
|
88
|
-
code: value.code,
|
|
89
|
-
components,
|
|
90
|
-
format: 1,
|
|
91
|
-
imageHosts: value.imageHosts,
|
|
92
|
-
procedures,
|
|
93
|
-
prose
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
function parseDiagnostic(value) {
|
|
97
|
-
if (!isRecord(value) || !isString(value.code) || !isString(value.message) || value.severity !== "error" && value.severity !== "warning" && value.severity !== "info" && value.severity !== "hint" || !isRecord(value.span) || typeof value.span.line !== "number") return null;
|
|
98
|
-
return {
|
|
99
|
-
code: value.code,
|
|
100
|
-
message: value.message,
|
|
101
|
-
severity: value.severity,
|
|
102
|
-
span: { line: value.span.line }
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
/** The diagnostics that parse; one the browser cannot read is skipped, not the event. */
|
|
106
|
-
function parseDiagnostics(value) {
|
|
107
|
-
if (!Array.isArray(value)) return null;
|
|
108
|
-
return value.flatMap((entry) => {
|
|
109
|
-
const parsed = parseDiagnostic(entry);
|
|
110
|
-
return parsed === null ? [] : [parsed];
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
/** One session event from its JSON text, or null when it is not one. */
|
|
114
|
-
function parseSessionEvent(text) {
|
|
115
|
-
let value;
|
|
116
|
-
try {
|
|
117
|
-
value = JSON.parse(text);
|
|
118
|
-
} catch {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
if (!isRecord(value)) return null;
|
|
122
|
-
if (value.type === "error") {
|
|
123
|
-
const error = parseErrorData(value.error);
|
|
124
|
-
return error === null ? null : {
|
|
125
|
-
error,
|
|
126
|
-
type: "error"
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
const diagnostics = parseDiagnostics(value.diagnostics);
|
|
130
|
-
if (diagnostics === null || typeof value.revision !== "number") return null;
|
|
131
|
-
if (value.type === "program") {
|
|
132
|
-
const program = parseProgram(value.program);
|
|
133
|
-
return program === null ? null : {
|
|
134
|
-
diagnostics,
|
|
135
|
-
fresh: value.fresh === true,
|
|
136
|
-
program,
|
|
137
|
-
revision: value.revision,
|
|
138
|
-
type: "program"
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
if (value.type === "done" && isString(value.source)) return {
|
|
142
|
-
diagnostics,
|
|
143
|
-
revision: value.revision,
|
|
144
|
-
source: value.source,
|
|
145
|
-
type: "done"
|
|
146
|
-
};
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
|
-
//#endregion
|
|
150
|
-
export { parseErrorData, parseInvalidations, parseProgram, parseSessionEvent };
|
package/dist/protocol.d.ts
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { JigsErrorData, ProcedureEntry } from "./types.js";
|
|
2
|
-
//#region src/protocol.d.ts
|
|
3
|
-
/**
|
|
4
|
-
* Prop value on the wire. Functions never leave the worker; they travel as
|
|
5
|
-
* `{ fn }` ids. Lists of plain objects travel as `{ rows }`.
|
|
6
|
-
*/
|
|
7
|
-
export type WireValue = boolean | number | string | {
|
|
8
|
-
fn: string;
|
|
9
|
-
} | {
|
|
10
|
-
rows: ReadonlyArray<Row>;
|
|
11
|
-
};
|
|
12
|
-
/** One row of a list prop: flat fields only. */
|
|
13
|
-
export type Row = Readonly<Record<string, boolean | number | string | null>>;
|
|
14
|
-
export type WireArg = boolean | number | string;
|
|
15
|
-
/** The props of one node on the wire. */
|
|
16
|
-
export type WireProps = Record<string, WireValue>;
|
|
17
|
-
/** UI operations produced by the worker's reconciler. The root container has id 0. */
|
|
18
|
-
export type Op = {
|
|
19
|
-
id: number;
|
|
20
|
-
op: "create";
|
|
21
|
-
props: WireProps;
|
|
22
|
-
type: string;
|
|
23
|
-
} | {
|
|
24
|
-
id: number;
|
|
25
|
-
op: "createText";
|
|
26
|
-
text: string;
|
|
27
|
-
} | {
|
|
28
|
-
id: number;
|
|
29
|
-
index: number;
|
|
30
|
-
op: "place";
|
|
31
|
-
parent: number;
|
|
32
|
-
} | {
|
|
33
|
-
id: number;
|
|
34
|
-
op: "remove";
|
|
35
|
-
} | {
|
|
36
|
-
id: number;
|
|
37
|
-
op: "set";
|
|
38
|
-
props: Record<string, WireValue | null>;
|
|
39
|
-
} | {
|
|
40
|
-
id: number;
|
|
41
|
-
op: "text";
|
|
42
|
-
text: string;
|
|
43
|
-
};
|
|
44
|
-
export interface RuntimeError {
|
|
45
|
-
line: number | null;
|
|
46
|
-
message: string;
|
|
47
|
-
}
|
|
48
|
-
/** A query as the program sees it from `useQuery`. */
|
|
49
|
-
export type QueryResult = {
|
|
50
|
-
data: null;
|
|
51
|
-
error: null;
|
|
52
|
-
status: "pending";
|
|
53
|
-
} | {
|
|
54
|
-
data: unknown;
|
|
55
|
-
error: null;
|
|
56
|
-
status: "success";
|
|
57
|
-
} | {
|
|
58
|
-
data: null;
|
|
59
|
-
error: JigsErrorData;
|
|
60
|
-
status: "error";
|
|
61
|
-
};
|
|
62
|
-
/** How a mutation settled. */
|
|
63
|
-
export type MutationResult = {
|
|
64
|
-
data: unknown;
|
|
65
|
-
status: "success";
|
|
66
|
-
} | {
|
|
67
|
-
error: JigsErrorData;
|
|
68
|
-
status: "error";
|
|
69
|
-
};
|
|
70
|
-
/** A query the program read during a render. `key` is stable for a name and input. */
|
|
71
|
-
export interface QueryRead {
|
|
72
|
-
input: unknown;
|
|
73
|
-
key: string;
|
|
74
|
-
name: string;
|
|
75
|
-
}
|
|
76
|
-
/** A `mutate()` call made inside a handler. */
|
|
77
|
-
export interface MutationCall {
|
|
78
|
-
id: number;
|
|
79
|
-
input: unknown;
|
|
80
|
-
name: string;
|
|
81
|
-
}
|
|
82
|
-
/** Page to worker. */
|
|
83
|
-
export type HostMessage = {
|
|
84
|
-
args: ReadonlyArray<WireArg>;
|
|
85
|
-
fn: string;
|
|
86
|
-
seq: number;
|
|
87
|
-
type: "call";
|
|
88
|
-
} | {
|
|
89
|
-
id: number;
|
|
90
|
-
result: MutationResult;
|
|
91
|
-
seq: number;
|
|
92
|
-
type: "mutation";
|
|
93
|
-
} | {
|
|
94
|
-
key: string;
|
|
95
|
-
result: QueryResult;
|
|
96
|
-
seq: number;
|
|
97
|
-
type: "query";
|
|
98
|
-
} | {
|
|
99
|
-
code: string;
|
|
100
|
-
/** Drop the state and mutation statuses of the program before. */
|
|
101
|
-
fresh: boolean;
|
|
102
|
-
procedures: Readonly<Record<string, ProcedureEntry>>;
|
|
103
|
-
prose: Readonly<Record<string, string>>;
|
|
104
|
-
seq: number;
|
|
105
|
-
type: "update";
|
|
106
|
-
};
|
|
107
|
-
/** Worker to page. */
|
|
108
|
-
export type WorkerMessage = {
|
|
109
|
-
errors: Array<RuntimeError>;
|
|
110
|
-
mutations: Array<MutationCall>;
|
|
111
|
-
ops: Array<Op>;
|
|
112
|
-
queries: Array<QueryRead>;
|
|
113
|
-
seq: number;
|
|
114
|
-
type: "result";
|
|
115
|
-
} | {
|
|
116
|
-
message: string;
|
|
117
|
-
seq: number;
|
|
118
|
-
type: "failed";
|
|
119
|
-
};
|
|
120
|
-
/** What the frame sends to the page. */
|
|
121
|
-
export type FrameMessage = WorkerMessage | {
|
|
122
|
-
seq: number;
|
|
123
|
-
type: "quarantined";
|
|
124
|
-
} | {
|
|
125
|
-
type: "ready";
|
|
126
|
-
} |
|
|
127
|
-
/** The frame's own security policy is not in force, so it will not run. */
|
|
128
|
-
{
|
|
129
|
-
type: "unprotected";
|
|
130
|
-
};
|
|
131
|
-
export declare const ROOT_ID = 0;
|
|
132
|
-
//#endregion
|
package/dist/protocol.js
DELETED
package/dist/types.d.ts
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
//#region src/types.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Types the server and the browser share. The server builds these values;
|
|
4
|
-
* the browser receives them as JSON and checks them with `parse.ts`.
|
|
5
|
-
*/
|
|
6
|
-
/** The version of the program format. The runtime refuses any other. */
|
|
7
|
-
export declare const PROGRAM_FORMAT = 1;
|
|
8
|
-
export declare const jigsErrorCodes: readonly ["BAD_INPUT", "UNAUTHORIZED", "FORBIDDEN", "NOT_FOUND", "CONFLICT", "RATE_LIMITED", "INTERNAL", "UNKNOWN_PROCEDURE", "UNAVAILABLE", "NETWORK", "DECLINED"];
|
|
9
|
-
export type JigsErrorCode = (typeof jigsErrorCodes)[number];
|
|
10
|
-
/** A failure as the spec and the browser see it. */
|
|
11
|
-
export interface JigsErrorData {
|
|
12
|
-
code: JigsErrorCode;
|
|
13
|
-
message: string;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Where a diagnostic points. Every span has a line; one about something the
|
|
17
|
-
* compiler can place in the source also has its range, as offsets into the
|
|
18
|
-
* source the line counts (JavaScript string indices), and the column of its
|
|
19
|
-
* start.
|
|
20
|
-
*/
|
|
21
|
-
export interface Span {
|
|
22
|
-
/** 1-based, on `line`. */
|
|
23
|
-
column?: number;
|
|
24
|
-
/** Just past the range. */
|
|
25
|
-
end?: number;
|
|
26
|
-
line: number;
|
|
27
|
-
/** The range's first character. */
|
|
28
|
-
start?: number;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* A rewrite that makes a diagnostic's problem go away: replace each edit's
|
|
32
|
-
* span with its text. The server attaches one only when there is a single
|
|
33
|
-
* way to write it, and applies it itself, so its diagnostic is `info`.
|
|
34
|
-
*/
|
|
35
|
-
export interface DiagnosticFix {
|
|
36
|
-
edits: Array<{
|
|
37
|
-
span: Span;
|
|
38
|
-
text: string;
|
|
39
|
-
}>;
|
|
40
|
-
title: string;
|
|
41
|
-
}
|
|
42
|
-
export interface Diagnostic {
|
|
43
|
-
code: string;
|
|
44
|
-
fix?: DiagnosticFix;
|
|
45
|
-
message: string;
|
|
46
|
-
/** `error` marks something the compiler had to change; the program still runs. */
|
|
47
|
-
severity: "error" | "hint" | "info" | "warning";
|
|
48
|
-
span: Span;
|
|
49
|
-
}
|
|
50
|
-
export interface NumberPropSchema {
|
|
51
|
-
kind: "number";
|
|
52
|
-
max?: number;
|
|
53
|
-
min?: number;
|
|
54
|
-
}
|
|
55
|
-
/** How the view checks one prop before a registry component sees it. */
|
|
56
|
-
export type PropSchema = {
|
|
57
|
-
kind: "boolean";
|
|
58
|
-
} | {
|
|
59
|
-
kind: "enum";
|
|
60
|
-
values: ReadonlyArray<string>;
|
|
61
|
-
} | {
|
|
62
|
-
kind: "handler";
|
|
63
|
-
} | {
|
|
64
|
-
kind: "image";
|
|
65
|
-
} | {
|
|
66
|
-
kind: "link";
|
|
67
|
-
} | {
|
|
68
|
-
kind: "list";
|
|
69
|
-
} | NumberPropSchema | {
|
|
70
|
-
kind: "string";
|
|
71
|
-
};
|
|
72
|
-
export type ChildrenKind = "block" | "inline" | "none";
|
|
73
|
-
export interface ComponentSchema {
|
|
74
|
-
children: ChildrenKind;
|
|
75
|
-
props: Readonly<Record<string, PropSchema>>;
|
|
76
|
-
}
|
|
77
|
-
/** `action` is a mutation the embedding page handles instead of the server. */
|
|
78
|
-
export type ProcedureKind = "action" | "mutation" | "query";
|
|
79
|
-
export interface ProcedureEntry {
|
|
80
|
-
invalidates: ReadonlyArray<string>;
|
|
81
|
-
kind: ProcedureKind;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* One query a mutation refreshed, as the server sends it with the mutation's
|
|
85
|
-
* data. Without `input`, every input of the query refetches. With it, only
|
|
86
|
-
* the queries whose input contains those values.
|
|
87
|
-
*/
|
|
88
|
-
export interface Invalidation {
|
|
89
|
-
input?: JsonValue;
|
|
90
|
-
name: string;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* A compiled spec. Opaque to apps: pass it from the server to the view and
|
|
94
|
-
* nowhere else. The view trusts its schemas and image hosts.
|
|
95
|
-
*/
|
|
96
|
-
export interface Program {
|
|
97
|
-
code: string;
|
|
98
|
-
/** Prop schemas of the components the program draws. */
|
|
99
|
-
components: Readonly<Record<string, ComponentSchema>>;
|
|
100
|
-
format: typeof PROGRAM_FORMAT;
|
|
101
|
-
imageHosts: ReadonlyArray<string>;
|
|
102
|
-
/** Procedures the program calls, with their kind and `invalidates` list. */
|
|
103
|
-
procedures: Readonly<Record<string, ProcedureEntry>>;
|
|
104
|
-
/** Text nodes pulled out of the code, so a prose edit swaps a table. */
|
|
105
|
-
prose: Readonly<Record<string, string>>;
|
|
106
|
-
}
|
|
107
|
-
export interface Message {
|
|
108
|
-
content: string;
|
|
109
|
-
role: "assistant" | "user";
|
|
110
|
-
}
|
|
111
|
-
export type SessionEvent = {
|
|
112
|
-
diagnostics: ReadonlyArray<Diagnostic>;
|
|
113
|
-
/**
|
|
114
|
-
* True on the first program of a new UI: the model wrote a whole
|
|
115
|
-
* document rather than editing the last one, or a stored one was
|
|
116
|
-
* restored. The view starts it from fresh state.
|
|
117
|
-
*/
|
|
118
|
-
fresh: boolean;
|
|
119
|
-
program: Program;
|
|
120
|
-
revision: number;
|
|
121
|
-
type: "program";
|
|
122
|
-
} | {
|
|
123
|
-
diagnostics: ReadonlyArray<Diagnostic>;
|
|
124
|
-
revision: number;
|
|
125
|
-
source: string;
|
|
126
|
-
type: "done";
|
|
127
|
-
} | {
|
|
128
|
-
error: JigsErrorData;
|
|
129
|
-
type: "error";
|
|
130
|
-
};
|
|
131
|
-
/**
|
|
132
|
-
* Where a chat block is: still arriving, being repaired after it closed with
|
|
133
|
-
* errors, or final.
|
|
134
|
-
*/
|
|
135
|
-
export type ChatBlockStatus = "done" | "repairing" | "streaming";
|
|
136
|
-
/**
|
|
137
|
-
* A jig in a chat reply: one fenced `jigs` block, compiled on its own.
|
|
138
|
-
* The server sends it again whenever its program changes, under the same
|
|
139
|
-
* `key`.
|
|
140
|
-
*/
|
|
141
|
-
export interface ChatBlock {
|
|
142
|
-
/** Lines refer to `source`, which starts with its pragma line. */
|
|
143
|
-
diagnostics: ReadonlyArray<Diagnostic>;
|
|
144
|
-
/** Set when the block could not be built, e.g. an edit to a missing UI. */
|
|
145
|
-
error?: JigsErrorData;
|
|
146
|
-
/** The id the model gave the block, e.g. "monthly-spending". */
|
|
147
|
-
id: string;
|
|
148
|
-
/** `${id}@${version}`: unique in a conversation. */
|
|
149
|
-
key: string;
|
|
150
|
-
program: Program;
|
|
151
|
-
/** The checked source, with its pragma. Store this, not the program. */
|
|
152
|
-
source: string;
|
|
153
|
-
status: ChatBlockStatus;
|
|
154
|
-
/** 1 for the first block with this id in the conversation, then 2, 3… */
|
|
155
|
-
version: number;
|
|
156
|
-
}
|
|
157
|
-
/** A value that travels as JSON. */
|
|
158
|
-
export type JsonValue = boolean | null | number | string | ReadonlyArray<JsonValue> | {
|
|
159
|
-
readonly [key: string]: JsonValue;
|
|
160
|
-
};
|
|
161
|
-
/** App options sent with a turn; the server parses them with its `options` schema. */
|
|
162
|
-
export type SessionOptions = Readonly<Record<string, JsonValue>>;
|
|
163
|
-
/** What a session request carries. */
|
|
164
|
-
export interface SessionBody {
|
|
165
|
-
messages: ReadonlyArray<Message>;
|
|
166
|
-
options?: SessionOptions;
|
|
167
|
-
}
|
|
168
|
-
//#endregion
|
package/dist/types.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
//#region src/types.ts
|
|
2
|
-
/**
|
|
3
|
-
* Types the server and the browser share. The server builds these values;
|
|
4
|
-
* the browser receives them as JSON and checks them with `parse.ts`.
|
|
5
|
-
*/
|
|
6
|
-
/** The version of the program format. The runtime refuses any other. */
|
|
7
|
-
const PROGRAM_FORMAT = 1;
|
|
8
|
-
const jigsErrorCodes = [
|
|
9
|
-
"BAD_INPUT",
|
|
10
|
-
"UNAUTHORIZED",
|
|
11
|
-
"FORBIDDEN",
|
|
12
|
-
"NOT_FOUND",
|
|
13
|
-
"CONFLICT",
|
|
14
|
-
"RATE_LIMITED",
|
|
15
|
-
"INTERNAL",
|
|
16
|
-
"UNKNOWN_PROCEDURE",
|
|
17
|
-
"UNAVAILABLE",
|
|
18
|
-
"NETWORK",
|
|
19
|
-
"DECLINED"
|
|
20
|
-
];
|
|
21
|
-
//#endregion
|
|
22
|
-
export { PROGRAM_FORMAT, jigsErrorCodes };
|
package/dist/urls.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
//#region src/urls.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* A hostname, or `*.example.com` for its subdomains (not the apex),
|
|
4
|
-
* optionally followed by a path prefix that ends in `/`, such as
|
|
5
|
-
* `images.example.com/products/`.
|
|
6
|
-
*/
|
|
7
|
-
export declare function isImageHostEntry(entry: string): boolean;
|
|
8
|
-
/** The entry with its host lowercased; the path keeps its case. */
|
|
9
|
-
export declare function normalizeImageHost(entry: string): string;
|
|
10
|
-
/** An `https` URL with no credentials or port, on one of `hosts`. */
|
|
11
|
-
export declare function isAllowedImage(src: string, hosts: ReadonlyArray<string>): boolean;
|
|
12
|
-
/** An absolute `http` or `https` URL. Anything else, such as `javascript:`, is refused. */
|
|
13
|
-
export declare function isAllowedLink(href: string): boolean;
|
|
14
|
-
//#endregion
|
package/dist/urls.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
//#region src/urls.ts
|
|
2
|
-
const HOST = /^(\*\.)?[a-z0-9-]+(\.[a-z0-9-]+)*$/;
|
|
3
|
-
const PATH_PREFIX = /^\/(?:[A-Za-z0-9._~!$&'()*+,;=:@%/-]*\/)?$/;
|
|
4
|
-
/** An image host entry split into its host and optional path prefix. */
|
|
5
|
-
function splitEntry(entry) {
|
|
6
|
-
const slash = entry.indexOf("/");
|
|
7
|
-
return slash === -1 ? {
|
|
8
|
-
host: entry.toLowerCase(),
|
|
9
|
-
path: "/"
|
|
10
|
-
} : {
|
|
11
|
-
host: entry.slice(0, slash).toLowerCase(),
|
|
12
|
-
path: entry.slice(slash)
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* A hostname, or `*.example.com` for its subdomains (not the apex),
|
|
17
|
-
* optionally followed by a path prefix that ends in `/`, such as
|
|
18
|
-
* `images.example.com/products/`.
|
|
19
|
-
*/
|
|
20
|
-
function isImageHostEntry(entry) {
|
|
21
|
-
const parts = splitEntry(entry);
|
|
22
|
-
return HOST.test(parts.host) && PATH_PREFIX.test(parts.path);
|
|
23
|
-
}
|
|
24
|
-
/** The entry with its host lowercased; the path keeps its case. */
|
|
25
|
-
function normalizeImageHost(entry) {
|
|
26
|
-
const parts = splitEntry(entry);
|
|
27
|
-
return entry.includes("/") ? `${parts.host}${parts.path}` : parts.host;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* An encoded `/`, `\`, `.` or `%`: a server that decodes it after matching can
|
|
31
|
-
* be walked out of the prefix with `..%2F`, or with `..%252F` if it decodes
|
|
32
|
-
* twice, and the URL parser leaves it alone. So a prefix can't serve object
|
|
33
|
-
* keys that hold an encoded `/`, such as Firebase Storage's `o/a%2Fb.jpg`.
|
|
34
|
-
*/
|
|
35
|
-
const ENCODED_SEPARATOR = /%(?:2f|5c|2e|25)/i;
|
|
36
|
-
function matchesHost(url, entry) {
|
|
37
|
-
if (!isImageHostEntry(entry)) return false;
|
|
38
|
-
const parts = splitEntry(entry);
|
|
39
|
-
return (parts.host.startsWith("*.") ? url.hostname.endsWith(parts.host.slice(1)) : url.hostname === parts.host) && url.pathname.startsWith(parts.path) && (parts.path === "/" || !ENCODED_SEPARATOR.test(url.pathname));
|
|
40
|
-
}
|
|
41
|
-
function parseUrl(text) {
|
|
42
|
-
try {
|
|
43
|
-
return new URL(text);
|
|
44
|
-
} catch {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
/** An `https` URL with no credentials or port, on one of `hosts`. */
|
|
49
|
-
function isAllowedImage(src, hosts) {
|
|
50
|
-
const url = parseUrl(src);
|
|
51
|
-
return url !== null && url.protocol === "https:" && url.username === "" && url.password === "" && url.port === "" && hosts.some((entry) => matchesHost(url, entry));
|
|
52
|
-
}
|
|
53
|
-
/** An absolute `http` or `https` URL. Anything else, such as `javascript:`, is refused. */
|
|
54
|
-
function isAllowedLink(href) {
|
|
55
|
-
const url = parseUrl(href);
|
|
56
|
-
return url !== null && (url.protocol === "https:" || url.protocol === "http:") && url.username === "" && url.password === "";
|
|
57
|
-
}
|
|
58
|
-
//#endregion
|
|
59
|
-
export { isAllowedImage, isAllowedLink, isImageHostEntry, normalizeImageHost };
|