@toshicloud/sdk 0.5.1 → 0.7.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 (48) hide show
  1. package/README.md +39 -455
  2. package/dist/client.d.ts +76 -6
  3. package/dist/client.js +20 -4
  4. package/dist/gen/toshi/permission_pb.d.ts +12 -0
  5. package/dist/gen/toshi/permission_pb.js +14 -2
  6. package/dist/gen/toshi/workspace/v1/connections_pb.d.ts +358 -29
  7. package/dist/gen/toshi/workspace/v1/connections_pb.js +112 -24
  8. package/dist/gen/toshi/workspace/v1/domains_pb.d.ts +26 -26
  9. package/dist/gen/toshi/workspace/v1/domains_pb.js +4 -4
  10. package/dist/gen/toshi/workspace/v1/{channels_pb.d.ts → endpoints_pb.d.ts} +945 -822
  11. package/dist/gen/toshi/workspace/v1/endpoints_pb.js +488 -0
  12. package/dist/gen/toshi/workspace/v1/grants_pb.d.ts +10 -10
  13. package/dist/gen/toshi/workspace/v1/grants_pb.js +3 -3
  14. package/dist/gen/toshi/workspace/v1/hostjobs_pb.d.ts +62 -24
  15. package/dist/gen/toshi/workspace/v1/hostjobs_pb.js +1 -1
  16. package/dist/gen/toshi/workspace/v1/hosts_pb.d.ts +110 -36
  17. package/dist/gen/toshi/workspace/v1/hosts_pb.js +1 -1
  18. package/dist/gen/toshi/workspace/v1/model_credentials_pb.d.ts +654 -0
  19. package/dist/gen/toshi/workspace/v1/model_credentials_pb.js +110 -0
  20. package/dist/gen/toshi/workspace/v1/recordings_pb.d.ts +21 -2
  21. package/dist/gen/toshi/workspace/v1/recordings_pb.js +1 -1
  22. package/dist/gen/toshi/workspace/v1/roles_pb.d.ts +1 -1
  23. package/dist/gen/toshi/workspace/v1/roles_pb.js +1 -1
  24. package/dist/gen/toshi/workspace/v1/secrets_pb.d.ts +19 -2
  25. package/dist/gen/toshi/workspace/v1/secrets_pb.js +1 -1
  26. package/dist/gen/toshi/workspace/v1/triggers_pb.d.ts +28 -28
  27. package/dist/gen/toshi/workspace/v1/triggers_pb.js +6 -6
  28. package/dist/gen/toshi/workspace/v1/webhook_pb.d.ts +202 -202
  29. package/dist/gen/toshi/workspace/v1/webhook_pb.js +46 -46
  30. package/dist/gen/toshi/workspace/v1/workloads_pb.d.ts +579 -30
  31. package/dist/gen/toshi/workspace/v1/workloads_pb.js +85 -55
  32. package/dist/gen/toshi/workspace/v1/workspace_pb.d.ts +991 -123
  33. package/dist/gen/toshi/workspace/v1/workspace_pb.js +294 -102
  34. package/dist/gen/toshi/workspace/v1/workspace_service_pb.d.ts +390 -8
  35. package/dist/gen/toshi/workspace/v1/workspace_service_pb.js +75 -50
  36. package/dist/index.d.ts +2 -1
  37. package/dist/index.js +2 -1
  38. package/dist/retry.js +38 -22
  39. package/dist/test/alarms.d.ts +29 -0
  40. package/dist/test/alarms.js +144 -0
  41. package/dist/test/fake.js +31 -0
  42. package/dist/test/index.d.ts +12 -2
  43. package/dist/test/index.js +12 -2
  44. package/dist/test/procedures.js +40 -34
  45. package/dist/version.d.ts +1 -1
  46. package/dist/version.js +1 -1
  47. package/package.json +1 -1
  48. package/dist/gen/toshi/workspace/v1/channels_pb.js +0 -483
package/README.md CHANGED
@@ -1,22 +1,34 @@
1
- # toshi-ts
1
+ <p align="center">
2
+ <img src="docs/assets/toshi-logo.svg" alt="Toshi" width="120">
3
+ </p>
2
4
 
3
- A TypeScript client for the Toshi Workspace API, `toshi.workspace.v1`,
4
- published as ES modules with type declarations.
5
+ <h1 align="center">toshi-ts</h1>
5
6
 
6
- ```sh
7
- npm install @toshicloud/sdk @bufbuild/protobuf @connectrpc/connect
8
- ```
7
+ <p align="center">The TypeScript SDK for the Toshi API.</p>
9
8
 
10
- The browser entry needs a transport such as `@connectrpc/connect-web`; the
11
- Node entry needs `@connectrpc/connect-node`. Both are peer dependencies, so
12
- install the ones your application uses.
9
+ <p align="center">
10
+ <img alt="licence" src="https://img.shields.io/badge/licence-Apache--2.0-blue">
11
+ <img alt="node" src="https://img.shields.io/badge/node-22%2B-339933">
12
+ <img alt="status" src="https://img.shields.io/badge/status-early-orange">
13
+ </p>
13
14
 
14
- Versions up to 0.1.2 were published as `@toshicloud/toshi-ts`.
15
+ <p align="center">
16
+ <a href="docs/usage.md">Get started</a> ·
17
+ <a href="docs/usage.md">Docs</a> ·
18
+ <a href="https://github.com/toshicloud/toshi-ts/issues">Issues</a>
19
+ </p>
15
20
 
16
- ## Using the client
21
+ A TypeScript client for the Toshi Workspace API, `toshi.workspace.v1`, published as ES modules with type declarations, for Node and browser programs that start jobs, sandboxes and services on Toshi and follow them. It retries safe calls, resumes watches and ships an in-memory fake Toshi for tests.
17
22
 
