@outputai/core 0.1.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/LICENSE +201 -0
- package/README.md +11 -0
- package/bin/healthcheck.mjs +36 -0
- package/bin/healthcheck.spec.js +90 -0
- package/bin/worker.sh +26 -0
- package/package.json +67 -0
- package/src/activity_integration/context.d.ts +27 -0
- package/src/activity_integration/context.js +17 -0
- package/src/activity_integration/context.spec.js +42 -0
- package/src/activity_integration/events.d.ts +7 -0
- package/src/activity_integration/events.js +10 -0
- package/src/activity_integration/index.d.ts +9 -0
- package/src/activity_integration/index.js +3 -0
- package/src/activity_integration/tracing.d.ts +32 -0
- package/src/activity_integration/tracing.js +37 -0
- package/src/async_storage.js +19 -0
- package/src/bus.js +3 -0
- package/src/consts.js +32 -0
- package/src/errors.d.ts +15 -0
- package/src/errors.js +14 -0
- package/src/hooks/index.d.ts +28 -0
- package/src/hooks/index.js +32 -0
- package/src/index.d.ts +49 -0
- package/src/index.js +4 -0
- package/src/interface/evaluation_result.d.ts +173 -0
- package/src/interface/evaluation_result.js +215 -0
- package/src/interface/evaluator.d.ts +70 -0
- package/src/interface/evaluator.js +34 -0
- package/src/interface/evaluator.spec.js +565 -0
- package/src/interface/index.d.ts +9 -0
- package/src/interface/index.js +26 -0
- package/src/interface/step.d.ts +138 -0
- package/src/interface/step.js +22 -0
- package/src/interface/types.d.ts +27 -0
- package/src/interface/validations/runtime.js +20 -0
- package/src/interface/validations/runtime.spec.js +29 -0
- package/src/interface/validations/schema_utils.js +8 -0
- package/src/interface/validations/schema_utils.spec.js +67 -0
- package/src/interface/validations/static.js +136 -0
- package/src/interface/validations/static.spec.js +366 -0
- package/src/interface/webhook.d.ts +84 -0
- package/src/interface/webhook.js +64 -0
- package/src/interface/webhook.spec.js +122 -0
- package/src/interface/workflow.d.ts +273 -0
- package/src/interface/workflow.js +128 -0
- package/src/interface/workflow.spec.js +467 -0
- package/src/interface/workflow_context.js +31 -0
- package/src/interface/workflow_utils.d.ts +76 -0
- package/src/interface/workflow_utils.js +50 -0
- package/src/interface/workflow_utils.spec.js +190 -0
- package/src/interface/zod_integration.spec.js +646 -0
- package/src/internal_activities/index.js +66 -0
- package/src/internal_activities/index.spec.js +102 -0
- package/src/logger.js +73 -0
- package/src/tracing/internal_interface.js +71 -0
- package/src/tracing/processors/local/index.js +111 -0
- package/src/tracing/processors/local/index.spec.js +149 -0
- package/src/tracing/processors/s3/configs.js +31 -0
- package/src/tracing/processors/s3/configs.spec.js +64 -0
- package/src/tracing/processors/s3/index.js +114 -0
- package/src/tracing/processors/s3/index.spec.js +153 -0
- package/src/tracing/processors/s3/redis_client.js +62 -0
- package/src/tracing/processors/s3/redis_client.spec.js +185 -0
- package/src/tracing/processors/s3/s3_client.js +27 -0
- package/src/tracing/processors/s3/s3_client.spec.js +62 -0
- package/src/tracing/tools/build_trace_tree.js +83 -0
- package/src/tracing/tools/build_trace_tree.spec.js +135 -0
- package/src/tracing/tools/utils.js +21 -0
- package/src/tracing/tools/utils.spec.js +14 -0
- package/src/tracing/trace_engine.js +97 -0
- package/src/tracing/trace_engine.spec.js +199 -0
- package/src/utils/index.d.ts +134 -0
- package/src/utils/index.js +2 -0
- package/src/utils/resolve_invocation_dir.js +34 -0
- package/src/utils/resolve_invocation_dir.spec.js +102 -0
- package/src/utils/utils.js +211 -0
- package/src/utils/utils.spec.js +448 -0
- package/src/worker/bundler_options.js +43 -0
- package/src/worker/catalog_workflow/catalog.js +114 -0
- package/src/worker/catalog_workflow/index.js +54 -0
- package/src/worker/catalog_workflow/index.spec.js +196 -0
- package/src/worker/catalog_workflow/workflow.js +24 -0
- package/src/worker/configs.js +49 -0
- package/src/worker/configs.spec.js +130 -0
- package/src/worker/index.js +89 -0
- package/src/worker/index.spec.js +177 -0
- package/src/worker/interceptors/activity.js +62 -0
- package/src/worker/interceptors/activity.spec.js +212 -0
- package/src/worker/interceptors/workflow.js +70 -0
- package/src/worker/interceptors/workflow.spec.js +167 -0
- package/src/worker/interceptors.js +10 -0
- package/src/worker/loader.js +151 -0
- package/src/worker/loader.spec.js +236 -0
- package/src/worker/loader_tools.js +132 -0
- package/src/worker/loader_tools.spec.js +156 -0
- package/src/worker/log_hooks.js +95 -0
- package/src/worker/log_hooks.spec.js +217 -0
- package/src/worker/sandboxed_utils.js +18 -0
- package/src/worker/shutdown.js +26 -0
- package/src/worker/shutdown.spec.js +82 -0
- package/src/worker/sinks.js +74 -0
- package/src/worker/start_catalog.js +36 -0
- package/src/worker/start_catalog.spec.js +118 -0
- package/src/worker/webpack_loaders/consts.js +9 -0
- package/src/worker/webpack_loaders/tools.js +548 -0
- package/src/worker/webpack_loaders/tools.spec.js +330 -0
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.js +221 -0
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +336 -0
- package/src/worker/webpack_loaders/workflow_rewriter/index.mjs +61 -0
- package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +216 -0
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.js +196 -0
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.spec.js +123 -0
- package/src/worker/webpack_loaders/workflow_validator/index.mjs +205 -0
- package/src/worker/webpack_loaders/workflow_validator/index.spec.js +613 -0
|
@@ -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/@outputai/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/@outputai/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/@outputai/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/@outputai/core/src/async_storage.js:12:44)
|
|
88
|
+
at ActivityExecutionInterceptor.execute (file:///app/node_modules/@outputai/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
|
+
} );
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { METADATA_ACCESS_SYMBOL } from '#consts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Node safe clone implementation that doesn't use global structuredClone()
|
|
5
|
+
* @param {object} v
|
|
6
|
+
* @returns {object}
|
|
7
|
+
*/
|
|
8
|
+
export const clone = v => JSON.parse( JSON.stringify( v ) );
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Detect a JS plain object.
|
|
12
|
+
*
|
|
13
|
+
* @param {unknown} v
|
|
14
|
+
* @returns {boolean}
|
|
15
|
+
*/
|
|
16
|
+
export const isPlainObject = v =>
|
|
17
|
+
typeof v === 'object' &&
|
|
18
|
+
!Array.isArray( v ) &&
|
|
19
|
+
v !== null &&
|
|
20
|
+
[ Object.prototype, null ].includes( Object.getPrototypeOf( v ) );
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Throw given error
|
|
24
|
+
* @param {Error} e
|
|
25
|
+
* @throws {e}
|
|
26
|
+
*/
|
|
27
|
+
export const throws = e => {
|
|
28
|
+
throw e;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Add metadata "values" property to a given object
|
|
33
|
+
* @param {object} target
|
|
34
|
+
* @param {object} values
|
|
35
|
+
* @returns
|
|
36
|
+
*/
|
|
37
|
+
export const setMetadata = ( target, values ) =>
|
|
38
|
+
Object.defineProperty( target, METADATA_ACCESS_SYMBOL, { value: values, writable: false, enumerable: false, configurable: false } );
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Read metadata previously attached via setMetadata
|
|
42
|
+
* @param {Function} target
|
|
43
|
+
* @returns {object|null}
|
|
44
|
+
*/
|
|
45
|
+
export const getMetadata = target => target[METADATA_ACCESS_SYMBOL] ?? null;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Returns true if string value is stringbool and true
|
|
49
|
+
* @param {string} v
|
|
50
|
+
* @returns
|
|
51
|
+
*/
|
|
52
|
+
export const isStringboolTrue = v => [ '1', 'true', 'on' ].includes( v );
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Consume Fetch's HTTP Response and return a serialized version of it;
|
|
56
|
+
*
|
|
57
|
+
* @param {Response} response
|
|
58
|
+
* @returns {object} Serialized response
|
|
59
|
+
*/
|
|
60
|
+
export const serializeFetchResponse = async response => {
|
|
61
|
+
const headers = Object.fromEntries( response.headers );
|
|
62
|
+
const contentType = headers['content-type'] || '';
|
|
63
|
+
|
|
64
|
+
const body = await ( async () => {
|
|
65
|
+
if ( contentType.includes( 'application/json' ) ) {
|
|
66
|
+
return response.json();
|
|
67
|
+
}
|
|
68
|
+
if ( contentType.startsWith( 'text/' ) ) {
|
|
69
|
+
return response.text();
|
|
70
|
+
}
|
|
71
|
+
return response.arrayBuffer().then( buf => Buffer.from( buf ).toString( 'base64' ) );
|
|
72
|
+
} )();
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
url: response.url,
|
|
76
|
+
status: response.status,
|
|
77
|
+
statusText: response.statusText,
|
|
78
|
+
ok: response.ok,
|
|
79
|
+
headers,
|
|
80
|
+
body
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Duck-typing to detect a Node Readable (Stream) without importing anything
|
|
86
|
+
*
|
|
87
|
+
* @param {unknown} v
|
|
88
|
+
* @returns {boolean}
|
|
89
|
+
*/
|
|
90
|
+
const isReadable = v =>
|
|
91
|
+
typeof v === 'object' &&
|
|
92
|
+
typeof v?.read === 'function' &&
|
|
93
|
+
typeof v?.on === 'function' &&
|
|
94
|
+
typeof v?.pipe === 'function' &&
|
|
95
|
+
v?.readable !== false;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Based on the type of a payload, serialized it to be send as the body of a fetch POST request and also infer its Content Type.
|
|
99
|
+
*
|
|
100
|
+
* Non serializable types versus Content-Type reference (for Node)
|
|
101
|
+
*
|
|
102
|
+
* |Type|Is self-describing)|Inferred type by fetch|Defined mime type|
|
|
103
|
+
* |-|-|-|-}
|
|
104
|
+
* |Blob|yes|`blob.type`||
|
|
105
|
+
* |File|yes|`file.type`||
|
|
106
|
+
* |FormData|yes|"multipart/form-data; boundary=..."||
|
|
107
|
+
* |URLSearchParams|yes|"application/x-www-form-urlencoded;charset=UTF-8"||
|
|
108
|
+
* |ArrayBuffer|no||"application/octet-stream"|
|
|
109
|
+
* |TypedArray (Uint8Array,Uint16Array)||"application/octet-stream"||
|
|
110
|
+
* |DataView|no||"application/octet-stream"|
|
|
111
|
+
* |ReadableStream, Readable, AsyncIterator|no||Can't, because stream must be read|
|
|
112
|
+
*
|
|
113
|
+
* If payload is none of the above types, test it:
|
|
114
|
+
* If the it is an object, serialize using JSON.stringify and set content-type to `application/json`;
|
|
115
|
+
* Else, it is a JS primitive, serialize using JSON.stringify and set content-type to `text/plain`;
|
|
116
|
+
*
|
|
117
|
+
* This implementation is overkill for temporal workflows since the only types available there will be:
|
|
118
|
+
* - URLSearchParams
|
|
119
|
+
* - ArrayBuffer
|
|
120
|
+
* - TypedArrays
|
|
121
|
+
* - DataView
|
|
122
|
+
* - asyncGenerator
|
|
123
|
+
* The others are non deterministic and are not available at runtime, but this function was build to be flexible
|
|
124
|
+
*
|
|
125
|
+
* @see {@link https://fetch.spec.whatwg.org/#bodyinit}
|
|
126
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch}
|
|
127
|
+
*
|
|
128
|
+
* @param {unknown} payload
|
|
129
|
+
* @returns {object} An object with the serialized body and inferred content-type
|
|
130
|
+
*/
|
|
131
|
+
export const serializeBodyAndInferContentType = payload => {
|
|
132
|
+
const dataTypes = [ Blob, File, URLSearchParams, FormData ];
|
|
133
|
+
|
|
134
|
+
// empty body
|
|
135
|
+
if ( [ null, undefined ].includes( payload ) ) {
|
|
136
|
+
return { body: undefined, contentType: undefined };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Buffer types, covers ArrayBuffer, TypedArrays and DataView
|
|
140
|
+
if ( payload instanceof ArrayBuffer || ArrayBuffer.isView( payload ) ) {
|
|
141
|
+
return { body: payload, contentType: 'application/octet-stream' };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// These data types auto assigned mime types
|
|
145
|
+
if ( dataTypes.some( t => payload instanceof t ) ) {
|
|
146
|
+
return { body: payload, contentType: undefined };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ReadableStream, Readable and Async Iterator mimes cant be determined without reading it
|
|
150
|
+
if ( payload instanceof ReadableStream || typeof payload[Symbol.asyncIterator] === 'function' || isReadable( payload ) ) {
|
|
151
|
+
return { body: payload, contentType: undefined };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if ( typeof payload === 'object' ) {
|
|
155
|
+
return { body: JSON.stringify( payload ), contentType: 'application/json; charset=UTF-8' };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return { body: String( payload ), contentType: 'text/plain; charset=UTF-8' };
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Receives an array and returns a copy of it with the elements shuffled
|
|
163
|
+
*
|
|
164
|
+
* @param {array} arr
|
|
165
|
+
* @returns {array}
|
|
166
|
+
*/
|
|
167
|
+
export const shuffleArray = arr => arr
|
|
168
|
+
.map( v => ( { v, sort: Math.random() } ) )
|
|
169
|
+
.sort( ( a, b ) => a.sort - b.sort )
|
|
170
|
+
.map( ( { v } ) => v );
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Creates a new object merging object "b" onto object "a" biased to "b":
|
|
174
|
+
* - Object "b" will overwrite fields on object "a";
|
|
175
|
+
* - Object "b" fields that don't exist on object "a" will be created;
|
|
176
|
+
* - Object "a" fields that don't exist on object "b" will not be touched;
|
|
177
|
+
*
|
|
178
|
+
* If "b" isn't an object, a new object equal to "a" is returned
|
|
179
|
+
*
|
|
180
|
+
* @param {object} a - The base object
|
|
181
|
+
* @param {object} b - The target object
|
|
182
|
+
* @returns {object} A new object
|
|
183
|
+
*/
|
|
184
|
+
export const deepMerge = ( a, b ) => {
|
|
185
|
+
if ( !isPlainObject( a ) ) {
|
|
186
|
+
throw new Error( 'Parameter "a" is not an object.' );
|
|
187
|
+
}
|
|
188
|
+
if ( !isPlainObject( b ) ) {
|
|
189
|
+
return clone( a );
|
|
190
|
+
}
|
|
191
|
+
return Object.entries( b ).reduce( ( obj, [ k, v ] ) =>
|
|
192
|
+
Object.assign( obj, { [k]: isPlainObject( v ) && isPlainObject( a[k] ) ? deepMerge( a[k], v ) : v } )
|
|
193
|
+
, clone( a ) );
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Shortens a UUID by re-encoding it to base62.
|
|
198
|
+
*
|
|
199
|
+
* This is a Temporal friendly, without crypto or Buffer.
|
|
200
|
+
* @param {string} uuid
|
|
201
|
+
* @returns {string}
|
|
202
|
+
*/
|
|
203
|
+
export const toUrlSafeBase64 = uuid => {
|
|
204
|
+
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-';
|
|
205
|
+
const alphabetLen = alphabet.length;
|
|
206
|
+
const base = BigInt( alphabetLen );
|
|
207
|
+
const hex = uuid.replace( /-/g, '' );
|
|
208
|
+
|
|
209
|
+
const toDigits = n => n <= 0n ? [] : toDigits( n / base ).concat( alphabet[Number( n % base )] );
|
|
210
|
+
return toDigits( BigInt( '0x' + hex ) ).join( '' );
|
|
211
|
+
};
|