@phpsandbox/sdk 0.0.28 → 0.0.30

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
@@ -101,6 +101,7 @@ Each `NotebookInstance` exposes service clients:
101
101
  - `notebook.laravel` (`Laravel`)
102
102
  - `notebook.auth` (`Auth`)
103
103
  - `notebook.log` (`Log`)
104
+ - `notebook.services` (`Services`)
104
105
 
105
106
  ## Common Operations
106
107
 
@@ -144,6 +145,23 @@ result.throw();
144
145
  console.log(result.output);
145
146
  ```
146
147
 
148
+ ### Services
149
+
150
+ ```ts
151
+ const services = await notebook.services.list();
152
+
153
+ await notebook.services.run('redis');
154
+
155
+ const snapshot = await notebook.services.logs('redis', { tail: 50 });
156
+ console.log(snapshot);
157
+
158
+ const stream = notebook.services.logs('redis', { follow: true, tail: 25 });
159
+ const reader = stream.output.getReader();
160
+ const chunk = await reader.read();
161
+ console.log(chunk.value);
162
+ await stream.stop();
163
+ ```
164
+
147
165
  ### Composer
148
166
 
149
167
  ```ts
@@ -532,8 +532,7 @@ var Filesystem = class {
532
532
  };
533
533
  const subscription = new FilesystemSubscription(path, this.okra, wrappedDisposable);
534
534
  this.watches.set(path, { options, path, onDidChange });
535
- this.okra.invoke("fs.watch", { path, options });
536
- return subscription;
535
+ return this.okra.invoke("fs.watch", { path, options }).then(() => subscription);
537
536
  }
538
537
  exists(path) {
539
538
  return this.stat(path).then(() => true).catch(() => false);
@@ -573,6 +572,13 @@ var SendTimeoutError = class _SendTimeoutError extends PromiseTimeoutError {
573
572
  return new _SendTimeoutError(error.message, error.time);
574
573
  }
575
574
  };
575
+ var NotebookUnavailableError = class extends Error {
576
+ constructor(message = "Notebook is no longer available", raw = {}) {
577
+ super(message);
578
+ this.raw = raw;
579
+ this.name = "NotebookUnavailableError";
580
+ }
581
+ };
576
582
 
577
583
  // src/utils/promise.ts
578
584
  var timeout = (prom, time) => {
@@ -1020,6 +1026,103 @@ var Shell = class {
1020
1026
  }
1021
1027
  };
1022
1028
 
1029
+ // src/services.ts
1030
+ var Services = class {
1031
+ constructor(okra) {
1032
+ this.okra = okra;
1033
+ }
1034
+ list() {
1035
+ return this.okra.invoke("service.list");
1036
+ }
1037
+ run(name) {
1038
+ return this.okra.invoke("service.run", { name });
1039
+ }
1040
+ stop(name) {
1041
+ return this.okra.invoke("service.stop", { name });
1042
+ }
1043
+ onLog(id, handler) {
1044
+ return this.okra.listen(`service.log.${id}`, handler);
1045
+ }
1046
+ listen(event, handler) {
1047
+ return this.okra.listen(event, handler);
1048
+ }
1049
+ logs(name, options = {}) {
1050
+ if (options.follow) {
1051
+ return this.followLogs(name, options);
1052
+ }
1053
+ return this.okra.invoke("service.logs", {
1054
+ name,
1055
+ tail: options.tail
1056
+ }).then((response) => {
1057
+ if (!("output" in response)) {
1058
+ throw new Error(`Unexpected service.logs response for ${name}`);
1059
+ }
1060
+ return response.output;
1061
+ });
1062
+ }
1063
+ followLogs(name, options) {
1064
+ const id = options.id || nanoid();
1065
+ const disposables = /* @__PURE__ */ new Set();
1066
+ let controller = null;
1067
+ let closed = false;
1068
+ const dispose = () => {
1069
+ for (const disposable of disposables) {
1070
+ disposable.dispose();
1071
+ }
1072
+ disposables.clear();
1073
+ };
1074
+ const close = () => {
1075
+ if (closed) {
1076
+ return;
1077
+ }
1078
+ closed = true;
1079
+ dispose();
1080
+ if (controller) {
1081
+ try {
1082
+ controller.close();
1083
+ } catch {
1084
+ }
1085
+ }
1086
+ };
1087
+ const fail = (error) => {
1088
+ if (closed) {
1089
+ return;
1090
+ }
1091
+ closed = true;
1092
+ dispose();
1093
+ if (controller) {
1094
+ controller.error(error);
1095
+ }
1096
+ };
1097
+ const stop = once(() => {
1098
+ close();
1099
+ return this.okra.invoke("service.stop-logs", { id });
1100
+ });
1101
+ const output = new ReadableStream({
1102
+ start: (_controller) => {
1103
+ controller = _controller;
1104
+ disposables.add(
1105
+ this.onLog(id, (data) => {
1106
+ controller?.enqueue(data.output);
1107
+ })
1108
+ );
1109
+ void this.okra.invoke("service.logs", {
1110
+ name,
1111
+ tail: options.tail,
1112
+ follow: true,
1113
+ id
1114
+ }).catch((error) => {
1115
+ fail(error);
1116
+ });
1117
+ },
1118
+ cancel: () => {
1119
+ void stop();
1120
+ }
1121
+ });
1122
+ return { id, output, stop };
1123
+ }
1124
+ };
1125
+
1023
1126
  // node_modules/@msgpack/msgpack/dist.esm/utils/utf8.mjs
