From f1f2f474f67f105f43d166101170a76cf4437152 Mon Sep 17 00:00:00 2001 From: svacas Date: Thu, 7 May 2026 18:11:26 -0300 Subject: [PATCH 1/6] W-20884161: Add Node.js API for DataWeave native library Add @dataweave/native Node.js package mirroring the Python API, with N-API native addon using libuv for cross-platform Windows/Linux/macOS support. Includes run(), runStreaming(), and runTransform() APIs with full test suite, Gradle integration, and CI workflow updates. --- .github/workflows/main.yml | 25 +- native-lib/README.md | 297 +++ native-lib/build.gradle | 49 + native-lib/node/.gitignore | 5 + native-lib/node/binding.gyp | 31 + native-lib/node/node-api-plan.md | 180 ++ native-lib/node/package-lock.json | 2872 +++++++++++++++++++++ native-lib/node/package.json | 40 + native-lib/node/src/addon.c | 641 +++++ native-lib/node/src/ffi.ts | 59 + native-lib/node/src/index.ts | 336 +++ native-lib/node/src/types.ts | 41 + native-lib/node/src/utils.ts | 111 + native-lib/node/tests/dataweave.test.ts | 225 ++ native-lib/node/tests/fixtures/person.xml | Bin 0 -> 194 bytes native-lib/node/tsconfig.json | 16 + native-lib/node/vitest.config.ts | 7 + 17 files changed, 4934 insertions(+), 1 deletion(-) create mode 100644 native-lib/node/.gitignore create mode 100644 native-lib/node/binding.gyp create mode 100644 native-lib/node/node-api-plan.md create mode 100644 native-lib/node/package-lock.json create mode 100644 native-lib/node/package.json create mode 100644 native-lib/node/src/addon.c create mode 100644 native-lib/node/src/ffi.ts create mode 100644 native-lib/node/src/index.ts create mode 100644 native-lib/node/src/types.ts create mode 100644 native-lib/node/src/utils.ts create mode 100644 native-lib/node/tests/dataweave.test.ts create mode 100644 native-lib/node/tests/fixtures/person.xml create mode 100644 native-lib/node/tsconfig.json create mode 100644 native-lib/node/vitest.config.ts 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/native-lib/README.md b/native-lib/README.md index e829ecf..5e0df39 100644 --- a/native-lib/README.md +++ b/native-lib/README.md @@ -321,3 +321,300 @@ 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 `AsyncIterable` or `Iterable` in, receive an `AsyncGenerator` out. Ideal for processing large files or network streams with constant memory. + +```typescript +import { createReadStream } from "fs"; +import { createWriteStream } from "fs"; + +// Stream a file through DataWeave +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/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..ae50e6c --- /dev/null +++ b/native-lib/node/binding.gyp @@ -0,0 +1,31 @@ +{ + "targets": [ + { + "target_name": "dwlib_addon", + "sources": ["src/addon.c"], + "include_dirs": [ + "` +- `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 +- Setup Node.js 18 +- `./gradlew native-lib:buildNodePackage` +- Upload `dataweave-native-0.0.1.tgz` per OS + +## Technical Details + +### Threading Model + +``` +JS Main Thread Dedicated pthreads +───────────────── ────────────────── +initialize() ──────────► pthread(16MB stack) + graal_create_isolate() + ◄── return + +runScript() ───────────► pthread(2MB stack) + graal_attach_thread() + run_script() + graal_detach_thread() + ◄── return result + +runScriptStreaming() ──► pthread(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() ─────────────► pthread(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 pthreads (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 support | `binding.gyp` + `node-gyp` handles MSVC; needs testing on CI | +| 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..f3ef9b9 --- /dev/null +++ b/native-lib/node/package-lock.json @@ -0,0 +1,2872 @@ +{ + "name": "@dataweave/native", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@dataweave/native", + "version": "0.0.1", + "os": [ + "darwin", + "linux", + "win32" + ], + "dependencies": { + "node-addon-api": "^8.0" + }, + "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-addon-api": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.7.0.tgz", + "integrity": "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "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..02a66f6 --- /dev/null +++ b/native-lib/node/package.json @@ -0,0 +1,40 @@ +{ + "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": { + "node-addon-api": "^8.0" + }, + "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..7d03a7e --- /dev/null +++ b/native-lib/node/src/index.ts @@ -0,0 +1,336 @@ +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) : "{}"; + + // Pre-buffer all input chunks (the read callback is synchronous from native side) + const inputBuffers: Buffer[] = []; + let inputDone = false; + + const iter = Symbol.asyncIterator in (input as object) + ? (input as AsyncIterable)[Symbol.asyncIterator]() + : (input as Iterable)[Symbol.iterator](); + + // Eagerly read all input (needed because read callback is sync) + const feedPromise = (async () => { + try { + while (true) { + const { value, done: d } = await (iter as AsyncIterator).next(); + if (d) break; + inputBuffers.push(Buffer.isBuffer(value) ? value : Buffer.from(value)); + } + } catch { /* input error = EOF */ } + inputDone = true; + })(); + + // Wait for at least some input to be available + await feedPromise; + + let readOffset = 0; + let currentBuf: Buffer | null = null; + let bufIdx = 0; + + const 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; // EOF + } + }; + + 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 0000000000000000000000000000000000000000..376a6b7023181915f8f33bbbb42b51b9107252b5 GIT binary patch literal 194 zcmZXN%?g4*6h_awrH#E01f3!w>gBcXhC&c`F?Y`QaqcpE3SNv1 zIG(gTnCQ6?$k~$;k?3_w0$1@yX`uV27tT~1)HUM8 Date: Thu, 7 May 2026 18:51:37 -0300 Subject: [PATCH 2/6] Fix Windows build: remove node-addon-api C++ dependency MSVC rejects mixing /std:c++17 (injected by node-addon-api) with /std:c11 for our pure C addon. Removed node-addon-api since we use the raw node_api.h C header directly. Added CompileAs=1 (/TC) for Windows to ensure C compilation mode. Co-Authored-By: Claude Opus 4.6 --- native-lib/node/binding.gyp | 7 +------ native-lib/node/package-lock.json | 12 ------------ native-lib/node/package.json | 4 +--- 3 files changed, 2 insertions(+), 21 deletions(-) diff --git a/native-lib/node/binding.gyp b/native-lib/node/binding.gyp index ae50e6c..1ee8015 100644 --- a/native-lib/node/binding.gyp +++ b/native-lib/node/binding.gyp @@ -3,12 +3,7 @@ { "target_name": "dwlib_addon", "sources": ["src/addon.c"], - "include_dirs": [ - "= 0.6" } }, - "node_modules/node-addon-api": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.7.0.tgz", - "integrity": "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==", - "license": "MIT", - "engines": { - "node": "^18 || ^20 || >= 21" - } - }, "node_modules/node-gyp": { "version": "10.3.1", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.3.1.tgz", diff --git a/native-lib/node/package.json b/native-lib/node/package.json index 02a66f6..cf49228 100644 --- a/native-lib/node/package.json +++ b/native-lib/node/package.json @@ -28,9 +28,7 @@ "node": ">=18" }, "gypfile": true, - "dependencies": { - "node-addon-api": "^8.0" - }, + "dependencies": {}, "devDependencies": { "@types/node": "^20", "node-gyp": "^10", From 62e062ed8fb3c92a47a9bd3028855bffb0f6d23d Mon Sep 17 00:00:00 2001 From: svacas Date: Thu, 7 May 2026 19:28:33 -0300 Subject: [PATCH 3/6] Fix Windows build: remove MSVC-specific flags from binding.gyp MSVC compiles .c files as C and .cc files as C++ by extension, so no explicit CompileAs or /std:c11 flags are needed. The previous attempts conflicted with win_delay_load_hook.cc (a C++ file node-gyp includes automatically on Windows). Also updates node-api-plan.md to reflect libuv-based cross-platform architecture. Co-Authored-By: Claude Opus 4.6 --- native-lib/node/binding.gyp | 7 ----- native-lib/node/node-api-plan.md | 52 ++++++++++++++++++-------------- 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/native-lib/node/binding.gyp b/native-lib/node/binding.gyp index 1ee8015..b8ec6f9 100644 --- a/native-lib/node/binding.gyp +++ b/native-lib/node/binding.gyp @@ -12,13 +12,6 @@ }], ["OS=='linux'", { "cflags": ["-std=c11"] - }], - ["OS=='win'", { - "msvs_settings": { - "VCCLCompilerTool": { - "CompileAs": "1" - } - } }] ] } diff --git a/native-lib/node/node-api-plan.md b/native-lib/node/node-api-plan.md index c7c00e0..c9fd8d0 100644 --- a/native-lib/node/node-api-plan.md +++ b/native-lib/node/node-api-plan.md @@ -19,11 +19,13 @@ The solution is a N-API native addon written in C that runs ALL GraalVM calls on | `node-ffi-napi` | ❌ | Same V8 signal handler conflict as koffi | Key technical constraints: -- `graal_create_isolate` must run on a pthread with 16MB stack (not a V8/libuv thread) -- All subsequent GraalVM calls must use `graal_attach_thread`/`graal_detach_thread` on dedicated pthreads -- Streaming uses `napi_threadsafe_function` to bridge native pthread callbacks back to the JS event loop +- `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 @@ -34,7 +36,7 @@ native-lib/ ├── tsconfig.json ├── binding.gyp # node-gyp build config for native addon ├── src/ - │ ├── addon.c # N-API native addon (pthreads + GraalVM FFI) + │ ├── 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 @@ -84,17 +86,18 @@ dw.cleanup(); ## Implementation Status ### ✅ Phase 1: Core Package Structure -- `package.json` with node-addon-api, node-gyp, vitest, typescript +- `package.json` with node-gyp, vitest, typescript (no runtime dependencies) - `tsconfig.json` (CommonJS, ES2022, strict) -- `binding.gyp` (C11, NAPI_VERSION=8, platform-specific flags) +- `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 pthreads -- `initialize`: pthread with 16MB stack → `graal_create_isolate` -- `runScript`: pthread with 2MB stack → `attach_thread` + `run_script` + `detach_thread` -- `runScriptStreaming`: pthread + `napi_threadsafe_function` + sentinel for ordered delivery -- `runScriptTransform`: bidirectional with read tsfn (blocking condvar) + write tsfn + sentinel -- `cleanup`: pthread → `graal_tear_down_isolate`, ref-counted +- 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()` @@ -122,8 +125,10 @@ dw.cleanup(); - `clean` updated to remove node artifacts ### ✅ Phase 8: GitHub Actions CI -- Setup Node.js 18 -- `./gradlew native-lib:buildNodePackage` +- 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 @@ -131,19 +136,19 @@ dw.cleanup(); ### Threading Model ``` -JS Main Thread Dedicated pthreads -───────────────── ────────────────── -initialize() ──────────► pthread(16MB stack) - graal_create_isolate() +JS Main Thread Dedicated threads (via libuv) +───────────────── ────────────────────────────── +initialize() ──────────► uv_thread(16MB stack) + uv_dlopen() + graal_create_isolate() ◄── return -runScript() ───────────► pthread(2MB stack) +runScript() ───────────► uv_thread(2MB stack) graal_attach_thread() run_script() graal_detach_thread() ◄── return result -runScriptStreaming() ──► pthread(2MB stack) +runScriptStreaming() ──► uv_thread(2MB stack) graal_attach_thread() run_script_callback(write_cb) │ @@ -156,7 +161,7 @@ runScriptStreaming() ──► pthread(2MB stack) chunk → JS callback sentinel → resolve promise -cleanup() ─────────────► pthread(2MB stack) +cleanup() ─────────────► uv_thread(2MB stack) graal_tear_down_isolate() ◄── return ``` @@ -173,8 +178,9 @@ Multiple `DataWeave` instances share a single GraalVM isolate. Each `initialize( | Risk | Mitigation | |------|------------| -| V8/GraalVM signal handler conflict | All GraalVM calls on dedicated pthreads (verified working) | +| 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 support | `binding.gyp` + `node-gyp` handles MSVC; needs testing on CI | +| 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 | From a3a9846577ba8edf7867c583c16b43a4a39c80dd Mon Sep 17 00:00:00 2001 From: svacas Date: Thu, 7 May 2026 20:50:41 -0300 Subject: [PATCH 4/6] Fix Linux build: use gnu11 for POSIX extension compatibility libuv's uv/unix.h references pthread_rwlock_t which requires GNU/POSIX extensions. Strict -std=c11 hides these types and also hides strdup. Using -std=gnu11 on Linux enables the required extensions. Co-Authored-By: Claude Opus 4.6 --- native-lib/node/binding.gyp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native-lib/node/binding.gyp b/native-lib/node/binding.gyp index b8ec6f9..a746229 100644 --- a/native-lib/node/binding.gyp +++ b/native-lib/node/binding.gyp @@ -11,7 +11,7 @@ } }], ["OS=='linux'", { - "cflags": ["-std=c11"] + "cflags": ["-std=gnu11"] }] ] } From eca7d4251638530b9f2fec4a97622cc661c70069 Mon Sep 17 00:00:00 2001 From: svacas Date: Fri, 8 May 2026 11:01:38 -0300 Subject: [PATCH 5/6] Optimize runTransform memory: consume sync iterables on-demand Sync iterables (generators, arrays) are now consumed one chunk at a time during the native read callback, avoiding pre-buffering the entire input. This reduces memory from ~658 MB to ~150 MB for 50M elements. Async iterables still require pre-buffering due to the synchronous nature of the native callback. Adds example_streaming.mjs benchmark and documents the sync vs async input behavior in README. Co-Authored-By: Claude Opus 4.6 --- native-lib/README.md | 29 ++++++- native-lib/example_streaming.mjs | 127 +++++++++++++++++++++++++++++++ native-lib/node/src/index.ts | 92 +++++++++++++--------- 3 files changed, 212 insertions(+), 36 deletions(-) create mode 100755 native-lib/example_streaming.mjs diff --git a/native-lib/README.md b/native-lib/README.md index 5e0df39..1498898 100644 --- a/native-lib/README.md +++ b/native-lib/README.md @@ -531,13 +531,38 @@ const output = Buffer.concat(chunks).toString("utf-8"); ### 7) Input and output streaming (bidirectional) -Use `runTransform` to stream both input and output — feed an `AsyncIterable` or `Iterable` in, receive an `AsyncGenerator` out. Ideal for processing large files or network streams with constant memory. +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"; -// Stream a file through DataWeave +// 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", 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/src/index.ts b/native-lib/node/src/index.ts index 7d03a7e..1545170 100644 --- a/native-lib/node/src/index.ts +++ b/native-lib/node/src/index.ts @@ -201,53 +201,77 @@ export class DataWeave { const extraInputs = opts?.inputs ?? {}; const inputsJson = Object.keys(extraInputs).length > 0 ? buildInputsJson(extraInputs) : "{}"; - // Pre-buffer all input chunks (the read callback is synchronous from native side) - const inputBuffers: Buffer[] = []; - let inputDone = false; + const isAsync = Symbol.asyncIterator in (input as object); - const iter = Symbol.asyncIterator in (input as object) - ? (input as AsyncIterable)[Symbol.asyncIterator]() - : (input as Iterable)[Symbol.iterator](); + let readCb: (bufSize: number) => Buffer | null; - // Eagerly read all input (needed because read callback is sync) - const feedPromise = (async () => { + 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 (iter as AsyncIterator).next(); + const { value, done: d } = await asyncIter.next(); if (d) break; inputBuffers.push(Buffer.isBuffer(value) ? value : Buffer.from(value)); } } catch { /* input error = EOF */ } - inputDone = true; - })(); - - // Wait for at least some input to be available - await feedPromise; - - let readOffset = 0; - let currentBuf: Buffer | null = null; - let bufIdx = 0; - - const 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; + + 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 Buffer.from(slice); + return null; } - if (bufIdx < inputBuffers.length) { - currentBuf = inputBuffers[bufIdx++]; + }; + } 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; - continue; } - return null; // EOF - } - }; + }; + } const chunks: Buffer[] = []; let resolveChunk: (() => void) | null = null; From 5a7f2943adf452b791d938768e3413f056b6c18b Mon Sep 17 00:00:00 2001 From: svacas Date: Fri, 8 May 2026 11:38:15 -0300 Subject: [PATCH 6/6] Add Python wheel, Node package, and native lib uploads to CI and release workflows Co-Authored-By: Claude Opus 4.6 --- .github/workflows/ci.yml | 53 ++++++++++++++++++++++- .github/workflows/release.yml | 79 ++++++++++++++++++++++++++++++++++- native-lib/.gitignore | 2 + 3 files changed, 132 insertions(+), 2 deletions(-) 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/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/