@phpsandbox/sdk 0.0.56 → 0.0.58

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.
@@ -1,18 +1,31 @@
1
+ import { ApiError } from './index.js';
2
+ import { DeploymentEventDataSchema, DeploymentLogChunkDataSchema, DeployStreamSseEventNameSchema, DeployStreamSseEventSchema, } from './schemas/deployments.js';
3
+ export * from './schemas/deployments.js';
1
4
  export class NotebookDeploymentApi {
2
5
  constructor(client) {
3
6
  this.client = client;
4
7
  }
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
+ async current(notebookId) {
9
+ try {
10
+ const response = await this.client.get(`/notebook/${notebookId}/deployment`);
11
+ return new DeploymentInstance(response.data, this.client, notebookId);
12
+ }
13
+ catch (error) {
14
+ if (error instanceof ApiError && error.status === 404) {
15
+ return null;
16
+ }
17
+ throw error;
18
+ }
8
19
  }
9
- async list(id) {
10
- const response = await this.client.get(`/notebook/${id}/deployments`);
11
- return response.data;
20
+ async publish(notebookId, input) {
21
+ const response = await this.client.post(`/notebook/${notebookId}/deployment/publish`, input);
22
+ const run = new DeploymentRun(new DeploymentInstance(response.data, this.client, notebookId), this.client);
23
+ run.start();
24
+ return run;
12
25
  }
13
26
  async slugAvailability(id, slug) {
14
27
  const query = new URLSearchParams({ slug }).toString();
15
- const response = await this.client.get(`/notebook/${id}/deployments/slug-availability?${query}`);
28
+ const response = await this.client.get(`/notebook/${id}/deployment/slug-availability?${query}`);
16
29
  return response.data;
17
30
  }
18
31
  }
@@ -24,10 +37,6 @@ export class DeploymentApi {
24
37
  const response = await this.client.get(`/deployments/${id}`);
25
38
  return new DeploymentInstance(response.data, this.client);
26
39
  }
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
40
  async delete(id) {
32
41
  await this.client.delete(`/deployments/${id}`);
33
42
  }
@@ -67,25 +76,140 @@ export class DeploymentApi {
67
76
  await this.client.delete(`/deployments/${id}/domains/${domainId}`);
68
77
  }
69
78
  async events(id) {
70
- return parseNdjsonStream(await this.client.stream(`/deployments/${id}/events/stream`));
79
+ return parseNdjsonStream(await this.client.stream(`/deployments/${id}/events/stream`), DeploymentEventDataSchema);
71
80
  }
72
81
  async buildLogs(id, buildId) {
73
- return parseNdjsonStream(await this.client.stream(`/deployments/${id}/builds/${buildId}/logs/stream`));
82
+ return parseNdjsonStream(await this.client.stream(`/deployments/${id}/builds/${buildId}/logs/stream`), DeploymentLogChunkDataSchema);
74
83
  }
75
84
  async logs(id) {
76
- return parseNdjsonStream(await this.client.stream(`/deployments/${id}/logs/stream`));
85
+ return parseNdjsonStream(await this.client.stream(`/deployments/${id}/logs/stream`), DeploymentLogChunkDataSchema);
86
+ }
87
+ }
88
+ export class DeploymentRun {
89
+ constructor(initial, client) {
90
+ this.initial = initial;
91
+ this.client = client;
92
+ this.started = false;
93
+ this.completed = false;
94
+ this.finalError = null;
95
+ this.finalResult = null;
96
+ this.resultPromise = null;
97
+ this.resolveResult = null;
98
+ this.rejectResult = null;
99
+ this.bufferedEvents = [];
100
+ this.subscribers = new Set();
101
+ }
102
+ async *events() {
103
+ this.start();
104
+ const queue = this.subscribe();
105
+ try {
106
+ for await (const event of queue) {
107
+ yield event;
108
+ }
109
+ }
110
+ finally {
111
+ this.subscribers.delete(queue);
112
+ queue.close();
113
+ }
114
+ }
115
+ result() {
116
+ this.start();
117
+ return this.resultPromise;
118
+ }
119
+ async deployment() {
120
+ await this.result();
121
+ return this.initial.refresh();
122
+ }
123
+ start() {
124
+ if (this.started) {
125
+ return this;
126
+ }
127
+ if (!this.initial.data.okraUrl) {
128
+ throw new Error(`Deployment ${this.initial.data.id} has no okraUrl for SSE streaming`);
129
+ }
130
+ this.started = true;
131
+ this.resultPromise = new Promise((resolve, reject) => {
132
+ this.resolveResult = resolve;
133
+ this.rejectResult = reject;
134
+ });
135
+ this.resultPromise.catch(() => undefined);
136
+ void this.consumeStream(this.initial.data.okraUrl);
137
+ return this;
138
+ }
139
+ subscribe() {
140
+ const queue = new AsyncDeployEventQueue();
141
+ for (const event of this.bufferedEvents) {
142
+ queue.push(event);
143
+ }
144
+ if (this.finalError) {
145
+ queue.fail(this.finalError);
146
+ }
147
+ else if (this.completed) {
148
+ queue.close();
149
+ }
150
+ else {
151
+ this.subscribers.add(queue);
152
+ }
153
+ return queue;
154
+ }
155
+ async consumeStream(okraUrl) {
156
+ try {
157
+ const stream = await this.client.sse(okraUrl);
158
+ for await (const event of readDeployStreamEvents(stream)) {
159
+ this.publish(event);
160
+ if (event.type === 'result') {
161
+ const { type: _, ...result } = event;
162
+ this.finalResult = result;
163
+ this.resolveResult?.(result);
164
+ }
165
+ }
166
+ if (!this.finalResult) {
167
+ throw new Error('Deploy stream ended without result');
168
+ }
169
+ this.closeSubscribers();
170
+ }
171
+ catch (err) {
172
+ this.fail(err instanceof Error ? err : new Error(String(err)));
173
+ }
174
+ }
175
+ publish(event) {
176
+ this.bufferedEvents.push(event);
177
+ for (const subscriber of this.subscribers) {
178
+ subscriber.push(event);
179
+ }
180
+ }
181
+ closeSubscribers() {
182
+ this.completed = true;
183
+ for (const subscriber of this.subscribers) {
184
+ subscriber.close();
185
+ }
186
+ this.subscribers.clear();
187
+ }
188
+ fail(error) {
189
+ this.finalError = error;
190
+ this.completed = true;
191
+ this.rejectResult?.(error);
192
+ for (const subscriber of this.subscribers) {
193
+ subscriber.fail(error);
194
+ }
195
+ this.subscribers.clear();
77
196
  }
78
197
  }
