@output.ai/core 0.0.7 → 0.0.8

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 (30) hide show
  1. package/package.json +7 -3
  2. package/src/configs.js +1 -1
  3. package/src/consts.js +3 -3
  4. package/src/index.d.ts +302 -30
  5. package/src/index.js +3 -2
  6. package/src/interface/metadata.js +3 -3
  7. package/src/interface/step.js +3 -3
  8. package/src/interface/webhook.js +13 -14
  9. package/src/interface/workflow.js +22 -42
  10. package/src/internal_activities/index.js +16 -5
  11. package/src/worker/catalog_workflow/catalog.js +105 -0
  12. package/src/worker/catalog_workflow/index.js +21 -0
  13. package/src/worker/catalog_workflow/index.spec.js +139 -0
  14. package/src/worker/catalog_workflow/workflow.js +13 -0
  15. package/src/worker/index.js +37 -5
  16. package/src/worker/internal_utils.js +54 -0
  17. package/src/worker/internal_utils.spec.js +134 -0
  18. package/src/worker/loader.js +30 -44
  19. package/src/worker/loader.spec.js +68 -0
  20. package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.js +117 -0
  21. package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +77 -0
  22. package/src/worker/webpack_loaders/workflow_rewriter/consts.js +3 -0
  23. package/src/worker/webpack_loaders/workflow_rewriter/index.mjs +56 -0
  24. package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +129 -0
  25. package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.js +64 -0
  26. package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.spec.js +33 -0
  27. package/src/worker/webpack_loaders/workflow_rewriter/tools.js +225 -0
  28. package/src/worker/webpack_loaders/workflow_rewriter/tools.spec.js +144 -0
  29. package/src/errors.d.ts +0 -3
  30. package/src/worker/temp/__workflows_entrypoint.js +0 -6
