@outputai/core 0.8.1 → 0.8.2-dev.e78f6b4.0

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 (100) hide show
  1. package/package.json +9 -10
  2. package/src/bus.js +1 -1
  3. package/src/consts.js +5 -2
  4. package/src/helpers/component.js +12 -0
  5. package/src/helpers/component.spec.js +54 -0
  6. package/src/helpers/fetch.js +105 -0
  7. package/src/helpers/fetch.spec.js +203 -0
  8. package/src/helpers/function.js +15 -0
  9. package/src/helpers/function.spec.js +48 -0
  10. package/src/helpers/object.js +98 -0
  11. package/src/helpers/object.spec.js +377 -0
  12. package/src/helpers/promise.js +29 -0
  13. package/src/helpers/promise.spec.js +35 -0
  14. package/src/helpers/string.js +30 -0
  15. package/src/helpers/string.spec.js +64 -0
  16. package/src/interface/evaluator.js +14 -12
  17. package/src/interface/evaluator.spec.js +10 -6
  18. package/src/interface/index.d.ts +7 -6
  19. package/src/interface/index.js +2 -0
  20. package/src/interface/logger.d.ts +53 -0
  21. package/src/interface/logger.js +68 -0
  22. package/src/interface/logger.spec.js +138 -0
  23. package/src/interface/step.js +15 -12
  24. package/src/interface/step.spec.js +10 -6
  25. package/src/interface/webhook.d.ts +21 -2
  26. package/src/interface/workflow.js +85 -78
  27. package/src/interface/workflow.spec.js +11 -4
  28. package/src/internal_activities/index.js +38 -35
  29. package/src/internal_activities/index.spec.js +27 -4
  30. package/src/logger/development.js +2 -2
  31. package/src/logger/development.spec.js +19 -2
  32. package/src/logger/production.js +1 -1
  33. package/src/logger/production.spec.js +24 -5
  34. package/src/sdk/README.md +47 -0
  35. package/src/sdk/helpers/component_metadata.d.ts +17 -0
  36. package/src/sdk/helpers/component_metadata.js +6 -0
  37. package/src/sdk/helpers/component_metadata.spec.js +30 -0
  38. package/src/sdk/helpers/index.d.ts +12 -0
  39. package/src/sdk/helpers/index.js +3 -0
  40. package/src/sdk/helpers/objects.d.ts +51 -0
  41. package/src/sdk/helpers/objects.js +8 -0
  42. package/src/sdk/helpers/objects.spec.js +16 -0
  43. package/src/sdk/helpers/path.d.ts +11 -0
  44. package/src/sdk/helpers/path.js +32 -0
  45. package/src/{utils/resolve_invocation_dir.spec.js → sdk/helpers/path.spec.js} +9 -9
  46. package/src/sdk/runtime/context.d.ts +30 -0
  47. package/src/sdk/runtime/context.js +15 -0
  48. package/src/{activity_integration → sdk/runtime}/context.spec.js +5 -5
  49. package/src/sdk/runtime/events.d.ts +15 -0
  50. package/src/sdk/runtime/events.js +18 -0
  51. package/src/{activity_integration → sdk/runtime}/events.spec.js +8 -9
  52. package/src/sdk/runtime/index.d.ts +12 -0
  53. package/src/sdk/runtime/index.js +3 -0
  54. package/src/sdk/runtime/tracing.d.ts +46 -0
  55. package/src/sdk/runtime/tracing.js +11 -0
  56. package/src/tracing/processors/s3/redis_client.spec.js +0 -6
  57. package/src/tracing/processors/s3/s3_client.spec.js +0 -6
  58. package/src/tracing/trace_engine.js +1 -1
  59. package/src/worker/catalog_workflow/catalog_job.js +1 -1
  60. package/src/worker/catalog_workflow/index.spec.js +8 -11
  61. package/src/worker/configs.js +1 -1
  62. package/src/worker/connection_monitor.js +1 -1
  63. package/src/worker/global_functions.js +14 -0
  64. package/src/worker/global_functions.spec.js +55 -0
  65. package/src/worker/index.js +4 -1
  66. package/src/worker/index.spec.js +7 -0
  67. package/src/worker/interceptors/activity.js +2 -2
  68. package/src/worker/interceptors/workflow.js +3 -3
  69. package/src/worker/interceptors/workflow.spec.js +1 -1
  70. package/src/worker/loader/matchers.js +1 -1
  71. package/src/worker/loader/tools.js +1 -1
  72. package/src/worker/loader/tools.spec.js +1 -1
  73. package/src/worker/log_hooks.js +14 -0
  74. package/src/worker/log_hooks.spec.js +83 -2
  75. package/src/worker/sinks.js +7 -1
  76. package/src/worker/sinks.spec.js +203 -0
  77. package/src/activity_integration/context.d.ts +0 -23
  78. package/src/activity_integration/context.js +0 -18
  79. package/src/activity_integration/event_id_integration.spec.js +0 -52
  80. package/src/activity_integration/events.d.ts +0 -10
  81. package/src/activity_integration/events.js +0 -15
  82. package/src/activity_integration/index.d.ts +0 -9
  83. package/src/activity_integration/index.js +0 -3
  84. package/src/activity_integration/tracing.d.ts +0 -40
  85. package/src/activity_integration/tracing.js +0 -48
  86. package/src/utils/index.d.ts +0 -180
  87. package/src/utils/index.js +0 -2
  88. package/src/utils/resolve_invocation_dir.js +0 -34
  89. package/src/utils/utils.js +0 -334
  90. package/src/utils/utils.spec.js +0 -723
  91. /package/src/{internal_utils → helpers}/aggregations.js +0 -0
  92. /package/src/{internal_utils → helpers}/aggregations.spec.js +0 -0
  93. /package/src/{internal_utils → helpers}/errors.js +0 -0
  94. /package/src/{internal_utils → helpers}/errors.spec.js +0 -0
  95. /package/src/{internal_utils → helpers}/temporal_context.js +0 -0
  96. /package/src/{internal_utils → helpers}/temporal_context.spec.ts +0 -0
  97. /package/src/{internal_utils → helpers}/trace_info.js +0 -0
  98. /package/src/{internal_utils → helpers}/trace_info.spec.js +0 -0
  99. /package/src/{internal_utils → helpers}/workflow_context.js +0 -0
  100. /package/src/{internal_utils → helpers}/workflow_context.spec.js +0 -0
