@output.ai/core 0.5.0 → 0.5.2

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.
@@ -0,0 +1,495 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { z } from 'zod';
3
+
4
+ const inWorkflowContextMock = vi.hoisted( () => vi.fn( () => true ) );
5
+ const traceDestinationsStepMock = vi.fn().mockResolvedValue( { local: '/tmp/trace' } );
6
+ const executeChildMock = vi.fn().mockResolvedValue( undefined );
7
+ const continueAsNewMock = vi.fn().mockResolvedValue( undefined );
8
+
9
+ const createStepsProxy = ( stepSpy = vi.fn() ) =>
10
+ new Proxy( {}, {
11
+ get: ( _, prop ) => {
12
+ if ( prop === '__internal#getTraceDestinations' ) {
13
+ return traceDestinationsStepMock;
14
+ }
15
+ if ( typeof prop === 'string' && ( prop.includes( '#' ) ) ) {
16
+ return stepSpy;
17
+ }
18
+ return vi.fn();
19
+ }
20
+ } );
21
+
22
+ const stepSpyRef = { current: vi.fn().mockResolvedValue( {} ) };
23
+ const proxyActivitiesMock = vi.fn( () => {
24
+ stepSpyRef.current = vi.fn().mockResolvedValue( {} );
25
+ return createStepsProxy( stepSpyRef.current );
26
+ } );
27
+
28
+ const workflowInfoReturn = {
29
+ workflowId: 'wf-test-123',
30
+ workflowType: 'test_wf',
31
+ memo: {},
32
+ startTime: new Date( '2025-01-01T00:00:00Z' ),
33
+ continueAsNewSuggested: false
34
+ };
35
+ const workflowInfoMock = vi.fn( () => ( { ...workflowInfoReturn } ) );
36
+
37
+ vi.mock( '@temporalio/workflow', () => ( {
38
+ proxyActivities: ( ...args ) => proxyActivitiesMock( ...args ),
39
+ inWorkflowContext: inWorkflowContextMock,
40
+ executeChild: ( ...args ) => executeChildMock( ...args ),
41
+ workflowInfo: workflowInfoMock,
42
+ uuid4: () => '550e8400e29b41d4a716446655440000',
43
+ ParentClosePolicy: { TERMINATE: 'TERMINATE', ABANDON: 'ABANDON' },
44
+ continueAsNew: continueAsNewMock
45
+ } ) );
46
+
47
+ vi.mock( '#consts', async importOriginal => {
48
+ const actual = await importOriginal();
49
+ return {
50
+ ...actual,
51
+ SHARED_STEP_PREFIX: '__shared',
52
+ ACTIVITY_GET_TRACE_DESTINATIONS: '__internal#getTraceDestinations'
53
+ };
54
+ } );
55
+
56
+ describe( 'workflow()', () => {
57
+ beforeEach( () => {
58
+ vi.clearAllMocks();
59
+ inWorkflowContextMock.mockReturnValue( true );
60
+ workflowInfoMock.mockReturnValue( { ...workflowInfoReturn } );
61
+ workflowInfoReturn.memo = {};
62
+ proxyActivitiesMock.mockImplementation( () => {
63
+ stepSpyRef.current = vi.fn().mockResolvedValue( {} );
64
+ return createStepsProxy( stepSpyRef.current );
65
+ } );
66
+ } );
67
+
68
+ describe( 'options and defaults', () => {
69
+ it( 'does not throw when options is omitted (disableTrace defaults to false)', async () => {
70
+ const { workflow } = await import( './workflow.js' );
71
+
72
+ const wf = workflow( {
73
+ name: 'no_options_wf',
74
+ description: 'Workflow without options',
75
+ inputSchema: z.object( { value: z.string() } ),
76
+ outputSchema: z.object( { value: z.string() } ),
77
+ fn: async ( { value } ) => ( { value } )
78
+ } );
79
+
80
+ const result = await wf( { value: 'hello' } );
81
+ expect( result.output ).toEqual( { value: 'hello' } );
82
+ } );
83
+
84
+ it( 'respects disableTrace: true when options is provided', async () => {
85
+ const { workflow } = await import( './workflow.js' );
86
+
87
+ const wf = workflow( {
88
+ name: 'trace_disabled_wf',
89
+ description: 'Workflow with tracing disabled',
90
+ inputSchema: z.object( { value: z.string() } ),
91
+ outputSchema: z.object( { value: z.string() } ),
92
+ options: { disableTrace: true },
93
+ fn: async ( { value } ) => ( { value } )
94
+ } );
95
+
96
+ const result = await wf( { value: 'hello' } );
97
+ expect( result.output ).toEqual( { value: 'hello' } );
98
+ } );
99
+
100
+ it( 'merges custom activityOptions with defaults via deepMerge', async () => {
101
+ const { workflow } = await import( './workflow.js' );
102
+
103
+ workflow( {
104
+ name: 'custom_activity_wf',
105
+ description: 'Workflow with custom activity options',
106
+ inputSchema: z.object( {} ),
107
+ outputSchema: z.object( {} ),
108
+ options: {
109
+ activityOptions: {
110
+ startToCloseTimeout: '5m',
111
+ retry: { maximumAttempts: 5 }
112
+ }
113
+ },
114
+ fn: async () => ( {} )
115
+ } );
116
+
117
+ expect( proxyActivitiesMock ).toHaveBeenCalledWith(
118
+ expect.objectContaining( {
119
+ startToCloseTimeout: '5m',
120
+ retry: expect.objectContaining( { maximumAttempts: 5 } )
121
+ } )
122
+ );
123
+ } );
124
+ } );
125
+
126
+ describe( 'wrapper metadata', () => {
127
+ it( 'attaches name, description, inputSchema, outputSchema to wrapper via setMetadata', async () => {
128
+ const { workflow } = await import( './workflow.js' );
129
+ const inputSchema = z.object( { x: z.number() } );
130
+ const outputSchema = z.object( { y: z.number() } );
131
+
132
+ const wf = workflow( {
133
+ name: 'meta_wf',
134
+ description: 'Meta workflow',
135
+ inputSchema,
136
+ outputSchema,
137
+ fn: async input => ( { y: input.x } )
138
+ } );
139
+
140
+ const symbols = Object.getOwnPropertySymbols( wf );
141
+ expect( symbols ).toHaveLength( 1 );
142
+ const meta = wf[symbols[0]];
143
+ expect( meta ).toEqual( { name: 'meta_wf', description: 'Meta workflow', inputSchema, outputSchema } );
144
+ } );
145
+ } );
146
+
147
+ describe( 'when not in workflow context (unit-test path)', () => {
148
+ it( 'validates input, runs fn with test context, validates output, returns plain output', async () => {
149
+ inWorkflowContextMock.mockReturnValue( false );
150
+ const { workflow } = await import( './workflow.js' );
151
+
152
+ const wf = workflow( {
153
+ name: 'unit_path_wf',
154
+ description: 'Unit path',
155
+ inputSchema: z.object( { a: z.string() } ),
156
+ outputSchema: z.object( { b: z.string() } ),
157
+ fn: async ( input, context ) => ( {
158
+ b: String( context.info.workflowId ) + input.a
159
+ } )
160
+ } );
161
+
162
+ const result = await wf( { a: '-ok' } );
163
+ expect( result ).toEqual( { b: 'test-workflow-ok' } );
164
+ expect( workflowInfoMock ).not.toHaveBeenCalled();
165
+ expect( traceDestinationsStepMock ).not.toHaveBeenCalled();
166
+ } );
167
+
168
+ it( 'merges extra.context into context when provided', async () => {
169
+ inWorkflowContextMock.mockReturnValue( false );
170
+ const { workflow } = await import( './workflow.js' );
171
+
172
+ const wf = workflow( {
173
+ name: 'extra_ctx_wf',
174
+ description: 'Extra context',
175
+ inputSchema: z.object( {} ),
176
+ outputSchema: z.object( { id: z.string() } ),
177
+ fn: async ( _, context ) => ( { id: context.extraId ?? 'default' } )
178
+ } );
179
+
180
+ const result = await wf( {}, { context: { extraId: 'injected' } } );
181
+ expect( result ).toEqual( { id: 'injected' } );
182
+ } );
183
+ } );
184
+
185
+ describe( 'input and output validation', () => {
186
+ it( 'throws ValidationError when input does not match inputSchema', async () => {
187
+ const { workflow } = await import( './workflow.js' );
188
+ const { ValidationError } = await import( '#errors' );
189
+
190
+ const wf = workflow( {
191
+ name: 'validate_in_wf',
192
+ description: 'Input validation',
193
+ inputSchema: z.object( { required: z.string() } ),
194
+ outputSchema: z.object( {} ),
195
+ fn: async () => ( {} )
196
+ } );
197
+
198
+ await expect( wf( { wrong: 1 } ) ).rejects.toThrow( ValidationError );
199
+ await expect( wf( { wrong: 1 } ) ).rejects.toThrow( /Workflow validate_in_wf input/ );
200
+ } );
201
+
202
+ it( 'throws ValidationError when output does not match outputSchema', async () => {
203
+ const { workflow } = await import( './workflow.js' );
204
+ const { ValidationError } = await import( '#errors' );
205
+
206
+ const wf = workflow( {
207
+ name: 'validate_out_wf',
208
+ description: 'Output validation',
209
+ inputSchema: z.object( {} ),
210
+ outputSchema: z.object( { required: z.string() } ),
211
+ fn: async () => ( { other: 1 } )
212
+ } );
213
+
214
+ await expect( wf( {} ) ).rejects.toThrow( ValidationError );
215
+ await expect( wf( {} ) ).rejects.toThrow( /Workflow validate_out_wf output/ );
216
+ } );
217
+ } );
218
+
219
+ describe( 'root workflow (in workflow context)', () => {
220
+ it( 'calls getTraceDestinations, returns { output, trace } and assigns executionContext to memo', async () => {
221
+ const { workflow } = await import( './workflow.js' );
222
+
223
+ const wf = workflow( {
224
+ name: 'root_wf',
225
+ description: 'Root',
226
+ inputSchema: z.object( {} ),
227
+ outputSchema: z.object( { v: z.number() } ),
228
+ fn: async () => ( { v: 42 } )
229
+ } );
230
+
231
+ const result = await wf( {} );
232
+ expect( traceDestinationsStepMock ).toHaveBeenCalledTimes( 1 );
233
+ expect( result ).toEqual( {
234
+ output: { v: 42 },
235
+ trace: { destinations: { local: '/tmp/trace' } }
236
+ } );
237
+ const memo = workflowInfoMock().memo;
238
+ expect( memo.executionContext ).toEqual( {
239
+ workflowId: 'wf-test-123',
240
+ workflowName: 'root_wf',
241
+ disableTrace: false,
242
+ startTime: new Date( '2025-01-01T00:00:00Z' ).getTime()
243
+ } );
244
+ } );
245
+
246
+ it( 'sets executionContext.disableTrace when options.disableTrace is true', async () => {
247
+ const { workflow } = await import( './workflow.js' );
248
+
249
+ const wf = workflow( {
250
+ name: 'root_no_trace_wf',
251
+ description: 'Root no trace',
252
+ inputSchema: z.object( {} ),
253
+ outputSchema: z.object( {} ),
254
+ options: { disableTrace: true },
255
+ fn: async () => ( {} )
256
+ } );
257
+
258
+ await wf( {} );
259
+ expect( workflowInfoMock().memo.executionContext.disableTrace ).toBe( true );
260
+ } );
261
+ } );
262
+
263
+ describe( 'child workflow (memo.executionContext already set)', () => {
264
+ it( 'does not call getTraceDestinations and returns plain output', async () => {
265
+ workflowInfoMock.mockReturnValue( {
266
+ ...workflowInfoReturn,
267
+ memo: { executionContext: { workflowId: 'parent-1', workflowName: 'parent_wf' } }
268
+ } );
269
+ const { workflow } = await import( './workflow.js' );
270
+
271
+ const wf = workflow( {
272
+ name: 'child_wf',
273
+ description: 'Child',
274
+ inputSchema: z.object( {} ),
275
+ outputSchema: z.object( { x: z.string() } ),
276
+ fn: async () => ( { x: 'child' } )
277
+ } );
278
+
279
+ const result = await wf( {} );
280
+ expect( traceDestinationsStepMock ).not.toHaveBeenCalled();
281
+ expect( result ).toEqual( { x: 'child' } );
282
+ } );
283
+ } );
284
+
285
+ describe( 'bound this: invokeStep, invokeSharedStep, invokeEvaluator', () => {
286
+ it( 'invokeStep calls steps with workflowName#stepName', async () => {
287
+ const getCalls = [];
288
+ proxyActivitiesMock.mockImplementation( () => new Proxy( {}, {
289
+ get: ( _, prop ) => {
290
+ if ( prop === '__internal#getTraceDestinations' ) {
291
+ return traceDestinationsStepMock;
292
+ }
293
+ if ( typeof prop === 'string' && prop.includes( '#' ) ) {
294
+ getCalls.push( prop );
295
+ return vi.fn().mockResolvedValue( {} );
296
+ }
297
+ return vi.fn();
298
+ }
299
+ } ) );
300
+
301
+ const { workflow } = await import( './workflow.js' );
302
+
303
+ const wf = workflow( {
304
+ name: 'invoke_wf',
305
+ description: 'Invoke',
306
+ inputSchema: z.object( {} ),
307
+ outputSchema: z.object( {} ),
308
+ async fn() {
309
+ await this.invokeStep( 'myStep', { foo: 1 } );
310
+ return {};
311
+ }
312
+ } );
313
+
314
+ await wf( {} );
315
+ expect( getCalls ).toContain( 'invoke_wf#myStep' );
316
+ } );
317
+
318
+ it( 'invokeSharedStep calls steps with SHARED_STEP_PREFIX#stepName', async () => {
319
+ const { workflow } = await import( './workflow.js' );
320
+ const sharedSpy = vi.fn().mockResolvedValue( {} );
321
+ proxyActivitiesMock.mockImplementation( () => new Proxy( {}, {
322
+ get: ( _, prop ) => {
323
+ if ( prop === '__internal#getTraceDestinations' ) {
324
+ return traceDestinationsStepMock;
325
+ }
326
+ if ( prop === '__shared#sharedStep' ) {
327
+ return sharedSpy;
328
+ }
329
+ return vi.fn();
330
+ }
331
+ } ) );
332
+
333
+ const wf = workflow( {
334
+ name: 'shared_wf',
335
+ description: 'Shared',
336
+ inputSchema: z.object( {} ),
337
+ outputSchema: z.object( {} ),
338
+ async fn() {
339
+ await this.invokeSharedStep( 'sharedStep', { data: 2 } );
340
+ return {};
341
+ }
342
+ } );
343
+
344
+ await wf( {} );
345
+ expect( sharedSpy ).toHaveBeenCalledWith( { data: 2 }, undefined );
346
+ } );
347
+
348
+ it( 'invokeEvaluator calls steps with workflowName#evaluatorName', async () => {
349
+ const evalSpy = vi.fn().mockResolvedValue( true );
350
+ proxyActivitiesMock.mockImplementation( () => new Proxy( {}, {
351
+ get: ( _, prop ) => {
352
+ if ( prop === '__internal#getTraceDestinations' ) {
353
+ return traceDestinationsStepMock;
354
+ }
355
+ if ( prop === 'eval_wf#myEvaluator' ) {
356
+ return evalSpy;
357
+ }
358
+ return vi.fn();
359
+ }
360
+ } ) );
361
+
362
+ const { workflow } = await import( './workflow.js' );
363
+
364
+ const wf = workflow( {
365
+ name: 'eval_wf',
366
+ description: 'Eval',
367
+ inputSchema: z.object( {} ),
368
+ outputSchema: z.object( {} ),
369
+ async fn() {
370
+ await this.invokeEvaluator( 'myEvaluator', { x: 3 } );
371
+ return {};
372
+ }
373
+ } );
374
+
375
+ await wf( {} );
376
+ expect( evalSpy ).toHaveBeenCalledWith( { x: 3 }, undefined );
377
+ } );
378
+ } );
379
+
380
+ describe( 'startWorkflow', () => {
381
+ it( 'calls executeChild with correct args and TERMINATE when not detached', async () => {
382
+ const { workflow } = await import( './workflow.js' );
383
+ const { ParentClosePolicy } = await import( '@temporalio/workflow' );
384
+
385
+ const wf = workflow( {
386
+ name: 'parent_wf',
387
+ description: 'Parent',
388
+ inputSchema: z.object( {} ),
389
+ outputSchema: z.object( {} ),
390
+ async fn() {
391
+ await this.startWorkflow( 'child_wf', { id: 1 } );
392
+ return {};
393
+ }
394
+ } );
395
+
396
+ await wf( {} );
397
+ expect( executeChildMock ).toHaveBeenCalledWith( 'child_wf', {
398
+ args: [ { id: 1 } ],
399
+ workflowId: expect.stringMatching( /^wf-test-123-/ ),
400
+ parentClosePolicy: ParentClosePolicy.TERMINATE,
401
+ memo: expect.objectContaining( {
402
+ executionContext: expect.any( Object ),
403
+ parentId: 'wf-test-123'
404
+ } )
405
+ } );
406
+ } );
407
+
408
+ it( 'uses ABANDON when extra.detached is true', async () => {
409
+ const { workflow } = await import( './workflow.js' );
410
+ const { ParentClosePolicy } = await import( '@temporalio/workflow' );
411
+
412
+ const wf = workflow( {
413
+ name: 'detach_wf',
414
+ description: 'Detach',
415
+ inputSchema: z.object( {} ),
416
+ outputSchema: z.object( {} ),
417
+ async fn() {
418
+ await this.startWorkflow( 'child_wf', null, { detached: true } );
419
+ return {};
420
+ }
421
+ } );
422
+
423
+ await wf( {} );
424
+ expect( executeChildMock ).toHaveBeenCalledWith( 'child_wf', expect.objectContaining( {
425
+ parentClosePolicy: ParentClosePolicy.ABANDON
426
+ } ) );
427
+ } );
428
+
429
+ it( 'passes empty args when input is null/omitted', async () => {
430
+ const { workflow } = await import( './workflow.js' );
431
+
432
+ const wf = workflow( {
433
+ name: 'no_input_wf',
434
+ description: 'No input',
435
+ inputSchema: z.object( {} ),
436
+ outputSchema: z.object( {} ),
437
+ async fn() {
438
+ await this.startWorkflow( 'child_wf' );
439
+ return {};
440
+ }
441
+ } );
442
+
443
+ await wf( {} );
444
+ expect( executeChildMock ).toHaveBeenCalledWith( 'child_wf', expect.objectContaining( {
445
+ args: []
446
+ } ) );
447
+ } );
448
+ } );
449
+
450
+ describe( 'context.control', () => {
451
+ it( 'exposes info.workflowId and control/info namespaces when not in workflow context', async () => {
452
+ inWorkflowContextMock.mockReturnValue( false );
453
+ const { workflow } = await import( './workflow.js' );
454
+
455
+ const wf = workflow( {
456
+ name: 'control_wf',
457
+ description: 'Control',
458
+ inputSchema: z.object( {} ),
459
+ outputSchema: z.object( {
460
+ workflowId: z.string(),
461
+ hasControl: z.boolean(),
462
+ hasInfo: z.boolean()
463
+ } ),
464
+ fn: async ( _, context ) => ( {
465
+ workflowId: context.info?.workflowId,
466
+ hasControl: 'control' in context,
467
+ hasInfo: 'info' in context
468
+ } )
469
+ } );
470
+
471
+ const result = await wf( {} );
472
+ expect( result.workflowId ).toBe( 'test-workflow' );
473
+ expect( result.hasControl ).toBe( true );
474
+ expect( result.hasInfo ).toBe( true );
475
+ } );
476
+ } );
477
+
478
+ describe( 'error handling (root workflow)', () => {
479
+ it( 'rethrows error from fn and rejects with same message', async () => {
480
+ const { workflow } = await import( './workflow.js' );
481
+
482
+ const wf = workflow( {
483
+ name: 'err_wf',
484
+ description: 'Error',
485
+ inputSchema: z.object( {} ),
486
+ outputSchema: z.object( {} ),
487
+ fn: async () => {
488
+ throw new Error( 'workflow failed' );
489
+ }
490
+ } );
491
+
492
+ await expect( wf( {} ) ).rejects.toThrow( 'workflow failed' );
493
+ } );
494
+ } );
495
+ } );
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Context instance builder
3
+ */
4
+ export class Context {
5
+
6
+ /**
7
+ * Builds a new context instance
8
+ * @param {object} options - Arguments to build a new context instance
9
+ * @param {string} workflowId
10
+ * @param {function} continueAsNew
11
+ * @param {function} isContinueAsNewSuggested
12
+ * @returns {object} context
13
+ */
14
+ static build( { workflowId, continueAsNew, isContinueAsNewSuggested } ) {
15
+ return {
16
+ /**
17
+ * Control namespace: This object adds functions to interact with Temporal flow mechanisms
18
+ */
19
+ control: {
20
+ continueAsNew,
21
+ isContinueAsNewSuggested
22
+ },
23
+ /**
24
+ * Info namespace: abstracts workflowInfo()
25
+ */
26
+ info: {
27
+ workflowId
28
+ }
29
+ };
30
+ }
31
+ };
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Result of a single job executed by `executeInParallel`.
3
+ *
4
+ * @typeParam T - The return type of the job function
5
+ */
6
+ export type ParallelJobResult<T> =
7
+ | { ok: true; result: T; index: number } |
8
+ { ok: false; error: unknown; index: number };
9
+
10
+ /**
11
+ * Execute jobs in parallel with optional concurrency limit.
12
+ *
13
+ * Returns all job results (successes and failures) sorted by original job index.
14
+ * Each result contains `ok` (boolean), `index` (original position), and either
15
+ * `result` (on success) or `error` (on failure).
16
+ *
17
+ * Jobs must be wrapped in arrow functions—do not pass promises directly.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * const results = await executeInParallel( {
22
+ * jobs: [
23
+ * () => myStep( data1 ),
24
+ * () => myStep( data2 ),
25
+ * () => myStep( data3 )
26
+ * ],
27
+ * concurrency: 2
28
+ * } );
29
+ *
30
+ * // Handle the discriminated union (result only exists when ok is true)
31
+ * const successfulResults = results.filter( r => r.ok ).map( r => r.result );
32
+ *
33
+ * // Or handle each result individually
34
+ * for ( const r of results ) {
35
+ * if ( r.ok ) {
36
+ * console.log( `Job ${r.index} succeeded:`, r.result );
37
+ * } else {
38
+ * console.log( `Job ${r.index} failed:`, r.error );
39
+ * }
40
+ * }
41
+ * ```
42
+ *
43
+ * @param params - Parameters object
44
+ * @param params.jobs - Array of arrow functions returning step/activity calls (not promises directly)
45
+ * @param params.concurrency - Max concurrent jobs (default: Infinity)
46
+ * @param params.onJobCompleted - Optional callback invoked as each job completes (in completion order)
47
+ * @returns Array of results sorted by original job index
48
+ */
49
+ export declare function executeInParallel<T>( params: {
50
+ jobs: Array<() => Promise<T> | T>;
51
+ concurrency?: number;
52
+ onJobCompleted?: ( result: ParallelJobResult<T> ) => void;
53
+ } ): Promise<Array<ParallelJobResult<T>>>;
@@ -1,3 +1,4 @@
1
+ // THIS RUNS IN THE TEMPORAL'S SANDBOX ENVIRONMENT
1
2
  import { validateExecuteInParallel } from './validations/static.js';
