mod convert;
mod streaming;
use convert::*;
use tensogram::{self as core, DecodeOptions, EncodeOptions};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn decode(buf: &[u8], verify_hash: Option<bool>) -> Result<DecodedMessage, JsError> {
let options = DecodeOptions {
verify_hash: verify_hash.unwrap_or(false),
..Default::default()
};
let (metadata, objects) = core::decode(buf, &options).map_err(js_err)?;
Ok(DecodedMessage { metadata, objects })
}
#[wasm_bindgen]
pub fn decode_metadata(buf: &[u8]) -> Result<JsValue, JsError> {
let meta = core::decode_metadata(buf).map_err(js_err)?;
to_js(&meta)
}
#[wasm_bindgen]
pub fn decode_object(
buf: &[u8],
index: usize,
verify_hash: Option<bool>,
) -> Result<DecodedMessage, JsError> {
let options = DecodeOptions {
verify_hash: verify_hash.unwrap_or(false),
..Default::default()
};
let (metadata, descriptor, data) = core::decode_object(buf, index, &options).map_err(js_err)?;
Ok(DecodedMessage {
metadata,
objects: vec![(descriptor, data)],
})
}
#[wasm_bindgen]
pub fn scan(buf: &[u8]) -> Result<JsValue, JsError> {
let positions = core::scan(buf);
to_js(&positions)
}
#[wasm_bindgen]
pub fn encode(
metadata_js: JsValue,
objects_js: js_sys::Array,
hash: Option<bool>,
) -> Result<js_sys::Uint8Array, JsError> {
use core::hash::HashAlgorithm;
use core::types::{DataObjectDescriptor, GlobalMetadata};
let metadata: GlobalMetadata =
serde_wasm_bindgen::from_value(metadata_js).map_err(|e| JsError::new(&e.to_string()))?;
let mut descriptors = Vec::new();
let mut data_vec: Vec<Vec<u8>> = Vec::new();
for i in 0..objects_js.length() {
let obj = objects_js.get(i);
let desc_val = js_sys::Reflect::get(&obj, &"descriptor".into())
.map_err(|_| JsError::new("each object must have a 'descriptor' field"))?;
let data_val = js_sys::Reflect::get(&obj, &"data".into())
.map_err(|_| JsError::new("each object must have a 'data' field"))?;
let desc: DataObjectDescriptor =
serde_wasm_bindgen::from_value(desc_val).map_err(|e| JsError::new(&e.to_string()))?;
let data_bytes = typed_array_to_bytes(&data_val).ok_or_else(|| {
JsError::new(
"data must be a TypedArray (Uint8Array, Float32Array, Float64Array, or Int32Array)",
)
})?;
descriptors.push(desc);
data_vec.push(data_bytes);
}
let options = EncodeOptions {
hash_algorithm: if hash.unwrap_or(true) {
Some(HashAlgorithm::Xxh3)
} else {
None
},
emit_preceders: false,
..Default::default()
};
let pairs: Vec<(&core::DataObjectDescriptor, &[u8])> = descriptors
.iter()
.zip(data_vec.iter())
.map(|(d, v)| (d, v.as_slice()))
.collect();
let encoded = core::encode(&metadata, &pairs, &options).map_err(js_err)?;
Ok(js_sys::Uint8Array::from(encoded.as_slice()))
}
#[wasm_bindgen]
pub struct DecodedMessage {
metadata: core::GlobalMetadata,
objects: Vec<core::DecodedObject>,
}
#[wasm_bindgen]
impl DecodedMessage {
pub fn metadata(&self) -> Result<JsValue, JsError> {
to_js(&self.metadata)
}
pub fn object_count(&self) -> usize {
self.objects.len()
}
pub fn object_descriptor(&self, index: usize) -> Result<JsValue, JsError> {
let _ = self.payload(index)?;
to_js(&self.objects[index].0)
}
pub fn object_data_f32(&self, index: usize) -> Result<js_sys::Float32Array, JsError> {
let data = self.payload(index)?;
view_as_f32(data)
}
pub fn object_data_f64(&self, index: usize) -> Result<js_sys::Float64Array, JsError> {
let data = self.payload(index)?;
view_as_f64(data)
}
pub fn object_data_i32(&self, index: usize) -> Result<js_sys::Int32Array, JsError> {
let data = self.payload(index)?;
view_as_i32(data)
}
pub fn object_data_u8(&self, index: usize) -> Result<js_sys::Uint8Array, JsError> {
let data = self.payload(index)?;
Ok(view_as_u8(data))
}
pub fn object_data_copy_f32(&self, index: usize) -> Result<js_sys::Float32Array, JsError> {
let data = self.payload(index)?;
copy_as_f32(data)
}
pub fn object_byte_length(&self, index: usize) -> Result<usize, JsError> {
Ok(self.payload(index)?.len())
}
}
impl DecodedMessage {
fn payload(&self, index: usize) -> Result<&[u8], JsError> {
if index >= self.objects.len() {
return Err(JsError::new(&format!(
"object index {index} out of range (have {})",
self.objects.len()
)));
}
Ok(&self.objects[index].1)
}
}
pub use streaming::StreamingDecoder;
fn js_err(e: core::TensogramError) -> JsError {
JsError::new(&e.to_string())
}
fn typed_array_to_bytes(val: &JsValue) -> Option<Vec<u8>> {
if let Some(arr) = val.dyn_ref::<js_sys::Uint8Array>() {
Some(arr.to_vec())
} else if let Some(arr) = val.dyn_ref::<js_sys::Float32Array>() {
Some(
js_sys::Uint8Array::new_with_byte_offset_and_length(
&arr.buffer(),
arr.byte_offset(),
arr.byte_length(),
)
.to_vec(),
)
} else if let Some(arr) = val.dyn_ref::<js_sys::Float64Array>() {
Some(
js_sys::Uint8Array::new_with_byte_offset_and_length(
&arr.buffer(),
arr.byte_offset(),
arr.byte_length(),
)
.to_vec(),
)
} else if let Some(arr) = val.dyn_ref::<js_sys::Int32Array>() {
Some(
js_sys::Uint8Array::new_with_byte_offset_and_length(
&arr.buffer(),
arr.byte_offset(),
arr.byte_length(),
)
.to_vec(),
)
} else {
None
}
}