diff --git a/.github/workflows/napi-ci.yml b/.github/workflows/napi-ci.yml new file mode 100644 index 0000000..604a2a8 --- /dev/null +++ b/.github/workflows/napi-ci.yml @@ -0,0 +1,82 @@ +name: NAPI-RS CI + +on: + push: + branches: [main] + paths: + - 'node/term-guard/**' + - '.github/workflows/napi-ci.yml' + pull_request: + types: [opened, synchronize, reopened] + paths: + - 'node/term-guard/**' + - '.github/workflows/napi-ci.yml' + +# Prevent concurrent builds from the same branch/PR +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +# Minimal permissions by default +permissions: + contents: read + +env: + DEBUG: napi:* + APP_NAME: term-guard + CARGO_TERM_COLOR: always + RUSTFLAGS: -D warnings + CARGO_INCREMENTAL: 0 + CARGO_NET_RETRY: 10 + +jobs: + build-and-test: + name: Build and Test Node.js Bindings + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: node/term-guard/package-lock.json + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + + - name: Cache Rust dependencies + uses: useblacksmith/rust-cache@v3 + with: + prefix-key: "v2-napi" + shared-key: "napi-linux" + cache-on-failure: true + cache-all-crates: true + cache-targets: true + + - name: Install dependencies + run: | + cd node/term-guard + npm ci + + - name: Build NAPI module + run: | + cd node/term-guard + npm run build + + - name: Test NAPI module + run: | + cd node/term-guard + npm test + + - name: Upload artifact + uses: actions/upload-artifact@v4 + if: github.ref == 'refs/heads/main' + with: + name: node-bindings + path: node/term-guard/*.node + if-no-files-found: error \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6ea83a0..fd49cda 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,17 @@ CLAUDE.md # Logs directory logs/ + +# Node.js dependencies +node_modules/ +node/term-guard/node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.npm +*.tgz + +# Node.js build artifacts +node/term-guard/*.node +node/term-guard/index.node +node/term-guard/artifacts.json diff --git a/Cargo.lock b/Cargo.lock index 458e0af..726e0fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -721,6 +721,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -878,6 +887,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "darling" version = "0.20.11" @@ -1534,7 +1553,8 @@ dependencies = [ [[package]] name = "datafusion-table-providers" version = "0.6.3" -source = "git+https://github.com/datafusion-contrib/datafusion-table-providers.git?tag=v0.6.3#dd29536fee6c6778b97a45264a7a91492fd4e239" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faa01adf138b743d9261bd7f5a4c11836080f853fb3e0c92a4b2ebeb956decff" dependencies = [ "arrow", "arrow-json", @@ -2444,6 +2464,16 @@ version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.0", +] + [[package]] name = "libm" version = "0.2.15" @@ -2692,6 +2722,66 @@ dependencies = [ "uuid", ] +[[package]] +name = "napi" +version = "2.16.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55740c4ae1d8696773c78fdafd5d0e5fe9bc9f1b071c7ba493ba5c413a9184f3" +dependencies = [ + "bitflags", + "ctor", + "napi-derive", + "napi-sys", + "once_cell", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "napi-build" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcae8ad5609d14afb3a3b91dee88c757016261b151e9dcecabf1b2a31a6cab14" + +[[package]] +name = "napi-derive" +version = "2.16.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c" +dependencies = [ + "cfg-if", + "convert_case", + "napi-derive-backend", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "napi-derive-backend" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf" +dependencies = [ + "convert_case", + "once_cell", + "proc-macro2", + "quote", + "regex", + "semver", + "syn", +] + +[[package]] +name = "napi-sys" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3" +dependencies = [ + "libloading", +] + [[package]] name = "native-tls" version = "0.2.14" @@ -4106,7 +4196,7 @@ dependencies = [ [[package]] name = "term-guard" -version = "0.0.1" +version = "0.0.2" dependencies = [ "arrow", "async-trait", @@ -4141,6 +4231,23 @@ dependencies = [ "zeroize", ] +[[package]] +name = "term-guard-node" +version = "0.1.0" +dependencies = [ + "arrow", + "async-trait", + "datafusion", + "napi", + "napi-build", + "napi-derive", + "serde", + "serde_json", + "term-guard", + "thiserror 2.0.16", + "tokio", +] + [[package]] name = "termcolor" version = "1.4.1" diff --git a/Cargo.toml b/Cargo.toml index f82eaae..2ac44e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "term-guard", "examples", + "node/term-guard", ] resolver = "2" diff --git a/node/term-guard/.gitignore b/node/term-guard/.gitignore new file mode 100644 index 0000000..77227c7 --- /dev/null +++ b/node/term-guard/.gitignore @@ -0,0 +1,45 @@ +# Node.js dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.npm + +# Build artifacts +*.node +index.node +artifacts.json +*.tgz + +# TypeScript build files +*.tsbuildinfo +dist/ +lib/ +build/ +*.js.map +*.d.ts.map + +# Keep TypeScript source files +!*.ts +!tsconfig.json + +# Testing +coverage/ +.nyc_output/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# Environment files +.env +.env.local +.env.*.local + +# Temporary files +tmp/ +temp/ \ No newline at end of file diff --git a/node/term-guard/.npmignore b/node/term-guard/.npmignore new file mode 100644 index 0000000..2dc21ab --- /dev/null +++ b/node/term-guard/.npmignore @@ -0,0 +1,25 @@ +target +Cargo.lock +Cargo.toml +build.rs +src/ +*.node +!index.node +test/ +benches/ +.github/ +.gitignore +tsconfig.json +yarn.lock +package-lock.json +pnpm-lock.yaml +.cargo/ +examples/ +docs/ +*.log +.DS_Store +thumbs.db +# TypeScript source files +*.ts +!*.d.ts +index.js \ No newline at end of file diff --git a/node/term-guard/Cargo.toml b/node/term-guard/Cargo.toml new file mode 100644 index 0000000..816e9ad --- /dev/null +++ b/node/term-guard/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "term-guard-node" +version = "0.1.0" +edition = "2021" +authors = ["Eric P. Simon "] +license = "MIT" +description = "Node.js bindings for Term data validation library" +repository = "https://github.com/withterm/term" +keywords = ["data-validation", "data-quality", "nodejs", "napi"] + +[lib] +crate-type = ["cdylib"] + +[dependencies] +# NAPI dependencies +napi = { version = "2", default-features = false, features = ["napi8", "async", "serde-json"] } +napi-derive = "2" + +# Term dependency +term-guard = { path = "../../term-guard", version = "0.0.2" } + +# DataFusion and Arrow for data handling +datafusion = "48.0.1" +arrow = "55.2" + +# Async runtime +tokio = { version = "1", features = ["full"] } +async-trait = "0.1" + +# Serialization +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +# Error handling +thiserror = "2" + +[build-dependencies] +napi-build = "2" + +[profile.release] +lto = true +strip = true +opt-level = 3 \ No newline at end of file diff --git a/node/term-guard/build.rs b/node/term-guard/build.rs new file mode 100644 index 0000000..9fc2367 --- /dev/null +++ b/node/term-guard/build.rs @@ -0,0 +1,5 @@ +extern crate napi_build; + +fn main() { + napi_build::setup(); +} diff --git a/node/term-guard/index.d.ts b/node/term-guard/index.d.ts new file mode 100644 index 0000000..db93c6c --- /dev/null +++ b/node/term-guard/index.d.ts @@ -0,0 +1,201 @@ +/* tslint:disable */ +/* eslint-disable */ + +/* auto-generated by NAPI-RS */ + +/** + * Level enum representing severity of validation checks + */ +export const enum Level { + Error = 0, + Warning = 1, + Info = 2 +} + +/** + * Status of a constraint evaluation + */ +export const enum ConstraintStatus { + Success = 0, + Failure = 1, + Skipped = 2 +} + +/** + * A validation issue found during suite execution + */ +export interface ValidationIssue { + checkName: string + level: string + message: string +} + +/** + * Report containing validation results + */ +export interface ValidationReport { + suiteName: string + totalChecks: number + passedChecks: number + failedChecks: number + issues: Array +} + +/** + * Performance metrics for validation execution + */ +export interface PerformanceMetrics { + totalDurationMs: number + checksPerSecond: number +} + +/** + * Result of running a validation suite + */ +export interface ValidationResult { + status: string + report: ValidationReport + metrics?: PerformanceMetrics +} + +/** + * Information about the Term Guard library + */ +export interface ValidationInfo { + name: string + version: string + rustVersion: string +} + +/** + * A validation check that can be added to a validation suite + */ +export declare class Check { + /** Get the name of this check */ + get name(): string + /** Get the severity level of this check */ + get level(): Level + /** Get the description of this check */ + get description(): string | null +} + +/** + * Builder for creating validation checks + */ +export declare class CheckBuilder { + constructor(name: string) + + /** Set the severity level for this check */ + level(level: Level): this + + /** Set a description for this check */ + description(desc: string): this + + /** Create a completeness check for a column */ + isComplete(column: string, ratio?: number | undefined | null): Check + + /** Create a minimum value check for a column */ + hasMin(column: string, minValue: number): Check + + /** Create a maximum value check for a column */ + hasMax(column: string, maxValue: number): Check + + /** Create a uniqueness check for a column */ + isUnique(column: string): Check + + /** Create a mean value check for a column */ + hasMean(column: string, expected: number, tolerance?: number | undefined | null): Check + + /** Build a generic check */ + build(): Check +} + +/** + * A suite of validation checks to run against data + */ +export declare class ValidationSuite { + /** Create a new ValidationSuiteBuilder */ + static builder(name: string): ValidationSuiteBuilder + + /** Get the name of this validation suite */ + get name(): string + + /** Get the description of this validation suite */ + get description(): string | null + + /** Get the number of checks in this suite */ + get checkCount(): number + + /** Run the validation suite against the provided data source */ + run(data: DataSource): Promise +} + +/** + * Builder for creating validation suites + */ +export declare class ValidationSuiteBuilder { + constructor(name: string) + + /** Set a description for this validation suite */ + description(desc: string): this + + /** Add a single check to the validation suite */ + addCheck(check: Check): this + + /** Add multiple checks to the validation suite */ + addChecks(checks: Array): this + + /** Build the validation suite */ + build(): ValidationSuite +} + +/** + * A data source for validation + */ +export declare class DataSource { + /** Create a DataSource from a Parquet file */ + static fromParquet(path: string): Promise + + /** Create a DataSource from a CSV file */ + static fromCsv(path: string): Promise + + /** Create a DataSource from a JSON file */ + static fromJson(path: string): Promise + + /** Get the row count of the data */ + getRowCount(): Promise + + /** Get the column names of the data */ + getColumnNames(): Promise> + + /** Get the table name */ + get tableName(): string +} + +/** + * Builder for creating data sources with multiple tables + */ +export declare class DataSourceBuilder { + constructor() + + /** Register a Parquet file as a table */ + registerParquet(name: string, path: string): Promise + + /** Register a CSV file as a table */ + registerCsv(name: string, path: string): Promise + + /** Build the DataSource */ + build(): DataSource +} + +/** Get a greeting from Term Guard */ +export function helloTerm(): string + +/** Get the version of the Term Guard library */ +export function getVersion(): string + +/** Get information about the Term Guard library */ +export function getInfo(): ValidationInfo + +/** Example function to validate sample data */ +export function validateSampleData(path: string): Promise diff --git a/node/term-guard/index.ts b/node/term-guard/index.ts new file mode 100644 index 0000000..9188353 --- /dev/null +++ b/node/term-guard/index.ts @@ -0,0 +1,254 @@ +/* tslint:disable */ +/* eslint-disable */ +/* prettier-ignore */ + +/* auto-generated by NAPI-RS */ + +import { existsSync, readFileSync } from 'fs'; +import { join } from 'path'; + +const { platform, arch } = process; + +let nativeBinding: any = null; +let localFileExisted = false; +let loadError: Error | null = null; + +function isMusl(): boolean { + // For Node 10 + if (!process.report || typeof process.report.getReport !== 'function') { + try { + const lddPath = require('child_process').execSync('which ldd 2>/dev/null', { encoding: 'utf8' }); + return readFileSync(lddPath, 'utf8').includes('musl'); + } catch (e) { + return true; + } + } else { + const { glibcVersionRuntime } = (process.report.getReport() as any).header; + return !glibcVersionRuntime; + } +} + +switch (platform) { + case 'android': + switch (arch) { + case 'arm64': + localFileExisted = existsSync(join(__dirname, 'term-guard.android-arm64.node')); + try { + if (localFileExisted) { + nativeBinding = require('./term-guard.android-arm64.node'); + } else { + nativeBinding = require('@withterm/term-guard-android-arm64'); + } + } catch (e) { + loadError = e as Error; + } + break; + case 'arm': + localFileExisted = existsSync(join(__dirname, 'term-guard.android-arm-eabi.node')); + try { + if (localFileExisted) { + nativeBinding = require('./term-guard.android-arm-eabi.node'); + } else { + nativeBinding = require('@withterm/term-guard-android-arm-eabi'); + } + } catch (e) { + loadError = e as Error; + } + break; + default: + throw new Error(`Unsupported architecture on Android ${arch}`); + } + break; + case 'win32': + switch (arch) { + case 'x64': + localFileExisted = existsSync( + join(__dirname, 'term-guard.win32-x64-msvc.node') + ); + try { + if (localFileExisted) { + nativeBinding = require('./term-guard.win32-x64-msvc.node'); + } else { + nativeBinding = require('@withterm/term-guard-win32-x64-msvc'); + } + } catch (e) { + loadError = e as Error; + } + break; + case 'ia32': + localFileExisted = existsSync( + join(__dirname, 'term-guard.win32-ia32-msvc.node') + ); + try { + if (localFileExisted) { + nativeBinding = require('./term-guard.win32-ia32-msvc.node'); + } else { + nativeBinding = require('@withterm/term-guard-win32-ia32-msvc'); + } + } catch (e) { + loadError = e as Error; + } + break; + case 'arm64': + localFileExisted = existsSync( + join(__dirname, 'term-guard.win32-arm64-msvc.node') + ); + try { + if (localFileExisted) { + nativeBinding = require('./term-guard.win32-arm64-msvc.node'); + } else { + nativeBinding = require('@withterm/term-guard-win32-arm64-msvc'); + } + } catch (e) { + loadError = e as Error; + } + break; + default: + throw new Error(`Unsupported architecture on Windows: ${arch}`); + } + break; + case 'darwin': + localFileExisted = existsSync(join(__dirname, 'term-guard.darwin-universal.node')); + try { + if (localFileExisted) { + nativeBinding = require('./term-guard.darwin-universal.node'); + } else { + nativeBinding = require('@withterm/term-guard-darwin-universal'); + } + } catch {} + switch (arch) { + case 'x64': + localFileExisted = existsSync(join(__dirname, 'term-guard.darwin-x64.node')); + try { + if (localFileExisted) { + nativeBinding = require('./term-guard.darwin-x64.node'); + } else { + nativeBinding = require('@withterm/term-guard-darwin-x64'); + } + } catch (e) { + loadError = e as Error; + } + break; + case 'arm64': + localFileExisted = existsSync( + join(__dirname, 'term-guard.darwin-arm64.node') + ); + try { + if (localFileExisted) { + nativeBinding = require('./term-guard.darwin-arm64.node'); + } else { + nativeBinding = require('@withterm/term-guard-darwin-arm64'); + } + } catch (e) { + loadError = e as Error; + } + break; + default: + throw new Error(`Unsupported architecture on macOS: ${arch}`); + } + break; + case 'freebsd': + if (arch !== 'x64') { + throw new Error(`Unsupported architecture on FreeBSD: ${arch}`); + } + localFileExisted = existsSync(join(__dirname, 'term-guard.freebsd-x64.node')); + try { + if (localFileExisted) { + nativeBinding = require('./term-guard.freebsd-x64.node'); + } else { + nativeBinding = require('@withterm/term-guard-freebsd-x64'); + } + } catch (e) { + loadError = e as Error; + } + break; + case 'linux': + switch (arch) { + case 'x64': + if (isMusl()) { + localFileExisted = existsSync( + join(__dirname, 'term-guard.linux-x64-musl.node') + ); + try { + if (localFileExisted) { + nativeBinding = require('./term-guard.linux-x64-musl.node'); + } else { + nativeBinding = require('@withterm/term-guard-linux-x64-musl'); + } + } catch (e) { + loadError = e as Error; + } + } else { + localFileExisted = existsSync( + join(__dirname, 'term-guard.linux-x64-gnu.node') + ); + try { + if (localFileExisted) { + nativeBinding = require('./term-guard.linux-x64-gnu.node'); + } else { + nativeBinding = require('@withterm/term-guard-linux-x64-gnu'); + } + } catch (e) { + loadError = e as Error; + } + } + break; + case 'arm64': + if (isMusl()) { + localFileExisted = existsSync( + join(__dirname, 'term-guard.linux-arm64-musl.node') + ); + try { + if (localFileExisted) { + nativeBinding = require('./term-guard.linux-arm64-musl.node'); + } else { + nativeBinding = require('@withterm/term-guard-linux-arm64-musl'); + } + } catch (e) { + loadError = e as Error; + } + } else { + localFileExisted = existsSync( + join(__dirname, 'term-guard.linux-arm64-gnu.node') + ); + try { + if (localFileExisted) { + nativeBinding = require('./term-guard.linux-arm64-gnu.node'); + } else { + nativeBinding = require('@withterm/term-guard-linux-arm64-gnu'); + } + } catch (e) { + loadError = e as Error; + } + } + break; + case 'arm': + localFileExisted = existsSync( + join(__dirname, 'term-guard.linux-arm-gnueabihf.node') + ); + try { + if (localFileExisted) { + nativeBinding = require('./term-guard.linux-arm-gnueabihf.node'); + } else { + nativeBinding = require('@withterm/term-guard-linux-arm-gnueabihf'); + } + } catch (e) { + loadError = e as Error; + } + break; + default: + throw new Error(`Unsupported architecture on Linux: ${arch}`); + } + break; + default: + throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`); +} + +if (!nativeBinding) { + if (loadError) { + throw loadError; + } + throw new Error(`Failed to load native binding`); +} + +export = nativeBinding; \ No newline at end of file diff --git a/node/term-guard/package-lock.json b/node/term-guard/package-lock.json new file mode 100644 index 0000000..974b856 --- /dev/null +++ b/node/term-guard/package-lock.json @@ -0,0 +1,625 @@ +{ + "name": "@withterm/term-guard", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@withterm/term-guard", + "version": "0.1.0", + "license": "MIT", + "devDependencies": { + "@napi-rs/cli": "^2.18.3", + "@types/node": "^20.14.0", + "tsx": "^4.7.0", + "typescript": "^5.4.5" + }, + "engines": { + "node": ">= 16" + }, + "optionalDependencies": { + "@withterm/term-guard-android-arm64": "0.1.0", + "@withterm/term-guard-darwin-arm64": "0.1.0", + "@withterm/term-guard-darwin-x64": "0.1.0", + "@withterm/term-guard-linux-arm-gnueabihf": "0.1.0", + "@withterm/term-guard-linux-arm64-gnu": "0.1.0", + "@withterm/term-guard-linux-arm64-musl": "0.1.0", + "@withterm/term-guard-linux-x64-gnu": "0.1.0", + "@withterm/term-guard-linux-x64-musl": "0.1.0", + "@withterm/term-guard-win32-arm64-msvc": "0.1.0", + "@withterm/term-guard-win32-ia32-msvc": "0.1.0", + "@withterm/term-guard-win32-x64-msvc": "0.1.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", + "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", + "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", + "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", + "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", + "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", + "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", + "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", + "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", + "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", + "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", + "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", + "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", + "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", + "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", + "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", + "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", + "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", + "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", + "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", + "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", + "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", + "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", + "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", + "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", + "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", + "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@napi-rs/cli": { + "version": "2.18.4", + "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.18.4.tgz", + "integrity": "sha512-SgJeA4df9DE2iAEpr3M2H0OKl/yjtg1BnRI5/JyowS71tUWhrfSu2LT0V3vlHET+g1hBVlrO60PmEXwUEKp8Mg==", + "dev": true, + "license": "MIT", + "bin": { + "napi": "scripts/index.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@types/node": { + "version": "20.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.19.tgz", + "integrity": "sha512-pb1Uqj5WJP7wrcbLU7Ru4QtA0+3kAXrkutGiD26wUKzSMgNNaPARTUDQmElUXp64kh3cWdou3Q0C7qwwxqSFmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/esbuild": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", + "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.10", + "@esbuild/android-arm": "0.25.10", + "@esbuild/android-arm64": "0.25.10", + "@esbuild/android-x64": "0.25.10", + "@esbuild/darwin-arm64": "0.25.10", + "@esbuild/darwin-x64": "0.25.10", + "@esbuild/freebsd-arm64": "0.25.10", + "@esbuild/freebsd-x64": "0.25.10", + "@esbuild/linux-arm": "0.25.10", + "@esbuild/linux-arm64": "0.25.10", + "@esbuild/linux-ia32": "0.25.10", + "@esbuild/linux-loong64": "0.25.10", + "@esbuild/linux-mips64el": "0.25.10", + "@esbuild/linux-ppc64": "0.25.10", + "@esbuild/linux-riscv64": "0.25.10", + "@esbuild/linux-s390x": "0.25.10", + "@esbuild/linux-x64": "0.25.10", + "@esbuild/netbsd-arm64": "0.25.10", + "@esbuild/netbsd-x64": "0.25.10", + "@esbuild/openbsd-arm64": "0.25.10", + "@esbuild/openbsd-x64": "0.25.10", + "@esbuild/openharmony-arm64": "0.25.10", + "@esbuild/sunos-x64": "0.25.10", + "@esbuild/win32-arm64": "0.25.10", + "@esbuild/win32-ia32": "0.25.10", + "@esbuild/win32-x64": "0.25.10" + } + }, + "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/get-tsconfig": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.12.0.tgz", + "integrity": "sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/tsx": { + "version": "4.20.6", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", + "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "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" + } + } +} diff --git a/node/term-guard/package.json b/node/term-guard/package.json new file mode 100644 index 0000000..3cf5bff --- /dev/null +++ b/node/term-guard/package.json @@ -0,0 +1,79 @@ +{ + "name": "@withterm/term-guard", + "version": "0.1.0", + "description": "High-performance data validation library for Node.js, powered by Rust", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/withterm/term" + }, + "keywords": [ + "data-validation", + "data-quality", + "deequ", + "datafusion", + "rust", + "napi", + "validation" + ], + "author": "Eric P. Simon ", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "napi": { + "name": "term-guard", + "triples": { + "defaults": true, + "additional": [ + "x86_64-pc-windows-msvc", + "aarch64-pc-windows-msvc", + "x86_64-apple-darwin", + "aarch64-apple-darwin", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-linux-musl", + "aarch64-unknown-linux-gnu", + "aarch64-linux-android", + "armv7-unknown-linux-gnueabihf", + "aarch64-unknown-linux-musl", + "aarch64-pc-windows-msvc" + ] + } + }, + "scripts": { + "artifacts": "napi artifacts", + "build": "tsc && napi build --platform --release", + "build:ts": "tsc", + "build:napi": "napi build --platform --release", + "build:debug": "napi build --platform", + "prebuild": "npm run build:ts", + "prepublishOnly": "napi prepublish -t npm && npm run build:ts", + "test": "tsx test/index.test.ts", + "universal": "napi universal", + "version": "napi version" + }, + "devDependencies": { + "@napi-rs/cli": "^2.18.3", + "@types/node": "^20.14.0", + "typescript": "^5.4.5", + "tsx": "^4.7.0" + }, + "optionalDependencies": { + "@withterm/term-guard-win32-x64-msvc": "0.1.0", + "@withterm/term-guard-darwin-x64": "0.1.0", + "@withterm/term-guard-linux-x64-gnu": "0.1.0", + "@withterm/term-guard-linux-x64-musl": "0.1.0", + "@withterm/term-guard-linux-arm64-gnu": "0.1.0", + "@withterm/term-guard-linux-arm64-musl": "0.1.0", + "@withterm/term-guard-darwin-arm64": "0.1.0", + "@withterm/term-guard-android-arm64": "0.1.0", + "@withterm/term-guard-linux-arm-gnueabihf": "0.1.0", + "@withterm/term-guard-win32-ia32-msvc": "0.1.0", + "@withterm/term-guard-win32-arm64-msvc": "0.1.0" + }, + "files": [ + "dist/**/*", + "*.node" + ] +} \ No newline at end of file diff --git a/node/term-guard/src/check.rs b/node/term-guard/src/check.rs new file mode 100644 index 0000000..de74281 --- /dev/null +++ b/node/term-guard/src/check.rs @@ -0,0 +1,182 @@ +use crate::types::Level; +use napi::bindgen_prelude::*; +use napi_derive::napi; +use std::sync::Arc; +use term_guard::core::{Check as CoreCheck, Level as CoreLevel}; + +#[napi] +pub struct Check { + inner: Arc, +} + +#[napi] +impl Check { + #[napi(getter)] + pub fn name(&self) -> String { + self.inner.name.clone() + } + + #[napi(getter)] + pub fn level(&self) -> Level { + self.inner.level.clone().into() + } + + #[napi(getter)] + pub fn description(&self) -> Option { + self.inner.description.clone() + } +} + +#[napi] +pub struct CheckBuilder { + name: String, + level: CoreLevel, + description: Option, +} + +#[napi] +impl CheckBuilder { + #[napi(constructor)] + pub fn new(name: String) -> Self { + CheckBuilder { + name, + level: CoreLevel::Error, + description: None, + } + } + + #[napi] + pub fn level(&mut self, level: Level) -> &Self { + self.level = level.into(); + self + } + + #[napi] + pub fn description(&mut self, desc: String) -> &Self { + self.description = Some(desc); + self + } + + #[napi] + pub fn is_complete(&mut self, column: String, ratio: Option) -> Result { + let mut builder = CoreCheck::builder(&self.name).level(self.level.clone()); + + if let Some(desc) = &self.description { + builder = builder.description(desc); + } + + let threshold = ratio.unwrap_or(1.0); + let check = builder + .is_complete(&column, threshold) + .build() + .map_err(|e| Error::from_reason(e.to_string()))?; + + Ok(Check { + inner: Arc::new(check), + }) + } + + #[napi] + pub fn has_min(&mut self, column: String, min_value: f64) -> Result { + let mut builder = CoreCheck::builder(&self.name).level(self.level.clone()); + + if let Some(desc) = &self.description { + builder = builder.description(desc); + } + + let check = builder + .has_min(&column, min_value) + .build() + .map_err(|e| Error::from_reason(e.to_string()))?; + + Ok(Check { + inner: Arc::new(check), + }) + } + + #[napi] + pub fn has_max(&mut self, column: String, max_value: f64) -> Result { + let mut builder = CoreCheck::builder(&self.name).level(self.level.clone()); + + if let Some(desc) = &self.description { + builder = builder.description(desc); + } + + let check = builder + .has_max(&column, max_value) + .build() + .map_err(|e| Error::from_reason(e.to_string()))?; + + Ok(Check { + inner: Arc::new(check), + }) + } + + #[napi] + pub fn is_unique(&mut self, column: String) -> Result { + let mut builder = CoreCheck::builder(&self.name).level(self.level.clone()); + + if let Some(desc) = &self.description { + builder = builder.description(desc); + } + + let check = builder + .is_unique(&column) + .build() + .map_err(|e| Error::from_reason(e.to_string()))?; + + Ok(Check { + inner: Arc::new(check), + }) + } + + #[napi] + pub fn has_mean( + &mut self, + column: String, + expected: f64, + tolerance: Option, + ) -> Result { + let mut builder = CoreCheck::builder(&self.name).level(self.level.clone()); + + if let Some(desc) = &self.description { + builder = builder.description(desc); + } + + let tol = tolerance.unwrap_or(0.01); + let check = builder + .has_mean(&column, expected, tol) + .build() + .map_err(|e| Error::from_reason(e.to_string()))?; + + Ok(Check { + inner: Arc::new(check), + }) + } + + #[napi] + pub fn build(&mut self) -> Result { + // Generic build for simple checks + let mut builder = CoreCheck::builder(&self.name).level(self.level.clone()); + + if let Some(desc) = &self.description { + builder = builder.description(desc); + } + + // Default to a simple row count check + let check = builder + .has_size(|size| size > 0) + .build() + .map_err(|e| Error::from_reason(e.to_string()))?; + + Ok(Check { + inner: Arc::new(check), + }) + } +} + +impl Check { + pub(crate) fn get_inner(&self) -> Arc { + self.inner.clone() + } +} diff --git a/node/term-guard/src/data_source.rs b/node/term-guard/src/data_source.rs new file mode 100644 index 0000000..612de7b --- /dev/null +++ b/node/term-guard/src/data_source.rs @@ -0,0 +1,151 @@ +use datafusion::prelude::*; +use napi::bindgen_prelude::*; +use napi_derive::napi; +use std::sync::Arc; +use tokio::sync::Mutex; + +#[napi] +pub struct DataSource { + ctx: Arc>, + table_name: String, +} + +#[napi] +impl DataSource { + #[napi(factory)] + pub async fn from_parquet(path: String) -> Result { + let ctx = SessionContext::new(); + + // Register the parquet file as a table + ctx.register_parquet("data", &path, ParquetReadOptions::default()) + .await + .map_err(|e| Error::from_reason(format!("Failed to read parquet file: {}", e)))?; + + Ok(DataSource { + ctx: Arc::new(Mutex::new(ctx)), + table_name: "data".to_string(), + }) + } + + #[napi(factory)] + pub async fn from_csv(path: String) -> Result { + let ctx = SessionContext::new(); + + // Register the CSV file as a table + ctx.register_csv("data", &path, CsvReadOptions::default()) + .await + .map_err(|e| Error::from_reason(format!("Failed to read CSV file: {}", e)))?; + + Ok(DataSource { + ctx: Arc::new(Mutex::new(ctx)), + table_name: "data".to_string(), + }) + } + + #[napi(factory)] + pub async fn from_json(path: String) -> Result { + let ctx = SessionContext::new(); + + // Register the JSON file as a table + ctx.register_json("data", &path, NdJsonReadOptions::default()) + .await + .map_err(|e| Error::from_reason(format!("Failed to read JSON file: {}", e)))?; + + Ok(DataSource { + ctx: Arc::new(Mutex::new(ctx)), + table_name: "data".to_string(), + }) + } + + #[napi] + pub async fn get_row_count(&self) -> Result { + let ctx = self.ctx.lock().await; + let df = ctx + .sql("SELECT COUNT(*) as count FROM data") + .await + .map_err(|e| Error::from_reason(e.to_string()))?; + + let batches = df + .collect() + .await + .map_err(|e| Error::from_reason(e.to_string()))?; + + if let Some(batch) = batches.first() { + if let Some(col) = batch + .column(0) + .as_any() + .downcast_ref::() + { + if let Some(count) = col.value(0).try_into().ok() { + return Ok(count); + } + } + } + + Ok(0) + } + + #[napi] + pub async fn get_column_names(&self) -> Result> { + let ctx = self.ctx.lock().await; + let df = ctx + .table("data") + .await + .map_err(|e| Error::from_reason(e.to_string()))?; + + let schema = df.schema(); + let fields = schema.fields(); + + Ok(fields.iter().map(|f| f.name().clone()).collect()) + } + + #[napi(getter)] + pub fn table_name(&self) -> String { + self.table_name.clone() + } + + pub(crate) async fn get_context(&self) -> Result { + Ok(self.ctx.lock().await.clone()) + } +} + +#[napi] +pub struct DataSourceBuilder { + ctx: SessionContext, +} + +#[napi] +impl DataSourceBuilder { + #[napi(constructor)] + pub fn new() -> Self { + DataSourceBuilder { + ctx: SessionContext::new(), + } + } + + #[napi] + pub async fn register_parquet(&mut self, name: String, path: String) -> Result<&Self> { + self.ctx + .register_parquet(&name, &path, ParquetReadOptions::default()) + .await + .map_err(|e| Error::from_reason(format!("Failed to register parquet: {}", e)))?; + Ok(self) + } + + #[napi] + pub async fn register_csv(&mut self, name: String, path: String) -> Result<&Self> { + self.ctx + .register_csv(&name, &path, CsvReadOptions::default()) + .await + .map_err(|e| Error::from_reason(format!("Failed to register CSV: {}", e)))?; + Ok(self) + } + + #[napi] + pub fn build(&self) -> Result { + Ok(DataSource { + ctx: Arc::new(Mutex::new(self.ctx.clone())), + table_name: "data".to_string(), + }) + } +} diff --git a/node/term-guard/src/lib.rs b/node/term-guard/src/lib.rs new file mode 100644 index 0000000..548c777 --- /dev/null +++ b/node/term-guard/src/lib.rs @@ -0,0 +1,70 @@ +#![deny(clippy::all)] + +mod check; +mod data_source; +mod types; +mod validation_suite; + +use napi::bindgen_prelude::*; +use napi_derive::napi; + +// Re-export the main types for the NAPI interface +pub use check::{Check, CheckBuilder}; +pub use data_source::{DataSource, DataSourceBuilder}; +pub use types::{ + ConstraintStatus, Level, PerformanceMetrics, ValidationIssue, ValidationReport, + ValidationResult, +}; +pub use validation_suite::{ValidationSuite, ValidationSuiteBuilder}; + +#[napi] +pub fn hello_term() -> String { + "Hello from Term Guard! Data validation powered by Rust.".to_string() +} + +#[napi] +pub fn get_version() -> String { + env!("CARGO_PKG_VERSION").to_string() +} + +#[napi(object)] +pub struct ValidationInfo { + pub name: String, + pub version: String, + pub rust_version: String, +} + +#[napi] +pub fn get_info() -> ValidationInfo { + ValidationInfo { + name: "term-guard".to_string(), + version: env!("CARGO_PKG_VERSION").to_string(), + rust_version: "1.70+".to_string(), + } +} + +/// Example usage function demonstrating the full API +#[napi] +pub async fn validate_sample_data(path: String) -> Result { + // Create a data source from a CSV file + let data_source = DataSource::from_csv(path).await?; + + // Create some checks + let completeness_check = CheckBuilder::new("completeness_check".to_string()) + .description("Check for data completeness".to_string()) + .is_complete("column1".to_string(), Some(0.95))?; + + // Build a validation suite + let suite = ValidationSuiteBuilder::new("sample_suite".to_string()) + .description("Sample validation suite".to_string()) + .add_check(&completeness_check) + .build()?; + + // Run the validation + let result = suite.run(&data_source).await?; + + Ok(format!( + "Validation {}: {} checks passed, {} failed", + result.status, result.report.passed_checks, result.report.failed_checks + )) +} diff --git a/node/term-guard/src/types.rs b/node/term-guard/src/types.rs new file mode 100644 index 0000000..d7fbe2c --- /dev/null +++ b/node/term-guard/src/types.rs @@ -0,0 +1,76 @@ +use napi::bindgen_prelude::*; +use napi_derive::napi; +use term_guard::core::{ConstraintStatus as CoreStatus, Level as CoreLevel}; + +#[napi] +pub enum Level { + Error, + Warning, + Info, +} + +impl From for Level { + fn from(level: CoreLevel) -> Self { + match level { + CoreLevel::Error => Level::Error, + CoreLevel::Warning => Level::Warning, + CoreLevel::Info => Level::Info, + } + } +} + +impl From for CoreLevel { + fn from(level: Level) -> Self { + match level { + Level::Error => CoreLevel::Error, + Level::Warning => CoreLevel::Warning, + Level::Info => CoreLevel::Info, + } + } +} + +#[napi(object)] +pub struct ValidationIssue { + pub check_name: String, + pub level: String, + pub message: String, +} + +#[napi(object)] +pub struct ValidationReport { + pub suite_name: String, + pub total_checks: u32, + pub passed_checks: u32, + pub failed_checks: u32, + pub issues: Vec, +} + +#[napi(object)] +pub struct PerformanceMetrics { + pub total_duration_ms: f64, + pub checks_per_second: f64, +} + +#[napi(object)] +pub struct ValidationResult { + pub status: String, + pub report: ValidationReport, + pub metrics: Option, +} + +#[napi] +pub enum ConstraintStatus { + Success, + Failure, + Skipped, +} + +impl From for ConstraintStatus { + fn from(status: CoreStatus) -> Self { + match status { + CoreStatus::Success => ConstraintStatus::Success, + CoreStatus::Failure => ConstraintStatus::Failure, + CoreStatus::Skipped => ConstraintStatus::Skipped, + } + } +} diff --git a/node/term-guard/src/validation_suite.rs b/node/term-guard/src/validation_suite.rs new file mode 100644 index 0000000..0c37e15 --- /dev/null +++ b/node/term-guard/src/validation_suite.rs @@ -0,0 +1,174 @@ +use crate::check::Check; +use crate::data_source::DataSource; +use crate::types::{PerformanceMetrics, ValidationIssue, ValidationReport, ValidationResult}; +use datafusion::prelude::SessionContext; +use napi::bindgen_prelude::*; +use napi_derive::napi; +use std::sync::Arc; +use std::time::Instant; +use term_guard::core::{ValidationReport as CoreReport, ValidationSuite as CoreValidationSuite}; + +#[napi] +pub struct ValidationSuite { + inner: Arc, +} + +#[napi] +impl ValidationSuite { + #[napi(factory)] + pub fn builder(name: String) -> ValidationSuiteBuilder { + ValidationSuiteBuilder::new(name) + } + + #[napi(getter)] + pub fn name(&self) -> String { + self.inner.name.clone() + } + + #[napi(getter)] + pub fn description(&self) -> Option { + self.inner.description.clone() + } + + #[napi] + pub async fn run(&self, data: &DataSource) -> Result { + let start = Instant::now(); + + // Get the SessionContext from the DataSource + let ctx = data.get_context().await?; + + // Run the validation suite + let report = self + .inner + .run(&ctx) + .await + .map_err(|e| Error::from_reason(e.to_string()))?; + + let duration = start.elapsed(); + let duration_ms = duration.as_secs_f64() * 1000.0; + + // Convert the core report to our NAPI types + let validation_report = convert_report(&report); + + let metrics = Some(PerformanceMetrics { + total_duration_ms: duration_ms, + checks_per_second: if duration_ms > 0.0 { + (validation_report.total_checks as f64) / (duration_ms / 1000.0) + } else { + 0.0 + }, + }); + + Ok(ValidationResult { + status: if validation_report.failed_checks == 0 { + "success".to_string() + } else { + "failure".to_string() + }, + report: validation_report, + metrics, + }) + } + + #[napi(getter)] + pub fn check_count(&self) -> u32 { + self.inner.checks.len() as u32 + } +} + +#[napi] +pub struct ValidationSuiteBuilder { + name: String, + description: Option, + checks: Vec>, +} + +#[napi] +impl ValidationSuiteBuilder { + #[napi(constructor)] + pub fn new(name: String) -> Self { + ValidationSuiteBuilder { + name, + description: None, + checks: Vec::new(), + } + } + + #[napi] + pub fn description(&mut self, desc: String) -> &Self { + self.description = Some(desc); + self + } + + #[napi] + pub fn add_check(&mut self, check: &Check) -> &Self { + self.checks.push(check.get_inner()); + self + } + + #[napi] + pub fn add_checks(&mut self, checks: Vec<&Check>) -> &Self { + for check in checks { + self.checks.push(check.get_inner()); + } + self + } + + #[napi] + pub fn build(&self) -> Result { + let mut builder = CoreValidationSuite::builder(&self.name); + + if let Some(desc) = &self.description { + builder = builder.description(desc); + } + + for check in &self.checks { + builder = builder.add_check_arc(check.clone()); + } + + let suite = builder + .build() + .map_err(|e| Error::from_reason(e.to_string()))?; + + Ok(ValidationSuite { + inner: Arc::new(suite), + }) + } +} + +fn convert_report(report: &CoreReport) -> ValidationReport { + let issues: Vec = report + .check_results + .iter() + .filter_map(|result| { + if result.status != term_guard::core::ConstraintStatus::Success { + Some(ValidationIssue { + check_name: result.check_name.clone(), + level: format!("{:?}", result.level), + message: result + .message + .clone() + .unwrap_or_else(|| format!("Check {} failed", result.check_name)), + }) + } else { + None + } + }) + .collect(); + + let total = report.check_results.len() as u32; + let passed = report + .check_results + .iter() + .filter(|r| r.status == term_guard::core::ConstraintStatus::Success) + .count() as u32; + let failed = total - passed; + + ValidationReport { + suite_name: report.suite_name.clone(), + total_checks: total, + passed_checks: passed, + failed_checks: failed, + issues, + } +} diff --git a/node/term-guard/test/index.test.ts b/node/term-guard/test/index.test.ts new file mode 100644 index 0000000..c6bb27b --- /dev/null +++ b/node/term-guard/test/index.test.ts @@ -0,0 +1,281 @@ +import test from 'node:test'; +import assert from 'node:assert'; +import * as path from 'path'; +import * as fs from 'fs/promises'; + +// Load the native module +import * as termGuard from '../index'; + +test('Basic module functions work', () => { + // Test hello function + const greeting = termGuard.helloTerm(); + assert.strictEqual(typeof greeting, 'string'); + assert.ok(greeting.includes('Term Guard')); + + // Test version function + const version = termGuard.getVersion(); + assert.strictEqual(typeof version, 'string'); + assert.match(version, /^\d+\.\d+\.\d+$/); + + // Test info function + const info = termGuard.getInfo(); + assert.strictEqual(typeof info, 'object'); + assert.strictEqual(info.name, 'term-guard'); + assert.ok(info.rustVersion.includes('1.70')); +}); + +test('Level enum is exported correctly', () => { + assert.ok(termGuard.Level); + assert.strictEqual(termGuard.Level.Error, 0); + assert.strictEqual(termGuard.Level.Warning, 1); + assert.strictEqual(termGuard.Level.Info, 2); +}); + +test('CheckBuilder can be created and configured', () => { + const builder = new termGuard.CheckBuilder('test_check'); + assert.ok(builder); + + // Test method chaining + const result = builder.level(termGuard.Level.Warning); + assert.strictEqual(result, builder, 'Should return self for chaining'); + + const result2 = builder.description('Test description'); + assert.strictEqual(result2, builder, 'Should return self for chaining'); +}); + +test('CheckBuilder can create different check types', () => { + const builder = new termGuard.CheckBuilder('completeness_check'); + + // Test is_complete check + const check1 = builder.isComplete('column1', 0.95); + assert.ok(check1); + assert.strictEqual(check1.name, 'completeness_check'); + + // Test has_min check + const builder2 = new termGuard.CheckBuilder('min_check'); + const check2 = builder2.hasMin('value_column', 0); + assert.ok(check2); + assert.strictEqual(check2.name, 'min_check'); + + // Test has_max check + const builder3 = new termGuard.CheckBuilder('max_check'); + const check3 = builder3.hasMax('value_column', 100); + assert.ok(check3); + assert.strictEqual(check3.name, 'max_check'); + + // Test is_unique check + const builder4 = new termGuard.CheckBuilder('unique_check'); + const check4 = builder4.isUnique('id_column'); + assert.ok(check4); + assert.strictEqual(check4.name, 'unique_check'); + + // Test has_mean check + const builder5 = new termGuard.CheckBuilder('mean_check'); + const check5 = builder5.hasMean('metric_column', 50.0, 0.1); + assert.ok(check5); + assert.strictEqual(check5.name, 'mean_check'); +}); + +test('ValidationSuiteBuilder works correctly', () => { + const suite = termGuard.ValidationSuite.builder('test_suite'); + assert.ok(suite); + assert.ok(suite instanceof termGuard.ValidationSuiteBuilder); + + // Test method chaining + const result = suite.description('Test suite description'); + assert.strictEqual(result, suite, 'Should return self for chaining'); + + // Test adding a check + const check = new termGuard.CheckBuilder('test_check').build(); + const result2 = suite.addCheck(check); + assert.strictEqual(result2, suite, 'Should return self for chaining'); + + // Build the suite + const validationSuite = suite.build(); + assert.ok(validationSuite); + assert.strictEqual(validationSuite.name, 'test_suite'); + assert.strictEqual(validationSuite.checkCount, 1); +}); + +test('ValidationSuite can be created with multiple checks', () => { + const check1 = new termGuard.CheckBuilder('check1').build(); + const check2 = new termGuard.CheckBuilder('check2').build(); + const check3 = new termGuard.CheckBuilder('check3').build(); + + const suite = termGuard.ValidationSuite.builder('multi_check_suite') + .description('Suite with multiple checks') + .addChecks([check1, check2, check3]) + .build(); + + assert.ok(suite); + assert.strictEqual(suite.name, 'multi_check_suite'); + assert.strictEqual(suite.description, 'Suite with multiple checks'); + assert.strictEqual(suite.checkCount, 3); +}); + +test('DataSource can be created from CSV', async () => { + // Create a temporary CSV file for testing + const testData = `id,name,value +1,Alice,100 +2,Bob,200 +3,Charlie,300`; + + const testFile = path.join(__dirname, 'test_data.csv'); + await fs.writeFile(testFile, testData); + + try { + const dataSource = await termGuard.DataSource.fromCsv(testFile); + assert.ok(dataSource); + assert.strictEqual(dataSource.tableName, 'data'); + + // Test row count + const rowCount = await dataSource.getRowCount(); + assert.strictEqual(rowCount, 3n); + + // Test column names + const columns = await dataSource.getColumnNames(); + assert.ok(Array.isArray(columns)); + assert.ok(columns.includes('id')); + assert.ok(columns.includes('name')); + assert.ok(columns.includes('value')); + } finally { + // Clean up test file + await fs.unlink(testFile).catch(() => {}); + } +}); + +test('DataSourceBuilder can register multiple tables', async () => { + // Create test CSV files + const testData1 = `id,value\n1,100\n2,200`; + const testData2 = `id,score\n1,90\n2,85`; + + const testFile1 = path.join(__dirname, 'test_table1.csv'); + const testFile2 = path.join(__dirname, 'test_table2.csv'); + + await fs.writeFile(testFile1, testData1); + await fs.writeFile(testFile2, testData2); + + try { + const builder = new termGuard.DataSourceBuilder(); + await builder.registerCsv('table1', testFile1); + await builder.registerCsv('table2', testFile2); + + const dataSource = builder.build(); + assert.ok(dataSource); + } finally { + // Clean up test files + await fs.unlink(testFile1).catch(() => {}); + await fs.unlink(testFile2).catch(() => {}); + } +}); + +test('Full validation workflow works end-to-end', async () => { + // Create a test CSV file + const testData = `id,name,score,status +1,Alice,95,active +2,Bob,87,active +3,Charlie,92,active +4,David,78,inactive +5,Eve,,active`; + + const testFile = path.join(__dirname, 'test_validation.csv'); + await fs.writeFile(testFile, testData); + + try { + // Create data source + const dataSource = await termGuard.DataSource.fromCsv(testFile); + + // Create checks + const completenessCheck = new termGuard.CheckBuilder('score_completeness') + .level(termGuard.Level.Error) + .description('Check score column completeness') + .isComplete('score', 0.8); + + const minCheck = new termGuard.CheckBuilder('score_minimum') + .level(termGuard.Level.Warning) + .description('Check minimum score') + .hasMin('score', 70); + + const uniqueCheck = new termGuard.CheckBuilder('id_uniqueness') + .level(termGuard.Level.Error) + .description('Check ID uniqueness') + .isUnique('id'); + + // Build validation suite + const suite = termGuard.ValidationSuite.builder('test_validation_suite') + .description('Complete validation test suite') + .addCheck(completenessCheck) + .addCheck(minCheck) + .addCheck(uniqueCheck) + .build(); + + // Run validation + const result = await suite.run(dataSource); + + // Verify result structure + assert.ok(result); + assert.ok(['success', 'failure'].includes(result.status)); + assert.ok(result.report); + assert.strictEqual(result.report.suiteName, 'test_validation_suite'); + assert.strictEqual(typeof result.report.totalChecks, 'number'); + assert.strictEqual(typeof result.report.passedChecks, 'number'); + assert.strictEqual(typeof result.report.failedChecks, 'number'); + assert.ok(Array.isArray(result.report.issues)); + + // Check metrics + assert.ok(result.metrics); + assert.strictEqual(typeof result.metrics.totalDurationMs, 'number'); + assert.strictEqual(typeof result.metrics.checksPerSecond, 'number'); + + console.log(`Validation completed: ${result.status}`); + console.log(`Passed: ${result.report.passedChecks}/${result.report.totalChecks}`); + if (result.report.issues.length > 0) { + console.log('Issues found:'); + result.report.issues.forEach(issue => { + console.log(` - ${issue.checkName} (${issue.level}): ${issue.message}`); + }); + } + } finally { + // Clean up test file + await fs.unlink(testFile).catch(() => {}); + } +}); + +test('Error handling works correctly', async () => { + // Test invalid file path + await assert.rejects( + termGuard.DataSource.fromCsv('/non/existent/file.csv'), + /Failed to read CSV file/ + ); + + // Test invalid parquet file + await assert.rejects( + termGuard.DataSource.fromParquet('/non/existent/file.parquet'), + /Failed to read parquet file/ + ); +}); + +test('validateSampleData helper function works', async () => { + // Create a test CSV file + const testData = `column1,column2 +value1,100 +value2,200 +value3,300`; + + const testFile = path.join(__dirname, 'sample_data.csv'); + await fs.writeFile(testFile, testData); + + try { + const result = await termGuard.validateSampleData(testFile); + assert.ok(result); + assert.strictEqual(typeof result, 'string'); + assert.ok(result.includes('Validation')); + assert.ok(result.includes('checks passed')); + assert.ok(result.includes('failed')); + } finally { + // Clean up test file + await fs.unlink(testFile).catch(() => {}); + } +}); + +console.log('All tests completed successfully!'); \ No newline at end of file diff --git a/node/term-guard/tsconfig.json b/node/term-guard/tsconfig.json new file mode 100644 index 0000000..b24fb15 --- /dev/null +++ b/node/term-guard/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "declaration": true, + "declarationMap": true, + "outDir": "./dist", + "rootDir": "./", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "allowJs": false, + "types": ["node"], + "moduleResolution": "node" + }, + "include": [ + "index.ts", + "index.d.ts" + ], + "exclude": [ + "node_modules", + "test/**/*", + "dist", + "*.node", + "*.js" + ] +} \ No newline at end of file