@phpsandbox/sdk 0.1.20 → 0.2.0

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.
@@ -36,8 +36,8 @@ export interface HttpResponseDiagnostics {
36
36
  }
37
37
  export declare class TransportError<TCode extends TransportErrorCode = TransportErrorCode> extends PHPSandboxError {
38
38
  readonly code: TCode;
39
- readonly response?: HttpResponseDiagnostics;
40
- constructor(message: string, code: TCode, cause?: unknown, response?: HttpResponseDiagnostics);
39
+ readonly response?: HttpResponseDiagnostics | undefined;
40
+ constructor(message: string, code: TCode, cause?: unknown, response?: HttpResponseDiagnostics | undefined);
41
41
  static is(error: unknown): error is TransportError;
42
42
  static is<TCode extends TransportErrorCode>(error: unknown, code: TCode): error is TransportError<TCode>;
43
43
  is<TExpectedCode extends TransportErrorCode>(code: TExpectedCode): this is this & TransportError<TExpectedCode>;
@@ -1,23 +1,29 @@
1
- import * as MittModule from 'mitt';
1
+ import mitt from 'mitt';
2
2
  import type { EventHandlerMap, EventType, Handler } from 'mitt';
3
3
  import { Disposable } from '../types.js';
4
- export interface EventDispatcher {
5
- listen: (event: EventType, callback: Handler<unknown>, context?: unknown) => Disposable;
6
- once: (event: EventType, callback: Handler<unknown>, context?: unknown) => Disposable;
7
- emit: (event: EventType, ...args: unknown[]) => void;
8
- removeListener: (event?: EventType, callbackSignature?: Handler<unknown>) => void;
4
+ export interface EventDispatcher<Events extends Record<EventType, unknown> = Record<EventType, unknown>> {
5
+ listen: <Key extends keyof Events>(event: Key, callback: Handler<Events[Key]>, context?: unknown) => Disposable;
6
+ once: <Key extends keyof Events>(event: Key, callback: Handler<Events[Key]>, context?: unknown) => Disposable;
7
+ emit: {
8
+ <Key extends keyof Events>(event: Key, payload: Events[Key]): void;
9
+ <Key extends keyof Events>(event: undefined extends Events[Key] ? Key : never): void;
10
+ };
11
+ removeListener: <Key extends keyof Events>(event?: Key, callbackSignature?: Handler<Events[Key]>) => void;
9
12
  }
10
- export declare function mittWithOnce<Events extends Record<EventType, unknown>>(all?: EventHandlerMap<Events>): MittModule.Emitter<Events> & {
11
- once<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>): Disposable;
13
+ export declare function mittWithOnce<Events extends Record<EventType, unknown>>(all?: EventHandlerMap<Events>): mitt.Emitter<Events> & {
14
+ once: <Key extends keyof Events>(type: Key, fn: Handler<Events[Key]>) => {
15
+ dispose: () => void;
16
+ };
12
17
  };
13
- export default class EventManager implements EventDispatcher {
18
+ export default class EventManager<Events extends Record<EventType, unknown> = Record<EventType, unknown>> implements EventDispatcher<Events> {
14
19
  private readonly emitter;
15
- listen(event: EventType, callback: Handler<unknown>): Disposable;
16
- emit(event: EventType, ...data: unknown[]): void;
17
- once(event: EventType, callback: Handler<unknown>): Disposable;
18
- static make(): EventDispatcher;
20
+ listen<Key extends keyof Events>(event: Key, callback: Handler<Events[Key]>): Disposable;
21
+ emit<Key extends keyof Events>(event: Key, payload: Events[Key]): void;
22
+ emit<Key extends keyof Events>(event: undefined extends Events[Key] ? Key : never): void;
23
+ once<Key extends keyof Events>(event: Key, callback: Handler<Events[Key]>): Disposable;
24
+ static make<Events extends Record<EventType, unknown> = Record<EventType, unknown>>(): EventDispatcher<Events>;
19
25
  static refresh(): EventDispatcher;
20
- removeListener(event?: EventType, callbackSignature?: Handler<unknown>): void;
26
+ removeListener<Key extends keyof Events>(event?: Key, callbackSignature?: Handler<Events[Key]>): void;
21
27
  static createInstance(): EventDispatcher;
22
- inspect(): EventHandlerMap<Record<EventType, unknown>>;
28
+ inspect(): EventHandlerMap<Events>;
23
29
  }
@@ -1,8 +1,8 @@
1
- import * as MittModule from 'mitt';
2
- const createMitt = (MittModule.default ?? MittModule);
1
+ import mitt from 'mitt';
2
+ const createMitt = typeof mitt === 'function' ? mitt : mitt.default;
3
3
  export function mittWithOnce(all) {
4
4
  const inst = createMitt(all);
5
- inst.once = (type, fn) => {
5
+ const once = (type, fn) => {
6
6
  const onceHandler = (event) => {
7
7
  inst.off(type, onceHandler);
8
8
  fn(event);
@@ -14,7 +14,7 @@ export function mittWithOnce(all) {
14
14
  },
15
15
  };
16
16
  };
17
- return inst;
17
+ return Object.assign(inst, { once });
18
18
  }
19
19
  export default class EventManager {
20
20
  constructor() {
@@ -28,7 +28,7 @@ export default class EventManager {
28
28
  return { dispose };
29
29
  }
30
30
  emit(event, ...data) {
31
- this.emitter.emit(event, data.length > 1 ? data : data[0]);
31
+ this.emitter.emit(event, data[0]);
32
32
  }
33
33
  once(event, callback) {
34
34
  return this.emitter.once(event, callback);
@@ -40,6 +40,10 @@ export default class EventManager {
40
40
  return new EventManager();
41
41
  }
42
42
  removeListener(event, callbackSignature) {
43
+ if (event === undefined) {
44
+ this.emitter.all.clear();
45
+ return;
46
+ }
43
47
  this.emitter.off(event, callbackSignature);
44
48
  }
45
49
  static createInstance() {
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { NotebookPublication } from './publication/index.js';
1
2
  import { Filesystem, FilesystemActions, FilesystemEvents } from './filesystem.js';
2
3
  import Terminals, { TerminalEvents, TerminalActions, type SpawnOptions } from './terminal.js';
3
4
  import Auth, { AuthActions } from './auth.js';
@@ -12,13 +13,13 @@ import Config, { ConfigActions } from './config.js';
12
13
  import Git, { GitActions, type GitSyncAuthor } from './git.js';
13
14
  import { Disposable } from './types.js';
14
15
  import { RemoteError } from './errors/index.js';
15
- import { PublicationApi, PublicationInstance, PublicationRun, type PublishInput } from './publications.js';
16
+ import { PublicationApi } from './publication/index.js';
16
17
  import { ServerApi } from './servers.js';
17
18
  import { Feedback } from './feedback.js';
18
19
  import { IntegrationApi, SandboxIntegrationApi } from './integrations/index.js';
19
20
  export type { Disposable } from './types.js';
20
21
  export { LspConnection } from './lsp.js';
21
- export { FileChangeFilter, FileChangeType, FileType, } from './filesystem.js';
22
+ export { FileChangeFilter, FileChangeType, FileType } from './filesystem.js';
22
23
  export type { FileChange, FileContents, FileDeleteOptions, FileDownloadOptions, FileFollowOptions, FileLineRange, FileLineRangeResult, FileListOptions, FileOverwriteOptions, FileResult, FileSearchOptions, FileTailOptions, FileWriteOptions, Filesystem, Stats, TextSearchMatch, TextSearchOptions, TextSearchQuery, TextSearchResult, WatchOptions, } from './filesystem.js';
23
24
  export type { PortInfo } from './runtime.js';
24
25
  export type { RuntimeLogEntry } from './log.js';
@@ -30,9 +31,9 @@ export type { GitChangedFile, GitCheckoutResult, GitConflict, GitConflictsResult
30
31
  export { notebookBuiltinServices, notebookKnownServices } from './services.js';
31
32
  export type { NotebookBuiltinServiceName, NotebookKnownServiceName, NotebookServiceCommand, NotebookServiceKind, NotebookServiceName, NotebookServiceSource, NotebookServiceState, NotebookServiceStatus, ServiceLogOptions, ServiceLogStreamOptions, } from './services.js';
32
33
  export type { ConfigPortMapping, ProjectConfig } from './config.js';
33
- export { PHPSandboxError, RemoteError, TransportError, } from './errors/index.js';
34
+ export { PHPSandboxError, RemoteError, TransportError } from './errors/index.js';
34
35
  export type { HttpResponseDiagnostics, RemoteErrorCode, RemoteErrorSource, TransportErrorCode, ValidationErrorDetails, } from './errors/index.js';
35
- export type { BuiltInPublicationProviderName, CloudflareContainersProviderOptions, LaravelCloudProviderData, LaravelCloudProviderInput, LaravelCloudRegion, PublicationBuildData, PublicationBuildStatus, PublicationData, PublicationDomainData, PublicationDomains, PublicationEventData, PublicationInstance, PublicationJurisdiction, PublicationLogChunkData, PublicationLogStream, PublicationPlacement, PublicationProtectionData, PublicationProtectionInput, PublicationProtectionMode, PublicationProtectionSessionData, PublicationProvider, PublicationProviderData, PublicationProviderInput, PublicationProviderInputs, PublicationProviderName, PublicationRegion, PublicationReleaseData, PublicationReleaseStatus, PublicationRun, PublicationSize, PublicationStatus, PublishInput, PublishStreamEvent, PublishStreamLog, PublishStreamPhase, PublishStreamResult, PublishStreamSseEvent, SshServerProviderOptions, UpdatePublicationProtectionInput, } from './publications.js';
36
+ export * from './publication/index.js';
36
37
  export type { CreateServerInput, ServerData, ServerInstance, ServerListOptions, ServerSshOptions } from './servers.js';
37
38
  export type { ConfigureFeedbackInput, FeedbackData, FeedbackListOptions, FeedbackSource, FeedbackStatus, FeedbackType, FeedbackWidgetConfig, FeedbackWidgetTarget, SubmitFeedbackInput, UpdateFeedbackInput, } from './feedback.js';
38
39
  export type { Integration, IntegrationAuthorization, IntegrationAuthorizationType, IntegrationData, IntegrationProvider, IntegrationReference, LinkIntegrationInput, UpdateIntegrationInput, AttachSandboxIntegrationInput, SandboxIntegration, SandboxIntegrationData, SandboxIntegrationEffect, SandboxIntegrationReference, UpdateSandboxIntegrationInput, } from './integrations/index.js';
@@ -340,8 +341,8 @@ declare class ClientImplementation {
340
341
  post<T extends unknown>(path: string, body?: unknown): Promise<{
341
342
  data: T;
342
343
  }>;
343
- delete<T extends unknown>(path: string): Promise<{
344
- data: T;
344
+ delete<T>(path: string): Promise<{
345
+ data: T | undefined;
345
346
  }>;
346
347
  put<T extends unknown>(path: string, body?: unknown): Promise<{
347
348
  data: T;
@@ -351,6 +352,7 @@ declare class ClientImplementation {
351
352
  }>;
352
353
  stream(path: string): Promise<ReadableStream<Uint8Array>>;
353
354
  sse(url: string, signal?: AbortSignal): Promise<ReadableStream<Uint8Array>>;
355
+ private requireData;
354
356
  private makeRequest;
355
357
  private fetchResponse;
356
358
  }
@@ -399,6 +401,7 @@ export declare class NotebookInstance {
399
401
  readonly preview: NotebookPreview;
400
402
  readonly mail: NotebookMail;
401
403
  readonly feedback: Feedback;
404
+ readonly publication: NotebookPublication;
402
405
  private readonly socket;
403
406
  private readonly restInvoker;
404
407
  private readonly emitter;
@@ -410,8 +413,6 @@ export declare class NotebookInstance {
410
413
  /** Update sandbox metadata and Git defaults without restarting it. */
411
414
  update(input: UpdateNotebookInput): Promise<NotebookData>;
412
415
  fork(input?: Partial<ForkNotebookInput>): Promise<NotebookInstance>;
413
- publication(): Promise<PublicationInstance | null>;
414
- publish(input?: PublishInput): Promise<PublicationRun>;
415
416
  run(command: string | string[], opts?: SpawnOptions): ShellProcess;
416
417
  run(command: string, args: string[], opts?: SpawnOptions): ShellProcess;
417
418
  exec(command: string | string[], opts?: SpawnOptions): Promise<ProcessResult>;
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
10
10
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
11
  };
12
12
  var _NotebookInstance_instances, _NotebookInstance_initPromise, _NotebookInstance_init;
13
+ import { NotebookPublication } from './publication/index.js';
13
14
  import { Filesystem } from './filesystem.js';
14
15
  import Terminals from './terminal.js';
15
16
  import Auth from './auth.js';
@@ -23,17 +24,18 @@ import Config from './config.js';
23
24
  import { Transport } from './socket/index.js';
24
25
  import EventManager from './events/index.js';
25
26
  import Git from './git.js';
26
- import { PHPSandboxError, RemoteError, TransportError, remoteError, } from './errors/index.js';
27
- import { PublicationApi, PublicationInstance, PublicationRun, } from './publications.js';
27
+ import { PHPSandboxError, RemoteError, TransportError, remoteError } from './errors/index.js';
28
+ import { PublicationApi } from './publication/index.js';
28
29
  import { ServerApi } from './servers.js';
29
30
  import { RestRuntimeInvoker } from './runtime/rest.js';
30
31
  import { Feedback } from './feedback.js';
31
32
  import { IntegrationApi, SandboxIntegrationApi } from './integrations/index.js';
32
33
  import { authenticatedRequest } from './http.js';
33
34
  export { LspConnection } from './lsp.js';
34
- export { FileChangeFilter, FileChangeType, FileType, } from './filesystem.js';
35
+ export { FileChangeFilter, FileChangeType, FileType } from './filesystem.js';
35
36
  export { notebookBuiltinServices, notebookKnownServices } from './services.js';
36
- export { PHPSandboxError, RemoteError, TransportError, } from './errors/index.js';
37
+ export { PHPSandboxError, RemoteError, TransportError } from './errors/index.js';
38
+ export * from './publication/index.js';
37
39
  function normalizeNotebookSecretInputs(input) {
38
40
  if (Array.isArray(input)) {
39
41
  return input;
@@ -91,25 +93,25 @@ class ClientImplementation {
91
93
  this.fetch = options.fetch;
92
94
  }
93
95
  this.headers = {
94
- 'Accept': 'application/json',
95
- 'Authorization': `Bearer ${token}`,
96
+ Accept: 'application/json',
97
+ Authorization: `Bearer ${token}`,
96
98
  'Content-Type': 'application/json',
97
99
  };
98
100
  }
99
101
  get(path) {
100
- return this.makeRequest('GET', path);
102
+ return this.requireData(this.makeRequest('GET', path));
101
103
  }
102
104
  post(path, body) {
103
- return this.makeRequest('POST', path, { body: body === undefined ? undefined : JSON.stringify(body) });
105
+ return this.requireData(this.makeRequest('POST', path, { body: body === undefined ? undefined : JSON.stringify(body) }));
104
106
  }
105
107
  delete(path) {
106
108
  return this.makeRequest('DELETE', path);
107
109
  }
108
110
  put(path, body) {
109
- return this.makeRequest('PUT', path, { body: body === undefined ? undefined : JSON.stringify(body) });
111
+ return this.requireData(this.makeRequest('PUT', path, { body: body === undefined ? undefined : JSON.stringify(body) }));
110
112
  }
111
113
  patch(path, body) {
112
- return this.makeRequest('PATCH', path, { body: body === undefined ? undefined : JSON.stringify(body) });
114
+ return this.requireData(this.makeRequest('PATCH', path, { body: body === undefined ? undefined : JSON.stringify(body) }));
113
115
  }
114
116
  async stream(path) {
115
117
  const response = await this.fetchResponse(authenticatedRequest(new URL(path.replace(/^\//, ''), this.baseUrl), {
@@ -129,7 +131,7 @@ class ClientImplementation {
129
131
  method: 'GET',
130
132
  headers: {
131
133
  ...this.headers,
132
- 'Accept': 'text/event-stream',
134
+ Accept: 'text/event-stream',
133
135
  },
134
136
  ...(signal === undefined ? {} : { signal }),
135
137
  }));
@@ -141,6 +143,13 @@ class ClientImplementation {
141
143
  }
142
144
  return response.body;
143
145
  }
146
+ async requireData(request) {
147
+ const response = await request;
148
+ if (response.data === undefined) {
149
+ throw new TransportError('PHPSandbox API returned no data for a data request.', 'InvalidResponse');
150
+ }
151
+ return { ...response, data: response.data };
152
+ }
144
153
  async makeRequest(method, path, init) {
145
154
  const response = await this.fetchResponse(authenticatedRequest(new URL(path.replace(/^\//, ''), this.baseUrl), {
146
155
  method,
@@ -159,7 +168,10 @@ class ClientImplementation {
159
168
  }
160
169
  try {
161
170
  const payload = JSON.parse(body);
162
- if (payload === null || typeof payload !== 'object' || Array.isArray(payload) || !Object.prototype.hasOwnProperty.call(payload, 'data')) {
171
+ if (payload === null ||
172
+ typeof payload !== 'object' ||
173
+ Array.isArray(payload) ||
174
+ !Object.prototype.hasOwnProperty.call(payload, 'data')) {
163
175
  throw new TransportError('PHPSandbox API returned an invalid success response.', 'InvalidResponse', response);
164
176
  }
165
177
  return payload;
@@ -234,9 +246,7 @@ export class NotebookInstance {
234
246
  this.emitter = EventManager.createInstance();
235
247
  if (client.runtimeTransport === 'rest') {
236
248
  this.socket = null;
237
- this.restInvoker = new RestRuntimeInvoker(data, client.options.fetch ?? globalThis.fetch, runtimeUrlProvider === undefined
238
- ? undefined
239
- : () => runtimeUrlProvider(data.id));
249
+ this.restInvoker = new RestRuntimeInvoker(data, client.options.fetch ?? globalThis.fetch, runtimeUrlProvider === undefined ? undefined : () => runtimeUrlProvider(data.id));
240
250
  }
241
251
  else {
242
252
  this.restInvoker = null;
@@ -273,6 +283,7 @@ export class NotebookInstance {
273
283
  this.preview = new NotebookPreview(client, this.data.id);
274
284
  this.mail = new NotebookMail(client, this.data.id);
275
285
  this.feedback = new Feedback(client, this.data.id);
286
+ this.publication = new NotebookPublication(this.data.id, client);
276
287
  }
277
288
  async ready() {
278
289
  if (this.restInvoker !== null) {
@@ -307,24 +318,6 @@ export class NotebookInstance {
307
318
  const response = await this.client.post(`/notebook/${this.data.id}/fork`, input);
308
319
  return new NotebookInstance(response.data, this.client);
309
320
  }
310
- async publication() {
311
- try {
312
- const response = await this.client.get(`/notebook/${this.data.id}/publication`);
313
- return new PublicationInstance(response.data, this.client, this.data.id);
314
- }
315
- catch (error) {
316
- if (RemoteError.is(error) && error.status === 404) {
317
- return null;
318
- }
319
- throw error;
320
- }
321
- }
322
- async publish(input) {
323
- const response = await this.client.post(`/notebook/${this.data.id}/publication`, input);
324
- const run = new PublicationRun(new PublicationInstance(response.data, this.client, this.data.id), this.client);
325
- run.start();
326
- return run;
327
- }
328
321
  run(command, argsOrOpts = [], opts) {
329
322
  if (Array.isArray(argsOrOpts)) {
330
323
  return this.shell.run(command, argsOrOpts, opts);
@@ -447,7 +440,11 @@ export class NotebookInstance {
447
440
  return disposable;
448
441
  }
449
442
  onDidBootError(handler) {
450
- const disposable = this.realtimeSocket().listen('okra.boot_error', handler);
443
+ const disposable = this.realtimeSocket().listen('okra.boot_error', (error) => {
444
+ if (RemoteError.is(error, 'NotebookUnavailable')) {
445
+ handler(error);
446
+ }
447
+ });
451
448
  this.disposables.push(disposable);
452
449
  return disposable;
453
450
  }
@@ -0,0 +1,17 @@
1
+ import type { Client } from '../index.js';
2
+ import type { PublicationDomainData } from './types.js';
3
+ export declare class PublicationDomains {
4
+ private readonly client;
5
+ private readonly publicationId;
6
+ constructor(client: Client, publicationId: string);
7
+ list(): Promise<PublicationDomainData[]>;
8
+ create(hostname: string): Promise<PublicationDomainData>;
9
+ refresh(id: string): Promise<PublicationDomainData>;
10
+ /** Sets production APP_URL. Publish again to apply it to the running app. */
11
+ useAsApplicationUrl(id: string): Promise<{
12
+ url: string;
13
+ requiresPublication: boolean;
14
+ }>;
15
+ delete(id: string): Promise<void>;
16
+ private path;
17
+ }
@@ -0,0 +1,25 @@
1
+ export class PublicationDomains {
2
+ constructor(client, publicationId) {
3
+ this.client = client;
4
+ this.publicationId = publicationId;
5
+ }
6
+ async list() {
7
+ return (await this.client.get(this.path())).data;
8
+ }
9
+ async create(hostname) {
10
+ return (await this.client.post(this.path(), { hostname })).data;
11
+ }
12
+ async refresh(id) {
13
+ return (await this.client.post(`${this.path()}/${encodeURIComponent(id)}/refresh`)).data;
14
+ }
15
+ /** Sets production APP_URL. Publish again to apply it to the running app. */
16
+ async useAsApplicationUrl(id) {
17
+ return (await this.client.post(`${this.path()}/${encodeURIComponent(id)}/application-url`)).data;
18
+ }
19
+ async delete(id) {
20
+ await this.client.delete(`${this.path()}/${encodeURIComponent(id)}`);
21
+ }
22
+ path() {
23
+ return `/publications/${encodeURIComponent(this.publicationId)}/domains`;
24
+ }
25
+ }
@@ -0,0 +1,8 @@
1
+ export { NotebookPublication } from './notebook.js';
2
+ export { PublicationApi, PublicationInstance } from './instance.js';
3
+ export { PublicationRun } from './run.js';
4
+ export type * from './types.js';
5
+ export type { BuiltInPublicationProviderName, PublicationStatus, PublicationBuildStatus, PublicationReleaseStatus, PublicationSize, PublicationRegion, PublicationJurisdiction, PublicationProtectionMode, PublicationLogStream, PublicationEventData, PublicationLogChunkData, PublishStreamPhase, PublishStreamLog, PublishStreamResult, PublishStreamEvent, PublishStreamSseEvent, } from '../schemas/publications.js';
6
+ export { laravelCloudSetupForRepublish } from './providers/laravel-cloud.js';
7
+ export { PublicationResources } from './resources.js';
8
+ export { PublicationDomains } from './domains.js';
@@ -0,0 +1,6 @@
1
+ export { NotebookPublication } from './notebook.js';
2
+ export { PublicationApi, PublicationInstance } from './instance.js';
3
+ export { PublicationRun } from './run.js';
4
+ export { laravelCloudSetupForRepublish } from './providers/laravel-cloud.js';
5
+ export { PublicationResources } from './resources.js';
6
+ export { PublicationDomains } from './domains.js';
@@ -0,0 +1,42 @@
1
+ import type { Client } from '../index.js';
2
+ import type { PublicationEventData, PublicationLogChunkData } from '../schemas/publications.js';
3
+ import type { PublicationProtectionData, LaravelCloudSetupInput, PublicationProviderName, PublicationData, UpdatePublicationProtectionInput, PublicationProtectionSessionData, PublicationDestroyOptions } from './types.js';
4
+ import { PublicationRun } from './run.js';
5
+ import { PublicationResources } from './resources.js';
6
+ import { PublicationDomains } from './domains.js';
7
+ export declare class PublicationApi {
8
+ private readonly client;
9
+ readonly resources: PublicationResources;
10
+ constructor(client: Client);
11
+ get<TProvider extends PublicationProviderName = PublicationProviderName>(id: string): Promise<PublicationInstance<TProvider>>;
12
+ }
13
+ export declare class PublicationInstance<TProvider extends PublicationProviderName = PublicationProviderName> {
14
+ readonly data: PublicationData<TProvider>;
15
+ private readonly client;
16
+ private readonly notebookId?;
17
+ readonly domains: PublicationDomains;
18
+ constructor(data: PublicationData<TProvider>, client: Client, notebookId?: string | undefined);
19
+ reconcileLaravelCloudResource(resourceId: string): Promise<PublicationInstance<TProvider>>;
20
+ configureLaravelCloud(setup: LaravelCloudSetupInput): Promise<PublicationInstance<TProvider>>;
21
+ refresh(): Promise<PublicationInstance<TProvider>>;
22
+ /** Start or reattach to the current release stream without queuing another release. */
23
+ follow(): PublicationRun<TProvider>;
24
+ publish(): Promise<PublicationRun<TProvider>>;
25
+ destroy(options?: PublicationDestroyOptions): Promise<void>;
26
+ wait(options?: {
27
+ intervalMs?: number;
28
+ timeoutMs?: number;
29
+ }): Promise<PublicationInstance<TProvider>>;
30
+ events(): Promise<ReadableStream<PublicationEventData>>;
31
+ buildLogs(buildId?: string): Promise<ReadableStream<PublicationLogChunkData>>;
32
+ logSnapshot(stream: 'build' | 'runtime', limit?: number): Promise<{
33
+ stream: 'build' | 'runtime';
34
+ content: string;
35
+ truncated: boolean;
36
+ }>;
37
+ logs(): Promise<ReadableStream<PublicationLogChunkData>>;
38
+ setProtection(input: UpdatePublicationProtectionInput): Promise<PublicationInstance<TProvider>>;
39
+ protection(): Promise<PublicationProtectionData>;
40
+ createProtectionSession(): Promise<PublicationProtectionSessionData>;
41
+ disableProtection(): Promise<PublicationInstance<TProvider>>;
42
+ }
@@ -0,0 +1,133 @@
1
+ import { PublicationEventDataSchema, PublicationLogChunkDataSchema } from '../schemas/publications.js';
2
+ import { PHPSandboxError, TransportError } from '../errors/index.js';
3
+ import { PublicationRun } from './run.js';
4
+ import { PublicationResources } from './resources.js';
5
+ import { PublicationDomains } from './domains.js';
6
+ export class PublicationApi {
7
+ constructor(client) {
8
+ this.client = client;
9
+ this.resources = new PublicationResources(client);
10
+ }
11
+ async get(id) {
12
+ const response = await this.client.get(`/publications/${encodeURIComponent(id)}`);
13
+ return new PublicationInstance(response.data, this.client);
14
+ }
15
+ }
16
+ export class PublicationInstance {
17
+ constructor(data, client, notebookId) {
18
+ this.data = data;
19
+ this.client = client;
20
+ this.notebookId = notebookId;
21
+ this.domains = new PublicationDomains(client, data.id);
22
+ }
23
+ async reconcileLaravelCloudResource(resourceId) {
24
+ const response = await this.client.post(`/publications/${encodeURIComponent(this.data.id)}/laravel-cloud/reconcile`, { resourceId });
25
+ return new PublicationInstance(response.data, this.client, this.notebookId);
26
+ }
27
+ async configureLaravelCloud(setup) {
28
+ const response = await this.client.put(`/publications/${encodeURIComponent(this.data.id)}/laravel-cloud/setup`, { setup });
29
+ return new PublicationInstance(response.data, this.client, this.notebookId);
30
+ }
31
+ async refresh() {
32
+ const response = await this.client.get(`/publications/${encodeURIComponent(this.data.id)}`);
33
+ return new PublicationInstance(response.data, this.client, this.notebookId);
34
+ }
35
+ /** Start or reattach to the current release stream without queuing another release. */
36
+ follow() {
37
+ return new PublicationRun(this, this.client).start();
38
+ }
39
+ async publish() {
40
+ if (!this.notebookId) {
41
+ throw new PHPSandboxError('Publishing requires a notebook-scoped publication instance.');
42
+ }
43
+ const response = await this.client.post(`/notebook/${encodeURIComponent(this.notebookId)}/publication`);
44
+ const run = new PublicationRun(new PublicationInstance(response.data, this.client, this.notebookId), this.client);
45
+ run.start();
46
+ return run;
47
+ }
48
+ async destroy(options = {}) {
49
+ const query = options.deleteResources ? '?deleteResources=1' : '';
50
+ await this.client.delete(`/publications/${encodeURIComponent(this.data.id)}${query}`);
51
+ }
52
+ async wait(options = {}) {
53
+ const intervalMs = options.intervalMs ?? 1000;
54
+ const timeoutMs = options.timeoutMs ?? 300000;
55
+ const startedAt = Date.now();
56
+ let current = this;
57
+ while (!publicationStatusIsTerminal(current.data.status)) {
58
+ if (Date.now() - startedAt > timeoutMs) {
59
+ throw new PHPSandboxError(`Timed out waiting for publication ${this.data.id}`);
60
+ }
61
+ await delay(intervalMs);
62
+ current = await current.refresh();
63
+ }
64
+ return current;
65
+ }
66
+ async events() {
67
+ return parseNdjsonStream(await this.client.stream(`/publications/${encodeURIComponent(this.data.id)}/events/stream`), PublicationEventDataSchema);
68
+ }
69
+ buildLogs(buildId = this.data.latestBuild?.id ?? '') {
70
+ if (buildId === '') {
71
+ return Promise.reject(new PHPSandboxError('Publication has no latest build id'));
72
+ }
73
+ return this.client
74
+ .stream(`/publications/${encodeURIComponent(this.data.id)}/builds/${encodeURIComponent(buildId)}/logs/stream`)
75
+ .then((stream) => parseNdjsonStream(stream, PublicationLogChunkDataSchema));
76
+ }
77
+ async logSnapshot(stream, limit = 50) {
78
+ const query = new URLSearchParams({ stream, limit: String(limit) });
79
+ return (await this.client.get(`/publications/${encodeURIComponent(this.data.id)}/logs?${query}`)).data;
80
+ }
81
+ async logs() {
82
+ return parseNdjsonStream(await this.client.stream(`/publications/${encodeURIComponent(this.data.id)}/logs/stream`), PublicationLogChunkDataSchema);
83
+ }
84
+ async setProtection(input) {
85
+ const response = await this.client.put(`/publications/${encodeURIComponent(this.data.id)}/protection`, input);
86
+ return new PublicationInstance(response.data, this.client, this.notebookId);
87
+ }
88
+ async protection() {
89
+ const response = await this.client.get(`/publications/${encodeURIComponent(this.data.id)}/protection`);
90
+ return response.data;
91
+ }
92
+ async createProtectionSession() {
93
+ const response = await this.client.post(`/publications/${encodeURIComponent(this.data.id)}/protection/session`);
94
+ return response.data;
95
+ }
96
+ async disableProtection() {
97
+ const response = await this.client.delete(`/publications/${encodeURIComponent(this.data.id)}/protection`);
98
+ if (!response.data) {
99
+ throw new TransportError('Protection response has no publication data.', 'InvalidResponse');
100
+ }
101
+ return new PublicationInstance(response.data, this.client, this.notebookId);
102
+ }
103
+ }
104
+ function publicationStatusIsTerminal(status) {
105
+ return ['healthy', 'failed', 'stopped', 'deleted', 'delete_failed'].includes(status);
106
+ }
107
+ function delay(ms) {
108
+ return new Promise((resolve) => setTimeout(resolve, ms));
109
+ }
110
+ function parseNdjsonStream(input, schema) {
111
+ const decoder = new TextDecoder();
112
+ let buffer = '';
113
+ return input.pipeThrough(new TransformStream({
114
+ transform(chunk, controller) {
115
+ buffer += decoder.decode(chunk, { stream: true });
116
+ const lines = buffer.split('\n');
117
+ buffer = lines.pop() ?? '';
118
+ for (const line of lines) {
119
+ const trimmed = line.trim();
120
+ if (trimmed !== '') {
121
+ controller.enqueue(schema.parse(JSON.parse(trimmed)));
122
+ }
123
+ }
124
+ },
125
+ flush(controller) {
126
+ buffer += decoder.decode();
127
+ const trimmed = buffer.trim();
128
+ if (trimmed !== '') {
129
+ controller.enqueue(schema.parse(JSON.parse(trimmed)));
130
+ }
131
+ },
132
+ }));
133
+ }
@@ -0,0 +1,20 @@
1
+ import type { Client } from '../index.js';
2
+ import { PublicationInstance } from './instance.js';
3
+ import { PublicationRun } from './run.js';
4
+ import { PublicationResources } from './resources.js';
5
+ import type { PlannedPublishInput, PublicationPlan, PublicationPlanInput, PublicationProviderName, PublicationReadiness, PublicationResourceCatalog } from './types.js';
6
+ export declare class NotebookPublication {
7
+ private readonly notebookId;
8
+ private readonly client;
9
+ readonly resources: PublicationResources;
10
+ constructor(notebookId: string, client: Client);
11
+ private get path();
12
+ current(): Promise<PublicationInstance | null>;
13
+ readiness(): Promise<PublicationReadiness>;
14
+ /** Review a publishing plan without committing changes or creating resources. */
15
+ prepare<TName extends PublicationProviderName>(input: PublicationPlanInput<TName>): Promise<PublicationPlan<TName>>;
16
+ catalog(provider: 'laravel-cloud' | 'cloudflare-containers'): Promise<PublicationResourceCatalog>;
17
+ catalog(provider: 'ssh-server', serverId: string): Promise<PublicationResourceCatalog>;
18
+ /** Core validates the plan, prepares source and executes publishing. */
19
+ publish(input?: PlannedPublishInput): Promise<PublicationRun>;
20
+ }
@@ -0,0 +1,46 @@
1
+ import { RemoteError } from '../errors/index.js';
2
+ import { PublicationInstance } from './instance.js';
3
+ import { PublicationRun } from './run.js';
4
+ import { PublicationResources } from './resources.js';
5
+ export class NotebookPublication {
6
+ constructor(notebookId, client) {
7
+ this.notebookId = notebookId;
8
+ this.client = client;
9
+ this.resources = new PublicationResources(client, `${this.path}/publication/resources`);
10
+ }
11
+ get path() {
12
+ return `/notebook/${encodeURIComponent(this.notebookId)}`;
13
+ }
14
+ async current() {
15
+ try {
16
+ const response = await this.client.get(`${this.path}/publication`);
17
+ return new PublicationInstance(response.data, this.client, this.notebookId);
18
+ }
19
+ catch (error) {
20
+ if (RemoteError.is(error) && error.status === 404) {
21
+ return null;
22
+ }
23
+ throw error;
24
+ }
25
+ }
26
+ async readiness() {
27
+ return (await this.client.get(`${this.path}/publication/readiness`)).data;
28
+ }
29
+ /** Review a publishing plan without committing changes or creating resources. */
30
+ async prepare(input) {
31
+ return (await this.client.post(`${this.path}/publication/plan`, input)).data;
32
+ }
33
+ async catalog(provider, serverId) {
34
+ const query = new URLSearchParams({ provider });
35
+ if (serverId !== undefined) {
36
+ query.set('serverId', serverId);
37
+ }
38
+ return (await this.client.get(`${this.path}/publication/catalog?${query}`))
39
+ .data;
40
+ }
41
+ /** Core validates the plan, prepares source and executes publishing. */
42
+ async publish(input) {
43
+ const response = await this.client.post(`${this.path}/publication`, input);
44
+ return new PublicationRun(new PublicationInstance(response.data, this.client, this.notebookId), this.client).start();
45
+ }
46
+ }