79
198
  export class DeploymentInstance {
80
- constructor(data, client) {
199
+ constructor(data, client, notebookId) {
81
200
  this.data = data;
82
201
  this.client = client;
202
+ this.notebookId = notebookId;
83
203
  }
84
- refresh() {
85
- return this.client.deployments.get(this.data.id);
204
+ async refresh() {
205
+ const response = await this.client.get(`/deployments/${this.data.id}`);
206
+ return new DeploymentInstance(response.data, this.client, this.notebookId);
86
207
  }
87
- redeploy(input = {}) {
88
- return this.client.deployments.redeploy(this.data.id, input);
208
+ publish() {
209
+ if (!this.notebookId) {
210
+ return Promise.reject(new Error('Deployment publish requires a notebook-scoped deployment instance.'));
211
+ }
212
+ return this.client.notebook.deployment.publish(this.notebookId);
89
213
  }
90
214
  delete() {
91
215
  return this.client.deployments.delete(this.data.id);
@@ -107,7 +231,7 @@ export class DeploymentInstance {
107
231
  events() {
108
232
  return this.client.deployments.events(this.data.id);
109
233
  }
110
- buildLogs(buildId = this.data.latest_build?.id ?? '') {
234
+ buildLogs(buildId = this.data.latestBuild?.id ?? '') {
111
235
  if (buildId === '') {
112
236
  return Promise.reject(new Error('Deployment has no latest build id'));
113
237
  }
@@ -156,7 +280,143 @@ function unwrapDeploymentDomainCollection(input) {
156
280
  }
157
281
  return Array.isArray(input.data) ? input.data : [];
158
282
  }
159
- function parseNdjsonStream(input) {
283
+ class AsyncDeployEventQueue {
284
+ constructor() {
285
+ this.values = [];
286
+ this.pending = [];
287
+ this.closed = false;
288
+ this.error = null;
289
+ }
290
+ push(value) {
291
+ if (this.closed) {
292
+ return;
293
+ }
294
+ const next = this.pending.shift();
295
+ if (next) {
296
+ next.resolve({ done: false, value });
297
+ return;
298
+ }
299
+ this.values.push(value);
300
+ }
301
+ close() {
302
+ if (this.closed) {
303
+ return;
304
+ }
305
+ this.closed = true;
306
+ for (const next of this.pending.splice(0)) {
307
+ next.resolve({ done: true, value: undefined });
308
+ }
309
+ }
310
+ fail(error) {
311
+ if (this.closed) {
312
+ return;
313
+ }
314
+ this.error = error;
315
+ this.closed = true;
316
+ for (const next of this.pending.splice(0)) {
317
+ next.reject(error);
318
+ }
319
+ }
320
+ async *[Symbol.asyncIterator]() {
321
+ while (true) {
322
+ const next = await this.next();
323
+ if (next.done) {
324
+ return;
325
+ }
326
+ yield next.value;
327
+ }
328
+ }
329
+ next() {
330
+ const value = this.values.shift();
331
+ if (value) {
332
+ return Promise.resolve({ done: false, value });
333
+ }
334
+ if (this.error) {
335
+ return Promise.reject(this.error);
336
+ }
337
+ if (this.closed) {
338
+ return Promise.resolve({ done: true, value: undefined });
339
+ }
340
+ return new Promise((resolve, reject) => {
341
+ this.pending.push({ resolve, reject });
342
+ });
343
+ }
344
+ }
345
+ async function* readDeployStreamEvents(stream) {
346
+ const reader = parseSSEStream(stream).getReader();
347
+ try {
348
+ while (true) {
349
+ const { done, value } = await reader.read();
350
+ if (done) {
351
+ return;
352
+ }
353
+ const event = parseDeployStreamEvent(value);
354
+ if (event) {
355
+ yield event;
356
+ }
357
+ }
358
+ }
359
+ finally {
360
+ reader.releaseLock();
361
+ }
362
+ }
363
+ function parseDeployStreamEvent(event) {
364
+ if (!DeployStreamSseEventNameSchema.safeParse(event.event).success) {
365
+ return null;
366
+ }
367
+ return DeployStreamSseEventSchema.parse(event);
368
+ }
369
+ function parseSSEStream(input) {
370
+ const decoder = new TextDecoder();
371
+ let buffer = '';
372
+ return input.pipeThrough(new TransformStream({
373
+ transform(chunk, controller) {
374
+ buffer += decoder.decode(chunk, { stream: true });
375
+ const blocks = buffer.split('\n\n');
376
+ buffer = blocks.pop() ?? '';
377
+ for (const block of blocks) {
378
+ const event = parseSSEBlock(block);
379
+ if (event) {
380
+ controller.enqueue(event);
381
+ }
382
+ }
383
+ },
384
+ flush(controller) {
385
+ buffer += decoder.decode();
386
+ if (buffer.trim() !== '') {
387
+ const event = parseSSEBlock(buffer);
388
+ if (event) {
389
+ controller.enqueue(event);
390
+ }
391
+ }
392
+ },
393
+ }));
394
+ }
395
+ function parseSSEBlock(block) {
396
+ let eventType = 'message';
397
+ let data = '';
398
+ for (const line of block.split('\n')) {
399
+ if (line.startsWith('event: ')) {
400
+ eventType = line.slice(7).trim();
401
+ }
402
+ else if (line.startsWith('data: ')) {
403
+ data += line.slice(6);
404
+ }
405
+ else if (line.startsWith('data:')) {
406
+ data += line.slice(5);
407
+ }
408
+ }
409
+ if (data === '') {
410
+ return null;
411
+ }
412
+ try {
413
+ return { event: eventType, data: JSON.parse(data) };
414
+ }
415
+ catch {
416
+ return { event: eventType, data };
417
+ }
418
+ }
419
+ function parseNdjsonStream(input, schema) {
160
420
  const decoder = new TextDecoder();
161
421
  let buffer = '';
162
422
  return input.pipeThrough(new TransformStream({
@@ -167,7 +427,7 @@ function parseNdjsonStream(input) {
167
427
  for (const line of lines) {
168
428
  const trimmed = line.trim();
169
429
  if (trimmed !== '') {
170
- controller.enqueue(JSON.parse(trimmed));
430
+ controller.enqueue(schema.parse(JSON.parse(trimmed)));
171
431
  }
172
432
  }
173
433
  },
@@ -175,7 +435,7 @@ function parseNdjsonStream(input) {
175
435
  buffer += decoder.decode();
176
436
  const trimmed = buffer.trim();
177
437
  if (trimmed !== '') {
178
- controller.enqueue(JSON.parse(trimmed));
438
+ controller.enqueue(schema.parse(JSON.parse(trimmed)));
179
439
  }
180
440
  },
181
441
  }));
package/dist/git.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { Client, SecretRef } from './index.js';
1
2
  import { Action, NotebookInstance } from './index.js';
2
3
  export interface GitLog {
3
4
  hash: string;
@@ -48,6 +49,32 @@ export interface GitCredentials {
48
49
  abilities: string[];
49
50
  expiresAt: string;
50
51
  }
52
+ export type GitProvider = 'github' | (string & {});
53
+ export type GitRepositoryVisibility = 'private' | 'public';
54
+ export type GitSyncDirection = 'pull' | 'push' | 'both';
55
+ export interface GitSyncTargetInput {
56
+ provider: GitProvider;
57
+ repository: string;
58
+ author: string;
59
+ branch?: string;
60
+ credential: SecretRef;
61
+ direction?: GitSyncDirection;
62
+ visibility?: GitRepositoryVisibility;
63
+ setDefault?: boolean;
64
+ }
65
+ export interface GitSyncTargetData {
66
+ id: string;
67
+ provider: GitProvider;
68
+ repository: string;
69
+ url: string | null;
70
+ branch: string;
71
+ credentialConfigured: boolean;
72
+ default: boolean;
73
+ lastCommitSha: string | null;
74
+ lastSyncedAt: string | null;
75
+ createdAt: string | null;
76
+ updatedAt: string | null;
77
+ }
51
78
  export interface GitCheckoutResult extends GitRef {
52
79
  branch: string;
53
80
  created: boolean;
@@ -180,10 +207,12 @@ export interface GitActions {
180
207
  }
181
208
  export default class Git {
182
209
  protected okra: NotebookInstance;
183
- constructor(okra: NotebookInstance);
210
+ private readonly client;
211
+ constructor(okra: NotebookInstance, client: Client);
184
212
  checkpoint(author: string, message: string, branch?: string, allowEmpty?: boolean): Promise<GitRef>;
185
213
  checkout(branch: string, create?: boolean): Promise<GitCheckoutResult>;
186
- sync(url: string, author: string, ref?: string, token?: string, direction?: 'pull' | 'push' | 'both', force?: boolean): Promise<GitRef>;
214
+ sync(input: GitSyncTargetInput): Promise<GitSyncTargetData>;
215
+ syncTargets(): Promise<GitSyncTargetData[]>;
187
216
  log(ref?: string): Promise<GitLog[]>;
188
217
  diff(input: GitDiffInput): Promise<GitDiffPreview>;
189
218
  merge(input: GitMergeInput): Promise<GitMergeResult>;
@@ -199,5 +228,6 @@ export default class Git {
199
228
  status(): Promise<GitStatus>;
200
229
  private normalizeRemoteInput;
201
230
  private gitUrlForNotebook;
231
+ private omitUndefined;
202
232
  }
203
233
  export {};
package/dist/git.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export default class Git {
2
- constructor(okra) {
2
+ constructor(okra, client) {
3
3
  this.okra = okra;
4
+ this.client = client;
4
5
  }
5
6
  checkpoint(author, message, branch = 'main', allowEmpty = false) {
6
7
  return this.okra.invoke('git.checkpoint', { author, message, branch, allowEmpty });
@@ -8,8 +9,13 @@ export default class Git {
8
9
  checkout(branch, create = true) {
9
10
  return this.okra.invoke('git.checkout', { branch, create });
10
11
  }
11
- sync(url, author, ref = 'main', token, direction = 'both', force = false) {
12
- return this.okra.invoke('git.sync', { url, author, ref, token, direction, force });
12
+ async sync(input) {
13
+ const response = await this.client.post(`/notebook/${this.okra.data.id}/git/sync-targets`, this.omitUndefined({ ...input }));
14
+ return response.data;
15
+ }
16
+ async syncTargets() {
17
+ const response = await this.client.get(`/notebook/${this.okra.data.id}/git/sync-targets`);
18
+ return response.data;
13
19
  }
14
20
  log(ref = 'main') {
15
21
  return this.okra.invoke('git.log', { ref });
@@ -79,4 +85,7 @@ export default class Git {
79
85
  url.hash = '';
80
86
  return url.toString();
81
87
  }
88
+ omitUndefined(input) {
89
+ return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
90
+ }
82
91
  }
package/dist/index.d.ts CHANGED
@@ -15,7 +15,8 @@ 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
+ import { DeploymentApi, NotebookDeploymentApi, type DeploymentInstance, type DeploymentRun, type PublishInput } from './deployments.js';
19
+ import { ServerApi } from './servers.js';
19
20
  export * from './types.js';
20
21
  export { Transport } from './socket/index.js';
21
22
  export * from './lsp.js';
@@ -30,6 +31,7 @@ export * from './utils/promise.js';
30
31
  export * from './beacon/index.js';
31
32
  export * from './errors/index.js';
32
33
  export * from './deployments.js';
34
+ export * from './servers.js';
33
35
  interface Result<TType extends 'success' | 'error' | 'running', TData extends object> {
34
36
  type: TType;
35
37
  message: string;
@@ -124,21 +126,35 @@ export interface NotebookPolicyData {
124
126
  stale_after_minutes: number | null;
125
127
  prune_after_at: string | null;
126
128
  }
129
+ export type SecretEnvironment = 'development' | 'production';
130
+ export type SecretType = 'environment_variable' | 'credential';
131
+ export interface SecretRef {
132
+ secret: string;
133
+ environment?: SecretEnvironment;
134
+ }
127
135
  export interface NotebookSecretData {
128
136
  uuid: string;
129
137
  name: string;
130
- type: 'environment_variable';
138
+ type: SecretType;
139
+ environment: SecretEnvironment;
131
140
  createdAt: string | null;
132
141
  updatedAt: string | null;
133
142
  }
143
+ export interface NotebookSecretListOptions {
144
+ environment?: SecretEnvironment;
145
+ }
134
146
  export interface UpsertNotebookSecretInput {
135
147
  value: string;
136
- type?: 'environment_variable';
148
+ type?: SecretType;
149
+ environment?: SecretEnvironment;
137
150
  }
138
151
  export interface UpsertNotebookSecretItemInput extends UpsertNotebookSecretInput {
139
152
  name: string;
140
153
  }
141
154
  export type UpsertNotebookSecretsInput = UpsertNotebookSecretItemInput[] | Record<string, string | UpsertNotebookSecretInput>;
155
+ export interface NotebookSecretDeleteOptions {
156
+ environment?: SecretEnvironment;
157
+ }
142
158
  export interface NotebookPreviewDataDisabled {
143
159
  enabled: false;
144
160
  }
@@ -214,7 +230,7 @@ export declare class NotebookApi {
214
230
  readonly secrets: NotebookSecretApi;
215
231
  readonly preview: NotebookPreviewApi;
216
232
  readonly mail: NotebookMailApi;
217
- readonly deployments: NotebookDeploymentApi;
233
+ readonly deployment: NotebookDeploymentApi;
218
234
  constructor(client: Client);
219
235
  create(template: string, input?: Partial<CreateNotebookInput>, init?: boolean): Promise<NotebookInstance>;
220
236
  get(id: string): Promise<NotebookInstance>;
@@ -236,6 +252,7 @@ export interface PHPSandboxClientOptions {
236
252
  export declare class Client {
237
253
  readonly notebook: NotebookApi;
238
254
  readonly deployments: DeploymentApi;
255
+ readonly servers: ServerApi;
239
256
  readonly options: PHPSandboxClientOptions;
240
257
  private readonly fetch;
241
258
  private readonly baseUrl;
@@ -254,6 +271,7 @@ export declare class Client {
254
271
  data: T;
255
272
  }>;
256
273
  stream(path: string): Promise<ReadableStream<Uint8Array>>;
274
+ sse(url: string): Promise<ReadableStream<Uint8Array>>;
257
275
  private makeRequest;
258
276
  }
259
277
  export declare class PHPSandbox extends Client {
@@ -261,10 +279,10 @@ export declare class PHPSandbox extends Client {
261
279
  export declare class NotebookSecretApi {
262
280
  private readonly client;
263
281
  constructor(client: Client);
264
- list(id: string): Promise<NotebookSecretData[]>;
282
+ list(id: string, options?: NotebookSecretListOptions): Promise<NotebookSecretData[]>;
265
283
  set(id: string, name: string, input: string | UpsertNotebookSecretInput): Promise<NotebookSecretData>;
266
284
  setMany(id: string, input: UpsertNotebookSecretsInput): Promise<NotebookSecretData[]>;
267
- delete(id: string, name: string): Promise<void>;
285
+ delete(id: string, name: string, options?: NotebookSecretDeleteOptions): Promise<void>;
268
286
  }
269
287
  export declare class NotebookPreviewApi {
270
288
  private readonly client;
@@ -322,7 +340,8 @@ export declare class NotebookInstance {
322
340
  constructor(data: NotebookData, client: Client);
323
341
  ready(): Promise<NotebookInitSuccessResult>;
324
342
  fork(input?: Partial<ForkNotebookInput>): Promise<NotebookInstance>;
325
- deploy(input?: DeployInput): Promise<DeploymentInstance>;
343
+ deployment(): Promise<DeploymentInstance | null>;
344
+ publish(input?: PublishInput): Promise<DeploymentRun>;
326
345
  gitCredentials(): Promise<GitCredentials>;
327
346
  delete(): Promise<void>;
328
347
  stop(): Promise<void>;
@@ -349,10 +368,10 @@ export declare class NotebookSecrets {
349
368
  private readonly client;
350
369
  private readonly notebookId;
351
370
  constructor(client: Client, notebookId: string);
352
- list(): Promise<NotebookSecretData[]>;
371
+ list(options?: NotebookSecretListOptions): Promise<NotebookSecretData[]>;
353
372
  set(name: string, input: string | UpsertNotebookSecretInput): Promise<NotebookSecretData>;
354
373
  setMany(input: UpsertNotebookSecretsInput): Promise<NotebookSecretData[]>;
355
- delete(name: string): Promise<void>;
374
+ delete(name: string, options?: NotebookSecretDeleteOptions): Promise<void>;
356
375
  }
357
376
  export declare class NotebookPreview {
358
377
  private readonly client;