@jigs.design/protocol 0.2.0 → 0.3.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.
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * - `error`: what the model wrote is wrong there. The program still runs,
11
11
  * with that part dropped or replaced, but the turn is not valid: the server
12
- * asks the model for a repair, and a strict compile (a stored spec, a
12
+ * asks the model for a repair, and a strict compile (a page, a
13
13
  * situation's example, `jigs.compile`) fails.
14
14
  * - `warning`: the source itself may be fine, but it was written for a
15
15
  * language or catalog newer than this server's, so it may not mean what it
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * - `error`: what the model wrote is wrong there. The program still runs,
11
11
  * with that part dropped or replaced, but the turn is not valid: the server
12
- * asks the model for a repair, and a strict compile (a stored spec, a
12
+ * asks the model for a repair, and a strict compile (a page, a
13
13
  * situation's example, `jigs.compile`) fails.
14
14
  * - `warning`: the source itself may be fine, but it was written for a
15
15
  * language or catalog newer than this server's, so it may not mean what it
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Diagnostic, DiagnosticFix, Severity, Span, severities } from "./diagnostics.js";
2
- import { ChatBlock, ChatBlockStatus, ChildrenKind, ComponentSchema, Invalidation, JigsErrorCode, JigsErrorData, JsonValue, Message, NumberPropSchema, PROGRAM_FORMAT, ProcedureEntry, ProcedureKind, Program, PropSchema, SessionBody, SessionEvent, SessionOptions, jigsErrorCodes } from "./types.js";
2
+ import { ChatBlock, ChatBlockStatus, ChildrenKind, ComponentSchema, Invalidation, JigsErrorCode, JigsErrorData, JsonValue, Message, NumberPropSchema, PROGRAM_FORMAT, PageInvalidation, PageProps, PageRef, PageReply, ProcedureEntry, ProcedureKind, Program, PropSchema, PushEvent, SessionBody, SessionDocument, SessionEvent, SessionOptions, jigsErrorCodes } from "./types.js";
3
3
  import { FrameMessage, HostMessage, MutationCall, MutationResult, Op, QueryRead, QueryResult, ROOT_ID, Row, RuntimeError, WireArg, WireProps, WireValue, WorkerMessage } from "./protocol.js";
4
- import { parseErrorData, parseInvalidations, parseProgram, parseSessionEvent } from "./parse.js";
4
+ import { parseErrorData, parseInvalidations, parsePageReply, parseProgram, parsePushEvent, parseSessionEvent } from "./parse.js";
5
5
  import { isAllowedImage, isAllowedLink, isImageHostEntry, normalizeImageHost } from "./urls.js";
6
- 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, 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 Severity, type Span, type WireArg, type WireProps, type WireValue, type WorkerMessage, isAllowedImage, isAllowedLink, isImageHostEntry, jigsErrorCodes, normalizeImageHost, parseErrorData, parseInvalidations, parseProgram, parseSessionEvent, severities };
6
+ 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, type Message, type MutationCall, type MutationResult, type NumberPropSchema, type Op, PROGRAM_FORMAT, type PageInvalidation, type PageProps, type PageRef, type PageReply, type ProcedureEntry, type ProcedureKind, type Program, type PropSchema, type PushEvent, type QueryRead, type QueryResult, ROOT_ID, type Row, type RuntimeError, type SessionBody, type SessionDocument, type SessionEvent, type SessionOptions, type Severity, type Span, type WireArg, type WireProps, type WireValue, type WorkerMessage, isAllowedImage, isAllowedLink, isImageHostEntry, jigsErrorCodes, normalizeImageHost, parseErrorData, parseInvalidations, parsePageReply, parseProgram, parsePushEvent, parseSessionEvent, severities };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { PROGRAM_FORMAT, jigsErrorCodes } from "./types.js";
2
2
  import { severities } from "./diagnostics.js";
3
3
  import { ROOT_ID } from "./protocol.js";
4
- import { parseErrorData, parseInvalidations, parseProgram, parseSessionEvent } from "./parse.js";
4
+ import { parseErrorData, parseInvalidations, parsePageReply, parseProgram, parsePushEvent, parseSessionEvent } from "./parse.js";
5
5
  import { isAllowedImage, isAllowedLink, isImageHostEntry, normalizeImageHost } from "./urls.js";
6
- export { PROGRAM_FORMAT, ROOT_ID, isAllowedImage, isAllowedLink, isImageHostEntry, jigsErrorCodes, normalizeImageHost, parseErrorData, parseInvalidations, parseProgram, parseSessionEvent, severities };
6
+ export { PROGRAM_FORMAT, ROOT_ID, isAllowedImage, isAllowedLink, isImageHostEntry, jigsErrorCodes, normalizeImageHost, parseErrorData, parseInvalidations, parsePageReply, parseProgram, parsePushEvent, parseSessionEvent, severities };
package/dist/parse.d.ts CHANGED
@@ -1,9 +1,17 @@
1
- import { Invalidation, JigsErrorData, Program, SessionEvent } from "./types.js";
1
+ import { Invalidation, JigsErrorData, PageReply, Program, PushEvent, SessionEvent } from "./types.js";
2
2
  //#region src/parse.d.ts
3
3
  export declare function parseErrorData(value: unknown): JigsErrorData | null;
4
4
  /** The `invalidate` list of a mutation's reply. */
5
5
  export declare function parseInvalidations(value: unknown): ReadonlyArray<Invalidation> | null;
6
6
  export declare function parseProgram(value: unknown): Program | null;
7
+ /** A page request's reply body, or null when it is not one. */
8
+ export declare function parsePageReply(value: unknown): PageReply | null;
7
9
  /** One session event from its JSON text, or null when it is not one. */
8
10
  export declare function parseSessionEvent(text: string): SessionEvent | null;
11
+ /**
12
+ * One event of the push stream from its JSON text, or null when it is not
13
+ * one the server may send. `open` and `version` come from the transport,
14
+ * never the wire, so they are null here.
15
+ */
16
+ export declare function parsePushEvent(text: string): PushEvent | null;
9
17
  //#endregion
package/dist/parse.js CHANGED
@@ -41,6 +41,27 @@ function parseInvalidations(value) {
41
41
  }
42
42
  return out;
43
43
  }
