@output.ai/core 0.1.14 → 0.1.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@output.ai/core",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "The core module of the output framework",
5
5
  "type": "module",
6
6
  "exports": {
package/src/index.d.ts CHANGED
@@ -45,6 +45,55 @@ export type WorkflowCallConfig = {
45
45
  detached?: boolean
46
46
  };
47
47
 
48
+ /**
49
+ * The second argument for the workflow's fn function
50
+ */
51
+ export type WorkflowContext<
52
+ InputSchema extends AnyZodSchema | undefined = undefined,
53
+ OutputSchema extends AnyZodSchema | undefined = undefined
54
+ > = {
55
+
56
+ /**
57
+ * Functions that allow fine control over the underlying Temporal workflows
58
+ */
59
+ control: {
60
+ /**
61
+ * Lets Workflow Execution close successfully and creates a new Workflow Execution.
62
+ *
63
+ * The new Workflow Execution is in the same chain as previous workflow but it will generate another trace file.
64
+ *
65
+ * It is akin to a checkpoint for when the Workflow gets too long or approaches certain scaling limits.
66
+ *
67
+ * It accepts input with the same schema as the parent workflow function (`inputSchema`).
68
+ *
69
+ * Calling the function needs to be the last statement for the workflow, accompanied by a `return`:
70
+ * ```js
71
+ * return control.continueAsNew();
72
+ * ```
73
+ * Upon returning it, the parent workflow execution is closed without any output and the new execution takes its place.
74
+ *
75
+ * The function return type respects the `outputSchema` although no values are ever returned, the execution is just replaced.
76
+ *
77
+ * @see {@link https://docs.temporal.io/develop/typescript/continue-as-new}
78
+ * @param {z.infer<InputSchema>|undefined} input - The input for the new run. Omit when the workflow has no input schema.
79
+ * @returns {z.infer<OutputSchema>|void} The workflow output type for type-checking; never returns at runtime.
80
+ */
81
+ continueAsNew: InputSchema extends AnyZodSchema ?
82
+ ( input: z.infer<InputSchema> ) => ( OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void ) :
83
+ () => ( OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void ),
84
+
85
+ /** Indicates whether the Temporal runtime suggests continuing this workflow as new.
86
+ *
87
+ * Use this to decide whether to `continueAsNew` before long waits or at loop boundaries.
88
+ * Prefer returning the `continueAsNew(...)` call immediately when this becomes `true`.
89
+ *
90
+ * @see {@link https://docs.temporal.io/develop/typescript/continue-as-new#how-to-test}
91
+ * @returns {boolean} True if a continue-as-new is suggested for the current run; otherwise false.
92
+ */
93
+ isContinueAsNewSuggested: () => boolean
94
+ }
95
+ };
96
+
48
97
  /*
49
98
  ╭─────────╮
50
99
  │ S T E P │╮
@@ -165,9 +214,9 @@ export async function step( options: {
165
214
  * @param {string} [options.description] - Description of the workflow
166
215
  * @param {z.ZodType} options.inputSchema - Zod schema for workflow input
167
216
  * @param {z.ZodType} options.outputSchema - Zod schema for workflow output
168
- * @param {function} options.fn - Workflow logic: `(input: z.infer<InputSchema>) => Promise<z.infer<OutputSchema>>`
217
+ * @param {function} options.fn - Workflow logic
169
218
  * @param {Options} [options.options] - Activity retry options
170
- * @returns {function} Callable workflow function: `(input: z.infer<InputSchema>) => Promise<z.infer<OutputSchema>>`
219
+ * @returns {function} Callable workflow function
171
220
  */
172
221
  export function workflow<
173
222
  InputSchema extends AnyZodSchema,
@@ -177,7 +226,7 @@ export function workflow<
177
226
  description?: string,
178
227
  inputSchema: InputSchema,
179
228
  outputSchema: OutputSchema,
180
- fn: ( input: z.infer<InputSchema> ) => Promise<z.infer<OutputSchema>>,
229
+ fn: ( input: z.infer<InputSchema>, context: WorkflowContext<InputSchema, OutputSchema> ) => Promise<z.infer<OutputSchema>>,
181
230
  options?: Options
182
231
  } ): ( input: z.infer<InputSchema>, extra?: WorkflowCallConfig ) => Promise<z.infer<OutputSchema>>;
183
232
 
@@ -188,9 +237,9 @@ export function workflow<
188
237
  * @param {string} options.name - Unique workflow name
189
238
  * @param {string} [options.description] - Description of the workflow
190
239
  * @param {z.ZodType} options.inputSchema - Zod schema for workflow input
191
- * @param {function} options.fn - Workflow logic: `(input: z.infer<InputSchema>) => Promise<void>`
240
+ * @param {function} options.fn - Workflow logic
192
241
  * @param {Options} [options.options] - Activity retry options
193
- * @returns {function} Callable workflow function: `(input: z.infer<InputSchema>) => Promise<void>`
242
+ * @returns {function} Callable workflow function
194
243
  */