18
- In Node, `@toshicloud/sdk/node` builds the transport and fills the server
19
- and the token from the environment:
23
+ > **Early stage.** The package stays at 0.x until a Toshi release serves the API; the latest release is the one to use.
24
+ > Interfaces may change before 1.0.
25
+ > Read the [release notes](https://github.com/toshicloud/toshi-ts/releases) when upgrading.
26
+
27
+ ## Quick start
28
+
29
+ ```sh
30
+ npm install @toshicloud/sdk @bufbuild/protobuf @connectrpc/connect @connectrpc/connect-node
31
+ ```
20
32
 
21
33
  <!-- examples/quick-start.ts -->
22
34
 
@@ -29,450 +41,22 @@ const { projects } = await toshi.project.listProjects({});
29
41
  console.log(projects.length);
30
42
  ```
31
43
 
32
- In a browser, with `@connectrpc/connect-web`:
33
-
34
- <!-- examples/browser.ts -->
35
-
36
- ```ts
37
- import { createConnectTransport } from "@connectrpc/connect-web";
38
- import { createToshiClient } from "@toshicloud/sdk";
39
-
40
- // The token the browser app already holds.
41
- declare const token: string;
42
-
43
- export const toshi = createToshiClient({
44
- transport: (interceptors) =>
45
- createConnectTransport({ baseUrl: "https://toshi.example.com", interceptors }),
46
- token,
47
- project: "default",
48
- });
49
- ```
50
-
51
- Interceptors of your own go before the client's. The Node entry takes
52
- `interceptors`; with a transport of your own, put them first:
53
- `interceptors: [...mine, ...interceptors]`. Each unary call enters them
54
- once, with its retries inside, and they never see the Authorization
55
- header: the client sets it on a copy of the request below them.
56
-
57
- The browser entry reads no environment variable and no file. The Node
58
- entry reads `TOSHI_SERVER`, `TOSHI_TOKEN`, `TOSHI_TOKEN_FILE` and
59
- `TOSHI_CA_FILE`; see Node options.
60
-
61
- ### Options
62
-
63
- - `tokenSource`: a function that gives each request attempt its token, so a
64
- refreshed token is used without a new client; it wins over `token`. See
65
- Tokens that change.
66
- - `token`: sent as `Authorization: Bearer <token>` on every call.
67
- - `project`, `environment`: fill the request's `project` and
68
- `environment` fields when they are empty. A value in the request wins.
69
- `environment` is left empty on a request with `shared: true`.
70
- - `retry`: `{ maxAttempts, initialBackoffMs, maxBackoffMs }`, by default
71
- 5, 200 ms and 10 s, with full jitter. Reads, and the calls whose request
72
- takes a `request_key`, are retried; a stream and every other call is not.
73
- `retry: false` turns off retries and watch reconnects.
74
- - `defaultTimeoutMs`: bounds each unary call except the Wait calls,
75
- which their request's timeout bounds; streams ignore it.
76
-
77
- A call may set `timeoutMs` instead: it bounds the whole call, retries
78
- included, and on a watch it bounds the open. It composes with `signal`.
79
- `ToshiOptions` is the object the client takes, and `CallOptions` the second
80
- argument of each call, which carries `signal` and `timeoutMs`.
81
-
82
- ### Node options
83
-
84
- `@toshicloud/sdk/node` builds a connect-node transport over HTTP/2 and
85
- fills what is missing from the environment:
86
-
87
- - `server`, else `TOSHI_SERVER`: the control plane's URL. Without either,
88
- the client refuses to start.
89
- - `tokenSource`, `token`, `tokenFile`, `TOSHI_TOKEN`, `TOSHI_TOKEN_FILE`, in
90
- that order. A token file is trimmed, and an empty file is an error.
91
- - `caFile`, else `TOSHI_CA_FILE`: a PEM bundle trusted besides Node's
92
- built-in roots.
93
- - `userAgent`: put in front of the User-Agent, for example "myapp/1.2".
94
- - `interceptors`: run before the client's own, in the order given.
95
- - `defaultTimeoutMs`: bounds each unary call except the Wait calls,
96
- which their request's timeout bounds; streams ignore it.
97
- - `transport`: a transport of your own; then `server` and `caFile` are not
98
- required.
99
-
100
- Every request carries `User-Agent: toshi-ts/<version> (node/<node
101
- version>)`, with `userAgent` and one space in front when it is set. The
102
- root entry sends no User-Agent: browsers own it.
103
-
104
- ### Tokens that change
105
-
106
- `tokenSource` gives the client a function it calls for the token of every
107
- request attempt, so a token that is refreshed, for example from a secret
108
- manager, is used without a new client. It wins over every other token
109
- option. The client asks it once per attempt, a retry included, and may
110
- ask it concurrently, so it must be fast: keep the token somewhere and
111
- refresh it outside the call. A source that throws, or that answers an
112
- empty token, fails the call with `UNAUTHENTICATED`, with the error as the
113
- cause, and the call is not retried.
114
-
115
- <!-- examples/token-source.ts -->
116
-
117
- ```ts
118
- // A token a refresh loop replaces; the client asks for it on every call.
119
- let token = "first-token";
120
-
121
- export const toshi = createToshiClient({
122
- project: "default",
123
- tokenSource: () => token,
124
- });
125
-
126
- export function rotate(next: string): void {
127
- token = next;
128
- }
129
- ```
130
-
131
- A token file, from `tokenFile` or `TOSHI_TOKEN_FILE`, is read again
132
- before a request when its modification time or size has changed, so a
133
- rotated token, such as a Kubernetes projected service account token, is
134
- picked up without a new client.
135
-
136
- ### The client's shape
137
-
138
- The client has one field per Workspace API service: `project`, `secret`,
139
- `webhook`, `display`, `channel`, `recording`, `volume`, `artifact`,
140
- `artifactUpload`, `workspace`, `hostJob`, `host`, `token`, `auditEvent`,
141
- `filler`, `grant`, `storageProvider`, `product`, `discovery`, `connection`,
142
- `role`, `trigger`, `account`, `image` and `domain`, each holding that service's
143
- generated client.
144
- `workload` holds `WorkloadService`'s calls, grouped by the type they
145
- name: `workload.jobs`, `workload.sandboxes` and `workload.services`, one
146
- method for each call (`create`, `get`, `list`, `update`, `delete`, `wait`,
147
- `watch`, and `cancel` on a job, `execute`, `start` and `stop` on a sandbox,
148
- `start`, `stop`, `setMode`, `listRuns`, `wake`, `hold`, `release`,
149
- `listRevisions`, `getRevision`, `rollback` and `listActivations` on a
150
- service). The calls that
151
- span the three types sit on `workload` itself: `listEvents`, `watchEvents`,
152
- `getOperation` and `waitOperation`. `run` sits at the top of the client, as
153
- `toshi run` sits at the top of the CLI.
154
-
155
- <!-- examples/client-shape.ts -->
156
-
157
- ```ts
158
- const { jobs } = await toshi.workload.jobs.list({ pageSize: 10 });
159
- const { job } = await toshi.workload.jobs.get({ id: jobs[0].id });
160
- console.log(job?.id);
161
- ```
162
-
163
- ### Lists
164
-
165
- `workload.jobs.all`, `workload.sandboxes.all`,
166
- `workload.sandboxes.allPreviewShareLinks`, `workload.services.all`,
167
- `workload.services.allRuns` and `workload.allEvents` take the request their
168
- `list` method takes and yield every item, one page after another. Each
169
- response's `nextPageToken` becomes the next request's `pageToken`, and a
170
- `pageToken` in the request resumes from that page. A service with no
171
- `all` method is one `paginate` call away: it takes the list call and a
172
- function that picks a response's items.
173
-
174
- <!-- examples/lists.ts -->
175
-
176
- ```ts
177
- import { paginate } from "@toshicloud/sdk";
44
+ The Node entry reads `TOSHI_SERVER` and `TOSHI_TOKEN` from the environment; the [usage guide](docs/usage.md) continues from there, including the browser entry.
178
45
 
179
- import { toshi } from "./client.js";
46
+ ## Documentation
180
47
 
181
- // Every job that matches the request, one page after another.
182
- for await (const job of toshi.workload.jobs.all({ pageSize: 10 })) {
183
- console.log(job.id);
184
- }
185
-
186
- // A service with no all() method: paginate takes its list call and items.
187
- for await (const project of paginate(
188
- {},
189
- (req, opts) => toshi.project.listProjects(req, opts),
190
- (resp) => resp.projects,
191
- )) {
192
- console.log(project.name);
193
- }
194
- ```
195
-
196
- `paginate` sends a copy of the request, so the caller's request keeps its
197
- fields. Every page is a call of its own, so `opts` goes to each one, and
198
- `timeoutMs` bounds a page and not the whole iteration. An error throws once
199
- and ends the iteration; leaving the loop sends no further request.
200
- `PageRequest` is a list request, which may name the page to read first with
201
- `pageToken`, and `PageResponse` a list response, which names the page after
202
- it with `nextPageToken`.
203
-
204
- ### Retries
205
-
206
- The client retries a read, and a write that carries a request key, when
207
- the connection fails: a `ConnectError` with code `Unavailable`, a
208
- connection reset or destroyed stream that `@connectrpc/connect-node`
209
- reports, or a stream the server refused before reading it
210
- (`REFUSED_STREAM`). A write that takes a request key gets a random one when the
211
- request has none, so the server applies a retried write once. Other
212
- writes are never retried.
213
-
214
- A `ResourceExhausted` refusal that names how long to wait, as the API's
215
- rate limit does, is retried the same way. The wait comes from its
216
- `google.rpc.RetryInfo` detail, else from its `Retry-After` header. So is an
217
- HTTP 429 with a `Retry-After`, which reaches the client as `Unavailable`.
218
- The client waits the longer of that wait and the backoff, never more than
219
- `maxBackoffMs`, and stops when the call's signal aborts. A
220
- `ResourceExhausted` that names no wait, such as a plan's limit, reaches the
221
- caller unchanged, as do every other error the server sends, including
222
- `Aborted` and `InvalidArgument`, and any connection failure the client
223
- cannot tell apart from a server error.
224
-
225
- `RetryPolicy` says how often and how long a call is retried, `maxAttempts`
226
- counting the first try; `defaultRetryPolicy` is the default — five
227
- attempts, a 200 ms initial backoff and a 10 s cap.
228
-
229
- ### Waiting and watching
230
-
231
- - `workload.jobs.wait(req)`, `workload.sandboxes.wait(req)` and
232
- `workload.services.wait(req)` ask again until the workload reaches the
233
- state; `workload.waitOperation(req)` asks until the operation is done. A
234
- call starts at least a second, and at most 1.2 seconds, after the start of
235
- the one before, and a signal ends a pause with its own error.
236
- - `workload.getOperation(req)` returns one operation by id: a call that
237
- takes long hands one back, and this asks for it again.
238
- - An event's payload says what happened: a trigger's `TriggerResult` names
239
- the key's `reason` and its workload, and with `process_exited` or
240
- `setup_failed` it carries the sandbox's `exitCode` and `message`.
241
- - `workload.listEvents(req)` returns the events after a cursor, oldest
242
- first, a page at a time, for a client that polls or catches up after
243
- downtime; `workload.watchEvents(req)` is the live form.
244
- - `workload.jobs.watch(req)`, `workload.sandboxes.watch(req)`,
245
- `workload.services.watch(req)`, `workload.watchEvents(req)` and
246
- `workload.jobs.watchOutput(req)` resolve to a watch you read with
247
- `for await`. A watch opens its stream when you start reading and
248
- reconnects after a connection failure from where it stopped: `cursor`
249
- for the typed watches and events, `offsets` (bytes, as `bigint`) for
250
- output. With no cursor, a typed watch lists that type first, and
251
- `snapshot` holds them once its promise resolves; `watchEvents`
252
- yields the past events page by page before the live ones. `close()` or
253
- leaving the loop ends the stream. The output watch ends at the output's
254
- end.
255
- - Every watch yields the stream's responses: a job watch yields
256
- `WatchJobsResponse` (`job`, `cursor`, `type`), an event watch yields
257
- `WatchEventsResponse` (`event`), and the output watch yields
258
- `WatchOutputResponse` (`stdout`, `stderr` or `end`).
259
-
260
- <!-- examples/watch-events.ts -->
261
-
262
- ```ts
263
- const id = "workload-id";
264
- const watch = await toshi.workload.watchEvents({ workloadId: id });
265
- for await (const { event } of watch) {
266
- console.log(event?.name);
267
- }
268
- // watch.cursor resumes a later watch after the last event read.
269
- ```
270
-
271
- Every helper takes `{ signal }`, an `AbortSignal` that ends it.
272
-
273
- ### Running a command
274
-
275
- `workload.sandboxes.execute(start)` runs a command in a running sandbox
276
- and resolves once the server has started it. The process is one stream of output chunks, `{ stream: "stdout" |
277
- "stderr", data: Uint8Array }`. `write` sends standard input in frames of
278
- at most 64 KiB and resolves once the stream has taken the last one; await
279
- it before the next `write`, which is refused while one is pending, so the
280
- client holds at most one buffer of input. `closeStdin` ends standard input
281
- after a pending write, and a later `write` is refused. `wait` settles when
282
- the command ends.
283
-
284
- <!-- examples/execute.ts -->
285
-
286
- ```ts
287
- const id = "sandbox-id";
288
- const proc = await toshi.workload.sandboxes.execute({
289
- sandboxId: id,
290
- command: { argv: ["cat"] },
291
- });
292
- await proc.write("hello\n");
293
- await proc.closeStdin();
294
- for await (const chunk of proc) {
295
- process[chunk.stream].write(chunk.data);
296
- }
297
- await proc.wait(); // rejects with ExitError on a non-zero exit
298
- ```
299
-
300
- Without a loop, `wait()` reads and drops the output itself. It rejects
301
- with `ExitError` when the command fails, is cancelled or ends without an
302
- exit status, and with `StreamLostError` when the stream ends first. The
303
- client never runs a command again by itself. Standard input cannot be
304
- replayed, so the caller decides whether to start again with the same
305
- `requestKey`. Leaving the loop early cancels the stream, and `wait()`
306
- then rejects with a `Canceled` error.
307
-
308
- Execute needs a bidirectional stream: `@connectrpc/connect-node` with
309
- `httpVersion: "2"`. In a browser, `execute` rejects at once and makes no
310
- call. Over a transport without HTTP/2, the call fails with the
311
- transport's or the server's error.
312
-
313
- ### Running a job
314
-
315
- `run(req)` creates a job with a `CreateJobRequest` and resolves to the
316
- job and its output, which is what `toshi run` does: `CreateJob`, then
317
- `WatchOutput`. It yields output chunks like an execution, and `wait()`
318
- resolves when the job exits 0 and rejects with `JobExitError` otherwise.
319
-
320
- <!-- examples/run-job.ts -->
321
-
322
- ```ts
323
- const run = await toshi.run({
324
- spec: { command: { argv: ["./test.sh"] } },
325
- });
326
- for await (const chunk of run) {
327
- process[chunk.stream].write(chunk.data);
328
- }
329
- await run.wait(); // rejects with JobExitError on a non-zero exit
330
- ```
331
-
332
- Stopping the loop stops only the output stream: the job keeps running,
333
- and `workload.jobs.cancel` ends it.
334
-
335
- ## Products that are off
336
-
337
- A call for a product that is off for the workspace rejects with a
338
- `ProductError`, `wait`, the watches and `execute` included.
339
- `isProductError(err)` tells it apart. It is still a `ConnectError` with the
340
- same `code`, and it has the product's ID (`product`), the `reason`
341
- (`ProductReason.Unavailable`), the input that turned the product off
342
- (`productCause`: `build`, `operator`, `plan` or `workspace`) and, for a
343
- product the deployment turns off, how far it has wound down (`state`:
344
- `no_new_work`, `draining` or `stopped`).
345
-
346
- <!-- examples/product-error.ts -->
347
-
348
- ```ts
349
- import { isProductError } from "@toshicloud/sdk";
350
-
351
- import { toshi } from "./client.js";
352
-
353
- // Turn a product on for the workspace.
354
- const enable = { id: "acme_ci", enabled: true };
355
- try {
356
- await toshi.product.updateProduct(enable);
357
- } catch (err) {
358
- if (!isProductError(err)) {
359
- throw err;
360
- }
361
- // Off for the workspace: which product, the input that turns it off, and,
362
- // for the deployment's config, how far it has wound down.
363
- console.log(err.product, err.productCause, err.state);
364
- }
365
- ```
366
-
367
- ## Refusals with a reason
368
-
369
- A refusal that is not a product's rejects with a `RefusalError`;
370
- `isRefusalError(err)` tells it apart. It is still a `ConnectError` with the
371
- same `code`, and its `reason` is one of `RefusalReason`:
372
- `RefusalReason.ImageInUse` when an image a workload or a revision still uses
373
- cannot be deleted, `RefusalReason.ImageNotReady` when a job, a sandbox or a
374
- service revision names a workspace image with no ready version,
375
- `RefusalReason.PlanExcludesImages` when the plan has no images, `RefusalReason.LastIdentity` when the caller's last identity cannot
376
- be unlinked, and so on for images, volumes, roles and display sessions.
377
-
378
- `workload.services.rollback` asks the revision's registry whether it still
379
- serves the revision's image before it selects it, and refuses with
380
- `RefusalReason.ImageNotServed` when the registry answered that it does not
381
- serve the image, or `RefusalReason.ImageCheckFailed` when it could not say.
382
- A revision's `imageCheck` carries the last answer: its `outcome`, the
383
- registry's `status` and `message`, and when it was asked, `checkedAt`.
384
- Reading a revision asks no registry.
385
-
386
- ## Testing with `@toshicloud/sdk/test`
387
-
388
- `createFake()` serves the whole Workspace API in memory: no server, no port,
389
- nothing to close, and no Node module, so it runs in the same runtimes as the
390
- client. `fake.client()` returns a `ToshiClient` wired to it, with the token
391
- `test-token` and the scope `default`/`production`, which is enough to drive
392
- `run`, the watchers and the generated clients from a test.
393
-
394
- <!-- examples/testing.ts -->
395
-
396
- ```ts
397
- import { createFake } from "@toshicloud/sdk/test";
398
-
399
- const fake = createFake();
400
- fake.job("job-1").stdout("deployed\n").exitCode(0);
401
-
402
- const toshi = fake.client();
403
- const run = await toshi.run({ spec: { command: { argv: ["./deploy.sh"] } } });
404
- for await (const chunk of run) {
405
- process[chunk.stream].write(chunk.data);
406
- }
407
- await run.wait();
408
- ```
409
-
410
- `fake.on(method, impl)` replaces the state machine for one method with a hook
411
- typed by that method: one call for unary, server-stream and bidirectional
412
- methods alike. `fake.calls()` returns every call the fake received, oldest
413
- first, with the procedure, the request and the headers.
414
-
415
- ## Generated code
416
-
417
- `src/gen/toshi/workspace/v1/` is generated by
418
- [`protoc-gen-es`](https://github.com/bufbuild/protobuf-es) from the protos
419
- in `proto/toshi/workspace/v1` of the
420
- [toshi-api](https://github.com/toshicloud/toshi-api) repository, one
421
- `*_pb.ts` file per proto, and `src/gen/toshi/permission_pb.ts` from
422
- `proto/toshi/permission.proto`, which they import. The generated code is committed and imports
423
- only `@bufbuild/protobuf`, so installing the package needs no generator.
424
-
425
- `pin.env` names the toshi-api commit the code is generated from:
426
- `2184ae8440834bb5b91991b3e4d62d14ce9dd15a` today, the commit of the
427
- release tag `v0.4.1`. `mise.toml` pins the versions of Node, pnpm and `buf`;
428
- `package.json` pins `protoc-gen-es` at the same version as the
429
- `@bufbuild/protobuf` development dependency.
430
-
431
- To regenerate:
432
-
433
- ```sh
434
- mise install
435
- pnpm install --frozen-lockfile
436
- mise exec -- scripts/generate.sh
437
- ```
438
-
439
- Moving to another commit or tag is one pull request that changes
440
- `pin.env` and `src/gen/` together. CI regenerates the code from `pin.env`
441
- and fails if the result differs from what is committed
442
- (`scripts/check-generated.sh`).
443
-
444
- ## Versions
445
-
446
- The package's major version follows the API's: `1.x` will serve
447
- `toshi.workspace.v1`, and a `toshi.workspace.v2` gets a new major
448
- version. Until a Toshi release serves the API, the package stays at
449
- `0.x` and is generated from a pinned commit. `VERSION` is the package
450
- version, the one `package.json` carries.
451
-
452
- ## Publishing
453
-
454
- Releases are published to npm by the publish workflow
455
- (`.github/workflows/publish.yml`) through npm trusted publishing: the
456
- workflow authenticates with its GitHub OIDC token, and no npm token
457
- exists. It runs on a `vX.Y.Z` tag that matches the version in
458
- `package.json`. A new version is a commit on `main` that sets the version
459
- in `package.json`, followed by its tag.
460
-
461
- ## Integration test
462
-
463
- `test/integration/` runs the client against a real `toshi serve` over
464
- HTTPS with a test CA it mints itself. `TOSHI_INTEGRATION_BIN` names the
465
- `toshi` binary it runs; when `TOSHI_INTEGRATION_EXPECT_COMMIT` is set, the
466
- binary must report that commit:
467
-
468
- ```sh
469
- TOSHI_INTEGRATION_BIN=/path/to/toshi mise exec -- pnpm vitest run test/integration
470
- ```
48
+ - [Usage](docs/usage.md): the client, options, retries, lists, watches, commands and jobs
49
+ - [Errors](docs/errors.md): products that are off and refusals with a reason
50
+ - [Testing with `@toshicloud/sdk/test`](docs/testing.md): the in-memory fake
51
+ - [Development](docs/development.md): generated code, versions, publishing and the integration test
52
+ - [The package on npm](https://www.npmjs.com/package/@toshicloud/sdk)
53
+ - [The Toshi API](https://github.com/toshicloud/toshi-api) the client calls
54
+ - [Releases](https://github.com/toshicloud/toshi-ts/releases)
55
+ - [Security](SECURITY.md) · [Licence](LICENSE)
471
56
 
472
- The Toshi server's CI runs it against each server change. Without
473
- `TOSHI_INTEGRATION_BIN`, `pnpm test` skips the integration test and says
474
- why.
57
+ ## Status and support
475
58
 
476
- ## Licence
59
+ Issues and questions go to [GitHub issues](https://github.com/toshicloud/toshi-ts/issues).
60
+ Security reports follow [SECURITY.md](SECURITY.md).
477
61
 
478
- Apache License 2.0, see LICENSE.
62
+ Apache-2.0
package/dist/client.d.ts CHANGED
@@ -3,7 +3,7 @@ import type { Client, Interceptor, Transport } from "@connectrpc/connect";
3
3
  import { AccountService } from "./gen/toshi/workspace/v1/account_pb.js";
4
4
  import { ArtifactService, ArtifactUploadService } from "./gen/toshi/workspace/v1/artifacts_pb.js";
5
5
  import { AuditEventService } from "./gen/toshi/workspace/v1/audit_events_pb.js";
6
- import { ChannelService } from "./gen/toshi/workspace/v1/channels_pb.js";
6
+ import { EndpointService } from "./gen/toshi/workspace/v1/endpoints_pb.js";
7
7
  import { ConnectionService } from "./gen/toshi/workspace/v1/connections_pb.js";
8
8
  import { DiscoveryService } from "./gen/toshi/workspace/v1/discovery_pb.js";
9
9
  import { DisplayService } from "./gen/toshi/workspace/v1/display_pb.js";
@@ -13,6 +13,7 @@ import { GrantService } from "./gen/toshi/workspace/v1/grants_pb.js";
13
13
  import { ImageService } from "./gen/toshi/workspace/v1/images_pb.js";
14
14
  import { HostJobService } from "./gen/toshi/workspace/v1/hostjobs_pb.js";
15
15
  import { HostService } from "./gen/toshi/workspace/v1/hosts_pb.js";
16
+ import { ModelCredentialService } from "./gen/toshi/workspace/v1/model_credentials_pb.js";
16
17
  import { ProductService } from "./gen/toshi/workspace/v1/products_pb.js";
17
18
  import { ProjectService } from "./gen/toshi/workspace/v1/projects_pb.js";
18
19
  import { RecordingService } from "./gen/toshi/workspace/v1/recordings_pb.js";
@@ -26,10 +27,10 @@ import { WebhookService } from "./gen/toshi/workspace/v1/webhook_pb.js";
26
27
  import { WorkspaceService } from "./gen/toshi/workspace/v1/workspace_service_pb.js";
27
28
  import { CreatePreviewShareLinkRequestSchema, ListPreviewShareLinksRequestSchema, RevokePreviewShareLinkRequestSchema, StartSandboxRequestSchema, StopSandboxRequestSchema } from "./gen/toshi/workspace/v1/workspace_pb.js";
28
29
  import type { CreatePreviewShareLinkResponse, ListPreviewShareLinksResponse, RevokePreviewShareLinkResponse, StartSandboxResponse, StopSandboxResponse } from "./gen/toshi/workspace/v1/workspace_pb.js";
29
- import { CancelJobRequestSchema, CreateJobRequestSchema, CreateSandboxRequestSchema, CreateServiceRequestSchema, DeleteJobRequestSchema, DeleteSandboxRequestSchema, DeleteServiceRequestSchema, GetJobRequestSchema, GetSandboxRequestSchema, GetServiceRequestSchema, ListJobsRequestSchema, ListSandboxesRequestSchema, ListServicesRequestSchema, UpdateJobRequestSchema, UpdateSandboxRequestSchema, UpdateServiceRequestSchema, WaitJobRequestSchema, WaitSandboxRequestSchema, WaitServiceRequestSchema, WatchJobsRequestSchema, WatchSandboxesRequestSchema, WatchServicesRequestSchema } from "./gen/toshi/workspace/v1/workloads_pb.js";
30
- import type { CancelJobResponse, CreateJobResponse, CreateSandboxResponse, CreateServiceResponse, DeleteJobResponse, DeleteSandboxResponse, DeleteServiceResponse, GetJobResponse, GetSandboxResponse, GetServiceResponse, ListJobsResponse, ListSandboxesResponse, ListServicesResponse, UpdateJobResponse, UpdateSandboxResponse, UpdateServiceResponse, WaitJobResponse, WaitSandboxResponse, WaitServiceResponse, WatchJobsResponse, WatchSandboxesResponse, WatchServicesResponse } from "./gen/toshi/workspace/v1/workloads_pb.js";
31
- import { ExecuteStartSchema, GetOperationRequestSchema, GetServiceRevisionRequestSchema, HoldServiceRequestSchema, ListEventsRequestSchema, ListServiceActivationsRequestSchema, ListServiceRevisionsRequestSchema, ListServiceRunsRequestSchema, ReleaseServiceRequestSchema, RollbackServiceRequestSchema, SetServiceModeRequestSchema, StartServiceRequestSchema, StopServiceRequestSchema, WaitOperationRequestSchema, WakeServiceRequestSchema, WatchEventsRequestSchema, WatchOutputRequestSchema } from "./gen/toshi/workspace/v1/workspace_pb.js";
32
- import type { Event, GetOperationResponse, GetServiceRevisionResponse, HoldServiceResponse, Job, ListEventsResponse, ListServiceActivationsResponse, ListServiceRevisionsResponse, ListServiceRunsResponse, Operation, PreviewShareLink, ReleaseServiceResponse, RollbackServiceResponse, Sandbox, Service, ServiceRun, SetServiceModeResponse, StartServiceResponse, StopServiceResponse, WakeServiceResponse, WatchEventsResponse, WatchOutputResponse } from "./gen/toshi/workspace/v1/workspace_pb.js";
30
+ import { CancelJobRequestSchema, CreateJobRequestSchema, CreateSandboxRequestSchema, CreateServiceRequestSchema, DeleteJobRequestSchema, DeleteSandboxRequestSchema, DeleteServiceAlarmRequestSchema, DeleteServiceRequestSchema, EmitWorkloadEventRequestSchema, GetJobRequestSchema, GetSandboxRequestSchema, GetServiceAlarmRequestSchema, GetServiceRequestSchema, ListJobsRequestSchema, ListSandboxesRequestSchema, ListServiceAlarmsRequestSchema, ListServicesRequestSchema, PauseServiceAlarmRequestSchema, ResumeServiceAlarmRequestSchema, RunServiceAlarmNowRequestSchema, SetServiceAlarmRequestSchema, UpdateJobRequestSchema, UpdateSandboxRequestSchema, UpdateServiceRequestSchema, WaitJobRequestSchema, WaitSandboxRequestSchema, WaitServiceRequestSchema, WatchJobsRequestSchema, WatchSandboxesRequestSchema, WatchServicesRequestSchema } from "./gen/toshi/workspace/v1/workloads_pb.js";
31
+ import type { CancelJobResponse, CreateJobResponse, CreateSandboxResponse, CreateServiceResponse, DeleteJobResponse, DeleteSandboxResponse, DeleteServiceAlarmResponse, DeleteServiceResponse, EmitWorkloadEventResponse, GetJobResponse, GetSandboxResponse, GetServiceAlarmResponse, GetServiceResponse, ListJobsResponse, ListSandboxesResponse, ListServiceAlarmsResponse, ListServicesResponse, PauseServiceAlarmResponse, ResumeServiceAlarmResponse, RunServiceAlarmNowResponse, SetServiceAlarmResponse, UpdateJobResponse, UpdateSandboxResponse, UpdateServiceResponse, WaitJobResponse, WaitSandboxResponse, WaitServiceResponse, WatchJobsResponse, WatchSandboxesResponse, WatchServicesResponse } from "./gen/toshi/workspace/v1/workloads_pb.js";
32
+ import { AnswerServiceSignInRequestSchema, CancelServiceSignInRequestSchema, ExecuteStartSchema, GetOperationRequestSchema, GetServiceRevisionRequestSchema, GetServiceSignInStatusRequestSchema, HoldServiceRequestSchema, HoldWorkloadRequestSchema, ListEventsRequestSchema, ListServiceActivationsRequestSchema, ListServiceRevisionsRequestSchema, ListServiceRunsRequestSchema, ReleaseServiceRequestSchema, ReleaseWorkloadRequestSchema, RollbackServiceRequestSchema, SetServiceModeRequestSchema, StartServiceRequestSchema, StartServiceSignInRequestSchema, StopServiceRequestSchema, WaitOperationRequestSchema, WakeServiceRequestSchema, WatchEventsRequestSchema, WatchOutputRequestSchema } from "./gen/toshi/workspace/v1/workspace_pb.js";
33
+ import type { AnswerServiceSignInResponse, CancelServiceSignInResponse, Event, GetOperationResponse, GetServiceRevisionResponse, GetServiceSignInStatusResponse, HoldServiceResponse, HoldWorkloadResponse, Job, ListEventsResponse, ListServiceActivationsResponse, ListServiceRevisionsResponse, ListServiceRunsResponse, Operation, PreviewShareLink, ReleaseServiceResponse, ReleaseWorkloadResponse, RollbackServiceResponse, Sandbox, Service, ServiceRun, SetServiceModeResponse, StartServiceResponse, StartServiceSignInResponse, StopServiceResponse, WakeServiceResponse, WatchEventsResponse, WatchOutputResponse } from "./gen/toshi/workspace/v1/workspace_pb.js";
33
34
  import type { RetryPolicy } from "./retry.js";
34
35
  /** How the client is configured; the caller builds the transport. */
35
36
  export interface ToshiOptions {
@@ -62,7 +63,7 @@ export interface ToshiClient {
62
63
  readonly secret: Client<typeof SecretService>;
63
64
  readonly webhook: Client<typeof WebhookService>;
64
65
  readonly display: Client<typeof DisplayService>;
65
- readonly channel: Client<typeof ChannelService>;
66
+ readonly endpoint: Client<typeof EndpointService>;
66
67
  readonly recording: Client<typeof RecordingService>;
67
68
  readonly volume: Client<typeof VolumeService>;
68
69
  readonly artifact: Client<typeof ArtifactService>;
@@ -84,6 +85,7 @@ export interface ToshiClient {
84
85
  readonly account: Client<typeof AccountService>;
85
86
  readonly image: Client<typeof ImageService>;
86
87
  readonly domain: Client<typeof DomainService>;
88
+ readonly modelCredential: Client<typeof ModelCredentialService>;
87
89
  run(req: MessageInitShape<typeof CreateJobRequestSchema>, opts?: CallOptions): Promise<JobRun>;
88
90
  }
89
91
  /** WorkloadService's calls, grouped by the type they name. */
@@ -97,6 +99,22 @@ export interface WorkloadClient {
97
99
  /** Yields every event listEvents lists, following the page token from one page to the next. */
98
100
  allEvents(req: MessageInitShape<typeof ListEventsRequestSchema>, opts?: CallOptions): AsyncIterable<Event>;
99
101
  watchEvents(req: MessageInitShape<typeof WatchEventsRequestSchema>, opts?: CallOptions): Promise<EventWatch>;
102
+ /**
103
+ * Appends an event of type EMITTED, with the name and data given, to a job's, sandbox's or
104
+ * service's event log, as its guest emits one, and returns it; the workload need not be running.
105
+ */
106
+ emitEvent(req: MessageInitShape<typeof EmitWorkloadEventRequestSchema>, opts?: CallOptions): Promise<EmitWorkloadEventResponse>;
107
+ /**
108
+ * Places an operator's hold on a sandbox or service, stopping it if it runs. Only an
109
+ * operator may call it; the caller sets the operator's reason in the Toshi-Operator-Reason
110
+ * header with an interceptor, since the client sends none itself.
111
+ */
112
+ holdWorkload(req: MessageInitShape<typeof HoldWorkloadRequestSchema>, opts?: CallOptions): Promise<HoldWorkloadResponse>;
113
+ /**
114
+ * Lifts an operator's hold and starts nothing. Only an operator may call it; the caller
115
+ * sets the operator's reason in the Toshi-Operator-Reason header with an interceptor.
116
+ */
117
+ releaseWorkload(req: MessageInitShape<typeof ReleaseWorkloadRequestSchema>, opts?: CallOptions): Promise<ReleaseWorkloadResponse>;
100
118
  /** Returns one operation: the id a long call hands back, asked for again. */
101
119
  getOperation(req: MessageInitShape<typeof GetOperationRequestSchema>, opts?: CallOptions): Promise<GetOperationResponse>;
102
120
  }
@@ -162,6 +180,58 @@ export interface ServicesClient {
162
180
  rollback(req: MessageInitShape<typeof RollbackServiceRequestSchema>, opts?: CallOptions): Promise<RollbackServiceResponse>;
163
181
  /** Returns a service's activations, oldest first. */
164
182
  listActivations(req: MessageInitShape<typeof ListServiceActivationsRequestSchema>, opts?: CallOptions): Promise<ListServiceActivationsResponse>;
183
+ /**
184
+ * Creates or replaces the service's alarm with this name and returns it: at each instant of its
185
+ * schedule the alarm publishes its event, with its data, on one of the service's event endpoints.
186
+ */
187
+ setAlarm(req: MessageInitShape<typeof SetServiceAlarmRequestSchema>, opts?: CallOptions): Promise<SetServiceAlarmResponse>;
188
+ /** Returns one alarm and its most recent occurrences, newest first. */
189
+ getAlarm(req: MessageInitShape<typeof GetServiceAlarmRequestSchema>, opts?: CallOptions): Promise<GetServiceAlarmResponse>;
190
+ /**
191
+ * Returns all of a service's alarms, by name, in one answer: the call has no page token, so
192
+ * there is no `allAlarms`.
193
+ */
194
+ listAlarms(req: MessageInitShape<typeof ListServiceAlarmsRequestSchema>, opts?: CallOptions): Promise<ListServiceAlarmsResponse>;
195
+ /** Removes an alarm and its occurrences; messages it already published stay on the endpoint. */
196
+ deleteAlarm(req: MessageInitShape<typeof DeleteServiceAlarmRequestSchema>, opts?: CallOptions): Promise<DeleteServiceAlarmResponse>;
197
+ /** Stops an alarm publishing until it is resumed; pausing a paused alarm changes nothing. */
198
+ pauseAlarm(req: MessageInitShape<typeof PauseServiceAlarmRequestSchema>, opts?: CallOptions): Promise<PauseServiceAlarmResponse>;
199
+ /**
200
+ * Starts a paused alarm again from the next instant of its schedule; instants that passed while
201
+ * it was paused are not published.
202
+ */
203
+ resumeAlarm(req: MessageInitShape<typeof ResumeServiceAlarmRequestSchema>, opts?: CallOptions): Promise<ResumeServiceAlarmResponse>;
204
+ /**
205
+ * Publishes the alarm's event once, now, paused or not, and returns the occurrence; it does not
206
+ * move the alarm's next instant. It takes a request key, so a retried call is applied once.
207
+ */
208
+ runAlarmNow(req: MessageInitShape<typeof RunServiceAlarmNowRequestSchema>, opts?: CallOptions): Promise<RunServiceAlarmNowResponse>;
209
+ /**
210
+ * Runs one of the service's recipe's sign_in programs and hands back its stream as the server
211
+ * sends it. The caller reads the started message first, which names the sign-in; then the
212
+ * output messages, the program's standard output and standard error; answers the program with
213
+ * `answerSignIn` or ends it early with `cancelSignIn`; and reads the ended message last, which
214
+ * says how it ended. A sign-in may run for 15 minutes.
215
+ *
216
+ * The stream is not retried or resumed: a sign-in has no cursor. Its stream ending for another
217
+ * reason, as a client that leaves, also ends it, cancelled; the caller starts a new one.
218
+ */
219
+ startSignIn(req: MessageInitShape<typeof StartServiceSignInRequestSchema>, opts?: CallOptions): AsyncIterable<StartServiceSignInResponse>;
220
+ /**
221
+ * Writes one line, of at most 4 KiB and with no newline in it, to a running sign-in's standard
222
+ * input; the sign-in's program reads it.
223
+ */
224
+ answerSignIn(req: MessageInitShape<typeof AnswerServiceSignInRequestSchema>, opts?: CallOptions): Promise<AnswerServiceSignInResponse>;
225
+ /**
226
+ * Ends a running sign-in: its program is cancelled and its stream ends CANCELLED. It returns
227
+ * once the cancel is asked for, not once the sign-in has ended.
228
+ */
229
+ cancelSignIn(req: MessageInitShape<typeof CancelServiceSignInRequestSchema>, opts?: CallOptions): Promise<CancelServiceSignInResponse>;
230
+ /**
231
+ * Runs the status commands of the service's recipe and reports, for each sign_in program,
232
+ * whether it is signed in and what its status command wrote.
233
+ */
234
+ getSignInStatus(req: MessageInitShape<typeof GetServiceSignInStatusRequestSchema>, opts?: CallOptions): Promise<GetServiceSignInStatusResponse>;
165
235
  }
166
236
  /** A live stream of a job's changes. */
167
237
  export interface JobWatch extends AsyncIterable<WatchJobsResponse> {