feat: draft ordhook-sdk-js

This commit is contained in:
Ludo Galabru
2023-08-28 23:11:09 -04:00
parent 4f5cb49c03
commit b264e7281b
18 changed files with 8618 additions and 21 deletions

8
Cargo.lock generated
View File

@@ -440,8 +440,6 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chainhook-sdk"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbaf86f829fe63fd8aab5fb43483cc8ada8d02537cec76df6a23b77d1a75cb5c"
dependencies = [
"base58 0.2.0",
"base64 0.13.1",
@@ -461,7 +459,7 @@ dependencies = [
"regex",
"reqwest",
"rocket",
"schemars 0.8.11",
"schemars 0.8.12",
"serde",
"serde-hex",
"serde_derive",
@@ -475,11 +473,9 @@ dependencies = [
[[package]]
name = "chainhook-types"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03af00efeffbc0259632c2ffc410663f326f6cdeee604e91971f889648a92c2f"
dependencies = [
"hex",
"schemars 0.8.11",
"schemars 0.8.12",
"serde",
"serde_derive",
"serde_json",

View File

@@ -2,6 +2,7 @@
members = [
"components/ordhook-cli",
"components/ordhook-core",
"components/ordhook-sdk-js"
]
default-members = ["components/ordhook-cli"]

View File

@@ -21,7 +21,9 @@ use crate::{
core::{
pipeline::processors::block_archiving::store_compacted_blocks,
protocol::{
inscription_parsing::{get_inscriptions_revealed_in_block, get_inscriptions_transferred_in_block},
inscription_parsing::{
get_inscriptions_revealed_in_block, get_inscriptions_transferred_in_block,
},
inscription_sequencing::{
augment_block_with_ordinals_inscriptions_data_and_write_to_db_tx,
parallelize_inscription_data_computations, SequenceCursor,

View File

@@ -6,7 +6,7 @@ use chainhook_sdk::bitcoincore_rpc_json::bitcoin::Txid;
use chainhook_sdk::indexer::bitcoin::{standardize_bitcoin_block, BitcoinBlockFullBreakdown};
use chainhook_sdk::types::{
BitcoinBlockData, BitcoinNetwork, BitcoinTransactionData, OrdinalInscriptionCurseType,
OrdinalInscriptionRevealData, OrdinalOperation, OrdinalInscriptionTransferData,
OrdinalInscriptionRevealData, OrdinalInscriptionTransferData, OrdinalOperation,
};
use chainhook_sdk::utils::Context;
use chainhook_sdk::{

View File

@@ -19,7 +19,7 @@ use chainhook_sdk::chainhooks::types::BitcoinChainhookSpecification;
use chainhook_sdk::indexer::bitcoin::{
build_http_client, download_and_parse_block_with_retry, retrieve_block_hash_with_retry,
};
use chainhook_sdk::observer::{gather_proofs, EventObserverConfig};
use chainhook_sdk::observer::{gather_proofs, DataHandlerEvent, EventObserverConfig};
use chainhook_sdk::types::{
BitcoinBlockData, BitcoinChainEvent, BitcoinChainUpdatedWithBlocksData,
};
@@ -273,7 +273,7 @@ pub async fn execute_predicates_action<'a>(
}
BitcoinChainhookOccurrence::Data(payload) => {
if let Some(ref tx) = config.data_handler_tx {
let _ = tx.send(payload);
let _ = tx.send(DataHandlerEvent::Process(payload));
}
}
};

View File

@@ -31,8 +31,8 @@ use chainhook_sdk::chainhooks::types::{
BitcoinChainhookSpecification, ChainhookFullSpecification, ChainhookSpecification,
};
use chainhook_sdk::observer::{
start_event_observer, BitcoinBlockDataCached, EventObserverConfig, HandleBlock,
ObserverCommand, ObserverEvent, ObserverSidecar,
start_event_observer, BitcoinBlockDataCached, DataHandlerEvent, EventObserverConfig,
HandleBlock, ObserverCommand, ObserverEvent, ObserverSidecar,
};
use chainhook_sdk::types::{BitcoinBlockData, BlockIdentifier};
use chainhook_sdk::utils::{BlockHeights, Context};
@@ -374,7 +374,7 @@ impl Service {
) -> Result<
(
EventObserverConfig,
Option<crossbeam_channel::Receiver<BitcoinChainhookOccurrencePayload>>,
Option<crossbeam_channel::Receiver<DataHandlerEvent>>,
),
String,
> {
@@ -456,9 +456,7 @@ impl Service {
&self.ctx,
)?;
}
let tx_replayer = start_observer_forwarding(event_observer_config, &self.ctx);
self.update_state(Some(tx_replayer)).await?;
Ok(())
}
@@ -469,7 +467,6 @@ impl Service {
) -> Result<(), String> {
// Start predicate processor
let mut last_block_processed = 0;
while let Some((start_block, end_block, speed)) =
should_sync_ordhook_db(&self.config, &self.ctx)?
{
@@ -482,10 +479,12 @@ impl Service {
block_post_processor.clone(),
);
info!(
self.ctx.expect_logger(),
"Indexing inscriptions from block #{start_block} to block #{end_block}"
);
self.ctx.try_log(|logger| {
info!(
logger,
"Indexing inscriptions from block #{start_block} to block #{end_block}"
)
});
let ordhook_config = self.config.get_ordhook_config();
let first_inscription_height = ordhook_config.first_inscription_height;
@@ -515,7 +514,7 @@ impl Service {
let blocks_post_processor =
start_transfers_recomputing_processor(&self.config, &self.ctx, block_post_processor);
let ordhook_config = self.config.get_ordhook_config();
let ordhook_config = self.config.get_ordhook_config();
let first_inscription_height = ordhook_config.first_inscription_height;
download_and_pipeline_blocks(
&self.config,

4755
components/ordhook-sdk-js/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,25 @@
[package]
name = "ordhook-sdk-js"
version = "0.2.0"
license = "ISC"
edition = "2018"
exclude = ["index.node"]
[lib]
crate-type = ["cdylib"]
[dependencies]
serde = "1"
error-chain = "0.12"
ordhook = { path = "../ordhook-core" }
hiro-system-kit = "0.1.0"
crossbeam-channel = "0.5.6"
[dependencies.neon]
version = "0.9.1"
default-features = false
features = ["napi-4", "channel-api", "event-queue-api", "try-catch-api"]
[dependencies.num]
version = "0.2"
default-features = false

View File

@@ -0,0 +1,58 @@
# ordhook-sdk-js
`ordhook-sdk-js` is a node library, designed to let developers write protocols sitting on top of the Ordinals theory.
It is implemented as a dynamic library that can be loaded by Node.
### Installation
```console
# Yarn
yarn add dev @hirosystems/ordhook-sdk-js
# NPM
npm install --save-dev @hirosystems/ordhook-sdk-js
```
If any error occurs during the installation of this package, feel free to open an issue on this repository.
### Usage
```typescript
import { OrdinalsIndexer } from "@hirosystems/ordhook-sdk-js";
let indexer = new OrdinalsIndexer({
bitcoinRpcUrl: 'http://localhost:8332',
bitcoinRpcUsername: 'devnet',
bitcoinRpcPassword: 'devnet',
workingDirectory: '/etc/ordinals/db'
logs: true
});
indexer.applyBlock((block) => {
})
indexer.undoBlock((block) => {
})
indexer.onTermination(() => {
})
indexer.start();
// indexer.dropBlock();
// indexer.recomputeBlock();
// indexer.replayBlocks();
```
### Case Study
BRC20
### Screencasts

View File

@@ -0,0 +1,71 @@
"use strict";
const {
ordinalsIndexerNew,
ordinalsIndexerStart,
ordinalsIndexerOnBlockApply,
ordinalsIndexerOnBlockUndo,
} = require("../native/index.node");
// import {
// BitcoinChainUpdate,
// Block,
// StacksBlockMetadata,
// StacksBlockUpdate,
// StacksChainUpdate,
// StacksTransaction,
// StacksTransactionMetadata,
// Transaction,
// } from "@hirosystems/chainhook-types";
// export * from "@hirosystems/chainhook-types";
export class OrdinalsIndexer {
handle: any;
/**
* @summary Construct a new OrdinalsIndexer
* @param
* @memberof OrdinalsIndexer
*/
constructor(settings: {
bitcoinRpcUrl: string,
bitcoinRpcUsername: string,
bitcoinRpcPassword: string,
workingDirectory: string,
logs: boolean,
}) {
this.handle = ordinalsIndexerNew(settings);
}
/**
* @summary Start indexing ordinals
* @memberof OrdinalsIndexer
*/
start() {
return ordinalsIndexerStart.call(this.handle);
}
/**
* @summary Apply Block
* @memberof OrdinalsIndexer
*/
applyBlock(callback: (block: any) => void) {
return ordinalsIndexerOnBlockApply.call(this.handle, callback);
}
/**
* @summary Undo Block
* @memberof OrdinalsIndexer
*/
undoBlock(callback: (block: any) => void) {
return ordinalsIndexerOnBlockUndo.call(this.handle, callback);
}
// /**
// * @summary Terminates the containers
// * @memberof DevnetNetworkOrchestrator
// */
// terminate(): boolean {
// return stacksDevnetTerminate.call(this.handle);
// }
}

View File

@@ -0,0 +1,19 @@
import { OrdinalsIndexer } from "./index";
const indexer = new OrdinalsIndexer({
bitcoinRpcUrl: 'http://0.0.0.0:8332',
bitcoinRpcUsername: 'devnet',
bitcoinRpcPassword: 'devnet',
workingDirectory: '/Users/ludovic/ordhook-sdk-js',
logs: false
});
indexer.applyBlock(block => {
console.log(`Hello from JS ${JSON.stringify(block)}`);
});
indexer.undoBlock(block => {
console.log(`Hello from JS ${JSON.stringify(block)}`);
});
indexer.start();

2552
components/ordhook-sdk-js/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,48 @@
{
"name": "@hirosystems/ordhook-sdk-js",
"version": "1.7.1",
"description": "ordhook-sdk-js is a library for writing protocols .",
"author": "Ludo Galabru",
"repository": "https://github.com/hirosystems/ordhook/tree/main/components/ordhook-sdk-js",
"license": "GPL-3.0",
"main": "dist/index.js",
"files": [
"dist"
],
"scripts": {
"build": "tsc --build && cargo-cp-artifact -nc native/index.node -- cargo build --message-format=json-render-diagnostics",
"build-debug": "npm run build --",
"build-release": "npm run build -- --release",
"build-linux-x64-glibc": "npm run build-release -- --target x86_64-unknown-linux-gnu",
"build-linux-x64-musl": "npm run build-release -- --target x86_64-unknown-linux-musl",
"build-windows-x64": "npm run build-release -- --target x86_64-pc-windows-msvc",
"build-darwin-x64": "npm run build-release -- --target x86_64-apple-darwin",
"build-darwin-arm64": "npm run build-release -- --target aarch64-apple-darwin",
"install": "node-pre-gyp install --fallback-to-build=false || npm run build-release",
"lint": "eslint .",
"package": "node-pre-gyp package",
"spec": "jest",
"test": "npm run build && npm run spec",
"upload-binary": "npm run build-release && node-pre-gyp package && node-pre-gyp-github publish",
"version": "npm run build-release"
},
"dependencies": {
"@hirosystems/chainhook-types": "^1.1.2",
"@mapbox/node-pre-gyp": "^1.0.8",
"neon-cli": "^0.9.1",
"node-pre-gyp-github": "^1.4.3",
"typescript": "^4.5.5"
},
"devDependencies": {
"@types/node": "^16.11.11",
"cargo-cp-artifact": "^0.1"
},
"binary": {
"module_name": "index",
"host": "https://github.com/hirosystems/clarinet/releases/download/",
"remote_path": "v{version}",
"package_name": "stacks-devnet-js-{platform}-{arch}-{libc}.tar.gz",
"module_path": "./native",
"pkg_path": "."
}
}

View File

@@ -0,0 +1,308 @@
#[macro_use]
extern crate error_chain;
mod serde;
use core::panic;
use crossbeam_channel::{select, Sender};
use neon::prelude::*;
use ordhook::chainhook_sdk::chainhooks::bitcoin::{
BitcoinApplyTransactionPayload, BitcoinRollbackTransactionPayload,
};
use ordhook::chainhook_sdk::observer::DataHandlerEvent;
use ordhook::chainhook_sdk::utils::Context as OrdhookContext;
use ordhook::config::Config;
use ordhook::db::{initialize_ordhook_db, open_readwrite_ordhook_db_conn_rocks_db};
use ordhook::service::Service;
use std::thread;
type ApplyCallback = Box<dyn FnOnce(&Channel, BitcoinApplyTransactionPayload) + Send>;
type UndoCallback = Box<dyn Fn(&Channel, BitcoinRollbackTransactionPayload) + Send>;
struct OrdinalsIndexerConfig {
pub bitcoin_rpc_url: String,
pub bitcoin_rpc_username: String,
pub bitcoin_rpc_password: String,
pub working_directory: String,
pub logs_enabled: bool,
}
impl OrdinalsIndexerConfig {
pub fn default() -> OrdinalsIndexerConfig {
OrdinalsIndexerConfig {
bitcoin_rpc_url: "http://0.0.0.0:8332".to_string(),
bitcoin_rpc_username: "devnet".to_string(),
bitcoin_rpc_password: "devnet".to_string(),
working_directory: "/tmp/ordinals".to_string(),
logs_enabled: true,
}
}
}
struct OrdinalsIndexer {
command_tx: Sender<IndexerCommand>,
custom_indexer_command_tx: Sender<CustomIndexerCommand>,
}
enum IndexerCommand {
Start,
Stop,
}
enum CustomIndexerCommand {
UpdateApplyCallback(Root<JsFunction>),
UpdateUndoCallback(Root<JsFunction>),
}
impl Finalize for OrdinalsIndexer {}
impl OrdinalsIndexer {
fn new<'a, C>(cx: &mut C, ordhook_config: Config) -> Self
where
C: Context<'a>,
{
let (command_tx, command_rx) = crossbeam_channel::unbounded();
let (custom_indexer_command_tx, custom_indexer_command_rx) = crossbeam_channel::unbounded();
let logger = hiro_system_kit::log::setup_logger();
let _guard = hiro_system_kit::log::setup_global_logger(logger.clone());
let ctx = OrdhookContext {
logger: Some(logger),
tracer: false,
};
// Initialize service
// {
// let _ = initialize_ordhook_db(&ordhook_config.expected_cache_path(), &ctx);
// let _ = open_readwrite_ordhook_db_conn_rocks_db(&ordhook_config.expected_cache_path(), &ctx);
// }
let mut service: Service = Service::new(ordhook_config, ctx);
// Set-up the observer sidecar - used for augmenting the bitcoin blocks with
// ordinals informations
let observer_sidecar = service
.set_up_observer_sidecar_runloop()
.expect("unable to setup indexer");
// Prepare internal predicate
let (observer_config, payload_rx) = service
.set_up_observer_config(vec![], true)
.expect("unable to setup indexer");
// Indexing thread
let channel = cx.channel();
thread::spawn(move || {
let payload_rx = payload_rx.unwrap();
channel.send(move |mut cx| {
let mut apply_callback: Option<Root<JsFunction>> = None;
let mut undo_callback: Option<Root<JsFunction>> = None;
loop {
select! {
recv(payload_rx) -> msg => {
match msg {
Ok(DataHandlerEvent::Process(payload)) => {
if let Some(ref callback) = undo_callback {
for to_rollback in payload.rollback.into_iter() {
let callback = callback.clone(&mut cx).into_inner(&mut cx);
let this = cx.undefined();
let payload = serde::to_value(&mut cx, &to_rollback).expect("Unable to serialize block");
let args: Vec<Handle<JsValue>> = vec![payload];
callback.call(&mut cx, this, args)?;
}
}
if let Some(ref callback) = apply_callback {
for to_apply in payload.apply.into_iter() {
let callback = callback.clone(&mut cx).into_inner(&mut cx);
let this = cx.undefined();
let payload = serde::to_value(&mut cx, &to_apply).expect("Unable to serialize block");
let args: Vec<Handle<JsValue>> = vec![payload];
callback.call(&mut cx, this, args)?;
}
}
}
Ok(DataHandlerEvent::Terminate) => {
return Ok(());
}
_ => {
}
}
}
recv(custom_indexer_command_rx) -> msg => {
match msg {
Ok(CustomIndexerCommand::UpdateApplyCallback(callback)) => {
apply_callback = Some(callback);
}
Ok(CustomIndexerCommand::UpdateUndoCallback(callback)) => {
undo_callback = Some(callback);
}
_ => {}
}
}
}
}
});
});
// Processing thread
thread::spawn(move || {
loop {
let cmd = match command_rx.recv() {
Ok(cmd) => cmd,
Err(e) => {
panic!("Runloop error: {}", e.to_string());
}
};
match cmd {
IndexerCommand::Start => {
// We start the service as soon as the start() method is being called.
let future = service.catch_up_with_chain_tip(false, &observer_config);
let _ = hiro_system_kit::nestable_block_on(future)
.expect("unable to start indexer");
let future = service.start_event_observer(observer_sidecar);
let (command_tx, event_rx) = hiro_system_kit::nestable_block_on(future)
.expect("unable to start indexer");
// Blocking call
let _ = service.start_main_runloop(&command_tx, event_rx, None);
break;
}
IndexerCommand::Stop => {
break;
}
}
}
});
Self {
command_tx,
custom_indexer_command_tx,
// termination_rx,
}
}
fn start(&self) -> Result<bool, String> {
let _ = self.command_tx.send(IndexerCommand::Start);
Ok(true)
}
fn update_apply_callback(&self, callback: Root<JsFunction>) -> Result<bool, String> {
let _ = self
.custom_indexer_command_tx
.send(CustomIndexerCommand::UpdateApplyCallback(callback));
Ok(true)
}
fn update_undo_callback(&self, callback: Root<JsFunction>) -> Result<bool, String> {
let _ = self
.custom_indexer_command_tx
.send(CustomIndexerCommand::UpdateUndoCallback(callback));
Ok(true)
}
}
impl OrdinalsIndexer {
fn js_new(mut cx: FunctionContext) -> JsResult<JsBox<OrdinalsIndexer>> {
let settings = cx.argument::<JsObject>(0)?;
let mut config = OrdinalsIndexerConfig::default();
if let Ok(res) = settings
.get(&mut cx, "bitcoinRpcUrl")?
.downcast::<JsString, _>(&mut cx)
{
config.bitcoin_rpc_url = res.value(&mut cx);
}
if let Ok(res) = settings
.get(&mut cx, "bitcoinRpcUsername")?
.downcast::<JsString, _>(&mut cx)
{
config.bitcoin_rpc_username = res.value(&mut cx);
}
if let Ok(res) = settings
.get(&mut cx, "bitcoinRpcPassword")?
.downcast::<JsString, _>(&mut cx)
{
config.bitcoin_rpc_password = res.value(&mut cx);
}
if let Ok(res) = settings
.get(&mut cx, "workingDirectory")?
.downcast::<JsString, _>(&mut cx)
{
config.working_directory = res.value(&mut cx);
}
if let Ok(res) = settings
.get(&mut cx, "logs")?
.downcast::<JsBoolean, _>(&mut cx)
{
config.logs_enabled = res.value(&mut cx);
}
let mut ordhook_config = Config::mainnet_default();
ordhook_config.network.bitcoind_rpc_username = config.bitcoin_rpc_username.clone();
ordhook_config.network.bitcoind_rpc_password = config.bitcoin_rpc_password.clone();
ordhook_config.network.bitcoind_rpc_url = config.bitcoin_rpc_url.clone();
ordhook_config.storage.working_dir = config.working_directory.clone();
ordhook_config.logs.chainhook_internals = config.logs_enabled;
ordhook_config.logs.ordinals_internals = config.logs_enabled;
let devnet: OrdinalsIndexer = OrdinalsIndexer::new(&mut cx, ordhook_config);
Ok(cx.boxed(devnet))
}
fn js_start(mut cx: FunctionContext) -> JsResult<JsUndefined> {
cx.this()
.downcast_or_throw::<JsBox<OrdinalsIndexer>, _>(&mut cx)?
.start()
.or_else(|err| cx.throw_error(err.to_string()))?;
Ok(cx.undefined())
}
fn js_terminate(mut cx: FunctionContext) -> JsResult<JsBoolean> {
unimplemented!();
}
fn js_on_block_apply(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let callback = cx.argument::<JsFunction>(0)?.root(&mut cx);
cx.this()
.downcast_or_throw::<JsBox<OrdinalsIndexer>, _>(&mut cx)?
.update_apply_callback(callback)
.or_else(|err| cx.throw_error(err.to_string()))?;
Ok(cx.undefined())
}
fn js_on_block_undo(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let callback = cx.argument::<JsFunction>(0)?.root(&mut cx);
cx.this()
.downcast_or_throw::<JsBox<OrdinalsIndexer>, _>(&mut cx)?
.update_undo_callback(callback)
.or_else(|err| cx.throw_error(err.to_string()))?;
Ok(cx.undefined())
}
}
#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
cx.export_function("ordinalsIndexerNew", OrdinalsIndexer::js_new)?;
cx.export_function("ordinalsIndexerStart", OrdinalsIndexer::js_start)?;
cx.export_function("ordinalsIndexerTerminate", OrdinalsIndexer::js_terminate)?;
cx.export_function(
"ordinalsIndexerOnBlockApply",
OrdinalsIndexer::js_on_block_apply,
)?;
cx.export_function(
"ordinalsIndexerOnBlockUndo",
OrdinalsIndexer::js_on_block_undo,
)?;
Ok(())
}

View File

@@ -0,0 +1,84 @@
//! Defines error handling types used by the create
//! uses the `error-chain` create for generation
use neon;
use serde::ser;
use std::convert::From;
use std::fmt::Display;
error_chain! {
errors {
/// nodejs has a hard coded limit on string length
/// trying to serialize a string that is too long will result in an error
StringTooLong(len: usize) {
description("String too long for nodejs")
display("String too long for nodejs len: {}", len)
}
/// when deserializing to a boolean `false` `undefined` `null` `number`
/// are valid inputs
/// any other types will result in error
UnableToCoerce(to_type: &'static str) {
description("Unable to coerce")
display("Unable to coerce value to type: {}", to_type)
}
/// occurs when deserializing a char from an empty string
EmptyString {
description("EmptyString")
display("EmptyString")
}
/// occurs when deserializing a char from a sting with
/// more than one character
StringTooLongForChar(len: usize) {
description("String too long to be a char")
display("String too long to be a char expected len: 1 got len: {}", len)
}
/// occurs when a deserializer expects a `null` or `undefined`
/// property and found another type
ExpectingNull {
description("ExpectingNull")
display("ExpectingNull")
}
/// occurs when deserializing to an enum and the source object has
/// a none-1 number of properties
InvalidKeyType(key: String) {
description("InvalidKeyType")
display("key: '{}'", key)
}
/// an internal deserialization error from an invalid array
ArrayIndexOutOfBounds(index: u32, length: u32) {
description("ArrayIndexOutOfBounds")
display(
"ArrayIndexOutOfBounds: attempt to access ({}) size: ({})",
index,
length
)
} #[doc(hidden)]
/// This type of object is not supported
NotImplemented(name: &'static str) {
description("Not Implemented")
display("Not Implemented: '{}'", name)
}
/// A JS exception was thrown
Js(throw: neon::result::Throw) {
description("JS exception")
display("JS exception")
}
/// failed to convert something to f64
CastError {
description("CastError")
display("CastError")
}
}
}
impl ser::Error for Error {
fn custom<T: Display>(msg: T) -> Self {
ErrorKind::Msg(msg.to_string()).into()
}
}
impl From<neon::result::Throw> for Error {
fn from(throw: neon::result::Throw) -> Self {
ErrorKind::Js(throw).into()
}
}

View File

@@ -0,0 +1,4 @@
mod errors;
mod ser;
pub use ser::to_value;

View File

@@ -0,0 +1,575 @@
//!
//! Serialize a Rust data structure into a `JsValue`
//!
use super::errors::Error;
use super::errors::ErrorKind;
use super::errors::Result as LibResult;
use neon::prelude::*;
use num;
use serde::ser::{self, Serialize};
use std::marker::PhantomData;
fn as_num<T: num::cast::NumCast, OutT: num::cast::NumCast>(n: T) -> LibResult<OutT> {
match num::cast::<T, OutT>(n) {
Some(n2) => Ok(n2),
None => bail!(ErrorKind::CastError),
}
}
/// Converts a value of type `V` to a `JsValue`
///
/// # Errors
///
/// * `NumberCastError` trying to serialize a `u64` can fail if it overflows in a cast to `f64`
/// * `StringTooLong` if the string exceeds v8's max string size
///
#[inline]
pub fn to_value<'j, C, V>(cx: &mut C, value: &V) -> LibResult<Handle<'j, JsValue>>
where
C: Context<'j>,
V: Serialize + ?Sized,
{
let serializer = Serializer {
cx,
ph: PhantomData,
};
let serialized_value = value.serialize(serializer)?;
Ok(serialized_value)
}
#[doc(hidden)]
pub struct Serializer<'a, 'j, C: 'a>
where
C: Context<'j>,
{
cx: &'a mut C,
ph: PhantomData<&'j ()>,
}
#[doc(hidden)]
pub struct ArraySerializer<'a, 'j, C: 'a>
where
C: Context<'j>,
{
cx: &'a mut C,
array: Handle<'j, JsArray>,
}
#[doc(hidden)]
pub struct TupleVariantSerializer<'a, 'j, C: 'a>
where
C: Context<'j>,
{
outer_object: Handle<'j, JsObject>,
inner: ArraySerializer<'a, 'j, C>,
}
#[doc(hidden)]
pub struct MapSerializer<'a, 'j, C: 'a>
where
C: Context<'j>,
{
cx: &'a mut C,
object: Handle<'j, JsObject>,
key_holder: Handle<'j, JsObject>,
}
#[doc(hidden)]
pub struct StructSerializer<'a, 'j, C: 'a>
where
C: Context<'j>,
{
cx: &'a mut C,
object: Handle<'j, JsObject>,
}
#[doc(hidden)]
pub struct StructVariantSerializer<'a, 'j, C: 'a>
where
C: Context<'j>,
{
outer_object: Handle<'j, JsObject>,
inner: StructSerializer<'a, 'j, C>,
}
#[doc(hidden)]
impl<'a, 'j, C> ser::Serializer for Serializer<'a, 'j, C>
where
C: Context<'j>,
{
type Ok = Handle<'j, JsValue>;
type Error = Error;
type SerializeSeq = ArraySerializer<'a, 'j, C>;
type SerializeTuple = ArraySerializer<'a, 'j, C>;
type SerializeTupleStruct = ArraySerializer<'a, 'j, C>;
type SerializeTupleVariant = TupleVariantSerializer<'a, 'j, C>;
type SerializeMap = MapSerializer<'a, 'j, C>;
type SerializeStruct = StructSerializer<'a, 'j, C>;
type SerializeStructVariant = StructVariantSerializer<'a, 'j, C>;
#[inline]
fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error> {
Ok(JsBoolean::new(self.cx, v).upcast())
}
#[inline]
fn serialize_i8(self, v: i8) -> Result<Self::Ok, Self::Error> {
Ok(JsNumber::new(self.cx, as_num::<_, f64>(v)?).upcast())
}
#[inline]
fn serialize_i16(self, v: i16) -> Result<Self::Ok, Self::Error> {
Ok(JsNumber::new(self.cx, as_num::<_, f64>(v)?).upcast())
}
#[inline]
fn serialize_i32(self, v: i32) -> Result<Self::Ok, Self::Error> {
Ok(JsNumber::new(self.cx, as_num::<_, f64>(v)?).upcast())
}
#[inline]
fn serialize_i64(self, v: i64) -> Result<Self::Ok, Self::Error> {
Ok(JsNumber::new(self.cx, as_num::<_, f64>(v)?).upcast())
}
#[inline]
fn serialize_i128(self, v: i128) -> Result<Self::Ok, Self::Error> {
Ok(JsNumber::new(self.cx, as_num::<_, f64>(v)?).upcast())
}
#[inline]
fn serialize_u8(self, v: u8) -> Result<Self::Ok, Self::Error> {
Ok(JsNumber::new(self.cx, as_num::<_, f64>(v)?).upcast())
}
#[inline]
fn serialize_u16(self, v: u16) -> Result<Self::Ok, Self::Error> {
Ok(JsNumber::new(self.cx, as_num::<_, f64>(v)?).upcast())
}
#[inline]
fn serialize_u32(self, v: u32) -> Result<Self::Ok, Self::Error> {
Ok(JsNumber::new(self.cx, as_num::<_, f64>(v)?).upcast())
}
#[inline]
fn serialize_u64(self, v: u64) -> Result<Self::Ok, Self::Error> {
Ok(JsNumber::new(self.cx, as_num::<_, f64>(v)?).upcast())
}
#[inline]
fn serialize_u128(self, v: u128) -> Result<Self::Ok, Self::Error> {
Ok(JsNumber::new(self.cx, as_num::<_, f64>(v)?).upcast())
}
#[inline]
fn serialize_f32(self, v: f32) -> Result<Self::Ok, Self::Error> {
Ok(JsNumber::new(self.cx, as_num::<_, f64>(v)?).upcast())
}
#[inline]
fn serialize_f64(self, v: f64) -> Result<Self::Ok, Self::Error> {
Ok(JsNumber::new(self.cx, v).upcast())
}
fn serialize_char(self, v: char) -> Result<Self::Ok, Self::Error> {
let mut b = [0; 4];
let result = v.encode_utf8(&mut b);
let js_str =
JsString::try_new(self.cx, result).map_err(|_| ErrorKind::StringTooLongForChar(4))?;
Ok(js_str.upcast())
}
#[inline]
fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
let len = v.len();
let js_str = JsString::try_new(self.cx, v).map_err(|_| ErrorKind::StringTooLong(len))?;
Ok(js_str.upcast())
}
#[inline]
fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok, Self::Error> {
let mut buff = JsBuffer::new(self.cx, as_num::<_, u32>(v.len())?)?;
self.cx
.borrow_mut(&mut buff, |buff| buff.as_mut_slice().clone_from_slice(v));
Ok(buff.upcast())
}
#[inline]
fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
Ok(JsNull::new(self.cx).upcast())
}
#[inline]
fn serialize_some<T: ?Sized>(self, value: &T) -> Result<Self::Ok, Self::Error>
where
T: Serialize,
{
value.serialize(self)
}
#[inline]
fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
Ok(JsNull::new(self.cx).upcast())
}
#[inline]
fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok, Self::Error> {
Ok(JsNull::new(self.cx).upcast())
}
#[inline]
fn serialize_unit_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
) -> Result<Self::Ok, Self::Error> {
self.serialize_str(variant)
}
#[inline]
fn serialize_newtype_struct<T: ?Sized>(
self,
_name: &'static str,
value: &T,
) -> Result<Self::Ok, Self::Error>
where
T: Serialize,
{
value.serialize(self)
}
#[inline]
fn serialize_newtype_variant<T: ?Sized>(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
value: &T,
) -> Result<Self::Ok, Self::Error>
where
T: Serialize,
{
let obj = JsObject::new(&mut *self.cx);
let value_js = to_value(self.cx, value)?;
obj.set(self.cx, variant, value_js)?;
Ok(obj.upcast())
}
#[inline]
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
Ok(ArraySerializer::new(self.cx))
}
#[inline]
fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, Self::Error> {
Ok(ArraySerializer::new(self.cx))
}
#[inline]
fn serialize_tuple_struct(
self,
_name: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleStruct, Self::Error> {
Ok(ArraySerializer::new(self.cx))
}
#[inline]
fn serialize_tuple_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleVariant, Self::Error> {
TupleVariantSerializer::new(self.cx, variant)
}
#[inline]
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
Ok(MapSerializer::new(self.cx))
}
#[inline]
fn serialize_struct(
self,
_name: &'static str,
_len: usize,
) -> Result<Self::SerializeStruct, Self::Error> {
Ok(StructSerializer::new(self.cx))
}
#[inline]
fn serialize_struct_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
_len: usize,
) -> Result<Self::SerializeStructVariant, Self::Error> {
StructVariantSerializer::new(self.cx, variant)
}
}
#[doc(hidden)]
impl<'a, 'j, C> ArraySerializer<'a, 'j, C>
where
C: Context<'j>,
{
#[inline]
fn new(cx: &'a mut C) -> Self {
let array = JsArray::new(cx, 0);
ArraySerializer { cx, array }
}
}
#[doc(hidden)]
impl<'a, 'j, C> ser::SerializeSeq for ArraySerializer<'a, 'j, C>
where
C: Context<'j>,
{
type Ok = Handle<'j, JsValue>;
type Error = Error;
fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<(), Self::Error>
where
T: Serialize,
{
let value = to_value(self.cx, value)?;
let arr: Handle<'j, JsArray> = self.array;
let len = arr.len(self.cx);
arr.set(self.cx, len, value)?;
Ok(())
}
#[inline]
fn end(self) -> Result<Self::Ok, Self::Error> {
Ok(self.array.upcast())
}
}
impl<'a, 'j, C> ser::SerializeTuple for ArraySerializer<'a, 'j, C>
where
C: Context<'j>,
{
type Ok = Handle<'j, JsValue>;
type Error = Error;
#[inline]
fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<(), Self::Error>
where
T: Serialize,
{
ser::SerializeSeq::serialize_element(self, value)
}
#[inline]
fn end(self) -> Result<Self::Ok, Self::Error> {
ser::SerializeSeq::end(self)
}
}
#[doc(hidden)]
impl<'a, 'j, C> ser::SerializeTupleStruct for ArraySerializer<'a, 'j, C>
where
C: Context<'j>,
{
type Ok = Handle<'j, JsValue>;
type Error = Error;
#[inline]
fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<(), Self::Error>
where
T: Serialize,
{
ser::SerializeSeq::serialize_element(self, value)
}
#[inline]
fn end(self) -> Result<Self::Ok, Self::Error> {
ser::SerializeSeq::end(self)
}
}
#[doc(hidden)]
impl<'a, 'j, C> TupleVariantSerializer<'a, 'j, C>
where
C: Context<'j>,
{
fn new(cx: &'a mut C, key: &'static str) -> LibResult<Self> {
let inner_array = JsArray::new(cx, 0);
let outer_object = JsObject::new(cx);
outer_object.set(cx, key, inner_array)?;
Ok(TupleVariantSerializer {
outer_object,
inner: ArraySerializer {
cx,
array: inner_array,
},
})
}
}
#[doc(hidden)]
impl<'a, 'j, C> ser::SerializeTupleVariant for TupleVariantSerializer<'a, 'j, C>
where
C: Context<'j>,
{
type Ok = Handle<'j, JsValue>;
type Error = Error;
#[inline]
fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<(), Self::Error>
where
T: Serialize,
{
use serde::ser::SerializeSeq;
self.inner.serialize_element(value)
}
#[inline]
fn end(self) -> Result<Self::Ok, Self::Error> {
Ok(self.outer_object.upcast())
}
}
#[doc(hidden)]
impl<'a, 'j, C> MapSerializer<'a, 'j, C>
where
C: Context<'j>,
{
fn new(cx: &'a mut C) -> Self {
let object = JsObject::new(cx);
let key_holder = JsObject::new(cx);
MapSerializer {
cx,
object,
key_holder,
}
}
}
#[doc(hidden)]
impl<'a, 'j, C> ser::SerializeMap for MapSerializer<'a, 'j, C>
where
C: Context<'j>,
{
type Ok = Handle<'j, JsValue>;
type Error = Error;
fn serialize_key<T: ?Sized>(&mut self, key: &T) -> Result<(), Self::Error>
where
T: Serialize,
{
let key = to_value(self.cx, key)?;
self.key_holder.set(self.cx, "key", key)?;
Ok(())
}
fn serialize_value<T: ?Sized>(&mut self, value: &T) -> Result<(), Self::Error>
where
T: Serialize,
{
let key: Handle<'j, JsValue> = self.key_holder.get(&mut *self.cx, "key")?;
let value_obj = to_value(self.cx, value)?;
self.object.set(self.cx, key, value_obj)?;
Ok(())
}
#[inline]
fn end(self) -> Result<Self::Ok, Self::Error> {
Ok(self.object.upcast())
}
}
#[doc(hidden)]
impl<'a, 'j, C> StructSerializer<'a, 'j, C>
where
C: Context<'j>,
{
#[inline]
fn new(cx: &'a mut C) -> Self {
let object = JsObject::new(cx);
StructSerializer { cx, object }
}
}
#[doc(hidden)]
impl<'a, 'j, C> ser::SerializeStruct for StructSerializer<'a, 'j, C>
where
C: Context<'j>,
{
type Ok = Handle<'j, JsValue>;
type Error = Error;
#[inline]
fn serialize_field<T: ?Sized>(
&mut self,
key: &'static str,
value: &T,
) -> Result<(), Self::Error>
where
T: Serialize,
{
let value = to_value(self.cx, value)?;
self.object.set(self.cx, key, value)?;
Ok(())
}
#[inline]
fn end(self) -> Result<Self::Ok, Self::Error> {
Ok(self.object.upcast())
}
}
#[doc(hidden)]
impl<'a, 'j, C> StructVariantSerializer<'a, 'j, C>
where
C: Context<'j>,
{
fn new(cx: &'a mut C, key: &'static str) -> LibResult<Self> {
let inner_object = JsObject::new(cx);
let outer_object = JsObject::new(cx);
outer_object.set(cx, key, inner_object)?;
Ok(StructVariantSerializer {
outer_object: outer_object,
inner: StructSerializer {
cx,
object: inner_object,
},
})
}
}
#[doc(hidden)]
impl<'a, 'j, C> ser::SerializeStructVariant for StructVariantSerializer<'a, 'j, C>
where
C: Context<'j>,
{
type Ok = Handle<'j, JsValue>;
type Error = Error;
#[inline]
fn serialize_field<T: ?Sized>(
&mut self,
key: &'static str,
value: &T,
) -> Result<(), Self::Error>
where
T: Serialize,
{
use serde::ser::SerializeStruct;
self.inner.serialize_field(key, value)
}
#[inline]
fn end(self) -> Result<Self::Ok, Self::Error> {
Ok(self.outer_object.upcast())
}
}

View File

@@ -0,0 +1,100 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
// "incremental": true, /* Enable incremental compilation */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es5", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "resolveJsonModule": true, /* Enable importing .json files */
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
/* Emit */
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
"declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
"outDir": "./dist", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true, /* Skip type checking all .d.ts files. */
}
}