@toshicloud/sdk 0.2.0 → 0.3.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.
Files changed (53) hide show
  1. package/README.md +74 -18
  2. package/dist/client.d.ts +111 -18
  3. package/dist/client.js +82 -16
  4. package/dist/errors.d.ts +9 -1
  5. package/dist/errors.js +22 -0
  6. package/dist/execute.d.ts +3 -2
  7. package/dist/execute.js +3 -3
  8. package/dist/gen/toshi/workspace/v1/artifacts_pb.d.ts +25 -23
  9. package/dist/gen/toshi/workspace/v1/artifacts_pb.js +7 -7
  10. package/dist/gen/toshi/workspace/v1/audit_events_pb.d.ts +297 -0
  11. package/dist/gen/toshi/workspace/v1/audit_events_pb.js +37 -0
  12. package/dist/gen/toshi/workspace/v1/channels_pb.d.ts +2713 -0
  13. package/dist/gen/toshi/workspace/v1/channels_pb.js +336 -0
  14. package/dist/gen/toshi/workspace/v1/display_pb.d.ts +35 -11
  15. package/dist/gen/toshi/workspace/v1/display_pb.js +2 -2
  16. package/dist/gen/toshi/workspace/v1/fillers_pb.d.ts +920 -0
  17. package/dist/gen/toshi/workspace/v1/fillers_pb.js +94 -0
  18. package/dist/gen/toshi/workspace/v1/hostjobs_pb.d.ts +2048 -0
  19. package/dist/gen/toshi/workspace/v1/hostjobs_pb.js +226 -0
  20. package/dist/gen/toshi/workspace/v1/hosts_pb.d.ts +2135 -0
  21. package/dist/gen/toshi/workspace/v1/hosts_pb.js +301 -0
  22. package/dist/gen/toshi/workspace/v1/projects_pb.d.ts +157 -3
  23. package/dist/gen/toshi/workspace/v1/projects_pb.js +2 -2
  24. package/dist/gen/toshi/workspace/v1/recordings_pb.d.ts +76 -0
  25. package/dist/gen/toshi/workspace/v1/recordings_pb.js +1 -1
  26. package/dist/gen/toshi/workspace/v1/secrets_pb.d.ts +175 -23
  27. package/dist/gen/toshi/workspace/v1/secrets_pb.js +9 -9
  28. package/dist/gen/toshi/workspace/v1/storage_providers_pb.d.ts +717 -0
  29. package/dist/gen/toshi/workspace/v1/storage_providers_pb.js +74 -0
  30. package/dist/gen/toshi/workspace/v1/tokens_pb.d.ts +518 -0
  31. package/dist/gen/toshi/workspace/v1/tokens_pb.js +88 -0
  32. package/dist/gen/toshi/workspace/v1/volumes_pb.d.ts +39 -1
  33. package/dist/gen/toshi/workspace/v1/volumes_pb.js +2 -2
  34. package/dist/gen/toshi/workspace/v1/webhook_pb.d.ts +62 -18
  35. package/dist/gen/toshi/workspace/v1/webhook_pb.js +3 -3
  36. package/dist/gen/toshi/workspace/v1/workloads_pb.d.ts +2858 -0
  37. package/dist/gen/toshi/workspace/v1/workloads_pb.js +261 -0
  38. package/dist/gen/toshi/workspace/v1/workspace_pb.d.ts +1377 -2130
  39. package/dist/gen/toshi/workspace/v1/workspace_pb.js +235 -272
  40. package/dist/gen/toshi/workspace/v1/workspace_service_pb.d.ts +3719 -0
  41. package/dist/gen/toshi/workspace/v1/workspace_service_pb.js +270 -0
  42. package/dist/index.d.ts +14 -2
  43. package/dist/index.js +13 -1
  44. package/dist/output.d.ts +3 -2
  45. package/dist/output.js +2 -2
  46. package/dist/retry.js +116 -30
  47. package/dist/run.d.ts +10 -0
  48. package/dist/run.js +175 -0
  49. package/dist/wait.d.ts +10 -5
  50. package/dist/wait.js +23 -11
  51. package/dist/watch.d.ts +14 -6
  52. package/dist/watch.js +138 -85
  53. package/package.json +1 -1
