Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
},
"pnpm": {
"onlyBuiltDependencies": [
"isolated-vm",
"sqlite3"
],
"overrides": {
Expand Down
16 changes: 8 additions & 8 deletions packages/@n8n/expression-runtime/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ Secure, isolated expression evaluation runtime for n8n workflows.
**In progress — landing as a series of incremental PRs.**

Implemented so far:
- ✅ TypeScript interfaces and architecture design
- ✅ Core architecture documentation
- ✅ TypeScript interfaces and architecture design (PR 1)
- ✅ Core architecture documentation (PR 1)
- ✅ Runtime bundle: extension functions, deep lazy proxy system (PR 2)
- ✅ `IsolatedVmBridge`: V8 isolate management via `isolated-vm` (PR 3)

Coming in later PRs:
- 🚧 Runtime bundle: extension functions, deep lazy proxy system (PR 2)
- 🚧 `IsolatedVmBridge`: V8 isolate management via `isolated-vm` (PR 3)
- 🚧 `ExpressionEvaluator`: tournament integration, expression code caching (PR 4)
- 🚧 Integration tests (PR 4)
- 🚧 Workflow integration behind `N8N_EXPRESSION_ENGINE=vm` flag (PR 5)
Expand Down Expand Up @@ -165,7 +165,7 @@ interface RuntimeBridge {

### Bridge Implementations

- **IsolatedVmBridge**: 🚧 For Node.js backend (isolated-vm with V8 isolates) - coming in PR 3
- **IsolatedVmBridge**: For Node.js backend (isolated-vm with V8 isolates)
- Memory isolation with hard 128MB limit
- Timeout enforcement (5s default)
- Deep lazy proxy system for workflow data
Expand All @@ -185,9 +185,9 @@ interface EvaluatorConfig {
}

interface BridgeConfig {
memoryLimit?: number; // Default: 128 MB (PR 3)
timeout?: number; // Default: 5000 ms (PR 3)
debug?: boolean; // Default: false (PR 3)
memoryLimit?: number; // Default: 128 MB
timeout?: number; // Default: 5000 ms
debug?: boolean; // Default: false
}
```

Expand Down
19 changes: 9 additions & 10 deletions packages/@n8n/expression-runtime/docs/architecture-diagram.mmd
Original file line number Diff line number Diff line change
Expand Up @@ -69,24 +69,23 @@ graph TB
sequenceDiagram
participant WF as Workflow
participant Eval as ExpressionEvaluator
participant Bridge as RuntimeBridge
participant Bridge as IsolatedVmBridge
participant Runtime as Runtime (Isolated)
participant Store as Data Store

WF->>Eval: evaluate(expr, data)
Eval->>Eval: Transform with Tournament
Eval->>Eval: Check cache
Eval->>Store: Store data with ID
Eval->>Bridge: execute(code, dataId)
Bridge->>Runtime: Run code in isolation
Eval->>Eval: Check code cache
Eval->>Bridge: execute(code, data)
Bridge->>Bridge: Register ivm.Reference callbacks with data
Bridge->>Runtime: evalSync("resetDataProxies()")
Runtime->>Runtime: Create lazy proxies for $json, $input, etc.
Bridge->>Runtime: Run compiled script

Runtime->>Runtime: Access $json.email
Runtime->>Bridge: getData(dataId, 'email')
Bridge->>Store: Lookup 'email'
Store-->>Bridge: Value
Runtime->>Bridge: __getValueAtPath(['$json','email']) [ivm.Reference]
Bridge->>Bridge: Navigate path in data
Bridge-->>Runtime: Value

Runtime-->>Bridge: Expression result
Bridge-->>Eval: Result
Eval->>Eval: Cache result
Eval-->>WF: Result
10 changes: 7 additions & 3 deletions packages/@n8n/expression-runtime/docs/deep-lazy-proxy.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ The Deep Lazy Proxy is a memory-efficient mechanism for providing workflow data

## Architecture

The deep lazy proxy is implemented entirely within `src/runtime/index.ts`, which is
bundled into `dist/bundle/runtime.iife.js` and injected into the V8 isolate at startup.
The deep lazy proxy is implemented in `src/runtime/lazy-proxy.ts`, which is bundled
together with the other runtime modules into `dist/bundle/runtime.iife.js` and injected
into the V8 isolate at startup.

Key functions exposed on `globalThis` inside the isolate:

Expand Down Expand Up @@ -223,6 +224,9 @@ When modifying the proxy implementation:

## Related Files

- Implementation: `packages/@n8n/expression-runtime/src/runtime/index.ts` — proxy system, `resetDataProxies`, `__sanitize`, `SafeObject`, `SafeError`
- Proxy implementation: `packages/@n8n/expression-runtime/src/runtime/lazy-proxy.ts` — `createDeepLazyProxy`
- Reset: `packages/@n8n/expression-runtime/src/runtime/reset.ts` — `resetDataProxies`
- Security globals: `packages/@n8n/expression-runtime/src/runtime/safe-globals.ts` — `SafeObject`, `SafeError`, `__sanitize`
- Runtime entry: `packages/@n8n/expression-runtime/src/runtime/index.ts` — wires all modules to `globalThis`
- Bridge: `packages/@n8n/expression-runtime/src/bridge/isolated-vm-bridge.ts` — registers `ivm.Reference` callbacks, loads bundle, calls `resetDataProxies`
- Build: `packages/@n8n/expression-runtime/esbuild.config.js` — bundles runtime to `dist/bundle/runtime.iife.js`
1 change: 1 addition & 0 deletions packages/@n8n/expression-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
],
"license": "SEE LICENSE IN LICENSE.md",
"dependencies": {
"isolated-vm": "^6.0.2",
"js-base64": "catalog:",
"jssha": "3.3.1",
"lodash": "catalog:",
Expand Down
Loading