@jxsuite/protocol 0.1.0 → 0.4.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 CHANGED
@@ -62,4 +62,6 @@ doc?, options? }` through the project's format registry; without it only
62
62
 
63
63
  The types are published as TypeScript source (like every `@jxsuite` package)
64
64
  and follow the monorepo's release train. Within the monorepo, packages depend
65
- on it via `workspace:^`.
65
+ on it via `workspace:^`. Consumers install `@jxsuite/protocol` and import its
66
+ `/types` and `/routes` entrypoints; its only runtime dependency is
67
+ `@jxsuite/schema`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jxsuite/protocol",
3
- "version": "0.1.0",
3
+ "version": "0.4.0",
4
4
  "description": "The Studio Backend Protocol — wire types and the canonical route table every Jx Studio backend implements",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -18,13 +18,13 @@
18
18
  "./routes": "./src/routes.ts"
19
19
  },
20
20
  "publishConfig": {
21
- "provenance": false
21
+ "provenance": true
22
22
  },
23
23
  "scripts": {
24
24
  "test": "bun test --isolate",
25
25
  "test:coverage": "bun test --isolate --coverage"
26
26
  },
27
27
  "dependencies": {
28
- "@jxsuite/schema": "workspace:^"
28
+ "@jxsuite/schema": "^1.0.0"
29
29
  }
30
30
  }
package/src/routes.ts CHANGED
@@ -84,6 +84,16 @@ export const STUDIO_ROUTES = {
84
84
  fileUpload: route("POST", "/__studio/file/upload", "Upload binary content to a path"),
85
85
  fileRename: route("POST", "/__studio/file/rename", "Rename/move (+ refactor report)"),
86
86
  locate: route("POST", "/__studio/locate", "Find a file by name → {path | null}"),
87
+ collab: route(
88
+ "GET",
89
+ "/__studio/collab",
90
+ "Realtime co-editing: a WebSocket upgrade speaking the @jxsuite/collab wire envelope (one " +
91
+ "socket per project, documents multiplexed by path; y-protocols sync + project-level " +
92
+ "awareness in lib0 binary frames — see @jxsuite/collab/envelope for the frame layout and " +
93
+ "the docEpoch/doc-reset lifecycle). A plain GET (no Upgrade) answers {collab: true, " +
94
+ "version} as the capability probe.",
95
+ "Realtime co-editing is unavailable; Studio edits solo with file-level saves",
96
+ ),
87
97
 
88
98
  // ─── Documents / components / formats ─────────────────────────────────────
89
99
  components: route("GET", "/__studio/components", "Discover components (ComponentMeta[])"),
@@ -96,8 +106,20 @@ export const STUDIO_ROUTES = {
96
106
  formats: route(
97
107
  "GET",
98
108
  "/__studio/formats",
99
- "The project's format registry entries",
100
- "Only .json documents open (backs listFormats).",
109
+ "The project's format registry entries plus a sibling `extensions` array — per enabled " +
110
+ "extension its manifest identity and project-section contributions (ExtensionsInfo[]; " +
111
+ "backs listFormats and listExtensions).",
112
+ "Only .json documents open (backs listFormats); descriptor-contributed settings sections " +
113
+ "do not appear.",
114
+ ),
115
+ projectSchemas: route(
116
+ "GET",
117
+ "/__studio/project-schemas",
118
+ "The project's generated entry documents (project.schema.json / document.schema.json), " +
119
+ "PRE-BUNDLED into self-contained schemas — {project, document} (backs fetchProjectSchemas). " +
120
+ "Regenerated on demand when missing or older than project.json.",
121
+ "The JSON editor falls back to the bundled core schemas (extension sections get no " +
122
+ "editor validation/completion).",
101
123
  ),
102
124
  format: route(
103
125
  "POST",
@@ -171,6 +193,72 @@ export const STUDIO_ROUTES = {
171
193
  "Studio falls back to a direct GitHub API call with the user's token.",
172
194
  ),
173
195
 
196
+ // ─── Data surface (connector domain owner console) ───────────────────────
197
+ // These routes intentionally bypass table permission rules — the backend boundary
198
+ // (dev-server loopback/token, cloud collaboration permission) is the gate
199
+ // (specs/extensions.md §13). Secret VALUES never ride them; env-var NAMES only.
200
+ dataConnections: route(
201
+ "GET",
202
+ "/__studio/data/connections",
203
+ "Connector connections with configured/missingSecrets/isDefault state, reachable table " +
204
+ "names, and registry-descriptor provider metadata (DataConnectionsResponse; backs " +
205
+ "dataConnections)",
206
+ "The connections settings section shows no status, and data-domain actions stay hidden.",
207
+ ),
208
+ dataConnectionTest: route(
209
+ "POST",
210
+ "/__studio/data/connections/test",
211
+ "Probe a connection {connection} → DataConnectionTestResult (backs dataConnectionTest)",
212
+ "The Test Connection action is hidden.",
213
+ ),
214
+ dataPush: route(
215
+ "POST",
216
+ "/__studio/data/push",
217
+ "Additive schema push {connection?, dryRun?} → DataPushResult (plan of DataPushStep[], " +
218
+ "applied, warnings/errors; backs dataPush)",
219
+ "The Push Schema action is hidden; schemas deploy via `jx db push` instead.",
220
+ ),
221
+ dataRows: route(
222
+ "GET",
223
+ "/__studio/data/rows",
224
+ "Page a table (DataRowsQuery params) → DataRowsResult {rows, total, columns} (backs dataRows)",
225
+ "The data grid is unavailable.",
226
+ ),
227
+ dataInsertRow: route(
228
+ "POST",
229
+ "/__studio/data/rows",
230
+ "Insert a row {table, connection?, values} → {row} (backs dataInsertRow)",
231
+ "The data grid hides its add-row footer.",
232
+ ),
233
+ dataUpdateRow: route(
234
+ "PUT",
235
+ "/__studio/data/rows",
236
+ "Update a row keyed on its primary key {table, connection?, pk, set} → {row} (backs " +
237
+ "dataUpdateRow)",
238
+ "Data grid cells are read-only.",
239
+ ),
240
+ dataDeleteRow: route(
241
+ "DELETE",
242
+ "/__studio/data/rows",
243
+ "Delete a row (?table=&pk=&connection=) → {ok} (backs dataDeleteRow)",
244
+ "The data grid hides row deletion.",
245
+ ),
246
+
247
+ // ─── Secrets (names only) ─────────────────────────────────────────────────
248
+ secretsList: route(
249
+ "GET",
250
+ "/__studio/secrets",
251
+ "Configured secret env-var NAMES, never values (SecretsListResponse; backs listSecrets)",
252
+ "Secret fields cannot show set/unset state.",
253
+ ),
254
+ secretsSet: route(
255
+ "PUT",
256
+ "/__studio/secrets",
257
+ "Write/remove secrets in the backend store (.dev.vars locally) {set?, remove?} → names " +
258
+ "(backs setSecrets)",
259
+ "The secret form control renders disabled; secrets are edited in .dev.vars by hand.",
260
+ ),
261
+
174
262
  // ─── AI proxy ─────────────────────────────────────────────────────────────
175
263
  aiChat: route(
176
264
  "POST",
package/src/types.ts CHANGED
@@ -112,6 +112,233 @@ export interface ComponentMeta {
112
112
  hasElements?: boolean;
113
113
  }
114
114
 
115
+ // ─── Extensions (specs/extensions.md §9/§9.1) ────────────────────────────────
116
+
117
+ /** The `project` admission block of a section-owning extension class. */
118
+ export interface ExtensionProjectBlock {
119
+ /** The project.json top-level property the class owns. */
120
+ key: string;
121
+ title?: string;
122
+ description?: string;
123
+ referenceable?: boolean;
124
+ }
125
+
126
+ /** One project-section contribution of an extension class, as served on the formats route. */
127
+ export interface ExtensionContributionInfo {
128
+ /** The $prototype-visible class name declaring the `project` block. */
129
+ className: string;
130
+ project: ExtensionProjectBlock;
131
+ /** The class descriptor's `$studio` block (settings-section vocabulary), when declared. */
132
+ studio?: Record<string, unknown> | null;
133
+ /**
134
+ * The section's value schema — `properties[<key>]` of the extension's shipped project fragment —
135
+ * resolved backend-side; null when the extension ships no fragment (or it lacks the key).
136
+ */
137
+ entrySchema?: Record<string, unknown> | null;
138
+ }
139
+
140
+ /**
141
+ * One enabled extension package on the wire — the `extensions` sibling of the formats route's
142
+ * `formats` array (additive; the `formats` shape is unchanged).
143
+ */
144
+ export interface ExtensionsInfo {
145
+ /** The project.json `extensions` entry that produced this extension. */
146
+ specifier: string;
147
+ /** The manifest's package name. */
148
+ name: string;
149
+ title?: string;
150
+ description?: string;
151
+ /** Project-section contributions, in class declaration order. */
152
+ contributions: ExtensionContributionInfo[];
153
+ /**
154
+ * Every manifest class of the extension with its backend-resolved descriptor path, in declaration
155
+ * order. Optional (additive): older backends omit it, and consumers needing a class `$src` fall
156
+ * back to their historical literals. Plain state classes (no admission blocks) carry `state:
157
+ * true` plus their `$studio.stateDefaults` hint, seeding studio-created defs.
158
+ */
159
+ classes?: {
160
+ name: string;
161
+ path: string;
162
+ state?: boolean;
163
+ stateDefaults?: Record<string, unknown>;
164
+ }[];
165
+ }
166
+
167
+ /**
168
+ * Response of the project-schemas route: the project's generated entry documents
169
+ * (project.schema.json / document.schema.json), PRE-BUNDLED into self-contained compound schemas so
170
+ * editors can register them without resolving relative `$ref`s.
171
+ */
172
+ export interface ProjectSchemasResponse {
173
+ project?: Record<string, unknown> | null;
174
+ document?: Record<string, unknown> | null;
175
+ }
176
+
177
+ // ─── Data surface (connector domain; specs/extensions.md §13) ───────────────
178
+ /* The /__studio/data/* routes are the project owner's admin console over the connector
179
+ extension's connections and tables. They intentionally bypass table permission rules —
180
+ the backend's own boundary (dev-server loopback/token, cloud collaboration permission)
181
+ is the gate. Secret VALUES never ride these shapes; env-var NAMES only. */
182
+
183
+ /** Connector-provider metadata resolved from the extension registry's class descriptors. */
184
+ export interface DataConnectorInfo {
185
+ /** Backing-service identifier (`d1`, `supabase`, `sqlite`, ...). */
186
+ provider: string;
187
+ /** SQL dialect family (`"sqlite"` | `"postgres"`). */
188
+ kind?: string;
189
+ /** Dev-server stand-in provider (specs/extensions.md §12), when the class declares one. */
190
+ local?: string;
191
+ /** The descriptor's human description, when present. */
192
+ description?: string;
193
+ }
194
+
195
+ /** One project connection with its configuration state (identifiers and env NAMES only). */
196
+ export interface DataConnectionInfo {
197
+ /** The `connections` section key. */
198
+ name: string;
199
+ provider: string;
200
+ /** The connection's project.json value — identifiers and env-var names, never secrets. */
201
+ settings: Record<string, unknown>;
202
+ /** True when every env-var name the connection references resolves in the backend env. */
203
+ configured: boolean;
204
+ /** Referenced env-var NAMES with no value in the backend environment. */
205
+ missingSecrets: string[];
206
+ /** True for the first-declared connection (the console's default pick). */
207
+ isDefault: boolean;
208
+ /**
209
+ * Table names reachable on this connection: tables declared in project.json plus tables
210
+ * discovered by introspection (e.g. auth system tables). Best-effort — introspection is skipped
211
+ * when the connection is missing secrets or unreachable.
212
+ */
213
+ tables: string[];
214
+ /** Provider metadata from the registry descriptor, when the provider class is resolvable. */
215
+ connector?: DataConnectorInfo | null;
216
+ }
217
+
218
+ /** Response of the data-connections route. */
219
+ export interface DataConnectionsResponse {
220
+ connections: DataConnectionInfo[];
221
+ }
222
+
223
+ /** Result of the connection-test route (mirrors the connector's testConnection capability). */
224
+ export interface DataConnectionTestResult {
225
+ ok: boolean;
226
+ error?: string;
227
+ }
228
+
229
+ /**
230
+ * A push-plan step kind. Open string union: connector DDL kinds today, and future push-step
231
+ * contributors (e.g. `"auth"` migration steps) extend it without a protocol bump.
232
+ */
233
+ export type DataPushStepKind =
234
+ | "createTable"
235
+ | "addColumn"
236
+ | "index"
237
+ | "junction"
238
+ | "auth"
239
+ | (string & Record<never, never>);
240
+
241
+ /** One step of a schema-push plan. */
242
+ export interface DataPushStep {
243
+ kind: DataPushStepKind;
244
+ /** Affected table, when derivable from the statement. */
245
+ table?: string;
246
+ /** One-line human description of the step. */
247
+ summary: string;
248
+ /** The compiled SQL statement, when the step is SQL-backed. */
249
+ sql?: string;
250
+ /** The connection the step applies to. */
251
+ connection?: string;
252
+ }
253
+
254
+ /** Request body of the data-push route. */
255
+ export interface DataPushRequest {
256
+ /** Restrict the push to one connection; omitted = every declared connection. */
257
+ connection?: string;
258
+ /** Compile the plan without executing it. */
259
+ dryRun?: boolean;
260
+ }
261
+
262
+ /** Result of the data-push route. */
263
+ export interface DataPushResult {
264
+ plan: DataPushStep[];
265
+ /** True when the plan was executed (never on dryRun or after an error). */
266
+ applied: boolean;
267
+ /** Drift/orphan warnings from the additive-only sync. */
268
+ warnings?: string[];
269
+ errors?: string[];
270
+ }
271
+
272
+ /** Query of the data-rows route (wire form: query params). */
273
+ export interface DataRowsQuery {
274
+ table: string;
275
+ /** Connection to resolve undeclared (system) tables against; default = first declared. */
276
+ connection?: string;
277
+ /** Page size; default 50. */
278
+ limit?: number;
279
+ offset?: number;
280
+ /** Column to order by (must exist on the table). */
281
+ orderBy?: string;
282
+ dir?: "asc" | "desc";
283
+ }
284
+
285
+ /** Column metadata from backend introspection. */
286
+ export interface DataColumnMeta {
287
+ name: string;
288
+ type: string;
289
+ pk?: boolean;
290
+ nullable?: boolean;
291
+ }
292
+
293
+ /** Result of the data-rows route. */
294
+ export interface DataRowsResult {
295
+ rows: Record<string, unknown>[];
296
+ /** Total row count of the table (for pagination). */
297
+ total: number;
298
+ columns: DataColumnMeta[];
299
+ }
300
+
301
+ /** Body of the row-insert route. */
302
+ export interface DataRowInsert {
303
+ table: string;
304
+ connection?: string;
305
+ values: Record<string, unknown>;
306
+ }
307
+
308
+ /** Body of the row-update route (keyed on the introspected primary key). */
309
+ export interface DataRowUpdate {
310
+ table: string;
311
+ connection?: string;
312
+ pk: string | number;
313
+ set: Record<string, unknown>;
314
+ }
315
+
316
+ /** Parameters of the row-delete route. */
317
+ export interface DataRowDelete {
318
+ table: string;
319
+ connection?: string;
320
+ pk: string | number;
321
+ }
322
+
323
+ // ─── Secrets (names only; specs/extensions.md §13) ──────────────────────────
324
+
325
+ /** Response of the secrets-list route — env-var NAMES only, never values. */
326
+ export interface SecretsListResponse {
327
+ names: string[];
328
+ }
329
+
330
+ /** Request body of the secrets-set route (values flow in, never back out). */
331
+ export interface SecretsSetRequest {
332
+ set?: Record<string, string>;
333
+ remove?: string[];
334
+ }
335
+
336
+ /** Response of the secrets-set route — the resulting NAMES, never values. */
337
+ export interface SecretsSetResponse {
338
+ ok: boolean;
339
+ names: string[];
340
+ }
341
+
115
342
  // ─── Packages ────────────────────────────────────────────────────────────────
116
343
 
117
344
  export interface PackageInfo {