@toshicloud/sdk 0.5.0 → 0.6.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.
Files changed (76) hide show
  1. package/README.md +138 -41
  2. package/dist/client.d.ts +103 -4
  3. package/dist/client.js +43 -4
  4. package/dist/errors.d.ts +74 -22
  5. package/dist/errors.js +99 -27
  6. package/dist/gen/toshi/permission_pb.d.ts +207 -2
  7. package/dist/gen/toshi/permission_pb.js +54 -2
  8. package/dist/gen/toshi/workspace/v1/account_pb.d.ts +590 -0
  9. package/dist/gen/toshi/workspace/v1/account_pb.js +75 -0
  10. package/dist/gen/toshi/workspace/v1/artifacts_pb.d.ts +14 -8
  11. package/dist/gen/toshi/workspace/v1/artifacts_pb.js +9 -7
  12. package/dist/gen/toshi/workspace/v1/audit_events_pb.d.ts +191 -6
  13. package/dist/gen/toshi/workspace/v1/audit_events_pb.js +11 -1
  14. package/dist/gen/toshi/workspace/v1/channels_pb.d.ts +1194 -21
  15. package/dist/gen/toshi/workspace/v1/channels_pb.js +185 -39
  16. package/dist/gen/toshi/workspace/v1/connections_pb.d.ts +1616 -0
  17. package/dist/gen/toshi/workspace/v1/connections_pb.js +224 -0
  18. package/dist/gen/toshi/workspace/v1/discovery_pb.d.ts +291 -43
  19. package/dist/gen/toshi/workspace/v1/discovery_pb.js +79 -30
  20. package/dist/gen/toshi/workspace/v1/display_pb.d.ts +198 -15
  21. package/dist/gen/toshi/workspace/v1/display_pb.js +52 -2
  22. package/dist/gen/toshi/workspace/v1/domains_pb.d.ts +864 -0
  23. package/dist/gen/toshi/workspace/v1/domains_pb.js +152 -0
  24. package/dist/gen/toshi/workspace/v1/fillers_pb.d.ts +12 -4
  25. package/dist/gen/toshi/workspace/v1/fillers_pb.js +1 -1
  26. package/dist/gen/toshi/workspace/v1/grants_pb.d.ts +1297 -0
  27. package/dist/gen/toshi/workspace/v1/grants_pb.js +271 -0
  28. package/dist/gen/toshi/workspace/v1/hostjobs_pb.d.ts +329 -15
  29. package/dist/gen/toshi/workspace/v1/hostjobs_pb.js +10 -5
  30. package/dist/gen/toshi/workspace/v1/hosts_pb.d.ts +196 -7
  31. package/dist/gen/toshi/workspace/v1/hosts_pb.js +12 -7
  32. package/dist/gen/toshi/workspace/v1/images_pb.d.ts +1463 -0
  33. package/dist/gen/toshi/workspace/v1/images_pb.js +263 -0
  34. package/dist/gen/toshi/workspace/v1/products_pb.d.ts +99 -296
  35. package/dist/gen/toshi/workspace/v1/products_pb.js +7 -53
  36. package/dist/gen/toshi/workspace/v1/projects_pb.d.ts +1474 -99
  37. package/dist/gen/toshi/workspace/v1/projects_pb.js +150 -33
  38. package/dist/gen/toshi/workspace/v1/recordings_pb.d.ts +181 -14
  39. package/dist/gen/toshi/workspace/v1/recordings_pb.js +54 -14
  40. package/dist/gen/toshi/workspace/v1/roles_pb.d.ts +1994 -0
  41. package/dist/gen/toshi/workspace/v1/roles_pb.js +355 -0
  42. package/dist/gen/toshi/workspace/v1/secrets_pb.d.ts +314 -8
  43. package/dist/gen/toshi/workspace/v1/secrets_pb.js +42 -18
  44. package/dist/gen/toshi/workspace/v1/storage_providers_pb.d.ts +14 -0
  45. package/dist/gen/toshi/workspace/v1/storage_providers_pb.js +1 -1
  46. package/dist/gen/toshi/workspace/v1/tokens_pb.d.ts +434 -5
  47. package/dist/gen/toshi/workspace/v1/tokens_pb.js +65 -8
  48. package/dist/gen/toshi/workspace/v1/triggers_pb.d.ts +1099 -0
  49. package/dist/gen/toshi/workspace/v1/triggers_pb.js +185 -0
  50. package/dist/gen/toshi/workspace/v1/volumes_pb.d.ts +1052 -33
  51. package/dist/gen/toshi/workspace/v1/volumes_pb.js +169 -11
  52. package/dist/gen/toshi/workspace/v1/webhook_pb.d.ts +66 -8
  53. package/dist/gen/toshi/workspace/v1/webhook_pb.js +4 -4
  54. package/dist/gen/toshi/workspace/v1/workloads_pb.d.ts +2069 -54
  55. package/dist/gen/toshi/workspace/v1/workloads_pb.js +204 -54
  56. package/dist/gen/toshi/workspace/v1/workspace_pb.d.ts +6634 -933
  57. package/dist/gen/toshi/workspace/v1/workspace_pb.js +912 -93
  58. package/dist/gen/toshi/workspace/v1/workspace_service_pb.d.ts +3209 -1378
  59. package/dist/gen/toshi/workspace/v1/workspace_service_pb.js +211 -74
  60. package/dist/index.d.ts +8 -1
  61. package/dist/index.js +8 -1
  62. package/dist/interceptors.d.ts +6 -2
  63. package/dist/interceptors.js +13 -8
  64. package/dist/retry.d.ts +18 -0
  65. package/dist/retry.js +230 -23
  66. package/dist/test/alarms.d.ts +29 -0
  67. package/dist/test/alarms.js +144 -0
  68. package/dist/test/fake.js +31 -0
  69. package/dist/test/index.d.ts +12 -2
  70. package/dist/test/index.js +12 -2
  71. package/dist/test/procedures.js +96 -5
  72. package/dist/version.d.ts +1 -1
  73. package/dist/version.js +1 -1
  74. package/dist/wait.d.ts +4 -0
  75. package/dist/wait.js +24 -1
  76. package/package.json +3 -2
