@output.ai/core 0.4.2 → 0.4.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.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "The core module of the output framework",
5
5
  "type": "module",
6
6
  "exports": {
@@ -5,16 +5,19 @@ import { EOL } from 'node:os';
5
5
 
6
6
  const oneMonthInSeconds = 60 * 60 * 24 * 30;
7
7
 
8
- const accumulate = async ( { entry, executionContext: { workflowName, workflowId } } ) => {
8
+ const addEntry = async ( { entry, executionContext: { workflowName, workflowId } } ) => {
9
9
  const key = `traces/${workflowName}/${workflowId}`;
10
- const transaction = ( await getRedisClient() ).multi();
11
-
12
- transaction.zAdd( key, [
13
- { score: entry.timestamp, value: JSON.stringify( entry ) }
14
- ], { NX: true } );
15
- transaction.expire( key, oneMonthInSeconds, 'GT' );
16
- transaction.zRange( key, 0, -1 );
17
- const [ ,, zList ] = await transaction.exec();
10
+ const client = await getRedisClient();
11
+ await client.multi()
12
+ .zAdd( key, [ { score: entry.timestamp, value: JSON.stringify( entry ) } ], { NX: true } )
13
+ .expire( key, oneMonthInSeconds, 'GT' )
14
+ .exec();
15
+ };
16
+
17
+ const getAllEntries = async ( { workflowName, workflowId } ) => {
18
+ const key = `traces/${workflowName}/${workflowId}`;
19
+ const client = await getRedisClient();
20
+ const zList = await client.zRange( key, 0, -1 );
18
21
  return zList.map( v => JSON.parse( v ) );
19
22
  };
20
23
 
@@ -41,13 +44,19 @@ export const init = async () => {
41
44
  */
42
45
  export const exec = async ( { entry, executionContext } ) => {
43
46
  const { workflowName, workflowId, startTime } = executionContext;
44
- const content = buildTraceTree( await accumulate( { entry, executionContext } ) );
47
+
48
+ await addEntry( { entry, executionContext } );
45
49
 
46
50
  const isRootWorkflowEnd = !entry.parentId && entry.phase !== 'start';
47
- return isRootWorkflowEnd ? upload( {
51
+ if ( !isRootWorkflowEnd ) {
52
+ return 0;
53
+ }
54
+
55
+ const content = buildTraceTree( await getAllEntries( { workflowName, workflowId } ) );
56
+ return upload( {
48
57
  key: getS3Key( { workflowId, workflowName, startTime } ),
49
58
  content: JSON.stringify( content, undefined, 2 ) + EOL
50
- } ) : 0;
59
+ } );
51
60
  };
52
61
 
53
62
  /**
@@ -3,10 +3,10 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
3
3
  const redisMulti = {
4
4
  zAdd: vi.fn().mockReturnThis(),
5
5
  expire: vi.fn().mockReturnThis(),
6
- zRange: vi.fn().mockReturnThis(),
7
6
  exec: vi.fn()
8
7
  };
9
- const getRedisClientMock = vi.fn( async () => ( { multi: () => redisMulti } ) );
8
+ const zRangeMock = vi.fn();
9
+ const getRedisClientMock = vi.fn( async () => ( { multi: () => redisMulti, zRange: zRangeMock } ) );
10
10
  vi.mock( './redis_client.js', () => ( { getRedisClient: getRedisClientMock } ) );
11
11
 
12
12
  const uploadMock = vi.fn();
@@ -32,18 +32,15 @@ describe( 'tracing/processors/s3', () => {
32
32
  const startTime = Date.parse( '2020-01-02T03:04:05.678Z' );
33
33
  const ctx = { executionContext: { workflowId: 'id1', workflowName: 'WF', startTime } };
34
34
 
35
- // Redis will return progressively larger sorted sets
36
- redisMulti.exec
37
- .mockResolvedValueOnce( [ , , [ JSON.stringify( { name: 'A', phase: 'start', timestamp: startTime } ) ] ] )
38
- .mockResolvedValueOnce( [ , , [
39
- JSON.stringify( { name: 'A', phase: 'start', timestamp: startTime } ),
40
- JSON.stringify( { name: 'A', phase: 'tick', timestamp: startTime + 1 } )
41
- ] ] )
42
- .mockResolvedValueOnce( [ , , [
43
- JSON.stringify( { name: 'A', phase: 'start', timestamp: startTime } ),
44
- JSON.stringify( { name: 'A', phase: 'tick', timestamp: startTime + 1 } ),
45
- JSON.stringify( { name: 'A', phase: 'end', timestamp: startTime + 2 } )
46
- ] ] );
35
+ // multi().exec() just needs to resolve for addEntry calls
36
+ redisMulti.exec.mockResolvedValue( [] );
37
+
38
+ // zRange is only called once at the end to get all entries
39
+ zRangeMock.mockResolvedValue( [
40
+ JSON.stringify( { name: 'A', phase: 'start', timestamp: startTime } ),
41
+ JSON.stringify( { name: 'A', phase: 'tick', timestamp: startTime + 1 } ),
42
+ JSON.stringify( { name: 'A', phase: 'end', timestamp: startTime + 2 } )
43
+ ] );
47
44
 
48
45
  await exec( { ...ctx, entry: { name: 'A', phase: 'start', timestamp: startTime, parentId: 'root' } } );
49
46
  await exec( { ...ctx, entry: { name: 'A', phase: 'tick', timestamp: startTime + 1, parentId: 'root' } } );
@@ -52,7 +49,10 @@ describe( 'tracing/processors/s3', () => {
52
49
 
53
50
  // Accumulation happened 3 times
54
51
  expect( redisMulti.zAdd ).toHaveBeenCalledTimes( 3 );
55
- expect( buildTraceTreeMock ).toHaveBeenCalledTimes( 3 );
52
+
53
+ // Tree is only built once at the end (not on every event)
54
+ expect( buildTraceTreeMock ).toHaveBeenCalledTimes( 1 );
55
+ expect( zRangeMock ).toHaveBeenCalledTimes( 1 );
56
56
 
57
57
  // Only last call triggers upload
58
58
  expect( uploadMock ).toHaveBeenCalledTimes( 1 );