@actana/sdk 0.2.2 → 0.3.2

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.
@@ -0,0 +1,227 @@
1
+ import { type CoreFilesFetch } from "./core-files-http.ts";
2
+ /**
3
+ * Whether this Core's file surface may be used at all, and — when it may not —
4
+ * why, in words an operator can act on (F9).
5
+ */
6
+ export type CoreFilesAvailability = {
7
+ available: true;
8
+ } | {
9
+ available: false;
10
+ reason: string;
11
+ };
12
+ /** How a written entry landed. `overwritten` names a file that was already there (F5). */
13
+ export type CoreFileWriteResult = "written" | "overwritten";
14
+ /**
15
+ * One entry of a listing, in the manifest shape PR 215 established: `{path,
16
+ * size, mtime, mode, sha256}`.
17
+ *
18
+ * `path` is **Project-relative**, which is the address space F1 gives the
19
+ * operator — the same string that goes back to `download`.
20
+ *
21
+ * `sha256` is nullable on purpose, and the Core settled which way it means:
22
+ * hashing every entry of a large tree is expensive, so a listing computes
23
+ * digests **on request** ({@link CoreFileListOptions.sha256}) and a transfer
24
+ * computes them eagerly, the bytes being already in hand. So `null` reads as
25
+ * "no bytes" for a directory and "nobody asked" for anything else.
26
+ */
27
+ export type CoreFileEntry = {
28
+ path: string;
29
+ size: number;
30
+ mtime: number;
31
+ mode: number;
32
+ sha256: string | null;
33
+ /** Present when the listing distinguishes them; absent listings read as files. */
34
+ kind?: "file" | "directory" | "symlink";
35
+ };
36
+ /** One line of a write's NDJSON progress stream. */
37
+ export type CoreFileProgress = ({
38
+ type: "entry";
39
+ result: CoreFileWriteResult;
40
+ } & CoreFileEntry) | {
41
+ type: "done";
42
+ entries: number;
43
+ bytes: number;
44
+ };
45
+ /**
46
+ * What a read hands back: metadata, and **a stream**.
47
+ *
48
+ * There is no `bytes` or `buffer` field here and that absence is the feature.
49
+ * A gigabyte file must not materialise in memory, so the only way to get at the
50
+ * content is to consume `stream` — a caller who wants it whole has to write
51
+ * that themselves and own the decision. `size` is what the Core declared, which
52
+ * is `null` for a folder, whose tar length is not known until it has been walked.
53
+ */
54
+ export type CoreFileDownload = {
55
+ /** `file` for raw bytes, `tar` for a folder crossing as one archive (ADR 0029). */
56
+ kind: "file" | "tar";
57
+ size: number | null;
58
+ mode: number | null;
59
+ mtime: number | null;
60
+ stream: ReadableStream<Uint8Array>;
61
+ };
62
+ /**
63
+ * Anything a caller can upload.
64
+ *
65
+ * A Node `Readable` — `fs.createReadStream(…)` — is an async iterable of
66
+ * `Buffer`, so it is accepted directly and without this package importing
67
+ * `node:stream` to say so. A web `ReadableStream` passes through untouched.
68
+ */
69
+ export type CoreFileSource = ReadableStream<Uint8Array> | AsyncIterable<Uint8Array | string>;
70
+ export type CoreFileUploadOptions = {
71
+ /** Project-relative destination. `""` is the Project root, which only a tar may target. */
72
+ path: string;
73
+ body: CoreFileSource;
74
+ /**
75
+ * `file` writes one file at `path`; `tar` unpacks an archive into it
76
+ * (ADR 0029). Default `file`.
77
+ */
78
+ kind?: "file" | "tar";
79
+ /** Unix permission bits for a single-file write. The Core defaults to `0o644`. */
80
+ mode?: number;
81
+ /** Modification time in epoch milliseconds, preserved across the wire. */
82
+ mtime?: number;
83
+ /**
84
+ * Declared body length, when the caller knows it.
85
+ *
86
+ * Worth passing: it is what lets the Core run its free-space precheck and
87
+ * refuse `507 insufficient-storage` before the transfer rather than fail with
88
+ * `ENOSPC` half-way through. Omitted, the upload is chunked and there is
89
+ * nothing to check against.
90
+ */
91
+ contentLength?: number;
92
+ signal?: AbortSignal;
93
+ };
94
+ export type CoreFileDownloadOptions = {
95
+ /** Project-relative path. A directory comes back as one streamed tar. */
96
+ path: string;
97
+ signal?: AbortSignal;
98
+ };
99
+ export type CoreFileListOptions = {
100
+ /** Subtree to list, Project-relative. Default: the whole Project. */
101
+ path?: string;
102
+ /** Maximum depth to descend. `1` is the immediate children. Default: the whole tree. */
103
+ depth?: number;
104
+ /**
105
+ * Ask the Core to compute {@link CoreFileEntry.sha256} for every file and
106
+ * symlink under the path. **Off by default, and not free**: a listing does not
107
+ * have the bytes in hand, so digests mean reading every one of them (ADR 0027
108
+ * D6). Left off, every `sha256` comes back `null`.
109
+ */
110
+ sha256?: boolean;
111
+ signal?: AbortSignal;
112
+ };
113
+ export type CoreFilesOptions = {
114
+ projectId: string;
115
+ /** The Core's HTTPS origin — `CoreConnection.httpsBaseUrl`. No trailing slash. */
116
+ baseUrl: string;
117
+ /** The same signed bearer the core link's `auth` frame presents, or null on a loopback rig. */
118
+ bearer: string | null;
119
+ /**
120
+ * Read at the top of every call rather than once at construction: a Core can
121
+ * be downgraded and a client survives reconnects, so the answer belongs to
122
+ * the *current* connection and a remembered one would send a caller at a route
123
+ * that is no longer there.
124
+ */
125
+ availability: () => CoreFilesAvailability;
126
+ fetch: CoreFilesFetch;
127
+ };
128
+ /**
129
+ * A Project's files, over the Core's HTTPS routes.
130
+ *
131
+ * Reached as `client.project(id).files` rather than constructed directly.
132
+ */
133
+ export declare class CoreFiles {
134
+ private readonly opts;
135
+ constructor(opts: CoreFilesOptions);
136
+ /**
137
+ * The Project's tree, one entry at a time.
138
+ *
139
+ * Reads the Core's real listing route — `GET …/files/list`, see
140
+ * {@link listUrl} — which streams `{path, kind, size, mtime, mode, sha256}`
141
+ * per entry as NDJSON. `core-files-list-contract.test.ts` drives this method
142
+ * against that route in process and runs in **both** packages' suites, so the
143
+ * two halves of the URL cannot drift apart again without a red test (#218).
144
+ *
145
+ * Both line shapes are accepted — a bare manifest entry and one wrapped as
146
+ * `{type: "entry", …}`, which is what the Core's listing and its write route
147
+ * both emit. `skipped` lines are passed over: a path the walk could not read
148
+ * is a fact about the tree, not an entry in it, and not a reason to stop.
149
+ */
150
+ list(opts?: CoreFileListOptions): AsyncGenerator<CoreFileEntry, void, undefined>;
151
+ /**
152
+ * Write a stream into the Project, and watch it land.
153
+ *
154
+ * The returned iterable is the Core's NDJSON progress stream, parsed: one
155
+ * `entry` line per file — each carrying `result: "written" | "overwritten"`,
156
+ * so **every overwrite is named** rather than inferred — then one `done` line
157
+ * with the totals.
158
+ *
159
+ * **Lazy, like everything else here.** Calling `upload` sends nothing; the
160
+ * request goes out on the first `next()`. A caller who does not care about
161
+ * progress still has to drain the iterable, and that is the honest shape: the
162
+ * alternative is a method that returns a promise and quietly buffers a
163
+ * gigabyte of progress nobody read.
164
+ *
165
+ * Throws {@link CoreFilesConflictError} when another write already holds this
166
+ * Project's lease (F8) — immediately, and without retrying. Throws
167
+ * {@link CoreFilesStreamError} when the write fails part-way through, which
168
+ * arrives as the stream's last line rather than as a status code, the `200`
169
+ * having been spent on the first entry.
170
+ */
171
+ upload(opts: CoreFileUploadOptions): AsyncGenerator<CoreFileProgress, void, undefined>;
172
+ /**
173
+ * Read a file — or a folder, as one streamed tar — **as a stream**.
174
+ *
175
+ * The response body is handed through untouched. Nothing on this path calls
176
+ * `arrayBuffer`, `text` or `blob`, so a gigabyte file crosses in whatever
177
+ * chunks the socket delivers and only ever occupies what the consumer has not
178
+ * yet read. `core-files-streaming.test.ts` holds that claim to a real
179
+ * gigabyte and watches the heap while it crosses.
180
+ *
181
+ * The metadata comes off headers the Core already had — `stat` on the file it
182
+ * was about to open — so nothing is read twice to produce it.
183
+ */
184
+ download(opts: CoreFileDownloadOptions): Promise<CoreFileDownload>;
185
+ /**
186
+ * `…/v1/projects/:projectId/files/list?path=<relative>` — a route of its own.
187
+ *
188
+ * This module used to send `?list=1` on the read route instead, and argued for
189
+ * it on cost: the Core's `parseRoute` matched `/v1/projects/:id/files` on an
190
+ * exact four-segment split, so a `…/files/list` leaf was a change to that
191
+ * parser and a query parameter was not. That cost is now paid — #216 shipped a
192
+ * parser that reads the fifth segment — and what is left is the Core's
193
+ * argument, which was never about cost: a listing and a read of the same
194
+ * folder answer with completely different things, one a manifest and one a
195
+ * tar. A query parameter that a proxy, a redirect or a hand-edited URL can
196
+ * drop turns "list this folder" into "download this folder", which for a
197
+ * `node_modules` is a mistake measured in gigabytes rather than in a 400. A
198
+ * path segment cannot be dropped silently. See issue 218.
199
+ *
200
+ * Still `protected`: a caller pinned to an older Core can subclass rather than
201
+ * wait for a release.
202
+ */
203
+ protected listUrl(opts: CoreFileListOptions): string;
204
+ /** `…/v1/projects/:projectId/files?path=<relative>` — the read and write route. */
205
+ protected fileUrl(filePath: string): string;
206
+ /**
207
+ * `<baseUrl>/v1/projects/:projectId/<leaf>`, with the Project id escaped.
208
+ *
209
+ * The two routes above differ by their leaf and by nothing else, and that is
210
+ * worth having in one place: the day this surface gains a third, the origin,
211
+ * the version prefix and the escaping should not be a third opportunity to get
212
+ * one of them subtly wrong.
213
+ */
214
+ private routeUrl;
215
+ private authHeaders;
216
+ /**
217
+ * The F9 gate, checked before every call and *before any byte leaves*.
218
+ *
219
+ * The reason is carried into the error rather than flattened to a boolean,
220
+ * because the two ways this fails want different actions from an operator: a
221
+ * Core that has not finished connecting is a wait, and a Core that announced
222
+ * no `files` capability is a Core that predates the surface — which is a
223
+ * supported state, not a fault and not a needs-update.
224
+ */
225
+ private requireAvailable;
226
+ }
227
+ //# sourceMappingURL=core-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core-files.d.ts","sourceRoot":"","sources":["../src/core-files.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,410 @@
1
+ // `project.files.list / upload / download` — the SDK half of #129 F12 (#167).
2
+ //
3
+ // A Project's files are reached over the Core's `/v1/…` HTTPS routes, never
4
+ // over the core link (ADR 0028). What this module adds on top of those routes
5
+ // is the shape a caller should actually hold:
6
+ //
7
+ // const project = client.project(projectId);
8
+ // for await (const entry of project.files.list()) …
9
+ // for await (const line of project.files.upload({ path, body })) …
10
+ // const { stream } = await project.files.download({ path });
11
+ //
12
+ // ## Everything here streams, and streams *lazily*
13
+ //
14
+ // The three methods have one property in common and it is the reason the module
15
+ // exists rather than four `fetch` calls at a call site: **nothing runs ahead of
16
+ // its consumer.** Each is a pull-driven async generator or a stream handed
17
+ // straight through — no background pump, no queue that fills while the caller is
18
+ // busy, no `await res.arrayBuffer()` anywhere on a success path.
19
+ //
20
+ // This is a correctness property, not a tidiness one. An async iterable that
21
+ // eagerly drains its source has *defeated the streaming it appears to offer*:
22
+ // the bytes are all in memory, the only thing being paced is the handing-out.
23
+ // A caller writing each downloaded chunk to a slow disk, or posting each upload
24
+ // progress line to a webhook, is the ordinary case — and it is exactly the case
25
+ // an eager implementation turns into unbounded memory growth. `for await` over
26
+ // a generator suspends it at the `yield` until the consumer asks again, and the
27
+ // Core's own `res.write` backpressures once the socket buffer fills, so a slow
28
+ // consumer here becomes a slow reader on the socket and eventually a paused
29
+ // writer on the Core. That chain is what `core-files-backpressure.test.ts`
30
+ // pins, with a consumer that deliberately dawdles.
31
+ //
32
+ // ## What this module does not do
33
+ //
34
+ // **It does not build tar archives.** A folder crosses as one streamed tar
35
+ // (ADR 0029) and the packing lives on the Core, which is the side with the
36
+ // files. `upload` takes whatever stream it is given and labels it; a caller
37
+ // uploading a folder hands it a tar stream and says `kind: "tar"`.
38
+ //
39
+ // **It does not retry.** See {@link CoreFilesConflictError} — the one-write-
40
+ // per-Project rule (F8) is a conflict for a human to resolve, and a silent
41
+ // retry loop would turn the Core's immediate, well-worded refusal into a hang.
42
+ import { CoreFilesStreamError, CoreFilesUnavailableError, refusalFrom, } from "./core-files-http.js";
43
+ /**
44
+ * A Project's files, over the Core's HTTPS routes.
45
+ *
46
+ * Reached as `client.project(id).files` rather than constructed directly.
47
+ */
48
+ export class CoreFiles {
49
+ opts;
50
+ constructor(opts) {
51
+ this.opts = opts;
52
+ }
53
+ /**
54
+ * The Project's tree, one entry at a time.
55
+ *
56
+ * Reads the Core's real listing route — `GET …/files/list`, see
57
+ * {@link listUrl} — which streams `{path, kind, size, mtime, mode, sha256}`
58
+ * per entry as NDJSON. `core-files-list-contract.test.ts` drives this method
59
+ * against that route in process and runs in **both** packages' suites, so the
60
+ * two halves of the URL cannot drift apart again without a red test (#218).
61
+ *
62
+ * Both line shapes are accepted — a bare manifest entry and one wrapped as
63
+ * `{type: "entry", …}`, which is what the Core's listing and its write route
64
+ * both emit. `skipped` lines are passed over: a path the walk could not read
65
+ * is a fact about the tree, not an entry in it, and not a reason to stop.
66
+ */
67
+ async *list(opts = {}) {
68
+ this.requireAvailable("listing a Project's files");
69
+ const res = await this.opts.fetch({
70
+ method: "GET",
71
+ url: this.listUrl(opts),
72
+ headers: { ...this.authHeaders(), accept: "application/x-ndjson" },
73
+ ...(opts.signal ? { signal: opts.signal } : {}),
74
+ });
75
+ if (!res.ok)
76
+ throw await refusalFrom(res, "listing this Project");
77
+ if (!res.body)
78
+ return;
79
+ for await (const line of ndjsonLines(res.body)) {
80
+ const record = line;
81
+ const type = typeof record.type === "string" ? record.type : "entry";
82
+ if (type === "done")
83
+ return;
84
+ if (type === "error") {
85
+ throw new CoreFilesStreamError(typeof record.code === "string" ? record.code : "read-failed", typeof record.message === "string" ? record.message : "the listing failed part-way through");
86
+ }
87
+ if (type !== "entry")
88
+ continue;
89
+ const entry = readEntry(record);
90
+ if (entry)
91
+ yield entry;
92
+ }
93
+ }
94
+ /**
95
+ * Write a stream into the Project, and watch it land.
96
+ *
97
+ * The returned iterable is the Core's NDJSON progress stream, parsed: one
98
+ * `entry` line per file — each carrying `result: "written" | "overwritten"`,
99
+ * so **every overwrite is named** rather than inferred — then one `done` line
100
+ * with the totals.
101
+ *
102
+ * **Lazy, like everything else here.** Calling `upload` sends nothing; the
103
+ * request goes out on the first `next()`. A caller who does not care about
104
+ * progress still has to drain the iterable, and that is the honest shape: the
105
+ * alternative is a method that returns a promise and quietly buffers a
106
+ * gigabyte of progress nobody read.
107
+ *
108
+ * Throws {@link CoreFilesConflictError} when another write already holds this
109
+ * Project's lease (F8) — immediately, and without retrying. Throws
110
+ * {@link CoreFilesStreamError} when the write fails part-way through, which
111
+ * arrives as the stream's last line rather than as a status code, the `200`
112
+ * having been spent on the first entry.
113
+ */
114
+ async *upload(opts) {
115
+ this.requireAvailable("writing files to a Project");
116
+ const headers = {
117
+ ...this.authHeaders(),
118
+ // `application/x-tar` is what tells the Core to unpack rather than to
119
+ // write one file — the route branches on this header and nothing else.
120
+ "content-type": opts.kind === "tar" ? "application/x-tar" : "application/octet-stream",
121
+ accept: "application/x-ndjson",
122
+ };
123
+ if (opts.mode !== undefined)
124
+ headers["x-actana-file-mode"] = String(opts.mode & 0o777);
125
+ if (opts.mtime !== undefined)
126
+ headers["x-actana-file-mtime"] = String(Math.floor(opts.mtime));
127
+ if (opts.contentLength !== undefined)
128
+ headers["content-length"] = String(opts.contentLength);
129
+ const res = await this.opts.fetch({
130
+ method: "PUT",
131
+ url: this.fileUrl(opts.path),
132
+ headers,
133
+ body: sourceStream(opts.body),
134
+ ...(opts.signal ? { signal: opts.signal } : {}),
135
+ });
136
+ // The refusal a large upload cares about arrives here, while the body is
137
+ // still going out: the Core takes the write lease before it reads a byte,
138
+ // so `409 transfer-in-progress` is answered from the status line rather
139
+ // than after a gigabyte has crossed.
140
+ if (!res.ok)
141
+ throw await refusalFrom(res, `writing ${opts.path || "this Project"}`);
142
+ if (!res.body)
143
+ return;
144
+ for await (const line of ndjsonLines(res.body)) {
145
+ const record = line;
146
+ if (record.type === "error") {
147
+ throw new CoreFilesStreamError(typeof record.code === "string" ? record.code : "write-failed", typeof record.message === "string" ? record.message : "the write failed part-way through");
148
+ }
149
+ if (record.type === "done") {
150
+ yield {
151
+ type: "done",
152
+ entries: numberOr(record.entries, 0),
153
+ bytes: numberOr(record.bytes, 0),
154
+ };
155
+ continue;
156
+ }
157
+ const entry = readEntry(record);
158
+ if (entry) {
159
+ yield {
160
+ type: "entry",
161
+ ...entry,
162
+ result: record.result === "overwritten" ? "overwritten" : "written",
163
+ };
164
+ }
165
+ }
166
+ }
167
+ /**
168
+ * Read a file — or a folder, as one streamed tar — **as a stream**.
169
+ *
170
+ * The response body is handed through untouched. Nothing on this path calls
171
+ * `arrayBuffer`, `text` or `blob`, so a gigabyte file crosses in whatever
172
+ * chunks the socket delivers and only ever occupies what the consumer has not
173
+ * yet read. `core-files-streaming.test.ts` holds that claim to a real
174
+ * gigabyte and watches the heap while it crosses.
175
+ *
176
+ * The metadata comes off headers the Core already had — `stat` on the file it
177
+ * was about to open — so nothing is read twice to produce it.
178
+ */
179
+ async download(opts) {
180
+ this.requireAvailable("reading a Project's files");
181
+ const res = await this.opts.fetch({
182
+ method: "GET",
183
+ url: this.fileUrl(opts.path),
184
+ headers: this.authHeaders(),
185
+ ...(opts.signal ? { signal: opts.signal } : {}),
186
+ });
187
+ if (!res.ok)
188
+ throw await refusalFrom(res, `reading ${opts.path || "this Project"}`);
189
+ if (!res.body) {
190
+ throw new CoreFilesStreamError("read-failed", `the Core answered ${opts.path} with no body`);
191
+ }
192
+ return {
193
+ kind: res.headers.get("x-actana-transfer-kind") === "tar" ? "tar" : "file",
194
+ size: headerNumber(res, "x-actana-file-size") ?? headerNumber(res, "content-length"),
195
+ mode: headerNumber(res, "x-actana-file-mode"),
196
+ mtime: headerNumber(res, "x-actana-file-mtime"),
197
+ stream: res.body,
198
+ };
199
+ }
200
+ /**
201
+ * `…/v1/projects/:projectId/files/list?path=<relative>` — a route of its own.
202
+ *
203
+ * This module used to send `?list=1` on the read route instead, and argued for
204
+ * it on cost: the Core's `parseRoute` matched `/v1/projects/:id/files` on an
205
+ * exact four-segment split, so a `…/files/list` leaf was a change to that
206
+ * parser and a query parameter was not. That cost is now paid — #216 shipped a
207
+ * parser that reads the fifth segment — and what is left is the Core's
208
+ * argument, which was never about cost: a listing and a read of the same
209
+ * folder answer with completely different things, one a manifest and one a
210
+ * tar. A query parameter that a proxy, a redirect or a hand-edited URL can
211
+ * drop turns "list this folder" into "download this folder", which for a
212
+ * `node_modules` is a mistake measured in gigabytes rather than in a 400. A
213
+ * path segment cannot be dropped silently. See issue 218.
214
+ *
215
+ * Still `protected`: a caller pinned to an older Core can subclass rather than
216
+ * wait for a release.
217
+ */
218
+ listUrl(opts) {
219
+ const url = this.routeUrl("files/list");
220
+ url.searchParams.set("path", opts.path ?? "");
221
+ if (opts.depth !== undefined)
222
+ url.searchParams.set("depth", String(opts.depth));
223
+ // Off unless asked for, which is the Core's default too: a digest means
224
+ // reading every byte under the path, and a listing never has them in hand.
225
+ if (opts.sha256)
226
+ url.searchParams.set("sha256", "1");
227
+ return url.toString();
228
+ }
229
+ /** `…/v1/projects/:projectId/files?path=<relative>` — the read and write route. */
230
+ fileUrl(filePath) {
231
+ const url = this.routeUrl("files");
232
+ url.searchParams.set("path", filePath);
233
+ return url.toString();
234
+ }
235
+ /**
236
+ * `<baseUrl>/v1/projects/:projectId/<leaf>`, with the Project id escaped.
237
+ *
238
+ * The two routes above differ by their leaf and by nothing else, and that is
239
+ * worth having in one place: the day this surface gains a third, the origin,
240
+ * the version prefix and the escaping should not be a third opportunity to get
241
+ * one of them subtly wrong.
242
+ */
243
+ routeUrl(leaf) {
244
+ const base = this.opts.baseUrl.replace(/\/+$/, "");
245
+ return new URL(`${base}/v1/projects/${encodeURIComponent(this.opts.projectId)}/${leaf}`);
246
+ }
247
+ authHeaders() {
248
+ // mTLS is not the gate on its own and is not treated as if it were: the
249
+ // client certificate says a Panel talked to this Core once, the bearer says
250
+ // the pairing is still current. A loopback rig configures no bearer, and
251
+ // the Core with no `authVerifier` asks for none.
252
+ return this.opts.bearer ? { authorization: `Bearer ${this.opts.bearer}` } : {};
253
+ }
254
+ /**
255
+ * The F9 gate, checked before every call and *before any byte leaves*.
256
+ *
257
+ * The reason is carried into the error rather than flattened to a boolean,
258
+ * because the two ways this fails want different actions from an operator: a
259
+ * Core that has not finished connecting is a wait, and a Core that announced
260
+ * no `files` capability is a Core that predates the surface — which is a
261
+ * supported state, not a fault and not a needs-update.
262
+ */
263
+ requireAvailable(what) {
264
+ const availability = this.opts.availability();
265
+ if (availability.available)
266
+ return;
267
+ throw new CoreFilesUnavailableError(`${what} is unavailable on this Core: ${availability.reason}`);
268
+ }
269
+ }
270
+ /**
271
+ * NDJSON, one parsed line at a time, **pulled**.
272
+ *
273
+ * The generator reads from the socket only when its buffer holds no complete
274
+ * line *and* the consumer has asked for another value. Between those two
275
+ * conditions sits the whole backpressure story: suspended at a `yield`, this
276
+ * function is not reading, so the response body's queue fills, so the TCP window
277
+ * closes, so the Core's `res.write` returns false and its handler parks in
278
+ * `drained`. A background pump feeding an array — the obvious alternative —
279
+ * would break that chain at the first link and read the entire stream into
280
+ * memory while the consumer worked through line one.
281
+ */
282
+ async function* ndjsonLines(body) {
283
+ const reader = body.getReader();
284
+ const decoder = new TextDecoder();
285
+ let buffered = "";
286
+ try {
287
+ for (;;) {
288
+ const newline = buffered.indexOf("\n");
289
+ if (newline >= 0) {
290
+ const line = buffered.slice(0, newline).trim();
291
+ buffered = buffered.slice(newline + 1);
292
+ // Yield before looking for the next line, and before reading: this is
293
+ // the suspension point that makes the consumer set the pace.
294
+ if (line.length > 0)
295
+ yield parseLine(line);
296
+ continue;
297
+ }
298
+ const chunk = await reader.read();
299
+ if (chunk.done)
300
+ break;
301
+ buffered += decoder.decode(chunk.value, { stream: true });
302
+ }
303
+ // A final line with no trailing newline is still a line. The Core always
304
+ // sends one, but a stream that ended mid-write may not have.
305
+ const rest = (buffered + decoder.decode()).trim();
306
+ if (rest.length > 0)
307
+ yield parseLine(rest);
308
+ }
309
+ finally {
310
+ // Runs on an early `break` out of the caller's `for await` too, which is
311
+ // what tells the Core the reader walked away — without it an abandoned
312
+ // upload would leave the Core's handler parked in `drained` until its
313
+ // socket timed out, still holding the Project's write lease.
314
+ await reader.cancel().catch(() => { });
315
+ }
316
+ }
317
+ /**
318
+ * One NDJSON line, parsed **into this module's error taxonomy**.
319
+ *
320
+ * The comment above the last-line case anticipates exactly this shape: a stream
321
+ * that ended mid-write leaves a fragment of JSON behind, and `JSON.parse` on it
322
+ * throws a bare `SyntaxError`. That escapes {@link CoreFilesError} entirely, so
323
+ * a caller who had correctly written one `catch (e) { e instanceof
324
+ * CoreFilesError }` around the surface would see the one failure the module
325
+ * itself predicted come out as an unrelated language error, with no `code` and
326
+ * a message about a character position. Wrapping it here is what makes that
327
+ * single `catch` sufficient, which is the whole point of having a taxonomy.
328
+ *
329
+ * `read-failed` rather than `write-failed` because the failure is in reading
330
+ * this stream — whatever the Core was doing when it stopped, what is known here
331
+ * is that the progress line did not arrive whole.
332
+ */
333
+ function parseLine(line) {
334
+ try {
335
+ return JSON.parse(line);
336
+ }
337
+ catch {
338
+ throw new CoreFilesStreamError("read-failed", `the Core's progress stream ended mid-line — ${truncate(line)} is not a complete NDJSON record, ` +
339
+ "which is what a transfer that died part-way through leaves behind");
340
+ }
341
+ }
342
+ /** Enough of a bad line to recognise it, never a megabyte of it in an error message. */
343
+ function truncate(line) {
344
+ return line.length <= 120 ? JSON.stringify(line) : `${JSON.stringify(line.slice(0, 120))}…`;
345
+ }
346
+ /**
347
+ * A caller's source as a `ReadableStream`, **one chunk per pull**.
348
+ *
349
+ * The upload side of the same property the reader above keeps. `pull` is called
350
+ * by the stream only when the consumer — here, `fetch` writing to the socket —
351
+ * has room, so a slow network reads slowly from the caller's file rather than
352
+ * racing a gigabyte off the disk into a queue.
353
+ */
354
+ function sourceStream(source) {
355
+ if (isReadableStream(source))
356
+ return source;
357
+ const iterator = source[Symbol.asyncIterator]();
358
+ const encoder = new TextEncoder();
359
+ return new ReadableStream({
360
+ async pull(controller) {
361
+ const next = await iterator.next();
362
+ if (next.done) {
363
+ controller.close();
364
+ return;
365
+ }
366
+ const value = next.value;
367
+ controller.enqueue(typeof value === "string" ? encoder.encode(value) : value);
368
+ },
369
+ async cancel(reason) {
370
+ // An aborted upload has to reach the caller's stream, or a file handle
371
+ // stays open for the life of the process.
372
+ await iterator.return?.(reason);
373
+ },
374
+ });
375
+ }
376
+ function isReadableStream(source) {
377
+ return typeof source.getReader === "function";
378
+ }
379
+ /**
380
+ * A manifest entry off the wire, or null when the line is not one.
381
+ *
382
+ * `path` is the only field worth refusing a line over — an entry with no path
383
+ * names nothing and is unusable. The rest default, because a listing that
384
+ * computes `sha256` only on request — which is what the Core settled on — or
385
+ * that gives a directory no meaningful size is still a listing.
386
+ */
387
+ function readEntry(record) {
388
+ if (typeof record.path !== "string")
389
+ return null;
390
+ const kind = record.kind;
391
+ return {
392
+ path: record.path,
393
+ size: numberOr(record.size, 0),
394
+ mtime: numberOr(record.mtime, 0),
395
+ mode: numberOr(record.mode, 0),
396
+ sha256: typeof record.sha256 === "string" ? record.sha256 : null,
397
+ ...(kind === "file" || kind === "directory" || kind === "symlink" ? { kind } : {}),
398
+ };
399
+ }
400
+ function numberOr(value, fallback) {
401
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
402
+ }
403
+ function headerNumber(res, name) {
404
+ const raw = res.headers.get(name);
405
+ if (raw === null)
406
+ return null;
407
+ const value = Number(raw);
408
+ return Number.isFinite(value) ? value : null;
409
+ }
410
+ //# sourceMappingURL=core-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core-files.js","sourceRoot":"","sources":["../src/core-files.ts"],"names":[],"mappings":"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"}