chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { SandboxBroker } from '@/lib/execution/sandbox/types'
|
||||
import {
|
||||
fetchWorkspaceFileBuffer,
|
||||
getWorkspaceFile,
|
||||
} from '@/lib/uploads/contexts/workspace/workspace-file-manager'
|
||||
|
||||
const logger = createLogger('SandboxWorkspaceFileBroker')
|
||||
|
||||
interface WorkspaceFileArgs {
|
||||
fileId: string
|
||||
}
|
||||
|
||||
interface WorkspaceFileResult {
|
||||
dataUri: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Host-side broker that resolves a workspace file id into a base64 data URI.
|
||||
*
|
||||
* Exposed to isolate code through `__brokers.workspaceFile(fileId)` and wrapped
|
||||
* by the task bootstrap as `getFileBase64(fileId)`.
|
||||
*/
|
||||
export const workspaceFileBroker: SandboxBroker<WorkspaceFileArgs, WorkspaceFileResult> = {
|
||||
name: 'workspaceFile',
|
||||
async handle(ctx, args) {
|
||||
if (!args || typeof args.fileId !== 'string' || args.fileId.length === 0) {
|
||||
throw new Error('workspaceFile broker requires a non-empty fileId')
|
||||
}
|
||||
if (!ctx.workspaceId) {
|
||||
throw new Error('workspaceFile broker requires a workspaceId')
|
||||
}
|
||||
|
||||
const record = await getWorkspaceFile(ctx.workspaceId, args.fileId)
|
||||
if (!record) {
|
||||
logger.warn('Workspace file not found for sandbox broker', {
|
||||
workspaceId: ctx.workspaceId,
|
||||
fileId: args.fileId,
|
||||
})
|
||||
throw new Error(`File not found: ${args.fileId}`)
|
||||
}
|
||||
|
||||
const buffer = await fetchWorkspaceFileBuffer(record)
|
||||
const mime = record.type || 'image/png'
|
||||
return { dataUri: `data:${mime};base64,${buffer.toString('base64')}` }
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Minimal isolate-side shim run at the top of every bundle entry.
|
||||
*
|
||||
* Must execute BEFORE `process/browser` because that shim captures
|
||||
* `setTimeout` at module-init time. Timers themselves are installed by
|
||||
* `isolated-vm-worker.cjs` (delegated to Node's real timers via
|
||||
* `ivm.Reference` per laverdet/isolated-vm#136) BEFORE the bundle runs, so
|
||||
* `process/browser` picks up the real delegated `setTimeout`.
|
||||
*
|
||||
* The only thing this file still does is alias `global -> globalThis` for
|
||||
* UMD-style fallbacks inside the bundles. All other runtime surface
|
||||
* (`console`, `TextEncoder`, `TextDecoder`, timers) is installed by the
|
||||
* worker via `ivm.Callback` / `ivm.Reference` bridges to Node's native
|
||||
* implementations — no hand-rolled polyfill logic lives in the isolate.
|
||||
*/
|
||||
|
||||
const g: typeof globalThis & { global?: typeof globalThis } = globalThis
|
||||
|
||||
if (typeof g.global === 'undefined') g.global = globalThis
|
||||
|
||||
export {}
|
||||
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env bun
|
||||
/**
|
||||
* Builds isolate-compatible bundles for the document-generation libraries.
|
||||
*
|
||||
* Each library is bundled with `target=browser, format=iife` so it can be
|
||||
* evaluated inside a V8 isolate that has no Node APIs (`require`, `process`,
|
||||
* `fs`). The emitted files attach their exports to `globalThis.__bundles[name]`
|
||||
* and are checked in so production images don't need the bundler at runtime.
|
||||
*
|
||||
* Run via: `bun run build:sandbox-bundles`.
|
||||
*/
|
||||
|
||||
import { mkdirSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { createLogger } from '@sim/logger'
|
||||
|
||||
const logger = createLogger('SandboxBundleBuild')
|
||||
|
||||
interface BunBuildResult {
|
||||
success: boolean
|
||||
logs: unknown[]
|
||||
outputs: Array<{ text: () => Promise<string> }>
|
||||
}
|
||||
interface BunBuildOptions {
|
||||
entrypoints: string[]
|
||||
target: string
|
||||
format: string
|
||||
minify: boolean
|
||||
sourcemap: string
|
||||
root: string
|
||||
}
|
||||
declare const Bun: { build: (opts: BunBuildOptions) => Promise<BunBuildResult> }
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url))
|
||||
const BUNDLES_DIR = HERE
|
||||
const ENTRIES_DIR = join(HERE, '.entries')
|
||||
const APP_SIM_ROOT = join(HERE, '..', '..', '..', '..')
|
||||
|
||||
interface BundleSpec {
|
||||
/** Key on `globalThis.__bundles`. */
|
||||
name: string
|
||||
/** Short filename written under `bundles/<file>.cjs`. */
|
||||
outFile: string
|
||||
/** Source of the entry file bun will bundle. */
|
||||
entry: string
|
||||
}
|
||||
|
||||
const POLYFILLS_PATH = join(HERE, '_polyfills.ts')
|
||||
const POLYFILL_PRELUDE = `
|
||||
// Isolate-side polyfills must execute BEFORE any other import (process/browser
|
||||
// captures setTimeout at module-init time). Keep this as the first import.
|
||||
import '${POLYFILLS_PATH}'
|
||||
import { Buffer as __BufferPolyfill } from 'buffer'
|
||||
import * as __processPolyfill from 'process/browser'
|
||||
if (typeof globalThis.Buffer === 'undefined') globalThis.Buffer = __BufferPolyfill
|
||||
if (typeof globalThis.process === 'undefined') globalThis.process = __processPolyfill
|
||||
`
|
||||
|
||||
const BUNDLES: ReadonlyArray<BundleSpec> = [
|
||||
{
|
||||
name: 'pdf-lib',
|
||||
outFile: 'pdf-lib.cjs',
|
||||
entry: `
|
||||
${POLYFILL_PRELUDE}
|
||||
import * as mod from 'pdf-lib'
|
||||
globalThis.__bundles = globalThis.__bundles || {}
|
||||
globalThis.__bundles['pdf-lib'] = mod
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: 'docx',
|
||||
outFile: 'docx.cjs',
|
||||
entry: `
|
||||
${POLYFILL_PRELUDE}
|
||||
import * as mod from 'docx'
|
||||
globalThis.__bundles = globalThis.__bundles || {}
|
||||
globalThis.__bundles['docx'] = mod
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: 'pptxgenjs',
|
||||
outFile: 'pptxgenjs.cjs',
|
||||
entry: `
|
||||
${POLYFILL_PRELUDE}
|
||||
import PptxGenJS from 'pptxgenjs'
|
||||
globalThis.__bundles = globalThis.__bundles || {}
|
||||
globalThis.__bundles['pptxgenjs'] = PptxGenJS
|
||||
`,
|
||||
},
|
||||
]
|
||||
|
||||
async function main(): Promise<void> {
|
||||
rmSync(ENTRIES_DIR, { recursive: true, force: true })
|
||||
mkdirSync(ENTRIES_DIR, { recursive: true })
|
||||
mkdirSync(BUNDLES_DIR, { recursive: true })
|
||||
|
||||
for (const spec of BUNDLES) {
|
||||
const entryPath = join(ENTRIES_DIR, `${spec.name}.entry.ts`)
|
||||
writeFileSync(entryPath, spec.entry, 'utf-8')
|
||||
|
||||
const result = await Bun.build({
|
||||
entrypoints: [entryPath],
|
||||
target: 'browser',
|
||||
format: 'iife',
|
||||
minify: true,
|
||||
sourcemap: 'none',
|
||||
root: APP_SIM_ROOT,
|
||||
})
|
||||
|
||||
if (!result.success) {
|
||||
for (const log of result.logs) {
|
||||
logger.error(String(log))
|
||||
}
|
||||
throw new Error(`Failed to build sandbox bundle: ${spec.name}`)
|
||||
}
|
||||
|
||||
if (result.outputs.length === 0) {
|
||||
throw new Error(`No output produced for sandbox bundle: ${spec.name}`)
|
||||
}
|
||||
|
||||
const code = await result.outputs[0].text()
|
||||
const banner = `// sandbox bundle: ${spec.name}\n// generated by apps/sim/lib/execution/sandbox/bundles/build.ts\n// do not edit by hand. run \`bun run build:sandbox-bundles\` to regenerate.\n`
|
||||
writeFileSync(join(BUNDLES_DIR, spec.outFile), banner + code, 'utf-8')
|
||||
logger.info(`built ${spec.outFile} (${code.length.toLocaleString()} chars)`)
|
||||
}
|
||||
|
||||
rmSync(ENTRIES_DIR, { recursive: true, force: true })
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
logger.error('sandbox bundle build failed', err)
|
||||
process.exit(1)
|
||||
})
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,27 @@
|
||||
import type { SandboxTask, SandboxTaskInput } from '@/lib/execution/sandbox/types'
|
||||
|
||||
/**
|
||||
* Helper that preserves the task's input type through declaration.
|
||||
* Mirrors the `task(...)` / `defineConfig(...)` pattern used elsewhere in the
|
||||
* codebase so sandbox tasks look familiar next to trigger.dev tasks.
|
||||
*/
|
||||
export function defineSandboxTask<TInput extends SandboxTaskInput = SandboxTaskInput>(
|
||||
task: SandboxTask<TInput>
|
||||
): SandboxTask<TInput> {
|
||||
if (!task.id || !/^[a-z][a-z0-9-]*$/.test(task.id)) {
|
||||
throw new Error(`Sandbox task id must be kebab-case: got "${task.id}"`)
|
||||
}
|
||||
const brokerNames = new Set<string>()
|
||||
for (const broker of task.brokers) {
|
||||
if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(broker.name)) {
|
||||
throw new Error(
|
||||
`Sandbox broker name must be a valid JS identifier: got "${broker.name}" on task "${task.id}"`
|
||||
)
|
||||
}
|
||||
if (brokerNames.has(broker.name)) {
|
||||
throw new Error(`Duplicate broker name "${broker.name}" on task "${task.id}"`)
|
||||
}
|
||||
brokerNames.add(broker.name)
|
||||
}
|
||||
return task
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { generateShortId } from '@sim/utils/id'
|
||||
import {
|
||||
executeInIsolatedVM,
|
||||
type IsolatedVMBrokerHandler,
|
||||
type IsolatedVMExecutionRequest,
|
||||
} from '@/lib/execution/isolated-vm'
|
||||
import type { SandboxBrokerContext, SandboxTaskInput } from '@/lib/execution/sandbox/types'
|
||||
import { getSandboxTask, type SandboxTaskId } from '@/sandbox-tasks/registry'
|
||||
|
||||
const logger = createLogger('SandboxRunTask')
|
||||
|
||||
export interface RunSandboxTaskOptions {
|
||||
/**
|
||||
* Owner key used by the isolated-vm pool for fairness + distributed leases.
|
||||
* Typically `user:<userId>` or `workspace:<workspaceId>`.
|
||||
*/
|
||||
ownerKey?: string
|
||||
/** Optional AbortSignal to cancel the execution early. */
|
||||
signal?: AbortSignal
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when the sandbox failure is attributable to the caller — user code
|
||||
* errors (SyntaxError, ReferenceError, user-thrown exceptions), timeouts from
|
||||
* user code, client aborts, or per-owner rate limits. Callers should translate
|
||||
* this into a 4xx response so genuine 5xx remains a signal of server health.
|
||||
*
|
||||
* System-origin failures (worker crash, IPC failure, pool saturation, task
|
||||
* misconfig) are tagged with `isSystemError` at the isolated-vm layer and
|
||||
* surface as a plain `Error` → 500.
|
||||
*/
|
||||
export class SandboxUserCodeError extends Error {
|
||||
constructor(message: string, name: string, stack?: string) {
|
||||
super(message)
|
||||
this.name = name || 'SandboxUserCodeError'
|
||||
if (stack) this.stack = stack
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a sandbox task inside the shared isolated-vm pool and returns the
|
||||
* binary result buffer. Throws with a human-readable message if the task fails
|
||||
* so callers can propagate the error verbatim to UI.
|
||||
*/
|
||||
export async function runSandboxTask<TInput extends SandboxTaskInput>(
|
||||
taskId: SandboxTaskId,
|
||||
input: TInput,
|
||||
options: RunSandboxTaskOptions = {}
|
||||
): Promise<Buffer> {
|
||||
const task = getSandboxTask(taskId)
|
||||
const requestId = generateShortId(12)
|
||||
|
||||
const brokerContext: SandboxBrokerContext = {
|
||||
workspaceId: input.workspaceId,
|
||||
requestId,
|
||||
}
|
||||
const brokers: Record<string, IsolatedVMBrokerHandler> = {}
|
||||
for (const broker of task.brokers) {
|
||||
brokers[broker.name] = (args) => broker.handle(brokerContext, args)
|
||||
}
|
||||
|
||||
const request: IsolatedVMExecutionRequest = {
|
||||
code: input.code,
|
||||
params: {},
|
||||
envVars: {},
|
||||
contextVariables: {},
|
||||
timeoutMs: task.timeoutMs,
|
||||
requestId,
|
||||
ownerKey: options.ownerKey,
|
||||
ownerWeight: 1,
|
||||
task: {
|
||||
id: task.id,
|
||||
bundles: [...task.bundles],
|
||||
bootstrap: task.bootstrap,
|
||||
brokers: task.brokers.map((b) => b.name),
|
||||
finalize: task.finalize,
|
||||
},
|
||||
}
|
||||
|
||||
const start = Date.now()
|
||||
const result = await executeInIsolatedVM(request, { brokers, signal: options.signal })
|
||||
const elapsedMs = Date.now() - start
|
||||
|
||||
// Phase timings come from the worker (see executeTask). `queue` is the
|
||||
// gap between client call and worker-side start — useful for diagnosing
|
||||
// pool saturation vs. isolate-internal slowness.
|
||||
const queueMs = result.timings ? Math.max(0, elapsedMs - result.timings.total) : undefined
|
||||
|
||||
if (result.error) {
|
||||
const isSystemError = result.error.isSystemError === true
|
||||
const logFn = isSystemError ? logger.error.bind(logger) : logger.warn.bind(logger)
|
||||
logFn('Sandbox task failed', {
|
||||
taskId,
|
||||
requestId,
|
||||
workspaceId: input.workspaceId,
|
||||
elapsedMs,
|
||||
queueMs,
|
||||
timings: result.timings,
|
||||
error: result.error.message,
|
||||
errorName: result.error.name,
|
||||
isSystemError,
|
||||
})
|
||||
if (isSystemError) {
|
||||
const err = new Error(result.error.message)
|
||||
err.name = result.error.name || 'SandboxSystemError'
|
||||
if (result.error.stack) err.stack = result.error.stack
|
||||
throw err
|
||||
}
|
||||
throw new SandboxUserCodeError(
|
||||
result.error.message,
|
||||
result.error.name || 'SandboxTaskError',
|
||||
result.error.stack
|
||||
)
|
||||
}
|
||||
|
||||
if (typeof result.bytesBase64 !== 'string' || result.bytesBase64.length === 0) {
|
||||
logger.error('Sandbox task returned no bytes', {
|
||||
taskId,
|
||||
requestId,
|
||||
workspaceId: input.workspaceId,
|
||||
timings: result.timings,
|
||||
})
|
||||
throw new Error(`Sandbox task "${taskId}" finalize did not return any bytes`)
|
||||
}
|
||||
|
||||
const bytes = Buffer.from(result.bytesBase64, 'base64')
|
||||
logger.info('Sandbox task completed', {
|
||||
taskId,
|
||||
requestId,
|
||||
workspaceId: input.workspaceId,
|
||||
elapsedMs,
|
||||
queueMs,
|
||||
timings: result.timings,
|
||||
bytes: bytes.length,
|
||||
})
|
||||
return task.toResult(new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength), input)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Types for the sandbox task system.
|
||||
*
|
||||
* A `SandboxTask` is a recipe that tells the isolated-vm pool how to run a
|
||||
* particular kind of user code: which pre-built library bundles to install,
|
||||
* which host-side brokers to expose, and how to serialize the final result.
|
||||
*/
|
||||
|
||||
export type SandboxBundleName = 'pptxgenjs' | 'docx' | 'pdf-lib'
|
||||
|
||||
export interface SandboxBroker<TArgs = unknown, TResult = unknown> {
|
||||
/**
|
||||
* Name the isolate-side bootstrap references (e.g. `__brokers.workspaceFile`).
|
||||
* Must be a plain JS identifier segment.
|
||||
*/
|
||||
name: string
|
||||
/**
|
||||
* Host-side handler invoked when the isolate calls the broker.
|
||||
* `ctx` carries per-execution metadata (workspaceId, requestId, etc.).
|
||||
*/
|
||||
handle(ctx: SandboxBrokerContext, args: TArgs): Promise<TResult>
|
||||
}
|
||||
|
||||
export interface SandboxBrokerContext {
|
||||
workspaceId: string
|
||||
requestId: string
|
||||
}
|
||||
|
||||
export interface SandboxTaskInput {
|
||||
workspaceId: string
|
||||
code: string
|
||||
}
|
||||
|
||||
export interface SandboxTask<TInput extends SandboxTaskInput = SandboxTaskInput> {
|
||||
/** Kebab-case stable identifier, used for logging + lookups. */
|
||||
id: string
|
||||
/** Script execution timeout inside the isolate. */
|
||||
timeoutMs: number
|
||||
/** Library bundles to load as isolate globals before the bootstrap runs. */
|
||||
bundles: ReadonlyArray<SandboxBundleName>
|
||||
/** Host-side brokers this task is allowed to call from inside the isolate. */
|
||||
brokers: ReadonlyArray<SandboxBroker>
|
||||
/**
|
||||
* JS code run inside the isolate after bundles are installed and before
|
||||
* user code. Should hoist bundle globals to friendly names and install any
|
||||
* helper functions users expect (e.g. `getFileBase64`).
|
||||
*/
|
||||
bootstrap: string
|
||||
/**
|
||||
* JS source that, when evaluated inside an async IIFE after user code, must
|
||||
* return a `Uint8Array`. The bytes are transferred out via `ExternalCopy`.
|
||||
*/
|
||||
finalize: string
|
||||
/** Host-side transform from raw isolate bytes to the caller's return type. */
|
||||
toResult(bytes: Uint8Array, input: TInput): Buffer
|
||||
}
|
||||
Reference in New Issue
Block a user