package/dist/run.js ADDED
@@ -0,0 +1,175 @@
1
+ import { create } from "@bufbuild/protobuf";
2
+ import { JobExitError } from "./errors.js";
3
+ import { OutputEndSchema, WatchOutputRequestSchema } from "./gen/toshi/workspace/v1/workspace_pb.js";
4
+ import { EndReason } from "./gen/toshi/workspace/v1/workspace_pb.js";
5
+ import { openOutputWatch } from "./output.js";
6
+ /**
7
+ * Starts a job and follows its output, as `toshi run` does: CreateJob, then
8
+ * WatchOutput from the first byte. A job whose create answered its output
9
+ * (wait_for_end was set) is returned finished.
10
+ */
11
+ export async function runJob(service, retry, req, opts) {
12
+ const created = await service.createJob(req, opts);
13
+ const job = created.job;
14
+ if (job === undefined) {
15
+ throw new Error("toshi: CreateJob answered no job");
16
+ }
17
+ if (created.output !== undefined) {
18
+ return new FinishedRun(job, created.output);
19
+ }
20
+ const watch = await openOutputWatch(service, retry, create(WatchOutputRequestSchema, {
21
+ project: req.project,
22
+ environment: req.environment,
23
+ id: job.id,
24
+ }), opts);
25
+ return new LiveRun(job, watch);
26
+ }
27
+ /** A run whose create answered the job's output instead of a live stream. */
28
+ class FinishedRun {
29
+ job;
30
+ output;
31
+ queue;
32
+ constructor(job, output) {
33
+ this.job = job;
34
+ this.output = output;
35
+ }
36
+ [Symbol.asyncIterator]() {
37
+ this.queue ??= chunksOf(this.output);
38
+ const queue = this.queue;
39
+ return {
40
+ next: () => {
41
+ const chunk = queue.shift();
42
+ return Promise.resolve(chunk === undefined ? { done: true, value: undefined } : { done: false, value: chunk });
43
+ },
44
+ };
45
+ }
46
+ wait() {
47
+ const end = create(OutputEndSchema, {
48
+ exitCode: this.job.status?.exitCode,
49
+ cancelled: this.job.status?.endReason === EndReason.CANCELLED,
50
+ truncated: this.output.truncated,
51
+ });
52
+ if (end.exitCode === 0) {
53
+ return Promise.resolve();
54
+ }
55
+ return Promise.reject(new JobExitError(this.job.id, end));
56
+ }
57
+ }
58
+ /** A run whose output the server streams while the job runs. */
59
+ class LiveRun {
60
+ job;
61
+ watch;
62
+ responses;
63
+ queue = [];
64
+ end;
65
+ failure;
66
+ readerOwned = false;
67
+ waitPromise;
68
+ constructor(job, watch) {
69
+ this.job = job;
70
+ this.watch = watch;
71
+ this.responses = watch[Symbol.asyncIterator]();
72
+ }
73
+ [Symbol.asyncIterator]() {
74
+ if (this.readerOwned) {
75
+ throw new Error("toshi: this run already has an output reader");
76
+ }
77
+ this.readerOwned = true;
78
+ return {
79
+ next: () => this.pull(),
80
+ // A break ends only this reader: the job keeps running.
81
+ return: () => {
82
+ this.watch.close();
83
+ return Promise.resolve({ done: true, value: undefined });
84
+ },
85
+ };
86
+ }
87
+ wait() {
88
+ this.waitPromise ??= this.awaitOutcome();
89
+ return this.waitPromise;
90
+ }
91
+ async awaitOutcome() {
92
+ if (!this.readerOwned) {
93
+ // No reader owns the output: take it and drain it, so wait() never stalls.
94
+ this.readerOwned = true;
95
+ try {
96
+ for (;;) {
97
+ const result = await this.pull();
98
+ if (result.done === true) {
99
+ break;
100
+ }
101
+ }
102
+ }
103
+ catch {
104
+ // pull() recorded the outcome; it is reported below.
105
+ }
106
+ }
107
+ for (;;) {
108
+ if (this.failure !== undefined) {
109
+ throw this.failure;
110
+ }
111
+ if (this.end !== undefined) {
112
+ if (this.end.exitCode === 0 && !this.end.cancelled) {
113
+ return;
114
+ }
115
+ throw new JobExitError(this.job.id, this.end);
116
+ }
117
+ await this.readOne();
118
+ }
119
+ }
120
+ async pull() {
121
+ for (;;) {
122
+ const chunk = this.queue.shift();
123
+ if (chunk !== undefined) {
124
+ return { done: false, value: chunk };
125
+ }
126
+ if (this.failure !== undefined) {
127
+ throw this.failure;
128
+ }
129
+ if (this.end !== undefined) {
130
+ return { done: true, value: undefined };
131
+ }
132
+ await this.readOne();
133
+ }
134
+ }
135
+ async readOne() {
136
+ let result;
137
+ try {
138
+ result = await this.responses.next();
139
+ }
140
+ catch (err) {
141
+ this.failure = err instanceof Error ? err : new Error(String(err));
142
+ return;
143
+ }
144
+ if (result.done === true) {
145
+ this.failure = new Error("toshi: the output stream ended without its end message");
146
+ return;
147
+ }
148
+ const message = result.value.message;
149
+ switch (message.case) {
150
+ case "stdout":
151
+ this.queue.push({ stream: "stdout", data: message.value });
152
+ return;
153
+ case "stderr":
154
+ this.queue.push({ stream: "stderr", data: message.value });
155
+ return;
156
+ case "end":
157
+ this.end = message.value;
158
+ // The watch has nothing left to send; release the RPC.
159
+ this.watch.close();
160
+ return;
161
+ default:
162
+ return;
163
+ }
164
+ }
165
+ }
166
+ function chunksOf(output) {
167
+ const chunks = [];
168
+ if (output.stdout.length > 0) {
169
+ chunks.push({ stream: "stdout", data: output.stdout });
170
+ }
171
+ if (output.stderr.length > 0) {
172
+ chunks.push({ stream: "stderr", data: output.stderr });
173
+ }
174
+ return chunks;
175
+ }
package/dist/wait.d.ts CHANGED
@@ -1,8 +1,13 @@
1
1
  import type { Client } from "@connectrpc/connect";
