@polyxml/node 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Cargo.toml ADDED
@@ -0,0 +1,20 @@
1
+ [package]
2
+ name = "polyxml-js"
3
+ version.workspace = true
4
+ edition.workspace = true
5
+ license.workspace = true
6
+ authors.workspace = true
7
+ repository.workspace = true
8
+ readme = "README.md"
9
+ description = "Node.js and TypeScript native addon bindings for PolyXML"
10
+
11
+ [lib]
12
+ crate-type = ["cdylib"]
13
+
14
+ [dependencies]
15
+ polyxml = { path = "../polyxml-core" }
16
+ napi = { version = "2.16", default-features = false, features = ["napi4"] }
17
+ napi-derive = "2.16"
18
+
19
+ [build-dependencies]
20
+ napi-build = "2.1"
package/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # PolyXML Node.js & TypeScript Bindings
2
+
3
+ High-performance, polyglot streaming XML data-binding engine for Node.js and TypeScript.
4
+
5
+ - Precompiled native binaries for Linux (x86_64), macOS (Apple Silicon & Intel), and Windows (x64).
6
+ - Zero external native toolchain dependencies needed at runtime.
7
+ - Powered by the ultra-fast Rust `polyxml-core` engine and NAPI-RS.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @polyxml/node
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```javascript
18
+ const polyxml = require('@polyxml/node');
19
+
20
+ const schema = {
21
+ name: 'Book',
22
+ fields: [
23
+ { name: 'id', xmlName: 'id', kind: 'attribute', scalarType: 'int' },
24
+ { name: 'title', xmlName: 'title', kind: 'element', scalarType: 'string' },
25
+ { name: 'price', xmlName: 'price', kind: 'element', scalarType: 'float' }
26
+ ]
27
+ };
28
+
29
+ const xml = '<Book id="42"><title>Rust in Action</title><price>39.99</price></Book>';
30
+ const book = polyxml.deserialize(xml, schema);
31
+
32
+ console.log(book);
33
+ // { id: 42, title: 'Rust in Action', price: 39.99 }
34
+ ```
35
+
36
+ ## License
37
+
38
+ MIT
package/build.rs ADDED
@@ -0,0 +1,3 @@
1
+ fn main() {
2
+ let _ = std::panic::catch_unwind(napi_build::setup);
3
+ }
package/index.d.ts ADDED
@@ -0,0 +1,62 @@
1
+ /**
2
+ * PolyXML TypeScript Type Definitions
3
+ */
4
+
5
+ export type FieldKind = 'attribute' | 'element' | 'text';
6
+
7
+ export type ScalarType =
8
+ | 'string'
9
+ | 'int'
10
+ | 'float'
11
+ | 'bool'
12
+ | 'decimal'
13
+ | 'xml_date'
14
+ | 'xml_datetime'
15
+ | 'any';
16
+
17
+ export interface FieldDefinition {
18
+ name: string;
19
+ xmlName: string;
20
+ kind: FieldKind;
21
+ scalarType: ScalarType;
22
+ namespace?: string | null;
23
+ }
24
+
25
+ export interface ModelSchema {
26
+ name: string;
27
+ namespace?: string | null;
28
+ fields: FieldDefinition[];
29
+ }
30
+
31
+ export type PolyValue =
32
+ | null
33
+ | boolean
34
+ | number
35
+ | string
36
+ | PolyValue[]
37
+ | { [key: string]: PolyValue };
38
+
39
+ /**
40
+ * Deserialize an XML string or Uint8Array into a JavaScript object based on schema.
41
+ */
42
+ export function deserialize(
43
+ xml: string | Uint8Array,
44
+ schema: ModelSchema
45
+ ): PolyValue;
46
+
47
+ /**
48
+ * Serialize a JavaScript object into XML bytes based on schema.
49
+ */
50
+ export function serialize(
51
+ rootName: string,
52
+ value: Record<string, any>,
53
+ schema: ModelSchema,
54
+ indent?: number | null,
55
+ namespaces?: boolean | null,
56
+ nsMap?: Record<string, string> | null
57
+ ): Uint8Array;
58
+
59
+ /**
60
+ * Return the PolyXML engine version.
61
+ */
62
+ export function version(): string;
package/index.js ADDED
@@ -0,0 +1,92 @@
1
+ const { existsSync } = require('fs')
2
+ const { join } = require('path')
3
+
4
+ const { platform, arch } = process
5
+
6
+ let nativeBinding = null
7
+ let localFileExisted = false
8
+ let loadError = null
9
+
10
+ switch (platform) {
11
+ case 'win32':
12
+ switch (arch) {
13
+ case 'x64':
14
+ localFileExisted = existsSync(join(__dirname, 'polyxml.win32-x64-msvc.node'))
15
+ try {
16
+ if (localFileExisted) {
17
+ nativeBinding = require('./polyxml.win32-x64-msvc.node')
18
+ } else {
19
+ nativeBinding = require('polyxml-win32-x64-msvc')
20
+ }
21
+ } catch (e) {
22
+ loadError = e
23
+ }
24
+ break
25
+ default:
26
+ throw new Error(`Unsupported architecture on Windows: ${arch}`)
27
+ }
28
+ break
29
+ case 'darwin':
30
+ switch (arch) {
31
+ case 'x64':
32
+ localFileExisted = existsSync(join(__dirname, 'polyxml.darwin-x64.node'))
33
+ try {
34
+ if (localFileExisted) {
35
+ nativeBinding = require('./polyxml.darwin-x64.node')
36
+ } else {
37
+ nativeBinding = require('polyxml-darwin-x64')
38
+ }
39
+ } catch (e) {
40
+ loadError = e
41
+ }
42
+ break
43
+ case 'arm64':
44
+ localFileExisted = existsSync(join(__dirname, 'polyxml.darwin-arm64.node'))
45
+ try {
46
+ if (localFileExisted) {
47
+ nativeBinding = require('./polyxml.darwin-arm64.node')
48
+ } else {
49
+ nativeBinding = require('polyxml-darwin-arm64')
50
+ }
51
+ } catch (e) {
52
+ loadError = e
53
+ }
54
+ break
55
+ default:
56
+ throw new Error(`Unsupported architecture on macOS: ${arch}`)
57
+ }
58
+ break
59
+ case 'linux':
60
+ switch (arch) {
61
+ case 'x64':
62
+ localFileExisted = existsSync(join(__dirname, 'polyxml.linux-x64-gnu.node'))
63
+ try {
64
+ if (localFileExisted) {
65
+ nativeBinding = require('./polyxml.linux-x64-gnu.node')
66
+ } else {
67
+ nativeBinding = require('polyxml-linux-x64-gnu')
68
+ }
69
+ } catch (e) {
70
+ loadError = e
71
+ }
72
+ break
73
+ default:
74
+ throw new Error(`Unsupported architecture on Linux: ${arch}`)
75
+ }
76
+ break
77
+ default:
78
+ throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
79
+ }
80
+
81
+ if (!nativeBinding) {
82
+ try {
83
+ nativeBinding = require('./polyxml.node')
84
+ } catch (e) {
85
+ if (loadError) {
86
+ throw loadError
87
+ }
88
+ throw new Error(`Failed to load native PolyXML addon for ${platform}-${arch}`)
89
+ }
90
+ }
91
+
92
+ module.exports = nativeBinding
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@polyxml/node",
3
+ "version": "0.23.0",
4
+ "description": "High-performance, polyglot XML data-binding engine built in Rust",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "scripts": {
8
+ "test": "node test/index.test.js"
9
+ },
10
+ "napi": {
11
+ "binaryName": "polyxml"
12
+ },
13
+ "devDependencies": {
14
+ "@napi-rs/cli": "^3.0.0"
15
+ },
16
+ "keywords": [
17
+ "xml",
18
+ "parser",
19
+ "serializer",
20
+ "deserializer",
21
+ "fast-xml",
22
+ "polyglot"
23
+ ],
24
+ "author": "Bailey Nguyen <bailey.tan.nguyen@gmail.com>",
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/nth-bailey/PolyXML.git"
29
+ }
30
+ }
Binary file
Binary file
@@ -0,0 +1,4 @@
1
+ [diffend] Oversized file quarantined before diffing.
2
+ name: package/polyxml.linux-x64-gnu.node
3
+ size: 41468296 bytes
4
+ sha256: 9487b870e98dc1182f36cd3ec9887a3c8b36d66f0e86f1bbdb841b8c3a8a64ca
Binary file
package/src/lib.rs ADDED
@@ -0,0 +1,173 @@
1
+ use napi::bindgen_prelude::*;
2
+ use napi::JsUnknown;
3
+ use napi_derive::napi;
4
+ use std::sync::Arc;
5
+
6
+ use polyxml::schema::{FieldKind, FieldSchema, ModelSchema, ScalarType, ValueType};
7
+ use polyxml::value::PolyValue;
8
+
9
+ #[napi(object)]
10
+ pub struct JsFieldDef {
11
+ pub name: String,
12
+ pub xml_name: String,
13
+ pub kind: String,
14
+ pub scalar_type: String,
15
+ pub namespace: Option<String>,
16
+ }
17
+
18
+ #[napi(object)]
19
+ pub struct JsModelSchema {
20
+ pub name: String,
21
+ pub fields: Vec<JsFieldDef>,
22
+ pub namespace: Option<String>,
23
+ }
24
+
25
+ fn convert_js_schema(schema: &JsModelSchema) -> Arc<ModelSchema> {
26
+ let mut builder = ModelSchema::builder(&schema.name);
27
+ if let Some(ns) = &schema.namespace {
28
+ builder = builder.namespace(ns);
29
+ }
30
+ for f in &schema.fields {
31
+ let kind = match f.kind.as_str() {
32
+ "attribute" => FieldKind::Attribute,
33
+ "text" => FieldKind::Text,
34
+ _ => FieldKind::Element,
35
+ };
36
+ let sc = match f.scalar_type.as_str() {
37
+ "int" => ScalarType::Int,
38
+ "float" => ScalarType::Float,
39
+ "bool" => ScalarType::Bool,
40
+ "decimal" => ScalarType::Decimal,
41
+ "xml_date" => ScalarType::XmlDate,
42
+ "xml_datetime" => ScalarType::XmlDateTime,
43
+ _ => ScalarType::String,
44
+ };
45
+ let mut field =
46
+ FieldSchema::new(&f.name, f.xml_name.as_bytes(), kind, ValueType::Scalar(sc));
47
+ if let Some(ns) = &f.namespace {
48
+ field = field.namespace(ns);
49
+ }
50
+ builder = builder.field(field);
51
+ }
52
+ builder.build()
53
+ }
54
+
55
+ fn poly_value_to_js(env: &Env, val: &PolyValue) -> Result<JsUnknown> {
56
+ match val {
57
+ PolyValue::Null => Ok(env.get_null()?.into_unknown()),
58
+ PolyValue::Bool(b) => Ok(env.get_boolean(*b)?.into_unknown()),
59
+ PolyValue::Int(i) => Ok(env.create_int64(*i)?.into_unknown()),
60
+ PolyValue::Float(f) => Ok(env.create_double(*f)?.into_unknown()),
61
+ PolyValue::String(s) => Ok(env.create_string(s)?.into_unknown()),
62
+ PolyValue::List(list) => {
63
+ let mut arr = env.create_array(list.len() as u32)?;
64
+ for (idx, item) in list.iter().enumerate() {
65
+ let js_item = poly_value_to_js(env, item)?;
66
+ arr.set(idx as u32, js_item)?;
67
+ }
68
+ Ok(arr.coerce_to_object()?.into_unknown())
69
+ }
70
+ PolyValue::Object(map) => {
71
+ let mut obj = env.create_object()?;
72
+ for (k, v) in map {
73
+ let js_val = poly_value_to_js(env, v)?;
74
+ obj.set(k.as_str(), js_val)?;
75
+ }
76
+ Ok(obj.into_unknown())
77
+ }
78
+ PolyValue::Record { schema, values } => {
79
+ let mut obj = env.create_object()?;
80
+ for (idx, field) in schema.fields.iter().enumerate() {
81
+ if let Some(Some(item)) = values.get(idx) {
82
+ let js_val = poly_value_to_js(env, item)?;
83
+ obj.set(field.name.as_str(), js_val)?;
84
+ }
85
+ }
86
+ Ok(obj.into_unknown())
87
+ }
88
+ }
89
+ }
90
+
91
+ #[napi]
92
+ pub fn deserialize(
93
+ env: Env,
94
+ xml: Either<String, Buffer>,
95
+ schema: JsModelSchema,
96
+ ) -> Result<JsUnknown> {
97
+ let xml_bytes: &[u8] = match &xml {
98
+ Either::A(s) => s.as_bytes(),
99
+ Either::B(b) => b.as_ref(),
100
+ };
101
+
102
+ let model_schema = convert_js_schema(&schema);
103
+ let val = polyxml::deserialize(xml_bytes, model_schema)
104
+ .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?;
105
+
106
+ poly_value_to_js(&env, &val)
107
+ }
108
+
109
+ fn js_to_poly_value(_env: &Env, val: JsUnknown, scalar_type: &str) -> Result<PolyValue> {
110
+ let val_type = val.get_type()?;
111
+ if val_type == napi::ValueType::Null || val_type == napi::ValueType::Undefined {
112
+ return Ok(PolyValue::Null);
113
+ }
114
+ match scalar_type {
115
+ "int" => {
116
+ let num = val.coerce_to_number()?;
117
+ Ok(PolyValue::Int(num.get_int64()?))
118
+ }
119
+ "float" => {
120
+ let num = val.coerce_to_number()?;
121
+ Ok(PolyValue::Float(num.get_double()?))
122
+ }
123
+ "bool" => {
124
+ let b = val.coerce_to_bool()?;
125
+ Ok(PolyValue::Bool(b.get_value()?))
126
+ }
127
+ _ => {
128
+ let s = val.coerce_to_string()?;
129
+ let utf8 = s.into_utf8()?;
130
+ Ok(PolyValue::String(utf8.as_str()?.to_string()))
131
+ }
132
+ }
133
+ }
134
+
135
+ #[napi]
136
+ pub fn serialize(
137
+ env: Env,
138
+ root_name: String,
139
+ value: napi::JsObject,
140
+ schema: JsModelSchema,
141
+ indent: Option<u32>,
142
+ namespaces: Option<bool>,
143
+ ns_map: Option<std::collections::HashMap<String, String>>,
144
+ ) -> Result<Buffer> {
145
+ use std::collections::HashMap;
146
+
147
+ let mut map = HashMap::new();
148
+ for f in &schema.fields {
149
+ if value.has_named_property(&f.name)? {
150
+ let prop: JsUnknown = value.get_named_property(&f.name)?;
151
+ let pv = js_to_poly_value(&env, prop, &f.scalar_type)?;
152
+ map.insert(f.name.clone(), pv);
153
+ }
154
+ }
155
+ let poly_val = PolyValue::Object(map);
156
+ let model_schema = convert_js_schema(&schema);
157
+ let indent_opt = indent.map(|i| i as usize);
158
+ let bytes = polyxml::serialize_with_options(
159
+ &root_name,
160
+ &poly_val,
161
+ &model_schema,
162
+ indent_opt,
163
+ namespaces,
164
+ ns_map.as_ref(),
165
+ )
166
+ .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?;
167
+ Ok(bytes.into())
168
+ }
169
+
170
+ #[napi]
171
+ pub fn version() -> &'static str {
172
+ env!("CARGO_PKG_VERSION")
173
+ }
@@ -0,0 +1,92 @@
1
+ const test = require('node:test');
2
+ const assert = require('node:assert');
3
+
4
+ test('PolyXML JavaScript bindings', (t) => {
5
+ let polyxml;
6
+ try {
7
+ polyxml = require('../index.js');
8
+ } catch (err) {
9
+ // If native addon not yet compiled locally, skip with notice
10
+ t.diagnostic('Native addon not compiled in current environment; skipping live execution test.');
11
+ return;
12
+ }
13
+
14
+ assert.strictEqual(typeof polyxml.version(), 'string');
15
+
16
+ const schema = {
17
+ name: 'Sensor',
18
+ fields: [
19
+ { name: 'id', xmlName: 'id', kind: 'attribute', scalarType: 'int' },
20
+ { name: 'name', xmlName: 'name', kind: 'element', scalarType: 'string' },
21
+ { name: 'reading', xmlName: 'reading', kind: 'element', scalarType: 'float' },
22
+ { name: 'calibrated', xmlName: 'calibrated', kind: 'element', scalarType: 'bool' },
23
+ ],
24
+ };
25
+
26
+ const xml = '<Sensor id="101"><name>Barometric Altimeter</name><reading>1013.25</reading><calibrated>true</calibrated></Sensor>';
27
+ const val = polyxml.deserialize(xml, schema);
28
+
29
+ assert.strictEqual(val.id, 101);
30
+ assert.strictEqual(val.name, 'Barometric Altimeter');
31
+ assert.strictEqual(val.reading, 1013.25);
32
+ assert.strictEqual(val.calibrated, true);
33
+
34
+ // Test serialization
35
+ const outBytes = polyxml.serialize('Sensor', val, schema, 2);
36
+ assert.ok(outBytes instanceof Uint8Array || Buffer.isBuffer(outBytes));
37
+ const outXml = Buffer.from(outBytes).toString('utf-8');
38
+ assert.ok(outXml.includes('id="101"'));
39
+ assert.ok(outXml.includes('<name>Barometric Altimeter</name>'));
40
+ assert.ok(outXml.includes('<reading>1013.25</reading>'));
41
+ assert.ok(outXml.includes('<calibrated>true</calibrated>'));
42
+
43
+ // Test namespaced schema & serialization
44
+ const orderSchema = {
45
+ name: 'Order',
46
+ namespace: 'https://example.com/orders',
47
+ fields: [
48
+ { name: 'id', xmlName: 'id', kind: 'attribute', scalarType: 'int' },
49
+ { name: 'item', xmlName: 'item', kind: 'element', scalarType: 'string', namespace: 'https://example.com/items' },
50
+ ],
51
+ };
52
+
53
+ const orderXml = '<ns0:Order xmlns:ns0="https://example.com/orders" xmlns:ns1="https://example.com/items" id="777"><ns1:item>NodeGadget</ns1:item></ns0:Order>';
54
+ const orderVal = polyxml.deserialize(orderXml, orderSchema);
55
+ assert.strictEqual(orderVal.id, 777);
56
+ assert.strictEqual(orderVal.item, 'NodeGadget');
57
+
58
+ const orderOut = polyxml.serialize('Order', orderVal, orderSchema, 0, true, {
59
+ ord: 'https://example.com/orders',
60
+ itm: 'https://example.com/items',
61
+ });
62
+ const orderOutStr = Buffer.from(orderOut).toString('utf-8');
63
+ assert.ok(orderOutStr.includes('xmlns:ord="https://example.com/orders"'));
64
+ assert.ok(orderOutStr.includes('xmlns:itm="https://example.com/items"'));
65
+ assert.ok(orderOutStr.includes('<ord:Order'));
66
+ assert.ok(orderOutStr.includes('<itm:item>NodeGadget</itm:item>'));
67
+
68
+ // Test real-world fixture conformance: atom_feed.xml
69
+ const fs = require('node:fs');
70
+ const path = require('node:path');
71
+ const atomPath = path.resolve(__dirname, '../../../tests/fixtures/atom_feed.xml');
72
+ if (fs.existsSync(atomPath)) {
73
+ const atomXml = fs.readFileSync(atomPath, 'utf-8');
74
+ const atomSchema = {
75
+ name: 'feed',
76
+ namespace: 'http://www.w3.org/2005/Atom',
77
+ fields: [
78
+ { name: 'title', xmlName: 'title', kind: 'element', scalarType: 'string' },
79
+ { name: 'id', xmlName: 'id', kind: 'element', scalarType: 'string' },
80
+ ],
81
+ };
82
+ const atomVal = polyxml.deserialize(atomXml, atomSchema);
83
+ assert.strictEqual(atomVal.title, 'PolyXML Engineering Updates');
84
+
85
+ const atomOut = polyxml.serialize('feed', atomVal, atomSchema, 2, true, {
86
+ '': 'http://www.w3.org/2005/Atom',
87
+ });
88
+ const atomOutStr = Buffer.from(atomOut).toString('utf-8');
89
+ assert.ok(atomOutStr.includes('xmlns="http://www.w3.org/2005/Atom"'));
90
+ assert.ok(atomOutStr.includes('<title>PolyXML Engineering Updates</title>'));
91
+ }
92
+ });