Custom Adapters
Custom adapters are JavaScript functions that run in sandboxed V8 isolates. Use them when the declarative mapping DSL isn’t enough — XML parsing, HMAC signatures, complex business logic, etc.
What adapters are
Section titled “What adapters are”An adapter is a JavaScript function that transforms data. Five adapter types cover different stages of the pipeline:
| Type | Purpose | Example |
|---|---|---|
request_transform | Transform outbound request payload | Convert JSON to XML |
response_transform | Transform inbound response | Parse XML response to JSON |
auth | Sign or authenticate requests | Add HMAC signature header |
enrichment | Enrich source data before mapping | Fetch additional data, compute fields |
full | Full request/response lifecycle | Complete custom protocol handling |
AI-assisted generation
Section titled “AI-assisted generation”Generate adapter code from a natural language description:
Via CLI
Section titled “Via CLI”fyrn adapters generate "Convert XML order to JSON format"fyrn adapters generate "Add HMAC signature header" --type authfyrn adapters generate "Flatten nested address fields" --sample '{"address":{"street":"123 Main"}}'The AI generates JavaScript code, runs it against sample input (if provided), and shows the results. You review, name, and save.
Via MCP
Section titled “Via MCP”{ "name": "create_adapter", "arguments": { "name": "shopify-order-transform", "description": "Transform Shopify order webhook payload to NetSuite SalesOrder format", "adapter_type": "request_transform", "sample_input": { "id": 12345, "line_items": [{"sku": "WIDGET-A", "quantity": 2, "price": "10.00"}] } }}Writing adapters manually
Section titled “Writing adapters manually”Provide JavaScript code directly:
{ "name": "create_adapter", "arguments": { "name": "add-timestamp", "description": "Adds a processed_at timestamp to every record", "adapter_type": "enrichment", "code": "export default function transform(input) { return { ...input, processed_at: new Date().toISOString() }; }" }}Adapter function signature
Section titled “Adapter function signature”export default function transform(input) { // input: the payload object // return: the transformed payload return { ...input, processed_at: new Date().toISOString() };}Available helpers
Section titled “Available helpers”The following helper modules are injected into the isolate and available to your adapter code:
| Helper | Import | Description |
|---|---|---|
xml | import { parse, build } from 'fyrn:xml' | Parse XML strings to JSON objects and build XML from JSON. Based on fast-xml-parser. |
crypto | import { hmac, hash, uuid } from 'fyrn:crypto' | HMAC-SHA256/SHA512, SHA-256/SHA-512 hashing, and UUID v4 generation. |
base64 | import { encode, decode } from 'fyrn:base64' | Base64 encode/decode strings and binary data. |
csv | import { parse, stringify } from 'fyrn:csv' | Parse CSV strings to arrays of objects and stringify back. |
date | import { format, parse, diff } from 'fyrn:date' | Date formatting (ISO 8601, Unix timestamps), parsing, and diff computation. |
Standard JavaScript globals are available: JSON, Date, Math, Map, Set, Array, Object, String, RegExp, Promise, parseInt, parseFloat, encodeURIComponent, decodeURIComponent, TextEncoder, TextDecoder.
Not available: fetch, XMLHttpRequest, require, process, eval, Function constructor, setTimeout, setInterval. Adapters are pure data transforms — side effects are handled by the runtime.
Sandboxed execution
Section titled “Sandboxed execution”Adapters run in isolated V8 isolates (via isolated-vm):
Security constraints:
- No network access. Adapters cannot make HTTP requests, open sockets, or communicate externally. All I/O is handled by the fyrn runtime outside the isolate.
- No filesystem access. No
fs,path, or any file system APIs. Adapters operate only on the data passed in. - Memory limit: 128 MB. The isolate is terminated if it exceeds this threshold. This prevents unbounded allocations from affecting other flows.
- Execution timeout: 5 seconds. Long-running or infinite loops are killed. Adapter logic should be fast — if you need heavy computation, break it into smaller steps.
- No dynamic code execution.
eval(),new Function(), andimport()are disabled. All code must be statically defined. - No ambient globals.
process,globalThis.fetch,require, and Node.js built-ins are not available.
What is injected:
The runtime injects the fyrn:* helper modules (see above) and the standard JavaScript built-ins (JSON, Date, Math, etc.). Your adapter receives a plain object as input and must return a plain object — no classes, streams, or callbacks.
Testing adapters
Section titled “Testing adapters”Via CLI
Section titled “Via CLI”fyrn adapters test <adapter-id> --input '{"order_id": "12345"}'Via MCP
Section titled “Via MCP”{ "name": "test_adapter", "arguments": { "adapter_id": "...", "input": {"order_id": "ORD-123", "line_items": [{"sku": "A", "qty": 2}]} }}Test results show the output payload and any errors.
Managing adapters
Section titled “Managing adapters”# List all adaptersfyrn adapters list
# View pre-built templatesfyrn adapters templatesUpdate via MCP
Section titled “Update via MCP”{ "name": "update_adapter", "arguments": { "adapter_id": "...", "code": "export default function transform(input) { return { ...input, version: 2 }; }", "description": "Updated to add version field" }}Common patterns
Section titled “Common patterns”XML to JSON
Section titled “XML to JSON”import { parse } from 'fyrn:xml';
export default function transform(input) { // input.body contains the raw XML string from the source system const parsed = parse(input.body, { ignoreAttributes: false, // preserve XML attributes attributeNamePrefix: '@_', // prefix attributes with @_ });
// Extract order data from the parsed XML structure const order = parsed.OrderResponse.Order;
return { order_id: order.OrderID, status: order['@_status'], items: Array.isArray(order.LineItem) ? order.LineItem.map(li => ({ sku: li.SKU, quantity: Number(li.Quantity), price: Number(li.UnitPrice), })) : [{ sku: order.LineItem.SKU, quantity: Number(order.LineItem.Quantity), price: Number(order.LineItem.UnitPrice), }], };}HMAC request signing
Section titled “HMAC request signing”import { hmac } from 'fyrn:crypto';
export default function transform(input) { const { headers, body, secrets } = input;
// Compute HMAC-SHA256 over the raw request body const timestamp = new Date().toISOString(); const payload = `${timestamp}.${JSON.stringify(body)}`; const signature = hmac('sha256', secrets.signing_key, payload);
return { ...input, headers: { ...headers, 'X-Signature': signature, 'X-Timestamp': timestamp, }, };}The secrets object is injected by the runtime from the flow’s credential store. Secrets are never logged or persisted in adapter output.
Data enrichment
Section titled “Data enrichment”export default function transform(input) { const { line_items, customer } = input;
// Compute order totals const subtotal = line_items.reduce( (sum, item) => sum + item.quantity * item.unit_price, 0 ); const tax = subtotal * 0.08; const total = subtotal + tax;
// Derive customer tier from lifetime spend const tier = customer.lifetime_spend > 10000 ? 'enterprise' : customer.lifetime_spend > 1000 ? 'professional' : 'starter';
return { ...input, subtotal: Number(subtotal.toFixed(2)), tax: Number(tax.toFixed(2)), total: Number(total.toFixed(2)), customer: { ...customer, computed_tier: tier, }, };}What’s next
Section titled “What’s next”- Creating Flows — Use adapters in your flows
- CLI Usage — All
fyrn adapterscommands - MCP Tools —
create_adapter,test_adapter,update_adapter