2
2
  import type { CallOptions } from "./client.js";
3
- import type { WorkspaceService } from "./gen/toshi/workspace/v1/workspace_pb.js";
4
- import type { Operation, WaitOperationRequest, WaitWorkloadRequest, WaitWorkloadResponse } from "./gen/toshi/workspace/v1/workspace_pb.js";
5
- /** Asks again while reached is false, and returns the response when it is (wait.go:11-27). */
6
- export declare function waitWorkload(workspace: Client<typeof WorkspaceService>, req: WaitWorkloadRequest, opts?: CallOptions): Promise<WaitWorkloadResponse>;
3
+ import type { WorkloadService } from "./gen/toshi/workspace/v1/workloads_pb.js";
4
+ import type { WaitJobRequest, WaitJobResponse, WaitSandboxRequest, WaitSandboxResponse, WaitServiceRequest, WaitServiceResponse } from "./gen/toshi/workspace/v1/workloads_pb.js";
5
+ import type { Operation, WaitOperationRequest } from "./gen/toshi/workspace/v1/workspace_pb.js";
6
+ /** Asks again while the job is not in a state it waits for, and returns the answer when it is. */
7
+ export declare function waitJob(service: Client<typeof WorkloadService>, req: WaitJobRequest, opts?: CallOptions): Promise<WaitJobResponse>;
8
+ /** Asks again while the sandbox is not in a state it waits for. */
9
+ export declare function waitSandbox(service: Client<typeof WorkloadService>, req: WaitSandboxRequest, opts?: CallOptions): Promise<WaitSandboxResponse>;
10
+ /** Asks again while the service is not in a state it waits for. */
11
+ export declare function waitService(service: Client<typeof WorkloadService>, req: WaitServiceRequest, opts?: CallOptions): Promise<WaitServiceResponse>;
7
12
  /** Asks again while the operation is not done, and returns it when it is (wait.go:29-45). */
