@phpsandbox/sdk 0.0.29 → 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
@@ -572,6 +572,13 @@ var SendTimeoutError = class _SendTimeoutError extends PromiseTimeoutError {
572
572
  return new _SendTimeoutError(error.message, error.time);
573
573
  }
574
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
+ };
575
582
 
576
583
  // src/utils/promise.ts
577
584
  var timeout = (prom, time) => {
@@ -1019,6 +1026,103 @@ var Shell = class {
1019
1026
  }
1020
1027
  };
1021
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
+
1022
1126
  // node_modules/@msgpack/msgpack/dist.esm/utils/utf8.mjs
1023
1127
  function utf8Count(str) {
1024
1128
  const strLength = str.length;
@@ -3042,6 +3146,7 @@ var Transport = class {
3042
3146
  this.closed = false;
3043
3147
  this.disposables = new NamedDisposable();
3044
3148
  this.connectPromise = null;
3149
+ this.terminalError = null;
3045
3150
  this.pingIntervalStarted = false;
3046
3151
  // Connection health monitoring
3047
3152
  this.connectionStats = {
@@ -3073,7 +3178,7 @@ var Transport = class {
3073
3178
  const startClosed = options.startClosed !== false;
3074
3179
  this.rws = new reconnecting_websocket_mjs_default(this.url.toString(), [], {
3075
3180
  WebSocket: globalThis.WebSocket ?? browser_default,
3076
- connectionTimeout: options.connectionTimeout ?? 1e3,
3181
+ connectionTimeout: options.connectionTimeout,
3077
3182
  maxReconnectionDelay: 2e3,
3078
3183
  minReconnectionDelay: 200,
3079
3184
  maxEnqueuedMessages: 0,
@@ -3116,6 +3221,9 @@ var Transport = class {
3116
3221
  * Ensure the websocket is open without requiring an application-level roundtrip.
3117
3222
  */
3118
3223
  connect() {
3224
+ if (this.terminalError) {
3225
+ return Promise.reject(this.terminalError);
3226
+ }
3119
3227
  return __privateMethod(this, _Transport_instances, connect_fn).call(this);
3120
3228
  }
3121
3229
  id() {
@@ -3172,7 +3280,12 @@ var Transport = class {
3172
3280
  return;
3173
3281
  }
3174
3282
  if (event === "Events.BootError" /* BootError */) {
3283
+ const error = new NotebookUnavailableError(
3284
+ data?.message || "Notebook is no longer available",
3285
+ data || {}
3286
+ );
3175
3287
  this.log("error", "Boot error received", { data });
3288
+ this.terminate(error, 4e3, error.message);
3176
3289
  return;
3177
3290
  }
3178
3291
  if (event === "response" /* Response */) {
@@ -3231,6 +3344,11 @@ var Transport = class {
3231
3344
  listen(event, listener, _context) {
3232
3345
  return this.eventEmitter.listen(event, listener);
3233
3346
  }
3347
+ onDidBootError(listener) {
3348
+ this.eventEmitter.listen("transport.boot_error", (event) => {
3349
+ listener(event.error);
3350
+ });
3351
+ }
3234
3352
  removeListener(event, listener) {
3235
3353
  this.eventEmitter.removeListener(event, listener);
3236
3354
  }
@@ -3257,6 +3375,9 @@ var Transport = class {
3257
3375
  get isClosed() {
3258
3376
  return this.closed;
3259
3377
  }
3378
+ getTerminalError() {
3379
+ return this.terminalError;
3380
+ }
3260
3381
  get status() {
3261
3382
  return {
3262
3383
  0: "CONNECTING",
@@ -3266,6 +3387,9 @@ var Transport = class {
3266
3387
  }[this.rws.readyState];
3267
3388
  }
3268
3389
  async call(action, data = {}, options = {}) {
3390
+ if (this.terminalError) {
3391
+ throw this.terminalError;
3392
+ }
3269
3393
  if (this.isRateLimited()) {
3270
3394
  throw new RateLimitError("Rate limit exceeded - too many requests");
3271
3395
  }
@@ -3282,6 +3406,10 @@ var Transport = class {
3282
3406
  this.eventEmitter.removeListener(errorEvent);
3283
3407
  };
3284
3408
  const handler = async (resolve, reject) => {
3409
+ if (this.terminalError) {
3410
+ reject(this.terminalError);
3411
+ return;
3412
+ }
3285
3413
  const abortError = new DOMException("Request aborted", "AbortError");
3286
3414
  if (options.abortSignal?.aborted) {
3287
3415
  reject(abortError);
@@ -3333,7 +3461,7 @@ var Transport = class {
3333
3461
  reject(new RateLimitError(_ev.reason || "Rate limit exceeded", _ev));
3334
3462
  return;
3335
3463
  }
3336
- 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"}`));
3337
3465
  };
3338
3466
  this.rws.addEventListener("close", closeHandler);
3339
3467
  this.rws.addEventListener("error", closeHandler);
@@ -3369,7 +3497,7 @@ var Transport = class {
3369
3497
  try {
3370
3498
  return await sender();
3371
3499
  } catch (e) {
3372
- 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) {
3373
3501
  this.log("debug", "Non-retryable error, bailing", {
3374
3502
  error: e.message,
3375
3503
  attempt
@@ -3421,6 +3549,9 @@ var Transport = class {
3421
3549
  * This preserves all event listeners and state.
3422
3550
  */
3423
3551
  reconnect() {
3552
+ if (this.terminalError) {
3553
+ throw this.terminalError;
3554
+ }
3424
3555
  if (this.closed) {
3425
3556
  throw new Error("Cannot reconnect a closed transport. The transport has been permanently closed.");
3426
3557
  }
@@ -3435,6 +3566,16 @@ var Transport = class {
3435
3566
  }
3436
3567
  this.close();
3437
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
+ }
3438
3579
  close(code, reason) {
3439
3580
  if (this.closed) {
3440
3581
  return;
@@ -3442,9 +3583,9 @@ var Transport = class {
3442
3583
  this.log("info", "Closing transport connection", { code, reason });
3443
3584
  this.connectPromise = null;
3444
3585
  const queuedCount = this.messageQueue.length;
3586
+ const closeError = this.terminalError || new Error("Connection closed while message was queued");
3445
3587
  this.messageQueue.forEach((msg) => {
3446
- console.log("Rejecting queued message due to connection close");
3447
- msg.reject(new Error("Connection closed while message was queued"));
3588
+ msg.reject(closeError);
3448
3589
  });
3449
3590
  this.messageQueue = [];
3450
3591
  if (queuedCount > 0) {
@@ -3532,6 +3673,9 @@ var Transport = class {
3532
3673
  this.log("error", "Connection closed due to policy violation");
3533
3674
  this.clearOldQueuedMessages();
3534
3675
  return "stop";
3676
+ case 4e3:
3677
+ this.log("info", "Connection closed due to terminal notebook error");
3678
+ return "stop";
3535
3679
  default:
3536
3680
  this.log("warn", `Unknown close code: ${code}, will reconnect`);
3537
3681
  return "reconnect";
@@ -3747,6 +3891,9 @@ _Transport_instances = new WeakSet();
3747
3891
  * by caching the connection promise.
3748
3892
  */
3749
3893
  connect_fn = function() {
3894
+ if (this.terminalError) {
3895
+ return Promise.reject(this.terminalError);
3896
+ }
3750
3897
  if (this.isConnected) {
3751
3898
  return Promise.resolve();
3752
3899
  }
@@ -4793,9 +4940,14 @@ var NotebookInstance = class {
4793
4940
  this.laravel = new Lravel(this);
4794
4941
  this.shell = new Shell(this);
4795
4942
  this.git = new Git(this);
4943
+ this.services = new Services(this);
4796
4944
  this.secrets = new NotebookSecrets(client, this.data.id);
4797
4945
  }
4798
4946
  async ready() {
4947
+ const terminalError = this.socket.getTerminalError();
4948
+ if (terminalError) {
4949
+ throw terminalError;
4950
+ }
4799
4951
  const ready = async () => {
4800
4952
  if (this.client.options.startClosed && !this.socket.isConnected) {
4801
4953
  await this.socket.connect();
@@ -4808,7 +4960,9 @@ var NotebookInstance = class {
4808
4960
  return this.client.notebook.fork(this.data.id);
4809
4961
  }
4810
4962
  delete() {
4811
- 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
+ });
4812
4966
  }
4813
4967
  stop() {
4814
4968
  return this.container.stop();
@@ -4832,6 +4986,10 @@ var NotebookInstance = class {
4832
4986
  !this.socket.isClosed && this.socket.disconnect();
4833
4987
  }
4834
4988
  connected() {
4989
+ const terminalError = this.socket.getTerminalError();
4990
+ if (terminalError) {
4991
+ return Promise.reject(terminalError);
4992
+ }
4835
4993
  if (this.socket.isConnected) {
4836
4994
  return Promise.resolve(this);
4837
4995
  }
@@ -4839,18 +4997,24 @@ var NotebookInstance = class {
4839
4997
  try {
4840
4998
  this.socket.onDidConnect(() => resolve(this));
4841
4999
  this.socket.onDidClose(() => reject(new Error("Connection closed")));
5000
+ this.socket.onDidBootError((error) => reject(error));
4842
5001
  } catch (e) {
4843
5002
  reject(e);
4844
5003
  }
4845
5004
  });
4846
5005
  }
4847
5006
  whenConnected() {
5007
+ const terminalError = this.socket.getTerminalError();
5008
+ if (terminalError) {
5009
+ return Promise.reject(terminalError);
5010
+ }
4848
5011
  if (this.socket.isConnected) {
4849
5012
  return Promise.resolve(this);
4850
5013
  }
4851
5014
  return new Promise((resolve, reject) => {
4852
5015
  try {
4853
5016
  this.socket.onDidConnect(() => resolve(this));
5017
+ this.socket.onDidBootError((error) => reject(error));
4854
5018
  } catch (e) {
4855
5019
  reject(e);
4856
5020
  }
@@ -4867,6 +5031,11 @@ var NotebookInstance = class {
4867
5031
  this.socket.emit("okra.disconnected");
4868
5032
  this.initialized = false;
4869
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
+ });
4870
5039
  }
4871
5040
  onDidConnect(handler) {
4872
5041
  this.socket.removeListener("okra.connected", handler);
@@ -4879,6 +5048,11 @@ var NotebookInstance = class {
4879
5048
  this.disposables.push(disposable);
4880
5049
  return disposable;
4881
5050
  }
5051
+ onDidBootError(handler) {
5052
+ const disposable = this.socket.listen("okra.boot_error", handler);
5053
+ this.disposables.push(disposable);
5054
+ return disposable;
5055
+ }
4882
5056
  update() {
4883
5057
  return this.invoke("notebook.update");
4884
5058
  }
@@ -4904,6 +5078,7 @@ _NotebookInstance_instances = new WeakSet();
4904
5078
  init_fn = function() {
4905
5079
  __privateSet(this, _initPromise, new Promise((resolve, reject) => {
4906
5080
  const initializationListener = this.onDidInitialize((result) => {
5081
+ bootErrorListener.dispose();
4907
5082
  initializationListener.dispose();
4908
5083
  this.initialized = result;
4909
5084
  if (result.type === "error") {
@@ -4912,6 +5087,11 @@ init_fn = function() {
4912
5087
  }
4913
5088
  resolve(result);
4914
5089
  });
5090
+ const bootErrorListener = this.onDidBootError((error) => {
5091
+ initializationListener.dispose();
5092
+ bootErrorListener.dispose();
5093
+ reject(error);
5094
+ });
4915
5095
  }));
4916
5096
  return __privateGet(this, _initPromise);
4917
5097
  };
@@ -4955,6 +5135,7 @@ export {
4955
5135
  NotebookSecretApi,
4956
5136
  NotebookSecrets,
4957
5137
  NotebookState,
5138
+ NotebookUnavailableError,
4958
5139
  PHPSandbox,
4959
5140
  PHPSandboxError,
4960
5141
  PromiseTimeoutError,