@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.
Files changed (139) hide show
  1. package/README.md +3 -1
  2. package/dist/browser/{ContentIcon-CUQHjQIw.js → ContentIcon-Cm1hbVzW.js} +4 -3
  3. package/dist/browser/{FlowCanvas-Dtf8_cQp.js → FlowCanvas-NzKtla_c.js} +62 -62
  4. package/dist/browser/{createResourceDialog-DI9pL-Yr.js → createResourceDialog-BgdedQqu.js} +3 -3
  5. package/dist/browser/{dialog-BYJGvItc.js → dialog-aSElFFw2.js} +322 -26
  6. package/dist/browser/{exportSvg-BOoy7-Db.js → exportSvg-Dcz4HTo_.js} +2 -2
  7. package/dist/browser/{flowChanges-RL-H4CjD.js → flowChanges-NZyS7HSp.js} +490 -438
  8. package/dist/browser/{flowWorkspace-BkDsETj0.js → flowWorkspace-Bl0SDehy.js} +499 -61
  9. package/dist/browser/preview.css +696 -825
  10. package/dist/browser/preview.js +1399 -717
  11. package/dist/browser/theme.css +114 -262
  12. package/dist/browser/{tooltip-wZJRB9oP.js → tooltip-3XMK9jkr.js} +1 -1
  13. package/dist/browser/{typeScriptSession-CSRwWmZm.js → typeScriptSession-C9sBGOKZ.js} +1 -1
  14. package/dist/browser/ui.css +354 -460
  15. package/dist/browser/ui.js +1 -1
  16. package/dist/browser/workbench.css +696 -825
  17. package/dist/browser/workbench.js +13 -7
  18. package/dist/browser/{workbenchSelect-BcWPWGzw.js → workbenchSelect-DBGP7Y4Z.js} +2 -2
  19. package/dist/command/index.js +41 -43
  20. package/dist/common/authoring.d.ts +5 -5
  21. package/dist/common/authoringExamples.d.ts +3 -2
  22. package/dist/common/authoringSchema.d.ts +70 -4
  23. package/dist/common/connector-proxy.d.ts +1 -1
  24. package/dist/common/control-api-conformance.js +250 -138
  25. package/dist/common/control-api.d.ts +3 -2
  26. package/dist/common/control-api.js +246 -134
  27. package/dist/common/control-requests.d.ts +3 -3
  28. package/dist/common/control-requests.js +353 -79
  29. package/dist/common/cron-trigger.d.ts +2 -1
  30. package/dist/common/decision.d.ts +4 -4
  31. package/dist/common/decision.js +5 -2
  32. package/dist/common/engine-contract.d.ts +6 -1
  33. package/dist/common/event-source-definition.d.ts +1 -1
  34. package/dist/common/event-source-view.d.ts +22 -0
  35. package/dist/common/event-source.d.ts +1 -1
  36. package/dist/common/event-source.js +24 -65
  37. package/dist/common/event-sources.d.ts +0 -21
  38. package/dist/common/flow-authoring.js +90 -19
  39. package/dist/common/flow-change-schema.d.ts +9 -1
  40. package/dist/common/flow-change.d.ts +7 -201
  41. package/dist/common/flow-change.js +96 -23
  42. package/dist/common/flow-encoding.js +89 -21
  43. package/dist/common/flow-graph.d.ts +2 -1
  44. package/dist/common/flow-input-value.d.ts +1 -1
  45. package/dist/common/flow-model.d.ts +121 -0
  46. package/dist/common/flow-notifications.d.ts +1 -1
  47. package/dist/common/flow-retry.d.ts +3 -5
  48. package/dist/common/flow-semantics.js +816 -586
  49. package/dist/common/flow-workflowReferences.d.ts +8 -0
  50. package/dist/common/flowInspection.d.ts +1 -1
  51. package/dist/common/http-hosts.d.ts +6 -0
  52. package/dist/common/http-request.d.ts +164 -0
  53. package/dist/common/http-request.js +5272 -0
  54. package/dist/common/integration-trigger.d.ts +2 -1
  55. package/dist/common/mcp.d.ts +3 -3
  56. package/dist/common/mcp.js +248 -136
  57. package/dist/common/openapi.d.ts +4 -4
  58. package/dist/common/openapi.js +4 -1
  59. package/dist/common/poll-trigger.d.ts +2 -1
  60. package/dist/common/run-events.js +11 -9
  61. package/dist/common/runtime-contract.d.ts +23 -1
  62. package/dist/common/runtime-contract.js +607 -72
  63. package/dist/common/runtime-nodejs-compat.d.ts +44 -0
  64. package/dist/common/runtime-nodejs-compat.js +5870 -0
  65. package/dist/common/scheduler-checkpoint.d.ts +1 -1
  66. package/dist/common/scheduler-node-execution.d.ts +1 -1
  67. package/dist/common/scheduler.d.ts +2 -2
  68. package/dist/common/scheduler.js +95 -25
  69. package/dist/common/trigger-catalog.d.ts +1 -7
  70. package/dist/common/trigger-config-options.d.ts +1 -1
  71. package/dist/common/trigger-config.d.ts +1 -1
  72. package/dist/common/trigger-contract.d.ts +2 -1
  73. package/dist/common/trigger-localization.d.ts +8 -2
  74. package/dist/common/trigger-model.d.ts +101 -0
  75. package/dist/common/webhook-response.d.ts +1 -3
  76. package/dist/common/webhook-trigger.d.ts +3 -1
  77. package/dist/common/webhook-trigger.js +12 -11
  78. package/dist/common/workflow.d.ts +3 -12
  79. package/dist/common/workflow.js +25 -23
  80. package/dist/index.d.ts +41 -14
  81. package/package.json +9 -1
  82. package/skills/open-flow/references/flow-authoring.md +1 -0
  83. package/skills/open-flow/references/flow-n8n-conversion.md +19 -15
  84. package/src/base/common/json.ts +1 -1
  85. package/src/connector/common/proxy.ts +1 -1
  86. package/src/control/common/api.ts +7 -5
  87. package/src/control/common/authoring.ts +14 -3
  88. package/src/control/common/authoringExamples.ts +8 -0
  89. package/src/control/common/authoringSchema.ts +38 -25
  90. package/src/control/common/authoringTools.ts +1 -1
  91. package/src/control/common/conformance.ts +1 -1
  92. package/src/control/common/connectorDecoders.ts +1 -1
  93. package/src/control/common/eventSources.ts +2 -29
  94. package/src/control/common/triggerCatalog.ts +1 -8
  95. package/src/decision/common/decision.ts +15 -7
  96. package/src/event-source/common/definition.ts +1 -1
  97. package/src/event-source/common/index.ts +1 -1
  98. package/src/event-source/common/view.ts +20 -0
  99. package/src/execution/common/checkpoint.ts +1 -1
  100. package/src/execution/common/events.ts +3 -0
  101. package/src/execution/common/node-execution.ts +5 -3
  102. package/src/execution/common/scheduler.ts +2 -1
  103. package/src/flow/common/change.ts +59 -192
  104. package/src/flow/common/changeSchema.ts +39 -5
  105. package/src/flow/common/connectionUsage.ts +1 -1
  106. package/src/flow/common/graph.ts +10 -4
  107. package/src/flow/common/inputValue.ts +1 -1
  108. package/src/flow/common/model.ts +130 -0
  109. package/src/flow/common/modules.ts +1 -1
  110. package/src/flow/common/portChanges.ts +1 -1
  111. package/src/flow/common/retry.ts +3 -5
  112. package/src/flow/common/semantics.ts +71 -7
  113. package/src/flow/common/webhookResponse.ts +4 -8
  114. package/src/flow/common/workflowReferences.ts +31 -0
  115. package/src/http/common/authBindings.ts +9 -0
  116. package/src/http/common/headers.ts +5 -0
  117. package/src/http/common/hosts.ts +13 -0
  118. package/src/http/common/httpRequest.ts +266 -0
  119. package/src/openapi/common/openapi.ts +9 -5
  120. package/src/{execution → runtime}/common/engineContract.ts +24 -15
  121. package/src/runtime/common/nodejsCompat.ts +275 -0
  122. package/src/runtime/common/nodejsGuest.generated.json +47 -0
  123. package/src/runtime/common/runtime.ts +1138 -0
  124. package/src/trigger/common/config.ts +1 -1
  125. package/src/trigger/common/configOptions.ts +1 -1
  126. package/src/trigger/common/contract.ts +2 -1
  127. package/src/trigger/common/cron.ts +2 -1
  128. package/src/trigger/common/integration.ts +2 -1
  129. package/src/trigger/common/model.ts +87 -0
  130. package/src/trigger/common/poll.ts +2 -1
  131. package/src/trigger/common/webhook.ts +7 -4
  132. package/src/trigger/providers/fieldDescriptions.ts +1 -1
  133. package/src/trigger/providers/localization.ts +9 -2
  134. package/src/trigger/providers/remote.ts +1 -1
  135. package/src/types/index.ts +44 -15
  136. package/src/workflow/common/workflow.ts +3 -44
  137. package/src/execution/common/runtime.ts +0 -563
  138. package/src/openapi/common/authBindings.ts +0 -12
  139. /package/src/{control → connector}/common/providerIconSprite.ts +0 -0
@@ -1,4 +1,4 @@
1
- import type { InputPort, JsonValue, ManagedTaskDefinition, Port } from '../../flow/common/change.ts'
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'): ManagedTaskDefinition {
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: ManagedTaskDefinition): OpenApiFieldIssue[] {
322
- if (task.executor.kind != 'openapi') return []
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: ManagedTaskDefinition): string[] {
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
- export const currentEngineContract = 'open-flow-engine/v8'
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 currentContract: EngineContract = {
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: `export const engineContract = ${JSON.stringify(currentEngineContract)}
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
- ...currentContract,
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: `export const engineContract = ${JSON.stringify(nodejsEngineContract)}
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 : currentContract),
74
+ ...(contract.endsWith('/nodejs-compat-v1') ? nodejsContract : v8Contract),
66
75
  workflowTools: false,
67
- platformSource: `export const engineContract = ${JSON.stringify(contract)}\nexport function identity(value) { return value }\n`,
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' ? currentContract : nodejsContract),
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: `export const engineContract = ${JSON.stringify(contract)}
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
+ }