@outputai/core 0.8.1-next.e92f632.0 → 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.
- package/package.json +9 -10
- package/src/bus.js +1 -1
- package/src/consts.js +5 -2
- package/src/helpers/component.js +12 -0
- package/src/helpers/component.spec.js +54 -0
- package/src/helpers/fetch.js +105 -0
- package/src/helpers/fetch.spec.js +203 -0
- package/src/helpers/function.js +15 -0
- package/src/helpers/function.spec.js +48 -0
- package/src/helpers/object.js +98 -0
- package/src/helpers/object.spec.js +377 -0
- package/src/helpers/promise.js +29 -0
- package/src/helpers/promise.spec.js +35 -0
- package/src/helpers/string.js +30 -0
- package/src/helpers/string.spec.js +64 -0
- package/src/interface/evaluator.js +14 -12
- package/src/interface/evaluator.spec.js +10 -6
- package/src/interface/index.d.ts +7 -6
- package/src/interface/index.js +2 -0
- package/src/interface/logger.d.ts +53 -0
- package/src/interface/logger.js +68 -0
- package/src/interface/logger.spec.js +138 -0
- package/src/interface/step.js +15 -12
- package/src/interface/step.spec.js +10 -6
- package/src/interface/webhook.d.ts +21 -2
- package/src/interface/workflow.js +85 -78
- package/src/interface/workflow.spec.js +11 -4
- package/src/internal_activities/index.js +38 -35
- package/src/internal_activities/index.spec.js +27 -4
- package/src/logger/development.js +2 -2
- package/src/logger/development.spec.js +19 -2
- package/src/logger/production.js +1 -1
- package/src/logger/production.spec.js +24 -5
- package/src/sdk/README.md +47 -0
- package/src/sdk/helpers/component_metadata.d.ts +17 -0
- package/src/sdk/helpers/component_metadata.js +6 -0
- package/src/sdk/helpers/component_metadata.spec.js +30 -0
- package/src/sdk/helpers/index.d.ts +12 -0
- package/src/sdk/helpers/index.js +3 -0
- package/src/sdk/helpers/objects.d.ts +51 -0
- package/src/sdk/helpers/objects.js +8 -0
- package/src/sdk/helpers/objects.spec.js +16 -0
- package/src/sdk/helpers/path.d.ts +11 -0
- package/src/sdk/helpers/path.js +32 -0
- package/src/{utils/resolve_invocation_dir.spec.js → sdk/helpers/path.spec.js} +9 -9
- package/src/sdk/runtime/context.d.ts +30 -0
- package/src/sdk/runtime/context.js +15 -0
- package/src/{activity_integration → sdk/runtime}/context.spec.js +5 -5
- package/src/sdk/runtime/events.d.ts +15 -0
- package/src/sdk/runtime/events.js +18 -0
- package/src/{activity_integration → sdk/runtime}/events.spec.js +8 -9
- package/src/sdk/runtime/index.d.ts +12 -0
- package/src/sdk/runtime/index.js +3 -0
- package/src/sdk/runtime/tracing.d.ts +46 -0
- package/src/sdk/runtime/tracing.js +11 -0
- package/src/tracing/processors/s3/redis_client.spec.js +0 -6
- package/src/tracing/processors/s3/s3_client.spec.js +0 -6
- package/src/tracing/trace_engine.js +1 -1
- package/src/worker/catalog_workflow/catalog_job.js +1 -1
- package/src/worker/catalog_workflow/index.spec.js +8 -11
- package/src/worker/configs.js +14 -1
- package/src/worker/configs.spec.js +34 -1
- package/src/worker/connection_monitor.js +3 -14
- package/src/worker/connection_monitor.spec.js +4 -21
- package/src/worker/global_functions.js +14 -0
- package/src/worker/global_functions.spec.js +55 -0
- package/src/worker/index.js +11 -3
- package/src/worker/index.spec.js +29 -1
- package/src/worker/interceptors/activity.js +2 -2
- package/src/worker/interceptors/workflow.js +3 -3
- package/src/worker/interceptors/workflow.spec.js +1 -1
- package/src/worker/loader/matchers.js +1 -1
- package/src/worker/loader/tools.js +1 -1
- package/src/worker/loader/tools.spec.js +1 -1
- package/src/worker/log_hooks.js +14 -0
- package/src/worker/log_hooks.spec.js +83 -2
- package/src/worker/sinks.js +7 -1
- package/src/worker/sinks.spec.js +203 -0
- package/src/activity_integration/context.d.ts +0 -23
- package/src/activity_integration/context.js +0 -18
- package/src/activity_integration/event_id_integration.spec.js +0 -52
- package/src/activity_integration/events.d.ts +0 -10
- package/src/activity_integration/events.js +0 -15
- package/src/activity_integration/index.d.ts +0 -9
- package/src/activity_integration/index.js +0 -3
- package/src/activity_integration/tracing.d.ts +0 -40
- package/src/activity_integration/tracing.js +0 -48
- package/src/utils/index.d.ts +0 -180
- package/src/utils/index.js +0 -2
- package/src/utils/resolve_invocation_dir.js +0 -34
- package/src/utils/utils.js +0 -334
- package/src/utils/utils.spec.js +0 -723
- /package/src/{internal_utils → helpers}/aggregations.js +0 -0
- /package/src/{internal_utils → helpers}/aggregations.spec.js +0 -0
- /package/src/{internal_utils → helpers}/errors.js +0 -0
- /package/src/{internal_utils → helpers}/errors.spec.js +0 -0
- /package/src/{internal_utils → helpers}/temporal_context.js +0 -0
- /package/src/{internal_utils → helpers}/temporal_context.spec.ts +0 -0
- /package/src/{internal_utils → helpers}/trace_info.js +0 -0
- /package/src/{internal_utils → helpers}/trace_info.spec.js +0 -0
- /package/src/{internal_utils → helpers}/workflow_context.js +0 -0
- /package/src/{internal_utils → helpers}/workflow_context.spec.js +0 -0
package/src/utils/utils.spec.js
DELETED
|
@@ -1,723 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { Readable } from 'node:stream';
|
|
3
|
-
import {
|
|
4
|
-
clone,
|
|
5
|
-
serializeBodyAndInferContentType,
|
|
6
|
-
serializeFetchResponse,
|
|
7
|
-
deepMerge,
|
|
8
|
-
deepMergeWithResolver,
|
|
9
|
-
isPlainObject,
|
|
10
|
-
toUrlSafeBase64,
|
|
11
|
-
allSettledWithTimeout,
|
|
12
|
-
CancellablePromise,
|
|
13
|
-
runOnce,
|
|
14
|
-
rxEscape
|
|
15
|
-
} from './utils.js';
|
|
16
|
-
|
|
17
|
-
describe( 'clone', () => {
|
|
18
|
-
it( 'produces a deep copy without shared references', () => {
|
|
19
|
-
const original = { a: 1, nested: { b: 2 } };
|
|
20
|
-
const copied = clone( original );
|
|
21
|
-
|
|
22
|
-
copied.nested.b = 3;
|
|
23
|
-
|
|
24
|
-
expect( original.nested.b ).toBe( 2 );
|
|
25
|
-
expect( copied.nested.b ).toBe( 3 );
|
|
26
|
-
expect( copied ).not.toBe( original );
|
|
27
|
-
} );
|
|
28
|
-
|
|
29
|
-
it( 'deep copies JSON-compatible arrays and objects', () => {
|
|
30
|
-
const original = {
|
|
31
|
-
arr: [ 1, { nested: true } ],
|
|
32
|
-
str: 'value',
|
|
33
|
-
bool: false,
|
|
34
|
-
nil: null
|
|
35
|
-
};
|
|
36
|
-
const copied = clone( original );
|
|
37
|
-
|
|
38
|
-
copied.arr[1].nested = false;
|
|
39
|
-
|
|
40
|
-
expect( copied ).toEqual( {
|
|
41
|
-
arr: [ 1, { nested: false } ],
|
|
42
|
-
str: 'value',
|
|
43
|
-
bool: false,
|
|
44
|
-
nil: null
|
|
45
|
-
} );
|
|
46
|
-
expect( original.arr[1].nested ).toBe( true );
|
|
47
|
-
expect( copied ).not.toBe( original );
|
|
48
|
-
expect( copied.arr ).not.toBe( original.arr );
|
|
49
|
-
} );
|
|
50
|
-
|
|
51
|
-
it( 'returns primitive JSON values when they can be parsed', () => {
|
|
52
|
-
expect( clone( null ) ).toBeNull();
|
|
53
|
-
expect( clone( true ) ).toBe( true );
|
|
54
|
-
expect( clone( false ) ).toBe( false );
|
|
55
|
-
expect( clone( 123 ) ).toBe( 123 );
|
|
56
|
-
expect( clone( 'hello' ) ).toBe( 'hello' );
|
|
57
|
-
} );
|
|
58
|
-
|
|
59
|
-
it( 'returns original values when JSON serialization produces no parseable payload', () => {
|
|
60
|
-
const sym = Symbol( 'x' );
|
|
61
|
-
const fn = () => {};
|
|
62
|
-
class Foo {}
|
|
63
|
-
|
|
64
|
-
expect( clone( undefined ) ).toBeUndefined();
|
|
65
|
-
expect( clone( sym ) ).toBe( sym );
|
|
66
|
-
expect( clone( fn ) ).toBe( fn );
|
|
67
|
-
expect( clone( Foo ) ).toBe( Foo );
|
|
68
|
-
expect( clone( Date ) ).toBe( Date );
|
|
69
|
-
expect( clone( Object ) ).toBe( Object );
|
|
70
|
-
expect( clone( Number ) ).toBe( Number );
|
|
71
|
-
} );
|
|
72
|
-
|
|
73
|
-
it( 'returns original values when JSON serialization throws', () => {
|
|
74
|
-
const circular = { name: 'circular' };
|
|
75
|
-
circular.self = circular;
|
|
76
|
-
const bigint = 1n;
|
|
77
|
-
|
|
78
|
-
expect( clone( circular ) ).toBe( circular );
|
|
79
|
-
expect( clone( bigint ) ).toBe( bigint );
|
|
80
|
-
} );
|
|
81
|
-
|
|
82
|
-
it( 'keeps JSON.stringify semantics for special numeric values', () => {
|
|
83
|
-
expect( clone( NaN ) ).toBeNull();
|
|
84
|
-
expect( clone( Infinity ) ).toBeNull();
|
|
85
|
-
expect( clone( -Infinity ) ).toBeNull();
|
|
86
|
-
} );
|
|
87
|
-
|
|
88
|
-
it( 'keeps JSON.stringify semantics for non-plain object instances', () => {
|
|
89
|
-
const date = new Date( '2025-01-01T00:00:00.000Z' );
|
|
90
|
-
|
|
91
|
-
expect( clone( date ) ).toBe( '2025-01-01T00:00:00.000Z' );
|
|
92
|
-
expect( clone( /abc/ ) ).toEqual( {} );
|
|
93
|
-
expect( clone( new Map( [ [ 'a', 1 ] ] ) ) ).toEqual( {} );
|
|
94
|
-
expect( clone( new Set( [ 1, 2 ] ) ) ).toEqual( {} );
|
|
95
|
-
} );
|
|
96
|
-
|
|
97
|
-
it( 'drops object properties that JSON.stringify omits', () => {
|
|
98
|
-
const sym = Symbol( 'x' );
|
|
99
|
-
const original = {
|
|
100
|
-
kept: 'yes',
|
|
101
|
-
missing: undefined,
|
|
102
|
-
fn: () => {},
|
|
103
|
-
sym
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
expect( clone( original ) ).toEqual( { kept: 'yes' } );
|
|
107
|
-
} );
|
|
108
|
-
} );
|
|
109
|
-
|
|
110
|
-
describe( 'allSettledWithTimeout', () => {
|
|
111
|
-
it( 'returns an empty array when no promises are provided', async () => {
|
|
112
|
-
await expect( allSettledWithTimeout( [], 100 ) ).resolves.toEqual( [] );
|
|
113
|
-
} );
|
|
114
|
-
|
|
115
|
-
it( 'returns native allSettled results when all promises settle before timeout', async () => {
|
|
116
|
-
const error = new Error( 'boom' );
|
|
117
|
-
const result = await allSettledWithTimeout( [
|
|
118
|
-
Promise.resolve( 'ok' ),
|
|
119
|
-
Promise.reject( error ),
|
|
120
|
-
42
|
|
121
|
-
], 100 );
|
|
122
|
-
|
|
123
|
-
expect( result ).toEqual( [
|
|
124
|
-
{ status: 'fulfilled', value: 'ok' },
|
|
125
|
-
{ status: 'rejected', reason: error },
|
|
126
|
-
{ status: 'fulfilled', value: 42 }
|
|
127
|
-
] );
|
|
128
|
-
} );
|
|
129
|
-
|
|
130
|
-
it( 'rejects with a timeout-shaped error when promises do not settle in time', async () => {
|
|
131
|
-
vi.useFakeTimers();
|
|
132
|
-
try {
|
|
133
|
-
const pending = new Promise( () => {} );
|
|
134
|
-
const result = allSettledWithTimeout( [ pending ], 1000 );
|
|
135
|
-
const assertion = expect( result ).rejects.toMatchObject( {
|
|
136
|
-
isTimeout: true,
|
|
137
|
-
message: 'Timed out before completing all promises'
|
|
138
|
-
} );
|
|
139
|
-
|
|
140
|
-
await vi.advanceTimersByTimeAsync( 1000 );
|
|
141
|
-
await assertion;
|
|
142
|
-
} finally {
|
|
143
|
-
vi.useRealTimers();
|
|
144
|
-
}
|
|
145
|
-
} );
|
|
146
|
-
} );
|
|
147
|
-
|
|
148
|
-
describe( 'CancellablePromise', () => {
|
|
149
|
-
it( 'exposes a pending promise until it is completed', async () => {
|
|
150
|
-
const cancellable = new CancellablePromise();
|
|
151
|
-
const onComplete = vi.fn();
|
|
152
|
-
|
|
153
|
-
cancellable.promise.then( onComplete );
|
|
154
|
-
await Promise.resolve();
|
|
155
|
-
|
|
156
|
-
expect( cancellable.completed ).toBe( false );
|
|
157
|
-
expect( onComplete ).not.toHaveBeenCalled();
|
|
158
|
-
|
|
159
|
-
cancellable.complete();
|
|
160
|
-
await cancellable.promise;
|
|
161
|
-
|
|
162
|
-
expect( cancellable.completed ).toBe( true );
|
|
163
|
-
expect( onComplete ).toHaveBeenCalledOnce();
|
|
164
|
-
} );
|
|
165
|
-
|
|
166
|
-
it( 'can be completed multiple times without resolving again', async () => {
|
|
167
|
-
const cancellable = new CancellablePromise();
|
|
168
|
-
const onComplete = vi.fn();
|
|
169
|
-
|
|
170
|
-
cancellable.promise.then( onComplete );
|
|
171
|
-
cancellable.complete();
|
|
172
|
-
cancellable.complete();
|
|
173
|
-
await cancellable.promise;
|
|
174
|
-
await Promise.resolve();
|
|
175
|
-
|
|
176
|
-
expect( cancellable.completed ).toBe( true );
|
|
177
|
-
expect( onComplete ).toHaveBeenCalledOnce();
|
|
178
|
-
} );
|
|
179
|
-
} );
|
|
180
|
-
|
|
181
|
-
describe( 'runOnce', () => {
|
|
182
|
-
it( 'calls the wrapped function only once', () => {
|
|
183
|
-
const fn = vi.fn();
|
|
184
|
-
const once = runOnce( fn );
|
|
185
|
-
|
|
186
|
-
once();
|
|
187
|
-
once();
|
|
188
|
-
once();
|
|
189
|
-
|
|
190
|
-
expect( fn ).toHaveBeenCalledOnce();
|
|
191
|
-
} );
|
|
192
|
-
|
|
193
|
-
it( 'passes arguments and replays the first call result', () => {
|
|
194
|
-
const fn = vi.fn( ( a, b ) => a + b );
|
|
195
|
-
const once = runOnce( fn );
|
|
196
|
-
|
|
197
|
-
expect( once( 2, 3 ) ).toBe( 5 );
|
|
198
|
-
expect( once( 4, 5 ) ).toBe( 5 );
|
|
199
|
-
expect( once( 6, 7 ) ).toBe( 5 );
|
|
200
|
-
expect( fn ).toHaveBeenCalledWith( 2, 3 );
|
|
201
|
-
expect( fn ).toHaveBeenCalledOnce();
|
|
202
|
-
} );
|
|
203
|
-
|
|
204
|
-
it( 'replays the first returned promise', () => {
|
|
205
|
-
const result = Promise.resolve( 'done' );
|
|
206
|
-
const fn = vi.fn( () => result );
|
|
207
|
-
const once = runOnce( fn );
|
|
208
|
-
|
|
209
|
-
expect( once() ).toBe( result );
|
|
210
|
-
expect( once() ).toBe( result );
|
|
211
|
-
expect( fn ).toHaveBeenCalledOnce();
|
|
212
|
-
} );
|
|
213
|
-
|
|
214
|
-
it( 'does not retry when the first call throws', () => {
|
|
215
|
-
const error = new Error( 'boom' );
|
|
216
|
-
const fn = vi.fn( () => {
|
|
217
|
-
throw error;
|
|
218
|
-
} );
|
|
219
|
-
const once = runOnce( fn );
|
|
220
|
-
|
|
221
|
-
expect( () => once() ).toThrow( error );
|
|
222
|
-
expect( once() ).toBeUndefined();
|
|
223
|
-
expect( fn ).toHaveBeenCalledOnce();
|
|
224
|
-
} );
|
|
225
|
-
} );
|
|
226
|
-
|
|
227
|
-
describe( 'rxEscape', () => {
|
|
228
|
-
it( 'escapes all regexp metacharacters', () => {
|
|
229
|
-
expect( rxEscape( '.*+?^${}()|[]\\' ) ).toBe( '\\.\\*\\+\\?\\^\\$\\{\\}\\(\\)\\|\\[\\]\\\\' );
|
|
230
|
-
} );
|
|
231
|
-
|
|
232
|
-
it( 'keeps file URL paths matchable as literal regexp input', () => {
|
|
233
|
-
const path = 'file://foo/bar';
|
|
234
|
-
const rx = new RegExp( `^${rxEscape( path )}$` );
|
|
235
|
-
|
|
236
|
-
expect( rx.test( path ) ).toBe( true );
|
|
237
|
-
expect( rx.test( 'file://foo/bar/baz' ) ).toBe( false );
|
|
238
|
-
} );
|
|
239
|
-
|
|
240
|
-
it( 'keeps Windows paths matchable as literal regexp input', () => {
|
|
241
|
-
const path = String.raw`C:\foo\bar`;
|
|
242
|
-
const rx = new RegExp( `^${rxEscape( path )}$` );
|
|
243
|
-
|
|
244
|
-
expect( rx.test( path ) ).toBe( true );
|
|
245
|
-
expect( rx.test( String.raw`C:\foo\bar\baz` ) ).toBe( false );
|
|
246
|
-
} );
|
|
247
|
-
} );
|
|
248
|
-
|
|
249
|
-
describe( 'serializeFetchResponse', () => {
|
|
250
|
-
it( 'serializes JSON response body and flattens headers', async () => {
|
|
251
|
-
const payload = { a: 1, b: 'two' };
|
|
252
|
-
const response = new Response( JSON.stringify( payload ), {
|
|
253
|
-
status: 200,
|
|
254
|
-
statusText: 'OK',
|
|
255
|
-
headers: { 'content-type': 'application/json' }
|
|
256
|
-
} );
|
|
257
|
-
|
|
258
|
-
const result = await serializeFetchResponse( response );
|
|
259
|
-
expect( result.status ).toBe( 200 );
|
|
260
|
-
expect( result.ok ).toBe( true );
|
|
261
|
-
expect( result.statusText ).toBe( 'OK' );
|
|
262
|
-
expect( result.headers['content-type'] ).toContain( 'application/json' );
|
|
263
|
-
expect( result.body ).toEqual( payload );
|
|
264
|
-
} );
|
|
265
|
-
|
|
266
|
-
it( 'serializes text/* response via text()', async () => {
|
|
267
|
-
const bodyText = 'hello world';
|
|
268
|
-
const response = new Response( bodyText, {
|
|
269
|
-
status: 201,
|
|
270
|
-
statusText: 'Created',
|
|
271
|
-
headers: { 'content-type': 'text/plain; charset=utf-8' }
|
|
272
|
-
} );
|
|
273
|
-
|
|
274
|
-
const result = await serializeFetchResponse( response );
|
|
275
|
-
expect( result.status ).toBe( 201 );
|
|
276
|
-
expect( result.ok ).toBe( true );
|
|
277
|
-
expect( result.statusText ).toBe( 'Created' );
|
|
278
|
-
expect( result.headers['content-type'] ).toContain( 'text/plain' );
|
|
279
|
-
expect( result.body ).toBe( bodyText );
|
|
280
|
-
} );
|
|
281
|
-
|
|
282
|
-
if ( typeof ReadableStream !== 'undefined' ) {
|
|
283
|
-
it( 'serializes ReadableStream body for text/* via text()', async () => {
|
|
284
|
-
const encoder = new TextEncoder();
|
|
285
|
-
const chunk = encoder.encode( 'streamed text' );
|
|
286
|
-
const stream = new ReadableStream( {
|
|
287
|
-
start( controller ) {
|
|
288
|
-
controller.enqueue( chunk );
|
|
289
|
-
controller.close();
|
|
290
|
-
}
|
|
291
|
-
} );
|
|
292
|
-
const response = new Response( stream, {
|
|
293
|
-
status: 200,
|
|
294
|
-
statusText: 'OK',
|
|
295
|
-
headers: { 'content-type': 'text/plain; charset=utf-8' }
|
|
296
|
-
} );
|
|
297
|
-
|
|
298
|
-
const result = await serializeFetchResponse( response );
|
|
299
|
-
expect( result.status ).toBe( 200 );
|
|
300
|
-
expect( result.ok ).toBe( true );
|
|
301
|
-
expect( result.statusText ).toBe( 'OK' );
|
|
302
|
-
expect( result.headers['content-type'] ).toContain( 'text/plain' );
|
|
303
|
-
expect( result.body ).toBe( 'streamed text' );
|
|
304
|
-
} );
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
it( 'serializes non-text/non-json response as base64 from arrayBuffer()', async () => {
|
|
308
|
-
const bytes = Uint8Array.from( [ 0, 1, 2, 3 ] );
|
|
309
|
-
const response = new Response( bytes, {
|
|
310
|
-
status: 200,
|
|
311
|
-
statusText: 'OK',
|
|
312
|
-
headers: { 'content-type': 'application/octet-stream' }
|
|
313
|
-
} );
|
|
314
|
-
|
|
315
|
-
const result = await serializeFetchResponse( response );
|
|
316
|
-
expect( result.status ).toBe( 200 );
|
|
317
|
-
expect( result.ok ).toBe( true );
|
|
318
|
-
expect( result.statusText ).toBe( 'OK' );
|
|
319
|
-
expect( result.headers['content-type'] ).toBe( 'application/octet-stream' );
|
|
320
|
-
expect( result.body ).toBe( Buffer.from( bytes ).toString( 'base64' ) );
|
|
321
|
-
} );
|
|
322
|
-
|
|
323
|
-
it( 'defaults to base64 when content-type header is missing', async () => {
|
|
324
|
-
const bytes = Uint8Array.from( [ 0, 1, 2, 3 ] );
|
|
325
|
-
const response = new Response( bytes, { status: 200 } );
|
|
326
|
-
// No headers set; content-type resolves to ''
|
|
327
|
-
|
|
328
|
-
const result = await serializeFetchResponse( response );
|
|
329
|
-
expect( result.headers['content-type'] ?? '' ).toBe( '' );
|
|
330
|
-
expect( result.body ).toBe( Buffer.from( bytes ).toString( 'base64' ) );
|
|
331
|
-
} );
|
|
332
|
-
} );
|
|
333
|
-
|
|
334
|
-
describe( 'serializeBodyAndInferContentType', () => {
|
|
335
|
-
it( 'returns undefineds for null payload', () => {
|
|
336
|
-
const { body, contentType } = serializeBodyAndInferContentType( null );
|
|
337
|
-
expect( body ).toBeUndefined();
|
|
338
|
-
expect( contentType ).toBeUndefined();
|
|
339
|
-
} );
|
|
340
|
-
|
|
341
|
-
it( 'returns undefineds for undefined payload', () => {
|
|
342
|
-
const { body, contentType } = serializeBodyAndInferContentType( undefined );
|
|
343
|
-
expect( body ).toBeUndefined();
|
|
344
|
-
expect( contentType ).toBeUndefined();
|
|
345
|
-
} );
|
|
346
|
-
|
|
347
|
-
it( 'handles ArrayBuffer with octet-stream', () => {
|
|
348
|
-
const buf = new ArrayBuffer( 4 );
|
|
349
|
-
const { body, contentType } = serializeBodyAndInferContentType( buf );
|
|
350
|
-
expect( body ).toBe( buf );
|
|
351
|
-
expect( contentType ).toBe( 'application/octet-stream' );
|
|
352
|
-
} );
|
|
353
|
-
|
|
354
|
-
it( 'handles TypedArray with octet-stream', () => {
|
|
355
|
-
const view = new Uint8Array( [ 1, 2, 3 ] );
|
|
356
|
-
const { body, contentType } = serializeBodyAndInferContentType( view );
|
|
357
|
-
expect( body ).toBe( view );
|
|
358
|
-
expect( contentType ).toBe( 'application/octet-stream' );
|
|
359
|
-
} );
|
|
360
|
-
|
|
361
|
-
it( 'handles DataView with octet-stream', () => {
|
|
362
|
-
const ab = new ArrayBuffer( 2 );
|
|
363
|
-
const dv = new DataView( ab );
|
|
364
|
-
const { body, contentType } = serializeBodyAndInferContentType( dv );
|
|
365
|
-
expect( body ).toBe( dv );
|
|
366
|
-
expect( contentType ).toBe( 'application/octet-stream' );
|
|
367
|
-
} );
|
|
368
|
-
|
|
369
|
-
// Environment-provided web types
|
|
370
|
-
if ( typeof URLSearchParams !== 'undefined' ) {
|
|
371
|
-
it( 'passes through URLSearchParams without content type', () => {
|
|
372
|
-
const usp = new URLSearchParams( { a: '1', b: 'two' } );
|
|
373
|
-
const { body, contentType } = serializeBodyAndInferContentType( usp );
|
|
374
|
-
expect( body ).toBe( usp );
|
|
375
|
-
expect( contentType ).toBeUndefined();
|
|
376
|
-
} );
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
if ( typeof FormData !== 'undefined' ) {
|
|
380
|
-
it( 'passes through FormData without content type', () => {
|
|
381
|
-
const fd = new FormData();
|
|
382
|
-
fd.append( 'a', '1' );
|
|
383
|
-
const { body, contentType } = serializeBodyAndInferContentType( fd );
|
|
384
|
-
expect( body ).toBe( fd );
|
|
385
|
-
expect( contentType ).toBeUndefined();
|
|
386
|
-
} );
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if ( typeof Blob !== 'undefined' ) {
|
|
390
|
-
it( 'passes through Blob without content type', () => {
|
|
391
|
-
const blob = new Blob( [ 'abc' ], { type: 'text/plain' } );
|
|
392
|
-
const { body, contentType } = serializeBodyAndInferContentType( blob );
|
|
393
|
-
expect( body ).toBe( blob );
|
|
394
|
-
expect( contentType ).toBeUndefined();
|
|
395
|
-
} );
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
if ( typeof File !== 'undefined' ) {
|
|
399
|
-
it( 'passes through File without content type', () => {
|
|
400
|
-
const file = new File( [ 'abc' ], 'a.txt', { type: 'text/plain' } );
|
|
401
|
-
const { body, contentType } = serializeBodyAndInferContentType( file );
|
|
402
|
-
expect( body ).toBe( file );
|
|
403
|
-
expect( contentType ).toBeUndefined();
|
|
404
|
-
} );
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
it( 'passes through async iterator without content type', () => {
|
|
408
|
-
const asyncIter = ( async function *() {
|
|
409
|
-
yield 'chunk';
|
|
410
|
-
} )();
|
|
411
|
-
const { body, contentType } = serializeBodyAndInferContentType( asyncIter );
|
|
412
|
-
expect( typeof body[Symbol.asyncIterator] ).toBe( 'function' );
|
|
413
|
-
expect( contentType ).toBeUndefined();
|
|
414
|
-
} );
|
|
415
|
-
|
|
416
|
-
it( 'passes through Node Readable without content type', () => {
|
|
417
|
-
const readable = Readable.from( [ 'a', 'b' ] );
|
|
418
|
-
const { body, contentType } = serializeBodyAndInferContentType( readable );
|
|
419
|
-
expect( body ).toBe( readable );
|
|
420
|
-
expect( contentType ).toBeUndefined();
|
|
421
|
-
} );
|
|
422
|
-
|
|
423
|
-
it( 'serializes plain object as JSON with JSON content type', () => {
|
|
424
|
-
const input = { a: 1, b: 'two' };
|
|
425
|
-
const { body, contentType } = serializeBodyAndInferContentType( input );
|
|
426
|
-
expect( body ).toBe( JSON.stringify( input ) );
|
|
427
|
-
expect( contentType ).toBe( 'application/json; charset=UTF-8' );
|
|
428
|
-
} );
|
|
429
|
-
|
|
430
|
-
it( 'serializes string primitive with text/plain content type', () => {
|
|
431
|
-
const { body, contentType } = serializeBodyAndInferContentType( 'hello' );
|
|
432
|
-
expect( body ).toBe( 'hello' );
|
|
433
|
-
expect( contentType ).toBe( 'text/plain; charset=UTF-8' );
|
|
434
|
-
} );
|
|
435
|
-
|
|
436
|
-
it( 'serializes number primitive with text/plain content type', () => {
|
|
437
|
-
const { body, contentType } = serializeBodyAndInferContentType( 42 );
|
|
438
|
-
expect( body ).toBe( '42' );
|
|
439
|
-
expect( contentType ).toBe( 'text/plain; charset=UTF-8' );
|
|
440
|
-
} );
|
|
441
|
-
|
|
442
|
-
it( 'serializes boolean primitive with text/plain content type', () => {
|
|
443
|
-
const { body, contentType } = serializeBodyAndInferContentType( true );
|
|
444
|
-
expect( body ).toBe( 'true' );
|
|
445
|
-
expect( contentType ).toBe( 'text/plain; charset=UTF-8' );
|
|
446
|
-
} );
|
|
447
|
-
} );
|
|
448
|
-
|
|
449
|
-
describe( 'deepMerge', () => {
|
|
450
|
-
it( 'Overwrites properties in object "a"', () => {
|
|
451
|
-
const a = {
|
|
452
|
-
a: 1,
|
|
453
|
-
b: {
|
|
454
|
-
c: 2
|
|
455
|
-
}
|
|
456
|
-
};
|
|
457
|
-
const b = {
|
|
458
|
-
a: false,
|
|
459
|
-
b: {
|
|
460
|
-
c: true
|
|
461
|
-
}
|
|
462
|
-
};
|
|
463
|
-
expect( deepMerge( a, b ) ).toEqual( {
|
|
464
|
-
a: false,
|
|
465
|
-
b: {
|
|
466
|
-
c: true
|
|
467
|
-
}
|
|
468
|
-
} );
|
|
469
|
-
} );
|
|
470
|
-
|
|
471
|
-
it( 'Adds properties existing in "b" but absent in "a"', () => {
|
|
472
|
-
const a = {
|
|
473
|
-
a: 1
|
|
474
|
-
};
|
|
475
|
-
const b = {
|
|
476
|
-
a: false,
|
|
477
|
-
b: true
|
|
478
|
-
};
|
|
479
|
-
expect( deepMerge( a, b ) ).toEqual( {
|
|
480
|
-
a: false,
|
|
481
|
-
b: true
|
|
482
|
-
} );
|
|
483
|
-
} );
|
|
484
|
-
|
|
485
|
-
it( 'Keep extra properties in "a"', () => {
|
|
486
|
-
const a = {
|
|
487
|
-
a: 1
|
|
488
|
-
};
|
|
489
|
-
const b = {
|
|
490
|
-
b: true
|
|
491
|
-
};
|
|
492
|
-
expect( deepMerge( a, b ) ).toEqual( {
|
|
493
|
-
a: 1,
|
|
494
|
-
b: true
|
|
495
|
-
} );
|
|
496
|
-
} );
|
|
497
|
-
|
|
498
|
-
it( 'Merge object is a clone', () => {
|
|
499
|
-
const a = {
|
|
500
|
-
a: 1
|
|
501
|
-
};
|
|
502
|
-
const b = {
|
|
503
|
-
b: 1
|
|
504
|
-
};
|
|
505
|
-
const result = deepMerge( a, b );
|
|
506
|
-
a.a = 2;
|
|
507
|
-
b.b = 2;
|
|
508
|
-
expect( result.a ).toEqual( 1 );
|
|
509
|
-
} );
|
|
510
|
-
|
|
511
|
-
it( 'Returns copy of "a" if "b" is not an object', () => {
|
|
512
|
-
const a = {
|
|
513
|
-
a: 1
|
|
514
|
-
};
|
|
515
|
-
expect( deepMerge( a, null ) ).toEqual( { a: 1 } );
|
|
516
|
-
expect( deepMerge( a, undefined ) ).toEqual( { a: 1 } );
|
|
517
|
-
} );
|
|
518
|
-
|
|
519
|
-
it( 'Copy of object "a" is a clone', () => {
|
|
520
|
-
const a = {
|
|
521
|
-
a: 1
|
|
522
|
-
};
|
|
523
|
-
const result = deepMerge( a, null );
|
|
524
|
-
a.a = 2;
|
|
525
|
-
expect( result.a ).toEqual( 1 );
|
|
526
|
-
} );
|
|
527
|
-
|
|
528
|
-
it( 'Throws when first argument is not a plain object', () => {
|
|
529
|
-
expect( () => deepMerge( Function ) ).toThrow( Error );
|
|
530
|
-
expect( () => deepMerge( () => {} ) ).toThrow( Error );
|
|
531
|
-
expect( () => deepMerge( 'a' ) ).toThrow( Error );
|
|
532
|
-
expect( () => deepMerge( true ) ).toThrow( Error );
|
|
533
|
-
expect( () => deepMerge( /a/ ) ).toThrow( Error );
|
|
534
|
-
expect( () => deepMerge( [] ) ).toThrow( Error );
|
|
535
|
-
expect( () => deepMerge( class Foo {}, class Foo {} ) ).toThrow( Error );
|
|
536
|
-
expect( () => deepMerge( Number.constructor, Number.constructor ) ).toThrow( Error );
|
|
537
|
-
expect( () => deepMerge( Number.constructor.prototype, Number.constructor.prototype ) ).toThrow( Error );
|
|
538
|
-
} );
|
|
539
|
-
} );
|
|
540
|
-
|
|
541
|
-
describe( 'deepMergeWithResolver', () => {
|
|
542
|
-
it( 'uses resolver for existing leaf values, including nested leaves', () => {
|
|
543
|
-
const a = {
|
|
544
|
-
cost: { total: 1 },
|
|
545
|
-
tokens: { total: 2, input: 3 }
|
|
546
|
-
};
|
|
547
|
-
const b = {
|
|
548
|
-
cost: { total: 4 },
|
|
549
|
-
tokens: { total: 5, input: 6, output: 7 }
|
|
550
|
-
};
|
|
551
|
-
|
|
552
|
-
expect( deepMergeWithResolver( a, b, ( x, y ) => x + y ) ).toEqual( {
|
|
553
|
-
cost: { total: 5 },
|
|
554
|
-
tokens: { total: 7, input: 9, output: 7 }
|
|
555
|
-
} );
|
|
556
|
-
} );
|
|
557
|
-
|
|
558
|
-
it( 'copies values from "b" when they do not exist in "a"', () => {
|
|
559
|
-
const resolver = vi.fn( ( x, y ) => x + y );
|
|
560
|
-
|
|
561
|
-
expect( deepMergeWithResolver( { a: 1 }, { b: 2, nested: { c: 3 } }, resolver ) ).toEqual( {
|
|
562
|
-
a: 1,
|
|
563
|
-
b: 2,
|
|
564
|
-
nested: { c: 3 }
|
|
565
|
-
} );
|
|
566
|
-
expect( resolver ).not.toHaveBeenCalled();
|
|
567
|
-
} );
|
|
568
|
-
|
|
569
|
-
it( 'keeps extra values from "a" when absent from "b"', () => {
|
|
570
|
-
expect( deepMergeWithResolver( { a: 1, nested: { kept: 2 } }, { b: 3 }, ( x, y ) => x + y ) ).toEqual( {
|
|
571
|
-
a: 1,
|
|
572
|
-
nested: { kept: 2 },
|
|
573
|
-
b: 3
|
|
574
|
-
} );
|
|
575
|
-
} );
|
|
576
|
-
|
|
577
|
-
it( 'returns a clone of "a" when "b" is not an object', () => {
|
|
578
|
-
const a = { nested: { value: 1 } };
|
|
579
|
-
const result = deepMergeWithResolver( a, null, ( x, y ) => x + y );
|
|
580
|
-
|
|
581
|
-
a.nested.value = 2;
|
|
582
|
-
expect( result ).toEqual( { nested: { value: 1 } } );
|
|
583
|
-
} );
|
|
584
|
-
|
|
585
|
-
it( 'throws when first argument is not a plain object', () => {
|
|
586
|
-
expect( () => deepMergeWithResolver( null, {}, ( x, y ) => x + y ) ).toThrow( Error );
|
|
587
|
-
expect( () => deepMergeWithResolver( [], {}, ( x, y ) => x + y ) ).toThrow( Error );
|
|
588
|
-
expect( () => deepMergeWithResolver( 'a', {}, ( x, y ) => x + y ) ).toThrow( Error );
|
|
589
|
-
} );
|
|
590
|
-
} );
|
|
591
|
-
|
|
592
|
-
describe( 'isPlainObject', () => {
|
|
593
|
-
it( 'Detects plain objects', () => {
|
|
594
|
-
expect( isPlainObject( {} ) ).toBe( true );
|
|
595
|
-
expect( isPlainObject( { a: 1 } ) ).toBe( true );
|
|
596
|
-
expect( isPlainObject( new Object() ) ).toBe( true );
|
|
597
|
-
expect( isPlainObject( new Object( { foo: 'bar' } ) ) ).toBe( true );
|
|
598
|
-
expect( isPlainObject( Object.create( {}.constructor.prototype ) ) ).toBe( true );
|
|
599
|
-
expect( isPlainObject( Object.create( Object.prototype ) ) ).toBe( true );
|
|
600
|
-
} );
|
|
601
|
-
|
|
602
|
-
it( 'Detects plain objects with different prototypes than Object.prototype', () => {
|
|
603
|
-
// Object with null prototype
|
|
604
|
-
expect( isPlainObject( Object.create( null ) ) ).toBe( true );
|
|
605
|
-
} );
|
|
606
|
-
|
|
607
|
-
it( 'Detects non plain objects that had their __proto__ mutated to Object.prototype or null', () => {
|
|
608
|
-
class Foo {}
|
|
609
|
-
const x = new Foo();
|
|
610
|
-
x.__proto__ = Object.prototype;
|
|
611
|
-
expect( isPlainObject( x ) ).toBe( true );
|
|
612
|
-
|
|
613
|
-
const y = new Foo();
|
|
614
|
-
y.__proto__ = null;
|
|
615
|
-
expect( isPlainObject( y ) ).toBe( true );
|
|
616
|
-
} );
|
|
617
|
-
|
|
618
|
-
it( 'Returns false for object which the prototype is not Object.prototype or null', () => {
|
|
619
|
-
// Object which the prototype is a plain {}
|
|
620
|
-
expect( isPlainObject( Object.create( {} ) ) ).toBe( false );
|
|
621
|
-
// Object which prototype is a another object with null prototype
|
|
622
|
-
expect( isPlainObject( Object.create( Object.create( null ) ) ) ).toBe( false );
|
|
623
|
-
} );
|
|
624
|
-
|
|
625
|
-
it( 'Returns false for functions', () => {
|
|
626
|
-
expect( isPlainObject( Function ) ).toBe( false );
|
|
627
|
-
expect( isPlainObject( () => {} ) ).toBe( false );
|
|
628
|
-
expect( isPlainObject( class Foo {} ) ).toBe( false );
|
|
629
|
-
expect( isPlainObject( Number.constructor ) ).toBe( false );
|
|
630
|
-
expect( isPlainObject( Number.constructor.prototype ) ).toBe( false );
|
|
631
|
-
} );
|
|
632
|
-
|
|
633
|
-
it( 'Returns false for arrays', () => {
|
|
634
|
-
expect( isPlainObject( [ 1, 2, 3 ] ) ).toBe( false );
|
|
635
|
-
expect( isPlainObject( [] ) ).toBe( false );
|
|
636
|
-
expect( isPlainObject( Array( 3 ) ) ).toBe( false );
|
|
637
|
-
} );
|
|
638
|
-
|
|
639
|
-
it( 'Returns false for primitives', () => {
|
|
640
|
-
expect( isPlainObject( null ) ).toBe( false );
|
|
641
|
-
expect( isPlainObject( undefined ) ).toBe( false );
|
|
642
|
-
expect( isPlainObject( false ) ).toBe( false );
|
|
643
|
-
expect( isPlainObject( true ) ).toBe( false );
|
|
644
|
-
expect( isPlainObject( 1 ) ).toBe( false );
|
|
645
|
-
expect( isPlainObject( 0 ) ).toBe( false );
|
|
646
|
-
expect( isPlainObject( '' ) ).toBe( false );
|
|
647
|
-
expect( isPlainObject( 'foo' ) ).toBe( false );
|
|
648
|
-
expect( isPlainObject( Symbol( 'foo' ) ) ).toBe( false );
|
|
649
|
-
expect( isPlainObject( Symbol.for( 'foo' ) ) ).toBe( false );
|
|
650
|
-
} );
|
|
651
|
-
|
|
652
|
-
it( 'Returns true for built in objects', () => {
|
|
653
|
-
expect( isPlainObject( Math ) ).toBe( true );
|
|
654
|
-
expect( isPlainObject( JSON ) ).toBe( true );
|
|
655
|
-
} );
|
|
656
|
-
|
|
657
|
-
it( 'Returns false for built in types', () => {
|
|
658
|
-
expect( isPlainObject( String ) ).toBe( false );
|
|
659
|
-
expect( isPlainObject( Number ) ).toBe( false );
|
|
660
|
-
expect( isPlainObject( Date ) ).toBe( false );
|
|
661
|
-
} );
|
|
662
|
-
|
|
663
|
-
it( 'Returns false for other instance where prototype is not object or null', () => {
|
|
664
|
-
expect( isPlainObject( /foo/ ) ).toBe( false );
|
|
665
|
-
expect( isPlainObject( new RegExp( 'foo' ) ) ).toBe( false );
|
|
666
|
-
expect( isPlainObject( new Date() ) ).toBe( false );
|
|
667
|
-
class Foo {}
|
|
668
|
-
expect( isPlainObject( new Foo() ) ).toBe( false );
|
|
669
|
-
expect( isPlainObject( Object.create( ( class Foo {} ).prototype ) ) ).toBe( false );
|
|
670
|
-
} );
|
|
671
|
-
|
|
672
|
-
it( 'Returns false if tries to change the prototype to simulate an object', () => {
|
|
673
|
-
function Bar() {}
|
|
674
|
-
Bar.prototype = Object.create( null );
|
|
675
|
-
expect( isPlainObject( new Bar() ) ).toBe( false );
|
|
676
|
-
} );
|
|
677
|
-
|
|
678
|
-
it( 'Returns false if object proto was mutated to anything else than object or null', () => {
|
|
679
|
-
const zum = {};
|
|
680
|
-
zum.__proto__ = Number.prototype;
|
|
681
|
-
expect( isPlainObject( zum ) ).toBe( false );
|
|
682
|
-
} );
|
|
683
|
-
} );
|
|
684
|
-
|
|
685
|
-
describe( 'toUrlSafeBase64', () => {
|
|
686
|
-
const urlSafeAlphabet = /^[A-Za-z0-9_-]+$/;
|
|
687
|
-
|
|
688
|
-
it( 'returns a string for a valid UUID', () => {
|
|
689
|
-
const uuid = '550e8400-e29b-41d4-a716-446655440000';
|
|
690
|
-
expect( typeof toUrlSafeBase64( uuid ) ).toBe( 'string' );
|
|
691
|
-
expect( toUrlSafeBase64( uuid ).length ).toBeGreaterThan( 0 );
|
|
692
|
-
} );
|
|
693
|
-
|
|
694
|
-
it( 'output length is 21 or 22 for a standard UUID', () => {
|
|
695
|
-
const uuid = '550e8400-e29b-41d4-a716-446655440000';
|
|
696
|
-
const out = toUrlSafeBase64( uuid );
|
|
697
|
-
expect( out.length ).toBeGreaterThanOrEqual( 21 );
|
|
698
|
-
expect( out.length ).toBeLessThanOrEqual( 22 );
|
|
699
|
-
} );
|
|
700
|
-
|
|
701
|
-
it( 'output contains only url-safe alphabet characters', () => {
|
|
702
|
-
const uuid = '550e8400-e29b-41d4-a716-446655440000';
|
|
703
|
-
const out = toUrlSafeBase64( uuid );
|
|
704
|
-
expect( out ).toMatch( urlSafeAlphabet );
|
|
705
|
-
} );
|
|
706
|
-
|
|
707
|
-
it( 'is deterministic for the same UUID', () => {
|
|
708
|
-
const uuid = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
|
|
709
|
-
expect( toUrlSafeBase64( uuid ) ).toBe( toUrlSafeBase64( uuid ) );
|
|
710
|
-
} );
|
|
711
|
-
|
|
712
|
-
it( 'different UUIDs produce different strings', () => {
|
|
713
|
-
const a = toUrlSafeBase64( '550e8400-e29b-41d4-a716-446655440000' );
|
|
714
|
-
const b = toUrlSafeBase64( '6ba7b810-9dad-11d1-80b4-00c04fd430c8' );
|
|
715
|
-
expect( a ).not.toBe( b );
|
|
716
|
-
} );
|
|
717
|
-
|
|
718
|
-
it( 'strips hyphens and encodes hex (same as 32-char hex)', () => {
|
|
719
|
-
const withHyphens = '550e8400-e29b-41d4-a716-446655440000';
|
|
720
|
-
const hexOnly = '550e8400e29b41d4a716446655440000';
|
|
721
|
-
expect( toUrlSafeBase64( withHyphens ) ).toBe( toUrlSafeBase64( hexOnly ) );
|
|
722
|
-
} );
|
|
723
|
-
} );
|