@toshicloud/sdk 0.5.0 → 0.5.1
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 +82 -40
- package/dist/client.d.ts +33 -2
- package/dist/client.js +28 -3
- package/dist/errors.d.ts +74 -22
- package/dist/errors.js +99 -27
- package/dist/gen/toshi/permission_pb.d.ts +207 -2
- package/dist/gen/toshi/permission_pb.js +54 -2
- package/dist/gen/toshi/workspace/v1/account_pb.d.ts +590 -0
- package/dist/gen/toshi/workspace/v1/account_pb.js +75 -0
- package/dist/gen/toshi/workspace/v1/artifacts_pb.d.ts +14 -8
- package/dist/gen/toshi/workspace/v1/artifacts_pb.js +9 -7
- package/dist/gen/toshi/workspace/v1/audit_events_pb.d.ts +191 -6
- package/dist/gen/toshi/workspace/v1/audit_events_pb.js +11 -1
- package/dist/gen/toshi/workspace/v1/channels_pb.d.ts +1194 -21
- package/dist/gen/toshi/workspace/v1/channels_pb.js +185 -39
- package/dist/gen/toshi/workspace/v1/connections_pb.d.ts +1481 -0
- package/dist/gen/toshi/workspace/v1/connections_pb.js +208 -0
- package/dist/gen/toshi/workspace/v1/discovery_pb.d.ts +291 -43
- package/dist/gen/toshi/workspace/v1/discovery_pb.js +79 -30
- package/dist/gen/toshi/workspace/v1/display_pb.d.ts +198 -15
- package/dist/gen/toshi/workspace/v1/display_pb.js +52 -2
- package/dist/gen/toshi/workspace/v1/domains_pb.d.ts +864 -0
- package/dist/gen/toshi/workspace/v1/domains_pb.js +152 -0
- package/dist/gen/toshi/workspace/v1/fillers_pb.d.ts +12 -4
- package/dist/gen/toshi/workspace/v1/fillers_pb.js +1 -1
- package/dist/gen/toshi/workspace/v1/grants_pb.d.ts +1297 -0
- package/dist/gen/toshi/workspace/v1/grants_pb.js +271 -0
- package/dist/gen/toshi/workspace/v1/hostjobs_pb.d.ts +315 -15
- package/dist/gen/toshi/workspace/v1/hostjobs_pb.js +10 -5
- package/dist/gen/toshi/workspace/v1/hosts_pb.d.ts +196 -7
- package/dist/gen/toshi/workspace/v1/hosts_pb.js +12 -7
- package/dist/gen/toshi/workspace/v1/images_pb.d.ts +1463 -0
- package/dist/gen/toshi/workspace/v1/images_pb.js +263 -0
- package/dist/gen/toshi/workspace/v1/products_pb.d.ts +99 -296
- package/dist/gen/toshi/workspace/v1/products_pb.js +7 -53
- package/dist/gen/toshi/workspace/v1/projects_pb.d.ts +1474 -99
- package/dist/gen/toshi/workspace/v1/projects_pb.js +150 -33
- package/dist/gen/toshi/workspace/v1/recordings_pb.d.ts +181 -14
- package/dist/gen/toshi/workspace/v1/recordings_pb.js +54 -14
- package/dist/gen/toshi/workspace/v1/roles_pb.d.ts +1994 -0
- package/dist/gen/toshi/workspace/v1/roles_pb.js +355 -0
- package/dist/gen/toshi/workspace/v1/secrets_pb.d.ts +314 -8
- package/dist/gen/toshi/workspace/v1/secrets_pb.js +42 -18
- package/dist/gen/toshi/workspace/v1/storage_providers_pb.d.ts +14 -0
- package/dist/gen/toshi/workspace/v1/storage_providers_pb.js +1 -1
- package/dist/gen/toshi/workspace/v1/tokens_pb.d.ts +434 -5
- package/dist/gen/toshi/workspace/v1/tokens_pb.js +65 -8
- package/dist/gen/toshi/workspace/v1/triggers_pb.d.ts +1099 -0
- package/dist/gen/toshi/workspace/v1/triggers_pb.js +185 -0
- package/dist/gen/toshi/workspace/v1/volumes_pb.d.ts +1052 -33
- package/dist/gen/toshi/workspace/v1/volumes_pb.js +169 -11
- package/dist/gen/toshi/workspace/v1/webhook_pb.d.ts +66 -8
- package/dist/gen/toshi/workspace/v1/webhook_pb.js +4 -4
- package/dist/gen/toshi/workspace/v1/workloads_pb.d.ts +2043 -54
- package/dist/gen/toshi/workspace/v1/workloads_pb.js +204 -54
- package/dist/gen/toshi/workspace/v1/workspace_pb.d.ts +6290 -937
- package/dist/gen/toshi/workspace/v1/workspace_pb.js +801 -88
- package/dist/gen/toshi/workspace/v1/workspace_service_pb.d.ts +2176 -630
- package/dist/gen/toshi/workspace/v1/workspace_service_pb.js +191 -74
- package/dist/index.d.ts +8 -1
- package/dist/index.js +8 -1
- package/dist/interceptors.d.ts +6 -2
- package/dist/interceptors.js +13 -8
- package/dist/retry.d.ts +18 -0
- package/dist/retry.js +225 -23
- package/dist/test/procedures.js +100 -5
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/wait.d.ts +4 -0
- package/dist/wait.js +24 -1
- package/package.json +3 -2
package/dist/interceptors.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ScalarType } from "@bufbuild/protobuf";
|
|
2
2
|
import { Code, ConnectError } from "@connectrpc/connect";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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
|
-
/**
|
|
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
|
-
|
|
103
|
+
const waitMs = serverWaitMs(err);
|
|
104
|
+
if (attempt + 1 >= policy.maxAttempts || (!isTransportLoss(err) && waitMs === undefined)) {
|
|
100
105
|
throw err;
|
|
101
106
|
}
|
|
102
|
-
await sleep(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
9
|
-
//
|
|
10
|
-
//
|
|
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,30 @@ 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": "
|
|
47
|
-
"/toshi.workspace.v1.WorkloadService/StopService": "
|
|
48
|
-
"/toshi.workspace.v1.WorkloadService/SetServiceMode": "
|
|
49
|
-
"/toshi.workspace.v1.WorkloadService/WakeService": "
|
|
50
|
-
"/toshi.workspace.v1.WorkloadService/HoldService": "
|
|
51
|
-
"/toshi.workspace.v1.WorkloadService/ReleaseService": "
|
|
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
|
+
"/toshi.workspace.v1.WorkloadService/HoldService": "keyed",
|
|
71
|
+
"/toshi.workspace.v1.WorkloadService/ReleaseService": "keyed",
|
|
72
|
+
"/toshi.workspace.v1.WorkloadService/ListServiceRevisions": "read",
|
|
73
|
+
"/toshi.workspace.v1.WorkloadService/GetServiceRevision": "read",
|
|
74
|
+
"/toshi.workspace.v1.WorkloadService/RollbackService": "keyed",
|
|
75
|
+
"/toshi.workspace.v1.WorkloadService/ListServiceActivations": "read",
|
|
52
76
|
"/toshi.workspace.v1.WorkloadService/ListServiceRuns": "read",
|
|
53
|
-
"/toshi.workspace.v1.
|
|
77
|
+
"/toshi.workspace.v1.WorkloadService/EmitWorkloadEvent": "none",
|
|
78
|
+
"/toshi.workspace.v1.WorkloadService/SetServiceAlarm": "none",
|
|
79
|
+
"/toshi.workspace.v1.WorkloadService/GetServiceAlarm": "read",
|
|
80
|
+
"/toshi.workspace.v1.WorkloadService/ListServiceAlarms": "read",
|
|
81
|
+
"/toshi.workspace.v1.WorkloadService/DeleteServiceAlarm": "none",
|
|
82
|
+
"/toshi.workspace.v1.WorkloadService/PauseServiceAlarm": "none",
|
|
83
|
+
"/toshi.workspace.v1.WorkloadService/ResumeServiceAlarm": "none",
|
|
84
|
+
"/toshi.workspace.v1.WorkloadService/RunServiceAlarmNow": "keyed",
|
|
85
|
+
"/toshi.workspace.v1.WorkloadService/StartServiceSignIn": "none",
|
|
86
|
+
"/toshi.workspace.v1.WorkloadService/AnswerServiceSignIn": "none",
|
|
87
|
+
"/toshi.workspace.v1.WorkloadService/CancelServiceSignIn": "none",
|
|
88
|
+
"/toshi.workspace.v1.WorkloadService/GetServiceSignInStatus": "read",
|
|
89
|
+
"/toshi.workspace.v1.SecretService/CreateSecretSet": "keyed",
|
|
54
90
|
"/toshi.workspace.v1.SecretService/ListSecretSets": "read",
|
|
55
91
|
"/toshi.workspace.v1.SecretService/DeleteSecretSet": "none",
|
|
56
92
|
"/toshi.workspace.v1.SecretService/PutSecret": "none",
|
|
@@ -60,18 +96,28 @@ const procedureRetryClass = {
|
|
|
60
96
|
"/toshi.workspace.v1.SecretService/GrantSecretSet": "none",
|
|
61
97
|
"/toshi.workspace.v1.SecretService/RevokeSecretSet": "none",
|
|
62
98
|
"/toshi.workspace.v1.SecretService/ListSecretSetGrants": "read",
|
|
99
|
+
"/toshi.workspace.v1.SecretService/UpdateSecretSet": "none",
|
|
63
100
|
"/toshi.workspace.v1.ProjectService/ListProjects": "read",
|
|
101
|
+
"/toshi.workspace.v1.ProjectService/UpdateProject": "none",
|
|
64
102
|
"/toshi.workspace.v1.ProjectService/ListRepositories": "read",
|
|
65
103
|
"/toshi.workspace.v1.ProjectService/GetProject": "read",
|
|
66
104
|
"/toshi.workspace.v1.ProjectService/ListBranchRules": "read",
|
|
67
|
-
"/toshi.workspace.v1.ProjectService/CreateProject": "
|
|
105
|
+
"/toshi.workspace.v1.ProjectService/CreateProject": "keyed",
|
|
68
106
|
"/toshi.workspace.v1.ProjectService/ClaimRepository": "none",
|
|
69
|
-
"/toshi.workspace.v1.ProjectService/CreateEnvironment": "
|
|
107
|
+
"/toshi.workspace.v1.ProjectService/CreateEnvironment": "keyed",
|
|
70
108
|
"/toshi.workspace.v1.ProjectService/DeleteEnvironment": "none",
|
|
71
109
|
"/toshi.workspace.v1.ProjectService/SetBranchRules": "none",
|
|
72
110
|
"/toshi.workspace.v1.ProjectService/CreateEmbedToken": "none",
|
|
73
111
|
"/toshi.workspace.v1.ProjectService/ListEmbedTokens": "read",
|
|
74
112
|
"/toshi.workspace.v1.ProjectService/RevokeEmbedToken": "none",
|
|
113
|
+
"/toshi.workspace.v1.ProjectService/GetProjectNetwork": "read",
|
|
114
|
+
"/toshi.workspace.v1.ProjectService/SetProjectNetwork": "none",
|
|
115
|
+
"/toshi.workspace.v1.ProjectService/GetEnvironmentNetwork": "read",
|
|
116
|
+
"/toshi.workspace.v1.ProjectService/SetEnvironmentNetwork": "none",
|
|
117
|
+
"/toshi.workspace.v1.ProjectService/GetEnvironmentRetention": "read",
|
|
118
|
+
"/toshi.workspace.v1.ProjectService/SetEnvironmentRetention": "none",
|
|
119
|
+
"/toshi.workspace.v1.ProjectService/SetEnvironmentVolumes": "none",
|
|
120
|
+
"/toshi.workspace.v1.ProjectService/UpdateEnvironment": "none",
|
|
75
121
|
"/toshi.workspace.v1.WebhookService/CreateWebhookEndpoint": "keyed",
|
|
76
122
|
"/toshi.workspace.v1.WebhookService/GetWebhookEndpoint": "read",
|
|
77
123
|
"/toshi.workspace.v1.WebhookService/ListWebhookEndpoints": "read",
|
|
@@ -80,6 +126,9 @@ const procedureRetryClass = {
|
|
|
80
126
|
"/toshi.workspace.v1.WebhookService/RotateWebhookEndpointSecret": "keyed",
|
|
81
127
|
"/toshi.workspace.v1.WebhookService/ListWebhookDeliveries": "read",
|
|
82
128
|
"/toshi.workspace.v1.WebhookService/ReplayWebhookEvents": "keyed",
|
|
129
|
+
// A retry returns the session the first call opened, with replayed set and
|
|
130
|
+
// no token, and opens nothing, so it cannot give the caller a session it can
|
|
131
|
+
// use.
|
|
83
132
|
"/toshi.workspace.v1.DisplayService/OpenDisplaySession": "none",
|
|
84
133
|
"/toshi.workspace.v1.DisplayService/GetDisplaySession": "read",
|
|
85
134
|
"/toshi.workspace.v1.DisplayService/ListDisplaySessions": "read",
|
|
@@ -88,7 +137,7 @@ const procedureRetryClass = {
|
|
|
88
137
|
"/toshi.workspace.v1.DisplayService/SendDisplayPointer": "none",
|
|
89
138
|
"/toshi.workspace.v1.DisplayService/SetDisplayClipboard": "none",
|
|
90
139
|
"/toshi.workspace.v1.DisplayService/TakeDisplayScreenshot": "none",
|
|
91
|
-
"/toshi.workspace.v1.ChannelService/CreateChannel": "
|
|
140
|
+
"/toshi.workspace.v1.ChannelService/CreateChannel": "keyed",
|
|
92
141
|
"/toshi.workspace.v1.ChannelService/ListChannels": "read",
|
|
93
142
|
"/toshi.workspace.v1.ChannelService/GetChannel": "read",
|
|
94
143
|
"/toshi.workspace.v1.ChannelService/GetChannelPolicy": "read",
|
|
@@ -101,28 +150,33 @@ const procedureRetryClass = {
|
|
|
101
150
|
"/toshi.workspace.v1.ChannelService/DeleteChannel": "none",
|
|
102
151
|
"/toshi.workspace.v1.ChannelService/ListForwards": "read",
|
|
103
152
|
"/toshi.workspace.v1.ChannelService/GetLink": "read",
|
|
153
|
+
"/toshi.workspace.v1.ChannelService/ReplayChannelMessage": "none",
|
|
154
|
+
"/toshi.workspace.v1.ChannelService/SendChannelMessage": "keyed",
|
|
155
|
+
"/toshi.workspace.v1.ChannelService/TailChannelMessages": "none",
|
|
104
156
|
"/toshi.workspace.v1.RecordingService/ListRecordings": "read",
|
|
105
157
|
"/toshi.workspace.v1.RecordingService/GetRecording": "read",
|
|
106
158
|
"/toshi.workspace.v1.RecordingService/CreateRecordingShareLink": "none",
|
|
107
159
|
"/toshi.workspace.v1.RecordingService/ListRecordingShareLinks": "read",
|
|
108
160
|
"/toshi.workspace.v1.RecordingService/RevokeRecordingShareLink": "none",
|
|
109
161
|
"/toshi.workspace.v1.VolumeService/ListVolumes": "read",
|
|
162
|
+
"/toshi.workspace.v1.VolumeService/GetVolume": "read",
|
|
110
163
|
"/toshi.workspace.v1.VolumeService/AttachVolume": "none",
|
|
111
164
|
"/toshi.workspace.v1.VolumeService/DetachVolume": "none",
|
|
165
|
+
"/toshi.workspace.v1.VolumeService/ListVolumeVersions": "read",
|
|
166
|
+
"/toshi.workspace.v1.VolumeService/RestoreVolume": "none",
|
|
167
|
+
"/toshi.workspace.v1.VolumeService/CreateVolume": "keyed",
|
|
168
|
+
"/toshi.workspace.v1.VolumeService/UpdateVolume": "none",
|
|
169
|
+
"/toshi.workspace.v1.VolumeService/DeleteVolume": "none",
|
|
112
170
|
"/toshi.workspace.v1.ArtifactService/ListArtifacts": "read",
|
|
113
171
|
"/toshi.workspace.v1.ArtifactService/GetArtifact": "read",
|
|
114
172
|
"/toshi.workspace.v1.ArtifactService/GetArtifactDownloadURL": "read",
|
|
115
173
|
"/toshi.workspace.v1.ArtifactUploadService/ReserveUpload": "none",
|
|
116
174
|
"/toshi.workspace.v1.ArtifactUploadService/CompleteUpload": "none",
|
|
117
175
|
"/toshi.workspace.v1.WorkspaceService/GetWorkspace": "read",
|
|
176
|
+
"/toshi.workspace.v1.WorkspaceService/UpdateWorkspace": "none",
|
|
118
177
|
"/toshi.workspace.v1.WorkspaceService/ListWorkspaces": "read",
|
|
119
178
|
"/toshi.workspace.v1.WorkspaceService/GetWorkspaceBilling": "read",
|
|
120
179
|
"/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",
|
|
126
180
|
"/toshi.workspace.v1.WorkspaceService/ListEgressIPs": "read",
|
|
127
181
|
"/toshi.workspace.v1.WorkspaceService/GetUsage": "read",
|
|
128
182
|
"/toshi.workspace.v1.WorkspaceService/GetQueueTimes": "read",
|
|
@@ -131,38 +185,99 @@ const procedureRetryClass = {
|
|
|
131
185
|
"/toshi.workspace.v1.WorkspaceService/CreateCheckoutSession": "none",
|
|
132
186
|
"/toshi.workspace.v1.WorkspaceService/CreateBillingPortalSession": "none",
|
|
133
187
|
"/toshi.workspace.v1.WorkspaceService/GetVersion": "read",
|
|
188
|
+
"/toshi.workspace.v1.WorkspaceService/ListSeats": "read",
|
|
189
|
+
"/toshi.workspace.v1.WorkspaceService/AssignSeat": "none",
|
|
190
|
+
"/toshi.workspace.v1.WorkspaceService/RemoveSeat": "none",
|
|
191
|
+
"/toshi.workspace.v1.WorkspaceService/DeleteWorkspace": "none",
|
|
192
|
+
"/toshi.workspace.v1.WorkspaceService/CancelWorkspaceDeletion": "none",
|
|
134
193
|
"/toshi.workspace.v1.HostJobService/ListHostJobs": "read",
|
|
135
194
|
"/toshi.workspace.v1.HostJobService/GetHostJob": "read",
|
|
136
195
|
"/toshi.workspace.v1.HostJobService/CancelHostJob": "none",
|
|
137
196
|
"/toshi.workspace.v1.HostJobService/GetHostJobFailure": "read",
|
|
138
197
|
"/toshi.workspace.v1.HostJobService/GetHostJobTelemetry": "read",
|
|
139
|
-
|
|
198
|
+
// Not a read: it hands out a debug key and a suspended workspace is refused
|
|
199
|
+
// it; the request carries no request_key, so it is never retried.
|
|
200
|
+
"/toshi.workspace.v1.HostJobService/GetHostJobDebugAccess": "none",
|
|
140
201
|
"/toshi.workspace.v1.HostJobService/StopHostJobDebug": "none",
|
|
141
202
|
"/toshi.workspace.v1.HostService/ListHosts": "read",
|
|
142
203
|
"/toshi.workspace.v1.HostService/ListHostStorage": "read",
|
|
143
204
|
"/toshi.workspace.v1.HostService/ListHostAlerts": "read",
|
|
144
205
|
"/toshi.workspace.v1.HostService/SetHostState": "none",
|
|
145
206
|
"/toshi.workspace.v1.HostService/RetireHost": "none",
|
|
146
|
-
"/toshi.workspace.v1.HostService/CreateCloudHost": "
|
|
207
|
+
"/toshi.workspace.v1.HostService/CreateCloudHost": "keyed",
|
|
147
208
|
"/toshi.workspace.v1.HostService/CheckCloudHost": "none",
|
|
148
209
|
"/toshi.workspace.v1.HostService/CreateEnrollmentToken": "none",
|
|
149
210
|
"/toshi.workspace.v1.TokenService/ListTokens": "read",
|
|
150
211
|
"/toshi.workspace.v1.TokenService/CreateWorkspaceToken": "none",
|
|
212
|
+
"/toshi.workspace.v1.TokenService/RollWorkspaceToken": "none",
|
|
213
|
+
"/toshi.workspace.v1.TokenService/UpdateToken": "none",
|
|
151
214
|
"/toshi.workspace.v1.TokenService/RevokeToken": "none",
|
|
152
215
|
"/toshi.workspace.v1.AuditEventService/ListAuditEvents": "read",
|
|
216
|
+
"/toshi.workspace.v1.AuditEventService/ExportAuditEvents": "none",
|
|
153
217
|
"/toshi.workspace.v1.FillerService/ListFillers": "read",
|
|
154
218
|
"/toshi.workspace.v1.FillerService/PutFiller": "none",
|
|
155
219
|
"/toshi.workspace.v1.FillerService/DeleteFiller": "none",
|
|
156
220
|
"/toshi.workspace.v1.FillerService/SetFillerEnabled": "none",
|
|
157
221
|
"/toshi.workspace.v1.FillerService/ListFillerRuns": "read",
|
|
222
|
+
"/toshi.workspace.v1.GrantService/CreateGrant": "keyed",
|
|
223
|
+
"/toshi.workspace.v1.GrantService/GetGrant": "read",
|
|
224
|
+
"/toshi.workspace.v1.GrantService/ListGrants": "read",
|
|
225
|
+
"/toshi.workspace.v1.GrantService/AcceptGrant": "none",
|
|
226
|
+
"/toshi.workspace.v1.GrantService/DeclineGrant": "none",
|
|
227
|
+
"/toshi.workspace.v1.GrantService/UpdateGrant": "none",
|
|
228
|
+
"/toshi.workspace.v1.GrantService/RevokeGrant": "none",
|
|
229
|
+
"/toshi.workspace.v1.GrantService/LeaveGrant": "none",
|
|
158
230
|
"/toshi.workspace.v1.StorageProviderService/ListStorageProviders": "read",
|
|
159
|
-
"/toshi.workspace.v1.StorageProviderService/CreateStorageProvider": "
|
|
231
|
+
"/toshi.workspace.v1.StorageProviderService/CreateStorageProvider": "keyed",
|
|
160
232
|
"/toshi.workspace.v1.StorageProviderService/CheckStorageProvider": "none",
|
|
161
233
|
"/toshi.workspace.v1.StorageProviderService/DeleteStorageProvider": "none",
|
|
162
234
|
"/toshi.workspace.v1.ProductService/ListProducts": "read",
|
|
163
235
|
"/toshi.workspace.v1.ProductService/UpdateProduct": "none",
|
|
164
|
-
"/toshi.workspace.v1.ProductService/RemoveProduct": "none",
|
|
165
236
|
"/toshi.workspace.v1.DiscoveryService/Discover": "read",
|
|
237
|
+
"/toshi.workspace.v1.ConnectionService/ListConnections": "read",
|
|
238
|
+
"/toshi.workspace.v1.ConnectionService/GetConnection": "read",
|
|
239
|
+
"/toshi.workspace.v1.ConnectionService/CreateConnection": "keyed",
|
|
240
|
+
"/toshi.workspace.v1.ConnectionService/DeleteConnection": "keyed",
|
|
241
|
+
"/toshi.workspace.v1.ConnectionService/RotateConnectionSecret": "keyed",
|
|
242
|
+
"/toshi.workspace.v1.ConnectionService/UpdateConnection": "none",
|
|
243
|
+
"/toshi.workspace.v1.ConnectionService/ListConnectionDeliveries": "read",
|
|
244
|
+
"/toshi.workspace.v1.ConnectionService/GetConnectionDelivery": "read",
|
|
245
|
+
"/toshi.workspace.v1.ConnectionService/ReplayConnectionDelivery": "keyed",
|
|
246
|
+
"/toshi.workspace.v1.ImageService/BuildImage": "none",
|
|
247
|
+
"/toshi.workspace.v1.ImageService/CreateImage": "keyed",
|
|
248
|
+
"/toshi.workspace.v1.ImageService/DeleteImage": "none",
|
|
249
|
+
"/toshi.workspace.v1.ImageService/GetImage": "read",
|
|
250
|
+
"/toshi.workspace.v1.ImageService/ListImages": "read",
|
|
251
|
+
"/toshi.workspace.v1.ImageService/UpdateImage": "none",
|
|
252
|
+
"/toshi.workspace.v1.RoleService/ListRoles": "read",
|
|
253
|
+
"/toshi.workspace.v1.RoleService/CreateRole": "keyed",
|
|
254
|
+
"/toshi.workspace.v1.RoleService/UpdateRole": "none",
|
|
255
|
+
"/toshi.workspace.v1.RoleService/DeleteRole": "none",
|
|
256
|
+
"/toshi.workspace.v1.RoleService/ListRoleGrants": "read",
|
|
257
|
+
"/toshi.workspace.v1.RoleService/GrantRole": "none",
|
|
258
|
+
"/toshi.workspace.v1.RoleService/RevokeRole": "none",
|
|
259
|
+
"/toshi.workspace.v1.RoleService/ListMembers": "read",
|
|
260
|
+
"/toshi.workspace.v1.RoleService/RemoveMember": "none",
|
|
261
|
+
"/toshi.workspace.v1.RoleService/CreateInvitation": "none",
|
|
262
|
+
"/toshi.workspace.v1.RoleService/ListInvitations": "read",
|
|
263
|
+
"/toshi.workspace.v1.RoleService/RevokeInvitation": "none",
|
|
264
|
+
// A repeat answers ALREADY_EXISTS ("a caller who is already a member fails
|
|
265
|
+
// with ALREADY_EXISTS"), so a retry would report failure for a call that
|
|
266
|
+
// worked; its permission's level is read although it changes state.
|
|
267
|
+
"/toshi.workspace.v1.RoleService/AcceptInvitation": "none",
|
|
268
|
+
"/toshi.workspace.v1.RoleService/ApproveMember": "none",
|
|
269
|
+
"/toshi.workspace.v1.RoleService/DeclineMember": "none",
|
|
270
|
+
"/toshi.workspace.v1.TriggerService/CreateTrigger": "keyed",
|
|
271
|
+
"/toshi.workspace.v1.TriggerService/GetTrigger": "read",
|
|
272
|
+
"/toshi.workspace.v1.TriggerService/ListTriggers": "read",
|
|
273
|
+
"/toshi.workspace.v1.TriggerService/UpdateTrigger": "keyed",
|
|
274
|
+
"/toshi.workspace.v1.TriggerService/DeleteTrigger": "keyed",
|
|
275
|
+
"/toshi.workspace.v1.DomainService/CreateDomain": "keyed",
|
|
276
|
+
"/toshi.workspace.v1.DomainService/GetDomain": "read",
|
|
277
|
+
"/toshi.workspace.v1.DomainService/ListDomains": "read",
|
|
278
|
+
"/toshi.workspace.v1.DomainService/VerifyDomain": "keyed",
|
|
279
|
+
"/toshi.workspace.v1.DomainService/IssueDomain": "keyed",
|
|
280
|
+
"/toshi.workspace.v1.DomainService/DeleteDomain": "none",
|
|
166
281
|
};
|
|
167
282
|
/** The retry class of a generated procedure path; an unknown path is "none". */
|
|
168
283
|
export function retryClassFor(procedure) {
|
|
@@ -263,9 +378,96 @@ export function backoffDelay(policy, attempt) {
|
|
|
263
378
|
if (limit <= 0) {
|
|
264
379
|
return 0;
|
|
265
380
|
}
|
|
266
|
-
|
|
381
|
+
// The jitter is not a secret, so a non-cryptographic source is enough.
|
|
382
|
+
const random = Math.random();
|
|
267
383
|
return Math.floor(random * (limit + 1));
|
|
268
384
|
}
|
|
385
|
+
/**
|
|
386
|
+
* How long the server asked the client to wait before a retry, in
|
|
387
|
+
* milliseconds, or undefined when it named no wait. A RESOURCE_EXHAUSTED
|
|
388
|
+
* refusal names the wait in a google.rpc.RetryInfo detail and in its
|
|
389
|
+
* Retry-After header, the detail winning when both are there; the API's rate
|
|
390
|
+
* limit sends both, with the same whole seconds. Such a refusal is retried
|
|
391
|
+
* only when it names a wait: one without, such as a plan's limit, does not
|
|
392
|
+
* pass on a retry. An HTTP 429 without a Connect error body reaches the
|
|
393
|
+
* client as Code.Unavailable with the message "HTTP 429" and the response's
|
|
394
|
+
* headers (connect 2.2.0 protocol-connect/validate-response.js:33), and names
|
|
395
|
+
* the wait in its Retry-After. Every other error names none.
|
|
396
|
+
*/
|
|
397
|
+
export function serverWaitMs(err) {
|
|
398
|
+
if (!(err instanceof ConnectError)) {
|
|
399
|
+
return undefined;
|
|
400
|
+
}
|
|
401
|
+
if (err.code === Code.ResourceExhausted) {
|
|
402
|
+
for (const detail of err.details) {
|
|
403
|
+
if (!("type" in detail) || detail.type !== "google.rpc.RetryInfo") {
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
const ms = decodeRetryInfoMs(detail.value);
|
|
407
|
+
if (ms !== undefined) {
|
|
408
|
+
return ms;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return parseRetryAfterMs(err.metadata.get("Retry-After"));
|
|
412
|
+
}
|
|
413
|
+
if (err.code === Code.Unavailable && err.rawMessage === "HTTP 429") {
|
|
414
|
+
return parseRetryAfterMs(err.metadata.get("Retry-After"));
|
|
415
|
+
}
|
|
416
|
+
return undefined;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* The delay before retry attempt + 1: the backoff's, or the server's wait when
|
|
420
|
+
* that is longer, never more than maxBackoffMs.
|
|
421
|
+
*/
|
|
422
|
+
export function retryDelayMs(policy, attempt, waitMs) {
|
|
423
|
+
return Math.min(Math.max(backoffDelay(policy, attempt), waitMs ?? 0), policy.maxBackoffMs);
|
|
424
|
+
}
|
|
425
|
+
/** A Retry-After of whole seconds, the form the API sends; a date or anything else names no wait. */
|
|
426
|
+
function parseRetryAfterMs(value) {
|
|
427
|
+
if (value === null || !/^\s*\d+\s*$/.test(value)) {
|
|
428
|
+
return undefined;
|
|
429
|
+
}
|
|
430
|
+
const ms = Number(value.trim()) * 1000;
|
|
431
|
+
return Number.isSafeInteger(ms) ? ms : undefined;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Reads google.rpc.RetryInfo's retry_delay (1), a google.protobuf.Duration of
|
|
435
|
+
* seconds (1) and nanos (2), as milliseconds, reading the wire format itself
|
|
436
|
+
* as errors.ts does for ErrorInfo.
|
|
437
|
+
*/
|
|
438
|
+
function decodeRetryInfoMs(bytes) {
|
|
439
|
+
try {
|
|
440
|
+
const reader = new BinaryReader(bytes);
|
|
441
|
+
while (reader.pos < reader.len) {
|
|
442
|
+
const [field, wireType] = reader.tag();
|
|
443
|
+
if (field !== 1 || wireType !== WireType.LengthDelimited) {
|
|
444
|
+
reader.skip(wireType);
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
const duration = new BinaryReader(reader.bytes());
|
|
448
|
+
let seconds = 0;
|
|
449
|
+
let nanos = 0;
|
|
450
|
+
while (duration.pos < duration.len) {
|
|
451
|
+
const [part, partType] = duration.tag();
|
|
452
|
+
if (part === 1 && partType === WireType.Varint) {
|
|
453
|
+
seconds = Number(duration.int64());
|
|
454
|
+
}
|
|
455
|
+
else if (part === 2 && partType === WireType.Varint) {
|
|
456
|
+
nanos = duration.int32();
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
duration.skip(partType);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
const ms = seconds * 1000 + Math.ceil(nanos / 1e6);
|
|
463
|
+
return seconds >= 0 && nanos >= 0 && Number.isSafeInteger(ms) ? ms : undefined;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
catch {
|
|
467
|
+
return undefined;
|
|
468
|
+
}
|
|
469
|
+
return undefined;
|
|
470
|
+
}
|
|
269
471
|
/** Sleeps, rejecting at once when the signal aborts (retry.go:186-200). */
|
|
270
472
|
export function sleep(ms, signal) {
|
|
271
473
|
return new Promise((resolve, reject) => {
|