@oomol-lab/open-flow 0.1.0-beta.74 → 0.1.0-beta.76
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/README.md +3 -1
- package/dist/browser/{ContentIcon-CUQHjQIw.js → ContentIcon-Cm1hbVzW.js} +4 -3
- package/dist/browser/{FlowCanvas-Dtf8_cQp.js → FlowCanvas-NzKtla_c.js} +62 -62
- package/dist/browser/{createResourceDialog-DI9pL-Yr.js → createResourceDialog-BgdedQqu.js} +3 -3
- package/dist/browser/{dialog-BYJGvItc.js → dialog-aSElFFw2.js} +322 -26
- package/dist/browser/{exportSvg-BOoy7-Db.js → exportSvg-Dcz4HTo_.js} +2 -2
- package/dist/browser/{flowChanges-RL-H4CjD.js → flowChanges-NZyS7HSp.js} +490 -438
- package/dist/browser/{flowWorkspace-BkDsETj0.js → flowWorkspace-Bl0SDehy.js} +499 -61
- package/dist/browser/preview.css +696 -825
- package/dist/browser/preview.js +1399 -717
- package/dist/browser/theme.css +114 -262
- package/dist/browser/{tooltip-wZJRB9oP.js → tooltip-3XMK9jkr.js} +1 -1
- package/dist/browser/{typeScriptSession-CSRwWmZm.js → typeScriptSession-C9sBGOKZ.js} +1 -1
- package/dist/browser/ui.css +354 -460
- package/dist/browser/ui.js +1 -1
- package/dist/browser/workbench.css +696 -825
- package/dist/browser/workbench.js +13 -7
- package/dist/browser/{workbenchSelect-BcWPWGzw.js → workbenchSelect-DBGP7Y4Z.js} +2 -2
- package/dist/command/index.js +41 -43
- package/dist/common/authoring.d.ts +5 -5
- package/dist/common/authoringExamples.d.ts +3 -2
- package/dist/common/authoringSchema.d.ts +70 -4
- package/dist/common/connector-proxy.d.ts +1 -1
- package/dist/common/control-api-conformance.js +250 -138
- package/dist/common/control-api.d.ts +3 -2
- package/dist/common/control-api.js +246 -134
- package/dist/common/control-requests.d.ts +3 -3
- package/dist/common/control-requests.js +353 -79
- package/dist/common/cron-trigger.d.ts +2 -1
- package/dist/common/decision.d.ts +4 -4
- package/dist/common/decision.js +5 -2
- package/dist/common/engine-contract.d.ts +6 -1
- package/dist/common/event-source-definition.d.ts +1 -1
- package/dist/common/event-source-view.d.ts +22 -0
- package/dist/common/event-source.d.ts +1 -1
- package/dist/common/event-source.js +24 -65
- package/dist/common/event-sources.d.ts +0 -21
- package/dist/common/flow-authoring.js +90 -19
- package/dist/common/flow-change-schema.d.ts +9 -1
- package/dist/common/flow-change.d.ts +7 -201
- package/dist/common/flow-change.js +96 -23
- package/dist/common/flow-encoding.js +89 -21
- package/dist/common/flow-graph.d.ts +2 -1
- package/dist/common/flow-input-value.d.ts +1 -1
- package/dist/common/flow-model.d.ts +121 -0
- package/dist/common/flow-notifications.d.ts +1 -1
- package/dist/common/flow-retry.d.ts +3 -5
- package/dist/common/flow-semantics.js +816 -586
- package/dist/common/flow-workflowReferences.d.ts +8 -0
- package/dist/common/flowInspection.d.ts +1 -1
- package/dist/common/http-hosts.d.ts +6 -0
- package/dist/common/http-request.d.ts +164 -0
- package/dist/common/http-request.js +5272 -0
- package/dist/common/integration-trigger.d.ts +2 -1
- package/dist/common/mcp.d.ts +3 -3
- package/dist/common/mcp.js +248 -136
- package/dist/common/openapi.d.ts +4 -4
- package/dist/common/openapi.js +4 -1
- package/dist/common/poll-trigger.d.ts +2 -1
- package/dist/common/run-events.js +11 -9
- package/dist/common/runtime-contract.d.ts +23 -1
- package/dist/common/runtime-contract.js +607 -72
- package/dist/common/runtime-nodejs-compat.d.ts +44 -0
- package/dist/common/runtime-nodejs-compat.js +5870 -0
- package/dist/common/scheduler-checkpoint.d.ts +1 -1
- package/dist/common/scheduler-node-execution.d.ts +1 -1
- package/dist/common/scheduler.d.ts +2 -2
- package/dist/common/scheduler.js +95 -25
- package/dist/common/trigger-catalog.d.ts +1 -7
- package/dist/common/trigger-config-options.d.ts +1 -1
- package/dist/common/trigger-config.d.ts +1 -1
- package/dist/common/trigger-contract.d.ts +2 -1
- package/dist/common/trigger-localization.d.ts +8 -2
- package/dist/common/trigger-model.d.ts +101 -0
- package/dist/common/webhook-response.d.ts +1 -3
- package/dist/common/webhook-trigger.d.ts +3 -1
- package/dist/common/webhook-trigger.js +12 -11
- package/dist/common/workflow.d.ts +3 -12
- package/dist/common/workflow.js +25 -23
- package/dist/index.d.ts +41 -14
- package/package.json +9 -1
- package/skills/open-flow/references/flow-authoring.md +1 -0
- package/skills/open-flow/references/flow-n8n-conversion.md +19 -15
- package/src/base/common/json.ts +1 -1
- package/src/connector/common/proxy.ts +1 -1
- package/src/control/common/api.ts +7 -5
- package/src/control/common/authoring.ts +14 -3
- package/src/control/common/authoringExamples.ts +8 -0
- package/src/control/common/authoringSchema.ts +38 -25
- package/src/control/common/authoringTools.ts +1 -1
- package/src/control/common/conformance.ts +1 -1
- package/src/control/common/connectorDecoders.ts +1 -1
- package/src/control/common/eventSources.ts +2 -29
- package/src/control/common/triggerCatalog.ts +1 -8
- package/src/decision/common/decision.ts +15 -7
- package/src/event-source/common/definition.ts +1 -1
- package/src/event-source/common/index.ts +1 -1
- package/src/event-source/common/view.ts +20 -0
- package/src/execution/common/checkpoint.ts +1 -1
- package/src/execution/common/events.ts +3 -0
- package/src/execution/common/node-execution.ts +5 -3
- package/src/execution/common/scheduler.ts +2 -1
- package/src/flow/common/change.ts +59 -192
- package/src/flow/common/changeSchema.ts +39 -5
- package/src/flow/common/connectionUsage.ts +1 -1
- package/src/flow/common/graph.ts +10 -4
- package/src/flow/common/inputValue.ts +1 -1
- package/src/flow/common/model.ts +130 -0
- package/src/flow/common/modules.ts +1 -1
- package/src/flow/common/portChanges.ts +1 -1
- package/src/flow/common/retry.ts +3 -5
- package/src/flow/common/semantics.ts +71 -7
- package/src/flow/common/webhookResponse.ts +4 -8
- package/src/flow/common/workflowReferences.ts +31 -0
- package/src/http/common/authBindings.ts +9 -0
- package/src/http/common/headers.ts +5 -0
- package/src/http/common/hosts.ts +13 -0
- package/src/http/common/httpRequest.ts +266 -0
- package/src/openapi/common/openapi.ts +9 -5
- package/src/{execution → runtime}/common/engineContract.ts +24 -15
- package/src/runtime/common/nodejsCompat.ts +275 -0
- package/src/runtime/common/nodejsGuest.generated.json +47 -0
- package/src/runtime/common/runtime.ts +1138 -0
- package/src/trigger/common/config.ts +1 -1
- package/src/trigger/common/configOptions.ts +1 -1
- package/src/trigger/common/contract.ts +2 -1
- package/src/trigger/common/cron.ts +2 -1
- package/src/trigger/common/integration.ts +2 -1
- package/src/trigger/common/model.ts +87 -0
- package/src/trigger/common/poll.ts +2 -1
- package/src/trigger/common/webhook.ts +7 -4
- package/src/trigger/providers/fieldDescriptions.ts +1 -1
- package/src/trigger/providers/localization.ts +9 -2
- package/src/trigger/providers/remote.ts +1 -1
- package/src/types/index.ts +44 -15
- package/src/workflow/common/workflow.ts +3 -44
- package/src/execution/common/runtime.ts +0 -563
- package/src/openapi/common/authBindings.ts +0 -12
- /package/src/{control → connector}/common/providerIconSprite.ts +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { InputPort, JsonValue,
|
|
1
|
+
import type { InputPort, JsonValue, ManagedTaskDefinitionOf, Port, TaskExecutor } from '../../flow/common/model.ts'
|
|
2
2
|
|
|
3
3
|
import { dequal } from 'dequal/lite'
|
|
4
4
|
import { matchesSchema } from '../../flow/common/schema.ts'
|
|
@@ -224,6 +224,10 @@ function schema(doc: ObjectValue, schemaValue: JsonValue): JsonValue {
|
|
|
224
224
|
if (typeof converted == 'boolean') return converted
|
|
225
225
|
return { $schema: 'https://json-schema.org/draft/2020-12/schema', ...object(converted), ...(Object.keys(defs).length ? { $defs: defs } : {}) }
|
|
226
226
|
}
|
|
227
|
+
function isOpenApiExecutor(executor: TaskExecutor): executor is OpenApiExecutor {
|
|
228
|
+
return executor.kind == 'openapi'
|
|
229
|
+
}
|
|
230
|
+
|
|
227
231
|
function parameters(config: OpenApiExecutor): ObjectValue[] {
|
|
228
232
|
const doc = parseOpenApi(config.document)
|
|
229
233
|
const { item, op } = operation(doc, config.path, config.method)
|
|
@@ -250,7 +254,7 @@ function jsonContent(doc: ObjectValue, value: JsonValue): { schema: JsonValue; r
|
|
|
250
254
|
export function authHandles(auth: readonly OpenApiAuth[]): string[] {
|
|
251
255
|
return auth.flatMap((scheme) => (scheme.type == 'basic' ? [`auth.${scheme.id}.username`, `auth.${scheme.id}.password`] : [`auth.${scheme.id}.token`]))
|
|
252
256
|
}
|
|
253
|
-
export function openApiTask(config: OpenApiExecutor, name = 'OpenAPI'):
|
|
257
|
+
export function openApiTask(config: OpenApiExecutor, name = 'OpenAPI'): ManagedTaskDefinitionOf<OpenApiExecutor> {
|
|
254
258
|
if (!config.path) return { name, executor: config, inputs: [], outputs: [] }
|
|
255
259
|
const doc = parseOpenApi(config.document)
|
|
256
260
|
const { op } = operation(doc, config.path, config.method)
|
|
@@ -318,8 +322,8 @@ export interface OpenApiFieldIssue {
|
|
|
318
322
|
}
|
|
319
323
|
|
|
320
324
|
/** The canvas validator and property panel share the same configuration contract. */
|
|
321
|
-
export function openApiFieldIssues(task:
|
|
322
|
-
if (task.executor
|
|
325
|
+
export function openApiFieldIssues<Executor extends TaskExecutor>(task: ManagedTaskDefinitionOf<Executor>): OpenApiFieldIssue[] {
|
|
326
|
+
if (!isOpenApiExecutor(task.executor)) return []
|
|
323
327
|
const config = task.executor
|
|
324
328
|
let field: OpenApiFieldIssue['field'] = 'sourceUrl'
|
|
325
329
|
try {
|
|
@@ -342,7 +346,7 @@ export function openApiFieldIssues(task: ManagedTaskDefinition): OpenApiFieldIss
|
|
|
342
346
|
}
|
|
343
347
|
}
|
|
344
348
|
|
|
345
|
-
export function openApiIssues(task:
|
|
349
|
+
export function openApiIssues(task: ManagedTaskDefinitionOf<TaskExecutor>): string[] {
|
|
346
350
|
return openApiFieldIssues(task).map((issue) => issue.message)
|
|
347
351
|
}
|
|
348
352
|
export function selectOperation(value: JsonValue, sourceUrl: string, path: string, method: string): OpenApiExecutor {
|
|
@@ -6,24 +6,32 @@ export interface EngineContract {
|
|
|
6
6
|
readonly waitAutoContinue?: boolean
|
|
7
7
|
readonly webhookResponses?: boolean
|
|
8
8
|
readonly workflowTools?: boolean
|
|
9
|
+
readonly httpRequests?: boolean
|
|
10
|
+
/** Code may call `context.fetch` for hosts declared by its `http` Capability. */
|
|
11
|
+
readonly codeFetch?: boolean
|
|
9
12
|
}
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
/** v9 is a single contract: the Node compatibility builtins, `context.fetch`, and no `context.egress`. */
|
|
15
|
+
export const currentEngineContract = 'open-flow-engine/v9'
|
|
16
|
+
/** The published v8 Node compatibility contract, retained for existing Publications and Runs. */
|
|
12
17
|
export const nodejsEngineContract = 'open-flow-engine/v8/nodejs-compat-v1'
|
|
13
18
|
|
|
14
|
-
const
|
|
19
|
+
const platformSource = (contract: string) => `export const engineContract = ${JSON.stringify(contract)}
|
|
20
|
+
export function identity(value) { return value }
|
|
21
|
+
`
|
|
22
|
+
|
|
23
|
+
const v8Contract: EngineContract = {
|
|
15
24
|
workflowTools: true,
|
|
25
|
+
httpRequests: true,
|
|
16
26
|
waitAutoContinue: true,
|
|
17
27
|
webhookResponses: true,
|
|
18
28
|
platformExports: new Set(['engineContract', 'identity']),
|
|
19
29
|
platformModule: 'open-flow:platform',
|
|
20
|
-
platformSource:
|
|
21
|
-
export function identity(value) { return value }
|
|
22
|
-
`,
|
|
30
|
+
platformSource: platformSource('open-flow-engine/v8'),
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
const nodejsContract: EngineContract = {
|
|
26
|
-
...
|
|
34
|
+
...v8Contract,
|
|
27
35
|
builtinModules: new Set(
|
|
28
36
|
[
|
|
29
37
|
'assert',
|
|
@@ -52,29 +60,30 @@ const nodejsContract: EngineContract = {
|
|
|
52
60
|
'zlib',
|
|
53
61
|
].flatMap((name) => [name, `node:${name}`]),
|
|
54
62
|
),
|
|
55
|
-
platformSource:
|
|
56
|
-
export function identity(value) { return value }
|
|
57
|
-
`,
|
|
63
|
+
platformSource: platformSource(nodejsEngineContract),
|
|
58
64
|
}
|
|
59
65
|
|
|
66
|
+
const currentContract: EngineContract = { ...nodejsContract, codeFetch: true, platformSource: platformSource(currentEngineContract) }
|
|
67
|
+
|
|
60
68
|
export function findEngineContract(contract: string): EngineContract | undefined {
|
|
61
69
|
if (contract == currentEngineContract) return currentContract
|
|
70
|
+
if (contract == 'open-flow-engine/v8') return v8Contract
|
|
62
71
|
if (contract == nodejsEngineContract) return nodejsContract
|
|
63
72
|
if (contract == 'open-flow-engine/v7' || contract == 'open-flow-engine/v7/nodejs-compat-v1')
|
|
64
73
|
return {
|
|
65
|
-
...(contract.endsWith('/nodejs-compat-v1') ? nodejsContract :
|
|
74
|
+
...(contract.endsWith('/nodejs-compat-v1') ? nodejsContract : v8Contract),
|
|
66
75
|
workflowTools: false,
|
|
67
|
-
|
|
76
|
+
httpRequests: false,
|
|
77
|
+
platformSource: platformSource(contract),
|
|
68
78
|
}
|
|
69
79
|
if (contract == 'open-flow-engine/v6' || contract == 'open-flow-engine/v6/nodejs-compat-v1')
|
|
70
80
|
return {
|
|
71
|
-
...(contract == 'open-flow-engine/v6' ?
|
|
81
|
+
...(contract == 'open-flow-engine/v6' ? v8Contract : nodejsContract),
|
|
72
82
|
workflowTools: false,
|
|
83
|
+
httpRequests: false,
|
|
73
84
|
waitAutoContinue: false,
|
|
74
85
|
webhookResponses: false,
|
|
75
|
-
platformSource:
|
|
76
|
-
export function identity(value) { return value }
|
|
77
|
-
`,
|
|
86
|
+
platformSource: platformSource(contract),
|
|
78
87
|
}
|
|
79
88
|
return undefined
|
|
80
89
|
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import type { EngineContract } from './engineContract.ts'
|
|
2
|
+
|
|
3
|
+
import * as z from 'zod'
|
|
4
|
+
import guestModules from './nodejsGuest.generated.json' with { type: 'json' }
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Guest modules of the Node compatibility engine, keyed by engine module path.
|
|
8
|
+
*
|
|
9
|
+
* A Host evaluates `engine/web.mjs` after installing the guest bridge, then `engine/globals.mjs`, then the Task.
|
|
10
|
+
* The bridge consists of `__hostPrimitive(request, buffer?, extra?)`, which forwards to {@link NodejsPrimitives.call};
|
|
11
|
+
* `__hostTimer(callback, delay, repeat)` and `__hostClearTimer(id)`, which schedule guest callbacks; and
|
|
12
|
+
* `__hostBuiltin(group)`, which synchronously evaluates {@link nodejsBuiltinGroupModule} and returns its namespace.
|
|
13
|
+
*/
|
|
14
|
+
export const nodejsGuestModules: Readonly<Record<string, string>> = guestModules
|
|
15
|
+
export const nodejsWebModule = 'engine/web.mjs'
|
|
16
|
+
export const nodejsGlobalsModule = 'engine/globals.mjs'
|
|
17
|
+
|
|
18
|
+
const builtinGroups: ReadonlySet<string> = new Set(['abort', 'buffer', 'crypto', 'process', 'streams', 'url'])
|
|
19
|
+
|
|
20
|
+
/** Returns the engine module of a lazily loaded builtin group requested through `__hostBuiltin`. */
|
|
21
|
+
export function nodejsBuiltinGroupModule(group: string): string {
|
|
22
|
+
if (!builtinGroups.has(group)) throw new TypeError('Unknown builtin group.')
|
|
23
|
+
return group == 'crypto' ? 'engine/deferred/crypto.mjs' : `engine/globals/${group}.mjs`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Resolves a builtin specifier or an import between engine modules; other specifiers belong to the Host closure. */
|
|
27
|
+
export function resolveNodejsModule(contract: EngineContract, specifier: string, referrer: string): string | undefined {
|
|
28
|
+
if (contract.builtinModules?.has(specifier)) return `engine/builtin/${specifier.replace(/^node:/, '')}.mjs`
|
|
29
|
+
if (!referrer.startsWith('engine/')) return undefined
|
|
30
|
+
const resolved = new URL(specifier, `https://modules.invalid/${referrer}`).pathname.slice(1)
|
|
31
|
+
return Object.hasOwn(nodejsGuestModules, resolved) ? resolved : undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type Encoding = 'utf8' | 'utf-8' | 'hex' | 'base64' | 'ascii' | 'latin1' | 'binary' | 'ucs2' | 'ucs-2' | 'utf16le' | 'utf-16le'
|
|
35
|
+
type ZlibMethod = 'gzipSync' | 'gunzipSync' | 'deflateSync' | 'inflateSync' | 'deflateRawSync' | 'inflateRawSync' | 'unzipSync'
|
|
36
|
+
|
|
37
|
+
interface PlatformHash {
|
|
38
|
+
update(data: Uint8Array): unknown
|
|
39
|
+
digest(): Uint8Array
|
|
40
|
+
destroy(): unknown
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Platform implementations behind the primitives; Node.js and Workers with `nodejs_compat` supply their own modules. */
|
|
44
|
+
export interface NodejsPrimitivePlatform {
|
|
45
|
+
readonly Buffer: {
|
|
46
|
+
from(text: string, encoding: Encoding): Uint8Array
|
|
47
|
+
from(buffer: ArrayBuffer, offset: number, length: number): { toString(encoding: Encoding): string }
|
|
48
|
+
byteLength(text: string, encoding: Encoding): number
|
|
49
|
+
}
|
|
50
|
+
createHash(algorithm: string): PlatformHash
|
|
51
|
+
createHmac(algorithm: string, key: Uint8Array): PlatformHash
|
|
52
|
+
readonly zlib: Readonly<Record<ZlibMethod, (input: Uint8Array, options: object) => Uint8Array>>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface NodejsPrimitives {
|
|
56
|
+
/** Serves one `__hostPrimitive` request; failures are returned as a serialized guest error. */
|
|
57
|
+
call(request: string, buffer?: ArrayBuffer, extra?: ArrayBuffer): string | ArrayBuffer
|
|
58
|
+
close(): void
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const binaryLimit = 1024 * 1024
|
|
62
|
+
const range = { offset: z.number().int().nonnegative(), length: z.number().int().min(0).max(binaryLimit) }
|
|
63
|
+
const encoding = z.enum(['utf8', 'utf-8', 'hex', 'base64', 'ascii', 'latin1', 'binary', 'ucs2', 'ucs-2', 'utf16le', 'utf-16le'])
|
|
64
|
+
const operation = z.discriminatedUnion('operation', [
|
|
65
|
+
z.object({
|
|
66
|
+
operation: z.literal('url'),
|
|
67
|
+
input: z.string(),
|
|
68
|
+
base: z.string().optional(),
|
|
69
|
+
change: z
|
|
70
|
+
.object({ key: z.enum(['href', 'protocol', 'username', 'password', 'host', 'hostname', 'port', 'pathname', 'search', 'hash']), value: z.string() })
|
|
71
|
+
.optional(),
|
|
72
|
+
entries: z.array(z.tuple([z.string(), z.string()])).optional(),
|
|
73
|
+
}),
|
|
74
|
+
z.object({ operation: z.literal('params.parse'), input: z.string() }),
|
|
75
|
+
z.object({ operation: z.literal('params.stringify'), entries: z.array(z.tuple([z.string(), z.string()])) }),
|
|
76
|
+
z.object({ operation: z.literal('encode'), text: z.string() }),
|
|
77
|
+
z.object({ operation: z.literal('decoder'), label: z.string(), fatal: z.boolean(), ignoreBOM: z.boolean() }),
|
|
78
|
+
z.object({
|
|
79
|
+
operation: z.literal('decode'),
|
|
80
|
+
id: z.number().int().positive().optional(),
|
|
81
|
+
label: z.string(),
|
|
82
|
+
fatal: z.boolean(),
|
|
83
|
+
ignoreBOM: z.boolean(),
|
|
84
|
+
...range,
|
|
85
|
+
stream: z.boolean(),
|
|
86
|
+
}),
|
|
87
|
+
z.object({ operation: z.enum(['atob', 'btoa']), text: z.string() }),
|
|
88
|
+
z.object({ operation: z.literal('random'), length: z.number().int().min(0).max(65536) }),
|
|
89
|
+
z.object({ operation: z.literal('uuid') }),
|
|
90
|
+
z.object({ operation: z.enum(['buffer.from', 'buffer.byteLength']), text: z.string(), encoding }),
|
|
91
|
+
z.object({ operation: z.literal('buffer.toString'), encoding, ...range }),
|
|
92
|
+
z.object({ operation: z.literal('hash.create'), algorithm: z.string(), hmac: z.boolean(), ...range, length: z.number().int().min(0).max(65536) }),
|
|
93
|
+
z.object({ operation: z.literal('hash.update'), id: z.number().int().positive(), ...range }),
|
|
94
|
+
z.object({ operation: z.enum(['hash.digest', 'hash.dispose']), id: z.number().int().positive() }),
|
|
95
|
+
z.object({
|
|
96
|
+
operation: z.literal('zlib'),
|
|
97
|
+
method: z.enum(['gzipSync', 'gunzipSync', 'deflateSync', 'inflateSync', 'deflateRawSync', 'inflateRawSync', 'unzipSync']),
|
|
98
|
+
...range,
|
|
99
|
+
dictionary: z.object(range).optional(),
|
|
100
|
+
options: z.object({
|
|
101
|
+
level: z.number().optional(),
|
|
102
|
+
windowBits: z.number().optional(),
|
|
103
|
+
memLevel: z.number().optional(),
|
|
104
|
+
strategy: z.number().optional(),
|
|
105
|
+
chunkSize: z.number().int().min(64).max(binaryLimit).optional(),
|
|
106
|
+
flush: z.number().optional(),
|
|
107
|
+
finishFlush: z.number().optional(),
|
|
108
|
+
maxOutputLength: z.number().int().min(1).max(binaryLimit).optional(),
|
|
109
|
+
}),
|
|
110
|
+
}),
|
|
111
|
+
])
|
|
112
|
+
|
|
113
|
+
/** Creates the per-Task primitive state behind the guest bridge, bounded by call count, bytes, and live instances. */
|
|
114
|
+
export function createNodejsPrimitives(platform: NodejsPrimitivePlatform): NodejsPrimitives {
|
|
115
|
+
// Only streaming decodes keep a host decoder, so Tasks may create any number of guest TextDecoders.
|
|
116
|
+
const decoders = new Map<number, TextDecoder>()
|
|
117
|
+
let nextDecoder = 0
|
|
118
|
+
const hashes = new Map<number, PlatformHash>()
|
|
119
|
+
let nextHash = 0
|
|
120
|
+
let remainingBytes = 64 * binaryLimit
|
|
121
|
+
let calls = 0
|
|
122
|
+
let closed = false
|
|
123
|
+
const spend = (bytes: number): void => {
|
|
124
|
+
remainingBytes -= bytes
|
|
125
|
+
if (remainingBytes < 0) throw new Error('Native builtin byte limit exceeded.')
|
|
126
|
+
}
|
|
127
|
+
const binaryResult = (bytes: Uint8Array): ArrayBuffer => {
|
|
128
|
+
if (bytes.byteLength > binaryLimit) throw new RangeError('Builtin result too large.')
|
|
129
|
+
spend(bytes.byteLength)
|
|
130
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer
|
|
131
|
+
}
|
|
132
|
+
const binaryView = (buffer: ArrayBuffer | undefined, offset: number, length: number): Uint8Array => {
|
|
133
|
+
const byteLength = buffer?.byteLength ?? 0
|
|
134
|
+
if (byteLength > binaryLimit || offset + length > byteLength) throw new RangeError('Invalid buffer range.')
|
|
135
|
+
spend(length)
|
|
136
|
+
return new Uint8Array(buffer ?? new ArrayBuffer(0), offset, length)
|
|
137
|
+
}
|
|
138
|
+
const serve = (source: string, buffer: ArrayBuffer | undefined, extra: ArrayBuffer | undefined): unknown => {
|
|
139
|
+
const request = operation.parse(JSON.parse(source))
|
|
140
|
+
switch (request.operation) {
|
|
141
|
+
case 'buffer.from':
|
|
142
|
+
spend(request.text.length * 2)
|
|
143
|
+
return binaryResult(platform.Buffer.from(request.text, request.encoding))
|
|
144
|
+
case 'buffer.byteLength':
|
|
145
|
+
spend(request.text.length * 2)
|
|
146
|
+
return { value: platform.Buffer.byteLength(request.text, request.encoding) }
|
|
147
|
+
case 'buffer.toString': {
|
|
148
|
+
const view = binaryView(buffer, request.offset, request.length)
|
|
149
|
+
const value = platform.Buffer.from(view.buffer as ArrayBuffer, view.byteOffset, view.byteLength).toString(request.encoding)
|
|
150
|
+
spend(value.length * 2)
|
|
151
|
+
return { value }
|
|
152
|
+
}
|
|
153
|
+
case 'hash.create': {
|
|
154
|
+
if (hashes.size >= 256) throw new Error('Hash instance limit exceeded.')
|
|
155
|
+
const key = binaryView(buffer, request.offset, request.length)
|
|
156
|
+
hashes.set(++nextHash, request.hmac ? platform.createHmac(request.algorithm, key) : platform.createHash(request.algorithm))
|
|
157
|
+
return { value: nextHash }
|
|
158
|
+
}
|
|
159
|
+
case 'hash.update': {
|
|
160
|
+
const hash = hashes.get(request.id)
|
|
161
|
+
if (hash == null) throw new Error('Digest already called or hash destroyed.')
|
|
162
|
+
try {
|
|
163
|
+
hash.update(binaryView(buffer, request.offset, request.length))
|
|
164
|
+
} catch (error) {
|
|
165
|
+
hashes.delete(request.id)
|
|
166
|
+
hash.destroy()
|
|
167
|
+
throw error
|
|
168
|
+
}
|
|
169
|
+
return { value: undefined }
|
|
170
|
+
}
|
|
171
|
+
case 'hash.digest': {
|
|
172
|
+
const hash = hashes.get(request.id)
|
|
173
|
+
if (hash == null) throw new Error('Digest already called or hash destroyed.')
|
|
174
|
+
hashes.delete(request.id)
|
|
175
|
+
try {
|
|
176
|
+
return binaryResult(hash.digest())
|
|
177
|
+
} finally {
|
|
178
|
+
hash.destroy()
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
case 'hash.dispose':
|
|
182
|
+
hashes.get(request.id)?.destroy()
|
|
183
|
+
hashes.delete(request.id)
|
|
184
|
+
return { value: undefined }
|
|
185
|
+
case 'zlib': {
|
|
186
|
+
const input = binaryView(buffer, request.offset, request.length)
|
|
187
|
+
const options = {
|
|
188
|
+
...request.options,
|
|
189
|
+
maxOutputLength: request.options.maxOutputLength ?? binaryLimit,
|
|
190
|
+
dictionary: request.dictionary == null ? undefined : binaryView(extra, request.dictionary.offset, request.dictionary.length),
|
|
191
|
+
}
|
|
192
|
+
return binaryResult(platform.zlib[request.method](input, options))
|
|
193
|
+
}
|
|
194
|
+
case 'url': {
|
|
195
|
+
let url: URL
|
|
196
|
+
try {
|
|
197
|
+
url = new URL(request.input, request.base)
|
|
198
|
+
} catch (error) {
|
|
199
|
+
if (!(error instanceof TypeError)) throw error
|
|
200
|
+
return { value: null }
|
|
201
|
+
}
|
|
202
|
+
if (request.change != null) url[request.change.key] = request.change.value
|
|
203
|
+
if (request.entries != null) url.search = new URLSearchParams(request.entries).toString()
|
|
204
|
+
const { hash, host, hostname, href, origin, password, pathname, port, protocol, search, username } = url
|
|
205
|
+
return { value: { hash, host, hostname, href, origin, password, pathname, port, protocol, search, username, entries: [...url.searchParams] } }
|
|
206
|
+
}
|
|
207
|
+
case 'params.parse':
|
|
208
|
+
return { value: [...new URLSearchParams(request.input)] }
|
|
209
|
+
case 'params.stringify':
|
|
210
|
+
return { value: new URLSearchParams(request.entries).toString() }
|
|
211
|
+
case 'encode': {
|
|
212
|
+
const encoded = new TextEncoder().encode(request.text)
|
|
213
|
+
if (encoded.byteLength > binaryLimit) throw new Error('Builtin result too large.')
|
|
214
|
+
return encoded.buffer
|
|
215
|
+
}
|
|
216
|
+
case 'decoder':
|
|
217
|
+
return { value: { encoding: new TextDecoder(request.label, { fatal: request.fatal, ignoreBOM: request.ignoreBOM }).encoding } }
|
|
218
|
+
case 'decode': {
|
|
219
|
+
const byteLength = buffer?.byteLength ?? 0
|
|
220
|
+
if (request.offset + request.length > byteLength) throw new RangeError('Invalid buffer range.')
|
|
221
|
+
const stored = request.id == null ? undefined : decoders.get(request.id)
|
|
222
|
+
if (request.id != null && stored == null) throw new TypeError('Unknown TextDecoder.')
|
|
223
|
+
const decoder = stored ?? new TextDecoder(request.label, { fatal: request.fatal, ignoreBOM: request.ignoreBOM })
|
|
224
|
+
let text: string
|
|
225
|
+
try {
|
|
226
|
+
text = decoder.decode(new Uint8Array(buffer ?? new ArrayBuffer(0), request.offset, request.length), { stream: request.stream })
|
|
227
|
+
} catch (error) {
|
|
228
|
+
if (request.id != null) decoders.delete(request.id)
|
|
229
|
+
throw error
|
|
230
|
+
}
|
|
231
|
+
if (!request.stream) {
|
|
232
|
+
if (request.id != null) decoders.delete(request.id)
|
|
233
|
+
return { value: { text } }
|
|
234
|
+
}
|
|
235
|
+
if (stored != null) return { value: { id: request.id, text } }
|
|
236
|
+
if (decoders.size >= 256) throw new Error('TextDecoder limit exceeded.')
|
|
237
|
+
decoders.set(++nextDecoder, decoder)
|
|
238
|
+
return { value: { id: nextDecoder, text } }
|
|
239
|
+
}
|
|
240
|
+
case 'atob':
|
|
241
|
+
return { value: atob(request.text) }
|
|
242
|
+
case 'btoa':
|
|
243
|
+
return { value: btoa(request.text) }
|
|
244
|
+
case 'random':
|
|
245
|
+
return crypto.getRandomValues(new Uint8Array(request.length)).buffer
|
|
246
|
+
case 'uuid':
|
|
247
|
+
return { value: crypto.randomUUID() }
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
call(source, buffer, extra) {
|
|
252
|
+
if (closed) throw new Error('Task completed.')
|
|
253
|
+
if (++calls > 100_000) throw new Error('Builtin call limit exceeded.')
|
|
254
|
+
if (source.length > binaryLimit) throw new Error('Builtin payload too large.')
|
|
255
|
+
try {
|
|
256
|
+
const result = serve(source, buffer, extra)
|
|
257
|
+
if (result instanceof ArrayBuffer) return result
|
|
258
|
+
const serialized = JSON.stringify(result)
|
|
259
|
+
if (serialized.length > binaryLimit) throw new Error('Builtin result too large.')
|
|
260
|
+
return serialized
|
|
261
|
+
} catch (error) {
|
|
262
|
+
const code = error instanceof Error && 'code' in error && typeof error.code == 'string' ? error.code : undefined
|
|
263
|
+
return JSON.stringify({
|
|
264
|
+
error: { name: error instanceof Error ? error.name : 'Error', message: String(error instanceof Error ? error.message : error), code },
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
close() {
|
|
269
|
+
closed = true
|
|
270
|
+
decoders.clear()
|
|
271
|
+
for (const hash of hashes.values()) hash.destroy()
|
|
272
|
+
hashes.clear()
|
|
273
|
+
},
|
|
274
|
+
}
|
|
275
|
+
}
|