8
- export declare function waitOperation(workspace: Client<typeof WorkspaceService>, req: WaitOperationRequest, opts?: CallOptions): Promise<Operation>;
13
+ export declare function waitOperation(service: Client<typeof WorkloadService>, req: WaitOperationRequest, opts?: CallOptions): Promise<Operation>;
package/dist/wait.js CHANGED
@@ -1,25 +1,37 @@
1
1
  import { abortReason } from "./retry.js";
2
- /** Asks again while reached is false, and returns the response when it is (wait.go:11-27). */
3
- export async function waitWorkload(workspace, req, opts) {
4
- for (;;) {
5
- throwIfAborted(opts);
6
- const resp = await workspace.waitWorkload(req, opts);
7
- if (resp.reached) {
8
- return resp;
9
- }
10
- }
2
+ /** Asks again while the job is not in a state it waits for, and returns the answer when it is. */
3
+ export function waitJob(service, req, opts) {
4
+ return waitReached(() => service.waitJob(req, opts), opts);
5
+ }
6
+ /** Asks again while the sandbox is not in a state it waits for. */
7
+ export function waitSandbox(service, req, opts) {
8
+ return waitReached(() => service.waitSandbox(req, opts), opts);
9
+ }
10
+ /** Asks again while the service is not in a state it waits for. */
11
+ export function waitService(service, req, opts) {
12
+ return waitReached(() => service.waitService(req, opts), opts);
11
13
  }
12
14
  /** Asks again while the operation is not done, and returns it when it is (wait.go:29-45). */
13
- export async function waitOperation(workspace, req, opts) {
15
+ export async function waitOperation(service, req, opts) {
14
16
  for (;;) {
15
17
  throwIfAborted(opts);
16
- const resp = await workspace.waitOperation(req, opts);
18
+ const resp = await service.waitOperation(req, opts);
17
19
  const op = resp.operation;
18
20
  if (op !== undefined && op.done) {
19
21
  return op;
20
22
  }
21
23
  }
22
24
  }
25
+ // The three waits share one loop: each answers reached when the state arrived.
26
+ async function waitReached(call, opts) {
27
+ for (;;) {
28
+ throwIfAborted(opts);
29
+ const resp = await call();
30
+ if (resp.reached) {
31
+ return resp;
32
+ }
33
+ }
34
+ }
23
35
  // wait.go:16-18: the caller's context ends the loop with its own error.
24
36
  function throwIfAborted(opts) {
25
37
  const signal = opts?.signal;
package/dist/watch.d.ts CHANGED
@@ -1,8 +1,16 @@
1
1
  import type { Client } from "@connectrpc/connect";
2
- import type { CallOptions, EventWatch, WorkloadWatch } from "./client.js";
3
- import type { WatchEventsRequest, WatchWorkloadsRequest, WorkspaceService } from "./gen/toshi/workspace/v1/workspace_pb.js";
2
+ import type { CallOptions, EventWatch, JobWatch, SandboxWatch, ServiceWatch } from "./client.js";
3
+ import type { WatchJobsRequest, WatchSandboxesRequest, WatchServicesRequest, WorkloadService } from "./gen/toshi/workspace/v1/workloads_pb.js";
4
+ import type { WatchEventsRequest } from "./gen/toshi/workspace/v1/workspace_pb.js";
4
5
  import type { RetrySetting } from "./retry.js";
5
- /** Starts a workload watch; with an empty cursor it lists the workloads first (watch.go:40-84). */
6
- export declare function openWorkloadWatch(workspace: Client<typeof WorkspaceService>, retry: RetrySetting, req: WatchWorkloadsRequest, opts?: CallOptions): Promise<WorkloadWatch>;
7
- /** Starts an event watch; with an empty cursor it pages ListEvents as the caller iterates (watch.go:261-283). */
8
- export declare function openEventWatch(workspace: Client<typeof WorkspaceService>, retry: RetrySetting, req: WatchEventsRequest, opts?: CallOptions): Promise<EventWatch>;
6
+ /** Starts a job watch; with an empty cursor it lists the jobs first. */
7
+ export declare function openJobWatch(service: Client<typeof WorkloadService>, retry: RetrySetting, req: WatchJobsRequest, opts?: CallOptions): Promise<JobWatch>;
8
+ /** Starts a sandbox watch; with an empty cursor it lists the sandboxes first. */
9
+ export declare function openSandboxWatch(service: Client<typeof WorkloadService>, retry: RetrySetting, req: WatchSandboxesRequest, opts?: CallOptions): Promise<SandboxWatch>;
10
+ /** Starts a service watch; with an empty cursor it lists the services first. */
11
+ export declare function openServiceWatch(service: Client<typeof WorkloadService>, retry: RetrySetting, req: WatchServicesRequest, opts?: CallOptions): Promise<ServiceWatch>;
12
+ /**
13
+ * Starts an event watch; with an empty cursor it pages the event log as the
14
+ * caller iterates. Every message it yields is the stream's response.
15
+ */
16
+ export declare function openEventWatch(service: Client<typeof WorkloadService>, retry: RetrySetting, req: WatchEventsRequest, opts?: CallOptions): Promise<EventWatch>;
package/dist/watch.js CHANGED
@@ -1,14 +1,89 @@
1
1
  import { clone, create } from "@bufbuild/protobuf";
2
- import { ListEventsRequestSchema, ListWorkloadsRequestSchema, WatchEventsRequestSchema, WatchWorkloadsRequestSchema, } from "./gen/toshi/workspace/v1/workspace_pb.js";
2
+ import { ListJobsRequestSchema, ListSandboxesRequestSchema, ListServicesRequestSchema, WatchJobsRequestSchema, WatchSandboxesRequestSchema, WatchServicesRequestSchema, } from "./gen/toshi/workspace/v1/workloads_pb.js";
3
+ import { ListEventsRequestSchema, WatchEventsRequestSchema, WatchEventsResponseSchema, } from "./gen/toshi/workspace/v1/workspace_pb.js";
3
4
  import { READER_TAKEN, WatchState, createReader, reconnect, streamEnded } from "./stream.js";
4
- /** Starts a workload watch; with an empty cursor it lists the workloads first (watch.go:40-84). */
5
- export async function openWorkloadWatch(workspace, retry, req, opts) {
6
- const state = new WatchState(req.cursor, opts?.signal);
5
+ /** Starts a job watch; with an empty cursor it lists the jobs first. */
6
+ export function openJobWatch(service, retry, req, opts) {
7
+ const request = clone(WatchJobsRequestSchema, req);
8
+ return openWatch({
9
+ request,
10
+ list: async (signal) => {
11
+ const list = create(ListJobsRequestSchema, {
12
+ project: request.project,
13
+ environment: request.environment,
14
+ });
15
+ const items = [];
16
+ for (;;) {
17
+ const page = await service.listJobs(list, { signal });
18
+ items.push(...page.jobs);
19
+ if (page.nextPageToken === "") {
20
+ return { items, cursor: page.cursor };
21
+ }
22
+ list.pageToken = page.nextPageToken;
23
+ }
24
+ },
25
+ watch: (signal) => service.watchJobs(request, { signal }),
26
+ cursor: (message) => message.cursor,
27
+ keepalive: (message) => message.keepalive,
28
+ }, retry, opts);
29
+ }
30
+ /** Starts a sandbox watch; with an empty cursor it lists the sandboxes first. */
31
+ export function openSandboxWatch(service, retry, req, opts) {
32
+ const request = clone(WatchSandboxesRequestSchema, req);
33
+ return openWatch({
34
+ request,
35
+ list: async (signal) => {
36
+ const list = create(ListSandboxesRequestSchema, {
37
+ project: request.project,
38
+ environment: request.environment,
39
+ });
40
+ const items = [];
41
+ for (;;) {
42
+ const page = await service.listSandboxes(list, { signal });
43
+ items.push(...page.sandboxes);
44
+ if (page.nextPageToken === "") {
45
+ return { items, cursor: page.cursor };
46
+ }
47
+ list.pageToken = page.nextPageToken;
48
+ }
49
+ },
50
+ watch: (signal) => service.watchSandboxes(request, { signal }),
51
+ cursor: (message) => message.cursor,
52
+ keepalive: (message) => message.keepalive,
53
+ }, retry, opts);
54
+ }
55
+ /** Starts a service watch; with an empty cursor it lists the services first. */
56
+ export function openServiceWatch(service, retry, req, opts) {
57
+ const request = clone(WatchServicesRequestSchema, req);
58
+ return openWatch({
59
+ request,
60
+ list: async (signal) => {
61
+ const list = create(ListServicesRequestSchema, {
62
+ project: request.project,
63
+ environment: request.environment,
64
+ });
65
+ const items = [];
66
+ for (;;) {
67
+ const page = await service.listServices(list, { signal });
68
+ items.push(...page.services);
69
+ if (page.nextPageToken === "") {
70
+ return { items, cursor: page.cursor };
71
+ }
72
+ list.pageToken = page.nextPageToken;
73
+ }
74
+ },
75
+ watch: (signal) => service.watchServices(request, { signal }),
76
+ cursor: (message) => message.cursor,
77
+ keepalive: (message) => message.keepalive,
78
+ }, retry, opts);
79
+ }
80
+ async function openWatch(wire, retry, opts) {
81
+ const state = new WatchState(wire.request.cursor, opts?.signal);
7
82
  let snapshot;
8
- if (req.cursor === "") {
83
+ if (wire.request.cursor === "") {
9
84
  try {
10
- const listed = await listWorkloads(workspace, req, state);
11
- snapshot = listed.workloads;
85
+ const listed = await wire.list(state.signal);
86
+ snapshot = listed.items;
12
87
  state.cursor = listed.cursor;
13
88
  }
14
89
  catch (err) {
@@ -16,9 +91,8 @@ export async function openWorkloadWatch(workspace, retry, req, opts) {
16
91
  throw err;
17
92
  }
18
93
  }
19
- const request = clone(WatchWorkloadsRequestSchema, req);
20
- request.cursor = state.cursor;
21
- request.keepalive = true;
94
+ wire.request.cursor = state.cursor;
95
+ wire.request.keepalive = true;
22
96
  let reader;
23
97
  return {
24
98
  get cursor() {
@@ -34,64 +108,12 @@ export async function openWorkloadWatch(workspace, retry, req, opts) {
34
108
  if (reader !== undefined) {
35
109
  throw new Error(READER_TAKEN);
36
110
  }
37
- reader = createReader(() => runWorkloads(workspace, retry, request, state), () => state.close());
38
- return reader;
39
- },
40
- };
41
- }
42
- /** Starts an event watch; with an empty cursor it pages ListEvents as the caller iterates (watch.go:261-283). */
43
- export function openEventWatch(workspace, retry, req, opts) {
44
- const state = new WatchState(req.cursor, opts?.signal);
45
- const request = clone(WatchEventsRequestSchema, req);
46
- request.keepalive = true;
47
- const list = createListEventsRequest(req);
48
- const listing = req.cursor === "";
49
- let reader;
50
- const watch = {
51
- get cursor() {
52
- return state.cursor;
53
- },
54
- close() {
55
- state.close();
56
- },
57
- [Symbol.asyncIterator]() {
58
- if (reader !== undefined) {
59
- throw new Error(READER_TAKEN);
60
- }
61
- reader = createReader(() => runEvents(workspace, retry, request, list, listing, state), () => state.close());
111
+ reader = createReader(() => runWatch(wire, retry, state), () => state.close());
62
112
  return reader;
63
113
  },
64
114
  };
65
- return Promise.resolve(watch);
66
- }
67
- // watch.go:65-84: every page of the list returns the event log's position.
68
- async function listWorkloads(workspace, req, state) {
69
- const list = createListWorkloadsRequest(req);
70
- const workloads = [];
71
- for (;;) {
72
- const page = await workspace.listWorkloads(list, { signal: state.signal });
73
- workloads.push(...page.workloads);
74
- if (page.nextPageToken === "") {
75
- return { workloads, cursor: page.cursor };
76
- }
77
- list.pageToken = page.nextPageToken;
78
- }
79
115
  }
80
- function createListWorkloadsRequest(req) {
81
- return create(ListWorkloadsRequestSchema, {
82
- project: req.project,
83
- environment: req.environment,
84
- kinds: req.kinds,
85
- });
86
- }
87
- function createListEventsRequest(req) {
88
- return create(ListEventsRequestSchema, {
89
- project: req.project,
90
- environment: req.environment,
91
- workloadId: req.workloadId,
92
- });
93
- }
94
- async function* runWorkloads(workspace, retry, request, state) {
116
+ async function* runWatch(wire, retry, state) {
95
117
  let attempt = 0;
96
118
  try {
97
119
  for (;;) {
@@ -100,20 +122,21 @@ async function* runWorkloads(workspace, retry, request, state) {
100
122
  }
101
123
  let cause;
102
124
  try {
103
- request.cursor = state.cursor;
104
- const stream = workspace.watchWorkloads(request, { signal: state.signal });
105
- for await (const msg of stream) {
125
+ wire.request.cursor = state.cursor;
126
+ for await (const message of wire.watch(state.signal)) {
106
127
  attempt = 0;
107
- if (msg.keepalive !== undefined) {
108
- if (msg.keepalive.cursor !== "") {
109
- state.cursor = msg.keepalive.cursor;
128
+ const keepalive = wire.keepalive(message);
129
+ if (keepalive !== undefined) {
130
+ if (keepalive.cursor !== "") {
131
+ state.cursor = keepalive.cursor;
110
132
  }
111
133
  continue;
112
134
  }
113
- if (msg.cursor !== "") {
114
- state.cursor = msg.cursor;
135
+ const cursor = wire.cursor(message);
136
+ if (cursor !== "") {
137
+ state.cursor = cursor;
115
138
  }
116
- yield msg;
139
+ yield message;
117
140
  }
118
141
  if (state.closed) {
119
142
  return;
@@ -136,14 +159,45 @@ async function* runWorkloads(workspace, retry, request, state) {
136
159
  throw err;
137
160
  }
138
161
  }
139
- async function* runEvents(workspace, retry, request, list, listingAtStart, state) {
162
+ /**
163
+ * Starts an event watch; with an empty cursor it pages the event log as the
164
+ * caller iterates. Every message it yields is the stream's response.
165
+ */
166
+ export function openEventWatch(service, retry, req, opts) {
167
+ const state = new WatchState(req.cursor, opts?.signal);
168
+ const request = clone(WatchEventsRequestSchema, req);
169
+ request.keepalive = true;
170
+ const list = create(ListEventsRequestSchema, {
171
+ project: req.project,
172
+ environment: req.environment,
173
+ workloadId: req.workloadId,
174
+ });
175
+ const listing = req.cursor === "";
176
+ let reader;
177
+ const watch = {
178
+ get cursor() {
179
+ return state.cursor;
180
+ },
181
+ close() {
182
+ state.close();
183
+ },
184
+ [Symbol.asyncIterator]() {
185
+ if (reader !== undefined) {
186
+ throw new Error(READER_TAKEN);
187
+ }
188
+ reader = createReader(() => runEvents(service, retry, request, list, listing, state), () => state.close());
189
+ return reader;
190
+ },
191
+ };
192
+ return Promise.resolve(watch);
193
+ }
194
+ async function* runEvents(service, retry, request, list, listingAtStart, state) {
140
195
  let attempt = 0;
141
196
  let page = [];
142
197
  let pageIndex = 0;
143
198
  let listing = listingAtStart;
144
- // watch.go:305-317: resume is the position the next stream opens at, separate
145
- // from the cursor the caller sees, which only moves on events handed out and
146
- // keepalives consumed.
199
+ // Resume is the position the next stream opens at, separate from the cursor
200
+ // the caller sees, which only moves on events handed out and keepalives read.
147
201
  let resume = state.cursor;
148
202
  try {
149
203
  for (;;) {
@@ -161,14 +215,14 @@ async function* runEvents(workspace, retry, request, list, listingAtStart, state
161
215
  state.cursor = event.cursor;
162
216
  resume = event.cursor;
163
217
  }
164
- yield event;
218
+ yield create(WatchEventsResponseSchema, { event });
165
219
  continue;
166
220
  }
167
- const listed = await workspace.listEvents(list, { signal: state.signal });
221
+ const listed = await service.listEvents(list, { signal: state.signal });
168
222
  page = listed.events;
169
223
  pageIndex = 0;
170
224
  if (listed.cursor !== "") {
171
- list.cursor = listed.cursor;
225
+ list.pageToken = listed.cursor;
172
226
  }
173
227
  if (page.length > 0) {
174
228
  continue;
@@ -180,10 +234,9 @@ async function* runEvents(workspace, retry, request, list, listingAtStart, state
180
234
  continue;
181
235
  }
182
236
  request.cursor = resume;
183
- const stream = workspace.watchEvents(request, { signal: state.signal });
184
- for await (const msg of stream) {
237
+ for await (const message of service.watchEvents(request, { signal: state.signal })) {
185
238
  attempt = 0;
186
- const keepalive = msg.keepalive;
239
+ const keepalive = message.keepalive;
187
240
  if (keepalive !== undefined) {
188
241
  if (keepalive.cursor !== "") {
189
242
  state.cursor = keepalive.cursor;
@@ -191,7 +244,7 @@ async function* runEvents(workspace, retry, request, list, listingAtStart, state
191
244
  }
192
245
  continue;
193
246
  }
194
- const event = msg.event;
247
+ const event = message.event;
195
248
  if (event === undefined) {
196
249
  continue;
197
250
  }
@@ -199,7 +252,7 @@ async function* runEvents(workspace, retry, request, list, listingAtStart, state
199
252
  state.cursor = event.cursor;
200
253
  resume = event.cursor;
201
254
  }
202
- yield event;
255
+ yield message;
203
256
  }
204
257
  if (state.closed) {
205
258
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toshicloud/sdk",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "TypeScript client for the Toshi Workspace API",
5
5
  "license": "Apache-2.0",
6
6
  "packageManager": "pnpm@12.15.0+sha512.b3951d6d6ff4a9f13dc7cf56cd28d2b8eb37dbcc9c68e52e1e1191873ea9f82a017287ab50baadc5ccc494f036c67c0e47eb13608c2fa36f47711b61ea128e61",