@jxsuite/protocol 2.2.0 → 2.3.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
@@ -1,17 +1,9 @@
1
1
  # @jxsuite/protocol
2
2
 
3
- The **Studio Backend Protocol**: the wire types and canonical route table that
4
- every Jx Studio backend implements. Jx Studio is backend-agnostic. The editor
5
- talks to whatever registered a `StudioPlatform` adapter (see
6
- `@jxsuite/studio/platform`), and every adapter ultimately speaks the contract
7
- defined here. This package is the single source of truth for that contract:
3
+ The **Studio Backend Protocol**: the wire types and canonical route table that every Jx Studio backend implements. Jx Studio is backend-agnostic. The editor talks to whatever registered a `StudioPlatform` adapter (see `@jxsuite/studio/platform`), and every adapter ultimately speaks the contract defined here. This package is the single source of truth for that contract:
8
4
 
9
- - `@jxsuite/protocol/types`: the request/response shapes (`DirEntry`,
10
- `GitStatusResult`, `StarterInfo`, `AiModelsResponse`, `CfConnection`, …).
11
- Environment-agnostic, with no DOM and no node, so the types are importable
12
- in browsers, Bun, and Cloudflare Workers alike.
13
- - `@jxsuite/protocol/routes`: `STUDIO_ROUTES`, the canonical endpoint table
14
- with methods, one-line contract summaries, and per-route optionality.
5
+ - `@jxsuite/protocol/types`: the request/response shapes (`DirEntry`, `GitStatusResult`, `StarterInfo`, `AiModelsResponse`, `CfConnection`, …). Environment-agnostic, with no DOM and no node, so the types are importable in browsers, Bun, and Cloudflare Workers alike.
6
+ - `@jxsuite/protocol/routes`: `STUDIO_ROUTES`, the canonical endpoint table with methods, one-line contract summaries, and per-route optionality.
15
7
 
16
8
  ## Who implements this
17
9
 
@@ -21,48 +13,21 @@ defined here. This package is the single source of truth for that contract:
21
13
  | Desktop (Electrobun / Chromium) | Typed RPC / WebSocket JSON-RPC dispatching into the same handlers |
22
14
  | Cloud platforms | HTTP behind a session gateway (e.g. `/api/v1/p/:owner/:repo/:branch/studio/*`), same sub-paths and shapes |
23
15
 
24
- A backend is free to change the transport and path prefix; the **sub-path,
25
- method, and body shapes are the contract**. `STUDIO_PROTOCOL_VERSION` bumps
26
- when any shape changes incompatibly.
16
+ A backend is free to change the transport and path prefix; the **sub-path, method, and body shapes are the contract**. `STUDIO_PROTOCOL_VERSION` bumps when any shape changes incompatibly.
27
17
 
28
18
  ## Core vs optional
29
19
 
30
- Optional routes back optional `StudioPlatform` members. Omitting one is not an
31
- error. Studio degrades exactly as each entry's `degradation` describes (the
32
- starter picker empties, the Projects catalogue hides, the Publish panel
33
- explains git-push publishing, and so on). `coreRouteNames()` /
34
- `optionalRouteNames()` split the table programmatically; a conformance test
35
- for a new backend can iterate `STUDIO_ROUTES` and assert every core route is
36
- served.
20
+ Optional routes back optional `StudioPlatform` members. Omitting one is not an error. Studio degrades exactly as each entry's `degradation` describes (the starter picker empties, the Projects catalogue hides, the Publish panel explains git-push publishing, and so on). `coreRouteNames()` / `optionalRouteNames()` split the table programmatically; a conformance test for a new backend can iterate `STUDIO_ROUTES` and assert every core route is served.
37
21
 
38
22
  Highlights an implementer should know:
39
23
 
40
- - **`git/commit`** commits the staged files if any are staged, otherwise all
41
- dirty files. Cloud backends may make commit+push atomic and treat
42
- `git/push` as a sync check (`ahead` stays 0).
43
- - **`git/pull`** returns `409 { code: "pull_conflict", conflicts: [paths] }`
44
- when local dirty files overlap remote changes.
45
- - **`format`** dispatches `{ format, action: "parse" | "serialize", source? |
46
- doc?, options? }` through the project's format registry; without it only
47
- `.json` documents open.
48
- - **`ai/chat`** accepts `{ messages, tools, systemPrompt, model }` and streams
49
- the normalized `StreamEvent` SSE defined by `@jxsuite/ai/streaming-client`.
50
- `ai/models` returns `AiModelsResponse`; report `configured: true` when the
51
- backend holds credentials (Studio then unlocks the assistant without a
52
- locally stored key) and `managed: true` when the platform brokers them.
53
- - **`cf/proxy`** is an allowlisted Cloudflare API passthrough (accounts and
54
- Pages projects/deployments only). Credentials are injected by the backend
55
- and must never be persisted by stateless implementations. On the dev server
56
- the credential is a header-borne user token, and on cloud platforms a
57
- vaulted OAuth token.
58
- - **Errors** are `{ error, code?, detail? }` (`ErrorBody`) with a meaningful
59
- HTTP status; `code` is the machine-readable discriminator Studio switches
60
- on (e.g. `remote_moved`, `cf_not_connected`, `needs_installation_access`).
24
+ - **`git/commit`** commits the staged files if any are staged, otherwise all dirty files. Cloud backends may make commit+push atomic and treat `git/push` as a sync check (`ahead` stays 0).
25
+ - **`git/pull`** returns `409 { code: "pull_conflict", conflicts: [paths] }` when local dirty files overlap remote changes.
26
+ - **`format`** dispatches `{ format, action: "parse" | "serialize", source? | doc?, options? }` through the project's format registry; without it only `.json` documents open.
27
+ - **`ai/chat`** accepts `{ messages, tools, systemPrompt, model }` and streams the normalized `StreamEvent` SSE defined by `@jxsuite/ai/streaming-client`. `ai/models` returns `AiModelsResponse`; report `configured: true` when the backend holds credentials (Studio then unlocks the assistant without a locally stored key) and `managed: true` when the platform brokers them.
28
+ - **`cf/proxy`** is an allowlisted Cloudflare API passthrough (accounts and Pages projects/deployments only). Credentials are injected by the backend and must never be persisted by stateless implementations. On the dev server the credential is a header-borne user token, and on cloud platforms a vaulted OAuth token.
29
+ - **Errors** are `{ error, code?, detail? }` (`ErrorBody`) with a meaningful HTTP status; `code` is the machine-readable discriminator Studio switches on (e.g. `remote_moved`, `cf_not_connected`, `needs_installation_access`).
61
30
 
62
31
  ## Versioning
63
32
 
64
- The types are published as TypeScript source (like every `@jxsuite` package)
65
- and follow the monorepo's release train. Within the monorepo, packages depend
66
- on it via `workspace:^`. Consumers install `@jxsuite/protocol` and import its
67
- `/types` and `/routes` entrypoints; its only runtime dependency is
68
- `@jxsuite/schema`.
33
+ The types are published as TypeScript source (like every `@jxsuite` package) and follow the monorepo's release train. Within the monorepo, packages depend on it via `workspace:^`. Consumers install `@jxsuite/protocol` and import its `/types` and `/routes` entrypoints; its only runtime dependency is `@jxsuite/schema`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jxsuite/protocol",
3
- "version": "2.2.0",
3
+ "version": "2.3.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": {
@@ -27,6 +27,6 @@
27
27
  "test:coverage": "bun test --isolate --coverage"
28
28
  },
29
29
  "dependencies": {
30
- "@jxsuite/schema": "^2.1.0"
30
+ "@jxsuite/schema": "^2.2.0"
31
31
  }
32
32
  }
package/src/routes.ts CHANGED
@@ -190,6 +190,16 @@ export const STUDIO_ROUTES = {
190
190
  "Only .json documents open (backs listFormats); descriptor-contributed settings sections " +
191
191
  "do not appear.",
192
192
  ),
193
+ extensionCatalog: route(
194
+ "GET",
195
+ "/__studio/catalog",
196
+ "Extensions this backend can OFFER (ExtensionCatalogEntry[]) — the shipped first-party " +
197
+ "catalogue plus any package in the project's own dependencies exporting a jx-extension.json, " +
198
+ "each marked with whether the host resolves it without a project install and whether the " +
199
+ "project has it installed. A capability rather than a constant: a Worker ships a fixed set " +
200
+ "of extension packages (specs/extensions.md §5.5). Backs listExtensionCatalog.",
201
+ "The Extensions section offers no catalogue; adding an extension stays a typed package name.",
202
+ ),
193
203
  projectSchemas: route(
194
204
  "GET",
195
205
  "/__studio/project-schemas",
package/src/types.ts CHANGED
@@ -251,6 +251,73 @@ export interface ExtensionsInfo {
251
251
  }[];
252
252
  }
253
253
 
254
+ // ─── Extension catalogue ─────────────────────────────────────────────────────
255
+ /* The AVAILABLE half of the pair whose ENABLED half is ExtensionsInfo above. A backend answers it
256
+ for itself rather than serving a constant, because not every host can run every extension: a
257
+ Worker ships a fixed set of extension packages (specs/extensions.md §5.5), and one it does not
258
+ bundle is dropped from the registry before composition. Offering such an extension would promise
259
+ an action the host would refuse. */
260
+
261
+ /** One `project.json` section an extension's classes claim (specs/extensions.md §9). */
262
+ export interface ExtensionSectionInfo {
263
+ /** The `project.json` top-level property, e.g. "content". */
264
+ key: string;
265
+ /** The owning class's `project.title`, e.g. "Content Types". */
266
+ title?: string;
267
+ }
268
+
269
+ /** One extension a backend can offer a project, whether or not the project enables it. */
270
+ export interface ExtensionCatalogEntry {
271
+ /** Package name: the identity, and the `bun add` argument. */
272
+ name: string;
273
+ /**
274
+ * The string that goes in `project.json` `extensions[]`. Usually `name`, but a linked or
275
+ * path-installed package enables under a different specifier — the same distinction
276
+ * {@link ExtensionsInfo} draws between `specifier` and `name`. Absent means they agree.
277
+ */
278
+ specifier?: string;
279
+ title?: string;
280
+ description?: string;
281
+ /**
282
+ * The sections enabling it makes legal.
283
+ *
284
+ * The one field a client cannot compute for itself: `listExtensions` describes only extensions
285
+ * already enabled, which is the state the reader is trying to leave. It is also what lets a
286
+ * surface warn before disabling something whose sections the project still uses.
287
+ */
288
+ sections: ExtensionSectionInfo[];
289
+ /** File extensions its format classes claim (".md", ".csv"), when it claims any. */
290
+ formats?: string[];
291
+ /** Other catalogue members it depends on — auth needs connector. */
292
+ requires?: string[];
293
+ /**
294
+ * THIS host resolves the package without a project install, so enabling it is a config write
295
+ * alone. Probed per host, never declared: what a desktop build stages and what a Worker bundles
296
+ * are different facts, and neither is knowable from the package list.
297
+ */
298
+ bundled?: boolean;
299
+ /**
300
+ * The PROJECT resolves it.
301
+ *
302
+ * Answered here rather than derived from `listPackages`, because that member does not mean one
303
+ * thing across backends: the dev server drops a declared dependency it cannot resolve, while the
304
+ * desktop reads the manifest and keeps it. A host with no module resolution at all degrades this
305
+ * to "declared in package.json" and says so in its own documentation.
306
+ */
307
+ installed?: boolean;
308
+ /** The shipped catalogue, or a package discovered in this project's own dependencies. */
309
+ source: "first-party" | "project";
310
+ /** Documentation deep link. */
311
+ docs?: string;
312
+ /**
313
+ * Why this entry cannot be enabled as it stands, as one sentence for a disabled control.
314
+ *
315
+ * Set rather than dropped: a package the reader installed on purpose vanishing with no
316
+ * explanation is worse than a row that says what is wrong with it.
317
+ */
318
+ problem?: string;
319
+ }
320
+
254
321
  /**
255
322
  * Response of the project-schemas route: the project's generated entry documents
256
323
  * (project.schema.json / document.schema.json), PRE-BUNDLED into self-contained compound schemas so