@phpsandbox/sdk 0.0.65 → 0.0.67

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.
@@ -153,6 +153,8 @@ export interface DeploymentData<TProvider extends DeploymentTargetProvider = Dep
153
153
  strategy: string;
154
154
  targetProvider: TProvider;
155
155
  options: DeploymentOptionsForProvider<TProvider>;
156
+ eventStreamUrl?: string | null;
157
+ /** @deprecated Use eventStreamUrl instead. */
156
158
  okraUrl: string | null;
157
159
  originUrl: string | null;
158
160
  currentRelease?: DeploymentReleaseData | null;
@@ -124,8 +124,9 @@ export class DeploymentRun {
124
124
  if (this.started) {
125
125
  return this;
126
126
  }
127
- if (!this.initial.data.okraUrl) {
128
- throw new Error(`Deployment ${this.initial.data.id} has no okraUrl for SSE streaming`);
127
+ const eventStreamUrl = this.initial.data.eventStreamUrl ?? this.initial.data.okraUrl;
128
+ if (!eventStreamUrl) {
129
+ throw new Error(`Deployment ${this.initial.data.id} has no eventStreamUrl for SSE streaming`);
129
130
  }
130
131
  this.started = true;
131
132
  this.resultPromise = new Promise((resolve, reject) => {
@@ -133,7 +134,7 @@ export class DeploymentRun {
133
134
  this.rejectResult = reject;
134
135
  });
135
136
  this.resultPromise.catch(() => undefined);
136
- void this.consumeStream(this.initial.data.okraUrl);
137
+ void this.consumeStream(eventStreamUrl);
137
138
  return this;
138
139
  }
139
140
  subscribe() {
@@ -152,9 +153,9 @@ export class DeploymentRun {
152
153
  }
153
154
  return queue;
154
155
  }
155
- async consumeStream(okraUrl) {
156
+ async consumeStream(eventStreamUrl) {
156
157
  try {
157
- const stream = await this.client.sse(okraUrl);
158
+ const stream = await this.client.sse(eventStreamUrl);
158
159
  for await (const event of readDeployStreamEvents(stream)) {
159
160
  this.publish(event);
160
161
  if (event.type === 'result') {
@@ -9,3 +9,6 @@ export declare class NotebookUnavailableError extends Error {
9
9
  readonly raw: object;
10
10
  constructor(message?: string, raw?: object);
11
11
  }
12
+ export declare class UnsupportedRuntimeTransportError extends Error {
13
+ constructor(message: string);
14
+ }
@@ -16,3 +16,9 @@ export class NotebookUnavailableError extends Error {
16
16
  this.name = 'NotebookUnavailableError';
17
17
  }
18
18
  }
19
+ export class UnsupportedRuntimeTransportError extends Error {
20
+ constructor(message) {
21
+ super(message);
22
+ this.name = 'UnsupportedRuntimeTransportError';
23
+ }
24
+ }
@@ -451,8 +451,8 @@ declare class FilesystemSubscription {
451
451
  export declare class Filesystem {
452
452
  protected okra: NotebookInstance;
453
453
  private watches;
454
- constructor(okra: NotebookInstance);
455
- private watchOkraConnection;
454
+ constructor(okra: NotebookInstance, registerReconnect?: ((handler: () => void) => Disposable) | null);
455
+ private registerReconnectHandler;
456
456
  info(path: string): Promise<FileInfo>;
457
457
  write(path: string, contents: string): Promise<boolean>;
458
458
  find(query: string, options?: Partial<FileSearchOptions>): Promise<FileResult[]>;
@@ -69,13 +69,16 @@ class FilesystemSubscription {
69
69
  }
70
70
  }
71
71
  export class Filesystem {
72
- constructor(okra) {
72
+ constructor(okra, registerReconnect = (handler) => okra.onDidConnect(handler)) {
73
73
  this.okra = okra;
74
74
  this.watches = new Map();
75
- this.watchOkraConnection();
75
+ this.registerReconnectHandler(registerReconnect);
76
76
  }
77
- watchOkraConnection() {
78
- this.okra.onDidConnect(() => {
77
+ registerReconnectHandler(registerReconnect) {
78
+ if (registerReconnect === null) {
79
+ return;
80
+ }
81
+ registerReconnect(() => {
79
82
  for (const { path, options } of this.watches.values()) {
80
83
  void this.okra.invoke('fs.watch', { path, options }).catch(() => { });
81
84
  }
package/dist/index.d.ts CHANGED
@@ -307,12 +307,18 @@ export interface PHPSandboxClientOptions {
307
307
  telemetry?: Set<TelemetryFeature>;
308
308
  fetch?: typeof globalThis.fetch;
309
309
  webSocket?: unknown;
310
+ runtimeUrlProvider?: (notebookId: string) => Promise<string>;
311
+ /** @deprecated Use runtimeUrlProvider instead. */
312
+ okraUrlProvider?: (notebookId: string) => Promise<string>;
310
313
  }
314
+ export type RuntimeTransport = 'realtime' | 'rest';
311
315
  export declare class Client {
312
316
  readonly notebook: NotebookApi;
313
317
  readonly deployments: DeploymentApi;
314
318
  readonly servers: ServerApi;
315
319
  readonly options: PHPSandboxClientOptions;
320
+ protected runtimeTransportValue: RuntimeTransport;
321
+ get runtimeTransport(): RuntimeTransport;
316
322
  private readonly fetch;
317
323
  private readonly baseUrl;
318
324
  private readonly headers;
@@ -334,6 +340,8 @@ export declare class Client {
334
340
  private makeRequest;
335
341
  }
336
342
  export declare class PHPSandbox extends Client {
343
+ static realtime(token: string, url?: string, options?: PHPSandboxClientOptions): PHPSandbox;
344
+ static rest(token: string, url?: string, options?: PHPSandboxClientOptions): PHPSandbox;
337
345
  }
338
346
  export declare class NotebookSecretApi {
339
347
  private readonly client;
@@ -371,6 +379,8 @@ export declare class NotebookMailApi {
371
379
  }
372
380
  export interface NotebookData {
373
381
  id: string;
382
+ runtimeUrl?: string;
383
+ /** @deprecated Use runtimeUrl instead. */
374
384
  okraUrl: string;
375
385
  gitUrl: string;
376
386
  type?: string;
@@ -400,8 +410,10 @@ export declare class NotebookInstance {
400
410
  readonly preview: NotebookPreview;
401
411
  readonly mail: NotebookMail;
402
412
  private readonly socket;
413
+ private readonly restInvoker;
403
414
  readonly emitter: EventDispatcher;
404
415
  initialized: NotebookInitResult | false;
416
+ get runtimeTransport(): RuntimeTransport;
405
417
  private readonly disposables;
406
418
  constructor(data: NotebookData, client: Client);
407
419
  ready(): Promise<NotebookInitSuccessResult>;
@@ -425,10 +437,12 @@ export declare class NotebookInstance {
425
437
  onDidConnect(handler: () => void): Disposable;
426
438
  onDidDisconnect(handler: () => void): Disposable;
427
439
  onDidBootError(handler: (error: NotebookUnavailableError) => void): Disposable;
440
+ private restInitialization;
428
441
  update(input: Partial<UpdateNotebookInput>): Promise<NotebookInstance>;
429
442
  onDidInitialize(handler: (result: NotebookInitResult) => void): Disposable;
430
443
  reconnect(): Promise<NotebookInstance>;
431
444
  beacon(iframe: HTMLIFrameElement, options?: BeaconOptions): Promise<Beacon>;
445
+ private realtimeSocket;
432
446
  }
433
447
  export declare class NotebookSecrets {
434
448
  private readonly client;
package/dist/index.js CHANGED
@@ -26,9 +26,10 @@ import { Transport } from './socket/index.js';
26
26
  import EventManager from './events/index.js';
27
27
  import Git from './git.js';
28
28
  import { createBeacon } from './beacon/index.js';
29
- import { NotebookUnavailableError } from './errors/index.js';
29
+ import { NotebookUnavailableError, UnsupportedRuntimeTransportError } from './errors/index.js';
30
30
  import { DeploymentApi, NotebookDeploymentApi } from './deployments.js';
31
31
  import { ServerApi } from './servers.js';
32
+ import { RestRuntimeInvoker } from './runtime/rest.js';
32
33
  export * from './types.js';
33
34
  export { Transport } from './socket/index.js';
34
35
  export * from './lsp.js';
@@ -150,7 +151,11 @@ function normalizeApiBaseUrl(url) {
150
151
  return baseUrl.toString();
151
152
  }
152
153
  export class Client {
154
+ get runtimeTransport() {
155
+ return this.runtimeTransportValue;
156
+ }
153
157
  constructor(token, url = 'https://api.phpsandbox.io/v1', options = {}) {
158
+ this.runtimeTransportValue = 'realtime';
154
159
  this.fetch = globalThis.fetch;
155
160
  this.deployments = new DeploymentApi(this);
156
161
  this.notebook = new NotebookApi(this);
@@ -224,6 +229,14 @@ export class Client {
224
229
  }
225
230
  }
226
231
  export class PHPSandbox extends Client {
232
+ static realtime(token, url = 'https://api.phpsandbox.io/v1', options = {}) {
233
+ return new PHPSandbox(token, url, options);
234
+ }
235
+ static rest(token, url = 'https://api.phpsandbox.io/v1', options = {}) {
236
+ const client = new PHPSandbox(token, url, options);
237
+ client.runtimeTransportValue = 'rest';
238
+ return client;
239
+ }
227
240
  }
228
241
  export class NotebookSecretApi {
229
242
  constructor(client) {
@@ -324,22 +337,46 @@ export class NotebookMailApi {
324
337
  }
325
338
  }
326
339
  export class NotebookInstance {
340
+ get runtimeTransport() {
341
+ return this.client.runtimeTransport;
342
+ }
327
343
  constructor(data, client) {
328
344
  _NotebookInstance_instances.add(this);
329
345
  this.data = data;
330
346
  this.client = client;
331
347
  this.initialized = false;
332
- _NotebookInstance_initPromise.set(this, void 0);
348
+ _NotebookInstance_initPromise.set(this, null);
333
349
  this.disposables = [];
350
+ const runtimeUrl = data.runtimeUrl ?? data.okraUrl;
351
+ const runtimeUrlProvider = client.options.runtimeUrlProvider ?? client.options.okraUrlProvider;
334
352
  this.emitter = EventManager.createInstance();
335
- this.socket = new Transport(data.okraUrl, this.emitter, {
336
- debug: client.options.debug,
337
- startClosed: client.options.startClosed,
338
- webSocket: client.options.webSocket,
339
- });
340
- this.watchConnection();
341
- __classPrivateFieldSet(this, _NotebookInstance_initPromise, __classPrivateFieldGet(this, _NotebookInstance_instances, "m", _NotebookInstance_init).call(this), "f");
342
- this.file = new Filesystem(this);
353
+ if (client.runtimeTransport === 'rest') {
354
+ this.socket = null;
355
+ this.restInvoker = new RestRuntimeInvoker(data, client.options.fetch ?? globalThis.fetch, runtimeUrlProvider === undefined
356
+ ? undefined
357
+ : () => runtimeUrlProvider(data.id));
358
+ }
359
+ else {
360
+ this.restInvoker = null;
361
+ let initialConnection = true;
362
+ const connectionUrl = runtimeUrlProvider === undefined
363
+ ? runtimeUrl
364
+ : async () => {
365
+ if (initialConnection) {
366
+ initialConnection = false;
367
+ return runtimeUrl;
368
+ }
369
+ return runtimeUrlProvider(data.id);
370
+ };
371
+ this.socket = new Transport(connectionUrl, this.emitter, {
372
+ debug: client.options.debug,
373
+ startClosed: client.options.startClosed,
374
+ webSocket: client.options.webSocket,
375
+ });
376
+ this.watchConnection();
377
+ __classPrivateFieldSet(this, _NotebookInstance_initPromise, __classPrivateFieldGet(this, _NotebookInstance_instances, "m", _NotebookInstance_init).call(this), "f");
378
+ }
379
+ this.file = new Filesystem(this, this.socket === null ? null : (handler) => this.onDidConnect(handler));
343
380
  this.terminal = new Terminal(this);
344
381
  this.auth = new Auth(this);
345
382
  this.lsp = new Lsp(this);
@@ -357,7 +394,11 @@ export class NotebookInstance {
357
394
  this.mail = new NotebookMail(client, this.data.id);
358
395
  }
359
396
  async ready() {
360
- const terminalError = this.socket.getTerminalError();
397
+ if (this.restInvoker !== null) {
398
+ return this.restInitialization();
399
+ }
400
+ const socket = this.realtimeSocket();
401
+ const terminalError = socket.getTerminalError();
361
402
  if (terminalError) {
362
403
  throw terminalError;
363
404
  }
@@ -366,8 +407,8 @@ export class NotebookInstance {
366
407
  * If ready is called, ensure the websocket is open so server-driven
367
408
  * initialization can begin, but do not block on an extra ping roundtrip.
368
409
  */
369
- if (this.client.options.startClosed && !this.socket.isConnected) {
370
- await this.socket.connect();
410
+ if (this.client.options.startClosed && !socket.isConnected) {
411
+ await socket.connect();
371
412
  }
372
413
  return __classPrivateFieldGet(this, _NotebookInstance_initPromise, "f");
373
414
  };
@@ -389,7 +430,7 @@ export class NotebookInstance {
389
430
  }
390
431
  delete() {
391
432
  return this.client.notebook.delete(this.data.id).then(() => {
392
- this.socket.terminate(new NotebookUnavailableError('Notebook has been deleted.', { id: this.data.id }));
433
+ this.socket?.terminate(new NotebookUnavailableError('Notebook has been deleted.', { id: this.data.id }));
393
434
  });
394
435
  }
395
436
  stop() {
@@ -399,10 +440,19 @@ export class NotebookInstance {
399
440
  return this.container.restart();
400
441
  }
401
442
  invoke(action, data = {}, options = {}) {
402
- return this.socket.invoke(action, data || {}, options);
443
+ if (this.restInvoker !== null) {
444
+ if (this.restInvoker.supports(String(action))) {
445
+ void this.restInitialization();
446
+ }
447
+ return this.restInvoker.invoke(action, data || {}, options);
448
+ }
449
+ return this.realtimeSocket().invoke(action, data || {}, options);
403
450
  }
404
451
  send(action, data = {}) {
405
- return this.socket.send(action, data || {});
452
+ if (this.restInvoker !== null) {
453
+ return this.restInvoker.send();
454
+ }
455
+ return this.realtimeSocket().send(action, data || {});
406
456
  }
407
457
  ping() {
408
458
  return this.invoke('ping');
@@ -414,7 +464,9 @@ export class NotebookInstance {
414
464
  }
415
465
  dispose() {
416
466
  this.disposables.forEach((d) => d.dispose());
417
- !this.socket.isClosed && this.socket.disconnect();
467
+ if (this.socket !== null && !this.socket.isClosed) {
468
+ this.socket.disconnect();
469
+ }
418
470
  }
419
471
  connected() {
420
472
  return this.waitForSocketConnection({ rejectOnClose: true });
@@ -423,11 +475,12 @@ export class NotebookInstance {
423
475
  return this.waitForSocketConnection({ rejectOnClose: false });
424
476
  }
425
477
  waitForSocketConnection(options) {
426
- const terminalError = this.socket.getTerminalError();
478
+ const socket = this.realtimeSocket();
479
+ const terminalError = socket.getTerminalError();
427
480
  if (terminalError) {
428
481
  return Promise.reject(terminalError);
429
482
  }
430
- if (this.socket.isConnected) {
483
+ if (socket.isConnected) {
431
484
  return Promise.resolve(this);
432
485
  }
433
486
  return new Promise((resolve, reject) => {
@@ -448,11 +501,11 @@ export class NotebookInstance {
448
501
  callback();
449
502
  };
450
503
  try {
451
- disposables.push(this.socket.onDidConnect(() => settle(() => resolve(this))));
504
+ disposables.push(socket.onDidConnect(() => settle(() => resolve(this))));
452
505
  if (options.rejectOnClose) {
453
- disposables.push(this.socket.onDidClose(() => settle(() => reject(new Error('Connection closed')))));
506
+ disposables.push(socket.onDidClose(() => settle(() => reject(new Error('Connection closed')))));
454
507
  }
455
- disposables.push(this.socket.onDidBootError((error) => settle(() => reject(error))));
508
+ disposables.push(socket.onDidBootError((error) => settle(() => reject(error))));
456
509
  }
457
510
  catch (e) {
458
511
  settle(() => reject(e));
@@ -460,38 +513,54 @@ export class NotebookInstance {
460
513
  });
461
514
  }
462
515
  watchConnection() {
463
- this.socket.onDidConnect(() => {
464
- this.socket.emit('okra.connected');
516
+ const socket = this.realtimeSocket();
517
+ socket.onDidConnect(() => {
518
+ socket.emit('okra.connected');
465
519
  if (this.client.options.telemetry) {
466
520
  this.container.enableTelemetry(this.client.options.telemetry);
467
521
  }
468
522
  });
469
- this.socket.onDidClose(() => {
470
- this.socket.emit('okra.disconnected');
523
+ socket.onDidClose(() => {
524
+ socket.emit('okra.disconnected');
471
525
  this.initialized = false;
472
526
  });
473
- this.socket.onDidBootError((error) => {
474
- this.socket.emit('okra.boot_error', error);
475
- this.socket.emit('okra.disconnected');
527
+ socket.onDidBootError((error) => {
528
+ socket.emit('okra.boot_error', error);
529
+ socket.emit('okra.disconnected');
476
530
  this.initialized = false;
477
531
  });
478
532
  }
479
533
  onDidConnect(handler) {
480
- this.socket.removeListener('okra.connected', handler);
481
- const disposable = this.socket.listen('okra.connected', handler);
534
+ const socket = this.realtimeSocket();
535
+ socket.removeListener('okra.connected', handler);
536
+ const disposable = socket.listen('okra.connected', handler);
482
537
  this.disposables.push(disposable);
483
538
  return disposable;
484
539
  }
485
540
  onDidDisconnect(handler) {
486
- const disposable = this.socket.listen('okra.disconnected', handler);
541
+ const disposable = this.realtimeSocket().listen('okra.disconnected', handler);
487
542
  this.disposables.push(disposable);
488
543
  return disposable;
489
544
  }
490
545
  onDidBootError(handler) {
491
- const disposable = this.socket.listen('okra.boot_error', handler);
546
+ const disposable = this.realtimeSocket().listen('okra.boot_error', handler);
492
547
  this.disposables.push(disposable);
493
548
  return disposable;
494
549
  }
550
+ restInitialization() {
551
+ if (this.restInvoker === null) {
552
+ throw new UnsupportedRuntimeTransportError('This operation requires the REST transport.');
553
+ }
554
+ if (__classPrivateFieldGet(this, _NotebookInstance_initPromise, "f") === null) {
555
+ __classPrivateFieldSet(this, _NotebookInstance_initPromise, this.restInvoker.ready().then((result) => {
556
+ this.initialized = result;
557
+ this.emitter.emit('notebook.initialized', result);
558
+ return result;
559
+ }), "f");
560
+ void __classPrivateFieldGet(this, _NotebookInstance_initPromise, "f").catch(() => { });
561
+ }
562
+ return __classPrivateFieldGet(this, _NotebookInstance_initPromise, "f");
563
+ }
495
564
  update(input) {
496
565
  return this.client.notebook.update(this.data.id, input);
497
566
  }
@@ -499,10 +568,11 @@ export class NotebookInstance {
499
568
  return this.listen('notebook.initialized', handler);
500
569
  }
501
570
  async reconnect() {
571
+ const socket = this.realtimeSocket();
502
572
  const whenConnected = this.whenConnected();
503
573
  // Use the socket's reconnect method which preserves listeners
504
574
  // and uses the underlying ReconnectingWebSocket mechanism
505
- this.socket.reconnect();
575
+ socket.reconnect();
506
576
  // Reset initialization state
507
577
  this.initialized = false;
508
578
  // Wait for the socket to reconnect
@@ -517,6 +587,12 @@ export class NotebookInstance {
517
587
  iframe.src = result.data.previewUrl;
518
588
  return createBeacon(iframe, options);
519
589
  }
590
+ realtimeSocket() {
591
+ if (this.socket === null) {
592
+ throw new UnsupportedRuntimeTransportError('This operation requires the realtime transport.');
593
+ }
594
+ return this.socket;
595
+ }
520
596
  }
521
597
  _NotebookInstance_initPromise = new WeakMap(), _NotebookInstance_instances = new WeakSet(), _NotebookInstance_init = function _NotebookInstance_init() {
522
598
  __classPrivateFieldSet(this, _NotebookInstance_initPromise, new Promise((resolve, reject) => {
@@ -0,0 +1,20 @@
1
+ import type { CallOption, Invokable, NotebookData, NotebookInitSuccessResult } from '../index.js';
2
+ type RestRuntimeAction = keyof Invokable | 'runtime.ready';
3
+ export declare class RestRuntimeInvoker {
4
+ private readonly fetch;
5
+ private readonly refresh?;
6
+ private actionUrl;
7
+ private ticket;
8
+ private refreshPromise;
9
+ private readonly notebookId;
10
+ constructor(data: NotebookData, fetch: typeof globalThis.fetch, refresh?: () => Promise<string>);
11
+ private configure;
12
+ ready(): Promise<NotebookInitSuccessResult>;
13
+ supports(action: string): action is RestRuntimeAction;
14
+ invoke<T extends keyof Invokable>(action: T, data?: Invokable[T]['args'], options?: CallOption): Promise<Invokable[T]['response']>;
15
+ send(): never;
16
+ private invokeRaw;
17
+ private request;
18
+ private refreshAuthorization;
19
+ }
20
+ export {};