@@ -0,0 +1,144 @@
1
+ import { clone, create } from "@bufbuild/protobuf";
2
+ import { DurationSchema, TimestampSchema } from "@bufbuild/protobuf/wkt";
3
+ import { Code, ConnectError } from "@connectrpc/connect";
4
+ import { DeleteServiceAlarmResponseSchema, GetServiceAlarmResponseSchema, ListServiceAlarmsResponseSchema, PauseServiceAlarmResponseSchema, ResumeServiceAlarmResponseSchema, RunServiceAlarmNowResponseSchema, ServiceAlarmMisfire, ServiceAlarmOccurrenceSchema, ServiceAlarmSchema, SetServiceAlarmResponseSchema, } from "../gen/toshi/workspace/v1/workloads_pb.js";
5
+ /** How late a missed instant may be published when SetServiceAlarm names no deadline. */
6
+ const defaultStartingDeadline = create(DurationSchema, { seconds: 600n });
7
+ /** How many occurrences GetServiceAlarm returns. */
8
+ const maxOccurrences = 20;
9
+ /**
10
+ * The alarms the fake keeps for any service ID: the fake holds no services. It
11
+ * has no clock, so an alarm publishes nothing on its own and a cron alarm has
12
+ * no next instant.
13
+ */
14
+ export class AlarmStore {
15
+ alarms = new Map();
16
+ runs = new Map();
17
+ /** Creates the alarm at generation 1, or replaces it at the next generation, resumed. */
18
+ set(request) {
19
+ const alarm = create(ServiceAlarmSchema, {
20
+ name: request.name,
21
+ timezone: request.timezone,
22
+ channel: request.channel,
23
+ event: request.event,
24
+ data: request.data.slice(),
25
+ misfire: request.misfire,
26
+ startingDeadline: clone(DurationSchema, request.startingDeadline ?? defaultStartingDeadline),
27
+ generation: 1n,
28
+ });
29
+ switch (request.schedule.case) {
30
+ case "cron":
31
+ alarm.schedule = { case: "cron", value: request.schedule.value };
32
+ break;
33
+ case "at":
34
+ alarm.schedule = { case: "at", value: clone(TimestampSchema, request.schedule.value) };
35
+ alarm.nextAt = clone(TimestampSchema, request.schedule.value);
36
+ break;
37
+ }
38
+ if (alarm.misfire === ServiceAlarmMisfire.UNSPECIFIED) {
39
+ alarm.misfire = ServiceAlarmMisfire.COALESCE;
40
+ }
41
+ const key = alarmKey(request.serviceId, request.name);
42
+ const held = this.alarms.get(key);
43
+ if (held === undefined) {
44
+ this.alarms.set(key, {
45
+ serviceId: request.serviceId,
46
+ name: request.name,
47
+ alarm,
48
+ occurrences: [],
49
+ });
50
+ }
51
+ else {
52
+ alarm.generation = held.alarm.generation + 1n;
53
+ held.alarm = alarm;
54
+ }
55
+ return create(SetServiceAlarmResponseSchema, { alarm: clone(ServiceAlarmSchema, alarm) });
56
+ }
57
+ /** Returns the alarm and its most recent occurrences, newest first. */
58
+ get(request) {
59
+ const record = this.alarm(request.serviceId, request.name);
60
+ return create(GetServiceAlarmResponseSchema, {
61
+ alarm: clone(ServiceAlarmSchema, record.alarm),
62
+ occurrences: record.occurrences
63
+ .slice(0, maxOccurrences)
64
+ .map((occurrence) => clone(ServiceAlarmOccurrenceSchema, occurrence)),
65
+ });
66
+ }
67
+ /** Returns every alarm of the service, by name. */
68
+ list(request) {
69
+ const alarms = [];
70
+ for (const record of this.alarms.values()) {
71
+ if (record.serviceId === request.serviceId) {
72
+ alarms.push(clone(ServiceAlarmSchema, record.alarm));
73
+ }
74
+ }
75
+ alarms.sort((a, b) => compareNames(a.name, b.name));
76
+ return create(ListServiceAlarmsResponseSchema, { alarms });
77
+ }
78
+ /** Removes an alarm and its occurrences. */
79
+ delete(request) {
80
+ this.alarm(request.serviceId, request.name);
81
+ this.alarms.delete(alarmKey(request.serviceId, request.name));
82
+ return create(DeleteServiceAlarmResponseSchema, {});
83
+ }
84
+ /** Pauses an alarm; pausing a paused alarm changes nothing. */
85
+ pause(request) {
86
+ const record = this.alarm(request.serviceId, request.name);
87
+ record.alarm.paused = true;
88
+ return create(PauseServiceAlarmResponseSchema, { alarm: clone(ServiceAlarmSchema, record.alarm) });
89
+ }
90
+ /** Resumes an alarm; resuming a running alarm changes nothing. */
91
+ resume(request) {
92
+ const record = this.alarm(request.serviceId, request.name);
93
+ record.alarm.paused = false;
94
+ return create(ResumeServiceAlarmResponseSchema, { alarm: clone(ServiceAlarmSchema, record.alarm) });
95
+ }
96
+ /**
97
+ * Records a manual occurrence at the alarm's generation. A request key the
98
+ * fake has already answered returns the same occurrence.
99
+ */
100
+ runNow(request) {
101
+ if (request.requestKey !== "") {
102
+ const answered = this.runs.get(request.requestKey);
103
+ if (answered !== undefined) {
104
+ return create(RunServiceAlarmNowResponseSchema, {
105
+ occurrence: clone(ServiceAlarmOccurrenceSchema, answered),
106
+ });
107
+ }
108
+ }
109
+ const record = this.alarm(request.serviceId, request.name);
110
+ const occurrence = create(ServiceAlarmOccurrenceSchema, {
111
+ generation: record.alarm.generation,
112
+ manual: true,
113
+ });
114
+ record.occurrences.unshift(occurrence);
115
+ if (request.requestKey !== "") {
116
+ this.runs.set(request.requestKey, occurrence);
117
+ }
118
+ return create(RunServiceAlarmNowResponseSchema, { occurrence: clone(ServiceAlarmOccurrenceSchema, occurrence) });
119
+ }
120
+ /** Returns the alarm of a service with this name. */
121
+ alarm(serviceId, name) {
122
+ const record = this.alarms.get(alarmKey(serviceId, name));
123
+ if (record === undefined) {
124
+ throw new ConnectError(`toshi: no alarm "${serviceId}/${name}"`, Code.NotFound);
125
+ }
126
+ return record;
127
+ }
128
+ }
129
+ /** The map key of one alarm: its service and its name. */
130
+ function alarmKey(serviceId, name) {
131
+ return JSON.stringify([serviceId, name]);
132
+ }
133
+ /** Compares two names by the bytes of their UTF-8 encoding, as Go's strings.Compare does. */
134
+ function compareNames(a, b) {
135
+ const left = new TextEncoder().encode(a);
136
+ const right = new TextEncoder().encode(b);
137
+ const shared = Math.min(left.length, right.length);
138
+ for (let index = 0; index < shared; index += 1) {
139
+ if (left[index] !== right[index]) {
140
+ return left[index] < right[index] ? -1 : 1;
141
+ }
142
+ }
143
+ return left.length - right.length;
144
+ }
package/dist/test/fake.js CHANGED
@@ -3,6 +3,7 @@ import { Code, ConnectError, createRouterTransport } from "@connectrpc/connect";
3
3
  import { createToshiClient } from "../client.js";
