@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
@@ -1,7 +1,7 @@
1
1
  import { ScalarType } from "@bufbuild/protobuf";
2
2
  import { Code, ConnectError } from "@connectrpc/connect";
3
- import { backoffDelay, isTransportLoss, newRequestKey, procedurePath, retryClassFor, sleep, } from "./retry.js";
4
- import { asProductError } from "./errors.js";
3
+ import { isTransportLoss, newRequestKey, procedurePath, retryClassFor, retryDelayMs, serverWaitMs, sleep, } from "./retry.js";
4
+ import { asTypedError } from "./errors.js";
5
5
  /**
6
6
  * Sends `Authorization: Bearer <token>` on every call and stream, on a copy of
7
7
  * the request: it is the innermost interceptor, so none outside it sees the
@@ -79,7 +79,11 @@ export function keyInterceptor() {
79
79
  return next(req);
80
80
  };
81
81
  }
82
- /** Retries a unary read or keyed write after a transport loss, with full jitter (retry.go:98-186). */
82
+ /**
83
+ * Retries a unary read or keyed write after a transport loss, with full jitter
84
+ * (retry.go:98-186), or after a refusal that names how long to wait
85
+ * (serverWaitMs), waiting at least that long.
86
+ */
83
87
  export function retryInterceptor(retry) {
84
88
  if (retry === false) {
85
89
  return (next) => next;
@@ -96,10 +100,11 @@ export function retryInterceptor(retry) {
96
100
  return await next(req);
97
101
  }
98
102
  catch (err) {
99
- if (attempt + 1 >= policy.maxAttempts || !isTransportLoss(err)) {
103
+ const waitMs = serverWaitMs(err);
104
+ if (attempt + 1 >= policy.maxAttempts || (!isTransportLoss(err) && waitMs === undefined)) {
100
105
  throw err;
101
106
  }
102
- await sleep(backoffDelay(policy, attempt), req.signal);
107
+ await sleep(retryDelayMs(policy, attempt, waitMs), req.signal);
103
108
  }
104
109
  }
105
110
  };
@@ -199,14 +204,14 @@ function isShared(msg, desc) {
199
204
  * on a stream's messages. It is the outermost interceptor, so the retry and
200
205
  * the helpers inside see the ConnectError they always did.
201
206
  */
202
- export function productInterceptor() {
207
+ export function refusalInterceptor() {
203
208
  return (next) => async (req) => {
204
209
  let res;
205
210
  try {
206
211
  res = await next(req);
207
212
  }
208
213
  catch (err) {
209
- throw asProductError(err);
214
+ throw asTypedError(err);
210
215
  }
211
216
  if (!res.stream) {
212
217
  return res;
@@ -219,6 +224,6 @@ async function* productStream(messages) {
219
224
  yield* messages;
220
225
  }
221
226
  catch (err) {
222
- throw asProductError(err);
227
+ throw asTypedError(err);
223
228
  }
224
229
  }
package/dist/retry.d.ts CHANGED
@@ -34,6 +34,24 @@ export declare function isTransportLoss(err: unknown): boolean;
34
34
  export declare function isStreamLoss(err: unknown): boolean;
35
35
  /** A uniform jittered delay in [0, min(maxBackoffMs, initialBackoffMs * 2**attempt)]. */
36
36
  export declare function backoffDelay(policy: RetryPolicy, attempt: number): number;
37
+ /**
38
+ * How long the server asked the client to wait before a retry, in
39
+ * milliseconds, or undefined when it named no wait. A RESOURCE_EXHAUSTED
40
+ * refusal names the wait in a google.rpc.RetryInfo detail and in its
41
+ * Retry-After header, the detail winning when both are there; the API's rate
42
+ * limit sends both, with the same whole seconds. Such a refusal is retried
43
+ * only when it names a wait: one without, such as a plan's limit, does not
44
+ * pass on a retry. An HTTP 429 without a Connect error body reaches the
45
+ * client as Code.Unavailable with the message "HTTP 429" and the response's
46
+ * headers (connect 2.2.0 protocol-connect/validate-response.js:33), and names
47
+ * the wait in its Retry-After. Every other error names none.
48
+ */
49
+ export declare function serverWaitMs(err: unknown): number | undefined;
50
+ /**
51
+ * The delay before retry attempt + 1: the backoff's, or the server's wait when
52
+ * that is longer, never more than maxBackoffMs.
53
+ */
54
+ export declare function retryDelayMs(policy: RetryPolicy, attempt: number, waitMs: number | undefined): number;
37
55
  /** Sleeps, rejecting at once when the signal aborts (retry.go:186-200). */
38
56
  export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
39
57
  /** The signal's reason as an error, or a Canceled ConnectError. */
package/dist/retry.js CHANGED
@@ -1,14 +1,26 @@
1
1
  import { Code, ConnectError } from "@connectrpc/connect";
2
+ import { BinaryReader, WireType } from "@bufbuild/protobuf/wire";
2
3
  /** Five attempts, a 200 ms initial backoff and a 10 s cap (options.go:79-85). */
3
4
  export const defaultRetryPolicy = {
4
5
  maxAttempts: 5,
5
6
  initialBackoffMs: 200,
6
7
  maxBackoffMs: 10_000,
7
8
  };
8
- // One entry per procedure of the Workspace API's services. A call that changes
9
- // what exists carries a request key and is retried; a call with no key is
10
- // retried only when it reads; a stream is never retried here.
9
+ // One entry per procedure of the Workspace API's services. A stream is never
10
+ // retried here; a call whose request takes a request_key is retried with that
11
+ // key, so the server answers a retry with the first result; a read, a call
12
+ // whose permission's level is read, is retried too; every other call is not.
13
+ // test/retry.test.ts derives each class from the generated descriptors and
14
+ // fails when a regeneration moves a procedure, so an entry follows the API and
15
+ // not a list someone remembered to update. An entry whose class needs saying
16
+ // more about has it above the entry.
11
17
  const procedureRetryClass = {
18
+ "/toshi.workspace.v1.AccountService/GetAccount": "read",
19
+ "/toshi.workspace.v1.AccountService/ListIdentities": "read",
20
+ // A repeat answers NOT_FOUND ("an identity the caller does not have fails
21
+ // with NOT_FOUND"), so a retry would report failure for a call that worked;
22
+ // its permission's level is read although it changes state.
23
+ "/toshi.workspace.v1.AccountService/UnlinkIdentity": "none",
12
24
  "/toshi.workspace.v1.WorkloadService/CreateJob": "keyed",
13
25
  "/toshi.workspace.v1.WorkloadService/GetJob": "read",
14
26
  "/toshi.workspace.v1.WorkloadService/ListJobs": "read",
@@ -21,7 +33,10 @@ const procedureRetryClass = {
21
33
  "/toshi.workspace.v1.WorkloadService/WatchTelemetry": "none",
22
34
  "/toshi.workspace.v1.WorkloadService/WatchGuestLog": "none",
23
35
  "/toshi.workspace.v1.WorkloadService/Execute": "none",
36
+ "/toshi.workspace.v1.WorkloadService/ExecuteCommand": "none",
37
+ "/toshi.workspace.v1.WorkloadService/AnswerPrompt": "none",
24
38
  "/toshi.workspace.v1.WorkloadService/CancelExecution": "keyed",
39
+ "/toshi.workspace.v1.WorkloadService/ExportEvents": "none",
25
40
  "/toshi.workspace.v1.WorkloadService/GetOperation": "read",
26
41
  "/toshi.workspace.v1.WorkloadService/WaitOperation": "read",
27
42
  "/toshi.workspace.v1.WorkloadService/ListEvents": "read",
@@ -29,11 +44,16 @@ const procedureRetryClass = {
29
44
  "/toshi.workspace.v1.WorkloadService/CreatePreviewShareLink": "none",
30
45
  "/toshi.workspace.v1.WorkloadService/ListPreviewShareLinks": "read",
31
46
  "/toshi.workspace.v1.WorkloadService/RevokePreviewShareLink": "none",
47
+ "/toshi.workspace.v1.WorkloadService/CreateServiceShareLink": "none",
48
+ "/toshi.workspace.v1.WorkloadService/ListServiceShareLinks": "read",
49
+ "/toshi.workspace.v1.WorkloadService/RevokeServiceShareLink": "none",
32
50
  "/toshi.workspace.v1.WorkloadService/CreateSandbox": "keyed",
33
51
  "/toshi.workspace.v1.WorkloadService/GetSandbox": "read",
34
52
  "/toshi.workspace.v1.WorkloadService/ListSandboxes": "read",
35
53
  "/toshi.workspace.v1.WorkloadService/UpdateSandbox": "keyed",
36
54
  "/toshi.workspace.v1.WorkloadService/DeleteSandbox": "keyed",
55
+ "/toshi.workspace.v1.WorkloadService/StopSandbox": "keyed",
56
+ "/toshi.workspace.v1.WorkloadService/StartSandbox": "keyed",
37
57
  "/toshi.workspace.v1.WorkloadService/WaitSandbox": "read",
38
58
  "/toshi.workspace.v1.WorkloadService/WatchSandboxes": "none",
39
59
  "/toshi.workspace.v1.WorkloadService/CreateService": "keyed",
@@ -43,14 +63,34 @@ const procedureRetryClass = {
43
63
  "/toshi.workspace.v1.WorkloadService/DeleteService": "keyed",
44
64
  "/toshi.workspace.v1.WorkloadService/WaitService": "read",
45
65
  "/toshi.workspace.v1.WorkloadService/WatchServices": "none",
46
- "/toshi.workspace.v1.WorkloadService/StartService": "none",
47
- "/toshi.workspace.v1.WorkloadService/StopService": "none",
48
- "/toshi.workspace.v1.WorkloadService/SetServiceMode": "none",
49
- "/toshi.workspace.v1.WorkloadService/WakeService": "none",
50
- "/toshi.workspace.v1.WorkloadService/HoldService": "none",
51
- "/toshi.workspace.v1.WorkloadService/ReleaseService": "none",
66
+ "/toshi.workspace.v1.WorkloadService/StartService": "keyed",
67
+ "/toshi.workspace.v1.WorkloadService/StopService": "keyed",
68
+ "/toshi.workspace.v1.WorkloadService/SetServiceMode": "keyed",
69
+ "/toshi.workspace.v1.WorkloadService/WakeService": "keyed",
70
+ // Operator-only, and neither takes a request key: a lost answer is reported
71
+ // rather than repeated.
72
+ "/toshi.workspace.v1.WorkloadService/HoldWorkload": "none",
73
+ "/toshi.workspace.v1.WorkloadService/ReleaseWorkload": "none",
74
+ "/toshi.workspace.v1.WorkloadService/HoldService": "keyed",
75
+ "/toshi.workspace.v1.WorkloadService/ReleaseService": "keyed",
76
+ "/toshi.workspace.v1.WorkloadService/ListServiceRevisions": "read",
77
+ "/toshi.workspace.v1.WorkloadService/GetServiceRevision": "read",
78
+ "/toshi.workspace.v1.WorkloadService/RollbackService": "keyed",
79
+ "/toshi.workspace.v1.WorkloadService/ListServiceActivations": "read",
52
80
  "/toshi.workspace.v1.WorkloadService/ListServiceRuns": "read",
53
- "/toshi.workspace.v1.SecretService/CreateSecretSet": "none",
81
+ "/toshi.workspace.v1.WorkloadService/EmitWorkloadEvent": "none",
82
+ "/toshi.workspace.v1.WorkloadService/SetServiceAlarm": "none",
83
+ "/toshi.workspace.v1.WorkloadService/GetServiceAlarm": "read",
84
+ "/toshi.workspace.v1.WorkloadService/ListServiceAlarms": "read",
85
+ "/toshi.workspace.v1.WorkloadService/DeleteServiceAlarm": "none",
86
+ "/toshi.workspace.v1.WorkloadService/PauseServiceAlarm": "none",
87
+ "/toshi.workspace.v1.WorkloadService/ResumeServiceAlarm": "none",
88
+ "/toshi.workspace.v1.WorkloadService/RunServiceAlarmNow": "keyed",
89
+ "/toshi.workspace.v1.WorkloadService/StartServiceSignIn": "none",
90
+ "/toshi.workspace.v1.WorkloadService/AnswerServiceSignIn": "none",
91
+ "/toshi.workspace.v1.WorkloadService/CancelServiceSignIn": "none",
92
+ "/toshi.workspace.v1.WorkloadService/GetServiceSignInStatus": "read",
93
+ "/toshi.workspace.v1.SecretService/CreateSecretSet": "keyed",
54
94
  "/toshi.workspace.v1.SecretService/ListSecretSets": "read",
55
95
  "/toshi.workspace.v1.SecretService/DeleteSecretSet": "none",
56
96
  "/toshi.workspace.v1.SecretService/PutSecret": "none",
@@ -60,18 +100,28 @@ const procedureRetryClass = {
60
100
  "/toshi.workspace.v1.SecretService/GrantSecretSet": "none",
61
101
  "/toshi.workspace.v1.SecretService/RevokeSecretSet": "none",
62
102
  "/toshi.workspace.v1.SecretService/ListSecretSetGrants": "read",
103
+ "/toshi.workspace.v1.SecretService/UpdateSecretSet": "none",
63
104
  "/toshi.workspace.v1.ProjectService/ListProjects": "read",
105
+ "/toshi.workspace.v1.ProjectService/UpdateProject": "none",
64
106
  "/toshi.workspace.v1.ProjectService/ListRepositories": "read",
65
107
  "/toshi.workspace.v1.ProjectService/GetProject": "read",
66
108
  "/toshi.workspace.v1.ProjectService/ListBranchRules": "read",
67
- "/toshi.workspace.v1.ProjectService/CreateProject": "none",
109
+ "/toshi.workspace.v1.ProjectService/CreateProject": "keyed",
68
110
  "/toshi.workspace.v1.ProjectService/ClaimRepository": "none",
69
- "/toshi.workspace.v1.ProjectService/CreateEnvironment": "none",
111
+ "/toshi.workspace.v1.ProjectService/CreateEnvironment": "keyed",
70
112
  "/toshi.workspace.v1.ProjectService/DeleteEnvironment": "none",
71
113
  "/toshi.workspace.v1.ProjectService/SetBranchRules": "none",
72
114
  "/toshi.workspace.v1.ProjectService/CreateEmbedToken": "none",
73
115
  "/toshi.workspace.v1.ProjectService/ListEmbedTokens": "read",
74
116
  "/toshi.workspace.v1.ProjectService/RevokeEmbedToken": "none",
117
+ "/toshi.workspace.v1.ProjectService/GetProjectNetwork": "read",
118
+ "/toshi.workspace.v1.ProjectService/SetProjectNetwork": "none",
119
+ "/toshi.workspace.v1.ProjectService/GetEnvironmentNetwork": "read",
120
+ "/toshi.workspace.v1.ProjectService/SetEnvironmentNetwork": "none",
121
+ "/toshi.workspace.v1.ProjectService/GetEnvironmentRetention": "read",
122
+ "/toshi.workspace.v1.ProjectService/SetEnvironmentRetention": "none",
123
+ "/toshi.workspace.v1.ProjectService/SetEnvironmentVolumes": "none",
124
+ "/toshi.workspace.v1.ProjectService/UpdateEnvironment": "none",
75
125
  "/toshi.workspace.v1.WebhookService/CreateWebhookEndpoint": "keyed",
76
126
  "/toshi.workspace.v1.WebhookService/GetWebhookEndpoint": "read",
77
127
  "/toshi.workspace.v1.WebhookService/ListWebhookEndpoints": "read",
@@ -80,6 +130,9 @@ const procedureRetryClass = {
80
130
  "/toshi.workspace.v1.WebhookService/RotateWebhookEndpointSecret": "keyed",
81
131
  "/toshi.workspace.v1.WebhookService/ListWebhookDeliveries": "read",
82
132
  "/toshi.workspace.v1.WebhookService/ReplayWebhookEvents": "keyed",
133
+ // A retry returns the session the first call opened, with replayed set and
134
+ // no token, and opens nothing, so it cannot give the caller a session it can
135
+ // use.
83
136
  "/toshi.workspace.v1.DisplayService/OpenDisplaySession": "none",
84
137
  "/toshi.workspace.v1.DisplayService/GetDisplaySession": "read",
85
138
  "/toshi.workspace.v1.DisplayService/ListDisplaySessions": "read",
@@ -88,7 +141,7 @@ const procedureRetryClass = {
88
141
  "/toshi.workspace.v1.DisplayService/SendDisplayPointer": "none",
89
142
  "/toshi.workspace.v1.DisplayService/SetDisplayClipboard": "none",
90
143
  "/toshi.workspace.v1.DisplayService/TakeDisplayScreenshot": "none",
91
- "/toshi.workspace.v1.ChannelService/CreateChannel": "none",
144
+ "/toshi.workspace.v1.ChannelService/CreateChannel": "keyed",
92
145
  "/toshi.workspace.v1.ChannelService/ListChannels": "read",
93
146
  "/toshi.workspace.v1.ChannelService/GetChannel": "read",
94
147
  "/toshi.workspace.v1.ChannelService/GetChannelPolicy": "read",
@@ -101,28 +154,34 @@ const procedureRetryClass = {
101
154
  "/toshi.workspace.v1.ChannelService/DeleteChannel": "none",
102
155
  "/toshi.workspace.v1.ChannelService/ListForwards": "read",
103
156
  "/toshi.workspace.v1.ChannelService/GetLink": "read",
157
+ "/toshi.workspace.v1.ChannelService/ReplayChannelMessage": "none",
158
+ "/toshi.workspace.v1.ChannelService/SendChannelMessage": "keyed",
159
+ "/toshi.workspace.v1.ChannelService/TailChannelMessages": "none",
104
160
  "/toshi.workspace.v1.RecordingService/ListRecordings": "read",
105
161
  "/toshi.workspace.v1.RecordingService/GetRecording": "read",
106
162
  "/toshi.workspace.v1.RecordingService/CreateRecordingShareLink": "none",
107
163
  "/toshi.workspace.v1.RecordingService/ListRecordingShareLinks": "read",
108
164
  "/toshi.workspace.v1.RecordingService/RevokeRecordingShareLink": "none",
109
165
  "/toshi.workspace.v1.VolumeService/ListVolumes": "read",
166
+ "/toshi.workspace.v1.VolumeService/GetVolume": "read",
110
167
  "/toshi.workspace.v1.VolumeService/AttachVolume": "none",
111
168
  "/toshi.workspace.v1.VolumeService/DetachVolume": "none",
169
+ "/toshi.workspace.v1.VolumeService/ListVolumeVersions": "read",
170
+ "/toshi.workspace.v1.VolumeService/RestoreVolume": "none",
171
+ "/toshi.workspace.v1.VolumeService/CreateVolume": "keyed",
172
+ "/toshi.workspace.v1.VolumeService/UpdateVolume": "none",
173
+ "/toshi.workspace.v1.VolumeService/DeleteVolume": "none",
112
174
  "/toshi.workspace.v1.ArtifactService/ListArtifacts": "read",
113
175
  "/toshi.workspace.v1.ArtifactService/GetArtifact": "read",
114
176
  "/toshi.workspace.v1.ArtifactService/GetArtifactDownloadURL": "read",
115
177
  "/toshi.workspace.v1.ArtifactUploadService/ReserveUpload": "none",
116
178
  "/toshi.workspace.v1.ArtifactUploadService/CompleteUpload": "none",
117
179
  "/toshi.workspace.v1.WorkspaceService/GetWorkspace": "read",
180
+ "/toshi.workspace.v1.WorkspaceService/UpdateWorkspace": "none",
118
181
  "/toshi.workspace.v1.WorkspaceService/ListWorkspaces": "read",
119
182
  "/toshi.workspace.v1.WorkspaceService/GetWorkspaceBilling": "read",
120
183
  "/toshi.workspace.v1.WorkspaceService/GetWorkspaceStatement": "read",
121
- "/toshi.workspace.v1.WorkspaceService/SetWorkspaceOwnHostsOnly": "none",
122
- "/toshi.workspace.v1.WorkspaceService/SetWorkspaceAllowOverflow": "none",
123
- "/toshi.workspace.v1.WorkspaceService/SetWorkspaceAllowCacheWarm": "none",
124
- "/toshi.workspace.v1.WorkspaceService/SetWorkspaceCancelRunOnVmDeath": "none",
125
- "/toshi.workspace.v1.WorkspaceService/SetWorkspaceSpendLimits": "none",
184
+ "/toshi.workspace.v1.WorkspaceService/GetWorkspaceUpcomingInvoice": "read",
126
185
  "/toshi.workspace.v1.WorkspaceService/ListEgressIPs": "read",
127
186
  "/toshi.workspace.v1.WorkspaceService/GetUsage": "read",
128
187
  "/toshi.workspace.v1.WorkspaceService/GetQueueTimes": "read",
@@ -131,38 +190,99 @@ const procedureRetryClass = {
131
190
  "/toshi.workspace.v1.WorkspaceService/CreateCheckoutSession": "none",
132
191
  "/toshi.workspace.v1.WorkspaceService/CreateBillingPortalSession": "none",
133
192
  "/toshi.workspace.v1.WorkspaceService/GetVersion": "read",
193
+ "/toshi.workspace.v1.WorkspaceService/ListSeats": "read",
194
+ "/toshi.workspace.v1.WorkspaceService/AssignSeat": "none",
195
+ "/toshi.workspace.v1.WorkspaceService/RemoveSeat": "none",
196
+ "/toshi.workspace.v1.WorkspaceService/DeleteWorkspace": "none",
197
+ "/toshi.workspace.v1.WorkspaceService/CancelWorkspaceDeletion": "none",
134
198
  "/toshi.workspace.v1.HostJobService/ListHostJobs": "read",
135
199
  "/toshi.workspace.v1.HostJobService/GetHostJob": "read",
136
200
  "/toshi.workspace.v1.HostJobService/CancelHostJob": "none",
137
201
  "/toshi.workspace.v1.HostJobService/GetHostJobFailure": "read",
138
202
  "/toshi.workspace.v1.HostJobService/GetHostJobTelemetry": "read",
139
- "/toshi.workspace.v1.HostJobService/GetHostJobDebugAccess": "read",
203
+ // Not a read: it hands out a debug key and a suspended workspace is refused
204
+ // it; the request carries no request_key, so it is never retried.
205
+ "/toshi.workspace.v1.HostJobService/GetHostJobDebugAccess": "none",
140
206
  "/toshi.workspace.v1.HostJobService/StopHostJobDebug": "none",
141
207
  "/toshi.workspace.v1.HostService/ListHosts": "read",
142
208
  "/toshi.workspace.v1.HostService/ListHostStorage": "read",
143
209
  "/toshi.workspace.v1.HostService/ListHostAlerts": "read",
144
210
  "/toshi.workspace.v1.HostService/SetHostState": "none",
145
211
  "/toshi.workspace.v1.HostService/RetireHost": "none",
146
- "/toshi.workspace.v1.HostService/CreateCloudHost": "none",
212
+ "/toshi.workspace.v1.HostService/CreateCloudHost": "keyed",
147
213
  "/toshi.workspace.v1.HostService/CheckCloudHost": "none",
148
214
  "/toshi.workspace.v1.HostService/CreateEnrollmentToken": "none",
149
215
  "/toshi.workspace.v1.TokenService/ListTokens": "read",
150
216
  "/toshi.workspace.v1.TokenService/CreateWorkspaceToken": "none",
217
+ "/toshi.workspace.v1.TokenService/RollWorkspaceToken": "none",
218
+ "/toshi.workspace.v1.TokenService/UpdateToken": "none",
151
219
  "/toshi.workspace.v1.TokenService/RevokeToken": "none",
152
220
  "/toshi.workspace.v1.AuditEventService/ListAuditEvents": "read",
221
+ "/toshi.workspace.v1.AuditEventService/ExportAuditEvents": "none",
153
222
  "/toshi.workspace.v1.FillerService/ListFillers": "read",
154
223
  "/toshi.workspace.v1.FillerService/PutFiller": "none",
155
224
  "/toshi.workspace.v1.FillerService/DeleteFiller": "none",
156
225
  "/toshi.workspace.v1.FillerService/SetFillerEnabled": "none",
157
226
  "/toshi.workspace.v1.FillerService/ListFillerRuns": "read",
227
+ "/toshi.workspace.v1.GrantService/CreateGrant": "keyed",
228
+ "/toshi.workspace.v1.GrantService/GetGrant": "read",
229
+ "/toshi.workspace.v1.GrantService/ListGrants": "read",
230
+ "/toshi.workspace.v1.GrantService/AcceptGrant": "none",
231
+ "/toshi.workspace.v1.GrantService/DeclineGrant": "none",
232
+ "/toshi.workspace.v1.GrantService/UpdateGrant": "none",
233
+ "/toshi.workspace.v1.GrantService/RevokeGrant": "none",
234
+ "/toshi.workspace.v1.GrantService/LeaveGrant": "none",
158
235
  "/toshi.workspace.v1.StorageProviderService/ListStorageProviders": "read",
159
- "/toshi.workspace.v1.StorageProviderService/CreateStorageProvider": "none",
236
+ "/toshi.workspace.v1.StorageProviderService/CreateStorageProvider": "keyed",
160
237
  "/toshi.workspace.v1.StorageProviderService/CheckStorageProvider": "none",
161
238
  "/toshi.workspace.v1.StorageProviderService/DeleteStorageProvider": "none",
162
239
  "/toshi.workspace.v1.ProductService/ListProducts": "read",
163
240
  "/toshi.workspace.v1.ProductService/UpdateProduct": "none",
164
- "/toshi.workspace.v1.ProductService/RemoveProduct": "none",
165
241
  "/toshi.workspace.v1.DiscoveryService/Discover": "read",
242
+ "/toshi.workspace.v1.ConnectionService/ListConnections": "read",
243
+ "/toshi.workspace.v1.ConnectionService/GetConnection": "read",
244
+ "/toshi.workspace.v1.ConnectionService/CreateConnection": "keyed",
245
+ "/toshi.workspace.v1.ConnectionService/DeleteConnection": "keyed",
246
+ "/toshi.workspace.v1.ConnectionService/RotateConnectionSecret": "keyed",
247
+ "/toshi.workspace.v1.ConnectionService/UpdateConnection": "none",
248
+ "/toshi.workspace.v1.ConnectionService/ListConnectionDeliveries": "read",
249
+ "/toshi.workspace.v1.ConnectionService/GetConnectionDelivery": "read",
250
+ "/toshi.workspace.v1.ConnectionService/ReplayConnectionDelivery": "keyed",
251
+ "/toshi.workspace.v1.ImageService/BuildImage": "none",
252
+ "/toshi.workspace.v1.ImageService/CreateImage": "keyed",
253
+ "/toshi.workspace.v1.ImageService/DeleteImage": "none",
254
+ "/toshi.workspace.v1.ImageService/GetImage": "read",
255
+ "/toshi.workspace.v1.ImageService/ListImages": "read",
256
+ "/toshi.workspace.v1.ImageService/UpdateImage": "none",
257
+ "/toshi.workspace.v1.RoleService/ListRoles": "read",
258
+ "/toshi.workspace.v1.RoleService/CreateRole": "keyed",
259
+ "/toshi.workspace.v1.RoleService/UpdateRole": "none",
260
+ "/toshi.workspace.v1.RoleService/DeleteRole": "none",
261
+ "/toshi.workspace.v1.RoleService/ListRoleGrants": "read",
262
+ "/toshi.workspace.v1.RoleService/GrantRole": "none",
263
+ "/toshi.workspace.v1.RoleService/RevokeRole": "none",
264
+ "/toshi.workspace.v1.RoleService/ListMembers": "read",
265
+ "/toshi.workspace.v1.RoleService/RemoveMember": "none",
266
+ "/toshi.workspace.v1.RoleService/CreateInvitation": "none",
267
+ "/toshi.workspace.v1.RoleService/ListInvitations": "read",
268
+ "/toshi.workspace.v1.RoleService/RevokeInvitation": "none",
269
+ // A repeat answers ALREADY_EXISTS ("a caller who is already a member fails
270
+ // with ALREADY_EXISTS"), so a retry would report failure for a call that
271
+ // worked; its permission's level is read although it changes state.
272
+ "/toshi.workspace.v1.RoleService/AcceptInvitation": "none",
273
+ "/toshi.workspace.v1.RoleService/ApproveMember": "none",
274
+ "/toshi.workspace.v1.RoleService/DeclineMember": "none",
275
+ "/toshi.workspace.v1.TriggerService/CreateTrigger": "keyed",
276
+ "/toshi.workspace.v1.TriggerService/GetTrigger": "read",
277
+ "/toshi.workspace.v1.TriggerService/ListTriggers": "read",
278
+ "/toshi.workspace.v1.TriggerService/UpdateTrigger": "keyed",
279
+ "/toshi.workspace.v1.TriggerService/DeleteTrigger": "keyed",
280
+ "/toshi.workspace.v1.DomainService/CreateDomain": "keyed",
281
+ "/toshi.workspace.v1.DomainService/GetDomain": "read",
282
+ "/toshi.workspace.v1.DomainService/ListDomains": "read",
283
+ "/toshi.workspace.v1.DomainService/VerifyDomain": "keyed",
284
+ "/toshi.workspace.v1.DomainService/IssueDomain": "keyed",
285
+ "/toshi.workspace.v1.DomainService/DeleteDomain": "none",
166
286
  };
167
287
  /** The retry class of a generated procedure path; an unknown path is "none". */
168
288
  export function retryClassFor(procedure) {
@@ -263,9 +383,96 @@ export function backoffDelay(policy, attempt) {
263
383
  if (limit <= 0) {
264
384
  return 0;
265
385
  }
266
- const random = globalThis.crypto.getRandomValues(new Uint32Array(1))[0] / 2 ** 32;
386
+ // The jitter is not a secret, so a non-cryptographic source is enough.
387
+ const random = Math.random();
267
388
  return Math.floor(random * (limit + 1));
268
389
  }
390
+ /**
391
+ * How long the server asked the client to wait before a retry, in
392
+ * milliseconds, or undefined when it named no wait. A RESOURCE_EXHAUSTED
393
+ * refusal names the wait in a google.rpc.RetryInfo detail and in its
394
+ * Retry-After header, the detail winning when both are there; the API's rate
395
+ * limit sends both, with the same whole seconds. Such a refusal is retried
396
+ * only when it names a wait: one without, such as a plan's limit, does not
397
+ * pass on a retry. An HTTP 429 without a Connect error body reaches the
398
+ * client as Code.Unavailable with the message "HTTP 429" and the response's
399
+ * headers (connect 2.2.0 protocol-connect/validate-response.js:33), and names
400
+ * the wait in its Retry-After. Every other error names none.
401
+ */
402
+ export function serverWaitMs(err) {
403
+ if (!(err instanceof ConnectError)) {
404
+ return undefined;
405
+ }
406
+ if (err.code === Code.ResourceExhausted) {
407
+ for (const detail of err.details) {
408
+ if (!("type" in detail) || detail.type !== "google.rpc.RetryInfo") {
409
+ continue;
410
+ }
411
+ const ms = decodeRetryInfoMs(detail.value);
412
+ if (ms !== undefined) {
413
+ return ms;
414
+ }
415
+ }
416
+ return parseRetryAfterMs(err.metadata.get("Retry-After"));
417
+ }
418
+ if (err.code === Code.Unavailable && err.rawMessage === "HTTP 429") {
419
+ return parseRetryAfterMs(err.metadata.get("Retry-After"));
420
+ }
421
+ return undefined;
422
+ }
423
+ /**
424
+ * The delay before retry attempt + 1: the backoff's, or the server's wait when
425
+ * that is longer, never more than maxBackoffMs.
426
+ */
427
+ export function retryDelayMs(policy, attempt, waitMs) {
428
+ return Math.min(Math.max(backoffDelay(policy, attempt), waitMs ?? 0), policy.maxBackoffMs);
429
+ }
430
+ /** A Retry-After of whole seconds, the form the API sends; a date or anything else names no wait. */
431
+ function parseRetryAfterMs(value) {
432
+ if (value === null || !/^\s*\d+\s*$/.test(value)) {
433
+ return undefined;
434
+ }
435
+ const ms = Number(value.trim()) * 1000;
436
+ return Number.isSafeInteger(ms) ? ms : undefined;
437
+ }
438
+ /**
439
+ * Reads google.rpc.RetryInfo's retry_delay (1), a google.protobuf.Duration of
440
+ * seconds (1) and nanos (2), as milliseconds, reading the wire format itself
441
+ * as errors.ts does for ErrorInfo.
442
+ */
443
+ function decodeRetryInfoMs(bytes) {
444
+ try {
445
+ const reader = new BinaryReader(bytes);
446
+ while (reader.pos < reader.len) {
447
+ const [field, wireType] = reader.tag();
448
+ if (field !== 1 || wireType !== WireType.LengthDelimited) {
449
+ reader.skip(wireType);
450
+ continue;
451
+ }
452
+ const duration = new BinaryReader(reader.bytes());
453
+ let seconds = 0;
454
+ let nanos = 0;
455
+ while (duration.pos < duration.len) {
456
+ const [part, partType] = duration.tag();
457
+ if (part === 1 && partType === WireType.Varint) {
458
+ seconds = Number(duration.int64());
459
+ }
460
+ else if (part === 2 && partType === WireType.Varint) {
461
+ nanos = duration.int32();
462
+ }
463
+ else {
464
+ duration.skip(partType);
465
+ }
466
+ }
467
+ const ms = seconds * 1000 + Math.ceil(nanos / 1e6);
468
+ return seconds >= 0 && nanos >= 0 && Number.isSafeInteger(ms) ? ms : undefined;
469
+ }
470
+ }
471
+ catch {
472
+ return undefined;
473
+ }
474
+ return undefined;
475
+ }
269
476
  /** Sleeps, rejecting at once when the signal aborts (retry.go:186-200). */
270
477
  export function sleep(ms, signal) {
271
478
  return new Promise((resolve, reject) => {
@@ -0,0 +1,29 @@
1
+ import type { DeleteServiceAlarmRequest, DeleteServiceAlarmResponse, GetServiceAlarmRequest, GetServiceAlarmResponse, ListServiceAlarmsRequest, ListServiceAlarmsResponse, PauseServiceAlarmRequest, PauseServiceAlarmResponse, ResumeServiceAlarmRequest, ResumeServiceAlarmResponse, RunServiceAlarmNowRequest, RunServiceAlarmNowResponse, SetServiceAlarmRequest, SetServiceAlarmResponse } from "../gen/toshi/workspace/v1/workloads_pb.js";
2
+ /**
3
+ * The alarms the fake keeps for any service ID: the fake holds no services. It
4
+ * has no clock, so an alarm publishes nothing on its own and a cron alarm has
5
+ * no next instant.
6
+ */
7
+ export declare class AlarmStore {
8
+ private readonly alarms;
9
+ private readonly runs;
10
+ /** Creates the alarm at generation 1, or replaces it at the next generation, resumed. */
11
+ set(request: SetServiceAlarmRequest): SetServiceAlarmResponse;
12
+ /** Returns the alarm and its most recent occurrences, newest first. */
13
+ get(request: GetServiceAlarmRequest): GetServiceAlarmResponse;
14
+ /** Returns every alarm of the service, by name. */
15
+ list(request: ListServiceAlarmsRequest): ListServiceAlarmsResponse;
16
+ /** Removes an alarm and its occurrences. */
17
+ delete(request: DeleteServiceAlarmRequest): DeleteServiceAlarmResponse;
18
+ /** Pauses an alarm; pausing a paused alarm changes nothing. */
19
+ pause(request: PauseServiceAlarmRequest): PauseServiceAlarmResponse;
20
+ /** Resumes an alarm; resuming a running alarm changes nothing. */
21
+ resume(request: ResumeServiceAlarmRequest): ResumeServiceAlarmResponse;
22
+ /**
23
+ * Records a manual occurrence at the alarm's generation. A request key the
24
+ * fake has already answered returns the same occurrence.
25
+ */
26
+ runNow(request: RunServiceAlarmNowRequest): RunServiceAlarmNowResponse;
27
+ /** Returns the alarm of a service with this name. */
28
+ private alarm;
29
+ }