@phpsandbox/sdk 0.6.0-canary.2 → 0.6.0-canary.4

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
@@ -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
@@ -378,6 +404,9 @@ await notebook.mail.disable();
378
404
  ### Feedback
379
405
 
380
406
  ```ts
407
+ // Feedback uses the same API token as the rest of the SDK.
408
+ // You can also address it directly with client.feedback.forProject(projectId)
409
+ // or client.feedback.forNotebook(notebookId), without fetching a notebook.
381
410
  await notebook.feedback.configure({
382
411
  widget: {
383
412
  targets: ['preview', 'publication'],
@@ -16372,17 +16372,14 @@ var Filesystem = class {
16372
16372
  exists(path) {
16373
16373
  return this.stat(path).then(() => true).catch(() => false);
16374
16374
  }
16375
- async download(options = {}) {
16376
- const id = nanoid3();
16377
- const stream = !!options.onChunk;
16378
- const chunks = [];
16379
- const disposable = this.okra.listen(
16380
- `fs.download.${id}`,
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 });
@@ -20343,6 +20340,7 @@ var restActionRoutes = {
20343
20340
  "shell.exec": { method: "POST", path: "/runtime/executions" },
20344
20341
  "fs.readFile": { method: "GET", path: "/runtime/files/content" },
20345
20342
  "fs.writeFile": { method: "PUT", path: "/runtime/files/content" },
20343
+ "fs.download": { method: "POST", path: "/runtime/files/download" },
20346
20344
  "fs.createDirectory": { method: "PUT", path: "/runtime/directories" },
20347
20345
  "fs.readDirectory": { method: "POST", path: "/runtime/directories/read" },
20348
20346
  "fs.stat": { method: "POST", path: "/runtime/files/stat" },
@@ -20379,6 +20377,7 @@ var RestRuntimeInvoker = class {
20379
20377
  this.fetch = fetch;
20380
20378
  this.refresh = refresh;
20381
20379
  this.refreshPromise = null;
20380
+ this.streams = /* @__PURE__ */ new Set();
20382
20381
  this.notebookId = data.id;
20383
20382
  this.configure(data.runtimeUrl);
20384
20383
  }
@@ -20411,6 +20410,127 @@ var RestRuntimeInvoker = class {
20411
20410
  send() {
20412
20411
  throw new PHPSandboxError("Fire-and-forget actions require the realtime transport.");
20413
20412
  }
20413
+ dispose() {
20414
+ for (const stream of this.streams) {
20415
+ stream.abort(new DOMException("Notebook disposed", "AbortError"));
20416
+ }
20417
+ }
20418
+ stream(action, data, options = {}) {
20419
+ const cancellation = new AbortController();
20420
+ this.streams.add(cancellation);
20421
+ const abort = runtimeAbort({ ...options, abortSignal: cancellation.signal });
20422
+ const abortFromCaller = () => cancellation.abort(options.abortSignal?.reason);
20423
+ let reader;
20424
+ let closed = false;
20425
+ let onAbort = () => {
20426
+ };
20427
+ const dispose = () => {
20428
+ this.streams.delete(cancellation);
20429
+ options.abortSignal?.removeEventListener("abort", abortFromCaller);
20430
+ abort.signal.removeEventListener("abort", onAbort);
20431
+ abort.dispose();
20432
+ };
20433
+ const cancelReader = async (reason) => {
20434
+ const activeReader = reader;
20435
+ reader = void 0;
20436
+ if (activeReader !== void 0) {
20437
+ await activeReader.cancel(reason).catch(() => void 0);
20438
+ activeReader.releaseLock();
20439
+ }
20440
+ };
20441
+ return new ReadableStream(
20442
+ {
20443
+ start: (controller) => {
20444
+ onAbort = () => {
20445
+ if (closed) {
20446
+ return;
20447
+ }
20448
+ closed = true;
20449
+ dispose();
20450
+ controller.error(abort.signal.reason);
20451
+ void cancelReader(abort.signal.reason);
20452
+ };
20453
+ abort.signal.addEventListener("abort", onAbort, { once: true });
20454
+ options.abortSignal?.addEventListener("abort", abortFromCaller, { once: true });
20455
+ if (options.abortSignal?.aborted) {
20456
+ abortFromCaller();
20457
+ }
20458
+ },
20459
+ pull: async (controller) => {
20460
+ try {
20461
+ if (reader === void 0) {
20462
+ const response = await this.streamResponse(action, data, abort.signal);
20463
+ if (closed) {
20464
+ await response.body?.cancel();
20465
+ return;
20466
+ }
20467
+ if (response.body === null) {
20468
+ throw new TransportError("PHPSandbox runtime returned an empty download.", "InvalidResponse", response);
20469
+ }
20470
+ reader = response.body.getReader();
20471
+ }
20472
+ const result = await reader.read();
20473
+ if (closed) {
20474
+ return;
20475
+ }
20476
+ if (result.done) {
20477
+ closed = true;
20478
+ dispose();
20479
+ reader.releaseLock();
20480
+ reader = void 0;
20481
+ controller.close();
20482
+ } else {
20483
+ controller.enqueue(result.value);
20484
+ }
20485
+ } catch (error51) {
20486
+ if (!closed) {
20487
+ closed = true;
20488
+ cancellation.abort(error51);
20489
+ dispose();
20490
+ await cancelReader(error51);
20491
+ controller.error(error51);
20492
+ }
20493
+ }
20494
+ },
20495
+ cancel: async (reason) => {
20496
+ closed = true;
20497
+ cancellation.abort(reason);
20498
+ dispose();
20499
+ await cancelReader(reason);
20500
+ }
20501
+ },
20502
+ { highWaterMark: 0 }
20503
+ );
20504
+ }
20505
+ async streamResponse(action, data, signal) {
20506
+ for (let attempt = 0; attempt < 2; attempt += 1) {
20507
+ signal.throwIfAborted();
20508
+ const response = await runtimeFetch(this.fetch, this.request(action, data, signal));
20509
+ if (signal.aborted) {
20510
+ await response.body?.cancel();
20511
+ signal.throwIfAborted();
20512
+ }
20513
+ if (response.status === 401 && attempt === 0 && this.refresh !== void 0) {
20514
+ await response.body?.cancel();
20515
+ await this.refreshAuthorization();
20516
+ continue;
20517
+ }
20518
+ if (!response.ok) {
20519
+ const payload = await runtimeActionResponse(response);
20520
+ throw remoteError("runtime", payload.error, response);
20521
+ }
20522
+ if (!response.headers.get("content-type")?.toLowerCase().startsWith("application/zip")) {
20523
+ await response.body?.cancel();
20524
+ throw new TransportError("PHPSandbox runtime returned an invalid download.", "InvalidResponse", response);
20525
+ }
20526
+ return response;
20527
+ }
20528
+ throw new RemoteError("Notebook download could not be authorized.", {
20529
+ source: "runtime",
20530
+ status: 401,
20531
+ code: "AuthenticationRequired"
20532
+ });
20533
+ }
20414
20534
  async invokeRaw(action, data, options) {
20415
20535
  const abort = runtimeAbort(options);
20416
20536
  try {
@@ -20443,7 +20563,7 @@ var RestRuntimeInvoker = class {
20443
20563
  const url2 = new URL(this.actionUrl);
20444
20564
  url2.pathname += route.path;
20445
20565
  const headers = {
20446
- Accept: "application/json, application/octet-stream",
20566
+ Accept: action === "fs.download" ? "application/zip" : "application/json, application/octet-stream",
20447
20567
  Authorization: `Bearer ${this.ticket}`
20448
20568
  };
20449
20569
  const body = runtimeActionRequestBody(action, data, url2, headers);
@@ -20560,9 +20680,9 @@ function runtimeAbort(options) {
20560
20680
 
20561
20681
  // src/feedback.ts
20562
20682
  var Feedback = class {
20563
- constructor(client, notebookId) {
20683
+ constructor(client, identifier) {
20564
20684
  this.client = client;
20565
- this.notebookId = notebookId;
20685
+ this.identifier = identifier;
20566
20686
  }
20567
20687
  async configure(input) {
20568
20688
  const response = await this.client.put(this.path("/configuration"), input);
@@ -20572,8 +20692,8 @@ var Feedback = class {
20572
20692
  const response = await this.client.get(this.path("/configuration"));
20573
20693
  return response.data;
20574
20694
  }
20575
- async submit(input) {
20576
- const response = await this.client.post(this.path(), input);
20695
+ async submit(input, options = {}) {
20696
+ const response = await this.client.post(this.path(), input, options);
20577
20697
  return response.data;
20578
20698
  }
20579
20699
  async list(options = {}) {
@@ -20594,7 +20714,18 @@ var Feedback = class {
20594
20714
  return response.data;
20595
20715
  }
20596
20716
  path(suffix = "") {
20597
- return `/notebook/${this.notebookId}/feedback${suffix}`;
20717
+ return `/platform/projects/${encodeURIComponent(this.identifier)}/feedback${suffix}`;
20718
+ }
20719
+ };
20720
+ var FeedbackApi = class {
20721
+ constructor(client) {
20722
+ this.client = client;
20723
+ }
20724
+ forProject(projectId) {
20725
+ return new Feedback(this.client, projectId);
20726
+ }
20727
+ forNotebook(notebookId) {
20728
+ return new Feedback(this.client, notebookId);
20598
20729
  }
20599
20730
  };
20600
20731
 
@@ -20755,6 +20886,7 @@ var ClientImplementation = class {
20755
20886
  this.integrations = new IntegrationApi(this);
20756
20887
  this.payments = new PaymentsApi(this);
20757
20888
  this.customers = new CustomersApi(this);
20889
+ this.feedback = new FeedbackApi(this);
20758
20890
  this.options = { ...options };
20759
20891
  this.runtimeTransportValue = runtimeTransport;
20760
20892
  this.baseUrl = normalizeApiBaseUrl(url2);
@@ -20773,8 +20905,13 @@ var ClientImplementation = class {
20773
20905
  get(path) {
20774
20906
  return this.requireData(this.makeRequest("GET", path));
20775
20907
  }
20776
- post(path, body) {
20777
- return this.requireData(this.makeRequest("POST", path, { body: body === void 0 ? void 0 : JSON.stringify(body) }));
20908
+ post(path, body, options = {}) {
20909
+ return this.requireData(
20910
+ this.makeRequest("POST", path, {
20911
+ body: body === void 0 ? void 0 : JSON.stringify(body),
20912
+ headers: options.idempotencyKey === void 0 ? void 0 : { "Idempotency-Key": options.idempotencyKey }
20913
+ })
20914
+ );
20778
20915
  }
20779
20916
  delete(path) {
20780
20917
  return this.makeRequest("DELETE", path);
@@ -20827,11 +20964,15 @@ var ClientImplementation = class {
20827
20964
  return { ...response, data: response.data };
20828
20965
  }
20829
20966
  async makeRequest(method, path, init) {
20967
+ const headers = new Headers(init?.headers);
20968
+ for (const [name, value] of Object.entries(this.headers)) {
20969
+ headers.set(name, value);
20970
+ }
20830
20971
  const response = await this.fetchResponse(
20831
20972
  authenticatedRequest(new URL(path.replace(/^\//, ""), this.baseUrl), {
20832
20973
  method,
20833
20974
  ...init,
20834
- headers: this.headers
20975
+ headers
20835
20976
  })
20836
20977
  );
20837
20978
  if (!response.ok) {
@@ -20897,6 +21038,7 @@ var PHPSandbox = class _PHPSandbox {
20897
21038
  this.integrations = client.integrations;
20898
21039
  this.payments = client.payments;
20899
21040
  this.customers = client.customers;
21041
+ this.feedback = client.feedback;
20900
21042
  }
20901
21043
  async whoami() {
20902
21044
  const { data } = await this.client.get("/auth/whoami");
@@ -20926,6 +21068,7 @@ var _NotebookInstance = class _NotebookInstance {
20926
21068
  this.data = data;
20927
21069
  this.client = client;
20928
21070
  __privateAdd(this, _NotebookInstance_instances);
21071
+ this.downloadInvoker = null;
20929
21072
  this.initialized = false;
20930
21073
  __privateAdd(this, _initPromise, null);
20931
21074
  this.disposables = [];
@@ -21047,12 +21190,23 @@ var _NotebookInstance = class _NotebookInstance {
21047
21190
  }
21048
21191
  return this.realtimeSocket().send(action, data || {});
21049
21192
  }
21193
+ /** Binary downloads use HTTP independently of the selected runtime transport. */
21194
+ stream(action, data = {}, options = {}) {
21195
+ const runtimeUrlProvider = this.client.options.runtimeUrlProvider;
21196
+ this.downloadInvoker ?? (this.downloadInvoker = this.restInvoker ?? new RestRuntimeInvoker(
21197
+ this.data,
21198
+ this.client.options.fetch ?? globalThis.fetch,
21199
+ runtimeUrlProvider === void 0 ? void 0 : () => runtimeUrlProvider(this.data.id)
21200
+ ));
21201
+ return this.downloadInvoker.stream(action, data, options);
21202
+ }
21050
21203
  listen(event, handler) {
21051
21204
  const disposable = this.emitter.listen(event, handler);
21052
21205
  this.disposables.push(disposable);
21053
21206
  return disposable;
21054
21207
  }
21055
21208
  dispose() {
21209
+ this.downloadInvoker?.dispose();
21056
21210
  this.disposables.forEach((d) => d.dispose());
21057
21211
  if (this.socket !== null && !this.socket.isClosed) {
21058
21212
  this.socket.disconnect();
@@ -21309,6 +21463,8 @@ export {
21309
21463
  CustomerRuntime,
21310
21464
  CustomersApi,
21311
21465
  DEFAULT_PAYMENT_CALLBACK_PATH,
21466
+ Feedback,
21467
+ FeedbackApi,
21312
21468
  FileChangeFilter,
21313
21469
  FileChangeType,
21314
21470
  FileType,