@phpsandbox/sdk 0.6.0-canary.3 → 0.6.0-canary.5
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 +26 -0
- package/dist/browser/phpsandbox-sdk.esm.js +148 -12
- package/dist/browser/phpsandbox-sdk.esm.min.js +8 -8
- package/dist/browser/phpsandbox-sdk.iife.js +148 -12
- package/dist/browser/phpsandbox-sdk.iife.min.js +8 -8
- package/dist/filesystem.d.ts +2 -2
- package/dist/filesystem.js +4 -13
- package/dist/index.d.ts +7 -1
- package/dist/index.js +9 -0
- package/dist/runtime/rest.d.ts +7 -0
- package/dist/runtime/rest.js +122 -1
- package/dist/servers.d.ts +28 -1
- package/dist/servers.js +6 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -209,6 +209,32 @@ const [hasMore, matches] = await notebook.files.search(
|
|
|
209
209
|
);
|
|
210
210
|
```
|
|
211
211
|
|
|
212
|
+
`files.download()` returns a `ReadableStream<Uint8Array>` containing the notebook ZIP.
|
|
213
|
+
Read it incrementally or pipe it to a writable stream:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
const controller = new AbortController();
|
|
217
|
+
const archive = notebook.files.download({
|
|
218
|
+
include: ['src/**'],
|
|
219
|
+
exclude: ['vendor/**'],
|
|
220
|
+
signal: controller.signal,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
for await (const chunk of archive) {
|
|
224
|
+
await consume(chunk);
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Downloads use HTTP for both realtime and REST clients and require the runtime's
|
|
229
|
+
`POST /api/v1/notebooks/{notebook}/runtime/files/download` endpoint. Reading starts
|
|
230
|
+
the request; cancelling the stream, aborting its signal, or disposing the notebook
|
|
231
|
+
stops the transfer. Failures reject stream reads and are not retried after bytes
|
|
232
|
+
have started arriving.
|
|
233
|
+
|
|
234
|
+
**Breaking change in 0.6.0-canary.4:** downloads no longer return a `Blob` or accept
|
|
235
|
+
`onChunk`. If you need a Blob, explicitly collect the archive with
|
|
236
|
+
`await new Response(notebook.files.download()).blob()`; this buffers the ZIP in memory.
|
|
237
|
+
|
|
212
238
|
### Terminal
|
|
213
239
|
|
|
214
240
|
```ts
|
|
@@ -16372,17 +16372,14 @@ var Filesystem = class {
|
|
|
16372
16372
|
exists(path) {
|
|
16373
16373
|
return this.stat(path).then(() => true).catch(() => false);
|
|
16374
16374
|
}
|
|
16375
|
-
|
|
16376
|
-
|
|
16377
|
-
|
|
16378
|
-
|
|
16379
|
-
|
|
16380
|
-
|
|
16381
|
-
stream ? options.onChunk : (data) => {
|
|
16382
|
-
chunks.push(data);
|
|
16375
|
+
download(options = {}) {
|
|
16376
|
+
return this.okra.stream(
|
|
16377
|
+
"fs.download",
|
|
16378
|
+
{ include: options.include, exclude: options.exclude },
|
|
16379
|
+
{
|
|
16380
|
+
abortSignal: options.signal
|
|
16383
16381
|
}
|
|
16384
16382
|
);
|
|
16385
|
-
return this.okra.invoke("fs.download", { id, exclude: options.exclude, include: options.include }).then(() => new Blob(chunks, { type: "application/octet-stream" })).finally(() => disposable.dispose());
|
|
16386
16383
|
}
|
|
16387
16384
|
tree(path = "/") {
|
|
16388
16385
|
return this.okra.invoke("fs.tree", { path });
|
|
@@ -20254,8 +20251,12 @@ var ServerInstance = class {
|
|
|
20254
20251
|
this.data = data;
|
|
20255
20252
|
this.client = client;
|
|
20256
20253
|
}
|
|
20257
|
-
async
|
|
20258
|
-
return (await this.client.
|
|
20254
|
+
async updateAgent(input) {
|
|
20255
|
+
return (await this.client.post(`/servers/${encodeURIComponent(this.data.id)}/agent/updates`, input)).data;
|
|
20256
|
+
}
|
|
20257
|
+
async installation(options = {}) {
|
|
20258
|
+
const query = options.managedUpdates ? "?managedUpdates=1" : "";
|
|
20259
|
+
return (await this.client.get(`/servers/${encodeURIComponent(this.data.id)}/installation${query}`)).data;
|
|
20259
20260
|
}
|
|
20260
20261
|
async members(options = {}) {
|
|
20261
20262
|
const query = options.page === void 0 ? "" : `?${new URLSearchParams({ page: String(options.page) }).toString()}`;
|
|
@@ -20343,6 +20344,7 @@ var restActionRoutes = {
|
|
|
20343
20344
|
"shell.exec": { method: "POST", path: "/runtime/executions" },
|
|
20344
20345
|
"fs.readFile": { method: "GET", path: "/runtime/files/content" },
|
|
20345
20346
|
"fs.writeFile": { method: "PUT", path: "/runtime/files/content" },
|
|
20347
|
+
"fs.download": { method: "POST", path: "/runtime/files/download" },
|
|
20346
20348
|
"fs.createDirectory": { method: "PUT", path: "/runtime/directories" },
|
|
20347
20349
|
"fs.readDirectory": { method: "POST", path: "/runtime/directories/read" },
|
|
20348
20350
|
"fs.stat": { method: "POST", path: "/runtime/files/stat" },
|
|
@@ -20379,6 +20381,7 @@ var RestRuntimeInvoker = class {
|
|
|
20379
20381
|
this.fetch = fetch;
|
|
20380
20382
|
this.refresh = refresh;
|
|
20381
20383
|
this.refreshPromise = null;
|
|
20384
|
+
this.streams = /* @__PURE__ */ new Set();
|
|
20382
20385
|
this.notebookId = data.id;
|
|
20383
20386
|
this.configure(data.runtimeUrl);
|
|
20384
20387
|
}
|
|
@@ -20411,6 +20414,127 @@ var RestRuntimeInvoker = class {
|
|
|
20411
20414
|
send() {
|
|
20412
20415
|
throw new PHPSandboxError("Fire-and-forget actions require the realtime transport.");
|
|
20413
20416
|
}
|
|
20417
|
+
dispose() {
|
|
20418
|
+
for (const stream of this.streams) {
|
|
20419
|
+
stream.abort(new DOMException("Notebook disposed", "AbortError"));
|
|
20420
|
+
}
|
|
20421
|
+
}
|
|
20422
|
+
stream(action, data, options = {}) {
|
|
20423
|
+
const cancellation = new AbortController();
|
|
20424
|
+
this.streams.add(cancellation);
|
|
20425
|
+
const abort = runtimeAbort({ ...options, abortSignal: cancellation.signal });
|
|
20426
|
+
const abortFromCaller = () => cancellation.abort(options.abortSignal?.reason);
|
|
20427
|
+
let reader;
|
|
20428
|
+
let closed = false;
|
|
20429
|
+
let onAbort = () => {
|
|
20430
|
+
};
|
|
20431
|
+
const dispose = () => {
|
|
20432
|
+
this.streams.delete(cancellation);
|
|
20433
|
+
options.abortSignal?.removeEventListener("abort", abortFromCaller);
|
|
20434
|
+
abort.signal.removeEventListener("abort", onAbort);
|
|
20435
|
+
abort.dispose();
|
|
20436
|
+
};
|
|
20437
|
+
const cancelReader = async (reason) => {
|
|
20438
|
+
const activeReader = reader;
|
|
20439
|
+
reader = void 0;
|
|
20440
|
+
if (activeReader !== void 0) {
|
|
20441
|
+
await activeReader.cancel(reason).catch(() => void 0);
|
|
20442
|
+
activeReader.releaseLock();
|
|
20443
|
+
}
|
|
20444
|
+
};
|
|
20445
|
+
return new ReadableStream(
|
|
20446
|
+
{
|
|
20447
|
+
start: (controller) => {
|
|
20448
|
+
onAbort = () => {
|
|
20449
|
+
if (closed) {
|
|
20450
|
+
return;
|
|
20451
|
+
}
|
|
20452
|
+
closed = true;
|
|
20453
|
+
dispose();
|
|
20454
|
+
controller.error(abort.signal.reason);
|
|
20455
|
+
void cancelReader(abort.signal.reason);
|
|
20456
|
+
};
|
|
20457
|
+
abort.signal.addEventListener("abort", onAbort, { once: true });
|
|
20458
|
+
options.abortSignal?.addEventListener("abort", abortFromCaller, { once: true });
|
|
20459
|
+
if (options.abortSignal?.aborted) {
|
|
20460
|
+
abortFromCaller();
|
|
20461
|
+
}
|
|
20462
|
+
},
|
|
20463
|
+
pull: async (controller) => {
|
|
20464
|
+
try {
|
|
20465
|
+
if (reader === void 0) {
|
|
20466
|
+
const response = await this.streamResponse(action, data, abort.signal);
|
|
20467
|
+
if (closed) {
|
|
20468
|
+
await response.body?.cancel();
|
|
20469
|
+
return;
|
|
20470
|
+
}
|
|
20471
|
+
if (response.body === null) {
|
|
20472
|
+
throw new TransportError("PHPSandbox runtime returned an empty download.", "InvalidResponse", response);
|
|
20473
|
+
}
|
|
20474
|
+
reader = response.body.getReader();
|
|
20475
|
+
}
|
|
20476
|
+
const result = await reader.read();
|
|
20477
|
+
if (closed) {
|
|
20478
|
+
return;
|
|
20479
|
+
}
|
|
20480
|
+
if (result.done) {
|
|
20481
|
+
closed = true;
|
|
20482
|
+
dispose();
|
|
20483
|
+
reader.releaseLock();
|
|
20484
|
+
reader = void 0;
|
|
20485
|
+
controller.close();
|
|
20486
|
+
} else {
|
|
20487
|
+
controller.enqueue(result.value);
|
|
20488
|
+
}
|
|
20489
|
+
} catch (error51) {
|
|
20490
|
+
if (!closed) {
|
|
20491
|
+
closed = true;
|
|
20492
|
+
cancellation.abort(error51);
|
|
20493
|
+
dispose();
|
|
20494
|
+
await cancelReader(error51);
|
|
20495
|
+
controller.error(error51);
|
|
20496
|
+
}
|
|
20497
|
+
}
|
|
20498
|
+
},
|
|
20499
|
+
cancel: async (reason) => {
|
|
20500
|
+
closed = true;
|
|
20501
|
+
cancellation.abort(reason);
|
|
20502
|
+
dispose();
|
|
20503
|
+
await cancelReader(reason);
|
|
20504
|
+
}
|
|
20505
|
+
},
|
|
20506
|
+
{ highWaterMark: 0 }
|
|
20507
|
+
);
|
|
20508
|
+
}
|
|
20509
|
+
async streamResponse(action, data, signal) {
|
|
20510
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
20511
|
+
signal.throwIfAborted();
|
|
20512
|
+
const response = await runtimeFetch(this.fetch, this.request(action, data, signal));
|
|
20513
|
+
if (signal.aborted) {
|
|
20514
|
+
await response.body?.cancel();
|
|
20515
|
+
signal.throwIfAborted();
|
|
20516
|
+
}
|
|
20517
|
+
if (response.status === 401 && attempt === 0 && this.refresh !== void 0) {
|
|
20518
|
+
await response.body?.cancel();
|
|
20519
|
+
await this.refreshAuthorization();
|
|
20520
|
+
continue;
|
|
20521
|
+
}
|
|
20522
|
+
if (!response.ok) {
|
|
20523
|
+
const payload = await runtimeActionResponse(response);
|
|
20524
|
+
throw remoteError("runtime", payload.error, response);
|
|
20525
|
+
}
|
|
20526
|
+
if (!response.headers.get("content-type")?.toLowerCase().startsWith("application/zip")) {
|
|
20527
|
+
await response.body?.cancel();
|
|
20528
|
+
throw new TransportError("PHPSandbox runtime returned an invalid download.", "InvalidResponse", response);
|
|
20529
|
+
}
|
|
20530
|
+
return response;
|
|
20531
|
+
}
|
|
20532
|
+
throw new RemoteError("Notebook download could not be authorized.", {
|
|
20533
|
+
source: "runtime",
|
|
20534
|
+
status: 401,
|
|
20535
|
+
code: "AuthenticationRequired"
|
|
20536
|
+
});
|
|
20537
|
+
}
|
|
20414
20538
|
async invokeRaw(action, data, options) {
|
|
20415
20539
|
const abort = runtimeAbort(options);
|
|
20416
20540
|
try {
|
|
@@ -20443,7 +20567,7 @@ var RestRuntimeInvoker = class {
|
|
|
20443
20567
|
const url2 = new URL(this.actionUrl);
|
|
20444
20568
|
url2.pathname += route.path;
|
|
20445
20569
|
const headers = {
|
|
20446
|
-
Accept: "application/json, application/octet-stream",
|
|
20570
|
+
Accept: action === "fs.download" ? "application/zip" : "application/json, application/octet-stream",
|
|
20447
20571
|
Authorization: `Bearer ${this.ticket}`
|
|
20448
20572
|
};
|
|
20449
20573
|
const body = runtimeActionRequestBody(action, data, url2, headers);
|
|
@@ -20948,6 +21072,7 @@ var _NotebookInstance = class _NotebookInstance {
|
|
|
20948
21072
|
this.data = data;
|
|
20949
21073
|
this.client = client;
|
|
20950
21074
|
__privateAdd(this, _NotebookInstance_instances);
|
|
21075
|
+
this.downloadInvoker = null;
|
|
20951
21076
|
this.initialized = false;
|
|
20952
21077
|
__privateAdd(this, _initPromise, null);
|
|
20953
21078
|
this.disposables = [];
|
|
@@ -21069,12 +21194,23 @@ var _NotebookInstance = class _NotebookInstance {
|
|
|
21069
21194
|
}
|
|
21070
21195
|
return this.realtimeSocket().send(action, data || {});
|
|
21071
21196
|
}
|
|
21197
|
+
/** Binary downloads use HTTP independently of the selected runtime transport. */
|
|
21198
|
+
stream(action, data = {}, options = {}) {
|
|
21199
|
+
const runtimeUrlProvider = this.client.options.runtimeUrlProvider;
|
|
21200
|
+
this.downloadInvoker ?? (this.downloadInvoker = this.restInvoker ?? new RestRuntimeInvoker(
|
|
21201
|
+
this.data,
|
|
21202
|
+
this.client.options.fetch ?? globalThis.fetch,
|
|
21203
|
+
runtimeUrlProvider === void 0 ? void 0 : () => runtimeUrlProvider(this.data.id)
|
|
21204
|
+
));
|
|
21205
|
+
return this.downloadInvoker.stream(action, data, options);
|
|
21206
|
+
}
|
|
21072
21207
|
listen(event, handler) {
|
|
21073
21208
|
const disposable = this.emitter.listen(event, handler);
|
|
21074
21209
|
this.disposables.push(disposable);
|
|
21075
21210
|
return disposable;
|
|
21076
21211
|
}
|
|
21077
21212
|
dispose() {
|
|
21213
|
+
this.downloadInvoker?.dispose();
|
|
21078
21214
|
this.disposables.forEach((d) => d.dispose());
|
|
21079
21215
|
if (this.socket !== null && !this.socket.isClosed) {
|
|
21080
21216
|
this.socket.disconnect();
|