@@ -0,0 +1,377 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { assignImmutableProperty, clone, deepMerge, deepMergeWithResolver, isPlainObject, shuffleArray } from './object.js';
3
+
4
+ describe( 'clone', () => {
5
+ it( 'produces a deep copy without shared references', () => {
6
+ const original = { a: 1, nested: { b: 2 } };
7
+ const copied = clone( original );
8
+
9
+ copied.nested.b = 3;
10
+
11
+ expect( original.nested.b ).toBe( 2 );
12
+ expect( copied.nested.b ).toBe( 3 );
13
+ expect( copied ).not.toBe( original );
14
+ } );
15
+
16
+ it( 'deep copies JSON-compatible arrays and objects', () => {
17
+ const original = {
18
+ arr: [ 1, { nested: true } ],
19
+ str: 'value',
20
+ bool: false,
21
+ nil: null
22
+ };
23
+ const copied = clone( original );
24
+
25
+ copied.arr[1].nested = false;
26
+
27
+ expect( copied ).toEqual( {
28
+ arr: [ 1, { nested: false } ],
29
+ str: 'value',
30
+ bool: false,
31
+ nil: null
32
+ } );
33
+ expect( original.arr[1].nested ).toBe( true );
34
+ expect( copied ).not.toBe( original );
35
+ expect( copied.arr ).not.toBe( original.arr );
36
+ } );
37
+
38
+ it( 'returns primitive JSON values when they can be parsed', () => {
39
+ expect( clone( null ) ).toBeNull();
40
+ expect( clone( true ) ).toBe( true );
41
+ expect( clone( false ) ).toBe( false );
42
+ expect( clone( 123 ) ).toBe( 123 );
43
+ expect( clone( 'hello' ) ).toBe( 'hello' );
44
+ } );
45
+
46
+ it( 'returns original values when JSON serialization produces no parseable payload', () => {
47
+ const sym = Symbol( 'x' );
48
+ const fn = () => {};
49
+ class Foo {}
50
+
51
+ expect( clone( undefined ) ).toBeUndefined();
52
+ expect( clone( sym ) ).toBe( sym );
53
+ expect( clone( fn ) ).toBe( fn );
54
+ expect( clone( Foo ) ).toBe( Foo );
55
+ expect( clone( Date ) ).toBe( Date );
56
+ expect( clone( Object ) ).toBe( Object );
57
+ expect( clone( Number ) ).toBe( Number );
58
+ } );
59
+
60
+ it( 'returns original values when JSON serialization throws', () => {
61
+ const circular = { name: 'circular' };
62
+ circular.self = circular;
63
+ const bigint = 1n;
64
+
65
+ expect( clone( circular ) ).toBe( circular );
66
+ expect( clone( bigint ) ).toBe( bigint );
67
+ } );
68
+
69
+ it( 'keeps JSON.stringify semantics for special numeric values', () => {
70
+ expect( clone( NaN ) ).toBeNull();
71
+ expect( clone( Infinity ) ).toBeNull();
72
+ expect( clone( -Infinity ) ).toBeNull();
73
+ } );
74
+
75
+ it( 'keeps JSON.stringify semantics for non-plain object instances', () => {
76
+ const date = new Date( '2025-01-01T00:00:00.000Z' );
77
+
78
+ expect( clone( date ) ).toBe( '2025-01-01T00:00:00.000Z' );
79
+ expect( clone( /abc/ ) ).toEqual( {} );
80
+ expect( clone( new Map( [ [ 'a', 1 ] ] ) ) ).toEqual( {} );
81
+ expect( clone( new Set( [ 1, 2 ] ) ) ).toEqual( {} );
82
+ } );
83
+
84
+ it( 'drops object properties that JSON.stringify omits', () => {
85
+ const sym = Symbol( 'x' );
86
+ const original = {
87
+ kept: 'yes',
88
+ missing: undefined,
89
+ fn: () => {},
90
+ sym
91
+ };
92
+
93
+ expect( clone( original ) ).toEqual( { kept: 'yes' } );
94
+ } );
95
+ } );
96
+
97
+ describe( 'deepMerge', () => {
98
+ it( 'Overwrites properties in object "a"', () => {
99
+ const a = {
100
+ a: 1,
101
+ b: {
102
+ c: 2
103
+ }
104
+ };
105
+ const b = {
106
+ a: false,
107
+ b: {
108
+ c: true
109
+ }
110
+ };
111
+ expect( deepMerge( a, b ) ).toEqual( {
112
+ a: false,
113
+ b: {
114
+ c: true
115
+ }
116
+ } );
117
+ } );
118
+
119
+ it( 'Adds properties existing in "b" but absent in "a"', () => {
120
+ const a = {
121
+ a: 1
122
+ };
123
+ const b = {
124
+ a: false,
125
+ b: true
126
+ };
127
+ expect( deepMerge( a, b ) ).toEqual( {
128
+ a: false,
129
+ b: true
130
+ } );
131
+ } );
132
+
133
+ it( 'Keep extra properties in "a"', () => {
134
+ const a = {
135
+ a: 1
136
+ };
137
+ const b = {
138
+ b: true
139
+ };
140
+ expect( deepMerge( a, b ) ).toEqual( {
141
+ a: 1,
142
+ b: true
143
+ } );
144
+ } );
145
+
146
+ it( 'Merge object is a clone', () => {
147
+ const a = {
148
+ a: 1
149
+ };
150
+ const b = {
151
+ b: 1
152
+ };
153
+ const result = deepMerge( a, b );
154
+ a.a = 2;
155
+ b.b = 2;
156
+ expect( result.a ).toEqual( 1 );
157
+ } );
158
+
159
+ it( 'Returns copy of "a" if "b" is not an object', () => {
160
+ const a = {
161
+ a: 1
162
+ };
163
+ expect( deepMerge( a, null ) ).toEqual( { a: 1 } );
164
+ expect( deepMerge( a, undefined ) ).toEqual( { a: 1 } );
165
+ } );
166
+
167
+ it( 'Copy of object "a" is a clone', () => {
168
+ const a = {
169
+ a: 1
170
+ };
171
+ const result = deepMerge( a, null );
172
+ a.a = 2;
173
+ expect( result.a ).toEqual( 1 );
174
+ } );
175
+
176
+ it( 'Throws when first argument is not a plain object', () => {
177
+ expect( () => deepMerge( Function ) ).toThrow( Error );
178
+ expect( () => deepMerge( () => {} ) ).toThrow( Error );
179
+ expect( () => deepMerge( 'a' ) ).toThrow( Error );
180
+ expect( () => deepMerge( true ) ).toThrow( Error );
181
+ expect( () => deepMerge( /a/ ) ).toThrow( Error );
182
+ expect( () => deepMerge( [] ) ).toThrow( Error );
183
+ expect( () => deepMerge( class Foo {}, class Foo {} ) ).toThrow( Error );
184
+ expect( () => deepMerge( Number.constructor, Number.constructor ) ).toThrow( Error );
185
+ expect( () => deepMerge( Number.constructor.prototype, Number.constructor.prototype ) ).toThrow( Error );
186
+ } );
187
+ } );
188
+
189
+ describe( 'deepMergeWithResolver', () => {
190
+ it( 'uses resolver for existing leaf values, including nested leaves', () => {
191
+ const a = {
192
+ cost: { total: 1 },
193
+ tokens: { total: 2, input: 3 }
194
+ };
195
+ const b = {
196
+ cost: { total: 4 },
197
+ tokens: { total: 5, input: 6, output: 7 }
198
+ };
199
+
200
+ expect( deepMergeWithResolver( a, b, ( x, y ) => x + y ) ).toEqual( {
201
+ cost: { total: 5 },
202
+ tokens: { total: 7, input: 9, output: 7 }
203
+ } );
204
+ } );
205
+
206
+ it( 'copies values from "b" when they do not exist in "a"', () => {
207
+ const resolver = vi.fn( ( x, y ) => x + y );
208
+
209
+ expect( deepMergeWithResolver( { a: 1 }, { b: 2, nested: { c: 3 } }, resolver ) ).toEqual( {
210
+ a: 1,
211
+ b: 2,
212
+ nested: { c: 3 }
213
+ } );
214
+ expect( resolver ).not.toHaveBeenCalled();
215
+ } );
216
+
217
+ it( 'keeps extra values from "a" when absent from "b"', () => {
218
+ expect( deepMergeWithResolver( { a: 1, nested: { kept: 2 } }, { b: 3 }, ( x, y ) => x + y ) ).toEqual( {
219
+ a: 1,
220
+ nested: { kept: 2 },
221
+ b: 3
222
+ } );
223
+ } );
224
+
225
+ it( 'returns a clone of "a" when "b" is not an object', () => {
226
+ const a = { nested: { value: 1 } };
227
+ const result = deepMergeWithResolver( a, null, ( x, y ) => x + y );
228
+
229
+ a.nested.value = 2;
230
+ expect( result ).toEqual( { nested: { value: 1 } } );
231
+ } );
232
+
233
+ it( 'throws when first argument is not a plain object', () => {
234
+ expect( () => deepMergeWithResolver( null, {}, ( x, y ) => x + y ) ).toThrow( Error );
235
+ expect( () => deepMergeWithResolver( [], {}, ( x, y ) => x + y ) ).toThrow( Error );
236
+ expect( () => deepMergeWithResolver( 'a', {}, ( x, y ) => x + y ) ).toThrow( Error );
237
+ } );
238
+ } );
239
+
240
+ describe( 'isPlainObject', () => {
241
+ it( 'Detects plain objects', () => {
242
+ expect( isPlainObject( {} ) ).toBe( true );
243
+ expect( isPlainObject( { a: 1 } ) ).toBe( true );
244
+ expect( isPlainObject( new Object() ) ).toBe( true );
245
+ expect( isPlainObject( new Object( { foo: 'bar' } ) ) ).toBe( true );
246
+ expect( isPlainObject( Object.create( {}.constructor.prototype ) ) ).toBe( true );
247
+ expect( isPlainObject( Object.create( Object.prototype ) ) ).toBe( true );
248
+ } );
249
+
250
+ it( 'Detects plain objects with different prototypes than Object.prototype', () => {
251
+ // Object with null prototype
252
+ expect( isPlainObject( Object.create( null ) ) ).toBe( true );
253
+ } );
254
+
255
+ it( 'Detects non plain objects that had their __proto__ mutated to Object.prototype or null', () => {
256
+ class Foo {}
257
+ const x = new Foo();
258
+ x.__proto__ = Object.prototype;
259
+ expect( isPlainObject( x ) ).toBe( true );
260
+
261
+ const y = new Foo();
262
+ y.__proto__ = null;
263
+ expect( isPlainObject( y ) ).toBe( true );
264
+ } );
265
+
266
+ it( 'Returns false for object which the prototype is not Object.prototype or null', () => {
267
+ // Object which the prototype is a plain {}
268
+ expect( isPlainObject( Object.create( {} ) ) ).toBe( false );
269
+ // Object which prototype is a another object with null prototype
270
+ expect( isPlainObject( Object.create( Object.create( null ) ) ) ).toBe( false );
271
+ } );
272
+
273
+ it( 'Returns false for functions', () => {
274
+ expect( isPlainObject( Function ) ).toBe( false );
275
+ expect( isPlainObject( () => {} ) ).toBe( false );
276
+ expect( isPlainObject( class Foo {} ) ).toBe( false );
277
+ expect( isPlainObject( Number.constructor ) ).toBe( false );
278
+ expect( isPlainObject( Number.constructor.prototype ) ).toBe( false );
279
+ } );
280
+
281
+ it( 'Returns false for arrays', () => {
282
+ expect( isPlainObject( [ 1, 2, 3 ] ) ).toBe( false );
283
+ expect( isPlainObject( [] ) ).toBe( false );
284
+ expect( isPlainObject( Array( 3 ) ) ).toBe( false );
285
+ } );
286
+
287
+ it( 'Returns false for primitives', () => {
288
+ expect( isPlainObject( null ) ).toBe( false );
289
+ expect( isPlainObject( undefined ) ).toBe( false );
290
+ expect( isPlainObject( false ) ).toBe( false );
291
+ expect( isPlainObject( true ) ).toBe( false );
292
+ expect( isPlainObject( 1 ) ).toBe( false );
293
+ expect( isPlainObject( 0 ) ).toBe( false );
294
+ expect( isPlainObject( '' ) ).toBe( false );
295
+ expect( isPlainObject( 'foo' ) ).toBe( false );
296
+ expect( isPlainObject( Symbol( 'foo' ) ) ).toBe( false );
297
+ expect( isPlainObject( Symbol.for( 'foo' ) ) ).toBe( false );
298
+ } );
299
+
300
+ it( 'Returns true for built in objects', () => {
301
+ expect( isPlainObject( Math ) ).toBe( true );
302
+ expect( isPlainObject( JSON ) ).toBe( true );
303
+ } );
304
+
305
+ it( 'Returns false for built in types', () => {
306
+ expect( isPlainObject( String ) ).toBe( false );
307
+ expect( isPlainObject( Number ) ).toBe( false );
308
+ expect( isPlainObject( Date ) ).toBe( false );
309
+ } );
310
+
311
+ it( 'Returns false for other instance where prototype is not object or null', () => {
312
+ expect( isPlainObject( /foo/ ) ).toBe( false );
313
+ expect( isPlainObject( new RegExp( 'foo' ) ) ).toBe( false );
314
+ expect( isPlainObject( new Date() ) ).toBe( false );
315
+ class Foo {}
316
+ expect( isPlainObject( new Foo() ) ).toBe( false );
317
+ expect( isPlainObject( Object.create( ( class Foo {} ).prototype ) ) ).toBe( false );
318
+ } );
319
+
320
+ it( 'Returns false if tries to change the prototype to simulate an object', () => {
321
+ function Bar() {}
322
+ Bar.prototype = Object.create( null );
323
+ expect( isPlainObject( new Bar() ) ).toBe( false );
324
+ } );
325
+
326
+ it( 'Returns false if object proto was mutated to anything else than object or null', () => {
327
+ const zum = {};
328
+ zum.__proto__ = Number.prototype;
329
+ expect( isPlainObject( zum ) ).toBe( false );
330
+ } );
331
+ } );
332
+
333
+ describe( 'assignImmutableProperty', () => {
334
+ it( 'defines a non-writable, non-configurable, non-enumerable property', () => {
335
+ const obj = {};
336
+ const key = Symbol( 'metadata' );
337
+ const value = { name: 'test' };
338
+
339
+ expect( assignImmutableProperty( obj, key, value ) ).toBe( obj );
340
+ expect( obj[key] ).toBe( value );
341
+ expect( Object.getOwnPropertyDescriptor( obj, key ) ).toEqual( {
342
+ value,
343
+ writable: false,
344
+ configurable: false,
345
+ enumerable: false
346
+ } );
347
+ expect( Object.keys( obj ) ).toEqual( [] );
348
+ } );
349
+
350
+ it( 'prevents reassignment and redefinition', () => {
351
+ const obj = {};
352
+ const key = 'metadata';
353
+
354
+ assignImmutableProperty( obj, key, 'original' );
355
+
356
+ expect( () => {
357
+ obj[key] = 'updated';
358
+ } ).toThrow( TypeError );
359
+ expect( () => {
360
+ Object.defineProperty( obj, key, { value: 'updated' } );
361
+ } ).toThrow( TypeError );
362
+ expect( obj[key] ).toBe( 'original' );
363
+ } );
364
+ } );
365
+
366
+ describe( 'shuffleArray', () => {
367
+ it( 'returns a shuffled copy based on random sort keys', () => {
368
+ vi.spyOn( Math, 'random' )
369
+ .mockReturnValueOnce( 0.4 )
370
+ .mockReturnValueOnce( 0.1 )
371
+ .mockReturnValueOnce( 0.3 );
372
+ const arr = [ 'a', 'b', 'c' ];
373
+
374
+ expect( shuffleArray( arr ) ).toEqual( [ 'b', 'c', 'a' ] );
375
+ expect( arr ).toEqual( [ 'a', 'b', 'c' ] );
376
+ } );
377
+ } );
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Builds a promise that can be resolved from the outside.
3
+ */
4
+ export class CancellablePromise {
5
+ #promise = null;
6
+ #complete = null;
7
+ #completed = false;
8
+
9
+ constructor() {
10
+ this.#promise = new Promise( resolve => {
11
+ this.#complete = () => {
12
+ resolve();
13
+ this.#completed = true;
14
+ };
15
+ } );
16
+ }
17
+ /** Retrieves the promise */
18
+ get promise() {
19
+ return this.#promise;
20
+ }
21
+ /** Returns whether the promise is resolved or not */
22
+ get completed() {
23
+ return this.#completed;
24
+ }
25
+ /** Resolves the promise */
26
+ complete() {
27
+ this.#complete();
28
+ }
29
+ };
@@ -0,0 +1,35 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { CancellablePromise } from './promise.js';
3
+
4
+ describe( 'CancellablePromise', () => {
5
+ it( 'exposes a pending promise until it is completed', async () => {
6
+ const cancellable = new CancellablePromise();
7
+ const onComplete = vi.fn();
8
+
9
+ cancellable.promise.then( onComplete );
10
+ await Promise.resolve();
11
+
12
+ expect( cancellable.completed ).toBe( false );
13
+ expect( onComplete ).not.toHaveBeenCalled();
14
+
15
+ cancellable.complete();
16
+ await cancellable.promise;
17
+
18
+ expect( cancellable.completed ).toBe( true );
19
+ expect( onComplete ).toHaveBeenCalledOnce();
20
+ } );
21
+
22
+ it( 'can be completed multiple times without resolving again', async () => {
23
+ const cancellable = new CancellablePromise();
24
+ const onComplete = vi.fn();
25
+
26
+ cancellable.promise.then( onComplete );
27
+ cancellable.complete();
28
+ cancellable.complete();
29
+ await cancellable.promise;
30
+ await Promise.resolve();
31
+
32
+ expect( cancellable.completed ).toBe( true );
33
+ expect( onComplete ).toHaveBeenCalledOnce();
34
+ } );
35
+ } );
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Returns true if string value is stringbool and true
3
+ * @param {string} v
4
+ * @returns
5
+ */
6
+ export const isStringboolTrue = v => [ '1', 'true', 'on' ].includes( v );
7
+
8
+ /**
9
+ * Shortens a UUID by re-encoding it to base62.
10
+ *
11
+ * This is a Temporal friendly, without crypto or Buffer.
12
+ * @param {string} uuid
13
+ * @returns {string}
14
+ */
15
+ export const toUrlSafeBase64 = uuid => {
16
+ const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-';
17
+ const alphabetLen = alphabet.length;
18
+ const base = BigInt( alphabetLen );
19
+ const hex = uuid.replace( /-/g, '' );
20
+
21
+ const toDigits = n => n <= 0n ? [] : toDigits( n / base ).concat( alphabet[Number( n % base )] );
22
+ return toDigits( BigInt( '0x' + hex ) ).join( '' );
23
+ };
24
+
25
+ /**
26
+ * Escape regexp characters in a string
27
+ * @param {*} value
28
+ * @returns
29
+ */
30
+ export const rxEscape = v => v.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
@@ -0,0 +1,64 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { toUrlSafeBase64, rxEscape } from './string.js';
3
+
4
+ describe( 'toUrlSafeBase64', () => {
5
+ const urlSafeAlphabet = /^[A-Za-z0-9_-]+$/;
6
+
7
+ it( 'returns a string for a valid UUID', () => {
8
+ const uuid = '550e8400-e29b-41d4-a716-446655440000';
9
+ expect( typeof toUrlSafeBase64( uuid ) ).toBe( 'string' );
10
+ expect( toUrlSafeBase64( uuid ).length ).toBeGreaterThan( 0 );
11
+ } );
12
+
13
+ it( 'output length is 21 or 22 for a standard UUID', () => {
14
+ const uuid = '550e8400-e29b-41d4-a716-446655440000';
15
+ const out = toUrlSafeBase64( uuid );
16
+ expect( out.length ).toBeGreaterThanOrEqual( 21 );
17
+ expect( out.length ).toBeLessThanOrEqual( 22 );
18
+ } );
19
+
20
+ it( 'output contains only url-safe alphabet characters', () => {
21
+ const uuid = '550e8400-e29b-41d4-a716-446655440000';
22
+ const out = toUrlSafeBase64( uuid );
23
+ expect( out ).toMatch( urlSafeAlphabet );
24
+ } );
25
+
26
+ it( 'is deterministic for the same UUID', () => {
27
+ const uuid = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
28
+ expect( toUrlSafeBase64( uuid ) ).toBe( toUrlSafeBase64( uuid ) );
29
+ } );
30
+
31
+ it( 'different UUIDs produce different strings', () => {
32
+ const a = toUrlSafeBase64( '550e8400-e29b-41d4-a716-446655440000' );
33
+ const b = toUrlSafeBase64( '6ba7b810-9dad-11d1-80b4-00c04fd430c8' );
34
+ expect( a ).not.toBe( b );
35
+ } );
36
+
37
+ it( 'strips hyphens and encodes hex (same as 32-char hex)', () => {
38
+ const withHyphens = '550e8400-e29b-41d4-a716-446655440000';
39
+ const hexOnly = '550e8400e29b41d4a716446655440000';
40
+ expect( toUrlSafeBase64( withHyphens ) ).toBe( toUrlSafeBase64( hexOnly ) );
41
+ } );
42
+ } );
43
+
44
+ describe( 'rxEscape', () => {
45
+ it( 'escapes all regexp metacharacters', () => {
46
+ expect( rxEscape( '.*+?^${}()|[]\\' ) ).toBe( '\\.\\*\\+\\?\\^\\$\\{\\}\\(\\)\\|\\[\\]\\\\' );
47
+ } );
48
+
49
+ it( 'keeps file URL paths matchable as literal regexp input', () => {
50
+ const path = 'file://foo/bar';
51
+ const rx = new RegExp( `^${rxEscape( path )}$` );
52
+
53
+ expect( rx.test( path ) ).toBe( true );
54
+ expect( rx.test( 'file://foo/bar/baz' ) ).toBe( false );
55
+ } );
56
+
57
+ it( 'keeps Windows paths matchable as literal regexp input', () => {
58
+ const path = String.raw`C:\foo\bar`;
59
+ const rx = new RegExp( `^${rxEscape( path )}$` );
60
+
61
+ expect( rx.test( path ) ).toBe( true );
62
+ expect( rx.test( String.raw`C:\foo\bar\baz` ) ).toBe( false );
63
+ } );
64
+ } );
@@ -1,6 +1,5 @@
1
1
  import { EvaluatorValidator } from './validations/index.js';
