@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.
- package/README.md +54 -0
- package/dist/core-client.d.ts +99 -1
- package/dist/core-client.d.ts.map +1 -1
- package/dist/core-client.js +138 -2
- package/dist/core-client.js.map +1 -1
- package/dist/core-files-error-codes.d.ts +23 -0
- package/dist/core-files-error-codes.d.ts.map +1 -0
- package/dist/core-files-error-codes.js +123 -0
- package/dist/core-files-error-codes.js.map +1 -0
- package/dist/core-files-http.d.ts +119 -0
- package/dist/core-files-http.d.ts.map +1 -0
- package/dist/core-files-http.js +223 -0
- package/dist/core-files-http.js.map +1 -0
- package/dist/core-files.d.ts +227 -0
- package/dist/core-files.d.ts.map +1 -0
- package/dist/core-files.js +410 -0
- package/dist/core-files.js.map +1 -0
- package/dist/core-link-frames.d.ts +59 -0
- package/dist/core-link-frames.d.ts.map +1 -1
- package/dist/core-link-frames.js +35 -0
- package/dist/core-link-frames.js.map +1 -1
- package/dist/core-link-transport.d.ts.map +1 -1
- package/dist/core-link-transport.js +6 -0
- package/dist/core-link-transport.js.map +1 -1
- package/dist/core-project.d.ts +19 -0
- package/dist/core-project.d.ts.map +1 -0
- package/dist/core-project.js +38 -0
- package/dist/core-project.js.map +1 -0
- package/dist/core-registration-blob.d.ts +11 -0
- package/dist/core-registration-blob.d.ts.map +1 -1
- package/dist/core-registration-blob.js +5 -1
- package/dist/core-registration-blob.js.map +1 -1
- package/package.json +2 -1
|
@@ -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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"}
|