@output.ai/core 0.1.2 → 0.1.3

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.2",
3
+ "version": "0.1.3",
4
4
  "description": "The core module of the output framework",
5
5
  "type": "module",
6
6
  "exports": {
@@ -11,6 +11,10 @@
11
11
  "./tracing": {
12
12
  "types": "./src/tracing/index.d.ts",
13
13
  "import": "./src/tracing/index.js"
14
+ },
15
+ "./utils": {
16
+ "types": "./src/utils/index.d.ts",
17
+ "import": "./src/utils/index.js"
14
18
  }
15
19
  },
16
20
  "files": [
@@ -33,13 +37,14 @@
33
37
  "@temporalio/worker": "1.13.1",
34
38
  "@temporalio/workflow": "1.13.1",
35
39
  "redis": "5.8.3",
40
+ "stacktrace-parser": "0.1.11",
36
41
  "zod": "4.1.12"
37
42
  },
38
43
  "license": "UNLICENSED",
39
44
  "imports": {
40
45
  "#consts": "./src/consts.js",
41
46
  "#errors": "./src/errors.js",
42
- "#utils": "./src/utils.js",
47
+ "#utils": "./src/utils/index.js",
43
48
  "#tracing": "./src/tracing/internal_interface.js",
44
49
  "#async_storage": "./src/async_storage.js",
45
50
  "#temporal_options": "./src/temporal_options.js",
@@ -1,10 +1,9 @@
1
1
  // THIS RUNS IN THE TEMPORAL'S SANDBOX ENVIRONMENT
2
2
  import { proxyActivities, inWorkflowContext, executeChild, workflowInfo } from '@temporalio/workflow';
3
- import { getInvocationDir } from './utils.js';
4
3
  import { validateWorkflow } from './validations/static.js';
5
4
  import { validateWithSchema } from './validations/runtime.js';
6
5
  import { SHARED_STEP_PREFIX } from '#consts';
7
- import { mergeActivityOptions, setMetadata } from '#utils';
6
+ import { mergeActivityOptions, resolveInvocationDir, setMetadata } from '#utils';
8
7
  import { FatalError, ValidationError } from '#errors';
9
8
 
10
9
  const defaultActivityOptions = {
@@ -20,7 +19,7 @@ const defaultActivityOptions = {
20
19
 
21
20
  export function workflow( { name, description, inputSchema, outputSchema, fn, options } ) {
22
21
  validateWorkflow( { name, description, inputSchema, outputSchema, fn, options } );
23
- const workflowPath = getInvocationDir();
22
+ const workflowPath = resolveInvocationDir();
24
23
 
25
24
  const activityOptions = mergeActivityOptions( defaultActivityOptions, options );
26
25
  const steps = proxyActivities( activityOptions );
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Return the directory of the file invoking the code that called this function
3
+ * Excludes `@output.ai/core`, node, and other internal paths
4
+ */
5
+ export function resolveInvocationDir(): string;
6
+
7
+ /**
8
+ * Node safe clone implementation that doesn't use global structuredClone()
9
+ * @param {object} v
10
+ * @returns {object}
11
+ */
12
+ export function clone( v: object ): object;
13
+
14
+ /**
15
+ * Throw given error
16
+ * @param {Error} e
17
+ * @throws {e}
18
+ */
19
+ export function throws( e: Error ): void;
20
+
21
+ /**
22
+ * Add metadata "values" property to a given object
23
+ * @param {object} target
24
+ * @param {object} values
25
+ * @returns
26
+ */
27
+ export function setMetadata( target: object, values: object ): void;
28
+
29
+ /**
30
+ * Merge two temporal activity options
31
+ */
32
+ export function mergeActivityOptions(
33
+ base?: import( '@temporalio/workflow' ).ActivityOptions,
34
+ ext?: import( '@temporalio/workflow' ).ActivityOptions
35
+ ): import( '@temporalio/workflow' ).ActivityOptions;
@@ -0,0 +1,2 @@
1
+ export { default as resolveInvocationDir } from './resolve_invocation_dir.js';
2
+ export * from './utils.js';
@@ -0,0 +1,27 @@
1
+ import * as stackTraceParser from 'stacktrace-parser';
2
+
3
+ // OS separator, but in a deterministic way, allowing this to work in Temporal's sandbox
4
+ // This avoids importing from node:path
5
+ const SEP = new Error().stack.includes( '/' ) ? '/' : '\\';
6
+ const ignorePaths = [
7
+ `${SEP}node_modules${SEP}`,
8
+ `${SEP}app${SEP}sdk${SEP}`,
9
+ `node:internal${SEP}`,
10
+ 'evalmachine.',
11
+ `webpack${SEP}bootstrap`
12
+ ];
13
+
14
+ /**
15
+ * Return the directory of the file invoking the code that called this function
16
+ * Excludes some internal paths and the sdk itself
17
+ */
18
+ export default () => {
19
+ const stack = new Error().stack;
20
+ const lines = stackTraceParser.parse( stack );
21
+
22
+ const frame = lines.find( l => !ignorePaths.some( p => l.file.includes( p ) ) );
23
+ if ( !frame ) {
24
+ throw new Error( `Invocation dir resolution via stack trace failed. Stack: ${stack}` );
25
+ }
26
+ return frame.file.replace( 'file://', '' ).split( SEP ).slice( 0, -1 ).join( SEP );
27
+ };
@@ -0,0 +1,102 @@
1
+ import { afterEach, describe, expect, it } from 'vitest';
2
+ import resolveInvocationDir from './resolve_invocation_dir';
3
+
4
+ const OriginalError = Error;
5
+
6
+ describe( 'Resolve Invocation Dir', () => {
7
+ afterEach( () => {
8
+ Error = OriginalError;
9
+ } );
10
+
11
+ it( 'Should detect the invocation dir from the tests workflow', () => {
12
+ const stack = `Error
13
+ at resolveInvocationDir (file:///app/sdk/core/src/utils/resolve_invocation_dir.js)
14
+ at fn (file:///app/test_workflows/dist/simple/steps.js:8:21)
15
+ at wrapper (file:///app/sdk/core/src/interface/step.js:12:26)
16
+ at executeNextHandler (/app/node_modules/@temporalio/worker/lib/activity.js:99:54)
17
+ at Storage.runWithContext.parentId (file:///app/sdk/core/src/worker/interceptors/activity.js:31:63)
18
+ at AsyncLocalStorage.run (node:internal/async_local_storage/async_context_frame:63:14)
19
+ at Object.runWithContext (file:///app/sdk/core/src/async_storage.js:12:44)
20
+ at ActivityExecutionInterceptor.execute (file:///app/sdk/core/src/worker/interceptors/activity.js:31:36)
21
+ at next (/app/node_modules/@temporalio/common/lib/interceptors.js:22:51)
22
+ at Activity.execute (/app/node_modules/@temporalio/worker/lib/activity.js:101:26)
23
+ at /app/node_modules/@temporalio/worker/lib/activity.js:149:29`;
24
+
25
+ Error = class extends OriginalError {
26
+ constructor( ...args ) {
27
+ super( ...args );
28
+ this.stack = stack;
29
+ }
30
+ };
31
+
32
+ expect( resolveInvocationDir() ).toBe( '/app/test_workflows/dist/simple' );
33
+ } );
34
+
35
+ it( 'Should detect the invocation dir from the sandbox environment at sdk/core', () => {
36
+ const stack = `Error
37
+ at resolveInvocationDir (file:///app/sdk/core/src/utils/resolve_invocation_dir.js)
38
+ at workflow (file:///app/sdk/core/src/interface/workflow.js:25:16)
39
+ at file:///app/test_workflows/dist/nested/workflow.js:4:16
40
+ at ModuleJob.run (node:internal/modules/esm/module_job:365:25)
41
+ at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:665:26)
42
+ at async importComponents (file:///app/sdk/core/src/worker/loader_tools.js:54:22)
43
+ at async loadWorkflows (file:///app/sdk/core/src/worker/loader.js:64:38)
44
+ at async file:///app/sdk/core/src/worker/index.js:21:21`;
45
+
46
+ Error = class extends OriginalError {
47
+ constructor( ...args ) {
48
+ super( ...args );
49
+ this.stack = stack;
50
+ }
51
+ };
52
+
53
+ expect( resolveInvocationDir() ).toBe( '/app/test_workflows/dist/nested' );
54
+ } );
55
+
56
+ it( 'Should detect the invocation dir from workflow loading at core', () => {
57
+ const stack = `Error
58
+ at __WEBPACK_DEFAULT_EXPORT__ (/app/sdk/core/src/utils/resolve_invocation_dir.js:13:0)
59
+ at workflow (/app/sdk/core/src/interface/workflow.js:22:43)
60
+ at ../../test_workflows/dist/nested/workflow.js (/app/test_workflows/dist/nested/workflow.js:4:24)
61
+ at __webpack_require__ (webpack/bootstrap:19:0)
62
+ at ./src/worker/temp/__workflows_entrypoint.js (null:null:null)
63
+ at __webpack_require__ (webpack/bootstrap:19:0)
64
+ at importWorkflows (/app/sdk/core/src/worker/temp/__workflows_entrypoint-autogenerated-entrypoint.cjs:9:9)
65
+ at Object.initRuntime (/app/node_modules/@temporalio/workflow/src/worker-interface.ts:78:16)
66
+ at __TEMPORAL_CALL_INTO_SCOPE (evalmachine.<anonymous>:30:40)
67
+ at evalmachine.<anonymous>:1:1`;
68
+
69
+ Error = class extends OriginalError {
70
+ constructor( ...args ) {
71
+ super( ...args );
72
+ this.stack = stack;
73
+ }
74
+ };
75
+
76
+ expect( resolveInvocationDir() ).toBe( '/app/test_workflows/dist/nested' );
77
+ } );
78
+
79
+ it( 'Should detect the invocation dir from a workflow using core installed via NPM', () => {
80
+ const stack = `Error
81
+ at resolveInvocationDir (file:///app/node_modules/@output.ai/core/src/utils/resolve_invocation_dir.js)
82
+ at fn (file:///app/dist/simple/steps.js:8:21)
83
+ at wrapper (file:///app/node_modules/@output.ai/core/src/interface/step.js:12:26)
84
+ at executeNextHandler (/app/node_modules/@temporalio/worker/lib/activity.js:99:54)
85
+ at Storage.runWithContext.parentId (file:///app/node_modules/@output.ai/core/src/worker/interceptors/activity.js:31:63)
86
+ at AsyncLocalStorage.run (node:internal/async_local_storage/async_context_frame:63:14)
87
+ at Object.runWithContext (file:///app/node_modules/@output.ai/core/src/async_storage.js:12:44)
88
+ at ActivityExecutionInterceptor.execute (file:///app/node_modules/@output.ai/core/src/worker/interceptors/activity.js:31:36)
89
+ at next (/app/node_modules/@temporalio/common/lib/interceptors.js:22:51)
90
+ at Activity.execute (/app/node_modules/@temporalio/worker/lib/activity.js:101:26)
91
+ at /app/node_modules/@temporalio/worker/lib/activity.js:149:29`;
92
+
93
+ Error = class extends OriginalError {
94
+ constructor( ...args ) {
95
+ super( ...args );
96
+ this.stack = stack;
97
+ }
98
+ };
99
+
100
+ expect( resolveInvocationDir() ).toBe( '/app/dist/simple' );
101
+ } );
102
+ } );
@@ -1,19 +0,0 @@
1
- /**
2
- * Function rigged to return the folder path of the source of calls for the interface methods (step/workflow)
3
- *
4
- * IMPORTANT!!!
5
- * If to refactor this, pay attention to the depth in the stack trace to extract the info.
6
- * Currently it is 3:
7
- * - 1st line is the name of the function;
8
- * - 2nd line is this function;
9
- * - 3rd line is step/workflow;
10
- * - 4th line is caller;
11
- *
12
- * @returns {string} The folder path of the caller
13
- */
14
- export const getInvocationDir = () => new Error()
15
- .stack.split( '\n' )[3]
16
- .split( ' ' )
17
- .at( -1 )
18
- .replace( /\((.+):\d+:\d+\)/, '$1' )
19
- .split( '/' ).slice( 0, -1 ).join( '/' );
@@ -1,35 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { getInvocationDir } from './utils.js';
3
-
4
- describe( 'interface/utils', () => {
5
- describe( 'getInvocationDir', () => {
6
- it( 'returns the caller directory from stack trace', () => {
7
- const fakeCaller = '/tmp/project/src/caller/file.js';
8
- const OriginalError = Error;
9
- // Provide a deterministic stack shape for the function under test
10
- // Lines: 1) Error, 2) getInvocationDir, 3) step/workflow, 4) actual caller
11
- // Include typical V8 formatting with leading spaces and without function name
12
- // for the caller line
13
-
14
- Error = class extends OriginalError {
15
- constructor( ...args ) {
16
- super( ...args );
17
- this.stack = [
18
- 'Error',
19
- ' at getInvocationDir (a:1:1)',
20
- ' at step (b:1:1)',
21
- ` at ${fakeCaller}:10:20`
22
- ].join( '\n' );
23
- }
24
- };
25
- try {
26
- const dir = getInvocationDir();
27
- expect( dir ).toBe( '/tmp/project/src/caller' );
28
- } finally {
29
-
30
- Error = OriginalError;
31
- }
32
- } );
33
- } );
34
- } );
35
-
File without changes
File without changes