@phpsandbox/sdk 0.0.53 → 0.0.55

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.
@@ -0,0 +1,191 @@
1
+ import type { Client, PaginatedApiResponse } from './index.js';
2
+ export type DeploymentStrategy = 'auto' | 'laravel' | 'phpsandbox' | 'compose' | 'custom';
3
+ export type DeploymentTargetProvider = 'okra' | 'cloudflare-containers';
4
+ export type DeploymentRuntimeProvider = 'docker' | 'compose' | 'cloudflare-container';
5
+ export type DeploymentStatus = 'pending' | 'queued' | 'building' | 'deploying' | 'healthy' | 'failed' | 'stopped' | 'deleting' | 'deleted' | 'delete_failed';
6
+ export type DeploymentSize = 'nano' | 'small' | 'medium' | 'large' | 'lite' | 'basic' | 'standard-1' | 'standard-2';
7
+ export interface DeploymentPlacement {
8
+ regions?: Array<'ENAM' | 'WNAM' | 'EEUR' | 'WEUR' | 'APAC' | 'SAM' | 'ME' | 'OC' | 'AFR' | string>;
9
+ jurisdiction?: 'eu' | 'fedramp' | string;
10
+ }
11
+ export interface DeployInput {
12
+ name?: string;
13
+ slug?: string;
14
+ strategy?: DeploymentStrategy;
15
+ target_provider?: DeploymentTargetProvider;
16
+ runtime_provider?: DeploymentRuntimeProvider;
17
+ size?: DeploymentSize;
18
+ sleep_after?: string;
19
+ placement?: DeploymentPlacement;
20
+ instances?: number;
21
+ source_ref?: string;
22
+ password?: string;
23
+ feedback_enabled?: boolean;
24
+ metadata?: Record<string, unknown>;
25
+ }
26
+ export interface DeploymentSlugAvailabilityData {
27
+ slug: string;
28
+ available: boolean;
29
+ domain: string;
30
+ }
31
+ export interface DeploymentBuildData {
32
+ id: string;
33
+ status: string;
34
+ source_ref: string | null;
35
+ strategy: DeploymentStrategy | string;
36
+ target_provider: DeploymentTargetProvider | string;
37
+ runtime_provider: DeploymentRuntimeProvider | string | null;
38
+ size: DeploymentSize | string;
39
+ artifact_path: string | null;
40
+ artifact_checksum: string | null;
41
+ error_message: string | null;
42
+ started_at: string | null;
43
+ finished_at: string | null;
44
+ }
45
+ export interface DeploymentReleaseData {
46
+ id: string;
47
+ status: string;
48
+ artifact_path: string;
49
+ artifact_checksum: string;
50
+ manifest: Record<string, unknown>;
51
+ }
52
+ export interface DeploymentDomainDnsData {
53
+ type: 'CNAME' | 'ALIAS' | 'ANAME' | string;
54
+ name: string;
55
+ value: string;
56
+ }
57
+ export interface DeploymentDomainData {
58
+ id: string;
59
+ deployment_id: string;
60
+ hostname: string;
61
+ status: string;
62
+ provider: string;
63
+ provider_hostname_id: string | null;
64
+ dns: DeploymentDomainDnsData;
65
+ ssl: {
66
+ status: string | null;
67
+ };
68
+ validation_errors: unknown[];
69
+ validation_records: unknown[];
70
+ metadata: Record<string, unknown>;
71
+ verified_at: string | null;
72
+ created_at: string | null;
73
+ updated_at: string | null;
74
+ }
75
+ export interface CreateDeploymentDomainInput {
76
+ hostname: string;
77
+ }
78
+ export interface DeploymentData {
79
+ id: string;
80
+ notebook_id: string;
81
+ name: string;
82
+ slug: string;
83
+ url: string;
84
+ status: DeploymentStatus | string;
85
+ strategy: DeploymentStrategy | string;
86
+ target_provider: DeploymentTargetProvider | string;
87
+ runtime_provider: DeploymentRuntimeProvider | string | null;
88
+ size: DeploymentSize | string;
89
+ sleep_after: string;
90
+ placement: DeploymentPlacement | null;
91
+ instances: number;
92
+ origin_url: string | null;
93
+ protection: {
94
+ mode: 'none' | 'password' | string;
95
+ enabled: boolean;
96
+ };
97
+ feedback: {
98
+ enabled: boolean;
99
+ config: Record<string, unknown>;
100
+ };
101
+ current_release?: DeploymentReleaseData | null;
102
+ domains?: DeploymentDomainData[];
103
+ latest_build?: DeploymentBuildData | null;
104
+ deployed_at: string | null;
105
+ created_at: string | null;
106
+ updated_at: string | null;
107
+ }
108
+ export interface DeploymentEventData {
109
+ id?: number | string;
110
+ sequence: number;
111
+ type: string;
112
+ payload: Record<string, unknown>;
113
+ created_at: string | null;
114
+ }
115
+ export interface DeploymentLogChunkData {
116
+ sequence: number;
117
+ stream: 'build' | 'runtime' | string;
118
+ content: string;
119
+ created_at: string | null;
120
+ }
121
+ export interface UpdateDeploymentProtectionInput {
122
+ mode: 'none' | 'password';
123
+ password?: string;
124
+ }
125
+ export interface DeploymentProtectionData {
126
+ mode: 'none' | 'password' | string;
127
+ enabled: boolean;
128
+ token?: string;
129
+ expires_at?: string;
130
+ url?: string;
131
+ }
132
+ export interface DeploymentProtectionSessionData {
133
+ preview_session_id: string;
134
+ token: string;
135
+ expires_at: string;
136
+ url: string;
137
+ }
138
+ export interface UpdateDeploymentFeedbackInput {
139
+ enabled: boolean;
140
+ config?: Record<string, unknown>;
141
+ }
142
+ export declare class NotebookDeploymentApi {
143
+ private readonly client;
144
+ constructor(client: Client);
145
+ create(id: string, input?: DeployInput): Promise<DeploymentInstance>;
146
+ list(id: string): Promise<PaginatedApiResponse<DeploymentData>>;
147
+ slugAvailability(id: string, slug: string): Promise<DeploymentSlugAvailabilityData>;
148
+ }
149
+ export declare class DeploymentApi {
150
+ private readonly client;
151
+ constructor(client: Client);
152
+ get(id: string): Promise<DeploymentInstance>;
153
+ redeploy(id: string, input?: DeployInput): Promise<DeploymentInstance>;
154
+ delete(id: string): Promise<void>;
155
+ setProtection(id: string, input: UpdateDeploymentProtectionInput): Promise<DeploymentInstance>;
156
+ protection(id: string): Promise<DeploymentProtectionData>;
157
+ createProtectionSession(id: string): Promise<DeploymentProtectionSessionData>;
158
+ disableProtection(id: string): Promise<DeploymentInstance>;
159
+ setFeedback(id: string, input: UpdateDeploymentFeedbackInput): Promise<DeploymentInstance>;
160
+ listDomains(id: string): Promise<DeploymentDomainData[]>;
161
+ createDomain(id: string, input: CreateDeploymentDomainInput): Promise<DeploymentDomainData>;
162
+ refreshDomain(id: string, domainId: string): Promise<DeploymentDomainData>;
163
+ deleteDomain(id: string, domainId: string): Promise<void>;
164
+ events(id: string): Promise<ReadableStream<DeploymentEventData>>;
165
+ buildLogs(id: string, buildId: string): Promise<ReadableStream<DeploymentLogChunkData>>;
166
+ logs(id: string): Promise<ReadableStream<DeploymentLogChunkData>>;
167
+ }
168
+ export declare class DeploymentInstance {
169
+ readonly data: DeploymentData;
170
+ private readonly client;
171
+ constructor(data: DeploymentData, client: Client);
172
+ refresh(): Promise<DeploymentInstance>;
173
+ redeploy(input?: DeployInput): Promise<DeploymentInstance>;
174
+ delete(): Promise<void>;
175
+ wait(options?: {
176
+ intervalMs?: number;
177
+ timeoutMs?: number;
178
+ }): Promise<DeploymentInstance>;
179
+ events(): Promise<ReadableStream<DeploymentEventData>>;
180
+ buildLogs(buildId?: string): Promise<ReadableStream<DeploymentLogChunkData>>;
181
+ logs(): Promise<ReadableStream<DeploymentLogChunkData>>;
182
+ setProtection(input: UpdateDeploymentProtectionInput): Promise<DeploymentInstance>;
183
+ protection(): Promise<DeploymentProtectionData>;
184
+ createProtectionSession(): Promise<DeploymentProtectionSessionData>;
185
+ disableProtection(): Promise<DeploymentInstance>;
186
+ setFeedback(input: UpdateDeploymentFeedbackInput): Promise<DeploymentInstance>;
187
+ domains(): Promise<DeploymentDomainData[]>;
188
+ addDomain(input: CreateDeploymentDomainInput): Promise<DeploymentDomainData>;
189
+ refreshDomain(domainId: string): Promise<DeploymentDomainData>;
190
+ deleteDomain(domainId: string): Promise<void>;
191
+ }
@@ -0,0 +1,182 @@
1
+ export class NotebookDeploymentApi {
2
+ constructor(client) {
3
+ this.client = client;
4
+ }
5
+ async create(id, input = {}) {
6
+ const response = await this.client.post(`/notebook/${id}/deployments`, input);
7
+ return new DeploymentInstance(response.data, this.client);
8
+ }
9
+ async list(id) {
10
+ const response = await this.client.get(`/notebook/${id}/deployments`);
11
+ return response.data;
12
+ }
13
+ async slugAvailability(id, slug) {
14
+ const query = new URLSearchParams({ slug }).toString();
15
+ const response = await this.client.get(`/notebook/${id}/deployments/slug-availability?${query}`);
16
+ return response.data;
17
+ }
18
+ }
19
+ export class DeploymentApi {
20
+ constructor(client) {
21
+ this.client = client;
22
+ }
23
+ async get(id) {
24
+ const response = await this.client.get(`/deployments/${id}`);
25
+ return new DeploymentInstance(response.data, this.client);
26
+ }
27
+ async redeploy(id, input = {}) {
28
+ const response = await this.client.post(`/deployments/${id}/redeploy`, input);
29
+ return new DeploymentInstance(response.data, this.client);
30
+ }
31
+ async delete(id) {
32
+ await this.client.delete(`/deployments/${id}`);
33
+ }
34
+ async setProtection(id, input) {
35
+ const response = await this.client.put(`/deployments/${id}/protection`, input);
36
+ return new DeploymentInstance(response.data, this.client);
37
+ }
38
+ async protection(id) {
39
+ const response = await this.client.get(`/deployments/${id}/protection`);
40
+ return response.data;
41
+ }
42
+ async createProtectionSession(id) {
43
+ const response = await this.client.post(`/deployments/${id}/protection/session`);
44
+ return response.data;
45
+ }
46
+ async disableProtection(id) {
47
+ const response = await this.client.delete(`/deployments/${id}/protection`);
48
+ return new DeploymentInstance(response.data, this.client);
49
+ }
50
+ async setFeedback(id, input) {
51
+ const response = await this.client.put(`/deployments/${id}/feedback`, input);
52
+ return new DeploymentInstance(response.data, this.client);
53
+ }
54
+ async listDomains(id) {
55
+ const response = await this.client.get(`/deployments/${id}/domains`);
56
+ return unwrapDeploymentDomainCollection(response.data);
57
+ }
58
+ async createDomain(id, input) {
59
+ const response = await this.client.post(`/deployments/${id}/domains`, input);
60
+ return response.data;
61
+ }
62
+ async refreshDomain(id, domainId) {
63
+ const response = await this.client.post(`/deployments/${id}/domains/${domainId}/refresh`);
64
+ return response.data;
65
+ }
66
+ async deleteDomain(id, domainId) {
67
+ await this.client.delete(`/deployments/${id}/domains/${domainId}`);
68
+ }
69
+ async events(id) {
70
+ return parseNdjsonStream(await this.client.stream(`/deployments/${id}/events/stream`));
71
+ }
72
+ async buildLogs(id, buildId) {
73
+ return parseNdjsonStream(await this.client.stream(`/deployments/${id}/builds/${buildId}/logs/stream`));
74
+ }
75
+ async logs(id) {
76
+ return parseNdjsonStream(await this.client.stream(`/deployments/${id}/logs/stream`));
77
+ }
78
+ }
79
+ export class DeploymentInstance {
80
+ constructor(data, client) {
81
+ this.data = data;
82
+ this.client = client;
83
+ }
84
+ refresh() {
85
+ return this.client.deployments.get(this.data.id);
86
+ }
87
+ redeploy(input = {}) {
88
+ return this.client.deployments.redeploy(this.data.id, input);
89
+ }
90
+ delete() {
91
+ return this.client.deployments.delete(this.data.id);
92
+ }
93
+ async wait(options = {}) {
94
+ const intervalMs = options.intervalMs ?? 1000;
95
+ const timeoutMs = options.timeoutMs ?? 300000;
96
+ const startedAt = Date.now();
97
+ let current = this;
98
+ while (!deploymentStatusIsTerminal(current.data.status)) {
99
+ if (Date.now() - startedAt > timeoutMs) {
100
+ throw new Error(`Timed out waiting for deployment ${this.data.id}`);
101
+ }
102
+ await delay(intervalMs);
103
+ current = await current.refresh();
104
+ }
105
+ return current;
106
+ }
107
+ events() {
108
+ return this.client.deployments.events(this.data.id);
109
+ }
110
+ buildLogs(buildId = this.data.latest_build?.id ?? '') {
111
+ if (buildId === '') {
112
+ return Promise.reject(new Error('Deployment has no latest build id'));
113
+ }
114
+ return this.client.deployments.buildLogs(this.data.id, buildId);
115
+ }
116
+ logs() {
117
+ return this.client.deployments.logs(this.data.id);
118
+ }
119
+ setProtection(input) {
120
+ return this.client.deployments.setProtection(this.data.id, input);
121
+ }
122
+ protection() {
123
+ return this.client.deployments.protection(this.data.id);
124
+ }
125
+ createProtectionSession() {
126
+ return this.client.deployments.createProtectionSession(this.data.id);
127
+ }
128
+ disableProtection() {
129
+ return this.client.deployments.disableProtection(this.data.id);
130
+ }
131
+ setFeedback(input) {
132
+ return this.client.deployments.setFeedback(this.data.id, input);
133
+ }
134
+ domains() {
135
+ return this.client.deployments.listDomains(this.data.id);
136
+ }
137
+ addDomain(input) {
138
+ return this.client.deployments.createDomain(this.data.id, input);
139
+ }
140
+ refreshDomain(domainId) {
141
+ return this.client.deployments.refreshDomain(this.data.id, domainId);
142
+ }
143
+ deleteDomain(domainId) {
144
+ return this.client.deployments.deleteDomain(this.data.id, domainId);
145
+ }
146
+ }
147
+ function deploymentStatusIsTerminal(status) {
148
+ return ['healthy', 'failed', 'stopped', 'deleted', 'delete_failed'].includes(status);
149
+ }
150
+ function delay(ms) {
151
+ return new Promise((resolve) => setTimeout(resolve, ms));
152
+ }
153
+ function unwrapDeploymentDomainCollection(input) {
154
+ if (Array.isArray(input)) {
155
+ return input;
156
+ }
157
+ return Array.isArray(input.data) ? input.data : [];
158
+ }
159
+ function parseNdjsonStream(input) {
160
+ const decoder = new TextDecoder();
161
+ let buffer = '';
162
+ return input.pipeThrough(new TransformStream({
163
+ transform(chunk, controller) {
164
+ buffer += decoder.decode(chunk, { stream: true });
165
+ const lines = buffer.split('\n');
166
+ buffer = lines.pop() ?? '';
167
+ for (const line of lines) {
168
+ const trimmed = line.trim();
169
+ if (trimmed !== '') {
170
+ controller.enqueue(JSON.parse(trimmed));
171
+ }
172
+ }
173
+ },
174
+ flush(controller) {
175
+ buffer += decoder.decode();
176
+ const trimmed = buffer.trim();
177
+ if (trimmed !== '') {
178
+ controller.enqueue(JSON.parse(trimmed));
179
+ }
180
+ },
181
+ }));
182
+ }
package/dist/index.d.ts CHANGED
@@ -15,6 +15,7 @@ import Git, { GitActions, GitCredentials, GitEvents } from './git.js';
15
15
  import { Disposable } from './types.js';