2
3
 
3
4
  /**
@@ -33,14 +33,6 @@ export function throws( error: Error ): void;
33
33
  */
34
34
  export function setMetadata( target: object, value: object ): void;
35
35
 
36
- /**
37
- * Merge two temporal activity options
38
- */
39
- export function mergeActivityOptions(
40
- base?: import( '@temporalio/workflow' ).ActivityOptions,
41
- ext?: import( '@temporalio/workflow' ).ActivityOptions
42
- ): import( '@temporalio/workflow' ).ActivityOptions;
43
-
44
36
  /** Represents a {Response} serialized to plain object */
45
37
  export type SerializedFetchResponse = {
46
38
  /** The response url */
@@ -102,7 +94,7 @@ export function isPlainObject( object: unknown ): boolean;
102
94
  * @param arr - The array to shuffle
103
95
  * @returns A shuffled array copy
104
96
  */
105
- export function shuffleArray( arr: array ): array;
97
+ export function shuffleArray( arr: unknown[] ): unknown[];
106
98
 
107
99
  /**
108
100
  * Creates a new object by merging object `b` onto object `a`, biased toward `b`:
@@ -37,17 +37,6 @@ export const throws = e => {
37
37
  export const setMetadata = ( target, values ) =>
38
38
  Object.defineProperty( target, METADATA_ACCESS_SYMBOL, { value: values, writable: false, enumerable: false, configurable: false } );
39
39
 
40
- /**
41
- * Merge two temporal activity options
42
- * @param {import('@temporalio/workflow').ActivityOptions} base
43
- * @param {import('@temporalio/workflow').ActivityOptions} ext
44
- * @returns {import('@temporalio/workflow').ActivityOptions}
45
- */
46
- export const mergeActivityOptions = ( base = {}, ext = {} ) =>
47
- Object.entries( ext ).reduce( ( options, [ k, v ] ) =>
48
- Object.assign( options, { [k]: typeof v === 'object' ? mergeActivityOptions( options[k], v ) : v } )
49
- , clone( base ) );
50
-
51
40
  /**
52
41
  * Returns true if string value is stringbool and true
53
42
  * @param {string} v
@@ -179,6 +168,7 @@ export const shuffleArray = arr => arr
179
168
  * - Object "b" fields that don't exist on object "a" will be created;
180
169
  * - Object "a" fields that don't exist on object "b" will not be touched;
181
170
  *
171
+ * If "b" isn't an object, a new object equal to "a" is returned
182
172
  *
183
173
  * @param {object} a - The base object
184
174
  * @param {object} b - The target object
@@ -189,7 +179,7 @@ export const deepMerge = ( a, b ) => {
189
179
  throw new Error( 'Parameter "a" is not an object.' );
190
180
  }
191
181
  if ( !isPlainObject( b ) ) {
192
- throw new Error( 'Parameter "b" is not an object.' );
182
+ return clone( a );
193
183
  }
194
184
  return Object.entries( b ).reduce( ( obj, [ k, v ] ) =>
195
185
  Object.assign( obj, { [k]: isPlainObject( v ) && isPlainObject( a[k] ) ? deepMerge( a[k], v ) : v } )