1024
1127
  function utf8Count(str) {
1025
1128
  const strLength = str.length;
@@ -3043,6 +3146,7 @@ var Transport = class {
3043
3146
  this.closed = false;
3044
3147
  this.disposables = new NamedDisposable();
3045
3148
  this.connectPromise = null;
3149
+ this.terminalError = null;
3046
3150
  this.pingIntervalStarted = false;
3047
3151
  // Connection health monitoring
3048
3152
  this.connectionStats = {
@@ -3074,7 +3178,7 @@ var Transport = class {
3074
3178
  const startClosed = options.startClosed !== false;
3075
3179
  this.rws = new reconnecting_websocket_mjs_default(this.url.toString(), [], {
3076
3180
  WebSocket: globalThis.WebSocket ?? browser_default,
3077
- connectionTimeout: options.connectionTimeout ?? 1e3,
3181
+ connectionTimeout: options.connectionTimeout,
3078
3182
  maxReconnectionDelay: 2e3,
3079
3183
  minReconnectionDelay: 200,
3080
3184
  maxEnqueuedMessages: 0,
@@ -3117,6 +3221,9 @@ var Transport = class {
3117
3221
  * Ensure the websocket is open without requiring an application-level roundtrip.
3118
3222
  */
3119
3223
  connect() {
3224
+ if (this.terminalError) {
3225
+ return Promise.reject(this.terminalError);
3226
+ }
3120
3227
  return __privateMethod(this, _Transport_instances, connect_fn).call(this);
3121
3228
  }
3122
3229
  id() {
@@ -3173,7 +3280,12 @@ var Transport = class {
3173
3280
  return;
3174
3281
  }
3175
3282
  if (event === "Events.BootError" /* BootError */) {
3283
+ const error = new NotebookUnavailableError(
3284
+ data?.message || "Notebook is no longer available",
3285
+ data || {}
3286
+ );
3176
3287
  this.log("error", "Boot error received", { data });
3288
+ this.terminate(error, 4e3, error.message);
3177
3289
  return;
3178
3290
  }
3179
3291
  if (event === "response" /* Response */) {
@@ -3232,6 +3344,11 @@ var Transport = class {
3232
3344
  listen(event, listener, _context) {
3233
3345
  return this.eventEmitter.listen(event, listener);
3234
3346
  }
3347
+ onDidBootError(listener) {
3348
+ this.eventEmitter.listen("transport.boot_error", (event) => {
3349
+ listener(event.error);
3350
+ });
3351
+ }
3235
3352
  removeListener(event, listener) {
3236
3353
  this.eventEmitter.removeListener(event, listener);
3237
3354
  }
@@ -3258,6 +3375,9 @@ var Transport = class {
3258
3375
  get isClosed() {
3259
3376
  return this.closed;
3260
3377
  }
3378
+ getTerminalError() {
3379
+ return this.terminalError;
3380
+ }
3261
3381
  get status() {
3262
3382
  return {
3263
3383
  0: "CONNECTING",
@@ -3267,6 +3387,9 @@ var Transport = class {
3267
3387
  }[this.rws.readyState];
3268
3388
  }
3269
3389
  async call(action, data = {}, options = {}) {
3390
+ if (this.terminalError) {
3391
+ throw this.terminalError;
3392
+ }
3270
3393
  if (this.isRateLimited()) {
3271
3394
  throw new RateLimitError("Rate limit exceeded - too many requests");
3272
3395
  }
@@ -3283,6 +3406,10 @@ var Transport = class {
3283
3406
  this.eventEmitter.removeListener(errorEvent);
3284
3407
  };
3285
3408
  const handler = async (resolve, reject) => {
3409
+ if (this.terminalError) {
3410
+ reject(this.terminalError);
3411
+ return;
3412
+ }
3286
3413
  const abortError = new DOMException("Request aborted", "AbortError");
3287
3414
  if (options.abortSignal?.aborted) {
3288
3415
  reject(abortError);
@@ -3334,7 +3461,7 @@ var Transport = class {
3334
3461
  reject(new RateLimitError(_ev.reason || "Rate limit exceeded", _ev));
3335
3462
  return;
3336
3463
  }
3337
- reject(new Error(`Connection lost to the notebook during request: ${_ev.reason || "Unknown reason"}`));
3464
+ reject(this.terminalError || new Error(`Connection lost to the notebook during request: ${_ev.reason || "Unknown reason"}`));
3338
3465
  };
3339
3466
  this.rws.addEventListener("close", closeHandler);
3340
3467
  this.rws.addEventListener("error", closeHandler);
@@ -3370,7 +3497,7 @@ var Transport = class {
3370
3497
  try {
3371
3498
  return await sender();
3372
3499
  } catch (e) {
3373
- if (e instanceof ErrorEvent || e instanceof RateLimitError || e instanceof InvalidConfigurationError || e instanceof InvalidMessageError || e instanceof DOMException) {
3500
+ if (e instanceof ErrorEvent || e instanceof RateLimitError || e instanceof InvalidConfigurationError || e instanceof InvalidMessageError || e instanceof NotebookUnavailableError || e instanceof DOMException) {
3374
3501
  this.log("debug", "Non-retryable error, bailing", {
3375
3502
  error: e.message,
3376
3503
  attempt
@@ -3422,6 +3549,9 @@ var Transport = class {
3422
3549
  * This preserves all event listeners and state.
3423
3550
  */
3424
3551
  reconnect() {
3552
+ if (this.terminalError) {
3553
+ throw this.terminalError;
3554
+ }
3425
3555
  if (this.closed) {
3426
3556
  throw new Error("Cannot reconnect a closed transport. The transport has been permanently closed.");
3427
3557
  }
@@ -3436,6 +3566,16 @@ var Transport = class {
3436
3566
  }
3437
3567
  this.close();
3438
3568
  }
3569
+ terminate(error, code = 4e3, reason = error.message) {
3570
+ this.terminalError = error;
3571
+ if (error instanceof NotebookUnavailableError) {
3572
+ this.eventEmitter.emit("transport.boot_error", {
3573
+ error,
3574
+ timestamp: Date.now()
3575
+ });
3576
+ }
3577
+ this.close(code, reason);
3578
+ }
3439
3579
  close(code, reason) {
3440
3580
  if (this.closed) {
3441
3581
  return;
@@ -3443,9 +3583,9 @@ var Transport = class {
3443
3583
  this.log("info", "Closing transport connection", { code, reason });
3444
3584
  this.connectPromise = null;
3445
3585
  const queuedCount = this.messageQueue.length;
3586
+ const closeError = this.terminalError || new Error("Connection closed while message was queued");
3446
3587
  this.messageQueue.forEach((msg) => {
3447
- console.log("Rejecting queued message due to connection close");
3448
- msg.reject(new Error("Connection closed while message was queued"));
3588
+ msg.reject(closeError);
3449
3589
  });
3450
3590
  this.messageQueue = [];
3451
3591
  if (queuedCount > 0) {
@@ -3533,6 +3673,9 @@ var Transport = class {
3533
3673
  this.log("error", "Connection closed due to policy violation");
3534
3674
  this.clearOldQueuedMessages();
3535
3675
  return "stop";
3676
+ case 4e3:
3677
+ this.log("info", "Connection closed due to terminal notebook error");
3678
+ return "stop";
3536
3679
  default:
3537
3680
  this.log("warn", `Unknown close code: ${code}, will reconnect`);
3538
3681
  return "reconnect";
@@ -3748,6 +3891,9 @@ _Transport_instances = new WeakSet();
3748
3891
  * by caching the connection promise.
3749
3892
  */
3750
3893
  connect_fn = function() {
3894
+ if (this.terminalError) {
3895
+ return Promise.reject(this.terminalError);
3896
+ }
3751
3897
  if (this.isConnected) {
3752
3898
  return Promise.resolve();
3753
3899
  }
@@ -4794,9 +4940,14 @@ var NotebookInstance = class {
4794
4940
  this.laravel = new Lravel(this);
4795
4941
  this.shell = new Shell(this);
4796
4942
  this.git = new Git(this);
4943
+ this.services = new Services(this);
4797
4944
  this.secrets = new NotebookSecrets(client, this.data.id);
4798
4945
  }
4799
4946
  async ready() {
4947
+ const terminalError = this.socket.getTerminalError();
4948
+ if (terminalError) {
4949
+ throw terminalError;
4950
+ }
4800
4951
  const ready = async () => {
4801
4952
  if (this.client.options.startClosed && !this.socket.isConnected) {
4802
4953
  await this.socket.connect();
@@ -4809,7 +4960,9 @@ var NotebookInstance = class {
4809
4960
  return this.client.notebook.fork(this.data.id);
4810
4961
  }
4811
4962
  delete() {
4812
- return this.client.notebook.delete(this.data.id);
4963
+ return this.client.notebook.delete(this.data.id).then(() => {
4964
+ this.socket.terminate(new NotebookUnavailableError("Notebook has been deleted.", { id: this.data.id }));
4965
+ });
4813
4966
  }
4814
4967
  stop() {
4815
4968
  return this.container.stop();
@@ -4833,6 +4986,10 @@ var NotebookInstance = class {
4833
4986
  !this.socket.isClosed && this.socket.disconnect();
4834
4987
  }
4835
4988
  connected() {
4989
+ const terminalError = this.socket.getTerminalError();
4990
+ if (terminalError) {
4991
+ return Promise.reject(terminalError);
4992
+ }
4836
4993
  if (this.socket.isConnected) {
4837
4994
  return Promise.resolve(this);
4838
4995
  }
@@ -4840,18 +4997,24 @@ var NotebookInstance = class {
4840
4997
  try {
4841
4998
  this.socket.onDidConnect(() => resolve(this));
4842
4999
  this.socket.onDidClose(() => reject(new Error("Connection closed")));
5000
+ this.socket.onDidBootError((error) => reject(error));
4843
5001
  } catch (e) {
4844
5002
  reject(e);
4845
5003
  }
4846
5004
  });
4847
5005
  }
4848
5006
  whenConnected() {
5007
+ const terminalError = this.socket.getTerminalError();
5008
+ if (terminalError) {
5009
+ return Promise.reject(terminalError);
5010
+ }
4849
5011
  if (this.socket.isConnected) {
4850
5012
  return Promise.resolve(this);
4851
5013
  }
4852
5014
  return new Promise((resolve, reject) => {
4853
5015
  try {
4854
5016
  this.socket.onDidConnect(() => resolve(this));
5017
+ this.socket.onDidBootError((error) => reject(error));
4855
5018
  } catch (e) {
4856
5019
  reject(e);
4857
5020
  }
@@ -4868,6 +5031,11 @@ var NotebookInstance = class {
4868
5031
  this.socket.emit("okra.disconnected");
4869
5032
  this.initialized = false;
4870
5033
  });
5034
+ this.socket.onDidBootError((error) => {
5035
+ this.socket.emit("okra.boot_error", error);
5036
+ this.socket.emit("okra.disconnected");
5037
+ this.initialized = false;
5038
+ });
4871
5039
  }
4872
5040
  onDidConnect(handler) {
4873
5041
  this.socket.removeListener("okra.connected", handler);
@@ -4880,6 +5048,11 @@ var NotebookInstance = class {
4880
5048
  this.disposables.push(disposable);
4881
5049
  return disposable;
4882
5050
  }
5051
+ onDidBootError(handler) {
5052
+ const disposable = this.socket.listen("okra.boot_error", handler);
5053
+ this.disposables.push(disposable);
5054
+ return disposable;
5055
+ }
4883
5056
  update() {
4884
5057
  return this.invoke("notebook.update");
4885
5058
  }
@@ -4905,6 +5078,7 @@ _NotebookInstance_instances = new WeakSet();
4905
5078
  init_fn = function() {
4906
5079
  __privateSet(this, _initPromise, new Promise((resolve, reject) => {
4907
5080
  const initializationListener = this.onDidInitialize((result) => {
5081
+ bootErrorListener.dispose();
4908
5082
  initializationListener.dispose();
4909
5083
  this.initialized = result;
4910
5084
  if (result.type === "error") {
@@ -4913,6 +5087,11 @@ init_fn = function() {
4913
5087
  }
4914
5088
  resolve(result);
4915
5089
  });
5090
+ const bootErrorListener = this.onDidBootError((error) => {
5091
+ initializationListener.dispose();
5092
+ bootErrorListener.dispose();
5093
+ reject(error);
5094
+ });
4916
5095
  }));
4917
5096
  return __privateGet(this, _initPromise);
4918
5097
  };
@@ -4956,6 +5135,7 @@ export {
4956
5135
  NotebookSecretApi,
4957
5136
  NotebookSecrets,
4958
5137
  NotebookState,
5138
+ NotebookUnavailableError,
4959
5139
  PHPSandbox,
4960
5140
  PHPSandboxError,
4961
5141
  PromiseTimeoutError,