16
16
  import { Beacon, BeaconOptions } from './beacon/index.js';
17
17
  import { NotebookUnavailableError } from './errors/index.js';
18
+ import { DeploymentApi, NotebookDeploymentApi, type DeployInput, type DeploymentInstance } from './deployments.js';
18
19
  export * from './types.js';
19
20
  export { Transport } from './socket/index.js';
20
21
  export * from './lsp.js';
@@ -28,6 +29,7 @@ export * from './config.js';
28
29
  export * from './utils/promise.js';
29
30
  export * from './beacon/index.js';
30
31
  export * from './errors/index.js';
32
+ export * from './deployments.js';
31
33
  interface Result<TType extends 'success' | 'error' | 'running', TData extends object> {
32
34
  type: TType;
33
35
  message: string;
@@ -203,6 +205,8 @@ export declare class ApiError extends Error {
203
205
  readonly response: Response;
204
206
  readonly body: string;
205
207
  readonly status: number;
208
+ readonly data: unknown;
209
+ readonly apiMessage?: string;
206
210
  constructor(response: Response, body: string);
207
211
  }
208
212
  export declare class NotebookApi {
@@ -210,6 +214,7 @@ export declare class NotebookApi {
210
214
  readonly secrets: NotebookSecretApi;
211
215
  readonly preview: NotebookPreviewApi;
212
216
  readonly mail: NotebookMailApi;
217
+ readonly deployments: NotebookDeploymentApi;
213
218
  constructor(client: Client);
214
219
  create(template: string, input?: Partial<CreateNotebookInput>, init?: boolean): Promise<NotebookInstance>;
215
220
  get(id: string): Promise<NotebookInstance>;
@@ -230,6 +235,7 @@ export interface PHPSandboxClientOptions {
230
235
  }
231
236
  export declare class Client {
232
237
  readonly notebook: NotebookApi;
238
+ readonly deployments: DeploymentApi;
233
239
  readonly options: PHPSandboxClientOptions;
234
240
  private readonly fetch;
235
241
  private readonly baseUrl;
@@ -247,6 +253,7 @@ export declare class Client {
247
253
  put<T extends unknown>(path: string, body?: unknown): Promise<{
248
254
  data: T;
249
255
  }>;
256
+ stream(path: string): Promise<ReadableStream<Uint8Array>>;
250
257
  private makeRequest;
251
258
  }
252
259
  export declare class PHPSandbox extends Client {
@@ -315,6 +322,7 @@ export declare class NotebookInstance {
315
322
  constructor(data: NotebookData, client: Client);
316
323
  ready(): Promise<NotebookInitSuccessResult>;
317
324
  fork(input?: Partial<ForkNotebookInput>): Promise<NotebookInstance>;
325
+ deploy(input?: DeployInput): Promise<DeploymentInstance>;
318
326
  gitCredentials(): Promise<GitCredentials>;
319
327
  delete(): Promise<void>;
320
328
  stop(): Promise<void>;
package/dist/index.js CHANGED
@@ -27,6 +27,7 @@ import EventManager from './events/index.js';
27
27
  import Git from './git.js';
28
28
  import { createBeacon } from './beacon/index.js';
29
29
  import { NotebookUnavailableError } from './errors/index.js';
30
+ import { DeploymentApi, NotebookDeploymentApi } from './deployments.js';
30
31
  export * from './types.js';
31
32
  export { Transport } from './socket/index.js';
32
33
  export * from './lsp.js';
@@ -40,6 +41,7 @@ export * from './config.js';
40
41
  export * from './utils/promise.js';
41
42
  export * from './beacon/index.js';
42
43
  export * from './errors/index.js';
44
+ export * from './deployments.js';
43
45
  function normalizeNotebookSecretInputs(input) {
44
46
  if (Array.isArray(input)) {
45
47
  return input;
@@ -59,18 +61,43 @@ export class NotebookInitError extends Error {
59
61
  }
60
62
  export class ApiError extends Error {
61
63
  constructor(response, body) {
62
- super(`PHPSandbox API Error: ${response.status} ${response.statusText} - ${body}`);
64
+ const data = parseApiErrorBody(body);
65
+ const apiMessage = apiErrorMessage(data, body);
66
+ super(`PHPSandbox API Error: ${response.status} ${response.statusText}${apiMessage === '' ? '' : ` - ${apiMessage}`}`);
63
67
  this.response = response;
64
68
  this.body = body;
65
69
  this.status = response.status;
70
+ this.data = data;
71
+ this.apiMessage = apiMessage === '' ? undefined : apiMessage;
66
72
  }
67
73
  }
74
+ function parseApiErrorBody(body) {
75
+ try {
76
+ return JSON.parse(body);
77
+ }
78
+ catch {
79
+ return null;
80
+ }
81
+ }
82
+ function apiErrorMessage(data, fallback) {
83
+ if (data !== null && typeof data === 'object') {
84
+ const record = data;
85
+ if (typeof record.message === 'string' && record.message.trim() !== '') {
86
+ return record.message.trim();
87
+ }
88
+ if (record.error !== undefined) {
89
+ return typeof record.error === 'string' ? record.error.trim() : JSON.stringify(record.error);
90
+ }
91
+ }
92
+ return fallback.trim();
93
+ }
68
94
  export class NotebookApi {
69
95
  constructor(client) {
70
96
  this.client = client;
71
97
  this.secrets = new NotebookSecretApi(client);
72
98
  this.preview = new NotebookPreviewApi(client);
73
99
  this.mail = new NotebookMailApi(client);
100
+ this.deployments = new NotebookDeploymentApi(client);
74
101
  }
75
102
  async create(template, input = {}, init = true) {
76
103
  const response = await this.client.post('/notebook', { template, ...input });
@@ -122,6 +149,7 @@ function normalizeApiBaseUrl(url) {
122
149
  export class Client {
123
150
  constructor(token, url = 'https://api.phpsandbox.io/v1', options = {}) {
124
151
  this.fetch = globalThis.fetch;
152
+ this.deployments = new DeploymentApi(this);
125
153
  this.notebook = new NotebookApi(this);
126
154
  this.options = Object.assign({ startClosed: true }, options);
127
155
  this.baseUrl = normalizeApiBaseUrl(url);
@@ -146,6 +174,19 @@ export class Client {
146
174
  put(path, body) {
147
175
  return this.makeRequest('PUT', path, { body: body === undefined ? undefined : JSON.stringify(body) });
148
176
  }
177
+ async stream(path) {
178
+ const response = await this.fetch(new Request(new URL(path.replace(/^\//, ''), this.baseUrl), {
179
+ method: 'GET',
180
+ headers: this.headers,
181
+ }));
182
+ if (!response.ok) {
183
+ throw new ApiError(response, await response.text());
184
+ }
185
+ if (!response.body) {
186
+ throw new Error('PHPSandbox API Error: response body is not readable');
187
+ }
188
+ return response.body;
189
+ }
149
190
  async makeRequest(method, path, init) {
150
191
  const response = await this.fetch(new Request(new URL(path.replace(/^\//, ''), this.baseUrl), {
151
192
  method,
@@ -293,6 +334,9 @@ export class NotebookInstance {
293
334
  fork(input = {}) {
294
335
  return this.client.notebook.fork(this.data.id, input);
295
336
  }
337
+ deploy(input = {}) {
338
+ return this.client.notebook.deployments.create(this.data.id, input);
339
+ }
296
340
  gitCredentials() {
297
341
  return this.client.notebook.gitCredentials(this.data.id);
298
342
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phpsandbox/sdk",
3
- "version": "0.0.53",
3
+ "version": "0.0.55",
4
4
  "description": "A comprehensive TypeScript SDK for interacting with cloud-based PHP development environments",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",