Skip to content

Latest commit

 

History

55 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DenoWeave

JSR

Independent implementation of a DataWeave-compatible runtime for Deno, written from scratch in TypeScript.

🎮 Try the Live Playground!


Requirements

VS Code Setup

If you use VS Code and have the Prettier extension installed, it may conflict with Deno's formatter. To use Deno's formatter automatically on save, add the following to your .vscode/settings.json:

"[typescript]": {
  "editor.defaultFormatter": "denoland.vscode-deno"
}

Quick Start

Interactive Playground

DenoWeave includes a built-in interactive web playground that lets you write transformations, edit payloads, and see the results live in your browser.

You can try it online at denoweave.carlosxfelipe.deno.net, or run it locally:

deno task playground

This will start a local server at http://localhost:8787/. The playground features syntax highlighting, automatic evaluation, and a collection of built-in examples to help you get started.


Command Line Usage

No install needed — run directly from the source:

echo '[{"name":"alice","active":true},{"name":"bob","active":false}]' | \
  deno run --allow-read src/cli/main.ts \
  --expr 'payload filter ($.active) map ((u) -> { name: upper(u.name) })'

Output:

[{ "name": "ALICE" }]

Or use a .dwl script file:

deno run --allow-read src/cli/main.ts \
  --script transform.dwl \
  --input data.json

Or use as a library in your Deno project:

import { evaluate } from './src/mod.ts';

const result = evaluate(
  `payload.users map ((u) -> { name: upper(u.name), active: u.enabled })`,
  { payload: { users: [{ name: 'alice', enabled: true }] } },
);
// → [{ name: 'ALICE', active: true }]

Running the Examples

The project includes several practical use-cases in the examples/ folder. Run them from the project root:

  • JSON Transformation: Load and transform a static file.
    deno run --allow-read examples/json-to-json/run.ts
  • JSON to DataWeave Literal (application/dw): Serialize data into native DataWeave notation, useful for generating static configurations.
    deno run --allow-read examples/json-to-dw/run.ts
  • Deep Descendant Selector (..): Traverse deeply nested JSON to find values.
    deno run --allow-read examples/descendant-selector/run.ts
  • ISO 8601 Date Math: Explore how the native Temporal API handles leap years and periods.
    deno run --allow-read examples/date-math/run.ts
  • Module System (import): Import functions and variables from other .dwl files.
    deno run --allow-read examples/modules/run.ts
  • CSV to XML (CLI): Transform CSV to XML directly via the CLI.
    deno task cli --script examples/csv-to-xml/transform.dwl --input examples/csv-to-xml/input.csv --out xml
  • YAML Kubernetes Pods: Parse a K8s Deployment YAML and extract environment variables.
    deno run --allow-read examples/yaml-k8s-pod/run.ts
  • Error Handling: Safely process a batch with malformed records using try().
    deno run --allow-read examples/error-handling/run.ts
  • HTTP Server: Transform incoming JSON payloads in real-time.
    deno run --allow-net --allow-read examples/http-server/server.ts
  • Connectors / ETL Flow: Fetch and transform real data from an external API.
    deno run --allow-net --allow-read examples/connectors/flow.ts
  • Docs-as-Code Pipeline: Orchestrate extraction, transformation, and loading.
    deno run --allow-net --allow-read examples/pipeline/run.ts
  • NDJSON Logs Processing: Parse and filter streaming log lines.
    deno run --allow-read examples/ndjson-to-json/run.ts <<< "n"
  • Multipart Form Data: Extract files and fields from a raw HTTP upload body.
    deno run --allow-read examples/multipart-to-json/run.ts <<< "n"
  • URLEncoded Forms: Parse x-www-form-urlencoded into deeply nested JSON structures.
    deno run --allow-read examples/urlencoded-to-json/run.ts <<< "n"
  • XLSX Aggregation: Read Microsoft Excel binary sheets and perform grouping and aggregations.
    deno run --allow-read examples/xlsx-to-json/run.ts <<< "n"
  • Plain Text Splitting: Split, map and reduce unstructured text reports into objects.
    deno run --allow-read examples/text-processing/run.ts <<< "n"
  • E-commerce Order Enrichment (CLI): Enrich an order payload using vars, p() properties, expr? existence checks and custom date parsing — all features injected via CLI flags.
    deno task cli \
      --input  examples/ecommerce/input.json \
      --script examples/ecommerce/transform.dwl \
      --props  examples/ecommerce/app.properties \
      --var 'region=south-america' \
      --var 'agentId=worker-07'
  • Multi-output Report: Produce a JSON enriched list and a CSV export from the same sales payload in a single .dwl script, using conditional fields and multi-output blocks.
    deno run --allow-read examples/multi-output/run.ts

