@mastra/client-js 0.0.0-storage-20250225005900 → 0.0.0-support-d1-client-20250701191943

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.
@@ -0,0 +1,177 @@
1
+ import type { WatchEvent } from '@mastra/core/workflows';
2
+
3
+ import type {
4
+ ClientOptions,
5
+ GetVNextNetworkResponse,
6
+ GenerateVNextNetworkResponse,
7
+ LoopVNextNetworkResponse,
8
+ GenerateOrStreamVNextNetworkParams,
9
+ LoopStreamVNextNetworkParams,
10
+ } from '../types';
11
+
12
+ import { BaseResource } from './base';
13
+
14
+ const RECORD_SEPARATOR = '\x1E';
15
+
16
+ export class VNextNetwork extends BaseResource {
17
+ constructor(
18
+ options: ClientOptions,
19
+ private networkId: string,
20
+ ) {
21
+ super(options);
22
+ }
23
+
24
+ /**
25
+ * Retrieves details about the network
26
+ * @returns Promise containing vNext network details
27
+ */
28
+ details(): Promise<GetVNextNetworkResponse> {
29
+ return this.request(`/api/networks/v-next/${this.networkId}`);
30
+ }
31
+
32
+ /**
33
+ * Generates a response from the v-next network
34
+ * @param params - Generation parameters including message
35
+ * @returns Promise containing the generated response
36
+ */
37
+ generate(params: GenerateOrStreamVNextNetworkParams): Promise<GenerateVNextNetworkResponse> {
38
+ return this.request(`/api/networks/v-next/${this.networkId}/generate`, {
39
+ method: 'POST',
40
+ body: params,
41
+ });
42
+ }
43
+
44
+ /**
45
+ * Generates a response from the v-next network using multiple primitives
46
+ * @param params - Generation parameters including message
47
+ * @returns Promise containing the generated response
48
+ */
49
+ loop(params: { message: string }): Promise<LoopVNextNetworkResponse> {
50
+ return this.request(`/api/networks/v-next/${this.networkId}/loop`, {
51
+ method: 'POST',
52
+ body: params,
53
+ });
54
+ }
55
+
56
+ private async *streamProcessor(stream: ReadableStream): AsyncGenerator<WatchEvent, void, unknown> {
57
+ const reader = stream.getReader();
58
+
59
+ // Track if we've finished reading from the stream
60
+ let doneReading = false;
61
+ // Buffer to accumulate partial chunks
62
+ let buffer = '';
63
+
64
+ try {
65
+ while (!doneReading) {
66
+ // Read the next chunk from the stream
67
+ const { done, value } = await reader.read();
68
+ doneReading = done;
69
+
70
+ // Skip processing if we're done and there's no value
71
+ if (done && !value) continue;
72
+
73
+ try {
74
+ // Decode binary data to text
75
+ const decoded = value ? new TextDecoder().decode(value) : '';
76
+
77
+ // Split the combined buffer and new data by record separator
78
+ const chunks = (buffer + decoded).split(RECORD_SEPARATOR);
79
+
80
+ // The last chunk might be incomplete, so save it for the next iteration
81
+ buffer = chunks.pop() || '';
82
+
83
+ // Process complete chunks
84
+ for (const chunk of chunks) {
85
+ if (chunk) {
86
+ // Only process non-empty chunks
87
+ if (typeof chunk === 'string') {
88
+ try {
89
+ const parsedChunk = JSON.parse(chunk);
90
+ yield parsedChunk;
91
+ } catch {
92
+ // Silently ignore parsing errors to maintain stream processing
93
+ // This allows the stream to continue even if one record is malformed
94
+ }
95
+ }
96
+ }
97
+ }
98
+ } catch {
99
+ // Silently ignore parsing errors to maintain stream processing
100
+ // This allows the stream to continue even if one record is malformed
101
+ }
102
+ }
103
+
104
+ // Process any remaining data in the buffer after stream is done
105
+ if (buffer) {
106
+ try {
107
+ yield JSON.parse(buffer);
108
+ } catch {
109
+ // Ignore parsing error for final chunk
110
+ }
111
+ }
112
+ } finally {
113
+ // Always ensure we clean up the reader
114
+ reader.cancel().catch(() => {
115
+ // Ignore cancel errors
116
+ });
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Streams a response from the v-next network
122
+ * @param params - Stream parameters including message
123
+ * @returns Promise containing the results
124
+ */
125
+ async stream(params: GenerateOrStreamVNextNetworkParams, onRecord: (record: WatchEvent) => void) {
126
+ const response: Response = await this.request(`/api/networks/v-next/${this.networkId}/stream`, {
127
+ method: 'POST',
128
+ body: params,
129
+ stream: true,
130
+ });
131
+
132
+ if (!response.ok) {
133
+ throw new Error(`Failed to stream vNext network: ${response.statusText}`);
134
+ }
135
+
136
+ if (!response.body) {
137
+ throw new Error('Response body is null');
138
+ }
139
+
140
+ for await (const record of this.streamProcessor(response.body)) {
141
+ if (typeof record === 'string') {
142
+ onRecord(JSON.parse(record));
143
+ } else {
144
+ onRecord(record);
145
+ }
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Streams a response from the v-next network loop
151
+ * @param params - Stream parameters including message
152
+ * @returns Promise containing the results
153
+ */
154
+ async loopStream(params: LoopStreamVNextNetworkParams, onRecord: (record: WatchEvent) => void) {
155
+ const response: Response = await this.request(`/api/networks/v-next/${this.networkId}/loop-stream`, {
156
+ method: 'POST',
157
+ body: params,
158
+ stream: true,
159
+ });
160
+
161
+ if (!response.ok) {
162
+ throw new Error(`Failed to stream vNext network loop: ${response.statusText}`);
163
+ }
164
+
165
+ if (!response.body) {
166
+ throw new Error('Response body is null');
167
+ }
168
+
169
+ for await (const record of this.streamProcessor(response.body)) {
170
+ if (typeof record === 'string') {
171
+ onRecord(JSON.parse(record));
172
+ } else {
173
+ onRecord(record);
174
+ }
175
+ }
176
+ }
177
+ }
@@ -1,7 +1,20 @@
1
- import type { GetWorkflowResponse, ClientOptions } from '../types';
1
+ import type { RuntimeContext } from '@mastra/core/runtime-context';
2
+ import type {
3
+ ClientOptions,
4
+ GetWorkflowResponse,
5
+ GetWorkflowRunsResponse,
6
+ GetWorkflowRunsParams,
7
+ WorkflowRunResult,
8
+ WorkflowWatchResult,
9
+ GetWorkflowRunByIdResponse,
10
+ GetWorkflowRunExecutionResultResponse,
11
+ } from '../types';
2
12
 
13
+ import { parseClientRuntimeContext } from '../utils';
3
14
  import { BaseResource } from './base';
4
15
 
16
+ const RECORD_SEPARATOR = '\x1E';
17
+
5
18
  export class Workflow extends BaseResource {
6
19
  constructor(
7
20
  options: ClientOptions,
@@ -10,6 +23,77 @@ export class Workflow extends BaseResource {
10
23
  super(options);
11
24
  }
12
25
 
26
+ /**
27
+ * Creates an async generator that processes a readable stream and yields workflow records
28
+ * separated by the Record Separator character (\x1E)
29
+ *
30
+ * @param stream - The readable stream to process
31
+ * @returns An async generator that yields parsed records
32
+ */
33
+ private async *streamProcessor(stream: ReadableStream): AsyncGenerator<WorkflowWatchResult, void, unknown> {
34
+ const reader = stream.getReader();
35
+
36
+ // Track if we've finished reading from the stream
37
+ let doneReading = false;
38
+ // Buffer to accumulate partial chunks
39
+ let buffer = '';
40
+
41
+ try {
42
+ while (!doneReading) {
43
+ // Read the next chunk from the stream
44
+ const { done, value } = await reader.read();
45
+ doneReading = done;
46
+
47
+ // Skip processing if we're done and there's no value
48
+ if (done && !value) continue;
49
+
50
+ try {
51
+ // Decode binary data to text
52
+ const decoded = value ? new TextDecoder().decode(value) : '';
53
+
54
+ // Split the combined buffer and new data by record separator
55
+ const chunks = (buffer + decoded).split(RECORD_SEPARATOR);
56
+
57
+ // The last chunk might be incomplete, so save it for the next iteration
58
+ buffer = chunks.pop() || '';
59
+
60
+ // Process complete chunks
61
+ for (const chunk of chunks) {
62
+ if (chunk) {
63
+ // Only process non-empty chunks
64
+ if (typeof chunk === 'string') {
65
+ try {
66
+ const parsedChunk = JSON.parse(chunk);
67
+ yield parsedChunk;
68
+ } catch {
69
+ // Silently ignore parsing errors to maintain stream processing
70
+ // This allows the stream to continue even if one record is malformed
71
+ }
72
+ }
73
+ }
74
+ }
75
+ } catch {
76
+ // Silently ignore parsing errors to maintain stream processing
77
+ // This allows the stream to continue even if one record is malformed
78
+ }
79
+ }
80
+
81
+ // Process any remaining data in the buffer after stream is done
82
+ if (buffer) {
83
+ try {
84
+ yield JSON.parse(buffer);
85
+ } catch {
86
+ // Ignore parsing error for final chunk
87
+ }
88
+ }
89
+ } finally {
90
+ // Always ensure we clean up the reader
91
+ reader.cancel().catch(() => {
92
+ // Ignore cancel errors
93
+ });
94
+ }
95
+ }
96
+
13
97
  /**
14
98
  * Retrieves details about the workflow
15
99
  * @returns Promise containing workflow details including steps and graphs
@@ -19,50 +103,282 @@ export class Workflow extends BaseResource {
19
103
  }
20
104
 
21
105
  /**
22
- * Executes the workflow with the provided parameters
23
- * @param params - Parameters required for workflow execution
24
- * @returns Promise containing the workflow execution results
106
+ * Retrieves all runs for a workflow
107
+ * @param params - Parameters for filtering runs
108
+ * @returns Promise containing workflow runs array
109
+ */
110
+ runs(params?: GetWorkflowRunsParams): Promise<GetWorkflowRunsResponse> {
111
+ const searchParams = new URLSearchParams();
112
+ if (params?.fromDate) {
113
+ searchParams.set('fromDate', params.fromDate.toISOString());
114
+ }
115
+ if (params?.toDate) {
116
+ searchParams.set('toDate', params.toDate.toISOString());
117
+ }
118
+ if (params?.limit) {
119
+ searchParams.set('limit', String(params.limit));
120
+ }
121
+ if (params?.offset) {
122
+ searchParams.set('offset', String(params.offset));
123
+ }
124
+ if (params?.resourceId) {
125
+ searchParams.set('resourceId', params.resourceId);
126
+ }
127
+
128
+ if (searchParams.size) {
129
+ return this.request(`/api/workflows/${this.workflowId}/runs?${searchParams}`);
130
+ } else {
131
+ return this.request(`/api/workflows/${this.workflowId}/runs`);
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Retrieves a specific workflow run by its ID
137
+ * @param runId - The ID of the workflow run to retrieve
138
+ * @returns Promise containing the workflow run details
139
+ */
140
+ runById(runId: string): Promise<GetWorkflowRunByIdResponse> {
141
+ return this.request(`/api/workflows/${this.workflowId}/runs/${runId}`);
142
+ }
143
+
144
+ /**
145
+ * Retrieves the execution result for a specific workflow run by its ID
146
+ * @param runId - The ID of the workflow run to retrieve the execution result for
147
+ * @returns Promise containing the workflow run execution result
25
148
  */
26
- execute(params: Record<string, any>): Promise<Record<string, any>> {
27
- return this.request(`/api/workflows/${this.workflowId}/execute`, {
149
+ runExecutionResult(runId: string): Promise<GetWorkflowRunExecutionResultResponse> {
150
+ return this.request(`/api/workflows/${this.workflowId}/runs/${runId}/execution-result`);
151
+ }
152
+
153
+ /**
154
+ * Cancels a specific workflow run by its ID
155
+ * @param runId - The ID of the workflow run to cancel
156
+ * @returns Promise containing a success message
157
+ */
158
+ cancelRun(runId: string): Promise<{ message: string }> {
159
+ return this.request(`/api/workflows/${this.workflowId}/runs/${runId}/cancel`, {
28
160
  method: 'POST',
29
- body: params,
30
161
  });
31
162
  }
32
163
 
33
164
  /**
34
- * Resumes a suspended workflow step
35
- * @param stepId - ID of the step to resume
36
- * @param runId - ID of the workflow run
37
- * @param context - Context to resume the workflow with
38
- * @returns Promise containing the workflow resume results
165
+ * Creates a new workflow run
166
+ * @param params - Optional object containing the optional runId
167
+ * @returns Promise containing the runId of the created run
168
+ */
169
+ createRun(params?: { runId?: string }): Promise<{ runId: string }> {
170
+ const searchParams = new URLSearchParams();
171
+
172
+ if (!!params?.runId) {
173
+ searchParams.set('runId', params.runId);
174
+ }
175
+
176
+ return this.request(`/api/workflows/${this.workflowId}/create-run?${searchParams.toString()}`, {
177
+ method: 'POST',
178
+ });
179
+ }
180
+
181
+ /**
182
+ * Starts a workflow run synchronously without waiting for the workflow to complete
183
+ * @param params - Object containing the runId, inputData and runtimeContext
184
+ * @returns Promise containing success message
185
+ */
186
+ start(params: {
187
+ runId: string;
188
+ inputData: Record<string, any>;
189
+ runtimeContext?: RuntimeContext | Record<string, any>;
190
+ }): Promise<{ message: string }> {
191
+ const runtimeContext = parseClientRuntimeContext(params.runtimeContext);
192
+ return this.request(`/api/workflows/${this.workflowId}/start?runId=${params.runId}`, {
193
+ method: 'POST',
194
+ body: { inputData: params?.inputData, runtimeContext },
195
+ });
196
+ }
197
+
198
+ /**
199
+ * Resumes a suspended workflow step synchronously without waiting for the workflow to complete
200
+ * @param params - Object containing the runId, step, resumeData and runtimeContext
201
+ * @returns Promise containing success message
39
202
  */
40
203
  resume({
41
- stepId,
204
+ step,
42
205
  runId,
43
- context,
206
+ resumeData,
207
+ ...rest
44
208
  }: {
45
- stepId: string;
209
+ step: string | string[];
46
210
  runId: string;
47
- context: Record<string, any>;
48
- }): Promise<Record<string, any>> {
49
- return this.request(`/api/workflows/${this.workflowId}/resume`, {
211
+ resumeData?: Record<string, any>;
212
+ runtimeContext?: RuntimeContext | Record<string, any>;
213
+ }): Promise<{ message: string }> {
214
+ const runtimeContext = parseClientRuntimeContext(rest.runtimeContext);
215
+ return this.request(`/api/workflows/${this.workflowId}/resume?runId=${runId}`, {
50
216
  method: 'POST',
217
+ stream: true,
51
218
  body: {
52
- stepId,
53
- runId,
54
- context,
219
+ step,
220
+ resumeData,
221
+ runtimeContext,
222
+ },
223
+ });
224
+ }
225
+
226
+ /**
227
+ * Starts a workflow run asynchronously and returns a promise that resolves when the workflow is complete
228
+ * @param params - Object containing the optional runId, inputData and runtimeContext
229
+ * @returns Promise containing the workflow execution results
230
+ */
231
+ startAsync(params: {
232
+ runId?: string;
233
+ inputData: Record<string, any>;
234
+ runtimeContext?: RuntimeContext | Record<string, any>;
235
+ }): Promise<WorkflowRunResult> {
236
+ const searchParams = new URLSearchParams();
237
+
238
+ if (!!params?.runId) {
239
+ searchParams.set('runId', params.runId);
240
+ }
241
+
242
+ const runtimeContext = parseClientRuntimeContext(params.runtimeContext);
243
+
244
+ return this.request(`/api/workflows/${this.workflowId}/start-async?${searchParams.toString()}`, {
245
+ method: 'POST',
246
+ body: { inputData: params.inputData, runtimeContext },
247
+ });
248
+ }
249
+
250
+ /**
251
+ * Starts a workflow run and returns a stream
252
+ * @param params - Object containing the optional runId, inputData and runtimeContext
253
+ * @returns Promise containing the workflow execution results
254
+ */
255
+ async stream(params: { runId?: string; inputData: Record<string, any>; runtimeContext?: RuntimeContext }) {
256
+ const searchParams = new URLSearchParams();
257
+
258
+ if (!!params?.runId) {
259
+ searchParams.set('runId', params.runId);
260
+ }
261
+
262
+ const runtimeContext = parseClientRuntimeContext(params.runtimeContext);
263
+ const response: Response = await this.request(
264
+ `/api/workflows/${this.workflowId}/stream?${searchParams.toString()}`,
265
+ {
266
+ method: 'POST',
267
+ body: { inputData: params.inputData, runtimeContext },
268
+ stream: true,
269
+ },
270
+ );
271
+
272
+ if (!response.ok) {
273
+ throw new Error(`Failed to stream vNext workflow: ${response.statusText}`);
274
+ }
275
+
276
+ if (!response.body) {
277
+ throw new Error('Response body is null');
278
+ }
279
+
280
+ // Create a transform stream that processes the response body
281
+ const transformStream = new TransformStream<ArrayBuffer, { type: string; payload: any }>({
282
+ start() {},
283
+ async transform(chunk, controller) {
284
+ try {
285
+ // Decode binary data to text
286
+ const decoded = new TextDecoder().decode(chunk);
287
+
288
+ // Split by record separator
289
+ const chunks = decoded.split(RECORD_SEPARATOR);
290
+
291
+ // Process each chunk
292
+ for (const chunk of chunks) {
293
+ if (chunk) {
294
+ try {
295
+ const parsedChunk = JSON.parse(chunk);
296
+ controller.enqueue(parsedChunk);
297
+ } catch {
298
+ // Silently ignore parsing errors
299
+ }
300
+ }
301
+ }
302
+ } catch {
303
+ // Silently ignore processing errors
304
+ }
305
+ },
306
+ });
307
+
308
+ // Pipe the response body through the transform stream
309
+ return response.body.pipeThrough(transformStream);
310
+ }
311
+
312
+ /**
313
+ * Resumes a suspended workflow step asynchronously and returns a promise that resolves when the workflow is complete
314
+ * @param params - Object containing the runId, step, resumeData and runtimeContext
315
+ * @returns Promise containing the workflow resume results
316
+ */
317
+ resumeAsync(params: {
318
+ runId: string;
319
+ step: string | string[];
320
+ resumeData?: Record<string, any>;
321
+ runtimeContext?: RuntimeContext | Record<string, any>;
322
+ }): Promise<WorkflowRunResult> {
323
+ const runtimeContext = parseClientRuntimeContext(params.runtimeContext);
324
+ return this.request(`/api/workflows/${this.workflowId}/resume-async?runId=${params.runId}`, {
325
+ method: 'POST',
326
+ body: {
327
+ step: params.step,
328
+ resumeData: params.resumeData,
329
+ runtimeContext,
55
330
  },
56
331
  });
57
332
  }
58
333
 
59
334
  /**
60
335
  * Watches workflow transitions in real-time
61
- * @returns Promise containing the workflow watch stream
336
+ * @param runId - Optional run ID to filter the watch stream
337
+ * @returns AsyncGenerator that yields parsed records from the workflow watch stream
62
338
  */
63
- watch(): Promise<Response> {
64
- return this.request(`/api/workflows/${this.workflowId}/watch`, {
339
+ async watch({ runId }: { runId?: string }, onRecord: (record: WorkflowWatchResult) => void) {
340
+ const response: Response = await this.request(`/api/workflows/${this.workflowId}/watch?runId=${runId}`, {
65
341
  stream: true,
66
342
  });
343
+
344
+ if (!response.ok) {
345
+ throw new Error(`Failed to watch workflow: ${response.statusText}`);
346
+ }
347
+
348
+ if (!response.body) {
349
+ throw new Error('Response body is null');
350
+ }
351
+
352
+ for await (const record of this.streamProcessor(response.body)) {
353
+ if (typeof record === 'string') {
354
+ onRecord(JSON.parse(record));
355
+ } else {
356
+ onRecord(record);
357
+ }
358
+ }
359
+ }
360
+
361
+ /**
362
+ * Creates a new ReadableStream from an iterable or async iterable of objects,
363
+ * serializing each as JSON and separating them with the record separator (\x1E).
364
+ *
365
+ * @param records - An iterable or async iterable of objects to stream
366
+ * @returns A ReadableStream emitting the records as JSON strings separated by the record separator
367
+ */
368
+ static createRecordStream(records: Iterable<any> | AsyncIterable<any>): ReadableStream {
369
+ const encoder = new TextEncoder();
370
+ return new ReadableStream({
371
+ async start(controller) {
372
+ try {
373
+ for await (const record of records as AsyncIterable<any>) {
374
+ const json = JSON.stringify(record) + RECORD_SEPARATOR;
375
+ controller.enqueue(encoder.encode(json));
376
+ }
377
+ controller.close();
378
+ } catch (err) {
379
+ controller.error(err);
380
+ }
381
+ },
382
+ });
67
383
  }
68
384
  }