4
4
  import { CancelJobResponseSchema, CreateJobResponseSchema, CreateSandboxResponseSchema, GetJobResponseSchema, GetSandboxResponseSchema, ListJobsResponseSchema, ListSandboxesResponseSchema, WaitJobResponseSchema, WaitSandboxResponseSchema, WatchJobsResponseSchema, WatchSandboxesResponseSchema, WorkloadService, } from "../gen/toshi/workspace/v1/workloads_pb.js";
5
5
  import { EndReason, EventType, ExecuteResponseSchema, ExecutionExitSchema, ExecutionStartedSchema, JobSchema, JobState, JobStatusSchema, OutputEndSchema, RecordingPolicy, SandboxSchema, SandboxState, SandboxStatusSchema, WatchOutputResponseSchema, } from "../gen/toshi/workspace/v1/workspace_pb.js";
6
+ import { AlarmStore } from "./alarms.js";
6
7
  import { apiMethods, apiPackage, hookOnly as hookOnlyMethods, isApiMethod, procedurePath } from "./procedures.js";
7
8
  /** The token `client` sends. */
8
9
  export const testToken = "test-token";
@@ -93,6 +94,34 @@ const builtinTable = [
93
94
  run: (requests) => fake.executeAnswer(requests),
94
95
  }),