Why Deno?

Deno provides unique advantages for a lightweight data transformation engine that Node and Bun don't offer natively:

  • Run from URLs (No Install): Users can execute DenoWeave directly from a URL (e.g., deno run https://...) without needing a package.json, node_modules, or an install step.
  • Single-Binary Compilation: The built-in deno compile cross-compiles the entire engine into a standalone, zero-dependency executable natively for Linux, Mac, or Windows.
  • Strict Sandbox Security: Unlike Bun, Deno denies file, network, and environment access by default. Executing an untrusted data transformation script is guaranteed safe unless explicitly granted (e.g., --allow-read).

Supported Features

DenoWeave implements a fully-featured parser and evaluator that supports modern DataWeave 2.x syntax:

  • Core Types: Strings, Numbers, Booleans, Null, Arrays, Objects.
  • Dates & Math: Native Temporal API support for ISO 8601 Date and Period literals (|2024-02-20|, |P1D|) with full date/time math capabilities (e.g. |2024-02-28| + |P1D|). Also supports custom date parsing and formatting via as Date {format: "dd/MM/yyyy"}.
  • Modules & Namespaces: Full support for import statements (e.g. import * from dw::core::Strings, import sum from custom::Math) and encapsulated namespace resolution.
  • Operations: Arithmetic, logical with short-circuit evaluation (and, or, not), comparisons, default (default), casting (as), array/string/object concatenation (++), and range slicing (to).
  • Object Properties: Supports dynamic keys (expr): value, shorthand { name }, and conditional properties (key: value) if (cond).
  • Selectors: Deep descendant selector (..) to traverse and query deeply nested data structures, field existence operator (expr?) to check if a key exists, optional chaining (?.) for safe navigation, and non-null assertion (!) to enforce values.
  • Context & Properties: Injection of context variables (vars.region) and properties (p("store.id")) via the CLI.
  • Functions & Lambdas: Named functions (fun), single-param lambdas ((x) -> x), multi-param lambdas, anonymous lambdas ($, $$).
  • Infix Higher-Order Functions: map, filter, reduce, plus DataWeave-style infix usage of groupBy, orderBy, distinctBy, flatMap, mapObject, filterObject and pluck.
  • Variables & Types: Local variables (var), type hints (type).
  • Pattern Matching: match / case expressions including literal match, type check (case is Type), and named capture with guards (case q if q > 100).
  • Scoping: Local scope evaluation via do { ... } blocks.
  • Control Flow & Error Handling: if / else expressions and safe evaluation using try().
  • Multi-output Scripts: A single .dwl file can declare multiple output <mime> --- <body> blocks, each with its own output format. The CLI evaluates all blocks against the same input and prints each result separated by a blank line.
  • Smart Error Suggestions: When an undefined variable is referenced, the runtime computes the Levenshtein distance against all names visible in scope (locals, var declarations, and the full stdlib) and suggests the closest match — e.g. Did you mean: "payload"?.
  • Utilities: Built-in stdlib functions including advanced deep object merging via update.

Compilation / Standalone Build

You can compile DenoWeave into a single, self-contained executable binary that runs on target systems without Deno or Node.js installed.

Build for your current platform:

deno task compile

This generates the executable binary at build/denoweave (or build/denoweave.exe on Windows).

Cross-compiling for other platforms:

You can build for other target operating systems using the --target flag:

# Target Linux (x64)
deno compile --allow-read --target x86_64-unknown-linux-gnu --output build/denoweave-linux src/cli/main.ts

# Target Windows (x64)
deno compile --allow-read --target x86_64-pc-windows-msvc --output build/denoweave-win src/cli/main.ts

# Target macOS (Apple Silicon / M1/M2/M3)
deno compile --allow-read --target aarch64-apple-darwin --output build/denoweave-mac src/cli/main.ts

Running the binary:

./build/denoweave --script examples/json-to-json/transform.dwl --input examples/json-to-json/input.json

CLI

# Filter active users from a JSON file
deno task cli --input data.json \
  --expr 'payload.users filter ((u) -> u.active)'

# Transform CSV → JSON with upper() and if
printf 'name,score\nAlice,95\nBob,72' | deno task cli \
  --in csv --out json \
  --expr 'payload map ((r) -> { name: upper(r.name), pass: if (r.score >= 80) true else false })'

# CSV → XML
deno task cli --input data.csv --out xml \
  --expr 'groupBy(payload, (r) -> r.category)'

# Run DSL script from a .dwl file
deno task cli --script script.dwl --input data.json

# Multi-output script (two formats from one file)
deno task cli --script multi.dwl --input data.json

DSL Example

%dw 2.0
output application/json

fun stockStatus(qty: Number) = qty match {
    case 0            -> "OUT_OF_STOCK"
    case q if q > 100 -> "BULK"
    case q if q > 0   -> "AVAILABLE"
    else              -> "UNKNOWN"
}

---
payload.items map (item, index) -> do {
    var total = item.qty * item.price * 1.10
    ---
    {
        position: index + 1,
        product: item.name,
        status: stockStatus(item.qty),
        totalWithTax: round(total * 100) / 100
    }
}

Output:

[
  {
    "position": 1,
    "product": "Notebook",
    "status": "AVAILABLE",
    "totalWithTax": 1099.99
  },
  {
    "position": 2,
    "product": "Mouse",
    "status": "AVAILABLE",
    "totalWithTax": 65.98
  },
  {
    "position": 3,
    "product": "Keyboard",
    "status": "OUT_OF_STOCK",
    "totalWithTax": 0
  }
]

Deep Descendant Selector (..)

The .. operator allows you to recursively extract all values matching a specific key from anywhere within a deeply nested JSON structure.

%dw 2.0
output application/json
var payload = {
  company: {
    departments: [
      {
        employees: [
          { name: "Alice", role: "Engineer" },
          { name: "Bob", role: "Manager" }
        ]
      },
      {
        employees: [
          { name: "Charlie", contacts: [{ name: "Dave" }] }
        ]
      }
    ]
  }
}
---
{
  all_names: payload..name,
  roles: (payload..role) distinctBy $
}

Output:

{
  "all_names": [
    "Alice",
    "Bob",
    "Charlie",
    "Dave"
  ],
  "roles": [
    "Engineer",
    "Manager"
  ]
}

Multi-output Scripts

A single .dwl file can produce multiple outputs in different formats. Declare several output <mime> --- blocks, each with its own body expression. All blocks share the same header declarations (%dw, var, fun, type).

%dw 2.0
var items = payload.items

output application/json
---
{ total: sizeOf(items), ids: items map ($.id) }

output application/csv
---
items map ((item) -> { name: item.name, price: item.price })
deno task cli --script report.dwl --input data.json
# → {"total":3,"ids":[1,2,3]}
#
# name,price
# Widget,9.99
# Gadget,24.99
# Doohickey,4.49

Routing outputs to files

You can map specific output blocks directly to files using the --target flag (format: <block_index>:<file_path>).

deno task cli --allow-write --script report.dwl --input data.json \
  --target 1:report.json \
  --target 2:report.csv

Unmapped blocks are printed directly to the console (stdout).


Smart Error Suggestions

When an undefined variable is referenced, DenoWeave searches all names visible in the current scope (local vars, function params, and the full stdlib) and suggests the closest match using the Levenshtein algorithm:

# Script: payload.users map ((u) -> { name: u.nme })
#                                              ^^^
# DenoWeave output:
# RuntimeError: Cannot access property "nme" of null

# Script: paylaod.users map ...
# DenoWeave output:
# ReferenceError: Undefined variable: "paylaod". Did you mean: "payload"?

Suggestions only appear when the edit distance is ≤ 3 to avoid noisy false positives.


Stdlib (Selection)

Category Functions
String upper, lower, trim, split, join, replace, contains, startsWith, endsWith, padLeft, padRight
Array map, filter, reduce, groupBy, orderBy, distinctBy, pluck, first, last, sum, avg, min, max, zip, flatten
Object keys, values, entries, merge, deepMerge, mapObject, filterObject, pick, omit, has
Math abs, ceil, floor, round, sqrt, pow, log, mod
Type typeOf, isNull, isEmpty, length

Supported Formats

DenoWeave supports the same data formats as the official DataWeave playground. All adapters are implemented in pure TypeScript/Deno with zero npm dependencies.

Format MIME type Input Output Notes
JSON application/json Default format
CSV application/csv RFC 4180; configurable delimiter & quoting
XML application/xml Hand-written recursive-descent parser
YAML application/yaml Via @std/yaml (MIT)
NDJSON application/ndjson One JSON value per line
TEXT text/plain Raw string pass-through
URLENCODED application/x-www-form-urlencoded Via native URLSearchParams
MULTIPART multipart/form-data RFC 2046; auto-detects boundary
DW application/dw DataWeave literal notation (unquoted keys, |date| pipes)
XLSX application/vnd.openxmlformats-officedocument.spreadsheetml.sheet Pure TS ZIP + ECMA-376; no native binaries

Tests

The project includes a suite of more than 360 automated tests covering the lexer, parser, evaluator, stdlib, and all data adapters.

To run the tests:

deno task test

To run the tests in watch mode:

deno task test:watch

To run the linter:

deno task lint

To format the TypeScript codebase:

deno task fmt

DataWeave Formatter

DenoWeave includes a built-in, custom pretty-printer specifically designed for DataWeave (.dwl) scripts. It intelligently formats code while preserving all comments, handles nested blocks, and correctly indents if/else and lambda continuations.

To format all .dwl files in your project:

deno task fmt:dwl

To check for formatting issues without modifying files (useful in CI pipelines):

deno task fmt:dwl --check

Architecture

DSL Code
    ↓ Lexer          (src/lexer/)
Tokens
    ↓ Parser         (src/parser/)
AST
    ↓ Evaluator      (src/evaluator/)
Value
    ↓ Adapter        (src/adapters/)
JSON / CSV / XML / YAML / NDJSON / TEXT / URLENCODED / MULTIPART / DW / XLSX

Architectural Notes

This is an independent, educational, and experimental project. It serves as an accessible bridge for developers who want to learn the DataWeave language and practice data transformation concepts, potentially preparing them for future work within the official MuleSoft ecosystem.

Because of this scope, its architecture is designed for simplicity and modern web environments:

  • In-Memory Processing: DenoWeave currently loads the entire payload into memory to build its Abstract Syntax Tree (AST) and evaluate the transformation. This means it works exceptionally well for small to medium payloads but will hit V8 memory limits if you attempt to process extremely large datasets.
  • Startup Time & Edge Computing: Built on the V8 JavaScript engine, DenoWeave natively leverages V8 Isolates. This enables near-instant startup times without compilation steps, making it an excellent fit for modern Edge environments (like Deno Deploy or Cloudflare Workers) where scripts need to start and execute in milliseconds.
  • Future Evolution (Streaming & Wasm): To handle larger files in the future, the modern Deno ecosystem provides excellent native paths. The data adapters could be refactored to use the Web Streams API to process data chunk-by-chunk. Alternatively, the core evaluation engine could be ported to Rust and compiled to WebAssembly (Wasm) for near-native speeds.

VS Code Extension

Note that using the official MuleSoft DataWeave extension in VS Code may cause some noise, such as false-positive linting errors and engine incompatibilities. Because of this, I created my own lightweight syntax highlighting extension for DenoWeave:

DataWeave Syntax Extension


Disclaimer

DenoWeave is an independent, open-source project implemented from scratch in TypeScript/Deno with the sole purpose of interoperability and data compatibility. This project does not include or redistribute any proprietary code. Compatibility with many DataWeave features is a project goal, but full compatibility is not guaranteed.

MuleSoft, Anypoint Platform, and DataWeave are registered trademarks of MuleSoft, LLC, a subsidiary of Salesforce, Inc. This project has no affiliation, sponsorship, association, or endorsement of any kind with MuleSoft or Salesforce.


License

MIT

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages