@phpsandbox/sdk 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/dist/beacon/index.js +4 -2
- package/dist/beacon/navigator.js +0 -1
- package/dist/beacon/types.d.ts +1 -1
- package/dist/browser/phpsandbox-sdk.esm.js +405 -308
- package/dist/browser/phpsandbox-sdk.esm.min.js +23 -25
- package/dist/browser/phpsandbox-sdk.iife.js +405 -308
- package/dist/browser/phpsandbox-sdk.iife.min.js +8 -10
- package/dist/composer.js +8 -4
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js +1 -3
- package/dist/feedback.js +2 -1
- package/dist/filesystem.js +32 -15
- package/dist/identity.d.ts +13 -0
- package/dist/identity.js +6 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +14 -5
- package/dist/integrations/sandbox.js +1 -1
- package/dist/lsp.d.ts +2 -2
- package/dist/lsp.js +2 -2
- package/dist/publication/index.d.ts +1 -0
- package/dist/publication/index.js +1 -0
- package/dist/publication/instance.d.ts +2 -0
- package/dist/publication/instance.js +8 -0
- package/dist/publication/notebook.js +7 -5
- package/dist/publication/resources.js +2 -1
- package/dist/publication/run.js +2 -76
- package/dist/publication/stream.d.ts +18 -0
- package/dist/publication/stream.js +185 -0
- package/dist/runtime/rest.js +2 -1
- package/dist/runtime.js +1 -1
- package/dist/schemas/publications.js +53 -59
- package/dist/servers.d.ts +2 -0
- package/dist/servers.js +5 -1
- package/dist/shell.js +5 -2
- package/dist/socket/index.js +25 -13
- package/dist/telemetry.js +8 -3
- package/dist/terminal.js +3 -4
- package/dist/utils/promise.js +1 -1
- package/dist/utils/websocket.js +2 -2
- package/package.json +49 -33
- package/dist/auth.d.ts +0 -11
- package/dist/auth.js +0 -12
package/dist/composer.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ProcessReadable
|
|
1
|
+
import { ProcessReadable } from './shell.js';
|
|
2
2
|
import { nanoid } from 'nanoid';
|
|
3
3
|
class ComposerCredentials {
|
|
4
4
|
constructor(client, notebookId) {
|
|
@@ -11,8 +11,9 @@ class ComposerCredentials {
|
|
|
11
11
|
}
|
|
12
12
|
async remove(type, url) {
|
|
13
13
|
const params = new URLSearchParams({ type });
|
|
14
|
-
if (url)
|
|
14
|
+
if (url) {
|
|
15
15
|
params.set('url', url);
|
|
16
|
+
}
|
|
16
17
|
await this.client.delete(`/notebook/${this.notebookId}/composer/credentials?${params.toString()}`);
|
|
17
18
|
}
|
|
18
19
|
}
|
|
@@ -52,10 +53,13 @@ export class ComposerProcess {
|
|
|
52
53
|
stdout.fail(error);
|
|
53
54
|
stderr.fail(error);
|
|
54
55
|
};
|
|
55
|
-
this.completion = this.okra
|
|
56
|
+
this.completion = this.okra
|
|
57
|
+
.invoke('composer.invoke', { id, command, args, options })
|
|
58
|
+
.then((result) => {
|
|
56
59
|
finish();
|
|
57
60
|
return result;
|
|
58
|
-
})
|
|
61
|
+
})
|
|
62
|
+
.catch((error) => {
|
|
59
63
|
fail(error);
|
|
60
64
|
throw error;
|
|
61
65
|
});
|
package/dist/errors/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type RemoteErrorSource = 'core' | 'runtime';
|
|
2
2
|
declare const remoteErrorCodes: readonly ["ActionNotFound", "AuthenticationRequired", "BadGateway", "BadRequest", "Conflict", "FileAlreadyExists", "FileIsDirectory", "FileNotDirectory", "FileNotFound", "FilesystemUnavailable", "InternalError", "InvalidArgument", "InvalidConfiguration", "InvalidRequest", "InvalidResponse", "InvalidServiceCommand", "MethodNotAllowed", "NotebookInitializationFailed", "NotebookUnavailable", "NotFound", "PayloadTooLarge", "PermissionDenied", "RateLimited", "RequestFailed", "RuntimeOperationFailed", "ServiceNotFound", "ServiceUnavailable", "UnprocessableEntity", "ValidationFailed", "WorkspaceUnavailable"];
|
|
3
|
-
export type RemoteErrorCode = typeof remoteErrorCodes[number];
|
|
3
|
+
export type RemoteErrorCode = (typeof remoteErrorCodes)[number];
|
|
4
4
|
export type TransportErrorCode = 'ConnectionFailed' | 'ConnectionTimeout' | 'InvalidResponse' | 'SendTimeout';
|
|
5
5
|
export interface ValidationErrorDetails {
|
|
6
6
|
errors: Record<string, string[]>;
|
package/dist/errors/index.js
CHANGED
|
@@ -94,9 +94,7 @@ function nonEmptyString(value) {
|
|
|
94
94
|
return typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined;
|
|
95
95
|
}
|
|
96
96
|
function record(value) {
|
|
97
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
98
|
-
? value
|
|
99
|
-
: undefined;
|
|
97
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
100
98
|
}
|
|
101
99
|
function stringArrayRecord(value) {
|
|
102
100
|
const candidate = record(value);
|
package/dist/feedback.js
CHANGED
|
@@ -18,8 +18,9 @@ export class Feedback {
|
|
|
18
18
|
async list(options = {}) {
|
|
19
19
|
const query = new URLSearchParams();
|
|
20
20
|
for (const [name, value] of Object.entries(options)) {
|
|
21
|
-
if (value !== undefined)
|
|
21
|
+
if (value !== undefined) {
|
|
22
22
|
query.set(name, String(value));
|
|
23
|
+
}
|
|
23
24
|
}
|
|
24
25
|
return this.client.get(`${this.path()}${query.size === 0 ? '' : `?${query.toString()}`}`);
|
|
25
26
|
}
|
package/dist/filesystem.js
CHANGED
|
@@ -80,11 +80,13 @@ export class Filesystem {
|
|
|
80
80
|
const source = typeof contents === 'string' ? new TextEncoder().encode(contents) : contents;
|
|
81
81
|
if (this.okra.runtimeTransport === 'rest') {
|
|
82
82
|
const bytes = source instanceof Uint8Array ? source : await collectBytes(source);
|
|
83
|
-
await this.okra
|
|
83
|
+
await this.okra
|
|
84
|
+
.invoke('fs.writeFile', {
|
|
84
85
|
path,
|
|
85
86
|
contents: bytes,
|
|
86
87
|
options: resolvedOptions,
|
|
87
|
-
})
|
|
88
|
+
})
|
|
89
|
+
.catch((error) => this.handleError(error));
|
|
88
90
|
return;
|
|
89
91
|
}
|
|
90
92
|
const id = nanoid();
|
|
@@ -166,13 +168,16 @@ export class Filesystem {
|
|
|
166
168
|
disposable = this.okra.listen(`fs.read.${id}`, (event) => {
|
|
167
169
|
controller.enqueue(event.data);
|
|
168
170
|
});
|
|
169
|
-
void this.okra
|
|
171
|
+
void this.okra
|
|
172
|
+
.invoke('fs.readStream', { id, path })
|
|
173
|
+
.then(() => {
|
|
170
174
|
if (!closed) {
|
|
171
175
|
closed = true;
|
|
172
176
|
disposable?.dispose();
|
|
173
177
|
controller.close();
|
|
174
178
|
}
|
|
175
|
-
})
|
|
179
|
+
})
|
|
180
|
+
.catch((error) => {
|
|
176
181
|
if (!closed) {
|
|
177
182
|
closed = true;
|
|
178
183
|
disposable?.dispose();
|
|
@@ -235,7 +240,9 @@ export class Filesystem {
|
|
|
235
240
|
options.signal.addEventListener('abort', abort, { once: true });
|
|
236
241
|
}
|
|
237
242
|
started = true;
|
|
238
|
-
void this.okra
|
|
243
|
+
void this.okra
|
|
244
|
+
.invoke('fs.follow', { id, path, lines: options.lines ?? 10 })
|
|
245
|
+
.then(() => {
|
|
239
246
|
if (!closed) {
|
|
240
247
|
closed = true;
|
|
241
248
|
disposable?.dispose();
|
|
@@ -245,7 +252,8 @@ export class Filesystem {
|
|
|
245
252
|
abort = null;
|
|
246
253
|
controller.close();
|
|
247
254
|
}
|
|
248
|
-
})
|
|
255
|
+
})
|
|
256
|
+
.catch((error) => {
|
|
249
257
|
if (!closed) {
|
|
250
258
|
closed = true;
|
|
251
259
|
disposable?.dispose();
|
|
@@ -340,7 +348,8 @@ export class Filesystem {
|
|
|
340
348
|
.finally(() => disposable.dispose());
|
|
341
349
|
}
|
|
342
350
|
tail(path, options = {}) {
|
|
343
|
-
return this.okra
|
|
351
|
+
return this.okra
|
|
352
|
+
.invoke('fs.tail', { path, lines: options.lines ?? 10 })
|
|
344
353
|
.then(({ content }) => content)
|
|
345
354
|
.catch((e) => this.handleError(e));
|
|
346
355
|
}
|
|
@@ -348,31 +357,39 @@ export class Filesystem {
|
|
|
348
357
|
return this.okra.invoke('fs.stat', { path }).catch((e) => this.handleError(e));
|
|
349
358
|
}
|
|
350
359
|
move(from, to, options = {}) {
|
|
351
|
-
return this.okra
|
|
360
|
+
return this.okra
|
|
361
|
+
.invoke('fs.rename', {
|
|
352
362
|
from,
|
|
353
363
|
to,
|
|
354
364
|
options: { overwrite: false, ...options },
|
|
355
|
-
})
|
|
365
|
+
})
|
|
366
|
+
.catch((e) => this.handleError(e));
|
|
356
367
|
}
|
|
357
368
|
remove(path, options = {}) {
|
|
358
|
-
return this.okra
|
|
369
|
+
return this.okra
|
|
370
|
+
.invoke('fs.delete', {
|
|
359
371
|
path,
|
|
360
372
|
options: { recursive: false, useTrash: false, atomic: false, ...options },
|
|
361
|
-
})
|
|
373
|
+
})
|
|
374
|
+
.catch((e) => this.handleError(e));
|
|
362
375
|
}
|
|
363
376
|
copy(source, destination, options = {}) {
|
|
364
|
-
return this.okra
|
|
377
|
+
return this.okra
|
|
378
|
+
.invoke('fs.copy', {
|
|
365
379
|
source,
|
|
366
380
|
destination,
|
|
367
381
|
options: { overwrite: false, ...options },
|
|
368
|
-
})
|
|
382
|
+
})
|
|
383
|
+
.catch((e) => this.handleError(e));
|
|
369
384
|
}
|
|
370
385
|
list(path, options = {}) {
|
|
371
|
-
return this.okra
|
|
386
|
+
return this.okra
|
|
387
|
+
.invoke('fs.readDirectory', {
|
|
372
388
|
path,
|
|
373
389
|
include: options.include ?? [],
|
|
374
390
|
exclude: options.exclude ?? [],
|
|
375
|
-
})
|
|
391
|
+
})
|
|
392
|
+
.catch((e) => this.handleError(e));
|
|
376
393
|
}
|
|
377
394
|
createDirectory(path) {
|
|
378
395
|
return this.okra.invoke('fs.createDirectory', { path }).catch((e) => this.handleError(e));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const identitySchema: z.ZodObject<{
|
|
3
|
+
user: z.ZodNullable<z.ZodObject<{
|
|
4
|
+
id: z.ZodString;
|
|
5
|
+
name: z.ZodString;
|
|
6
|
+
}, z.core.$strip>>;
|
|
7
|
+
organization: z.ZodObject<{
|
|
8
|
+
id: z.ZodString;
|
|
9
|
+
name: z.ZodString;
|
|
10
|
+
}, z.core.$strip>;
|
|
11
|
+
permissions: z.ZodArray<z.ZodString>;
|
|
12
|
+
}, z.core.$strip>;
|
|
13
|
+
export type Identity = z.infer<typeof identitySchema>;
|
package/dist/identity.js
ADDED
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { type Identity } from './identity.js';
|
|
2
|
+
export type { Identity } from './identity.js';
|
|
1
3
|
import { NotebookPublication } from './publication/index.js';
|
|
2
4
|
import { Filesystem, FilesystemActions, FilesystemEvents } from './filesystem.js';
|
|
3
5
|
import Terminals, { TerminalEvents, TerminalActions, type SpawnOptions } from './terminal.js';
|
|
4
|
-
import Auth, { AuthActions } from './auth.js';
|
|
5
6
|
import Lsp, { LspActions, LspEvents } from './lsp.js';
|
|
6
7
|
import Composer, { ComposerActions, ComposerEvents } from './composer.js';
|
|
7
8
|
import { LogEvents } from './log.js';
|
|
@@ -34,7 +35,7 @@ export type { ConfigPortMapping, ProjectConfig } from './config.js';
|
|
|
34
35
|
export { PHPSandboxError, RemoteError, TransportError } from './errors/index.js';
|
|
35
36
|
export type { HttpResponseDiagnostics, RemoteErrorCode, RemoteErrorSource, TransportErrorCode, ValidationErrorDetails, } from './errors/index.js';
|
|
36
37
|
export * from './publication/index.js';
|
|
37
|
-
export type { CreateServerInput, ServerData, ServerInstance, ServerListOptions, ServerMemberData, ServerSshOptions } from './servers.js';
|
|
38
|
+
export type { CreateServerInput, ServerData, ServerInstance, ServerListOptions, ServerMemberData, ServerSshOptions, } from './servers.js';
|
|
38
39
|
export type { ConfigureFeedbackInput, FeedbackData, FeedbackListOptions, FeedbackSource, FeedbackStatus, FeedbackType, FeedbackWidgetConfig, FeedbackWidgetTarget, SubmitFeedbackInput, UpdateFeedbackInput, } from './feedback.js';
|
|
39
40
|
export type { Integration, IntegrationAuthorization, IntegrationAuthorizationType, IntegrationData, IntegrationProvider, IntegrationReference, LinkIntegrationInput, UpdateIntegrationInput, AttachSandboxIntegrationInput, SandboxIntegration, SandboxIntegrationData, SandboxIntegrationEffect, SandboxIntegrationReference, UpdateSandboxIntegrationInput, } from './integrations/index.js';
|
|
40
41
|
interface Result<TType extends 'success' | 'error' | 'running', TData extends object> {
|
|
@@ -108,7 +109,7 @@ export interface Action<Args = object, Response = void> {
|
|
|
108
109
|
response: Response;
|
|
109
110
|
}
|
|
110
111
|
export type Events = TerminalEvents & RuntimeEvents & LspEvents & ComposerEvents & LogEvents & ServiceEvents & NotebookEvents & ReplEvents & ShellEvents & FilesystemEvents;
|
|
111
|
-
export type Invokable = TerminalActions & RuntimeActions & FilesystemActions &
|
|
112
|
+
export type Invokable = TerminalActions & RuntimeActions & FilesystemActions & LspActions & ComposerActions & ServiceActions & ConfigActions & ReplActions & ShellActions & GitActions;
|
|
112
113
|
export interface CreateNotebookImportAuthInput {
|
|
113
114
|
accessToken: string;
|
|
114
115
|
}
|
|
@@ -364,6 +365,7 @@ export declare class PHPSandbox {
|
|
|
364
365
|
readonly servers: ServerApi;
|
|
365
366
|
readonly integrations: IntegrationApi;
|
|
366
367
|
private constructor();
|
|
368
|
+
whoami(): Promise<Identity>;
|
|
367
369
|
get runtimeTransport(): RuntimeTransport;
|
|
368
370
|
static realtime(token: string, url?: string, options?: PHPSandboxClientOptions): PHPSandbox;
|
|
369
371
|
static rest(token: string, url?: string, options?: PHPSandboxClientOptions): PHPSandbox;
|
|
@@ -387,7 +389,6 @@ export declare class NotebookInstance {
|
|
|
387
389
|
protected client: Client;
|
|
388
390
|
readonly files: Filesystem;
|
|
389
391
|
readonly terminals: Terminals;
|
|
390
|
-
readonly auth: Auth;
|
|
391
392
|
readonly lsp: Lsp;
|
|
392
393
|
readonly composer: Composer;
|
|
393
394
|
readonly runtime: Runtime;
|
package/dist/index.js
CHANGED
|
@@ -10,10 +10,10 @@ 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 { identitySchema } from './identity.js';
|
|
13
14
|
import { NotebookPublication } from './publication/index.js';
|
|
14
15
|
import { Filesystem } from './filesystem.js';
|
|
15
16
|
import Terminals from './terminal.js';
|
|
16
|
-
import Auth from './auth.js';
|
|
17
17
|
import Lsp from './lsp.js';
|
|
18
18
|
import Composer from './composer.js';
|
|
19
19
|
import Runtime from './runtime.js';
|
|
@@ -221,6 +221,14 @@ export class PHPSandbox {
|
|
|
221
221
|
this.servers = client.servers;
|
|
222
222
|
this.integrations = client.integrations;
|
|
223
223
|
}
|
|
224
|
+
async whoami() {
|
|
225
|
+
const { data } = await this.client.get('/auth/whoami');
|
|
226
|
+
const result = identitySchema.safeParse(data);
|
|
227
|
+
if (!result.success) {
|
|
228
|
+
throw new PHPSandboxError('Invalid identity response.');
|
|
229
|
+
}
|
|
230
|
+
return result.data;
|
|
231
|
+
}
|
|
224
232
|
get runtimeTransport() {
|
|
225
233
|
return this.client.runtimeTransport;
|
|
226
234
|
}
|
|
@@ -269,7 +277,6 @@ export class NotebookInstance {
|
|
|
269
277
|
}
|
|
270
278
|
this.files = new Filesystem(this, this.socket === null ? null : (handler) => this.onDidConnect(handler));
|
|
271
279
|
this.terminals = new Terminals(this);
|
|
272
|
-
this.auth = new Auth(this);
|
|
273
280
|
this.lsp = new Lsp(this);
|
|
274
281
|
this.composer = new Composer(this, client);
|
|
275
282
|
this.runtime = new Runtime(this);
|
|
@@ -476,7 +483,7 @@ export class NotebookInstance {
|
|
|
476
483
|
// Wait for the socket to reconnect
|
|
477
484
|
await whenConnected;
|
|
478
485
|
// Re-initialize the notebook
|
|
479
|
-
__classPrivateFieldGet(this, _NotebookInstance_instances, "m", _NotebookInstance_init).call(this);
|
|
486
|
+
__classPrivateFieldSet(this, _NotebookInstance_initPromise, __classPrivateFieldGet(this, _NotebookInstance_instances, "m", _NotebookInstance_init).call(this), "f");
|
|
480
487
|
// Wait for initialization to complete
|
|
481
488
|
return this.ready().then(() => this);
|
|
482
489
|
}
|
|
@@ -524,8 +531,9 @@ class NotebookSecrets {
|
|
|
524
531
|
}
|
|
525
532
|
async list(options) {
|
|
526
533
|
const params = new URLSearchParams();
|
|
527
|
-
if (options?.environment)
|
|
534
|
+
if (options?.environment) {
|
|
528
535
|
params.set('environment', options.environment);
|
|
536
|
+
}
|
|
529
537
|
const query = params.toString();
|
|
530
538
|
const response = await this.client.get(`/notebook/${this.notebookId}/secrets${query ? `?${query}` : ''}`);
|
|
531
539
|
return response.data;
|
|
@@ -543,8 +551,9 @@ class NotebookSecrets {
|
|
|
543
551
|
}
|
|
544
552
|
async delete(name, options) {
|
|
545
553
|
const params = new URLSearchParams();
|
|
546
|
-
if (options?.environment)
|
|
554
|
+
if (options?.environment) {
|
|
547
555
|
params.set('environment', options.environment);
|
|
556
|
+
}
|
|
548
557
|
const query = params.toString();
|
|
549
558
|
await this.client.delete(`/notebook/${this.notebookId}/secrets/${encodeURIComponent(name)}${query ? `?${query}` : ''}`);
|
|
550
559
|
}
|
package/dist/lsp.d.ts
CHANGED
|
@@ -47,8 +47,8 @@ export declare class LspConnection {
|
|
|
47
47
|
onMessage(cb: (data: string) => void): Disposable;
|
|
48
48
|
onError(cb: (message: string) => void): Disposable;
|
|
49
49
|
onClose(cb: (code: number, reason: string) => void): Disposable;
|
|
50
|
-
dispose(): void
|
|
51
|
-
start(): void
|
|
50
|
+
dispose(): Promise<void>;
|
|
51
|
+
start(): Promise<void>;
|
|
52
52
|
onClientDisconnect(cb: (code: number, message: string) => void): Disposable;
|
|
53
53
|
whenSocketConnected(): Promise<NotebookInstance>;
|
|
54
54
|
static create(id: string, lsp: Lsp): LspConnection;
|
package/dist/lsp.js
CHANGED
|
@@ -51,10 +51,10 @@ export class LspConnection {
|
|
|
51
51
|
return this.lsp.onClose(this.id, cb);
|
|
52
52
|
}
|
|
53
53
|
dispose() {
|
|
54
|
-
this.lsp.close(this.id);
|
|
54
|
+
return this.lsp.close(this.id);
|
|
55
55
|
}
|
|
56
56
|
start() {
|
|
57
|
-
this.lsp.start(this.id);
|
|
57
|
+
return this.lsp.start(this.id);
|
|
58
58
|
}
|
|
59
59
|
onClientDisconnect(cb) {
|
|
60
60
|
return this.lsp.onClientDisconnect(cb);
|
|
@@ -6,3 +6,4 @@ export type { BuiltInPublicationProviderName, PublicationStatus, PublicationBuil
|
|
|
6
6
|
export { laravelCloudSetupForRepublish } from './providers/laravel-cloud.js';
|
|
7
7
|
export { PublicationResources } from './resources.js';
|
|
8
8
|
export { PublicationDomains } from './domains.js';
|
|
9
|
+
export { readPublicationStream, createPublicationStreamResponse } from './stream.js';
|
|
@@ -4,3 +4,4 @@ export { PublicationRun } from './run.js';
|
|
|
4
4
|
export { laravelCloudSetupForRepublish } from './providers/laravel-cloud.js';
|
|
5
5
|
export { PublicationResources } from './resources.js';
|
|
6
6
|
export { PublicationDomains } from './domains.js';
|
|
7
|
+
export { readPublicationStream, createPublicationStreamResponse } from './stream.js';
|
|
@@ -21,6 +21,8 @@ export declare class PublicationInstance<TProvider extends PublicationProviderNa
|
|
|
21
21
|
refresh(): Promise<PublicationInstance<TProvider>>;
|
|
22
22
|
/** Start or reattach to the current release stream without queuing another release. */
|
|
23
23
|
follow(): PublicationRun<TProvider>;
|
|
24
|
+
/** Open the original publishing SSE bytes for an authenticated, transparent proxy. */
|
|
25
|
+
stream(signal?: AbortSignal): Promise<ReadableStream<Uint8Array>>;
|
|
24
26
|
destroy(options?: PublicationDestroyOptions): Promise<void>;
|
|
25
27
|
wait(options?: {
|
|
26
28
|
intervalMs?: number;
|
|
@@ -36,6 +36,13 @@ export class PublicationInstance {
|
|
|
36
36
|
follow() {
|
|
37
37
|
return new PublicationRun(this, this.client).start();
|
|
38
38
|
}
|
|
39
|
+
/** Open the original publishing SSE bytes for an authenticated, transparent proxy. */
|
|
40
|
+
async stream(signal) {
|
|
41
|
+
if (!this.data.eventStreamUrl) {
|
|
42
|
+
throw new PHPSandboxError(`Publication ${this.data.id} has no eventStreamUrl for SSE streaming`);
|
|
43
|
+
}
|
|
44
|
+
return this.client.sse(this.data.eventStreamUrl, signal);
|
|
45
|
+
}
|
|
39
46
|
async destroy(options = {}) {
|
|
40
47
|
const query = options.deleteResources ? '?deleteResources=1' : '';
|
|
41
48
|
await this.client.delete(`/publications/${encodeURIComponent(this.data.id)}${query}`);
|
|
@@ -44,6 +51,7 @@ export class PublicationInstance {
|
|
|
44
51
|
const intervalMs = options.intervalMs ?? 1000;
|
|
45
52
|
const timeoutMs = options.timeoutMs ?? 300000;
|
|
46
53
|
const startedAt = Date.now();
|
|
54
|
+
// eslint-disable-next-line typescript/no-this-alias -- Polling starts with this instance and replaces it after each refresh.
|
|
47
55
|
let current = this;
|
|
48
56
|
while (!publicationStatusIsTerminal(current.data.status)) {
|
|
49
57
|
if (Date.now() - startedAt > timeoutMs) {
|
|
@@ -41,8 +41,9 @@ export class NotebookPublication {
|
|
|
41
41
|
/** Publish the exact app selection in a server-owned review. */
|
|
42
42
|
async publish(input) {
|
|
43
43
|
const application = (await this.client.post(`${this.path}/publication`, input)).data;
|
|
44
|
-
if (!('confirmationRequired' in application))
|
|
44
|
+
if (!('confirmationRequired' in application)) {
|
|
45
45
|
this.startApplicationRuns(application);
|
|
46
|
+
}
|
|
46
47
|
return application;
|
|
47
48
|
}
|
|
48
49
|
async application(applicationId) {
|
|
@@ -60,10 +61,12 @@ export class NotebookPublication {
|
|
|
60
61
|
}
|
|
61
62
|
startApplicationRuns(application) {
|
|
62
63
|
for (const app of application.apps) {
|
|
63
|
-
if (!(application.selectedAppIds ?? []).includes(app.appId))
|
|
64
|
+
if (!(application.selectedAppIds ?? []).includes(app.appId)) {
|
|
64
65
|
continue;
|
|
65
|
-
|
|
66
|
+
}
|
|
67
|
+
if (!['pending', 'queued', 'building', 'deploying'].includes(app.publication.status)) {
|
|
66
68
|
continue;
|
|
69
|
+
}
|
|
67
70
|
new PublicationRun(new PublicationInstance(app.publication, this.client, this.notebookId), this.client).start();
|
|
68
71
|
}
|
|
69
72
|
}
|
|
@@ -72,7 +75,6 @@ export class NotebookPublication {
|
|
|
72
75
|
if (serverId !== undefined) {
|
|
73
76
|
query.set('serverId', serverId);
|
|
74
77
|
}
|
|
75
|
-
return (await this.client.get(`${this.path}/publication/catalog?${query}`))
|
|
76
|
-
.data;
|
|
78
|
+
return (await this.client.get(`${this.path}/publication/catalog?${query}`)).data;
|
|
77
79
|
}
|
|
78
80
|
}
|
|
@@ -4,7 +4,8 @@ export class PublicationResources {
|
|
|
4
4
|
this.path = path;
|
|
5
5
|
}
|
|
6
6
|
async inspect(id) {
|
|
7
|
-
return (await this.client.post(`/publication-resources/${encodeURIComponent(id)}/inspect`, {}))
|
|
7
|
+
return (await this.client.post(`/publication-resources/${encodeURIComponent(id)}/inspect`, {}))
|
|
8
|
+
.data;
|
|
8
9
|
}
|
|
9
10
|
async list(options = {}) {
|
|
10
11
|
const query = new URLSearchParams();
|
package/dist/publication/run.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readPublicationStream } from './stream.js';
|
|
2
2
|
import { PHPSandboxError } from '../errors/index.js';
|
|
3
3
|
export class PublicationRun {
|
|
4
4
|
constructor(initial, client) {
|
|
@@ -89,7 +89,7 @@ export class PublicationRun {
|
|
|
89
89
|
async consumeStream(eventStreamUrl) {
|
|
90
90
|
try {
|
|
91
91
|
const stream = await this.client.sse(eventStreamUrl, this.streamAbort.signal);
|
|
92
|
-
for await (const event of
|
|
92
|
+
for await (const event of readPublicationStream(stream)) {
|
|
93
93
|
this.publish(event);
|
|
94
94
|
if (event.type === 'result') {
|
|
95
95
|
const { type: _, ...result } = event;
|
|
@@ -191,77 +191,3 @@ class AsyncPublishEventQueue {
|
|
|
191
191
|
});
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
|
-
async function* readPublishStreamEvents(stream) {
|
|
195
|
-
const reader = parseSSEStream(stream).getReader();
|
|
196
|
-
try {
|
|
197
|
-
while (true) {
|
|
198
|
-
const { done, value } = await reader.read();
|
|
199
|
-
if (done) {
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
const event = parsePublishStreamEvent(value);
|
|
203
|
-
if (event) {
|
|
204
|
-
yield event;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
finally {
|
|
209
|
-
reader.releaseLock();
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
function parsePublishStreamEvent(event) {
|
|
213
|
-
if (!PublishStreamSseEventNameSchema.safeParse(event.event).success) {
|
|
214
|
-
return null;
|
|
215
|
-
}
|
|
216
|
-
return PublishStreamSseEventSchema.parse(event);
|
|
217
|
-
}
|
|
218
|
-
function parseSSEStream(input) {
|
|
219
|
-
const decoder = new TextDecoder();
|
|
220
|
-
let buffer = '';
|
|
221
|
-
return input.pipeThrough(new TransformStream({
|
|
222
|
-
transform(chunk, controller) {
|
|
223
|
-
buffer += decoder.decode(chunk, { stream: true });
|
|
224
|
-
const blocks = buffer.split('\n\n');
|
|
225
|
-
buffer = blocks.pop() ?? '';
|
|
226
|
-
for (const block of blocks) {
|
|
227
|
-
const event = parseSSEBlock(block);
|
|
228
|
-
if (event) {
|
|
229
|
-
controller.enqueue(event);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
},
|
|
233
|
-
flush(controller) {
|
|
234
|
-
buffer += decoder.decode();
|
|
235
|
-
if (buffer.trim() !== '') {
|
|
236
|
-
const event = parseSSEBlock(buffer);
|
|
237
|
-
if (event) {
|
|
238
|
-
controller.enqueue(event);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
},
|
|
242
|
-
}));
|
|
243
|
-
}
|
|
244
|
-
function parseSSEBlock(block) {
|
|
245
|
-
let eventType = 'message';
|
|
246
|
-
let data = '';
|
|
247
|
-
for (const line of block.split('\n')) {
|
|
248
|
-
if (line.startsWith('event: ')) {
|
|
249
|
-
eventType = line.slice(7).trim();
|
|
250
|
-
}
|
|
251
|
-
else if (line.startsWith('data: ')) {
|
|
252
|
-
data += line.slice(6);
|
|
253
|
-
}
|
|
254
|
-
else if (line.startsWith('data:')) {
|
|
255
|
-
data += line.slice(5);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
if (data === '') {
|
|
259
|
-
return null;
|
|
260
|
-
}
|
|
261
|
-
try {
|
|
262
|
-
return { event: eventType, data: JSON.parse(data) };
|
|
263
|
-
}
|
|
264
|
-
catch {
|
|
265
|
-
return { event: eventType, data };
|
|
266
|
-
}
|
|
267
|
-
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PublishStreamEvent } from '../schemas/publications.js';
|
|
2
|
+
export type { PublishStreamEvent } from '../schemas/publications.js';
|
|
3
|
+
/** Consume publishing SSE in browsers or servers, including through an authenticated proxy.
|
|
4
|
+
* Cancels the source when iteration ends early or parsing fails. Fetch cancellation belongs
|
|
5
|
+
* to the caller's AbortSignal. Unknown event names and heartbeat comments are ignored.
|
|
6
|
+
* A complete stream must include a result; set requireResult to false only for fragments.
|
|
7
|
+
*/
|
|
8
|
+
export declare function readPublicationStream(input: Response | ReadableStream<Uint8Array>, options?: {
|
|
9
|
+
requireResult?: boolean;
|
|
10
|
+
}): AsyncIterable<PublishStreamEvent>;
|
|
11
|
+
/** Forward the original bytes with SSE headers and exactly-once resource cleanup.
|
|
12
|
+
* Works with Fetch-based servers and frameworks that accept a standard Response.
|
|
13
|
+
* Does not parse, buffer a replay, or re-encode events. Upstream errors fail the body.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createPublicationStreamResponse(stream: ReadableStream<Uint8Array>, options?: {
|
|
16
|
+
signal?: AbortSignal;
|
|
17
|
+
onDispose?: () => void | Promise<void>;
|
|
18
|
+
}): Response;
|