95
96
  ],
97
+ [
98
+ workloadMethod("setServiceAlarm"),
99
+ (fake) => ({ kind: "unary", run: (request) => fake.alarms.set(request) }),
100
+ ],
101
+ [
102
+ workloadMethod("getServiceAlarm"),
103
+ (fake) => ({ kind: "unary", run: (request) => fake.alarms.get(request) }),
104
+ ],
105
+ [
106
+ workloadMethod("listServiceAlarms"),
107
+ (fake) => ({ kind: "unary", run: (request) => fake.alarms.list(request) }),
108
+ ],
109
+ [
110
+ workloadMethod("deleteServiceAlarm"),
111
+ (fake) => ({ kind: "unary", run: (request) => fake.alarms.delete(request) }),
112
+ ],
113
+ [
114
+ workloadMethod("pauseServiceAlarm"),
115
+ (fake) => ({ kind: "unary", run: (request) => fake.alarms.pause(request) }),
116
+ ],
117
+ [
118
+ workloadMethod("resumeServiceAlarm"),
119
+ (fake) => ({ kind: "unary", run: (request) => fake.alarms.resume(request) }),
120
+ ],
121
+ [
122
+ workloadMethod("runServiceAlarmNow"),
123
+ (fake) => ({ kind: "unary", run: (request) => fake.alarms.runNow(request) }),
124
+ ],
96
125
  ];
97
126
  /** The procedures the state machine answers. The coverage test holds it against the API. */
98
127
  export const answeredProcedures = builtinTable.map(([method]) => method);