2
- import { setMetadata } from '#utils';
3
- import { ComponentType } from '#consts';
2
+ import { createEvaluator } from '#helpers/component';
4
3
 
5
4
  /**
6
5
  * Create a new evaluator (activity flavor) and return a wrapper function around its fn handler
@@ -9,13 +8,16 @@ export function evaluator( { name, description, inputSchema, fn, options } ) {
9
8
  EvaluatorValidator.validateDefinition( { name, description, inputSchema, fn, options } );
10
9
  const validator = new EvaluatorValidator( { name, inputSchema } );
11
10
 
12
- const wrapper = async input => {
13
- validator.validateInput( input );
14
- const output = await fn( input );
15
- validator.validateOutput( output );
16
- return output;
17
- };
18
-
19
- setMetadata( wrapper, { name, description, inputSchema, type: ComponentType.EVALUATOR, options } );
20
- return wrapper;
21
- };
11
+ return createEvaluator( {
12
+ name,
13
+ description,
14
+ inputSchema,
15
+ options,
16
+ handler: async input => {
17
+ validator.validateInput( input );
18
+ const output = await fn( input );
19
+ validator.validateOutput( output );
20
+ return output;
21
+ }
22
+ } );
23
+ }
@@ -7,12 +7,12 @@ import {
7
7
  EvaluationFeedback
8
8
  } from './evaluation_result.js';
9
9
  import { ValidationError } from '#errors';
10
- import { ComponentType } from '#consts';
11
10
 
12
11
  const validateDefinitionMock = vi.hoisted( () => vi.fn() );
13
12
  const validateInputMock = vi.hoisted( () => vi.fn() );
14
13
  const validateOutputMock = vi.hoisted( () => vi.fn() );
15
14
  const validatorConstructorMock = vi.hoisted( () => vi.fn() );
15
+ const createEvaluatorMock = vi.hoisted( () => vi.fn( ( { handler } ) => handler ) );
16
16
 
17
17
  vi.mock( './validations/index.js', () => {
18
18
  class EvaluatorValidator {
@@ -30,12 +30,16 @@ vi.mock( './validations/index.js', () => {
30
30
  return { EvaluatorValidator };
31
31
  } );
32
32
 
33
+ vi.mock( '#helpers/component', () => ( {
34
+ createEvaluator: createEvaluatorMock
35
+ } ) );
36
+
33
37
  describe( 'evaluator()', () => {
34
38
  beforeEach( () => {
35
39
  vi.clearAllMocks();
36
40
  } );
37
41
 
38
- it( 'validates the definition, creates a runtime validator, and attaches metadata', async () => {
42
+ it( 'validates the definition, creates a runtime validator, and creates an evaluator component', async () => {
39
43
  const { evaluator } = await import( './evaluator.js' );
40
44
  const inputSchema = { safeParse: vi.fn() };
41
45
  const fn = vi.fn().mockResolvedValue( new EvaluationResult( { value: 'ok', confidence: 1 } ) );
@@ -61,14 +65,14 @@ describe( 'evaluator()', () => {
61
65
  inputSchema
62
66
  } );
63
67
 
64
- const [ metadataSymbol ] = Object.getOwnPropertySymbols( wrapper );
65
- expect( wrapper[metadataSymbol] ).toEqual( {
68
+ expect( createEvaluatorMock ).toHaveBeenCalledWith( {
66
69
  name: 'test_evaluator',
67
70
  description: 'Test evaluator',
68
71
  inputSchema,
69
- type: ComponentType.EVALUATOR,
70
- options
72
+ options,
73
+ handler: expect.any( Function )
71
74
  } );
75
+ expect( wrapper ).toBe( createEvaluatorMock.mock.calls[0][0].handler );
72
76
  } );
73
77
 
74
78
  it( 'validates input and output around the evaluator function', async () => {
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Exports all public methods from the interface
3
3
  */
4
- export * from './webhook.d.ts';
5
- export * from './workflow_utils.d.ts';
6
- export * from './step.d.ts';
7
- export * from './evaluator.d.ts';
8
- export * from './workflow.d.ts';
9
- export * from './evaluation_result.d.ts';
4
+ export * from './webhook.js';
5
+ export * from './workflow_utils.js';
6
+ export * from './step.js';
7
+ export * from './evaluator.js';
8
+ export * from './workflow.js';
9
+ export * from './evaluation_result.js';
10
+ export * as Logger from './logger.js';
@@ -10,11 +10,13 @@ import { step } from './step.js';
10
10
  import { workflow } from './workflow.js';
11
11
  import { executeInParallel } from './workflow_utils.js';
12
12
  import { sendHttpRequest, sendPostRequestAndAwaitWebhook } from './webhook.js';
13
+ import * as Logger from './logger.js';
13
14
 
14
15
  export {
15
16
  evaluator,
16
17
  step,
17
18
  workflow,
19
+ Logger,
18
20
  EvaluationNumberResult,
19
21
  EvaluationStringResult,
20
22
  EvaluationBooleanResult,