195
244
  export function workflow<
196
245
  InputSchema extends AnyZodSchema
@@ -198,7 +247,7 @@ export function workflow<
198
247
  name: string,
199
248
  description?: string,
200
249
  inputSchema: InputSchema,
201
- fn: ( input: z.infer<InputSchema> ) => Promise<void>,
250
+ fn: ( input: z.infer<InputSchema>, context: WorkflowContext<InputSchema, undefined> ) => Promise<void>,
202
251
  options?: Options
203
252
  } ): ( input: z.infer<InputSchema>, extra?: WorkflowCallConfig ) => Promise<void>;
204
253
 
@@ -209,9 +258,9 @@ export function workflow<
209
258
  * @param {string} options.name - Unique workflow name
210
259
  * @param {string} [options.description] - Description of the workflow
211
260
  * @param {z.ZodType} options.outputSchema - Zod schema for workflow output
212
- * @param {function} options.fn - Workflow logic: `() => Promise<z.infer<OutputSchema>>`
261
+ * @param {function} options.fn - Workflow logic
213
262
  * @param {Options} [options.options] - Activity retry options
214
- * @returns {function} Callable workflow function: `() => Promise<z.infer<OutputSchema>>`
263
+ * @returns {function} Callable workflow function
215
264
  */
216
265
  export function workflow<
217
266
  OutputSchema extends AnyZodSchema
@@ -219,7 +268,7 @@ export function workflow<
219
268
  name: string,
220
269
  description?: string,
221
270
  outputSchema: OutputSchema,
222
- fn: () => Promise<z.infer<OutputSchema>>,
271
+ fn: ( input?: undefined | null, context: WorkflowContext<undefined, OutputSchema> ) => Promise<z.infer<OutputSchema>>,
223
272
  options?: Options
224
273
  } ): ( input?: undefined | null, extra?: WorkflowCallConfig ) => Promise<z.infer<OutputSchema>>;
225
274
 
@@ -229,14 +278,14 @@ export function workflow<
229
278
  * @param {object} options - Workflow options
230
279
  * @param {string} options.name - Unique workflow name
231
280
  * @param {string} [options.description] - Description of the workflow
232
- * @param {function} options.fn - Workflow logic: `() => Promise<void>`
281
+ * @param {function} options.fn - Workflow logic
233
282
  * @param {Options} [options.options] - Activity retry options
234
- * @returns {function} Callable workflow function: `() => Promise<void>`
283
+ * @returns {function} Callable workflow function
235
284
  */
236
285
  export function workflow( options : {
237
286
  name: string,
238
287
  description?: string,
239
- fn: () => Promise<void>,
288
+ fn: ( input?: undefined | null, context: WorkflowContext<undefined, undefined> ) => Promise<void>,
240
289
  options?: Options
241
290
  } ): ( input?: undefined | null, extra?: WorkflowCallConfig ) => Promise<void>;
242
291
 
package/src/index.js CHANGED
@@ -3,6 +3,7 @@ import { step } from './interface/step.js';
3
3
  import { workflow } from './interface/workflow.js';
4
4
  import { createWebhook } from './interface/webhook.js';
5
5
  import { FatalError, ValidationError } from './errors.js';
6
+ export { continueAsNew } from '@temporalio/workflow';
6
7
  import { z } from 'zod';
7
8
 