44
+ const isScalar = (value) => typeof value === "boolean" || typeof value === "number" || typeof value === "string";
45
+ function parsePageInvalidation(value) {
46
+ if (!isRecord(value) || !isString(value.name)) return null;
47
+ if (!("props" in value)) return { name: value.name };
48
+ const props = recordOf(value.props, (entry) => isScalar(entry) ? entry : null);
49
+ return props === null ? null : {
50
+ name: value.name,
51
+ props
52
+ };
53
+ }
54
+ /** The `pages` list of an `invalidate-pages` event. */
55
+ function parsePageInvalidations(value) {
56
+ if (!Array.isArray(value)) return null;
57
+ const out = [];
58
+ for (const entry of value) {
59
+ const parsed = parsePageInvalidation(entry);
60
+ if (parsed === null) return null;
61
+ out.push(parsed);
62
+ }
63
+ return out;
64
+ }
44
65
  function parsePropSchema(value) {
45
66
  if (!isRecord(value)) return null;
46
67
  switch (value.kind) {
@@ -73,7 +94,7 @@ function parseComponentSchema(value) {
73
94
  };
74
95
  }
75
96
  function parseProcedure(value) {
76
- if (!isRecord(value) || value.kind !== "query" && value.kind !== "mutation" && value.kind !== "action" || !isStringList(value.invalidates)) return null;
97
+ if (!isRecord(value) || value.kind !== "query" && value.kind !== "mutation" && value.kind !== "action" && value.kind !== "data" || !isStringList(value.invalidates)) return null;
77
98
  return {
78
99
  invalidates: value.invalidates,
79
100
  kind: value.kind
@@ -111,6 +132,17 @@ function parseDiagnostics(value) {
111
132
  return parsed === null ? [] : [parsed];
112
133
  });
113
134
  }
135
+ /** A page request's reply body, or null when it is not one. */
136
+ function parsePageReply(value) {
137
+ if (!isRecord(value) || !isString(value.version)) return null;
138
+ const program = parseProgram(value.program);
139
+ const diagnostics = parseDiagnostics(value.diagnostics);
140
+ return program === null || diagnostics === null ? null : {
141
+ diagnostics,
142
+ program,
143
+ version: value.version
144
+ };
145
+ }
114
146
  /** One session event from its JSON text, or null when it is not one. */
115
147
  function parseSessionEvent(text) {
116
148
  let value;
@@ -139,7 +171,13 @@ function parseSessionEvent(text) {
139
171
  type: "program"
140
172
  };
141
173
  }
142
- if (value.type === "done" && isString(value.source)) return {
174
+ if (value.type === "done" && isString(value.source)) return isString(value.reply) ? {
175
+ diagnostics,
176
+ reply: value.reply,
177
+ revision: value.revision,
178
+ source: value.source,
179
+ type: "done"
180
+ } : {
143
181
  diagnostics,
144
182
  revision: value.revision,
145
183
  source: value.source,
@@ -147,5 +185,35 @@ function parseSessionEvent(text) {
147
185
  };
148
186
  return null;
149
187
  }
188
+ /**
189
+ * One event of the push stream from its JSON text, or null when it is not
190
+ * one the server may send. `open` and `version` come from the transport,
191
+ * never the wire, so they are null here.
192
+ */
193
+ function parsePushEvent(text) {
194
+ let value;
195
+ try {
196
+ value = JSON.parse(text);
197
+ } catch {
198
+ return null;
199
+ }
200
+ if (!isRecord(value)) return null;
201
+ if (value.type === "close") return { type: "close" };
202
+ if (value.type === "invalidate") {
203
+ const invalidate = parseInvalidations(value.invalidate);
204
+ return invalidate === null ? null : {
205
+ invalidate,
206
+ type: "invalidate"
207
+ };
208
+ }
209
+ if (value.type === "invalidate-pages") {
210
+ const pages = parsePageInvalidations(value.pages);
211
+ return pages === null ? null : {
212
+ pages,
213
+ type: "invalidate-pages"
214
+ };
215
+ }
216
+ return null;
217
+ }
150
218
  //#endregion
151
- export { parseErrorData, parseInvalidations, parseProgram, parseSessionEvent };
219
+ export { parseErrorData, parseInvalidations, parsePageReply, parseProgram, parsePushEvent, parseSessionEvent };
package/dist/types.d.ts CHANGED
@@ -40,8 +40,11 @@ export interface ComponentSchema {
40
40
  children: ChildrenKind;
41
41
  props: Readonly<Record<string, PropSchema>>;
42
42
  }
43
- /** `action` is a mutation the embedding page handles instead of the server. */
44
- export type ProcedureKind = "action" | "mutation" | "query";
43
+ /**
44
+ * `action` is a mutation the embedding page handles instead of the server;
45
+ * `data` is a slot the page provides, read with `useQuery` and never fetched.
46
+ */
47
+ export type ProcedureKind = "action" | "data" | "mutation" | "query";
45
48
  export interface ProcedureEntry {
46
49
  invalidates: ReadonlyArray<string>;
47
50
  kind: ProcedureKind;
@@ -51,10 +54,35 @@ export interface ProcedureEntry {
51
54
  * data. Without `input`, every input of the query refetches. With it, only
52
55
  * the queries whose input contains those values.
53
56
  */
54
- export interface Invalidation {
57
+ export interface Invalidation<TName extends string = string> {
55
58
  input?: JsonValue;
56
- name: string;
59
+ name: TName;
57
60
  }
61
+ /**
62
+ * What the push channel carries. The server sends `invalidate` (the queries
63
+ * to refetch, by name), `invalidate-pages` (the pages to refetch, by name)
64
+ * and `close` (it is ending the stream on purpose, so the next connection is
65
+ * no gap). The transport adds `open` on every connection it makes, with
66
+ * `replayed` true when it asked the server to resume from the last event
67
+ * id, and `version`, the server's `jigs-version` on that connection. A
68
+ * server cannot send `open` or `version`. No event carries a program: a
69
+ * page refetches its own.
70
+ */
71
+ export type PushEvent = {
72
+ invalidate: ReadonlyArray<Invalidation>;
73
+ type: "invalidate";
74
+ } | {
75
+ pages: ReadonlyArray<PageInvalidation>;
76
+ type: "invalidate-pages";
77
+ } | {
78
+ replayed: boolean;
79
+ type: "open";
80
+ } | {
81
+ type: "close";
82
+ } | {
83
+ type: "version";
84
+ version: string;
85
+ };
58
86
  /**
59
87
  * A compiled spec. Opaque to apps: pass it from the server to the view and
60
88
  * nowhere else. The view trusts its schemas and image hosts.
@@ -70,6 +98,10 @@ export interface Program {
70
98
  /** Text nodes pulled out of the code, so a prose edit swaps a table. */
71
99
  prose: Readonly<Record<string, string>>;
72
100
  }
101
+ /**
102
+ * One message of a session's conversation: the user's ask, or the model's
103
+ * reply as it wrote it. The current UI travels beside it, as `document`.
104
+ */
73
105
  export interface Message {
74
106
  content: string;
75
107
  role: "assistant" | "user";
@@ -87,13 +119,52 @@ export type SessionEvent = {
87
119
  type: "program";
88
120
  } | {
89
121
  diagnostics: ReadonlyArray<Diagnostic>;
122
+ /**
123
+ * The assistant's message for the conversation, absent when no model
124
+ * ran: the reply as written when the turn took one round and the
125
+ * server did not reorder it, otherwise the checked source.
126
+ */
127
+ reply?: string;
90
128
  revision: number;
129
+ /** The new document: the checked source, with its pragma. */
91
130
  source: string;
92
131
  type: "done";
93
132
  } | {
94
133
  error: JigsErrorData;
95
134
  type: "error";
96
135
  };
136
+ /**
137
+ * A page's props. Scalars only: the compiler types a prop only when it is
138
+ * a literal, and a define cannot declare an object prop.
139
+ */
140
+ export type PageProps = Readonly<Record<string, boolean | number | string>>;
141
+ /** A page of the host and the props it was asked for. */
142
+ export interface PageRef {
143
+ /** The page jig's name. */
144
+ name: string;
145
+ /** Its props, keys sorted. */
146
+ props: PageProps;
147
+ }
148
+ /**
149
+ * One page a push event refreshes, by name. Without `props`, every props of
150
+ * the page refetches; with them, only the pages whose props contain those
151
+ * values, as an `Invalidation`'s `input` matches.
152
+ */
153
+ export interface PageInvalidation<TName extends string = string> {
154
+ name: TName;
155
+ props?: PageProps;
156
+ }
157
+ /**
158
+ * The answer to a page request, `POST { type: "page", name, props }`: the
159
+ * page's program, its diagnostics (a saved page is compiled leniently, so
160
+ * they may hold errors), and the server's `jigs-version`, so a client can
161
+ * drop programs an older server compiled.
162
+ */
163
+ export interface PageReply {
164
+ diagnostics: ReadonlyArray<Diagnostic>;
165
+ program: Program;
166
+ version: string;
167
+ }
97
168
  /**
98
169
  * Where a chat block is: still arriving, being repaired after it closed with
99
170
  * errors, or final.
@@ -109,15 +180,23 @@ export interface ChatBlock {
109
180
  diagnostics: ReadonlyArray<Diagnostic>;
110
181
  /** Set when the block could not be built, e.g. an edit to a missing UI. */
111
182
  error?: JigsErrorData;
112
- /** The id the model gave the block, e.g. "monthly-spending". */
113
- id: string;
114
- /** `${id}@${version}`: unique in a conversation. */
183
+ /** `${name}@${version}`: unique in a conversation. */
115
184
  key: string;
185
+ /**
186
+ * The fence id the model gave the block, e.g. "monthly-spending": the
187
+ * jig's name in the conversation's scope.
188
+ */
189
+ name: string;
116
190
  program: Program;
191
+ /**
192
+ * Set when the whole block is one tag naming a page of the host, with
193
+ * literal props: the block is that page, so the app can link to it.
194
+ */
195
+ ref?: PageRef;
117
196
  /** The checked source, with its pragma. Store this, not the program. */
118
197
  source: string;
119
198
  status: ChatBlockStatus;
120
- /** 1 for the first block with this id in the conversation, then 2, 3… */
199
+ /** 1 for the first block with this name in the conversation, then 2, 3… */
121
200
  version: number;
122
201
  }
123
202
  /** A value that travels as JSON. */
@@ -126,8 +205,21 @@ export type JsonValue = boolean | null | number | string | ReadonlyArray<JsonVal
126
205
  };
127
206
  /** App options sent with a turn; the server parses them with its `options` schema. */
128
207
  export type SessionOptions = Readonly<Record<string, JsonValue>>;
208
+ /**
209
+ * What a session is about: an inline source, or a page the server serves,
210
+ * by name, opened as a document.
211
+ */
212
+ export type SessionDocument = {
213
+ source: string;
214
+ } | {
215
+ name: string;
216
+ props?: PageProps;
217
+ };
129
218
  /** What a session request carries. */
130
219
  export interface SessionBody {
220
+ /** The current UI. Absent when the turn writes a new one. */
221
+ document?: SessionDocument;
222
+ /** The conversation about it: the user's asks and the model's replies. */
131
223
  messages: ReadonlyArray<Message>;
132
224
  options?: SessionOptions;
133
225
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jigs.design/protocol",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "The program format, the diagnostics, and the types and messages the server, the browser and the sandbox share. No runtime and no dependencies.",
5
5
  "license": "MIT",
6
6
  "repository": {