@@ -0,0 +1,225 @@
1
+ import parser from '@babel/parser';
2
+ import { resolve as resolvePath } from 'node:path';
3
+ import { readFileSync } from 'node:fs';
4
+ import {
5
+ blockStatement,
6
+ callExpression,
7
+ functionExpression,
8
+ identifier,
9
+ isAssignmentPattern,
10
+ isBlockStatement,
11
+ isCallExpression,
12
+ isExportNamedDeclaration,
13
+ isIdentifier,
14
+ isStringLiteral,
15
+ isVariableDeclaration,
16
+ isObjectExpression,
17
+ memberExpression,
18
+ returnStatement,
19
+ stringLiteral,
20
+ thisExpression,
21
+ isExportDefaultDeclaration
22
+ } from '@babel/types';
23
+ import { NodeType } from './consts.js';
24
+
25
+ /**
26
+ * Resolve a relative module specifier against a base directory.
27
+ * @param {string} fileDir - Base directory to resolve from.
28
+ * @param {string} rel - Relative path/specifier.
29
+ * @returns {string} Absolute path.
30
+ */
31
+ export const toAbsolutePath = ( fileDir, rel ) => resolvePath( fileDir, rel );
32
+
33
+ /**
34
+ * Parse JavaScript/TypeScript source into a Babel AST.
35
+ * @param {string} source - Source code to parse.
36
+ * @param {string} filename - Virtual filename for sourcemaps and diagnostics.
37
+ * @returns {import('@babel/types').File} Parsed AST.
38
+ */
39
+ export const parse = ( source, filename ) => parser.parse( source, {
40
+ sourceType: 'module',
41
+ sourceFilename: filename,
42
+ plugins: [ 'jsx' ]
43
+ } );
44
+
45
+ /**
46
+ * Extract top-level constant string bindings (e.g., const NAME = 'value').
47
+ * @param {import('@babel/types').File} ast - Parsed file AST.
48
+ * @returns {Map<string, string>} Map from identifier to string literal value.
49
+ */
50
+ export const extractTopLevelStringConsts = ast =>
51
+ ast.program.body
52
+ .filter( node => isVariableDeclaration( node ) && node.kind === NodeType.CONST )
53
+ .reduce( ( map, node ) => {
54
+ node.declarations
55
+ .filter( dec => isIdentifier( dec.id ) && isStringLiteral( dec.init ) )
56
+ .forEach( dec => map.set( dec.id.name, dec.init.value ) );
57
+ return map;
58
+ }, new Map() );
59
+
60
+ /**
61
+ * Resolve an object key name from an Identifier or StringLiteral.
62
+ * @param {import('@babel/types').Expression} node - Object key node.
63
+ * @returns {string|null} Key name or null when unsupported.
64
+ */
65
+ export const getObjectKeyName = node => {
66
+ if ( isIdentifier( node ) ) { return node.name; }
67
+ if ( isStringLiteral( node ) ) { return node.value; }
68
+ return null;
69
+ };
70
+
71
+ /**
72
+ * Extract the local identifier name from a destructured ObjectProperty.
73
+ * Supports: { a } and { a: local } and { a: local = default }.
74
+ * @param {import('@babel/types').ObjectProperty} prop - Object property.
75
+ * @returns {string|null} Local identifier name or null.
76
+ */
77
+ export const getLocalNameFromDestructuredProperty = prop => {
78
+ if ( isIdentifier( prop.value ) ) { return prop.value.name; }
79
+ if ( isAssignmentPattern( prop.value ) && isIdentifier( prop.value.left ) ) { return prop.value.left.name; }
80
+ return null;
81
+ };
82
+
83
+ /**
84
+ * Convert an ArrowFunctionExpression to a FunctionExpression.
85
+ * Wraps expression bodies in a block with a return statement.
86
+ * @param {import('@babel/types').ArrowFunctionExpression} arrow - Arrow function.
87
+ * @returns {import('@babel/types').FunctionExpression} Function expression.
88
+ */
89
+ export const toFunctionExpression = arrow => {
90
+ const body = isBlockStatement( arrow.body ) ? arrow.body : blockStatement( [ returnStatement( arrow.body ) ] );
91
+ return functionExpression( null, arrow.params, body, arrow.generator ?? false, arrow.async ?? false );
92
+ };
93
+
94
+ /**
95
+ * Check if a module specifier or request string points to steps.js.
96
+ * @param {string} value - Module path or request string.
97
+ * @returns {boolean} True if it matches steps.js.
98
+ */
99
+ export const isStepsPath = value => /(^|\/)steps\.js$/.test( value );
100
+
101
+ /**
102
+ * Check if a module specifier or request string points to workflow.js.
103
+ * @param {string} value - Module path or request string.
104
+ * @returns {boolean} True if it matches workflow.js.
105
+ */
106
+ export const isWorkflowPath = value => /(^|\/)workflow\.js$/.test( value );
107
+
108
+ /**
109
+ * Create a `this.method(literalName, ...args)` CallExpression.
110
+ * @param {string} method - Method name on `this`.
111
+ * @param {string} literalName - First string literal argument.
112
+ * @param {import('@babel/types').Expression[]} args - Remaining call arguments.
113
+ * @returns {import('@babel/types').CallExpression} Call expression node.
114
+ */
115
+ export const createThisMethodCall = ( method, literalName, args ) =>
116
+ callExpression( memberExpression( thisExpression(), identifier( method ) ), [ stringLiteral( literalName ), ...args ] );
117
+
118
+ /**
119
+ * Resolve an options object's name property to a string.
120
+ * Accepts literal strings or top-level const string identifiers.
121
+ * @param {import('@babel/types').Expression} optionsNode - The call options object.
122
+ * @param {Map<string,string>} consts - Top-level const string bindings.
123
+ * @param {string} errorMessagePrefix - Prefix used when throwing validation errors.
124
+ * @returns {string} Resolved name.
125
+ * @throws {Error} When name is missing or not a supported static form.
126
+ */
127
+ export const resolveNameFromOptions = ( optionsNode, consts, errorMessagePrefix ) => {
128
+ // If it is not an object
129
+ if ( !isObjectExpression( optionsNode ) ) {
130
+ throw new Error( `${errorMessagePrefix}: Missing properties` );
131
+ }
132
+
133
+ // Look specifically for the 'name' property
134
+ for ( const prop of optionsNode.properties ) {
135
+ if ( getObjectKeyName( prop.key ) !== 'name' ) { continue; }
136
+
137
+ const val = prop.value;
138
+ // if it is a string literal: jackpot
139
+ if ( isStringLiteral( val ) ) { return val.value; }
140
+
141
+ // if it is an identifier, it needs to be deterministic (top-level const)
142
+ if ( isIdentifier( val ) ) {
143
+ if ( consts.has( val.name ) ) { return consts.get( val.name ); }
144
+ throw new Error( `${errorMessagePrefix}: Name identifier "${val.name}" is not a top-level const string` );
145
+ }
146
+
147
+ throw new Error( `${errorMessagePrefix}: Name must be a string literal or a top-level const string` );
148
+ }
149
+
150
+ throw new Error( `${errorMessagePrefix}: Missing required name property` ); // No name field found
151
+ };
152
+
153
+ /**
154
+ * Build a map from exported step variable name to declared step name.
155
+ * Parses the steps module and extracts `step({ name: '...' })` names.
156
+ * @param {string} path - Absolute path to the steps module file.
157
+ * @param {Map<string, Map<string,string>>} cache - Cache of computed step name maps.
158
+ * @returns {Map<string,string>} Exported identifier -> step name.
159
+ * @throws {Error} When a step name is invalid (non-static or missing).
160
+ */
161
+ export const buildStepsNameMap = ( path, cache ) => {
162
+ if ( cache.has( path ) ) { return cache.get( path ); }
163
+ const text = readFileSync( path, 'utf8' );
164
+ const ast = parse( text, path );
165
+ const consts = extractTopLevelStringConsts( ast );
166
+
167
+ const stepMap = ast.program.body
168
+ .filter( node => isExportNamedDeclaration( node ) && isVariableDeclaration( node.declaration ) )
169
+ .reduce( ( map, node ) => {
170
+
171
+ node.declaration.declarations
172
+ .filter( dec => isIdentifier( dec.id ) && isCallExpression( dec.init ) && isIdentifier( dec.init.callee, { name: 'step' } ) )
173
+ .map( dec => [
174
+ dec,
175
+ resolveNameFromOptions( dec.init.arguments[0], consts, `Invalid step name in ${path} for "${dec.id.name}"` )
176
+ ] )
177
+ .forEach( ( [ dec, name ] ) => map.set( dec.id.name, name ) );
178
+ return map;
179
+ }, new Map() );
180
+
181
+ cache.set( path, stepMap );
182
+ return stepMap;
183
+ };
184
+
185
+ /**
186
+ * Build a structure with default and named workflow names from a workflow module.
187
+ * Extracts names from `workflow({ name: '...' })` calls.
188
+ * @param {string} path - Absolute path to the workflow module file.
189
+ * @param {Map<string, {default: (string|null), named: Map<string,string>}>} cache - Cache of workflow names.
190
+ * @returns {{ default: (string|null), named: Map<string,string> }} Names.
191
+ * @throws {Error} When a workflow name is invalid (non-static or missing).
192
+ */
193
+ export const buildWorkflowNameMap = ( path, cache ) => {
194
+ if ( cache.has( path ) ) { return cache.get( path ); }
195
+ const text = readFileSync( path, 'utf8' );
196
+ const ast = parse( text, path );
197
+ const consts = extractTopLevelStringConsts( ast );
198
+
199
+ const result = { default: null, named: new Map() };
200
+
201
+ for ( const node of ast.program.body ) {
202
+
203
+ // named exports
204
+ if ( isExportNamedDeclaration( node ) && isVariableDeclaration( node.declaration ) ) {
205
+
206
+ for ( const d of node.declaration.declarations ) {
207
+ if ( isIdentifier( d.id ) && isCallExpression( d.init ) && isIdentifier( d.init.callee, { name: 'workflow' } ) ) {
208
+ const name = resolveNameFromOptions( d.init.arguments[0], consts, `Invalid workflow name in ${path} for '${d.id.name}` );
209
+ if ( name ) { result.named.set( d.id.name, name ); }
210
+ }
211
+ }
212
+
213
+ // default exports
214
+ } else if (
215
+ isExportDefaultDeclaration( node ) &&
216
+ isCallExpression( node.declaration ) &&
217
+ isIdentifier( node.declaration.callee, { name: 'workflow' } )
218
+ ) {
219
+ result.default = resolveNameFromOptions( node.declaration.arguments[0], consts, `Invalid default workflow name in ${path}` );
220
+ }
221
+ }
222
+
223
+ cache.set( path, result );
224
+ return result;
225
+ };
@@ -0,0 +1,144 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join, resolve as resolvePath } from 'node:path';
5
+ import * as t from '@babel/types';
6
+ import {
7
+ toAbsolutePath,
8
+ parse,
9
+ extractTopLevelStringConsts,
10
+ getObjectKeyName,
11
+ getLocalNameFromDestructuredProperty,
12
+ toFunctionExpression,
13
+ isStepsPath,
14
+ isWorkflowPath,
15
+ createThisMethodCall,
16
+ resolveNameFromOptions,
17
+ buildStepsNameMap,
18
+ buildWorkflowNameMap
19
+ } from './tools.js';
20
+
21
+ describe( 'workflow_rewriter tools', () => {
22
+ it( 'parse: parses JS with JSX plugin enabled', () => {
23
+ const ast = parse( 'const A = 1; const C = () => <div />', 'file.js' );
24
+ expect( ast?.type ).toBe( 'File' );
25
+ expect( ast.program.body.length ).toBeGreaterThan( 0 );
26
+ } );
27
+
28
+ it( 'toAbsolutePath: resolves relative path against base directory', () => {
29
+ expect( toAbsolutePath( '/base/dir', './file.js' ) ).toBe( resolvePath( '/base/dir', './file.js' ) );
30
+ } );
31
+
32
+ it( 'extractTopLevelStringConsts: returns only const string bindings', () => {
33
+ const ast = parse( [
34
+ 'const A = \"a\"; let B = \"b\"; const C = 3;',
35
+ 'const D = `d`; const E = \"e\"'
36
+ ].join( '\n' ), 'file.js' );
37
+ const map = extractTopLevelStringConsts( ast );
38
+ expect( map.get( 'A' ) ).toBe( 'a' );
39
+ expect( map.has( 'B' ) ).toBe( false );
40
+ expect( map.has( 'C' ) ).toBe( false );
41
+ // Template literal is not a StringLiteral
42
+ expect( map.has( 'D' ) ).toBe( false );
43
+ expect( map.get( 'E' ) ).toBe( 'e' );
44
+ } );
45
+
46
+ it( 'resolveNameFromOptions: returns literal name from options object', () => {
47
+ const opts = t.objectExpression( [ t.objectProperty( t.identifier( 'name' ), t.stringLiteral( 'literal.name' ) ) ] );
48
+ const out = resolveNameFromOptions( opts, new Map(), 'X' );
49
+ expect( out ).toBe( 'literal.name' );
50
+ } );
51
+
52
+ it( 'getObjectKeyName: resolves from Identifier and StringLiteral', () => {
53
+ expect( getObjectKeyName( t.identifier( 'name' ) ) ).toBe( 'name' );
54
+ expect( getObjectKeyName( t.stringLiteral( 'x' ) ) ).toBe( 'x' );
55
+ expect( getObjectKeyName( t.numericLiteral( 1 ) ) ).toBeNull();
56
+ } );
57
+
58
+ it( 'buildStepsNameMap: reads names from steps module and caches result', () => {
59
+ const dir = mkdtempSync( join( tmpdir(), 'tools-steps-' ) );
60
+ const stepsPath = join( dir, 'steps.js' );
61
+ writeFileSync( stepsPath, [
62
+ 'export const StepA = step({ name: "step.a" })',
63
+ 'export const StepB = step({ name: "step.b" })'
64
+ ].join( '\n' ) );
65
+ const cache = new Map();
66
+ const map1 = buildStepsNameMap( stepsPath, cache );
67
+ expect( map1.get( 'StepA' ) ).toBe( 'step.a' );
68
+ expect( map1.get( 'StepB' ) ).toBe( 'step.b' );
69
+ expect( cache.get( stepsPath ) ).toBe( map1 );
70
+ const map2 = buildStepsNameMap( stepsPath, cache );
71
+ expect( map2 ).toBe( map1 );
72
+ rmSync( dir, { recursive: true, force: true } );
73
+ } );
74
+
75
+ it( 'getLocalNameFromDestructuredProperty: handles { a }, { a: b }, { a: b = 1 }', () => {
76
+ // { a }
77
+ const p1 = t.objectProperty( t.identifier( 'a' ), t.identifier( 'a' ), false, true );
78
+ expect( getLocalNameFromDestructuredProperty( p1 ) ).toBe( 'a' );
79
+ // { a: b }
80
+ const p2 = t.objectProperty( t.identifier( 'a' ), t.identifier( 'b' ) );
81
+ expect( getLocalNameFromDestructuredProperty( p2 ) ).toBe( 'b' );
82
+ // { a: b = 1 }
83
+ const p3 = t.objectProperty( t.identifier( 'a' ), t.assignmentPattern( t.identifier( 'b' ), t.numericLiteral( 1 ) ) );
84
+ expect( getLocalNameFromDestructuredProperty( p3 ) ).toBe( 'b' );
85
+ // Unsupported shape
86
+ const p4 = t.objectProperty( t.identifier( 'a' ), t.arrayExpression( [] ) );
87
+ expect( getLocalNameFromDestructuredProperty( p4 ) ).toBeNull();
88
+ } );
89
+
90
+ it( 'buildWorkflowNameMap: reads named and default workflow names and caches', () => {
91
+ const dir = mkdtempSync( join( tmpdir(), 'tools-flow-' ) );
92
+ const wfPath = join( dir, 'workflow.js' );
93
+ writeFileSync( wfPath, [
94
+ 'export const FlowA = workflow({ name: "flow.a" })',
95
+ 'export default workflow({ name: "flow.def" })'
96
+ ].join( '\n' ) );
97
+ const cache = new Map();
98
+ const res1 = buildWorkflowNameMap( wfPath, cache );
99
+ expect( res1.named.get( 'FlowA' ) ).toBe( 'flow.a' );
100
+ expect( res1.default ).toBe( 'flow.def' );
101
+ expect( cache.get( wfPath ) ).toBe( res1 );
102
+ const res2 = buildWorkflowNameMap( wfPath, cache );
103
+ expect( res2 ).toBe( res1 );
104
+ rmSync( dir, { recursive: true, force: true } );
105
+ } );
106
+
107
+ it( 'toFunctionExpression: converts arrow, wraps expression bodies', () => {
108
+ const arrowExprBody = t.arrowFunctionExpression( [ t.identifier( 'x' ) ], t.identifier( 'x' ) );
109
+ const arrowBlockBody = t.arrowFunctionExpression( [], t.blockStatement( [ t.returnStatement( t.numericLiteral( 1 ) ) ] ) );
110
+ const fn1 = toFunctionExpression( arrowExprBody );
111
+ const fn2 = toFunctionExpression( arrowBlockBody );
112
+ expect( t.isFunctionExpression( fn1 ) ).toBe( true );
113
+ expect( t.isBlockStatement( fn1.body ) ).toBe( true );
114
+ expect( t.isReturnStatement( fn1.body.body[0] ) ).toBe( true );
115
+ expect( t.isFunctionExpression( fn2 ) ).toBe( true );
116
+ } );
117
+
118
+ it( 'isStepsPath: matches steps.js at root or subpath', () => {
119
+ expect( isStepsPath( 'steps.js' ) ).toBe( true );
120
+ expect( isStepsPath( './steps.js' ) ).toBe( true );
121
+ expect( isStepsPath( '/a/b/steps.js' ) ).toBe( true );
122
+ expect( isStepsPath( 'steps.ts' ) ).toBe( false );
123
+ expect( isStepsPath( 'workflow.js' ) ).toBe( false );
124
+ } );
125
+
126
+ it( 'isWorkflowPath: matches workflow.js at root or subpath', () => {
127
+ expect( isWorkflowPath( 'workflow.js' ) ).toBe( true );
128
+ expect( isWorkflowPath( './workflow.js' ) ).toBe( true );
129
+ expect( isWorkflowPath( '/a/b/workflow.js' ) ).toBe( true );
130
+ expect( isWorkflowPath( 'workflow.ts' ) ).toBe( false );
131
+ expect( isWorkflowPath( 'steps.js' ) ).toBe( false );
132
+ } );
133
+
134
+ it( 'createThisMethodCall: builds this.method(\'name\', ...args) call', () => {
135
+ const call = createThisMethodCall( 'invoke', 'n', [ t.numericLiteral( 1 ), t.identifier( 'x' ) ] );
136
+ expect( t.isCallExpression( call ) ).toBe( true );
137
+ expect( t.isMemberExpression( call.callee ) ).toBe( true );
138
+ expect( t.isThisExpression( call.callee.object ) ).toBe( true );
139
+ expect( t.isIdentifier( call.callee.property, { name: 'invoke' } ) ).toBe( true );
140
+ expect( t.isStringLiteral( call.arguments[0], { value: 'n' } ) ).toBe( true );
141
+ expect( call.arguments.length ).toBe( 3 );
142
+ } );
143
+ } );
144
+
package/src/errors.d.ts DELETED
@@ -1,3 +0,0 @@
1
- export declare class FatalError extends Error { }
2
-
3
- export declare class ValidationError extends Error { }
@@ -1,6 +0,0 @@
1
- export { default as articleDraftAgentic } from '/app/test-workflows/dist/article-draft-agentic/index.js';
2
- export { default as httpClientDemo } from '/app/test-workflows/dist/http/index.js';
3
- export { default as nested } from '/app/test-workflows/dist/nested/index.js';
4
- export { default as local_prompt } from '/app/test-workflows/dist/prompt/index.js';
5
- export { default as simple } from '/app/test-workflows/dist/simple/index.js';
6
- export { default as webhook } from '/app/test-workflows/dist/webhook/index.js';