8
9
  export {
@@ -1,5 +1,5 @@
1
1
  // THIS RUNS IN THE TEMPORAL'S SANDBOX ENVIRONMENT
2
- import { proxyActivities, inWorkflowContext, executeChild, workflowInfo, uuid4, ParentClosePolicy } from '@temporalio/workflow';
2
+ import { proxyActivities, inWorkflowContext, executeChild, workflowInfo, uuid4, ParentClosePolicy, continueAsNew } from '@temporalio/workflow';
3
3
  import { validateWorkflow } from './validations/static.js';
4
4
  import { validateWithSchema } from './validations/runtime.js';
5
5
  import { SHARED_STEP_PREFIX, ACTIVITY_GET_TRACE_DESTINATIONS } from '#consts';
@@ -25,10 +25,9 @@ export function workflow( { name, description, inputSchema, outputSchema, fn, op
25
25
  const steps = proxyActivities( activityOptions );
26
26
 
27
27
  const wrapper = async input => {
28
- validateWithSchema( inputSchema, input, `Workflow ${name} input` );
29
-
30
28
  // this returns a plain function, for example, in unit tests
31
29
  if ( !inWorkflowContext() ) {
30
+ validateWithSchema( inputSchema, input, `Workflow ${name} input` );
32
31
  const output = await fn( input );
33
32
  validateWithSchema( outputSchema, output, `Workflow ${name} output` );
34
33
  return output;
@@ -36,12 +35,22 @@ export function workflow( { name, description, inputSchema, outputSchema, fn, op
36
35
 
37
36
  const { workflowId, memo, startTime } = workflowInfo();
38
37
 
38
+ /* Context Object definition
39
+ - Control namespace: This object adds functions to interact with Temporal flow mechanisms
40
+ */
41
+ const context = {
42
+ control: {
43
+ isContinueAsNewSuggested: () => workflowInfo().continueAsNewSuggested,
44
+ continueAsNew
45
+ }
46
+ };
47
+
39
48
  // Root workflows will not have the execution context yet, since it is set here.
40
49
  const isRoot = !memo.executionContext;
41
50
 
42
- // Create the execution context object or preserve if it already exists:
43
- // It will always contains the information about the root workflow
44
- // It will be used to as context for tracing (connecting events)
51
+ /* Creates the execution context object or preserve if it already exists:
52
+ It will always contain the information about the root workflow
53
+ It will be used to as context for tracing (connecting events) */
45
54
  const executionContext = memo.executionContext ?? {
46
55
  workflowId,
47
56
  workflowName: name,
@@ -53,6 +62,9 @@ export function workflow( { name, description, inputSchema, outputSchema, fn, op
53
62
  activityOptions: memo.activityOptions ?? activityOptions // Also preserve the original activity options
54
63
  } );
55
64
 
65
+ // validation comes after setting memo to have that info already set for interceptor even if validations fail
66
+ validateWithSchema( inputSchema, input, `Workflow ${name} input` );
67
+
56
68
  // binds the methods called in the code that Webpack loader will add, they will exposed via "this"
57
69
  const output = await fn.call( {
58
70
  invokeStep: async ( stepName, input, options ) => steps[`${workflowPath}#${stepName}`]( input, options ),
@@ -81,7 +93,7 @@ export function workflow( { name, description, inputSchema, outputSchema, fn, op
81
93
  ...( extra?.options && { activityOptions: mergeActivityOptions( activityOptions, extra.options ) } )
82
94
  }
83
95
  } )
84
- }, input );
96
+ }, input, context );
85
97
 
86
98
  validateWithSchema( outputSchema, output, `Workflow ${name} output` );
87
99
 
@@ -4,6 +4,7 @@ import { serializeError } from './tools/utils.js';
4
4
  import { isStringboolTrue } from '#utils';
5
5
  import * as localProcessor from './processors/local/index.js';
6
6
  import * as s3Processor from './processors/s3/index.js';
7
+ import { ComponentType } from '#consts';
7
8
 
8
9
  const traceBus = new EventEmitter();
9
10
  const processors = [
@@ -49,11 +50,15 @@ const serializeDetails = details => details instanceof Error ? serializeError( d
49
50
  * @param {object} fields - All the trace fields
50
51
  * @returns {void}
51
52
  */
52
- export const addEventPhase = ( phase, { kind, name, id, parentId, details, executionContext } ) =>
53
- traceBus.emit( 'entry', {
54
- executionContext,
55
- entry: { kind, phase, name, id, parentId, phase, timestamp: Date.now(), details: serializeDetails( details ) }
56
- } );
53
+ export const addEventPhase = ( phase, { kind, name, id, parentId, details, executionContext } ) => {
54
+ // Ignores internal steps in the actual trace files
55
+ if ( kind !== ComponentType.INTERNAL_STEP ) {
56
+ traceBus.emit( 'entry', {
57
+ executionContext,
58
+ entry: { kind, phase, name, id, parentId, phase, timestamp: Date.now(), details: serializeDetails( details ) }
59
+ } );
60
+ }
61
+ };
57
62
 
58
63
  /**
59
64
  * Adds an Event Phase, complementing the options with parentId and executionContext from the async storage.
@@ -1,5 +1,5 @@
1
1
  // THIS RUNS IN THE TEMPORAL'S SANDBOX ENVIRONMENT
2
- import { workflowInfo, proxySinks, ApplicationFailure } from '@temporalio/workflow';
2
+ import { workflowInfo, proxySinks, ApplicationFailure, ContinueAsNew } from '@temporalio/workflow';
3
3
  import { memoToHeaders } from '../sandboxed_utils.js';
4
4
  import { mergeActivityOptions } from '#utils';
5
5
  // this is a dynamic generated file with activity configs overwrites
@@ -37,6 +37,13 @@ class WorkflowExecutionInterceptor {
37
37
  sinks.trace.addWorkflowEventEnd( output );
38
38
  return output;
39
39
  } catch ( error ) {
40
+ /* When the error is ContinueAsNew it represents the point where the actual workflow code was
41
+ delegated to another run. In this case the result in the traces will be the string below and
42
+ a new trace file will be generated */
43
+ if ( error instanceof ContinueAsNew ) {
44
+ sinks.trace.addWorkflowEventEnd( '<continued_as_new>' );
45
+ throw error;
46
+ }
40
47
  sinks.trace.addWorkflowEventError( error );
41
48
  throw new ApplicationFailure( error.message, error.constructor.name );
42
49
  }