diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6cc913e..56197e6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: # Runs a single command using the runners shell - name: Run Build (Latest) run: | - ./gradlew --stacktrace --no-problems-report -PweaveVersion=2.11.0-SNAPSHOT -PweaveTestSuiteVersion=2.11.0-SNAPSHOT -PweaveSuiteVersion=2.11.0-SNAPSHOT build + ./gradlew --stacktrace --no-problems-report -PskipNodeTests=true -PweaveVersion=2.11.0-SNAPSHOT -PweaveTestSuiteVersion=2.11.0-SNAPSHOT -PweaveSuiteVersion=2.11.0-SNAPSHOT build shell: bash # Generate distro @@ -45,9 +45,60 @@ jobs: run: ./gradlew --stacktrace --no-problems-report -PweaveVersion=2.11.0-SNAPSHOT -PweaveTestSuiteVersion=2.11.0-SNAPSHOT -PweaveSuiteVersion=2.11.0-SNAPSHOT native-cli:distro shell: bash + # Install Python build dependencies (setuptools/wheel may be missing on Windows runners) + - name: Install Python build dependencies + run: python3 -m pip install --upgrade setuptools wheel + shell: bash + + # Generate native-lib python wheel + - name: Create Native Lib Python Wheel + run: ./gradlew --stacktrace --no-problems-report -PweaveVersion=2.11.0-SNAPSHOT native-lib:buildPythonWheel + shell: bash + + # Setup Node.js for native-lib Node package + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + # Stage the native lib and build Node package (npm install, node-gyp, tsc, npm pack) + - name: Create Native Lib Node Package + run: ./gradlew --stacktrace --no-problems-report -PweaveVersion=2.11.0-SNAPSHOT native-lib:buildNodePackage + shell: bash + + # Run Node.js tests + - name: Run Node.js Tests + run: ./gradlew --stacktrace --no-problems-report -PweaveVersion=2.11.0-SNAPSHOT native-lib:nodeTest + shell: bash + # Upload the artifact file - name: Upload generated script uses: actions/upload-artifact@v4 with: name: dw-${{env.NATIVE_VERSION}}-${{runner.os}} path: native-cli/build/distributions/native-cli-${{env.NATIVE_VERSION}}-native-distro-${{ matrix.script_name }}.zip + + # Upload the Python wheel + - name: Upload Python wheel + uses: actions/upload-artifact@v4 + with: + name: dw-python-wheel-${{env.NATIVE_VERSION}}-${{runner.os}} + path: native-lib/python/dist/dataweave_native-0.0.1-py3-*.whl + + # Upload the Node.js package + - name: Upload Node package + uses: actions/upload-artifact@v4 + with: + name: dw-node-package-${{env.NATIVE_VERSION}}-${{runner.os}} + path: native-lib/node/dataweave-native-0.0.1.tgz + + # Upload the native shared library + header together per OS + - name: Upload native shared library + uses: actions/upload-artifact@v4 + with: + name: dwlib-${{env.NATIVE_VERSION}}-${{runner.os}} + path: | + native-lib/python/src/dataweave/native/dwlib.dylib + native-lib/python/src/dataweave/native/dwlib.so + native-lib/python/src/dataweave/native/dwlib.dll + native-lib/python/src/dataweave/native/dwlib.h diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c439811..0c419e6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -43,7 +43,7 @@ jobs: # Runs a single command using the runners shell - name: Run Build run: | - ./gradlew --stacktrace --no-problems-report build + ./gradlew --stacktrace --no-problems-report -PskipNodeTests=true build shell: bash #Run regression tests - name: Run regression test 2.9.8 @@ -70,6 +70,22 @@ jobs: run: ./gradlew --stacktrace --no-problems-report native-lib:buildPythonWheel shell: bash + # Setup Node.js for native-lib Node package + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + # Stage the native lib and build Node package (npm install, node-gyp, tsc, npm pack) + - name: Create Native Lib Node Package + run: ./gradlew --stacktrace --no-problems-report native-lib:buildNodePackage + shell: bash + + # Run Node.js tests + - name: Run Node.js Tests + run: ./gradlew --stacktrace --no-problems-report native-lib:nodeTest + shell: bash + # Upload the artifact file - name: Upload generated script uses: actions/upload-artifact@v4 @@ -84,6 +100,13 @@ jobs: name: dw-python-wheel-${{env.NATIVE_VERSION}}-${{runner.os}} path: native-lib/python/dist/dataweave_native-0.0.1-py3-*.whl + # Upload the Node.js package + - name: Upload Node package + uses: actions/upload-artifact@v4 + with: + name: dw-node-package-${{env.NATIVE_VERSION}}-${{runner.os}} + path: native-lib/node/dataweave-native-0.0.1.tgz + # Upload the native shared library + header together per OS - name: Upload native shared library uses: actions/upload-artifact@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 82f417e..50b8344 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,7 +43,7 @@ jobs: # Runs a single command using the runners shell - name: Run Build run: | - ./gradlew --stacktrace --no-problems-report build -PnativeVersion=${{env.NATIVE_VERSION}} + ./gradlew --stacktrace --no-problems-report -PskipNodeTests=true build -PnativeVersion=${{env.NATIVE_VERSION}} shell: bash # Generate distro @@ -51,6 +51,32 @@ jobs: run: ./gradlew --stacktrace --no-problems-report native-cli:distro -PnativeVersion=${{env.NATIVE_VERSION}} shell: bash + # Install Python build dependencies (setuptools/wheel may be missing on Windows runners) + - name: Install Python build dependencies + run: python3 -m pip install --upgrade setuptools wheel + shell: bash + + # Generate native-lib python wheel + - name: Create Native Lib Python Wheel + run: ./gradlew --stacktrace --no-problems-report native-lib:buildPythonWheel -PnativeVersion=${{env.NATIVE_VERSION}} + shell: bash + + # Setup Node.js for native-lib Node package + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + # Stage the native lib and build Node package (npm install, node-gyp, tsc, npm pack) + - name: Create Native Lib Node Package + run: ./gradlew --stacktrace --no-problems-report native-lib:buildNodePackage -PnativeVersion=${{env.NATIVE_VERSION}} + shell: bash + + # Run Node.js tests + - name: Run Node.js Tests + run: ./gradlew --stacktrace --no-problems-report native-lib:nodeTest -PnativeVersion=${{env.NATIVE_VERSION}} + shell: bash + # Upload the artifact file - name: Upload binaries to release uses: svenstaro/upload-release-action@v2 @@ -60,3 +86,54 @@ jobs: asset_name: dw-${{env.NATIVE_VERSION}}-${{runner.os}} tag: ${{ github.ref }} overwrite: true + + # Upload the Python wheel + - name: Upload Python wheel to release + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: native-lib/python/dist/dataweave_native-0.0.1-py3-none-any.whl + asset_name: dw-python-wheel-${{env.NATIVE_VERSION}}-${{runner.os}}.whl + tag: ${{ github.ref }} + overwrite: true + + # Upload the Node.js package + - name: Upload Node package to release + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: native-lib/node/dataweave-native-0.0.1.tgz + asset_name: dw-node-package-${{env.NATIVE_VERSION}}-${{runner.os}}.tgz + tag: ${{ github.ref }} + overwrite: true + + # Upload the native shared library + - name: Upload native shared library to release (Linux) + if: runner.os == 'Linux' + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: native-lib/python/src/dataweave/native/dwlib.so + asset_name: dwlib-${{env.NATIVE_VERSION}}-${{runner.os}}.so + tag: ${{ github.ref }} + overwrite: true + + - name: Upload native shared library to release (Windows) + if: runner.os == 'Windows' + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: native-lib/python/src/dataweave/native/dwlib.dll + asset_name: dwlib-${{env.NATIVE_VERSION}}-${{runner.os}}.dll + tag: ${{ github.ref }} + overwrite: true + + # Upload the native library header + - name: Upload native library header to release + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: native-lib/python/src/dataweave/native/dwlib.h + asset_name: dwlib-${{env.NATIVE_VERSION}}.h + tag: ${{ github.ref }} + overwrite: true diff --git a/native-lib/.gitignore b/native-lib/.gitignore index 0e845cc..e093685 100644 --- a/native-lib/.gitignore +++ b/native-lib/.gitignore @@ -2,3 +2,5 @@ python/src/dataweave/native/ python/src/dataweave_native.egg-info/ python/dist/ python/build/ + +node_modules/ diff --git a/native-lib/README.md b/native-lib/README.md index e829ecf..1498898 100644 --- a/native-lib/README.md +++ b/native-lib/README.md @@ -321,3 +321,325 @@ result = dataweave.run_input_output_callback( print(result) # StreamingResult(success=True, ...) print(b"".join(chunks)) # [1,4,9,16,25] ``` + +--- + +## Installing for use in a Node.js project + +### Option A: Install the produced tarball (recommended) + +After `:native-lib:buildNodePackage`: + +```bash +npm install native-lib/node/dataweave-native-0.0.1.tgz +``` + +This tarball includes the pre-built native addon and the `dwlib.*` shared library. + +### Option B: Development install (link) + +1. Stage the native library: + +```bash +./gradlew :native-lib:stageNodeNativeLib +``` + +2. Build the Node package: + +```bash +cd native-lib/node +npm install +npx node-gyp rebuild +npx tsc +``` + +3. Link into your project: + +```bash +npm link native-lib/node +``` + +### Option C: Use an externally-built library via an environment variable + +Set `DATAWEAVE_NATIVE_LIB=/absolute/path/to/dwlib.(dylib|so|dll)` before running your application. + +The module also searches: +1. `/native/dwlib.*` +2. `/native-lib/build/native/nativeCompile/dwlib.*` (dev fallback) +3. Current working directory + +### Building with Gradle + +```bash +# Stage native library into node/native/ +./gradlew :native-lib:stageNodeNativeLib + +# Build the full .tgz package (stage + compile addon + tsc + npm pack) +./gradlew :native-lib:buildNodePackage + +# Run Node.js tests +./gradlew :native-lib:nodeTest + +# Skip Node tests in CI: -PskipNodeTests=true +``` + +### Requirements + +- Node.js >= 18 +- A C compiler (for `node-gyp` to build the native addon) +- The `dwlib` shared library (staged by Gradle or pointed to via env var) + +## Using the library (Node.js examples) + +All examples below assume: + +```typescript +import { run, runStreaming, runTransform, cleanup } from "@dataweave/native"; +``` + +### 1) Simple script + +```typescript +const result = run("2 + 2"); +console.log(result.getString()); // "4" +``` + +### 2) Script with inputs (auto-detected types) + +Inputs can be plain JS values. The module auto-encodes them as JSON. + +```typescript +const result = run("num1 + num2", { num1: 25, num2: 17 }); +console.log(result.getString()); // "42" +``` + +### 3) Script with inputs (explicit mime type, charset, properties) + +Use an explicit input object when you need full control over how DataWeave interprets bytes. + +```typescript +import { readFileSync } from "fs"; + +const xmlBytes = readFileSync("person.xml"); + +const result = run("payload.person", { + payload: { + content: xmlBytes, + mimeType: "application/xml", + charset: "UTF-16", + properties: { + nullValueOn: "empty", + maxAttributeSize: 256, + }, + }, +}); + +if (result.success) { + console.log(result.getString()); +} else { + console.error(result.error); +} +``` + +### 4) Explicit instance lifecycle + +The module-level API (`run(...)`) uses a shared singleton. Use the `DataWeave` class directly when you need explicit control over isolate lifecycle: + +```typescript +import { DataWeave } from "@dataweave/native"; + +const dw = new DataWeave(); +dw.initialize(); + +const r1 = dw.run("2 + 2"); +const r2 = dw.run("x + y", { x: 10, y: 32 }); + +console.log(r1.getString()); // "4" +console.log(r2.getString()); // "42" + +dw.cleanup(); +``` + +### 5) Error handling + +There are two error classes: + +- `DataWeaveError` — library/isolate-level failures (library not found, initialization failed). +- `DataWeaveScriptError` — script compilation or runtime error (subclass of `DataWeaveError`). Carries the full result on `.result`. + +**Option A: Use `raiseOnError: true` for try/catch (recommended)** + +```typescript +import { run, DataWeaveScriptError } from "@dataweave/native"; + +try { + const result = run("invalid syntax here", {}, { raiseOnError: true }); + console.log(result.getString()); +} catch (e) { + if (e instanceof DataWeaveScriptError) { + console.error(`Script error: ${e.result.error}`); + } else { + throw e; + } +} +``` + +**Option B: Check `result.success` manually (default)** + +```typescript +const result = run("invalid syntax here"); + +if (!result.success) { + console.error(`Error: ${result.error}`); +} else { + console.log(result.getString()); +} +``` + +### 6) Output streaming + +Use `runStreaming` to execute a script and receive output chunks as they are produced, without buffering the entire result in memory. Returns an `AsyncGenerator`. + +```typescript +const gen = runStreaming( + 'output application/json --- (1 to 10000) map {id: $, name: "item_" ++ $}' +); + +let result = await gen.next(); +while (!result.done) { + process.stdout.write(result.value); + result = await gen.next(); +} + +const metadata = result.value; // StreamingResult +console.log(`\nDone: ${metadata.mimeType}, ${metadata.charset}`); +``` + +Or with `for await`: + +```typescript +const gen = runStreaming("output application/csv --- payload", { + payload: [1, 2, 3], +}); + +const chunks: Buffer[] = []; +for await (const chunk of gen) { + chunks.push(chunk); +} +const output = Buffer.concat(chunks).toString("utf-8"); +``` + +### 7) Input and output streaming (bidirectional) + +Use `runTransform` to stream both input and output — feed an `Iterable` or `AsyncIterable` in, receive an `AsyncGenerator` out. + +**Important: sync vs async input and memory usage** + +The native read callback is invoked synchronously on the JS main thread, which means: + +- **Synchronous iterables** (arrays, generators) are consumed **on-demand** — only one chunk is held in memory at a time. This gives constant-memory streaming, comparable to the Python API. +- **Async iterables** (e.g. `fs.createReadStream()`) **must be fully pre-buffered** into memory before the transform starts, because their `.next()` returns a Promise that cannot be awaited inside a synchronous callback. + +For large inputs, prefer a **synchronous generator** to get true streaming with minimal memory: + +```typescript +import { readFileSync } from "fs"; + +// Good: sync generator → constant memory (~150 MB for 50M elements) +function* chunked(data: Buffer, size = 8192): Generator { + for (let i = 0; i < data.length; i += size) { + yield data.subarray(i, i + size); + } +} +const gen = runTransform("output csv --- payload", chunked(readFileSync("large.json")), { + mimeType: "application/json", +}); +``` + +Using an async readable stream still works but will buffer the entire input first: + +```typescript +import { createReadStream } from "fs"; +import { createWriteStream } from "fs"; + +// Works but pre-buffers the full input into memory +const input = createReadStream("large.json"); +const gen = runTransform("output application/csv --- payload", input, { + mimeType: "application/json", +}); + +const out = createWriteStream("output.csv"); +for await (const chunk of gen) { + out.write(chunk); +} +out.end(); +``` + +Works with any iterable — arrays, generators, streams: + +```typescript +// From an in-memory array +const input = [Buffer.from("[1,2,3,4,5]")]; +const gen = runTransform( + "output application/json --- payload map ($ * $)", + input, + { mimeType: "application/json" } +); + +const chunks: Buffer[] = []; +for await (const chunk of gen) { + chunks.push(chunk); +} +console.log(Buffer.concat(chunks).toString()); // [1,4,9,16,25] +``` + +```typescript +// From a generator producing chunks +function* chunked(data: Buffer, size = 4096): Generator { + for (let i = 0; i < data.length; i += size) { + yield data.subarray(i, i + size); + } +} + +const largeJson = Buffer.from(JSON.stringify(Array.from({ length: 1000 }, (_, i) => ({ id: i })))); +const gen = runTransform( + "output application/json --- sizeOf(payload)", + chunked(largeJson), + { mimeType: "application/json" } +); + +for await (const chunk of gen) { + process.stdout.write(chunk); // "1000" +} +``` + +### 8) Transform with additional inputs + +Pass extra named inputs alongside the streamed input: + +```typescript +const input = [Buffer.from('[{"price": 100}, {"price": 200}]')]; +const gen = runTransform( + "output application/json --- payload map ($.price * rate)", + input, + { + mimeType: "application/json", + inputs: { rate: 1.5 }, + } +); + +for await (const chunk of gen) { + process.stdout.write(chunk); // [150.0, 300.0] +} +``` + +### 9) Cleanup + +The module registers a `process.on('exit')` handler to clean up automatically. For explicit control: + +```typescript +import { cleanup } from "@dataweave/native"; + +// When done with all DataWeave operations +cleanup(); +``` diff --git a/native-lib/build.gradle b/native-lib/build.gradle index e495f06..0a448df 100644 --- a/native-lib/build.gradle +++ b/native-lib/build.gradle @@ -104,8 +104,53 @@ tasks.register('pythonTest', Exec) { commandLine(pythonExe, 'tests/test_dataweave_module.py') } +// --- Node.js native package tasks --- + +tasks.register('stageNodeNativeLib', Copy) { + dependsOn tasks.named('nativeCompile') + from("${buildDir}/native/nativeCompile") { + include('dwlib.*') + } + into("${projectDir}/node/native") +} + +tasks.register('buildNodePackage', Exec) { + dependsOn tasks.named('stageNodeNativeLib') + workingDir("${projectDir}/node") + inputs.dir("${projectDir}/node/src") + inputs.dir("${projectDir}/node/native") + inputs.file("${projectDir}/node/package.json") + inputs.file("${projectDir}/node/binding.gyp") + inputs.file("${projectDir}/node/tsconfig.json") + outputs.dir("${projectDir}/node/dist") + outputs.file("${projectDir}/node/build/Release/dwlib_addon.node") + doFirst { + delete("${projectDir}/node/dist") + } + if (System.getProperty('os.name').toLowerCase().contains('windows')) { + commandLine('cmd', '/c', 'npm install && npx node-gyp rebuild && npx tsc && npm pack') + } else { + commandLine('bash', '-c', 'npm install && npx node-gyp rebuild && npx tsc && npm pack') + } +} + +tasks.register('nodeTest', Exec) { + if (project.findProperty('skipNodeTests')?.toString()?.toBoolean() == true) { + enabled = false + } + + dependsOn tasks.named('stageNodeNativeLib') + workingDir("${projectDir}/node") + if (System.getProperty('os.name').toLowerCase().contains('windows')) { + commandLine('cmd', '/c', 'npm install && npx node-gyp rebuild && npx tsc && npx vitest run') + } else { + commandLine('bash', '-c', 'npm install && npx node-gyp rebuild && npx tsc && npx vitest run') + } +} + tasks.named('test') { dependsOn tasks.named('pythonTest') + dependsOn tasks.named('nodeTest') } tasks.named('clean') { @@ -113,4 +158,8 @@ tasks.named('clean') { delete("${projectDir}/python/build") delete("${projectDir}/python/src/dataweave/native") delete("${projectDir}/python/src/dataweave_native.egg-info") + delete("${projectDir}/node/dist") + delete("${projectDir}/node/build") + delete("${projectDir}/node/native") + delete("${projectDir}/node/node_modules") } diff --git a/native-lib/example_streaming.mjs b/native-lib/example_streaming.mjs new file mode 100755 index 0000000..26ee1b5 --- /dev/null +++ b/native-lib/example_streaming.mjs @@ -0,0 +1,127 @@ +#!/usr/bin/env node + +import { runTransform, cleanup } from "./node/dist/index.js"; + +function formatBytes(bytes) { + return (bytes / 1048576).toFixed(1); +} + +function getRSS() { + return process.memoryUsage().rss; +} + +function* inputChunks(numElements) { + const bufSize = 8192; + let i = 0; + let started = false; + let pendingToken = null; + + while (i < numElements || pendingToken !== null) { + const parts = []; + if (!started) { + parts.push(Buffer.from("[")); + started = true; + } + let remaining = bufSize - parts.reduce((sum, p) => sum + p.length, 0); + + if (pendingToken !== null) { + if (pendingToken.length <= remaining) { + parts.push(pendingToken); + remaining -= pendingToken.length; + pendingToken = null; + } else { + yield Buffer.concat(parts); + continue; + } + } + + while (remaining > 0 && i < numElements) { + const token = Buffer.from((i > 0 ? "," : "") + String(i)); + if (token.length > remaining) { + pendingToken = token; + break; + } + parts.push(token); + remaining -= token.length; + i++; + } + + if (i >= numElements && pendingToken === null) { + parts.push(Buffer.from("]")); + } + + if (parts.length > 0) { + yield Buffer.concat(parts); + } + } + + // If we exited without closing bracket (pending was last) + // This shouldn't happen but just in case +} + +async function exampleRunTransform() { + console.log("\nTesting streaming input and output using runTransform (square numbers)..."); + + const startTime = process.hrtime.bigint(); + const numElements = 1_000_000 * 50; + + const script = `output application/json deferred=true +--- +payload map ($ * $)`; + + const startRSS = getRSS(); + console.log(`>>> Before runTransform, RSS: ${formatBytes(startRSS)} MB`); + + const gen = runTransform(script, inputChunks(numElements), { + mimeType: "application/json", + charset: "utf-8", + }); + + let chunkCount = 0; + let totalBytes = 0; + let result = await gen.next(); + + while (!result.done) { + chunkCount++; + totalBytes += result.value.length; + if (chunkCount % 5000 === 0) { + const rss = getRSS(); + console.log( + `--- chunk ${chunkCount}: ${result.value.length} bytes, total: ${formatBytes(totalBytes)} MB, RSS: ${formatBytes(rss)} MB ---` + ); + } + result = await gen.next(); + } + + const metadata = result.value; + if (!metadata.success) { + throw new Error(metadata.error || "Unknown error"); + } + + const elapsed = Number(process.hrtime.bigint() - startTime) / 1e9; + const mins = Math.floor(elapsed / 60); + const secs = (elapsed % 60).toFixed(3).padStart(6, "0"); + const peakRSS = getRSS(); + + console.log( + `\n[OK] runTransform done (${chunkCount} chunks, ${formatBytes(totalBytes)} MB, ${numElements.toLocaleString()} elements) - Time: ${mins}:${secs}` + ); + console.log(`RSS at end: ${formatBytes(peakRSS)} MB`); +} + +async function main() { + console.log("=".repeat(70)); + console.log("Node.js runTransform (AsyncGenerator API)"); + console.log("=".repeat(70)); + + try { + await exampleRunTransform(); + } catch (e) { + console.error(`[FAIL] runTransform failed: ${e.message}`); + console.error(e.stack); + } finally { + cleanup(); + } +} + +main(); diff --git a/native-lib/node/.gitignore b/native-lib/node/.gitignore new file mode 100644 index 0000000..e76ab8e --- /dev/null +++ b/native-lib/node/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +build/ +native/ +*.tgz diff --git a/native-lib/node/binding.gyp b/native-lib/node/binding.gyp new file mode 100644 index 0000000..a746229 --- /dev/null +++ b/native-lib/node/binding.gyp @@ -0,0 +1,19 @@ +{ + "targets": [ + { + "target_name": "dwlib_addon", + "sources": ["src/addon.c"], + "defines": ["NAPI_VERSION=8"], + "conditions": [ + ["OS=='mac'", { + "xcode_settings": { + "OTHER_CFLAGS": ["-std=c11"] + } + }], + ["OS=='linux'", { + "cflags": ["-std=gnu11"] + }] + ] + } + ] +} diff --git a/native-lib/node/node-api-plan.md b/native-lib/node/node-api-plan.md new file mode 100644 index 0000000..c9fd8d0 --- /dev/null +++ b/native-lib/node/node-api-plan.md @@ -0,0 +1,186 @@ +# Node.js API for DataWeave Native Library — Implementation Plan + +## Overview + +Add a Node.js package (`@dataweave/native`) that mirrors the existing Python API, exposing the DataWeave native shared library (`dwlib`) via a N-API native addon. The package will be built as a platform-specific tarball (`.tgz`) and uploaded to GitHub Releases alongside the Python wheel. + +## Architecture Decisions + +### FFI Binding: N-API Native Addon (C) + +**Critical finding**: `koffi` (pure JS FFI) does not work with GraalVM Native Image in Node.js due to a fundamental signal handler conflict between V8 and GraalVM. Both engines install SIGSEGV handlers, causing crashes during isolate initialization regardless of which thread the call originates from. + +The solution is a N-API native addon written in C that runs ALL GraalVM calls on dedicated pthreads with sufficient stack size, completely isolated from V8's signal handling. + +| Option | Status | Notes | +|--------|--------|-------| +| `koffi` | ❌ REJECTED | V8/GraalVM signal handler conflict causes StackOverflowError during `graal_create_isolate` | +| N-API addon (C) | ✅ CHOSEN | Full control over thread creation, stack sizes, and signal isolation | +| `node-ffi-napi` | ❌ | Same V8 signal handler conflict as koffi | + +Key technical constraints: +- `graal_create_isolate` must run on a dedicated thread with 16MB stack (not a V8/libuv worker thread) +- All subsequent GraalVM calls must use `graal_attach_thread`/`graal_detach_thread` on dedicated threads +- Threading uses libuv (`uv_thread_create_ex`, `uv_mutex`, `uv_cond`, `uv_dlopen`) for cross-platform Windows/Linux/macOS support — no POSIX-only APIs +- Streaming uses `napi_threadsafe_function` to bridge native thread callbacks back to the JS event loop +- Sentinel values are sent through the same tsfn queue as data chunks to guarantee delivery ordering (avoids race between `napi_async_work` completion and pending tsfn dispatches) +- Reference counting on `initialize`/`cleanup` allows multiple `DataWeave` instances to share a single GraalVM isolate +- The addon uses the raw C `` header directly (not the `node-addon-api` C++ wrapper) to avoid MSVC `/std:c++17` conflicts on Windows + +### Package Layout + +``` +native-lib/ +└── node/ + ├── package.json + ├── tsconfig.json + ├── binding.gyp # node-gyp build config for native addon + ├── src/ + │ ├── addon.c # N-API native addon (libuv threads + GraalVM FFI) + │ ├── index.ts # Public API (module-level + class) + │ ├── ffi.ts # TypeScript wrapper loading .node addon + │ ├── types.ts # TypeScript interfaces & types + │ └── utils.ts # Input normalization, library path resolution + ├── tests/ + │ ├── dataweave.test.ts + │ └── fixtures/ + │ └── person.xml + ├── native/ # Staged dwlib.* (gitignored, populated by Gradle) + │ └── .gitkeep + └── dist/ # Compiled JS output (gitignored) +``` + +### API Design (mirrors Python) + +```typescript +// Module-level convenience (lazy-initializes a global instance) +import { run, runStreaming, runTransform, cleanup } from '@dataweave/native'; + +const result = run('2 + 2'); +console.log(result.getString()); // "4" + +// Streaming output (AsyncGenerator) +for await (const chunk of runStreaming('output json --- (1 to 10000) map {id: $}')) { + process.stdout.write(chunk); +} + +// Bidirectional streaming +import { createReadStream } from 'fs'; +const output = runTransform( + 'output csv --- payload', + createReadStream('large.json'), + { mimeType: 'application/json' } +); +for await (const chunk of output) { + process.stdout.write(chunk); +} + +// Explicit lifecycle +import { DataWeave } from '@dataweave/native'; +const dw = new DataWeave(); +dw.initialize(); +const result = dw.run('2 + 2'); +dw.cleanup(); +``` + +## Implementation Status + +### ✅ Phase 1: Core Package Structure +- `package.json` with node-gyp, vitest, typescript (no runtime dependencies) +- `tsconfig.json` (CommonJS, ES2022, strict) +- `binding.gyp` (C11 on macOS/Linux, CompileAs=C on Windows, NAPI_VERSION=8) + +### ✅ Phase 2: Native Addon (`src/addon.c`) +- All GraalVM calls on dedicated threads via libuv (`uv_thread_create_ex`) +- `initialize`: thread with 16MB stack → `graal_create_isolate` +- `runScript`: thread with 2MB stack → `attach_thread` + `run_script` + `detach_thread` +- `runScriptStreaming`: thread + `napi_threadsafe_function` + sentinel for ordered delivery +- `runScriptTransform`: bidirectional with read tsfn (blocking `uv_cond`) + write tsfn + sentinel +- `cleanup`: thread → `graal_tear_down_isolate`, ref-counted +- Library loading via `uv_dlopen`/`uv_dlsym` (cross-platform) + +### ✅ Phase 3: TypeScript FFI wrapper (`src/ffi.ts`) +- Loads `.node` addon via `require()` +- Thin typed wrapper + +### ✅ Phase 4: Library resolution (`src/utils.ts`) +- Same search order as Python: env var → `native/` → build dir → CWD + +### ✅ Phase 5: Public API (`src/index.ts`) +- `DataWeave` class: `initialize()`, `cleanup()`, `run()`, `runStreaming()`, `runTransform()` +- Module-level convenience functions with lazy singleton +- `runStreaming` → `AsyncGenerator` +- `runTransform` → accepts `AsyncIterable`, returns `AsyncGenerator` + +### ✅ Phase 6: Tests (14/14 passing) +- Basic arithmetic, inputs, explicit instance lifecycle +- UTF-16 XML encoding, auto-conversion, error handling +- Streaming: basic, large output, error propagation, with inputs +- Transform: basic bidirectional, large chunked input, file-based + +### ✅ Phase 7: Gradle Integration +- `stageNodeNativeLib` — copies dwlib.* to node/native/ +- `buildNodePackage` — npm install + node-gyp rebuild + tsc + npm pack +- `nodeTest` — full test run (skippable with `-PskipNodeTests=true`) +- `clean` updated to remove node artifacts + +### ✅ Phase 8: GitHub Actions CI +- Initial `./gradlew build` runs with `-PskipNodeTests=true` (Node.js not yet available) +- Setup Node.js 18 via `actions/setup-node@v4` +- `./gradlew native-lib:buildNodePackage` (stage + compile addon + tsc + npm pack) +- `./gradlew native-lib:nodeTest` (explicit test run after Node.js setup) +- Upload `dataweave-native-0.0.1.tgz` per OS + +## Technical Details + +### Threading Model + +``` +JS Main Thread Dedicated threads (via libuv) +───────────────── ────────────────────────────── +initialize() ──────────► uv_thread(16MB stack) + uv_dlopen() + graal_create_isolate() + ◄── return + +runScript() ───────────► uv_thread(2MB stack) + graal_attach_thread() + run_script() + graal_detach_thread() + ◄── return result + +runScriptStreaming() ──► uv_thread(2MB stack) + graal_attach_thread() + run_script_callback(write_cb) + │ + ├── write_cb: chunk → tsfn queue + ├── write_cb: chunk → tsfn queue + └── sentinel(-1) → tsfn queue + + tsfn dispatches on JS thread: + chunk → JS callback + chunk → JS callback + sentinel → resolve promise + +cleanup() ─────────────► uv_thread(2MB stack) + graal_tear_down_isolate() + ◄── return +``` + +### Sentinel-based Ordering + +The async streaming resolution uses a sentinel (chunk with `len == -1`) sent through the same `napi_threadsafe_function` queue as data chunks. This guarantees the promise resolves only after ALL data chunks have been delivered to JS, avoiding the race condition that occurs when using `napi_async_work` completion callbacks (which can fire before pending tsfn items are dispatched). + +### Reference Counting + +Multiple `DataWeave` instances share a single GraalVM isolate. Each `initialize()` increments a ref count; `cleanup()` decrements it. The isolate is only torn down when the last reference is released. + +## Risks & Mitigations + +| Risk | Mitigation | +|------|------------| +| V8/GraalVM signal handler conflict | All GraalVM calls on dedicated libuv threads (verified working) | +| Thread stack overflow | 16MB for init, 2MB for calls (matches GraalVM requirements) | +| Streaming ordering race | Sentinel through same tsfn queue (verified working) | +| Windows MSVC `/std:c++17` conflict | Removed `node-addon-api` C++ dep; use raw `node_api.h` C header + `CompileAs=1` (`/TC`) | +| Cross-platform threading | libuv primitives (`uv_thread`, `uv_mutex`, `uv_cond`, `uv_dlopen`) — no POSIX deps | +| node-gyp required at install | Package includes pre-built .node + source for rebuild | diff --git a/native-lib/node/package-lock.json b/native-lib/node/package-lock.json new file mode 100644 index 0000000..def3096 --- /dev/null +++ b/native-lib/node/package-lock.json @@ -0,0 +1,2860 @@ +{ + "name": "@dataweave/native", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@dataweave/native", + "version": "0.0.1", + "os": [ + "darwin", + "linux", + "win32" + ], + "devDependencies": { + "@types/node": "^20", + "node-gyp": "^10", + "typescript": "^5.5", + "vitest": "^3.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@npmcli/agent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", + "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "dev": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", + "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "dev": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.3.tgz", + "integrity": "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.3.tgz", + "integrity": "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.3.tgz", + "integrity": "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.3.tgz", + "integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.3.tgz", + "integrity": "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.3.tgz", + "integrity": "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.3.tgz", + "integrity": "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.3.tgz", + "integrity": "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.3.tgz", + "integrity": "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.3.tgz", + "integrity": "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.3.tgz", + "integrity": "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.3.tgz", + "integrity": "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.3.tgz", + "integrity": "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.3.tgz", + "integrity": "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.3.tgz", + "integrity": "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.3.tgz", + "integrity": "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.3.tgz", + "integrity": "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.3.tgz", + "integrity": "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.3.tgz", + "integrity": "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.3.tgz", + "integrity": "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.3.tgz", + "integrity": "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.3.tgz", + "integrity": "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.3.tgz", + "integrity": "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.3.tgz", + "integrity": "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.3.tgz", + "integrity": "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", + "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cacache": { + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", + "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-fetch-happen": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", + "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", + "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", + "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-gyp": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.3.1.tgz", + "integrity": "sha512-Pp3nFHBThHzVtNY7U6JfPjvT/DTE8+o/4xKsLQtBoU+j2HLsGlhcfzflAoUreaJbNmYnX+LlLi0qjV8kpyO6xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^4.1.0", + "semver": "^7.3.5", + "tar": "^6.2.1", + "which": "^4.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rollup": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.3.tgz", + "integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.3", + "@rollup/rollup-android-arm64": "4.60.3", + "@rollup/rollup-darwin-arm64": "4.60.3", + "@rollup/rollup-darwin-x64": "4.60.3", + "@rollup/rollup-freebsd-arm64": "4.60.3", + "@rollup/rollup-freebsd-x64": "4.60.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", + "@rollup/rollup-linux-arm-musleabihf": "4.60.3", + "@rollup/rollup-linux-arm64-gnu": "4.60.3", + "@rollup/rollup-linux-arm64-musl": "4.60.3", + "@rollup/rollup-linux-loong64-gnu": "4.60.3", + "@rollup/rollup-linux-loong64-musl": "4.60.3", + "@rollup/rollup-linux-ppc64-gnu": "4.60.3", + "@rollup/rollup-linux-ppc64-musl": "4.60.3", + "@rollup/rollup-linux-riscv64-gnu": "4.60.3", + "@rollup/rollup-linux-riscv64-musl": "4.60.3", + "@rollup/rollup-linux-s390x-gnu": "4.60.3", + "@rollup/rollup-linux-x64-gnu": "4.60.3", + "@rollup/rollup-linux-x64-musl": "4.60.3", + "@rollup/rollup-openbsd-x64": "4.60.3", + "@rollup/rollup-openharmony-arm64": "4.60.3", + "@rollup/rollup-win32-arm64-msvc": "4.60.3", + "@rollup/rollup-win32-ia32-msvc": "4.60.3", + "@rollup/rollup-win32-x64-gnu": "4.60.3", + "@rollup/rollup-win32-x64-msvc": "4.60.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.8.tgz", + "integrity": "sha512-NlGELfPrgX2f1TAAcz0WawlLn+0r3FyhhCRpFFK2CemXenPYvzMWWZINv3eDNo9ucdwme7oCHRY0Jnbs4aIkog==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ssri": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", + "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/native-lib/node/package.json b/native-lib/node/package.json new file mode 100644 index 0000000..cf49228 --- /dev/null +++ b/native-lib/node/package.json @@ -0,0 +1,38 @@ +{ + "name": "@dataweave/native", + "version": "0.0.1", + "description": "Node.js bindings for the DataWeave native library", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "node-gyp rebuild && tsc", + "build:addon": "node-gyp rebuild", + "build:ts": "tsc", + "test": "vitest run", + "test:watch": "vitest", + "pack": "npm pack" + }, + "files": [ + "dist/", + "native/", + "build/Release/dwlib_addon.node", + "src/addon.c", + "binding.gyp" + ], + "os": [ + "darwin", + "linux", + "win32" + ], + "engines": { + "node": ">=18" + }, + "gypfile": true, + "dependencies": {}, + "devDependencies": { + "@types/node": "^20", + "node-gyp": "^10", + "typescript": "^5.5", + "vitest": "^3.0" + } +} diff --git a/native-lib/node/src/addon.c b/native-lib/node/src/addon.c new file mode 100644 index 0000000..b18c2eb --- /dev/null +++ b/native-lib/node/src/addon.c @@ -0,0 +1,641 @@ +#include +#include +#include +#include +#include + +// GraalVM function pointer types +typedef int (*graal_create_isolate_fn)(void*, void**, void**); +typedef int (*graal_attach_thread_fn)(void*, void**); +typedef int (*graal_detach_thread_fn)(void*); +typedef int (*graal_tear_down_isolate_fn)(void*); +typedef void* (*run_script_fn)(void*, const char*, const char*); +typedef void (*free_cstring_fn)(void*, void*); +typedef int (*write_callback_t)(void* ctx, const char* buf, int len); +typedef int (*read_callback_t)(void* ctx, char* buf, int buf_size); +typedef void* (*run_script_callback_fn)(void*, const char*, const char*, write_callback_t, void*); +typedef void* (*run_script_input_output_callback_fn)(void*, const char*, const char*, const char*, const char*, const char*, read_callback_t, write_callback_t, void*); + +// Global state +static uv_lib_t g_lib; +static int g_lib_loaded = 0; +static void* g_isolate = NULL; +static void* g_thread = NULL; +static int g_initialized = 0; +static int g_ref_count = 0; +static uv_mutex_t g_mutex; + +static graal_create_isolate_fn fn_create_isolate = NULL; +static graal_attach_thread_fn fn_attach_thread = NULL; +static graal_detach_thread_fn fn_detach_thread = NULL; +static graal_tear_down_isolate_fn fn_tear_down_isolate = NULL; +static run_script_fn fn_run_script = NULL; +static free_cstring_fn fn_free_cstring = NULL; +static run_script_callback_fn fn_run_script_callback = NULL; +static run_script_input_output_callback_fn fn_run_script_input_output_callback = NULL; + +// --- Initialization --- + +struct init_args { + const char* lib_path; + int result; + char error[512]; +}; + +static void init_thread_fn(void* arg) { + struct init_args* args = (struct init_args*)arg; + + int rc = uv_dlopen(args->lib_path, &g_lib); + if (rc != 0) { + snprintf(args->error, sizeof(args->error), "Failed to load library: %s", uv_dlerror(&g_lib)); + args->result = -1; + return; + } + g_lib_loaded = 1; + + uv_dlsym(&g_lib, "graal_create_isolate", (void**)&fn_create_isolate); + uv_dlsym(&g_lib, "graal_attach_thread", (void**)&fn_attach_thread); + uv_dlsym(&g_lib, "graal_detach_thread", (void**)&fn_detach_thread); + uv_dlsym(&g_lib, "graal_tear_down_isolate", (void**)&fn_tear_down_isolate); + uv_dlsym(&g_lib, "run_script", (void**)&fn_run_script); + uv_dlsym(&g_lib, "free_cstring", (void**)&fn_free_cstring); + uv_dlsym(&g_lib, "run_script_callback", (void**)&fn_run_script_callback); + uv_dlsym(&g_lib, "run_script_input_output_callback", (void**)&fn_run_script_input_output_callback); + + if (!fn_create_isolate || !fn_run_script || !fn_free_cstring) { + snprintf(args->error, sizeof(args->error), "Missing required symbols in library"); + args->result = -2; + return; + } + + rc = fn_create_isolate(NULL, &g_isolate, &g_thread); + if (rc != 0) { + snprintf(args->error, sizeof(args->error), "graal_create_isolate failed with code %d", rc); + args->result = rc; + return; + } + + args->result = 0; +} + +static napi_value napi_initialize(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value argv[1]; + napi_get_cb_info(env, info, &argc, argv, NULL, NULL); + + if (argc < 1) { + napi_throw_error(env, NULL, "initialize requires a library path argument"); + return NULL; + } + + char lib_path[4096]; + size_t len; + napi_get_value_string_utf8(env, argv[0], lib_path, sizeof(lib_path), &len); + + uv_mutex_lock(&g_mutex); + if (g_initialized) { + g_ref_count++; + uv_mutex_unlock(&g_mutex); + return NULL; + } + + struct init_args args; + args.lib_path = lib_path; + args.result = -1; + args.error[0] = '\0'; + + uv_thread_t tid; + uv_thread_options_t opts; + opts.flags = UV_THREAD_HAS_STACK_SIZE; + opts.stack_size = 16 * 1024 * 1024; + uv_thread_create_ex(&tid, &opts, init_thread_fn, &args); + uv_thread_join(&tid); + + if (args.result != 0) { + uv_mutex_unlock(&g_mutex); + napi_throw_error(env, NULL, args.error[0] ? args.error : "Initialization failed"); + return NULL; + } + + g_initialized = 1; + g_ref_count++; + uv_mutex_unlock(&g_mutex); + return NULL; +} + +// --- Helper: run any GraalVM call on a dedicated thread --- + +struct script_call_args { + const char* script; + const char* inputs_json; + char* result; +}; + +static void run_script_thread_fn(void* arg) { + struct script_call_args* a = (struct script_call_args*)arg; + + void* thread = NULL; + int rc = fn_attach_thread(g_isolate, &thread); + if (rc != 0) { + a->result = strdup("{\"success\":false,\"error\":\"Failed to attach GraalVM thread\"}"); + return; + } + + void* ptr = fn_run_script(thread, a->script, a->inputs_json); + if (ptr) { + a->result = strdup((const char*)ptr); + fn_free_cstring(thread, ptr); + } else { + a->result = strdup(""); + } + + fn_detach_thread(thread); +} + +// --- runScript (synchronous from JS, but runs GraalVM on a thread) --- + +static napi_value dw_napi_run_script(napi_env env, napi_callback_info info) { + if (!g_initialized) { + napi_throw_error(env, NULL, "Not initialized. Call initialize() first."); + return NULL; + } + + size_t argc = 2; + napi_value argv[2]; + napi_get_cb_info(env, info, &argc, argv, NULL, NULL); + + if (argc < 2) { + napi_throw_error(env, NULL, "runScript requires (script, inputsJson)"); + return NULL; + } + + size_t script_len, inputs_len; + napi_get_value_string_utf8(env, argv[0], NULL, 0, &script_len); + napi_get_value_string_utf8(env, argv[1], NULL, 0, &inputs_len); + + char* script = malloc(script_len + 1); + char* inputs = malloc(inputs_len + 1); + napi_get_value_string_utf8(env, argv[0], script, script_len + 1, NULL); + napi_get_value_string_utf8(env, argv[1], inputs, inputs_len + 1, NULL); + + struct script_call_args call_args; + call_args.script = script; + call_args.inputs_json = inputs; + call_args.result = NULL; + + uv_thread_t tid; + uv_thread_options_t opts; + opts.flags = UV_THREAD_HAS_STACK_SIZE; + opts.stack_size = 2 * 1024 * 1024; + uv_thread_create_ex(&tid, &opts, run_script_thread_fn, &call_args); + uv_thread_join(&tid); + + free(script); + free(inputs); + + napi_value result; + if (call_args.result) { + napi_create_string_utf8(env, call_args.result, strlen(call_args.result), &result); + free(call_args.result); + } else { + napi_create_string_utf8(env, "", 0, &result); + } + return result; +} + +// --- Streaming output --- + +// chunk_data with len == -1 is a sentinel indicating completion (buf holds meta JSON) +struct chunk_data { + char* buf; + int len; +}; + +struct streaming_work { + uv_thread_t tid; + napi_threadsafe_function tsfn; + napi_deferred deferred; + char* script; + char* inputs_json; +}; + +static void call_js_write(napi_env env, napi_value js_callback, void* context, void* data) { + if (env == NULL || data == NULL) return; + struct chunk_data* chunk = (struct chunk_data*)data; + struct streaming_work* w = (struct streaming_work*)context; + + if (chunk->len == -1) { + napi_value result; + napi_create_string_utf8(env, chunk->buf, strlen(chunk->buf), &result); + napi_resolve_deferred(env, w->deferred, result); + + free(chunk->buf); + free(chunk); + free(w->script); + free(w->inputs_json); + + uv_thread_join(&w->tid); + napi_release_threadsafe_function(w->tsfn, napi_tsfn_release); + free(w); + return; + } + + napi_value buffer; + void* buf_data; + napi_create_buffer_copy(env, chunk->len, chunk->buf, &buf_data, &buffer); + + napi_value global; + napi_get_global(env, &global); + napi_call_function(env, global, js_callback, 1, &buffer, NULL); + + free(chunk->buf); + free(chunk); +} + +static int streaming_write_cb(void* ctx, const char* buf, int len) { + napi_threadsafe_function tsfn = (napi_threadsafe_function)ctx; + struct chunk_data* chunk = malloc(sizeof(struct chunk_data)); + chunk->buf = malloc(len); + memcpy(chunk->buf, buf, len); + chunk->len = len; + + napi_status status = napi_call_threadsafe_function(tsfn, chunk, napi_tsfn_blocking); + if (status != napi_ok) { + free(chunk->buf); + free(chunk); + return -1; + } + return 0; +} + +static void streaming_thread_fn(void* arg) { + struct streaming_work* w = (struct streaming_work*)arg; + + void* worker_thread = NULL; + int rc = fn_attach_thread(g_isolate, &worker_thread); + + char* meta_result = NULL; + if (rc != 0) { + char err[256]; + snprintf(err, sizeof(err), "{\"success\":false,\"error\":\"Failed to attach thread (code %d)\"}", rc); + meta_result = strdup(err); + } else { + void* result_ptr = fn_run_script_callback( + worker_thread, w->script, w->inputs_json, streaming_write_cb, (void*)w->tsfn + ); + if (result_ptr) { + meta_result = strdup((const char*)result_ptr); + fn_free_cstring(worker_thread, result_ptr); + } else { + meta_result = strdup("{\"success\":false,\"error\":\"Empty response\"}"); + } + fn_detach_thread(worker_thread); + } + + struct chunk_data* sentinel = malloc(sizeof(struct chunk_data)); + sentinel->buf = meta_result; + sentinel->len = -1; + napi_call_threadsafe_function(w->tsfn, sentinel, napi_tsfn_blocking); +} + +static napi_value napi_run_script_streaming(napi_env env, napi_callback_info info) { + if (!g_initialized) { + napi_throw_error(env, NULL, "Not initialized. Call initialize() first."); + return NULL; + } + if (!fn_run_script_callback) { + napi_throw_error(env, NULL, "run_script_callback not available in native library"); + return NULL; + } + + size_t argc = 3; + napi_value argv[3]; + napi_get_cb_info(env, info, &argc, argv, NULL, NULL); + + if (argc < 3) { + napi_throw_error(env, NULL, "runScriptStreaming requires (script, inputsJson, chunkCallback)"); + return NULL; + } + + size_t script_len, inputs_len; + napi_get_value_string_utf8(env, argv[0], NULL, 0, &script_len); + napi_get_value_string_utf8(env, argv[1], NULL, 0, &inputs_len); + + struct streaming_work* w = calloc(1, sizeof(struct streaming_work)); + w->script = malloc(script_len + 1); + w->inputs_json = malloc(inputs_len + 1); + napi_get_value_string_utf8(env, argv[0], w->script, script_len + 1, NULL); + napi_get_value_string_utf8(env, argv[1], w->inputs_json, inputs_len + 1, NULL); + + napi_value resource_name; + napi_create_string_utf8(env, "dwStreaming", NAPI_AUTO_LENGTH, &resource_name); + napi_create_threadsafe_function(env, argv[2], NULL, resource_name, 0, 1, NULL, NULL, w, call_js_write, &w->tsfn); + + napi_value promise; + napi_create_promise(env, &w->deferred, &promise); + + uv_thread_options_t opts; + opts.flags = UV_THREAD_HAS_STACK_SIZE; + opts.stack_size = 2 * 1024 * 1024; + uv_thread_create_ex(&w->tid, &opts, streaming_thread_fn, w); + + return promise; +} + +// --- Bidirectional streaming --- + +struct transform_work { + uv_thread_t tid; + napi_threadsafe_function read_tsfn; + napi_threadsafe_function write_tsfn; + napi_deferred deferred; + char* script; + char* inputs_json; + char* input_name; + char* input_mime_type; + char* input_charset; +}; + +struct read_request { + char* buffer; + int buffer_size; + int bytes_read; + uv_mutex_t mutex; + uv_cond_t cond; + int ready; +}; + +static void call_js_read(napi_env env, napi_value js_callback, void* context, void* data) { + if (env == NULL || data == NULL) return; + struct read_request* req = (struct read_request*)data; + + napi_value buf_size_val; + napi_create_int32(env, req->buffer_size, &buf_size_val); + + napi_value global; + napi_get_global(env, &global); + + napi_value result; + napi_status status = napi_call_function(env, global, js_callback, 1, &buf_size_val, &result); + + if (status == napi_ok && result != NULL) { + bool is_buffer; + napi_is_buffer(env, result, &is_buffer); + if (is_buffer) { + void* buf_data; + size_t buf_len; + napi_get_buffer_info(env, result, &buf_data, &buf_len); + int n = (int)buf_len < req->buffer_size ? (int)buf_len : req->buffer_size; + if (n > 0) memcpy(req->buffer, buf_data, n); + req->bytes_read = n; + } else { + req->bytes_read = 0; + } + } else { + req->bytes_read = 0; + } + + uv_mutex_lock(&req->mutex); + req->ready = 1; + uv_cond_signal(&req->cond); + uv_mutex_unlock(&req->mutex); +} + +static int transform_read_cb(void* ctx, char* buf, int buf_size) { + struct transform_work* w = (struct transform_work*)ctx; + + struct read_request req; + req.buffer = buf; + req.buffer_size = buf_size; + req.bytes_read = 0; + req.ready = 0; + uv_mutex_init(&req.mutex); + uv_cond_init(&req.cond); + + napi_status status = napi_call_threadsafe_function(w->read_tsfn, &req, napi_tsfn_blocking); + if (status != napi_ok) { + uv_mutex_destroy(&req.mutex); + uv_cond_destroy(&req.cond); + return -1; + } + + uv_mutex_lock(&req.mutex); + while (!req.ready) { + uv_cond_wait(&req.cond, &req.mutex); + } + uv_mutex_unlock(&req.mutex); + + int n = req.bytes_read; + uv_mutex_destroy(&req.mutex); + uv_cond_destroy(&req.cond); + return n; +} + +static int transform_write_cb(void* ctx, const char* buf, int len) { + struct transform_work* w = (struct transform_work*)ctx; + struct chunk_data* chunk = malloc(sizeof(struct chunk_data)); + chunk->buf = malloc(len); + memcpy(chunk->buf, buf, len); + chunk->len = len; + + napi_status status = napi_call_threadsafe_function(w->write_tsfn, chunk, napi_tsfn_blocking); + if (status != napi_ok) { + free(chunk->buf); + free(chunk); + return -1; + } + return 0; +} + +static void call_js_transform_write(napi_env env, napi_value js_callback, void* context, void* data) { + if (env == NULL || data == NULL) return; + struct chunk_data* chunk = (struct chunk_data*)data; + struct transform_work* w = (struct transform_work*)context; + + if (chunk->len == -1) { + napi_value result; + napi_create_string_utf8(env, chunk->buf, strlen(chunk->buf), &result); + napi_resolve_deferred(env, w->deferred, result); + + free(chunk->buf); + free(chunk); + free(w->script); + free(w->inputs_json); + free(w->input_name); + free(w->input_mime_type); + free(w->input_charset); + + uv_thread_join(&w->tid); + napi_release_threadsafe_function(w->read_tsfn, napi_tsfn_release); + napi_release_threadsafe_function(w->write_tsfn, napi_tsfn_release); + free(w); + return; + } + + napi_value buffer; + void* buf_data; + napi_create_buffer_copy(env, chunk->len, chunk->buf, &buf_data, &buffer); + + napi_value global; + napi_get_global(env, &global); + napi_call_function(env, global, js_callback, 1, &buffer, NULL); + + free(chunk->buf); + free(chunk); +} + +static void transform_thread_fn(void* arg) { + struct transform_work* w = (struct transform_work*)arg; + + void* worker_thread = NULL; + int rc = fn_attach_thread(g_isolate, &worker_thread); + + char* meta_result = NULL; + if (rc != 0) { + char err[256]; + snprintf(err, sizeof(err), "{\"success\":false,\"error\":\"Failed to attach thread (code %d)\"}", rc); + meta_result = strdup(err); + } else { + void* result_ptr = fn_run_script_input_output_callback( + worker_thread, w->script, w->inputs_json, + w->input_name, w->input_mime_type, w->input_charset, + transform_read_cb, transform_write_cb, (void*)w + ); + + if (result_ptr) { + meta_result = strdup((const char*)result_ptr); + fn_free_cstring(worker_thread, result_ptr); + } else { + meta_result = strdup("{\"success\":false,\"error\":\"Empty response\"}"); + } + fn_detach_thread(worker_thread); + } + + struct chunk_data* sentinel = malloc(sizeof(struct chunk_data)); + sentinel->buf = meta_result; + sentinel->len = -1; + napi_call_threadsafe_function(w->write_tsfn, sentinel, napi_tsfn_blocking); +} + +static napi_value napi_run_script_transform(napi_env env, napi_callback_info info) { + if (!g_initialized) { + napi_throw_error(env, NULL, "Not initialized. Call initialize() first."); + return NULL; + } + if (!fn_run_script_input_output_callback) { + napi_throw_error(env, NULL, "run_script_input_output_callback not available in native library"); + return NULL; + } + + size_t argc = 7; + napi_value argv[7]; + napi_get_cb_info(env, info, &argc, argv, NULL, NULL); + + if (argc < 7) { + napi_throw_error(env, NULL, "runScriptTransform requires 7 arguments"); + return NULL; + } + + struct transform_work* w = calloc(1, sizeof(struct transform_work)); + size_t len; + + napi_get_value_string_utf8(env, argv[0], NULL, 0, &len); + w->script = malloc(len + 1); + napi_get_value_string_utf8(env, argv[0], w->script, len + 1, NULL); + + napi_get_value_string_utf8(env, argv[1], NULL, 0, &len); + w->inputs_json = malloc(len + 1); + napi_get_value_string_utf8(env, argv[1], w->inputs_json, len + 1, NULL); + + napi_get_value_string_utf8(env, argv[2], NULL, 0, &len); + w->input_name = malloc(len + 1); + napi_get_value_string_utf8(env, argv[2], w->input_name, len + 1, NULL); + + napi_get_value_string_utf8(env, argv[3], NULL, 0, &len); + w->input_mime_type = malloc(len + 1); + napi_get_value_string_utf8(env, argv[3], w->input_mime_type, len + 1, NULL); + + napi_valuetype type; + napi_typeof(env, argv[4], &type); + if (type == napi_string) { + napi_get_value_string_utf8(env, argv[4], NULL, 0, &len); + w->input_charset = malloc(len + 1); + napi_get_value_string_utf8(env, argv[4], w->input_charset, len + 1, NULL); + } else { + w->input_charset = NULL; + } + + napi_value resource_name; + napi_create_string_utf8(env, "dwTransform", NAPI_AUTO_LENGTH, &resource_name); + + napi_create_threadsafe_function(env, argv[5], NULL, resource_name, 0, 1, NULL, NULL, NULL, call_js_read, &w->read_tsfn); + napi_create_threadsafe_function(env, argv[6], NULL, resource_name, 0, 1, NULL, NULL, w, call_js_transform_write, &w->write_tsfn); + + napi_value promise; + napi_create_promise(env, &w->deferred, &promise); + + uv_thread_options_t opts; + opts.flags = UV_THREAD_HAS_STACK_SIZE; + opts.stack_size = 2 * 1024 * 1024; + uv_thread_create_ex(&w->tid, &opts, transform_thread_fn, w); + + return promise; +} + +// --- Cleanup (must run on a separate thread to avoid V8 signal handler conflict) --- + +static void cleanup_thread_fn(void* arg) { + (void)arg; + if (fn_tear_down_isolate && g_thread) { + fn_tear_down_isolate(g_thread); + } +} + +static napi_value napi_cleanup(napi_env env, napi_callback_info info) { + uv_mutex_lock(&g_mutex); + if (g_initialized) { + g_ref_count--; + if (g_ref_count <= 0) { + uv_thread_t tid; + uv_thread_options_t opts; + opts.flags = UV_THREAD_HAS_STACK_SIZE; + opts.stack_size = 2 * 1024 * 1024; + uv_thread_create_ex(&tid, &opts, cleanup_thread_fn, NULL); + uv_thread_join(&tid); + + g_thread = NULL; + g_isolate = NULL; + g_initialized = 0; + g_ref_count = 0; + } + } + uv_mutex_unlock(&g_mutex); + return NULL; +} + +// --- Module init --- + +static napi_value Init(napi_env env, napi_value exports) { + uv_mutex_init(&g_mutex); + + napi_value fn; + + napi_create_function(env, "initialize", NAPI_AUTO_LENGTH, napi_initialize, NULL, &fn); + napi_set_named_property(env, exports, "initialize", fn); + + napi_create_function(env, "runScript", NAPI_AUTO_LENGTH, dw_napi_run_script, NULL, &fn); + napi_set_named_property(env, exports, "runScript", fn); + + napi_create_function(env, "runScriptStreaming", NAPI_AUTO_LENGTH, napi_run_script_streaming, NULL, &fn); + napi_set_named_property(env, exports, "runScriptStreaming", fn); + + napi_create_function(env, "runScriptTransform", NAPI_AUTO_LENGTH, napi_run_script_transform, NULL, &fn); + napi_set_named_property(env, exports, "runScriptTransform", fn); + + napi_create_function(env, "cleanup", NAPI_AUTO_LENGTH, napi_cleanup, NULL, &fn); + napi_set_named_property(env, exports, "cleanup", fn); + + return exports; +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/native-lib/node/src/ffi.ts b/native-lib/node/src/ffi.ts new file mode 100644 index 0000000..75bb65b --- /dev/null +++ b/native-lib/node/src/ffi.ts @@ -0,0 +1,59 @@ +import { join } from "node:path"; + +interface NativeAddon { + initialize(libPath: string): void; + runScript(script: string, inputsJson: string): string; + runScriptStreaming(script: string, inputsJson: string, chunkCb: (chunk: Buffer) => void): Promise; + runScriptTransform( + script: string, + inputsJson: string, + inputName: string, + inputMimeType: string, + inputCharset: string | null, + readCb: (bufSize: number) => Buffer | null, + writeCb: (chunk: Buffer) => void + ): Promise; + cleanup(): void; +} + +let addon: NativeAddon | null = null; + +function getAddon(): NativeAddon { + if (!addon) { + const addonPath = join(__dirname, "..", "build", "Release", "dwlib_addon.node"); + addon = require(addonPath) as NativeAddon; + } + return addon; +} + +export function initialize(libPath: string): void { + getAddon().initialize(libPath); +} + +export function runScript(script: string, inputsJson: string): string { + return getAddon().runScript(script, inputsJson); +} + +export function runScriptStreaming( + script: string, + inputsJson: string, + chunkCb: (chunk: Buffer) => void +): Promise { + return getAddon().runScriptStreaming(script, inputsJson, chunkCb); +} + +export function runScriptTransform( + script: string, + inputsJson: string, + inputName: string, + inputMimeType: string, + inputCharset: string | null, + readCb: (bufSize: number) => Buffer | null, + writeCb: (chunk: Buffer) => void +): Promise { + return getAddon().runScriptTransform(script, inputsJson, inputName, inputMimeType, inputCharset, readCb, writeCb); +} + +export function cleanup(): void { + getAddon().cleanup(); +} diff --git a/native-lib/node/src/index.ts b/native-lib/node/src/index.ts new file mode 100644 index 0000000..1545170 --- /dev/null +++ b/native-lib/node/src/index.ts @@ -0,0 +1,360 @@ +import * as ffi from "./ffi"; +import { findLibrary, buildInputsJson } from "./utils"; +import type { + ExecutionResult, + StreamingResult, + Inputs, + TransformOptions, +} from "./types"; + +export type { + ExecutionResult, + StreamingResult, + Inputs, + InputValue, + InputEntry, + TransformOptions, +} from "./types"; + +export class DataWeaveError extends Error { + constructor(message: string) { + super(message); + this.name = "DataWeaveError"; + } +} + +export class DataWeaveScriptError extends DataWeaveError { + result: ExecutionResult; + constructor(result: ExecutionResult) { + super(result.error ?? "Script execution failed"); + this.name = "DataWeaveScriptError"; + this.result = result; + } +} + +function parseNativeResponse(raw: string): ExecutionResult { + if (!raw) { + return makeResult(false, null, "Native returned empty response", false, null, null); + } + + let parsed: Record; + try { + parsed = JSON.parse(raw); + } catch (e) { + return makeResult(false, null, `Failed to parse native JSON response: ${e}`, false, null, null); + } + + const success = Boolean(parsed.success); + if (!success) { + return makeResult(false, null, (parsed.error as string) ?? null, false, null, null); + } + + return makeResult( + true, + (parsed.result as string) ?? null, + null, + Boolean(parsed.binary), + (parsed.mimeType as string) ?? null, + (parsed.charset as string) ?? null + ); +} + +function makeResult( + success: boolean, + result: string | null, + error: string | null, + binary: boolean, + mimeType: string | null, + charset: string | null +): ExecutionResult { + return { + success, + result, + error, + binary, + mimeType, + charset, + getBytes() { + if (!this.success || this.result === null) return null; + return Buffer.from(this.result, "base64"); + }, + getString() { + if (!this.success || this.result === null) return null; + if (this.binary) return this.result; + const bytes = Buffer.from(this.result, "base64"); + return bytes.toString((this.charset as BufferEncoding) ?? "utf-8"); + }, + }; +} + +function parseStreamingResult(raw: string): StreamingResult { + let meta: Record; + try { + meta = raw ? JSON.parse(raw) : { success: false, error: "Empty response" }; + } catch { + return { success: false, error: "Failed to parse metadata", mimeType: null, charset: null, binary: false }; + } + + const success = Boolean(meta.success); + if (!success) { + return { success: false, error: (meta.error as string) ?? null, mimeType: null, charset: null, binary: false }; + } + return { + success: true, + error: null, + mimeType: (meta.mimeType as string) ?? null, + charset: (meta.charset as string) ?? null, + binary: Boolean(meta.binary), + }; +} + +export class DataWeave { + private libPath: string; + private initialized = false; + + constructor(libPath?: string) { + this.libPath = libPath ?? findLibrary(); + } + + initialize(): void { + if (this.initialized) return; + try { + ffi.initialize(this.libPath); + } catch (e: unknown) { + throw new DataWeaveError(`Failed to initialize: ${e instanceof Error ? e.message : e}`); + } + this.initialized = true; + } + + cleanup(): void { + if (!this.initialized) return; + ffi.cleanup(); + this.initialized = false; + } + + run(script: string, inputs?: Inputs, opts?: { raiseOnError?: boolean }): ExecutionResult { + this.ensureInitialized(); + const inputsJson = buildInputsJson(inputs ?? {}); + const raw = ffi.runScript(script, inputsJson); + const result = parseNativeResponse(raw); + + if (opts?.raiseOnError && !result.success) { + throw new DataWeaveScriptError(result); + } + return result; + } + + async *runStreaming(script: string, inputs?: Inputs): AsyncGenerator { + this.ensureInitialized(); + const inputsJson = buildInputsJson(inputs ?? {}); + + const chunks: Buffer[] = []; + let resolveChunk: (() => void) | null = null; + let done = false; + let metaRaw: string | null = null; + + const chunkCb = (chunk: Buffer) => { + chunks.push(chunk); + if (resolveChunk) { + resolveChunk(); + resolveChunk = null; + } + }; + + const metaPromise = ffi.runScriptStreaming(script, inputsJson, chunkCb).then((raw) => { + metaRaw = raw; + done = true; + if (resolveChunk) { + resolveChunk(); + resolveChunk = null; + } + }); + + while (true) { + if (chunks.length > 0) { + yield chunks.shift()!; + continue; + } + if (done) break; + await new Promise((resolve) => { resolveChunk = resolve; }); + } + + // Drain remaining chunks + while (chunks.length > 0) { + yield chunks.shift()!; + } + + await metaPromise; + return parseStreamingResult(metaRaw ?? ""); + } + + async *runTransform( + script: string, + input: AsyncIterable | Iterable, + opts?: TransformOptions + ): AsyncGenerator { + this.ensureInitialized(); + + const inputName = opts?.inputName ?? "payload"; + const inputMimeType = opts?.mimeType ?? "application/json"; + const inputCharset = opts?.charset ?? null; + const extraInputs = opts?.inputs ?? {}; + const inputsJson = Object.keys(extraInputs).length > 0 ? buildInputsJson(extraInputs) : "{}"; + + const isAsync = Symbol.asyncIterator in (input as object); + + let readCb: (bufSize: number) => Buffer | null; + + if (isAsync) { + // Async iterables must be pre-buffered because the native read callback + // is invoked synchronously on the JS main thread and cannot await. + const inputBuffers: Buffer[] = []; + const asyncIter = (input as AsyncIterable)[Symbol.asyncIterator](); + try { + while (true) { + const { value, done: d } = await asyncIter.next(); + if (d) break; + inputBuffers.push(Buffer.isBuffer(value) ? value : Buffer.from(value)); + } + } catch { /* input error = EOF */ } + + let bufIdx = 0; + let currentBuf: Buffer | null = null; + let readOffset = 0; + + readCb = (bufSize: number): Buffer | null => { + while (true) { + if (currentBuf && readOffset < currentBuf.length) { + const n = Math.min(currentBuf.length - readOffset, bufSize); + const slice = currentBuf.subarray(readOffset, readOffset + n); + readOffset += n; + if (readOffset >= currentBuf.length) { + currentBuf = null; + readOffset = 0; + } + return Buffer.from(slice); + } + if (bufIdx < inputBuffers.length) { + currentBuf = inputBuffers[bufIdx++]; + readOffset = 0; + continue; + } + return null; + } + }; + } else { + // Sync iterables are consumed on-demand — constant memory, no pre-buffering. + const syncIter = (input as Iterable)[Symbol.iterator](); + let currentBuf: Buffer | null = null; + let readOffset = 0; + let iterDone = false; + + readCb = (bufSize: number): Buffer | null => { + while (true) { + if (currentBuf && readOffset < currentBuf.length) { + const n = Math.min(currentBuf.length - readOffset, bufSize); + const slice = currentBuf.subarray(readOffset, readOffset + n); + readOffset += n; + if (readOffset >= currentBuf.length) { + currentBuf = null; + readOffset = 0; + } + return Buffer.from(slice); + } + if (iterDone) return null; + const { value, done: d } = syncIter.next(); + if (d) { + iterDone = true; + return null; + } + currentBuf = Buffer.isBuffer(value) ? value : Buffer.from(value); + readOffset = 0; + } + }; + } + + const chunks: Buffer[] = []; + let resolveChunk: (() => void) | null = null; + let done = false; + let metaRaw: string | null = null; + + const writeCb = (chunk: Buffer) => { + chunks.push(chunk); + if (resolveChunk) { + resolveChunk(); + resolveChunk = null; + } + }; + + const metaPromise = ffi.runScriptTransform( + script, inputsJson, inputName, inputMimeType, inputCharset, readCb, writeCb + ).then((raw) => { + metaRaw = raw; + done = true; + if (resolveChunk) { + resolveChunk(); + resolveChunk = null; + } + }); + + while (true) { + if (chunks.length > 0) { + yield chunks.shift()!; + continue; + } + if (done) break; + await new Promise((resolve) => { resolveChunk = resolve; }); + } + + while (chunks.length > 0) { + yield chunks.shift()!; + } + + await metaPromise; + return parseStreamingResult(metaRaw ?? ""); + } + + private ensureInitialized(): void { + if (!this.initialized) { + throw new DataWeaveError("DataWeave runtime not initialized. Call initialize() first."); + } + } +} + +// Module-level convenience API with lazy singleton +let globalInstance: DataWeave | null = null; + +function getGlobalInstance(): DataWeave { + if (!globalInstance) { + globalInstance = new DataWeave(); + globalInstance.initialize(); + process.on("exit", () => cleanup()); + } + return globalInstance; +} + +export function run(script: string, inputs?: Inputs, opts?: { raiseOnError?: boolean }): ExecutionResult { + return getGlobalInstance().run(script, inputs, opts); +} + +export function runStreaming( + script: string, + inputs?: Inputs +): AsyncGenerator { + return getGlobalInstance().runStreaming(script, inputs); +} + +export function runTransform( + script: string, + input: AsyncIterable | Iterable, + opts?: TransformOptions +): AsyncGenerator { + return getGlobalInstance().runTransform(script, input, opts); +} + +export function cleanup(): void { + if (globalInstance) { + globalInstance.cleanup(); + globalInstance = null; + } +} diff --git a/native-lib/node/src/types.ts b/native-lib/node/src/types.ts new file mode 100644 index 0000000..eec8ee3 --- /dev/null +++ b/native-lib/node/src/types.ts @@ -0,0 +1,41 @@ +export interface ExecutionResult { + success: boolean; + result: string | null; + error: string | null; + binary: boolean; + mimeType: string | null; + charset: string | null; + getBytes(): Buffer | null; + getString(): string | null; +} + +export interface StreamingResult { + success: boolean; + error: string | null; + mimeType: string | null; + charset: string | null; + binary: boolean; +} + +export interface InputValue { + content: string | Buffer; + mimeType: string; + charset?: string; + properties?: Record; +} + +export type InputEntry = InputValue | string | number | boolean | null | object; + +export type Inputs = Record; + +export interface TransformOptions { + inputName?: string; + mimeType?: string; + charset?: string; + inputs?: Inputs; +} + +export interface StreamOutput { + stream: AsyncGenerator; + metadata: Promise; +} diff --git a/native-lib/node/src/utils.ts b/native-lib/node/src/utils.ts new file mode 100644 index 0000000..0e3ab0d --- /dev/null +++ b/native-lib/node/src/utils.ts @@ -0,0 +1,111 @@ +import { existsSync } from "node:fs"; +import { join, dirname } from "node:path"; +import type { InputEntry, Inputs } from "./types"; + +const ENV_NATIVE_LIB = "DATAWEAVE_NATIVE_LIB"; + +const LIB_EXTENSIONS = [".dylib", ".so", ".dll"]; + +function libNames(): string[] { + return LIB_EXTENSIONS.map((ext) => `dwlib${ext}`); +} + +export function findLibrary(): string { + const envValue = (process.env[ENV_NATIVE_LIB] ?? "").trim(); + if (envValue && existsSync(envValue)) { + return envValue; + } + + const thisDir = __dirname; + + // Packaged: /dist/utils.js → /native/dwlib.* + const nativeDir = join(thisDir, "..", "native"); + for (const name of libNames()) { + const p = join(nativeDir, name); + if (existsSync(p)) return p; + } + + // Dev fallback: walk up to find build/native/nativeCompile/dwlib.* + let dir = thisDir; + for (let i = 0; i < 10; i++) { + const buildDir = join(dir, "build", "native", "nativeCompile"); + if (existsSync(buildDir)) { + for (const name of libNames()) { + const p = join(buildDir, name); + if (existsSync(p)) return p; + } + } + const parent = dirname(dir); + if (parent === dir) break; + dir = parent; + } + + // CWD fallback + for (const name of libNames()) { + if (existsSync(name)) return join(process.cwd(), name); + } + + throw new Error( + `Could not find DataWeave native library (dwlib). ` + + `Set ${ENV_NATIVE_LIB} to an absolute path or install a package that bundles the native library.` + ); +} + +export function normalizeInputValue(value: InputEntry, mimeType?: string): Record { + if (value === null || value === undefined) { + const content = Buffer.from("null", "utf-8").toString("base64"); + return { content, mimeType: mimeType ?? "application/json", charset: "utf-8" }; + } + + if (typeof value === "object" && !Array.isArray(value) && !Buffer.isBuffer(value)) { + const obj = value as Record; + if ("content" in obj && "mimeType" in obj) { + const rawContent = obj.content; + const charset = (obj.charset as string) ?? "utf-8"; + let encodedContent: string; + if (Buffer.isBuffer(rawContent)) { + encodedContent = rawContent.toString("base64"); + } else { + encodedContent = Buffer.from(String(rawContent), charset as BufferEncoding).toString("base64"); + } + const normalized: Record = { + content: encodedContent, + mimeType: obj.mimeType, + }; + if (obj.charset) normalized.charset = obj.charset; + if (obj.properties) normalized.properties = obj.properties; + return normalized; + } + } + + let content: string; + let defaultMime: string; + + if (typeof value === "string") { + content = value; + defaultMime = "text/plain"; + } else if (typeof value === "number" || typeof value === "boolean") { + content = JSON.stringify(value); + defaultMime = "application/json"; + } else { + try { + content = JSON.stringify(value); + defaultMime = "application/json"; + } catch { + content = String(value); + defaultMime = "text/plain"; + } + } + + const charset = "utf-8"; + const encodedContent = Buffer.from(content, charset).toString("base64"); + return { content: encodedContent, mimeType: mimeType ?? defaultMime, charset }; +} + +export function buildInputsJson(inputs: Inputs): string { + const normalized: Record = {}; + for (const [key, val] of Object.entries(inputs)) { + normalized[key] = normalizeInputValue(val); + } + return JSON.stringify(normalized); +} diff --git a/native-lib/node/tests/dataweave.test.ts b/native-lib/node/tests/dataweave.test.ts new file mode 100644 index 0000000..2c04455 --- /dev/null +++ b/native-lib/node/tests/dataweave.test.ts @@ -0,0 +1,225 @@ +import { describe, it, expect, afterAll } from "vitest"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { DataWeave, run, runStreaming, runTransform, cleanup } from "../src/index"; + +afterAll(() => { + cleanup(); +}); + +describe("DataWeave Node.js API", () => { + describe("run (buffered)", () => { + it("basic arithmetic", () => { + const result = run("2 + 2"); + expect(result.success).toBe(true); + expect(result.getString()).toBe("4"); + }); + + it("with inputs", () => { + const result = run("num1 + num2", { num1: 25, num2: 17 }); + expect(result.success).toBe(true); + expect(result.getString()).toBe("42"); + }); + + it("explicit instance lifecycle", () => { + const dw = new DataWeave(); + dw.initialize(); + try { + const r1 = dw.run("sqrt(144)"); + expect(r1.getString()).toBe("12"); + const r2 = dw.run("sqrt(10000)"); + expect(r2.getString()).toBe("100"); + } finally { + dw.cleanup(); + } + }); + + it("encoding: UTF-16 XML to CSV", () => { + const xmlPath = join(__dirname, "fixtures", "person.xml"); + const xmlBytes = readFileSync(xmlPath); + + const script = `output application/csv header=true +--- +[payload.person]`; + + const result = run(script, { + payload: { + content: xmlBytes, + mimeType: "application/xml", + charset: "UTF-16", + }, + }); + + expect(result.success).toBe(true); + const out = result.getString()!; + expect(out).toContain("name"); + expect(out).toContain("age"); + expect(out).toContain("Billy"); + expect(out).toContain("31"); + }); + + it("auto-conversion of array input", () => { + const result = run("numbers[0]", { numbers: [1, 2, 3] }); + expect(result.success).toBe(true); + expect(result.getString()).toBe("1"); + }); + + it("error handling", () => { + const result = run("invalid_var_xyz"); + expect(result.success).toBe(false); + expect(result.error).toBeTruthy(); + }); + + it("raiseOnError throws", () => { + expect(() => run("invalid_var_xyz", {}, { raiseOnError: true })).toThrow(); + }); + }); + + describe("runStreaming", () => { + it("basic streaming output", async () => { + const chunks: Buffer[] = []; + const gen = runStreaming("output application/json --- {a: 1, b: 2}"); + let result = await gen.next(); + while (!result.done) { + chunks.push(result.value); + result = await gen.next(); + } + const metadata = result.value; + + const full = Buffer.concat(chunks).toString("utf-8"); + expect(full).toContain('"a": 1'); + expect(metadata.success).toBe(true); + expect(metadata.mimeType).toBe("application/json"); + }); + + it("large output produces multiple chunks", async () => { + const chunks: Buffer[] = []; + const gen = runStreaming( + 'output application/json --- (1 to 5000) map {id: $, name: "item_" ++ $}' + ); + let result = await gen.next(); + while (!result.done) { + chunks.push(result.value); + result = await gen.next(); + } + const metadata = result.value; + + expect(metadata.success).toBe(true); + expect(chunks.length).toBeGreaterThan(1); + const full = Buffer.concat(chunks).toString("utf-8"); + expect(full).toContain('"id": 5000'); + }); + + it("error propagation", async () => { + const chunks: Buffer[] = []; + const gen = runStreaming("output application/json --- invalid_var"); + let result = await gen.next(); + while (!result.done) { + chunks.push(result.value); + result = await gen.next(); + } + const metadata = result.value; + + expect(metadata.success).toBe(false); + expect(metadata.error).toBeTruthy(); + expect(chunks.length).toBe(0); + }); + + it("with inputs", async () => { + const chunks: Buffer[] = []; + const gen = runStreaming("num1 + num2", { num1: 25, num2: 17 }); + let result = await gen.next(); + while (!result.done) { + chunks.push(result.value); + result = await gen.next(); + } + const metadata = result.value; + + expect(metadata.success).toBe(true); + const text = Buffer.concat(chunks).toString("utf-8"); + expect(text.trim()).toBe("42"); + }); + }); + + describe("runTransform", () => { + it("basic bidirectional streaming", async () => { + const inputData = [Buffer.from("[10, 20, 30, 40, 50]")]; + const script = "output application/json\n---\npayload map ($ * 2)"; + + const chunks: Buffer[] = []; + const gen = runTransform(script, inputData, { mimeType: "application/json" }); + let result = await gen.next(); + while (!result.done) { + chunks.push(result.value); + result = await gen.next(); + } + const metadata = result.value; + + expect(metadata.success).toBe(true); + const text = Buffer.concat(chunks).toString("utf-8"); + expect(text).toContain("20"); + expect(text).toContain("100"); + }); + + it("large chunked input", async () => { + // Build a large JSON array in chunks + const parts: Buffer[] = [Buffer.from("[")]; + for (let i = 1; i <= 1000; i++) { + if (i > 1) parts.push(Buffer.from(",")); + parts.push(Buffer.from(`{"id":${i}}`)); + } + parts.push(Buffer.from("]")); + const fullInput = Buffer.concat(parts); + + // Feed in 4KB chunks + function* chunked(data: Buffer, size = 4096): Generator { + for (let i = 0; i < data.length; i += size) { + yield data.subarray(i, i + size); + } + } + + const script = "output application/json\n---\nsizeOf(payload)"; + const chunks: Buffer[] = []; + const gen = runTransform(script, chunked(fullInput), { mimeType: "application/json" }); + let result = await gen.next(); + while (!result.done) { + chunks.push(result.value); + result = await gen.next(); + } + const metadata = result.value; + + expect(metadata.success).toBe(true); + const text = Buffer.concat(chunks).toString("utf-8"); + expect(text).toBe("1000"); + }); + + it("file-based streaming input", async () => { + const xmlPath = join(__dirname, "fixtures", "person.xml"); + const xmlData = readFileSync(xmlPath); + + function* chunked(data: Buffer, size = 4096): Generator { + for (let i = 0; i < data.length; i += size) { + yield data.subarray(i, i + size); + } + } + + const script = "output application/csv header=true\n---\n[payload.person]"; + const chunks: Buffer[] = []; + const gen = runTransform(script, chunked(xmlData), { + mimeType: "application/xml", + charset: "UTF-16", + }); + let result = await gen.next(); + while (!result.done) { + chunks.push(result.value); + result = await gen.next(); + } + const metadata = result.value; + + expect(metadata.success).toBe(true); + const text = Buffer.concat(chunks).toString("utf-8"); + expect(text).toContain("Billy"); + expect(text).toContain("31"); + }); + }); +}); diff --git a/native-lib/node/tests/fixtures/person.xml b/native-lib/node/tests/fixtures/person.xml new file mode 100644 index 0000000..376a6b7 Binary files /dev/null and b/native-lib/node/tests/fixtures/person.xml differ diff --git a/native-lib/node/tsconfig.json b/native-lib/node/tsconfig.json new file mode 100644 index 0000000..0e1e862 --- /dev/null +++ b/native-lib/node/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "moduleResolution": "node", + "outDir": "dist", + "rootDir": "src", + "declaration": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "tests"] +} diff --git a/native-lib/node/vitest.config.ts b/native-lib/node/vitest.config.ts new file mode 100644 index 0000000..4870abb --- /dev/null +++ b/native-lib/node/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + testTimeout: 30000, + }, +});