@jxsuite/protocol 2.4.0 → 2.5.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
@@ -17,7 +17,7 @@ A backend is free to change the transport and path prefix; the **sub-path, metho
17
17
 
18
18
  ## Core vs optional
19
19
 
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.
20
+ Optional routes back optional `StudioPlatform` members, or, like `filesRead`, back no member and let an adapter serve one it already has more cheaply (their `degradation` names the route they stand in for). 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.
21
21
 
22
22
  Highlights an implementer should know:
23
23
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jxsuite/protocol",
3
- "version": "2.4.0",
3
+ "version": "2.5.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": {
package/src/index.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  /**
2
- * The Studio Backend Protocol package: wire types plus the canonical route
3
- * table every Jx Studio backend implements. See README.md for the implementer
4
- * guide.
2
+ * The Studio Backend Protocol package: wire types plus the canonical route table every Jx Studio
3
+ * backend implements. See README.md for the implementer guide.
5
4
  *
6
5
  * @license MIT
7
6
  */
@@ -10,3 +9,5 @@ export * from "./problem";
10
9
  export * from "./problems";
11
10
  export * from "./routes";
12
11
  export type * from "./types";
12
+ // The one runtime value types.ts carries: the batch-read limit a backend and a client both size to.
13
+ export { READ_FILES_MAX_PATHS } from "./types";
package/src/routes.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Studio Backend Protocol — the canonical route table. The dev server
3
- * (@jxsuite/server) is the reference implementation; every other backend
4
- * (desktop RPC bridge, cloud platforms) serves the same shapes, either at
5
- * these literal paths or through an equivalent transport. Optional routes
6
- * back optional StudioPlatform members — Studio degrades without them, as
7
- * described by each entry's `degradation`.
2
+ * Studio Backend Protocol — the canonical route table. The dev server (@jxsuite/server) is the
3
+ * reference implementation; every other backend (desktop RPC bridge, cloud platforms) serves the
4
+ * same shapes, either at these literal paths or through an equivalent transport. Optional routes
5
+ * mostly back optional StudioPlatform members — Studio degrades without them, as described by each
6
+ * entry's `degradation`. A few back no member at all: an adapter uses them to serve members it
7
+ * already has more cheaply (`filesRead`), and their `degradation` is the route they stand in for.
8
8
  *
9
9
  * @license MIT
10
10
  */
@@ -28,7 +28,10 @@ export type StudioRouteMethod = "GET" | "POST" | "PUT" | "DELETE";
28
28
  export interface StudioRoute {
29
29
  path: string;
30
30
  method: StudioRouteMethod;
31
- /** True when a backend may omit the route (its PAL member is optional). */
31
+ /**
32
+ * True when a backend may omit the route: its PAL member is optional, or it has no member and an
33
+ * adapter falls back to another route.
34
+ */
32
35
  optional: boolean;
33
36
  /** One-line contract summary. */
34
37
  summary: string;
@@ -111,6 +114,31 @@ export const STUDIO_ROUTES = {
111
114
  "files project-wide",
112
115
  ),
113
116
  fileRead: route("GET", "/__studio/file", "Read a file's text content"),
117
+ /**
118
+ * Many reads in ONE round trip, for a backend whose every request is a network hop.
119
+ *
120
+ * The first optional route with no `StudioPlatform` member behind it: the PAL keeps `readFile`,
121
+ * and an adapter for such a backend coalesces the reads issued in one tick into this call itself
122
+ * (specs/desktop.md §3.1, §10.1). Callers never see it, so a backend without it costs latency and
123
+ * nothing else — which is why the dev server, whose reads are loopback, does not serve it.
124
+ *
125
+ * The answer is partial by design, never a 400: past `READ_FILES_MAX_PATHS` paths or the
126
+ * backend's byte budget, the rest come back in `omitted` for the client to ask again, and the
127
+ * first path is always answered so that asking again makes progress. Each answered path carries
128
+ * its own outcome, because one missing file is not a failed batch. It is a READ: a read-only
129
+ * session may call it, and whatever a single `fileRead` does on the backend's side (a cloud
130
+ * session fetches and stores the file it serves) this does per path too.
131
+ */
132
+ filesRead: route(
133
+ "POST",
134
+ "/__studio/files/read",
135
+ "Read many files' text content {paths} → ReadFilesResult {files: [path, {content} | " +
136
+ "{error: {status, code?, message}}][], omitted}. At most READ_FILES_MAX_PATHS (200) paths " +
137
+ "and a backend byte budget per call: the excess is listed in `omitted` (never a 400) and " +
138
+ "the first path is always answered. Per-path errors: 404 missing/deleted, 415 binary, 413 " +
139
+ "too large, 502 upstream. Allowed for read-only sessions; keeps fileRead's server-side effects",
140
+ "Adapters fall back to one fileRead per path.",
141
+ ),
114
142
  fileWrite: route("PUT", "/__studio/file", "Write a file's text content"),
115
143
  fileDelete: route("DELETE", "/__studio/file", "Delete a file"),
116
144
  fileUpload: route(
@@ -475,7 +503,10 @@ export function coreRouteNames(): StudioRouteName[] {
475
503
  );
476
504
  }
477
505
 
478
- /** Names of the optional routes (each backs an optional StudioPlatform member). */
506
+ /**
507
+ * Names of the optional routes (each backs an optional StudioPlatform member, or is an adapter's
508
+ * optimisation with a fallback).
509
+ */
479
510
  export function optionalRouteNames(): StudioRouteName[] {
480
511
  return (Object.keys(STUDIO_ROUTES) as StudioRouteName[]).filter(
481
512
  (name) => STUDIO_ROUTES[name].optional,
package/src/types.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  /**
2
- * Studio Backend Protocol — wire types. Every shape a Studio backend serves
3
- * (or a `StudioPlatform` adapter consumes) lives here so client adapters and
4
- * server implementations (the dev server, the desktop RPC bridge, cloud
5
- * platforms) share one contract. Environment-agnostic: no DOM, no node —
2
+ * Studio Backend Protocol — wire types. Every shape a Studio backend serves (or a `StudioPlatform`
3
+ * adapter consumes) lives here so client adapters and server implementations (the dev server, the
4
+ * desktop RPC bridge, cloud platforms) share one contract. Environment-agnostic: no DOM, no node —
6
5
  * importable in browsers, Bun, and Cloudflare Workers alike.
7
6
  *
8
7
  * @license MIT
@@ -87,6 +86,45 @@ export interface UploadResult {
87
86
  size?: number;
88
87
  }
89
88
 
89
+ /**
90
+ * The most paths one `filesRead` request may name. A backend answers at most this many and lists
91
+ * the rest under {@link ReadFilesResult.omitted}, so a client that sends more is not refused — it is
92
+ * answered in part, and asks again.
93
+ *
94
+ * The one runtime value in this file: a client sizes its batches to it, so it has to exist at
95
+ * runtime rather than only in a summary string.
96
+ */
97
+ export const READ_FILES_MAX_PATHS = 200;
98
+
99
+ /** Body of `POST /__studio/files/read` (`filesRead`): the project-relative paths to read. */
100
+ export interface ReadFilesRequest {
101
+ paths: string[];
102
+ }
103
+
104
+ /**
105
+ * One path's answer inside a {@link ReadFilesResult}: its text, or why it has none.
106
+ *
107
+ * Per path rather than per request, because one unreadable file in a batch of two hundred is not a
108
+ * failed batch. `status` borrows the HTTP code the single-file read would have answered (404
109
+ * missing or deleted, 415 binary, 413 too large, 502 upstream), so a client can treat both routes'
110
+ * failures alike.
111
+ */
112
+ export type ReadFilesOutcome =
113
+ | { content: string }
114
+ | { error: { status: number; code?: string; message: string } };
115
+
116
+ /**
117
+ * Answer of `POST /__studio/files/read`. `files` pairs each answered path with its outcome;
118
+ * `omitted` lists the requested paths this response did not get to (a path or byte budget ran out).
119
+ * Omission is never an error and never a 400: the client asks for those paths again. A backend
120
+ * always answers the FIRST requested path, which is what guarantees that asking again makes
121
+ * progress.
122
+ */
123
+ export interface ReadFilesResult {
124
+ files: [path: string, outcome: ReadFilesOutcome][];
125
+ omitted: string[];
126
+ }
127
+
90
128
  /**
91
129
  * What a backend will accept as an upload, when it says.
92
130
  *