@@ -122,6 +151,8 @@ class FakeToshi {
122
151
  nextSandbox = 0;
123
152
  executions = new Map();
124
153
  nextExecution = 0;
154
+ /** The service alarms the fake holds; `builtinTable` answers from it. */
155
+ alarms = new AlarmStore();
125
156
  constructor() {
126
157
  for (const [method, make] of builtinTable) {
127
158
  this.builtin.set(procedurePath(method), make(this));
@@ -19,8 +19,8 @@
19
19
  * # What the fake answers
20
20
  *
21
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.
22
+ * with what the test set. It knows the job and the sandbox lifecycle and a
23
+ * service's alarms, and nothing else.
24
24
  *
25
25
  * - `job`, `sandbox` and `execute` set what the fake answers: a job's or a
26
26
  * sandbox's state, and the standard output, the standard error and the exit
@@ -46,6 +46,16 @@
46
46
  * - Execute answers the start message with an execution id, the standard
47
47
  * output, the standard error and the exit code the test set for the sandbox.
48
48
  * It does not read standard input.
49
+ * - SetServiceAlarm, GetServiceAlarm, ListServiceAlarms, DeleteServiceAlarm,
50
+ * PauseServiceAlarm, ResumeServiceAlarm and RunServiceAlarmNow keep alarms
51
+ * for any service ID: the fake holds no services. A set creates an alarm at
52
+ * generation 1, or replaces it at the next generation and resumes it.
53
+ * RunServiceAlarmNow records a manual occurrence, which GetServiceAlarm
54
+ * returns newest first; a run whose request key the fake has already
55
+ * answered returns the same occurrence. With no clock an alarm publishes
56
+ * nothing on its own, a cron alarm has no next instant, and the fake sets no
57
+ * time of its own: an alarm set at an instant has that instant as its next
58
+ * one. An unknown alarm is NOT_FOUND.
49
59
  *
50
60
  * Every other procedure is hook-only. An unhooked procedure the state machine
51
61
  * does not cover answers UNIMPLEMENTED, naming the procedure and the `on` call
@@ -19,8 +19,8 @@
19
19
  * # What the fake answers
20
20
  *
21
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.
22
+ * with what the test set. It knows the job and the sandbox lifecycle and a
23
+ * service's alarms, and nothing else.
24
24
  *
25
25
  * - `job`, `sandbox` and `execute` set what the fake answers: a job's or a
26
26
  * sandbox's state, and the standard output, the standard error and the exit
@@ -46,6 +46,16 @@
46
46
  * - Execute answers the start message with an execution id, the standard
47
47
  * output, the standard error and the exit code the test set for the sandbox.
48
48
  * It does not read standard input.
49
+ * - SetServiceAlarm, GetServiceAlarm, ListServiceAlarms, DeleteServiceAlarm,
50
+ * PauseServiceAlarm, ResumeServiceAlarm and RunServiceAlarmNow keep alarms
51
+ * for any service ID: the fake holds no services. A set creates an alarm at
52
+ * generation 1, or replaces it at the next generation and resumes it.
53
+ * RunServiceAlarmNow records a manual occurrence, which GetServiceAlarm
54
+ * returns newest first; a run whose request key the fake has already
55
+ * answered returns the same occurrence. With no clock an alarm publishes
56
+ * nothing on its own, a cron alarm has no next instant, and the fake sets no
57
+ * time of its own: an alarm set at an instant has that instant as its next
58
+ * one. An unknown alarm is NOT_FOUND.
49
59
  *
50
60
  * Every other procedure is hook-only. An unhooked procedure the state machine
51
61
  * does not cover answers UNIMPLEMENTED, naming the procedure and the `on` call
@@ -1,10 +1,15 @@
1
+ import { AccountService } from "../gen/toshi/workspace/v1/account_pb.js";
1
2
  import { ArtifactService, ArtifactUploadService } from "../gen/toshi/workspace/v1/artifacts_pb.js";
2
3
  import { AuditEventService } from "../gen/toshi/workspace/v1/audit_events_pb.js";
3
4
  import { ChannelService } from "../gen/toshi/workspace/v1/channels_pb.js";
4
5
  import { DisplayService } from "../gen/toshi/workspace/v1/display_pb.js";
6
+ import { DomainService } from "../gen/toshi/workspace/v1/domains_pb.js";
5
7
  import { FillerService } from "../gen/toshi/workspace/v1/fillers_pb.js";
8
+ import { GrantService } from "../gen/toshi/workspace/v1/grants_pb.js";
6
9
  import { HostJobService } from "../gen/toshi/workspace/v1/hostjobs_pb.js";
7
10
  import { HostService } from "../gen/toshi/workspace/v1/hosts_pb.js";
11
+ import { ImageService } from "../gen/toshi/workspace/v1/images_pb.js";
12
+ import { ProductService } from "../gen/toshi/workspace/v1/products_pb.js";
8
13
  import { ProjectService } from "../gen/toshi/workspace/v1/projects_pb.js";
9
14
  import { RecordingService } from "../gen/toshi/workspace/v1/recordings_pb.js";
10
15
  import { SecretService } from "../gen/toshi/workspace/v1/secrets_pb.js";
@@ -30,11 +35,16 @@ export const apiServices = [
30
35
  ArtifactUploadService,
31
36
  AuditEventService,
32
37
  FillerService,
38
+ GrantService,
33
39
  HostJobService,
34
40
  HostService,
35
41
  TokenService,
36
42
  StorageProviderService,
37
43
  WorkspaceService,
44
+ AccountService,
45
+ ImageService,
46
+ ProductService,
47
+ DomainService,
38
48
  ];
39
49
  /** The path a client calls a method by, for example `/toshi.workspace.v1.WorkloadService/CreateJob`. */
40
50
  export function procedurePath(method) {
@@ -62,17 +72,39 @@ export function isApiMethod(method) {
62
72
  * neither, so a new method is a decision and not a surprise at a caller.
63
73
  */
64
74
  export const hookOnly = [
75
+ // AccountService
76
+ AccountService.method.getAccount,
77
+ AccountService.method.listIdentities,
78
+ AccountService.method.unlinkIdentity,
79
+ // ImageService
80
+ ImageService.method.createImage,
81
+ ImageService.method.buildImage,
82
+ ImageService.method.getImage,
83
+ ImageService.method.listImages,
84
+ ImageService.method.deleteImage,
85
+ ImageService.method.updateImage,
86
+ // ProductService
87
+ ProductService.method.listProducts,
88
+ ProductService.method.updateProduct,
65
89
  // WorkloadService
66
90
  WorkloadService.method.updateJob,
67
91
  WorkloadService.method.deleteJob,
68
92
  WorkloadService.method.updateSandbox,
69
93
  WorkloadService.method.deleteSandbox,
94
+ WorkloadService.method.stopSandbox,
95
+ WorkloadService.method.startSandbox,
70
96
  WorkloadService.method.cancelExecution,
97
+ WorkloadService.method.exportEvents,
71
98
  WorkloadService.method.watchTelemetry,
72
99
  WorkloadService.method.watchGuestLog,
73
100
  WorkloadService.method.createPreviewShareLink,
74
101
  WorkloadService.method.listPreviewShareLinks,
75
102
  WorkloadService.method.revokePreviewShareLink,
103
+ WorkloadService.method.createServiceShareLink,
104
+ WorkloadService.method.listServiceShareLinks,
105
+ WorkloadService.method.revokeServiceShareLink,
106
+ WorkloadService.method.executeCommand,
107
+ WorkloadService.method.answerPrompt,
76
108
  WorkloadService.method.createService,
77
109
  WorkloadService.method.getService,
78
110
  WorkloadService.method.listServices,
@@ -84,13 +116,28 @@ export const hookOnly = [
84
116
  WorkloadService.method.stopService,
85
117
  WorkloadService.method.setServiceMode,
86
118
  WorkloadService.method.wakeService,
119
+ // The fake holds no workloads: the operator's holds are answered by hooks.
120
+ WorkloadService.method.holdWorkload,
121
+ WorkloadService.method.releaseWorkload,
87
122
  WorkloadService.method.holdService,
88
123
  WorkloadService.method.releaseService,
124
+ WorkloadService.method.listServiceRevisions,
125
+ WorkloadService.method.getServiceRevision,
126
+ WorkloadService.method.rollbackService,
127
+ WorkloadService.method.listServiceActivations,
89
128
  WorkloadService.method.listServiceRuns,
90
129
  WorkloadService.method.listEvents,
91
130
  WorkloadService.method.watchEvents,
92
131
  WorkloadService.method.getOperation,
93
132
  WorkloadService.method.waitOperation,
133
+ // The fake holds no services: their sign-ins are answered by hooks.
134
+ WorkloadService.method.startServiceSignIn,
135
+ WorkloadService.method.answerServiceSignIn,
136
+ WorkloadService.method.cancelServiceSignIn,
137
+ WorkloadService.method.getServiceSignInStatus,
138
+ // The fake keeps no event log: listEvents and watchEvents are hook-only, so
139
+ // an emitted event would be one no call can read back.
140
+ WorkloadService.method.emitWorkloadEvent,
94
141
  // SecretService
95
142
  SecretService.method.createSecretSet,
96
143
  SecretService.method.listSecretSets,
@@ -102,8 +149,11 @@ export const hookOnly = [
102
149
  SecretService.method.grantSecretSet,
103
150
  SecretService.method.revokeSecretSet,
104
151
  SecretService.method.listSecretSetGrants,
152
+ // The fake holds no secret sets.
153
+ SecretService.method.updateSecretSet,
105
154
  // ProjectService
106
155
  ProjectService.method.listProjects,
156
+ ProjectService.method.updateProject,
107
157
  ProjectService.method.getProject,
108
158
  ProjectService.method.listRepositories,
109
159
  ProjectService.method.createProject,
@@ -115,6 +165,15 @@ export const hookOnly = [
115
165
  ProjectService.method.createEmbedToken,
116
166
  ProjectService.method.listEmbedTokens,
117
167
  ProjectService.method.revokeEmbedToken,
168
+ ProjectService.method.getProjectNetwork,
169
+ ProjectService.method.setProjectNetwork,
170
+ ProjectService.method.getEnvironmentNetwork,
171
+ ProjectService.method.setEnvironmentNetwork,
172
+ // The fake holds no projects or environments.
173
+ ProjectService.method.updateEnvironment,
174
+ ProjectService.method.getEnvironmentRetention,
175
+ ProjectService.method.setEnvironmentRetention,
176
+ ProjectService.method.setEnvironmentVolumes,
118
177
  // WebhookService
119
178
  WebhookService.method.createWebhookEndpoint,
120
179
  WebhookService.method.getWebhookEndpoint,
@@ -147,6 +206,9 @@ export const hookOnly = [
147
206
  ChannelService.method.deleteChannel,
148
207
  ChannelService.method.listForwards,
149
208
  ChannelService.method.getLink,
209
+ ChannelService.method.sendChannelMessage,
210
+ ChannelService.method.tailChannelMessages,
211
+ ChannelService.method.replayChannelMessage,
150
212
  // RecordingService
151
213
  RecordingService.method.listRecordings,
152
214
  RecordingService.method.getRecording,
@@ -155,8 +217,15 @@ export const hookOnly = [
155
217
  RecordingService.method.revokeRecordingShareLink,
156
218
  // VolumeService
157
219
  VolumeService.method.listVolumes,
220
+ VolumeService.method.getVolume,
158
221
  VolumeService.method.attachVolume,
159
222
  VolumeService.method.detachVolume,
223
+ VolumeService.method.listVolumeVersions,
224
+ VolumeService.method.restoreVolume,
225
+ // The fake holds no volumes.
226
+ VolumeService.method.createVolume,
227
+ VolumeService.method.updateVolume,
228
+ VolumeService.method.deleteVolume,
160
229
  // ArtifactService
161
230
  ArtifactService.method.listArtifacts,
162
231
  ArtifactService.method.getArtifact,
@@ -166,12 +235,22 @@ export const hookOnly = [
166
235
  ArtifactUploadService.method.completeUpload,
167
236
  // AuditEventService
168
237
  AuditEventService.method.listAuditEvents,
238
+ AuditEventService.method.exportAuditEvents,
169
239
  // FillerService
170
240
  FillerService.method.listFillers,
171
241
  FillerService.method.putFiller,
172
242
  FillerService.method.deleteFiller,
173
243
  FillerService.method.setFillerEnabled,
174
244
  FillerService.method.listFillerRuns,
245
+ // GrantService
246
+ GrantService.method.createGrant,
247
+ GrantService.method.getGrant,
248
+ GrantService.method.listGrants,
249
+ GrantService.method.acceptGrant,
250
+ GrantService.method.declineGrant,
251
+ GrantService.method.updateGrant,
252
+ GrantService.method.revokeGrant,
253
+ GrantService.method.leaveGrant,
175
254
  // HostJobService
176
255
  HostJobService.method.listHostJobs,
177
256
  HostJobService.method.getHostJob,
@@ -193,6 +272,8 @@ export const hookOnly = [
193
272
  TokenService.method.listTokens,
194
273
  TokenService.method.createWorkspaceToken,
195
274
  TokenService.method.revokeToken,
275
+ TokenService.method.rollWorkspaceToken,
276
+ TokenService.method.updateToken,
196
277
  // StorageProviderService
197
278
  StorageProviderService.method.listStorageProviders,
198
279
  StorageProviderService.method.createStorageProvider,
@@ -200,14 +281,11 @@ export const hookOnly = [
200
281
  StorageProviderService.method.deleteStorageProvider,
201
282
  // WorkspaceService
202
283
  WorkspaceService.method.getWorkspace,
284
+ WorkspaceService.method.updateWorkspace,
203
285
  WorkspaceService.method.listWorkspaces,
204
286
  WorkspaceService.method.getWorkspaceBilling,
205
287
  WorkspaceService.method.getWorkspaceStatement,
206
- WorkspaceService.method.setWorkspaceOwnHostsOnly,
207
- WorkspaceService.method.setWorkspaceAllowOverflow,
208
- WorkspaceService.method.setWorkspaceAllowCacheWarm,
209
- WorkspaceService.method.setWorkspaceCancelRunOnVmDeath,
210
- WorkspaceService.method.setWorkspaceSpendLimits,
288
+ WorkspaceService.method.getWorkspaceUpcomingInvoice,
211
289
  WorkspaceService.method.listEgressIPs,
212
290
  WorkspaceService.method.getUsage,
213
291
  WorkspaceService.method.getQueueTimes,
@@ -216,4 +294,17 @@ export const hookOnly = [
216
294
  WorkspaceService.method.createCheckoutSession,
217
295
  WorkspaceService.method.createBillingPortalSession,
218
296
  WorkspaceService.method.getVersion,
297
+ WorkspaceService.method.listSeats,
298
+ WorkspaceService.method.assignSeat,
299
+ WorkspaceService.method.removeSeat,
300
+ // The fake holds no workspace record: getWorkspace is hook-only too.
301
+ WorkspaceService.method.deleteWorkspace,
302
+ WorkspaceService.method.cancelWorkspaceDeletion,
303
+ // DomainService
304
+ DomainService.method.createDomain,
305
+ DomainService.method.getDomain,
306
+ DomainService.method.listDomains,
307
+ DomainService.method.verifyDomain,
308
+ DomainService.method.issueDomain,
309
+ DomainService.method.deleteDomain,
219
310
  ];
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** The package version; test/node.test.ts keeps it equal to package.json. */
2
- export declare const VERSION = "0.5.0";
2
+ export declare const VERSION = "0.6.0";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /** The package version; test/node.test.ts keeps it equal to package.json. */
2
- export const VERSION = "0.5.0";
2
+ export const VERSION = "0.6.0";
package/dist/wait.d.ts CHANGED
@@ -3,6 +3,10 @@ import type { CallOptions } from "./client.js";
3
3
  import type { WorkloadService } from "./gen/toshi/workspace/v1/workloads_pb.js";
4
4
  import type { WaitJobRequest, WaitJobResponse, WaitSandboxRequest, WaitSandboxResponse, WaitServiceRequest, WaitServiceResponse } from "./gen/toshi/workspace/v1/workloads_pb.js";
5
5
  import type { Operation, WaitOperationRequest } from "./gen/toshi/workspace/v1/workspace_pb.js";
6
+ /** The shortest time between the start of one Wait call and the start of the next, in milliseconds. */
7
+ export declare const waitMinInterval = 1000;
8
+ /** The most added to waitMinInterval, at random and inclusive. */
9
+ export declare const waitMaxJitter: number;
6
10
  /** Asks again while the job is not in a state it waits for, and returns the answer when it is. */
7
11
  export declare function waitJob(service: Client<typeof WorkloadService>, req: WaitJobRequest, opts?: CallOptions): Promise<WaitJobResponse>;
8
12
  /** Asks again while the sandbox is not in a state it waits for. */
package/dist/wait.js CHANGED
@@ -1,5 +1,11 @@
1
- import { abortReason } from "./retry.js";
1
+ import { abortReason, sleep } from "./retry.js";
2
2
  import { runWithDeadline } from "./timeout.js";
3
+ // wait.go:13-17: one Wait call starts no sooner than waitMinInterval plus up
4
+ // to waitMaxJitter after the start of the one before.
5
+ /** The shortest time between the start of one Wait call and the start of the next, in milliseconds. */
6
+ export const waitMinInterval = 1_000;
7
+ /** The most added to waitMinInterval, at random and inclusive. */
8
+ export const waitMaxJitter = waitMinInterval / 5;
3
9
  /** Asks again while the job is not in a state it waits for, and returns the answer when it is. */
4
10
  export function waitJob(service, req, opts) {
5
11
  return runWithDeadline(opts, (callOpts) => waitReached((o) => service.waitJob(req, o), callOpts));
@@ -17,11 +23,13 @@ export function waitOperation(service, req, opts) {
17
23
  return runWithDeadline(opts, async (callOpts) => {
18
24
  for (;;) {
19
25
  throwIfAborted(callOpts);
26
+ const started = Date.now();
20
27
  const resp = await service.waitOperation(req, callOpts);
21
28
  const op = resp.operation;
22
29
  if (op !== undefined && op.done) {
23
30
  return op;
24
31
  }
32
+ await pauseWait(started, callOpts);
25
33
  }
26
34
  });
27
35
  }
@@ -29,12 +37,27 @@ export function waitOperation(service, req, opts) {
29
37
  async function waitReached(call, opts) {
30
38
  for (;;) {
31
39
  throwIfAborted(opts);
40
+ const started = Date.now();
32
41
  const resp = await call(opts);
33
42
  if (resp.reached) {
34
43
  return resp;
35
44
  }
45
+ await pauseWait(started, opts);
36
46
  }
37
47
  }
48
+ /**
49
+ * Leaves at least waitMinInterval plus up to waitMaxJitter between the start
50
+ * of the call at started and the start of the next one: it pauses for the
51
+ * difference, at once when the call already took that long, and ends with the
52
+ * caller's own error when its signal aborts (wait.go:27-40).
53
+ */
54
+ async function pauseWait(started, opts) {
55
+ const paused = waitMinInterval + Math.floor(Math.random() * (waitMaxJitter + 1)) - (Date.now() - started);
56
+ if (paused <= 0) {
57
+ return;
58
+ }
59
+ await sleep(paused, opts?.signal);
60
+ }
38
61
  // wait.go:16-18: the caller's context ends the loop with its own error.
39
62
  function throwIfAborted(opts) {
40
63
  const signal = opts?.signal;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toshicloud/sdk",
3
- "version": "0.5.0",
3
+ "version": "0.6.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",
@@ -58,7 +58,8 @@
58
58
  }
59
59
  },
60
60
  "devDependencies": {
61
- "@bufbuild/protobuf": "^2.16.0",
61
+ "@bufbuild/protobuf": "2.16.0",
62
+ "@bufbuild/protoc-gen-es": "2.16.0",
62
63
  "@connectrpc/connect": "^2.2.0",
63
64
  "@connectrpc/connect-node": "2.2.0",
64
65
  "@connectrpc/connect-web": "2.2.0",