polyxml 0.1.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,19 @@
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
+ description = "Node.js and TypeScript native addon bindings for PolyXML"
9
+
10
+ [lib]
11
+ crate-type = ["cdylib"]
12
+
13
+ [dependencies]
14
+ polyxml = { path = "../polyxml-core" }
15
+ napi = { version = "2.16", default-features = false, features = ["napi4"] }
16
+ napi-derive = "2.16"
17
+
18
+ [build-dependencies]
19
+ 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
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```javascript
18
+ const polyxml = require('polyxml');
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,58 @@
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
+ }
23
+
24
+ export interface ModelSchema {
25
+ name: string;
26
+ fields: FieldDefinition[];
27
+ }
28
+
29
+ export type PolyValue =
30
+ | null
31
+ | boolean
32
+ | number
33
+ | string
34
+ | PolyValue[]
35
+ | { [key: string]: PolyValue };
36
+
37
+ /**
38
+ * Deserialize an XML string or Uint8Array into a JavaScript object based on schema.
39
+ */
40
+ export function deserialize(
41
+ xml: string | Uint8Array,
42
+ schema: ModelSchema
43
+ ): PolyValue;
44
+
45
+ /**
46
+ * Serialize a JavaScript object into XML bytes based on schema.
47
+ */
48
+ export function serialize(
49
+ rootName: string,
50
+ value: Record<string, any>,
51
+ schema: ModelSchema,
52
+ indent?: number | null
53
+ ): Uint8Array;
54
+
55
+ /**
56
+ * Return the PolyXML engine version.
57
+ */
58
+ 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",
3
+ "version": "0.1.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
+ "name": "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
Binary file
Binary file
package/src/lib.rs ADDED
@@ -0,0 +1,149 @@
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
+ }
16
+
17
+ #[napi(object)]
18
+ pub struct JsModelSchema {
19
+ pub name: String,
20
+ pub fields: Vec<JsFieldDef>,
21
+ }
22
+
23
+ fn convert_js_schema(schema: &JsModelSchema) -> Arc<ModelSchema> {
24
+ let mut builder = ModelSchema::builder(&schema.name);
25
+ for f in &schema.fields {
26
+ let kind = match f.kind.as_str() {
27
+ "attribute" => FieldKind::Attribute,
28
+ "text" => FieldKind::Text,
29
+ _ => FieldKind::Element,
30
+ };
31
+ let sc = match f.scalar_type.as_str() {
32
+ "int" => ScalarType::Int,
33
+ "float" => ScalarType::Float,
34
+ "bool" => ScalarType::Bool,
35
+ "decimal" => ScalarType::Decimal,
36
+ "xml_date" => ScalarType::XmlDate,
37
+ "xml_datetime" => ScalarType::XmlDateTime,
38
+ _ => ScalarType::String,
39
+ };
40
+ builder = builder.field(FieldSchema::new(
41
+ &f.name,
42
+ f.xml_name.as_bytes(),
43
+ kind,
44
+ ValueType::Scalar(sc),
45
+ ));
46
+ }
47
+ builder.build()
48
+ }
49
+
50
+ fn poly_value_to_js(env: &Env, val: &PolyValue) -> Result<JsUnknown> {
51
+ match val {
52
+ PolyValue::Null => Ok(env.get_null()?.into_unknown()),
53
+ PolyValue::Bool(b) => Ok(env.get_boolean(*b)?.into_unknown()),
54
+ PolyValue::Int(i) => Ok(env.create_int64(*i)?.into_unknown()),
55
+ PolyValue::Float(f) => Ok(env.create_double(*f)?.into_unknown()),
56
+ PolyValue::String(s) => Ok(env.create_string(s)?.into_unknown()),
57
+ PolyValue::List(list) => {
58
+ let mut arr = env.create_array(list.len() as u32)?;
59
+ for (idx, item) in list.iter().enumerate() {
60
+ let js_item = poly_value_to_js(env, item)?;
61
+ arr.set(idx as u32, js_item)?;
62
+ }
63
+ Ok(arr.coerce_to_object()?.into_unknown())
64
+ }
65
+ PolyValue::Object(map) => {
66
+ let mut obj = env.create_object()?;
67
+ for (k, v) in map {
68
+ let js_val = poly_value_to_js(env, v)?;
69
+ obj.set(k.as_str(), js_val)?;
70
+ }
71
+ Ok(obj.into_unknown())
72
+ }
73
+ }
74
+ }
75
+
76
+ #[napi]
77
+ pub fn deserialize(
78
+ env: Env,
79
+ xml: Either<String, Buffer>,
80
+ schema: JsModelSchema,
81
+ ) -> Result<JsUnknown> {
82
+ let xml_bytes: &[u8] = match &xml {
83
+ Either::A(s) => s.as_bytes(),
84
+ Either::B(b) => b.as_ref(),
85
+ };
86
+
87
+ let model_schema = convert_js_schema(&schema);
88
+ let val = polyxml::deserialize(xml_bytes, model_schema)
89
+ .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?;
90
+
91
+ poly_value_to_js(&env, &val)
92
+ }
93
+
94
+ fn js_to_poly_value(_env: &Env, val: JsUnknown, scalar_type: &str) -> Result<PolyValue> {
95
+ let val_type = val.get_type()?;
96
+ if val_type == napi::ValueType::Null || val_type == napi::ValueType::Undefined {
97
+ return Ok(PolyValue::Null);
98
+ }
99
+ match scalar_type {
100
+ "int" => {
101
+ let num = val.coerce_to_number()?;
102
+ Ok(PolyValue::Int(num.get_int64()?))
103
+ }
104
+ "float" => {
105
+ let num = val.coerce_to_number()?;
106
+ Ok(PolyValue::Float(num.get_double()?))
107
+ }
108
+ "bool" => {
109
+ let b = val.coerce_to_bool()?;
110
+ Ok(PolyValue::Bool(b.get_value()?))
111
+ }
112
+ _ => {
113
+ let s = val.coerce_to_string()?;
114
+ let utf8 = s.into_utf8()?;
115
+ Ok(PolyValue::String(utf8.as_str()?.to_string()))
116
+ }
117
+ }
118
+ }
119
+
120
+ #[napi]
121
+ pub fn serialize(
122
+ env: Env,
123
+ root_name: String,
124
+ value: napi::JsObject,
125
+ schema: JsModelSchema,
126
+ indent: Option<u32>,
127
+ ) -> Result<Buffer> {
128
+ use std::collections::HashMap;
129
+
130
+ let mut map = HashMap::new();
131
+ for f in &schema.fields {
132
+ if value.has_named_property(&f.name)? {
133
+ let prop: JsUnknown = value.get_named_property(&f.name)?;
134
+ let pv = js_to_poly_value(&env, prop, &f.scalar_type)?;
135
+ map.insert(f.name.clone(), pv);
136
+ }
137
+ }
138
+ let poly_val = PolyValue::Object(map);
139
+ let model_schema = convert_js_schema(&schema);
140
+ let indent_opt = indent.map(|i| i as usize);
141
+ let bytes = polyxml::serialize(&root_name, &poly_val, &model_schema, indent_opt)
142
+ .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?;
143
+ Ok(bytes.into())
144
+ }
145
+
146
+ #[napi]
147
+ pub fn version() -> &'static str {
148
+ env!("CARGO_PKG_VERSION")
149
+ }
@@ -0,0 +1,42 @@
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
+ });