@toshicloud/sdk 0.3.2 → 0.5.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.
- package/README.md +189 -30
- package/dist/client.d.ts +30 -4
- package/dist/client.js +19 -6
- package/dist/errors.d.ts +47 -0
- package/dist/errors.js +136 -0
- package/dist/execute.js +15 -0
- package/dist/gen/toshi/permission_pb.d.ts +39 -0
- package/dist/gen/toshi/permission_pb.js +42 -0
- package/dist/gen/toshi/workspace/v1/artifacts_pb.d.ts +1 -1
- package/dist/gen/toshi/workspace/v1/artifacts_pb.js +3 -2
- package/dist/gen/toshi/workspace/v1/audit_events_pb.js +2 -1
- package/dist/gen/toshi/workspace/v1/channels_pb.js +2 -1
- package/dist/gen/toshi/workspace/v1/discovery_pb.d.ts +676 -0
- package/dist/gen/toshi/workspace/v1/discovery_pb.js +182 -0
- package/dist/gen/toshi/workspace/v1/display_pb.js +2 -1
- package/dist/gen/toshi/workspace/v1/fillers_pb.js +2 -1
- package/dist/gen/toshi/workspace/v1/hostjobs_pb.js +2 -1
- package/dist/gen/toshi/workspace/v1/hosts_pb.js +2 -1
- package/dist/gen/toshi/workspace/v1/products_pb.d.ts +703 -0
- package/dist/gen/toshi/workspace/v1/products_pb.js +106 -0
- package/dist/gen/toshi/workspace/v1/projects_pb.js +2 -1
- package/dist/gen/toshi/workspace/v1/recordings_pb.js +2 -1
- package/dist/gen/toshi/workspace/v1/secrets_pb.js +2 -1
- package/dist/gen/toshi/workspace/v1/storage_providers_pb.js +2 -1
- package/dist/gen/toshi/workspace/v1/tokens_pb.js +2 -1
- package/dist/gen/toshi/workspace/v1/volumes_pb.js +2 -1
- package/dist/gen/toshi/workspace/v1/webhook_pb.js +2 -1
- package/dist/gen/toshi/workspace/v1/workloads_pb.js +2 -1
- package/dist/gen/toshi/workspace/v1/workspace_pb.d.ts +2 -2
- package/dist/gen/toshi/workspace/v1/workspace_pb.js +1 -1
- package/dist/gen/toshi/workspace/v1/workspace_service_pb.js +2 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +5 -1
- package/dist/interceptors.d.ts +17 -2
- package/dist/interceptors.js +69 -4
- package/dist/node/client.d.ts +35 -0
- package/dist/node/client.js +178 -0
- package/dist/node/index.d.ts +3 -0
- package/dist/node/index.js +4 -0
- package/dist/output.js +6 -1
- package/dist/paginate.d.ts +36 -0
- package/dist/paginate.js +37 -0
- package/dist/retry.js +4 -0
- package/dist/run.js +3 -2
- package/dist/stream.d.ts +8 -1
- package/dist/stream.js +24 -2
- package/dist/test/fake.d.ts +88 -0
- package/dist/test/fake.js +845 -0
- package/dist/test/index.d.ts +58 -0
- package/dist/test/index.js +57 -0
- package/dist/test/procedures.d.ts +18 -0
- package/dist/test/procedures.js +219 -0
- package/dist/timeout.d.ts +29 -0
- package/dist/timeout.js +67 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/dist/wait.js +15 -12
- package/dist/watch.js +15 -2
- package/package.json +19 -3
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fake Toshi for tests: an in-memory Workspace API served through
|
|
3
|
+
* `@connectrpc/connect`'s router transport.
|
|
4
|
+
*
|
|
5
|
+
* A test creates a fake, tells it what to answer, and calls the client:
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* const fake = createFake();
|
|
9
|
+
* fake.job("job-1").stdout("deployed\n").exitCode(0);
|
|
10
|
+
* const toshi = fake.client();
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* `createFake` serves every service of `toshi.workspace.v1` from one
|
|
14
|
+
* in-memory transport: no server, no port, nothing to close, and no Node
|
|
15
|
+
* module, so it runs wherever the client runs. `client` returns a
|
|
16
|
+
* `ToshiClient` wired to it, with the token "test-token" and the scope
|
|
17
|
+
* default/production.
|
|
18
|
+
*
|
|
19
|
+
* # What the fake answers
|
|
20
|
+
*
|
|
21
|
+
* The fake holds one workspace and has no clock: every call answers at once
|
|
22
|
+
* with what the test set. It knows the job and the sandbox lifecycle and
|
|
23
|
+
* nothing else.
|
|
24
|
+
*
|
|
25
|
+
* - `job`, `sandbox` and `execute` set what the fake answers: a job's or a
|
|
26
|
+
* sandbox's state, and the standard output, the standard error and the exit
|
|
27
|
+
* code of a job or of a command run in a sandbox. A job or a sandbox the
|
|
28
|
+
* test set no state for is running; a job or a command the test set no exit
|
|
29
|
+
* code for exits 0.
|
|
30
|
+
* - CreateJob answers the jobs registered with `job`, in the order they were
|
|
31
|
+
* registered, and then creates job-1, job-2, ... of its own. CreateSandbox
|
|
32
|
+
* does the same for `sandbox` and sandbox-1, sandbox-2, .... A create whose
|
|
33
|
+
* request key the fake has already answered returns the same workload.
|
|
34
|
+
* - GetJob, ListJobs, CancelJob, WaitJob and WatchOutput answer from the jobs
|
|
35
|
+
* that exist; GetSandbox, ListSandboxes, WaitSandbox and WatchSandboxes
|
|
36
|
+
* from the sandboxes. An id the fake does not hold is NOT_FOUND. The lists
|
|
37
|
+
* answer every workload the fake holds, in creation order: they do not apply
|
|
38
|
+
* the request's filters or page size.
|
|
39
|
+
* - WaitJob and WaitSandbox answer at once, reached, with the workload as the
|
|
40
|
+
* test set it.
|
|
41
|
+
* - WatchOutput streams a job's standard output and standard error from the
|
|
42
|
+
* offsets the request names, then the job's end.
|
|
43
|
+
* - WatchJobs and WatchSandboxes report each change made after the cursor the
|
|
44
|
+
* request names: a workload the fake creates, and each state the test sets.
|
|
45
|
+
* ListJobs and ListSandboxes answer the cursor a watch starts at.
|
|
46
|
+
* - Execute answers the start message with an execution id, the standard
|
|
47
|
+
* output, the standard error and the exit code the test set for the sandbox.
|
|
48
|
+
* It does not read standard input.
|
|
49
|
+
*
|
|
50
|
+
* Every other procedure is hook-only. An unhooked procedure the state machine
|
|
51
|
+
* does not cover answers UNIMPLEMENTED, naming the procedure and the `on` call
|
|
52
|
+
* that supplies an answer. A hook replaces the state machine for its method.
|
|
53
|
+
*
|
|
54
|
+
* `calls` returns every call the fake received, in order, with its request and
|
|
55
|
+
* its headers.
|
|
56
|
+
*/
|
|
57
|
+
export { createFake, testToken } from "./fake.js";
|
|
58
|
+
export type { Call, Fake, FakeExecution, FakeJob, FakeOptions, FakeSandbox } from "./fake.js";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fake Toshi for tests: an in-memory Workspace API served through
|
|
3
|
+
* `@connectrpc/connect`'s router transport.
|
|
4
|
+
*
|
|
5
|
+
* A test creates a fake, tells it what to answer, and calls the client:
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* const fake = createFake();
|
|
9
|
+
* fake.job("job-1").stdout("deployed\n").exitCode(0);
|
|
10
|
+
* const toshi = fake.client();
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* `createFake` serves every service of `toshi.workspace.v1` from one
|
|
14
|
+
* in-memory transport: no server, no port, nothing to close, and no Node
|
|
15
|
+
* module, so it runs wherever the client runs. `client` returns a
|
|
16
|
+
* `ToshiClient` wired to it, with the token "test-token" and the scope
|
|
17
|
+
* default/production.
|
|
18
|
+
*
|
|
19
|
+
* # What the fake answers
|
|
20
|
+
*
|
|
21
|
+
* The fake holds one workspace and has no clock: every call answers at once
|
|
22
|
+
* with what the test set. It knows the job and the sandbox lifecycle and
|
|
23
|
+
* nothing else.
|
|
24
|
+
*
|
|
25
|
+
* - `job`, `sandbox` and `execute` set what the fake answers: a job's or a
|
|
26
|
+
* sandbox's state, and the standard output, the standard error and the exit
|
|
27
|
+
* code of a job or of a command run in a sandbox. A job or a sandbox the
|
|
28
|
+
* test set no state for is running; a job or a command the test set no exit
|
|
29
|
+
* code for exits 0.
|
|
30
|
+
* - CreateJob answers the jobs registered with `job`, in the order they were
|
|
31
|
+
* registered, and then creates job-1, job-2, ... of its own. CreateSandbox
|
|
32
|
+
* does the same for `sandbox` and sandbox-1, sandbox-2, .... A create whose
|
|
33
|
+
* request key the fake has already answered returns the same workload.
|
|
34
|
+
* - GetJob, ListJobs, CancelJob, WaitJob and WatchOutput answer from the jobs
|
|
35
|
+
* that exist; GetSandbox, ListSandboxes, WaitSandbox and WatchSandboxes
|
|
36
|
+
* from the sandboxes. An id the fake does not hold is NOT_FOUND. The lists
|
|
37
|
+
* answer every workload the fake holds, in creation order: they do not apply
|
|
38
|
+
* the request's filters or page size.
|
|
39
|
+
* - WaitJob and WaitSandbox answer at once, reached, with the workload as the
|
|
40
|
+
* test set it.
|
|
41
|
+
* - WatchOutput streams a job's standard output and standard error from the
|
|
42
|
+
* offsets the request names, then the job's end.
|
|
43
|
+
* - WatchJobs and WatchSandboxes report each change made after the cursor the
|
|
44
|
+
* request names: a workload the fake creates, and each state the test sets.
|
|
45
|
+
* ListJobs and ListSandboxes answer the cursor a watch starts at.
|
|
46
|
+
* - Execute answers the start message with an execution id, the standard
|
|
47
|
+
* output, the standard error and the exit code the test set for the sandbox.
|
|
48
|
+
* It does not read standard input.
|
|
49
|
+
*
|
|
50
|
+
* Every other procedure is hook-only. An unhooked procedure the state machine
|
|
51
|
+
* does not cover answers UNIMPLEMENTED, naming the procedure and the `on` call
|
|
52
|
+
* that supplies an answer. A hook replaces the state machine for its method.
|
|
53
|
+
*
|
|
54
|
+
* `calls` returns every call the fake received, in order, with its request and
|
|
55
|
+
* its headers.
|
|
56
|
+
*/
|
|
57
|
+
export { createFake, testToken } from "./fake.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { DescMethod, DescService } from "@bufbuild/protobuf";
|
|
2
|
+
/** The package the fake serves. */
|
|
3
|
+
export declare const apiPackage = "toshi.workspace.v1";
|
|
4
|
+
/** Every service of the Workspace API. */
|
|
5
|
+
export declare const apiServices: readonly DescService[];
|
|
6
|
+
/** The path a client calls a method by, for example `/toshi.workspace.v1.WorkloadService/CreateJob`. */
|
|
7
|
+
export declare function procedurePath(method: DescMethod): string;
|
|
8
|
+
/** Every method of the Workspace API, keyed by the path a client calls it by. */
|
|
9
|
+
export declare const apiMethods: ReadonlyMap<string, DescMethod>;
|
|
10
|
+
/** Whether method is a method of the Workspace API this package was generated from. */
|
|
11
|
+
export declare function isApiMethod(method: DescMethod): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* The methods the fake has no built-in answer for: a test supplies their
|
|
14
|
+
* answer with `on`. Every method of the Workspace API is either answered by
|
|
15
|
+
* the state machine or listed here, and the coverage test fails when one is
|
|
16
|
+
* neither, so a new method is a decision and not a surprise at a caller.
|
|
17
|
+
*/
|
|
18
|
+
export declare const hookOnly: readonly DescMethod[];
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { ArtifactService, ArtifactUploadService } from "../gen/toshi/workspace/v1/artifacts_pb.js";
|
|
2
|
+
import { AuditEventService } from "../gen/toshi/workspace/v1/audit_events_pb.js";
|
|
3
|
+
import { ChannelService } from "../gen/toshi/workspace/v1/channels_pb.js";
|
|
4
|
+
import { DisplayService } from "../gen/toshi/workspace/v1/display_pb.js";
|
|
5
|
+
import { FillerService } from "../gen/toshi/workspace/v1/fillers_pb.js";
|
|
6
|
+
import { HostJobService } from "../gen/toshi/workspace/v1/hostjobs_pb.js";
|
|
7
|
+
import { HostService } from "../gen/toshi/workspace/v1/hosts_pb.js";
|
|
8
|
+
import { ProjectService } from "../gen/toshi/workspace/v1/projects_pb.js";
|
|
9
|
+
import { RecordingService } from "../gen/toshi/workspace/v1/recordings_pb.js";
|
|
10
|
+
import { SecretService } from "../gen/toshi/workspace/v1/secrets_pb.js";
|
|
11
|
+
import { StorageProviderService } from "../gen/toshi/workspace/v1/storage_providers_pb.js";
|
|
12
|
+
import { TokenService } from "../gen/toshi/workspace/v1/tokens_pb.js";
|
|
13
|
+
import { VolumeService } from "../gen/toshi/workspace/v1/volumes_pb.js";
|
|
14
|
+
import { WebhookService } from "../gen/toshi/workspace/v1/webhook_pb.js";
|
|
15
|
+
import { WorkloadService } from "../gen/toshi/workspace/v1/workloads_pb.js";
|
|
16
|
+
import { WorkspaceService } from "../gen/toshi/workspace/v1/workspace_service_pb.js";
|
|
17
|
+
/** The package the fake serves. */
|
|
18
|
+
export const apiPackage = "toshi.workspace.v1";
|
|
19
|
+
/** Every service of the Workspace API. */
|
|
20
|
+
export const apiServices = [
|
|
21
|
+
WorkloadService,
|
|
22
|
+
SecretService,
|
|
23
|
+
ProjectService,
|
|
24
|
+
WebhookService,
|
|
25
|
+
DisplayService,
|
|
26
|
+
ChannelService,
|
|
27
|
+
RecordingService,
|
|
28
|
+
VolumeService,
|
|
29
|
+
ArtifactService,
|
|
30
|
+
ArtifactUploadService,
|
|
31
|
+
AuditEventService,
|
|
32
|
+
FillerService,
|
|
33
|
+
HostJobService,
|
|
34
|
+
HostService,
|
|
35
|
+
TokenService,
|
|
36
|
+
StorageProviderService,
|
|
37
|
+
WorkspaceService,
|
|
38
|
+
];
|
|
39
|
+
/** The path a client calls a method by, for example `/toshi.workspace.v1.WorkloadService/CreateJob`. */
|
|
40
|
+
export function procedurePath(method) {
|
|
41
|
+
return `/${method.parent.typeName}/${method.name}`;
|
|
42
|
+
}
|
|
43
|
+
/** Every method of the Workspace API, keyed by the path a client calls it by. */
|
|
44
|
+
export const apiMethods = buildApiMethods();
|
|
45
|
+
function buildApiMethods() {
|
|
46
|
+
const found = new Map();
|
|
47
|
+
for (const service of apiServices) {
|
|
48
|
+
for (const method of service.methods) {
|
|
49
|
+
found.set(procedurePath(method), method);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return found;
|
|
53
|
+
}
|
|
54
|
+
/** Whether method is a method of the Workspace API this package was generated from. */
|
|
55
|
+
export function isApiMethod(method) {
|
|
56
|
+
return apiMethods.get(procedurePath(method)) === method;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The methods the fake has no built-in answer for: a test supplies their
|
|
60
|
+
* answer with `on`. Every method of the Workspace API is either answered by
|
|
61
|
+
* the state machine or listed here, and the coverage test fails when one is
|
|
62
|
+
* neither, so a new method is a decision and not a surprise at a caller.
|
|
63
|
+
*/
|
|
64
|
+
export const hookOnly = [
|
|
65
|
+
// WorkloadService
|
|
66
|
+
WorkloadService.method.updateJob,
|
|
67
|
+
WorkloadService.method.deleteJob,
|
|
68
|
+
WorkloadService.method.updateSandbox,
|
|
69
|
+
WorkloadService.method.deleteSandbox,
|
|
70
|
+
WorkloadService.method.cancelExecution,
|
|
71
|
+
WorkloadService.method.watchTelemetry,
|
|
72
|
+
WorkloadService.method.watchGuestLog,
|
|
73
|
+
WorkloadService.method.createPreviewShareLink,
|
|
74
|
+
WorkloadService.method.listPreviewShareLinks,
|
|
75
|
+
WorkloadService.method.revokePreviewShareLink,
|
|
76
|
+
WorkloadService.method.createService,
|
|
77
|
+
WorkloadService.method.getService,
|
|
78
|
+
WorkloadService.method.listServices,
|
|
79
|
+
WorkloadService.method.updateService,
|
|
80
|
+
WorkloadService.method.deleteService,
|
|
81
|
+
WorkloadService.method.waitService,
|
|
82
|
+
WorkloadService.method.watchServices,
|
|
83
|
+
WorkloadService.method.startService,
|
|
84
|
+
WorkloadService.method.stopService,
|
|
85
|
+
WorkloadService.method.setServiceMode,
|
|
86
|
+
WorkloadService.method.wakeService,
|
|
87
|
+
WorkloadService.method.holdService,
|
|
88
|
+
WorkloadService.method.releaseService,
|
|
89
|
+
WorkloadService.method.listServiceRuns,
|
|
90
|
+
WorkloadService.method.listEvents,
|
|
91
|
+
WorkloadService.method.watchEvents,
|
|
92
|
+
WorkloadService.method.getOperation,
|
|
93
|
+
WorkloadService.method.waitOperation,
|
|
94
|
+
// SecretService
|
|
95
|
+
SecretService.method.createSecretSet,
|
|
96
|
+
SecretService.method.listSecretSets,
|
|
97
|
+
SecretService.method.deleteSecretSet,
|
|
98
|
+
SecretService.method.putSecret,
|
|
99
|
+
SecretService.method.deleteSecret,
|
|
100
|
+
SecretService.method.listSecrets,
|
|
101
|
+
SecretService.method.listSecretVersions,
|
|
102
|
+
SecretService.method.grantSecretSet,
|
|
103
|
+
SecretService.method.revokeSecretSet,
|
|
104
|
+
SecretService.method.listSecretSetGrants,
|
|
105
|
+
// ProjectService
|
|
106
|
+
ProjectService.method.listProjects,
|
|
107
|
+
ProjectService.method.getProject,
|
|
108
|
+
ProjectService.method.listRepositories,
|
|
109
|
+
ProjectService.method.createProject,
|
|
110
|
+
ProjectService.method.claimRepository,
|
|
111
|
+
ProjectService.method.createEnvironment,
|
|
112
|
+
ProjectService.method.deleteEnvironment,
|
|
113
|
+
ProjectService.method.listBranchRules,
|
|
114
|
+
ProjectService.method.setBranchRules,
|
|
115
|
+
ProjectService.method.createEmbedToken,
|
|
116
|
+
ProjectService.method.listEmbedTokens,
|
|
117
|
+
ProjectService.method.revokeEmbedToken,
|
|
118
|
+
// WebhookService
|
|
119
|
+
WebhookService.method.createWebhookEndpoint,
|
|
120
|
+
WebhookService.method.getWebhookEndpoint,
|
|
121
|
+
WebhookService.method.listWebhookEndpoints,
|
|
122
|
+
WebhookService.method.updateWebhookEndpoint,
|
|
123
|
+
WebhookService.method.deleteWebhookEndpoint,
|
|
124
|
+
WebhookService.method.rotateWebhookEndpointSecret,
|
|
125
|
+
WebhookService.method.listWebhookDeliveries,
|
|
126
|
+
WebhookService.method.replayWebhookEvents,
|
|
127
|
+
// DisplayService
|
|
128
|
+
DisplayService.method.openDisplaySession,
|
|
129
|
+
DisplayService.method.getDisplaySession,
|
|
130
|
+
DisplayService.method.listDisplaySessions,
|
|
131
|
+
DisplayService.method.closeDisplaySession,
|
|
132
|
+
DisplayService.method.sendDisplayKeys,
|
|
133
|
+
DisplayService.method.sendDisplayPointer,
|
|
134
|
+
DisplayService.method.setDisplayClipboard,
|
|
135
|
+
DisplayService.method.takeDisplayScreenshot,
|
|
136
|
+
// ChannelService
|
|
137
|
+
ChannelService.method.createChannel,
|
|
138
|
+
ChannelService.method.listChannels,
|
|
139
|
+
ChannelService.method.getChannel,
|
|
140
|
+
ChannelService.method.getChannelPolicy,
|
|
141
|
+
ChannelService.method.setChannelPolicy,
|
|
142
|
+
ChannelService.method.listChannelRequests,
|
|
143
|
+
ChannelService.method.getChannelRequest,
|
|
144
|
+
ChannelService.method.exportChannelLog,
|
|
145
|
+
ChannelService.method.createChannelConnectToken,
|
|
146
|
+
ChannelService.method.revokeChannelConnectToken,
|
|
147
|
+
ChannelService.method.deleteChannel,
|
|
148
|
+
ChannelService.method.listForwards,
|
|
149
|
+
ChannelService.method.getLink,
|
|
150
|
+
// RecordingService
|
|
151
|
+
RecordingService.method.listRecordings,
|
|
152
|
+
RecordingService.method.getRecording,
|
|
153
|
+
RecordingService.method.createRecordingShareLink,
|
|
154
|
+
RecordingService.method.listRecordingShareLinks,
|
|
155
|
+
RecordingService.method.revokeRecordingShareLink,
|
|
156
|
+
// VolumeService
|
|
157
|
+
VolumeService.method.listVolumes,
|
|
158
|
+
VolumeService.method.attachVolume,
|
|
159
|
+
VolumeService.method.detachVolume,
|
|
160
|
+
// ArtifactService
|
|
161
|
+
ArtifactService.method.listArtifacts,
|
|
162
|
+
ArtifactService.method.getArtifact,
|
|
163
|
+
ArtifactService.method.getArtifactDownloadURL,
|
|
164
|
+
// ArtifactUploadService
|
|
165
|
+
ArtifactUploadService.method.reserveUpload,
|
|
166
|
+
ArtifactUploadService.method.completeUpload,
|
|
167
|
+
// AuditEventService
|
|
168
|
+
AuditEventService.method.listAuditEvents,
|
|
169
|
+
// FillerService
|
|
170
|
+
FillerService.method.listFillers,
|
|
171
|
+
FillerService.method.putFiller,
|
|
172
|
+
FillerService.method.deleteFiller,
|
|
173
|
+
FillerService.method.setFillerEnabled,
|
|
174
|
+
FillerService.method.listFillerRuns,
|
|
175
|
+
// HostJobService
|
|
176
|
+
HostJobService.method.listHostJobs,
|
|
177
|
+
HostJobService.method.getHostJob,
|
|
178
|
+
HostJobService.method.cancelHostJob,
|
|
179
|
+
HostJobService.method.getHostJobFailure,
|
|
180
|
+
HostJobService.method.getHostJobTelemetry,
|
|
181
|
+
HostJobService.method.getHostJobDebugAccess,
|
|
182
|
+
HostJobService.method.stopHostJobDebug,
|
|
183
|
+
// HostService
|
|
184
|
+
HostService.method.listHosts,
|
|
185
|
+
HostService.method.listHostStorage,
|
|
186
|
+
HostService.method.listHostAlerts,
|
|
187
|
+
HostService.method.setHostState,
|
|
188
|
+
HostService.method.retireHost,
|
|
189
|
+
HostService.method.createCloudHost,
|
|
190
|
+
HostService.method.checkCloudHost,
|
|
191
|
+
HostService.method.createEnrollmentToken,
|
|
192
|
+
// TokenService
|
|
193
|
+
TokenService.method.listTokens,
|
|
194
|
+
TokenService.method.createWorkspaceToken,
|
|
195
|
+
TokenService.method.revokeToken,
|
|
196
|
+
// StorageProviderService
|
|
197
|
+
StorageProviderService.method.listStorageProviders,
|
|
198
|
+
StorageProviderService.method.createStorageProvider,
|
|
199
|
+
StorageProviderService.method.checkStorageProvider,
|
|
200
|
+
StorageProviderService.method.deleteStorageProvider,
|
|
201
|
+
// WorkspaceService
|
|
202
|
+
WorkspaceService.method.getWorkspace,
|
|
203
|
+
WorkspaceService.method.listWorkspaces,
|
|
204
|
+
WorkspaceService.method.getWorkspaceBilling,
|
|
205
|
+
WorkspaceService.method.getWorkspaceStatement,
|
|
206
|
+
WorkspaceService.method.setWorkspaceOwnHostsOnly,
|
|
207
|
+
WorkspaceService.method.setWorkspaceAllowOverflow,
|
|
208
|
+
WorkspaceService.method.setWorkspaceAllowCacheWarm,
|
|
209
|
+
WorkspaceService.method.setWorkspaceCancelRunOnVmDeath,
|
|
210
|
+
WorkspaceService.method.setWorkspaceSpendLimits,
|
|
211
|
+
WorkspaceService.method.listEgressIPs,
|
|
212
|
+
WorkspaceService.method.getUsage,
|
|
213
|
+
WorkspaceService.method.getQueueTimes,
|
|
214
|
+
WorkspaceService.method.getWorkflowUsage,
|
|
215
|
+
WorkspaceService.method.listPlans,
|
|
216
|
+
WorkspaceService.method.createCheckoutSession,
|
|
217
|
+
WorkspaceService.method.createBillingPortalSession,
|
|
218
|
+
WorkspaceService.method.getVersion,
|
|
219
|
+
];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ConnectError } from "@connectrpc/connect";
|
|
2
|
+
import type { Transport } from "@connectrpc/connect";
|
|
3
|
+
/** The error a call ends with when its timeout fires. */
|
|
4
|
+
export declare function timeoutError(timeoutMs: number): ConnectError;
|
|
5
|
+
/** A running deadline: one timer, one signal, and its state for the caller. */
|
|
6
|
+
export interface Deadline {
|
|
7
|
+
/** Aborts when the timeout fires or the caller's signal aborts. */
|
|
8
|
+
readonly signal: AbortSignal;
|
|
9
|
+
/** Whether the timeout fired, rather than the caller's signal. */
|
|
10
|
+
readonly timedOut: boolean;
|
|
11
|
+
/** Whether the caller's own signal aborted. */
|
|
12
|
+
readonly callerAborted: boolean;
|
|
13
|
+
/** Stops the timer; safe to call more than once. */
|
|
14
|
+
clear(): void;
|
|
15
|
+
}
|
|
16
|
+
/** Starts the deadline for a call's timeoutMs, or returns undefined without one. */
|
|
17
|
+
export declare function startDeadline(timeoutMs: number | undefined, caller?: AbortSignal): Deadline | undefined;
|
|
18
|
+
/** Runs body with a deadline signal, and reports the timeout instead of an abort. */
|
|
19
|
+
export declare function runWithDeadline<O extends {
|
|
20
|
+
signal?: AbortSignal;
|
|
21
|
+
timeoutMs?: number;
|
|
22
|
+
}, T>(opts: O | undefined, body: (opts: O | undefined) => Promise<T>): Promise<T>;
|
|
23
|
+
/**
|
|
24
|
+
* Gives every unary call defaultTimeoutMs when the caller set no timeout. The
|
|
25
|
+
* Wait calls are long polls: their request's timeout bounds them, so the
|
|
26
|
+
* default skips them. A stream keeps the timeout it was given, so a default
|
|
27
|
+
* never ends a watch.
|
|
28
|
+
*/
|
|
29
|
+
export declare function withDefaultUnaryTimeout(transport: Transport, timeoutMs: number | undefined): Transport;
|
package/dist/timeout.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Code, ConnectError } from "@connectrpc/connect";
|
|
2
|
+
/** The error a call ends with when its timeout fires. */
|
|
3
|
+
export function timeoutError(timeoutMs) {
|
|
4
|
+
return new ConnectError(`the call timed out after ${timeoutMs} ms`, Code.DeadlineExceeded);
|
|
5
|
+
}
|
|
6
|
+
/** Starts the deadline for a call's timeoutMs, or returns undefined without one. */
|
|
7
|
+
export function startDeadline(timeoutMs, caller) {
|
|
8
|
+
if (timeoutMs === undefined) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
const controller = new AbortController();
|
|
12
|
+
let fired = false;
|
|
13
|
+
const timer = setTimeout(() => {
|
|
14
|
+
fired = true;
|
|
15
|
+
controller.abort(timeoutError(timeoutMs));
|
|
16
|
+
}, timeoutMs);
|
|
17
|
+
return {
|
|
18
|
+
signal: caller === undefined ? controller.signal : AbortSignal.any([controller.signal, caller]),
|
|
19
|
+
get timedOut() {
|
|
20
|
+
return fired;
|
|
21
|
+
},
|
|
22
|
+
get callerAborted() {
|
|
23
|
+
return caller?.aborted === true;
|
|
24
|
+
},
|
|
25
|
+
clear() {
|
|
26
|
+
clearTimeout(timer);
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** Runs body with a deadline signal, and reports the timeout instead of an abort. */
|
|
31
|
+
export async function runWithDeadline(opts, body) {
|
|
32
|
+
const deadline = startDeadline(opts?.timeoutMs, opts?.signal);
|
|
33
|
+
const withDeadline = (deadline === undefined ? opts : { ...opts, signal: deadline.signal });
|
|
34
|
+
try {
|
|
35
|
+
return await body(withDeadline);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
if (deadline?.timedOut === true && !deadline.callerAborted) {
|
|
39
|
+
throw timeoutError(opts?.timeoutMs);
|
|
40
|
+
}
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
deadline?.clear();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Gives every unary call defaultTimeoutMs when the caller set no timeout. The
|
|
49
|
+
* Wait calls are long polls: their request's timeout bounds them, so the
|
|
50
|
+
* default skips them. A stream keeps the timeout it was given, so a default
|
|
51
|
+
* never ends a watch.
|
|
52
|
+
*/
|
|
53
|
+
export function withDefaultUnaryTimeout(transport, timeoutMs) {
|
|
54
|
+
if (timeoutMs === undefined) {
|
|
55
|
+
return transport;
|
|
56
|
+
}
|
|
57
|
+
const unary = transport.unary.bind(transport);
|
|
58
|
+
return {
|
|
59
|
+
unary: (method, signal, timeout, header, input, contextValues) => unary(method, signal, timeout ?? (isWaitCall(method) ? undefined : timeoutMs), header, input, contextValues),
|
|
60
|
+
stream: transport.stream.bind(transport),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
// WaitJob, WaitSandbox, WaitService and WaitOperation: the server holds the
|
|
64
|
+
// call for the timeout the request names, so a client default would cut it.
|
|
65
|
+
function isWaitCall(method) {
|
|
66
|
+
return method.name.startsWith("Wait");
|
|
67
|
+
}
|
package/dist/version.js
ADDED
package/dist/wait.js
CHANGED
|
@@ -1,32 +1,35 @@
|
|
|
1
1
|
import { abortReason } from "./retry.js";
|
|
2
|
+
import { runWithDeadline } from "./timeout.js";
|
|
2
3
|
/** Asks again while the job is not in a state it waits for, and returns the answer when it is. */
|
|
3
4
|
export function waitJob(service, req, opts) {
|
|
4
|
-
return waitReached(() => service.waitJob(req,
|
|
5
|
+
return runWithDeadline(opts, (callOpts) => waitReached((o) => service.waitJob(req, o), callOpts));
|
|
5
6
|
}
|
|
6
7
|
/** Asks again while the sandbox is not in a state it waits for. */
|
|
7
8
|
export function waitSandbox(service, req, opts) {
|
|
8
|
-
return waitReached(() => service.waitSandbox(req,
|
|
9
|
+
return runWithDeadline(opts, (callOpts) => waitReached((o) => service.waitSandbox(req, o), callOpts));
|
|
9
10
|
}
|
|
10
11
|
/** Asks again while the service is not in a state it waits for. */
|
|
11
12
|
export function waitService(service, req, opts) {
|
|
12
|
-
return waitReached(() => service.waitService(req,
|
|
13
|
+
return runWithDeadline(opts, (callOpts) => waitReached((o) => service.waitService(req, o), callOpts));
|
|
13
14
|
}
|
|
14
15
|
/** Asks again while the operation is not done, and returns it when it is (wait.go:29-45). */
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
export function waitOperation(service, req, opts) {
|
|
17
|
+
return runWithDeadline(opts, async (callOpts) => {
|
|
18
|
+
for (;;) {
|
|
19
|
+
throwIfAborted(callOpts);
|
|
20
|
+
const resp = await service.waitOperation(req, callOpts);
|
|
21
|
+
const op = resp.operation;
|
|
22
|
+
if (op !== undefined && op.done) {
|
|
23
|
+
return op;
|
|
24
|
+
}
|
|
22
25
|
}
|
|
23
|
-
}
|
|
26
|
+
});
|
|
24
27
|
}
|
|
25
28
|
// The three waits share one loop: each answers reached when the state arrived.
|
|
26
29
|
async function waitReached(call, opts) {
|
|
27
30
|
for (;;) {
|
|
28
31
|
throwIfAborted(opts);
|
|
29
|
-
const resp = await call();
|
|
32
|
+
const resp = await call(opts);
|
|
30
33
|
if (resp.reached) {
|
|
31
34
|
return resp;
|
|
32
35
|
}
|
package/dist/watch.js
CHANGED
|
@@ -2,6 +2,7 @@ import { clone, create } from "@bufbuild/protobuf";
|
|
|
2
2
|
import { ListJobsRequestSchema, ListSandboxesRequestSchema, ListServicesRequestSchema, WatchJobsRequestSchema, WatchSandboxesRequestSchema, WatchServicesRequestSchema, } from "./gen/toshi/workspace/v1/workloads_pb.js";
|
|
3
3
|
import { ListEventsRequestSchema, WatchEventsRequestSchema, WatchEventsResponseSchema, } from "./gen/toshi/workspace/v1/workspace_pb.js";
|
|
4
4
|
import { READER_TAKEN, WatchState, createReader, reconnect, streamEnded } from "./stream.js";
|
|
5
|
+
import { timeoutError } from "./timeout.js";
|
|
5
6
|
/** Starts a job watch; with an empty cursor it lists the jobs first. */
|
|
6
7
|
export function openJobWatch(service, retry, req, opts) {
|
|
7
8
|
const request = clone(WatchJobsRequestSchema, req);
|
|
@@ -78,7 +79,7 @@ export function openServiceWatch(service, retry, req, opts) {
|
|
|
78
79
|
}, retry, opts);
|
|
79
80
|
}
|
|
80
81
|
async function openWatch(wire, retry, opts) {
|
|
81
|
-
const state = new WatchState(wire.request.cursor, opts?.signal);
|
|
82
|
+
const state = new WatchState(wire.request.cursor, opts?.signal, opts?.timeoutMs);
|
|
82
83
|
let snapshot;
|
|
83
84
|
if (wire.request.cursor === "") {
|
|
84
85
|
try {
|
|
@@ -88,6 +89,9 @@ async function openWatch(wire, retry, opts) {
|
|
|
88
89
|
}
|
|
89
90
|
catch (err) {
|
|
90
91
|
state.close();
|
|
92
|
+
if (state.timedOut) {
|
|
93
|
+
throw timeoutError(state.timeoutMs);
|
|
94
|
+
}
|
|
91
95
|
throw err;
|
|
92
96
|
}
|
|
93
97
|
}
|
|
@@ -124,6 +128,7 @@ async function* runWatch(wire, retry, state) {
|
|
|
124
128
|
try {
|
|
125
129
|
wire.request.cursor = state.cursor;
|
|
126
130
|
for await (const message of wire.watch(state.signal)) {
|
|
131
|
+
state.markOpen();
|
|
127
132
|
attempt = 0;
|
|
128
133
|
const keepalive = wire.keepalive(message);
|
|
129
134
|
if (keepalive !== undefined) {
|
|
@@ -147,6 +152,9 @@ async function* runWatch(wire, retry, state) {
|
|
|
147
152
|
if (state.closed) {
|
|
148
153
|
return;
|
|
149
154
|
}
|
|
155
|
+
if (state.timedOut) {
|
|
156
|
+
throw timeoutError(state.timeoutMs);
|
|
157
|
+
}
|
|
150
158
|
cause = err;
|
|
151
159
|
}
|
|
152
160
|
attempt = await reconnect(retry, attempt, cause, state);
|
|
@@ -164,7 +172,7 @@ async function* runWatch(wire, retry, state) {
|
|
|
164
172
|
* caller iterates. Every message it yields is the stream's response.
|
|
165
173
|
*/
|
|
166
174
|
export function openEventWatch(service, retry, req, opts) {
|
|
167
|
-
const state = new WatchState(req.cursor, opts?.signal);
|
|
175
|
+
const state = new WatchState(req.cursor, opts?.signal, opts?.timeoutMs);
|
|
168
176
|
const request = clone(WatchEventsRequestSchema, req);
|
|
169
177
|
request.keepalive = true;
|
|
170
178
|
const list = create(ListEventsRequestSchema, {
|
|
@@ -215,6 +223,7 @@ async function* runEvents(service, retry, request, list, listingAtStart, state)
|
|
|
215
223
|
state.cursor = event.cursor;
|
|
216
224
|
resume = event.cursor;
|
|
217
225
|
}
|
|
226
|
+
state.markOpen();
|
|
218
227
|
yield create(WatchEventsResponseSchema, { event });
|
|
219
228
|
continue;
|
|
220
229
|
}
|
|
@@ -235,6 +244,7 @@ async function* runEvents(service, retry, request, list, listingAtStart, state)
|
|
|
235
244
|
}
|
|
236
245
|
request.cursor = resume;
|
|
237
246
|
for await (const message of service.watchEvents(request, { signal: state.signal })) {
|
|
247
|
+
state.markOpen();
|
|
238
248
|
attempt = 0;
|
|
239
249
|
const keepalive = message.keepalive;
|
|
240
250
|
if (keepalive !== undefined) {
|
|
@@ -263,6 +273,9 @@ async function* runEvents(service, retry, request, list, listingAtStart, state)
|
|
|
263
273
|
if (state.closed) {
|
|
264
274
|
return;
|
|
265
275
|
}
|
|
276
|
+
if (state.timedOut) {
|
|
277
|
+
throw timeoutError(state.timeoutMs);
|
|
278
|
+
}
|
|
266
279
|
cause = err;
|
|
267
280
|
}
|
|
268
281
|
attempt = await reconnect(retry, attempt, cause, state);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toshicloud/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "TypeScript client for the Toshi Workspace API",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"packageManager": "pnpm@12.15.0+sha512.b3951d6d6ff4a9f13dc7cf56cd28d2b8eb37dbcc9c68e52e1e1191873ea9f82a017287ab50baadc5ccc494f036c67c0e47eb13608c2fa36f47711b61ea128e61",
|
|
@@ -10,6 +10,14 @@
|
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"default": "./dist/index.js"
|
|
12
12
|
},
|
|
13
|
+
"./node": {
|
|
14
|
+
"types": "./dist/node/index.d.ts",
|
|
15
|
+
"default": "./dist/node/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./test": {
|
|
18
|
+
"types": "./dist/test/index.d.ts",
|
|
19
|
+
"default": "./dist/test/index.js"
|
|
20
|
+
},
|
|
13
21
|
"./package.json": "./package.json"
|
|
14
22
|
},
|
|
15
23
|
"types": "./dist/index.d.ts",
|
|
@@ -39,11 +47,19 @@
|
|
|
39
47
|
"generate": "scripts/generate.sh",
|
|
40
48
|
"prepack": "pnpm run build"
|
|
41
49
|
},
|
|
42
|
-
"
|
|
50
|
+
"peerDependencies": {
|
|
43
51
|
"@bufbuild/protobuf": "^2.16.0",
|
|
44
|
-
"@connectrpc/connect": "^2.2.0"
|
|
52
|
+
"@connectrpc/connect": "^2.2.0",
|
|
53
|
+
"@connectrpc/connect-node": "^2.2.0"
|
|
54
|
+
},
|
|
55
|
+
"peerDependenciesMeta": {
|
|
56
|
+
"@connectrpc/connect-node": {
|
|
57
|
+
"optional": true
|
|
58
|
+
}
|
|
45
59
|
},
|
|
46
60
|
"devDependencies": {
|
|
61
|
+
"@bufbuild/protobuf": "^2.16.0",
|
|
62
|
+
"@connectrpc/connect": "^2.2.0",
|
|
47
63
|
"@connectrpc/connect-node": "2.2.0",
|
|
48
64
|
"@connectrpc/connect-web": "2.2.0",
|
|
49
65
|
"@eslint/js": "10.0.1",
|