use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{DeriveInput, Ident};
use super::ViewAttributes;
pub fn mutable_attributes() -> (TokenStream, TokenStream) { (quote! { #[wasm_bindgen] }, quote! { #[wasm_bindgen(skip)] }) }
pub fn view_attributes(view_name: &Ident, mutable_name: &Ident, model_name: &Ident) -> ViewAttributes {
let subscribe_ts = format!(
r#"export interface {view_name} {{
subscribe(callback: () => void): SubscriptionGuard;
}}"#
);
ViewAttributes {
struct_attr: quote! { #[wasm_bindgen] },
impl_attr: quote! { #[wasm_bindgen] },
id_method_attr: quote! { #[wasm_bindgen(getter)] },
extra_impl: quote! {
#[wasm_bindgen(typescript_custom_section)]
const TS_VIEW_SUBSCRIBE: &'static str = #subscribe_ts;
#[wasm_bindgen]
impl #view_name {
#[wasm_bindgen(js_name = "edit")]
pub fn edit_wasm(&self, trx: &ankurah::transaction::Transaction) -> Result<#mutable_name, ::wasm_bindgen::JsValue> {
use ::ankurah::model::View;
match trx.edit::<#model_name>(&self.entity) {
Ok(mutable_borrow) => {
Ok(mutable_borrow.into_core())
}
Err(e) => Err(::wasm_bindgen::JsValue::from(e.to_string()))
}
}
#[wasm_bindgen(skip_typescript, js_name = "subscribe")]
pub fn subscribe_wasm(&self, callback: ::ankurah::derive_deps::js_sys::Function) -> ::ankurah::signals::SubscriptionGuard {
let callback = ::ankurah::derive_deps::send_wrapper::SendWrapper::new(callback);
::ankurah::core::model::view_subscribe_no_clone(self, move |_| {
let _ = callback.call0(
&::ankurah::derive_deps::wasm_bindgen::JsValue::NULL,
);
})
}
}
},
}
}
pub fn wasm_impl(input: &syn::DeriveInput, model: &crate::model::description::ModelDescription) -> TokenStream {
let namespace_struct = format_ident!("NS{}", model.name());
let pojo_interface = format_ident!("{}", model.name());
let tsify_impl = expand_ts_model_type(input, pojo_interface.to_string()).unwrap_or_else(syn::Error::into_compile_error);
let namespace_class = wasm_model_namespace(&namespace_struct, model, &model.view_name(), &model.livequery_name(), &pojo_interface);
let resultset_wrapper = wasm_resultset_wrapper(&model.resultset_name(), &model.view_name());
let changeset_wrapper = wasm_changeset_wrapper(&model.changeset_name(), &model.view_name(), &model.resultset_name());
let livequery_wrapper =
wasm_livequery_wrapper(&model.livequery_name(), &model.view_name(), &model.resultset_name(), &model.changeset_name());
let ref_wrapper = wasm_ref_wrapper(&model.ref_name(), model.name(), &model.view_name());
quote! {
#tsify_impl
#ref_wrapper
#resultset_wrapper
#changeset_wrapper
#livequery_wrapper
const _: () = {
use ::ankurah::derive_deps::{tracing::error,wasm_bindgen::prelude::*, wasm_bindgen_futures};
#namespace_class
};
}
}
pub fn wasm_resultset_wrapper(resultset_name: &Ident, view_name: &Ident) -> TokenStream {
quote! {
#[wasm_bindgen]
#[derive(Clone, Default)]
pub struct #resultset_name(::ankurah::core::resultset::ResultSet<#view_name>);
#[wasm_bindgen]
impl #resultset_name {
#[wasm_bindgen(getter)]
pub fn items(&self) -> Vec<#view_name> {
use ::ankurah::signals::Get;
self.0.get()
}
pub fn by_id(&self, id: &::ankurah::proto::EntityId) -> Option<#view_name> {
::ankurah::signals::CurrentObserver::track(&self);
self.0.by_id(id)
}
#[wasm_bindgen(getter)]
pub fn loaded(&self) -> bool {
::ankurah::signals::CurrentObserver::track(&self);
self.0.is_loaded()
}
pub fn map(&self, callback: ::ankurah::derive_deps::js_sys::Function) -> ::ankurah::derive_deps::js_sys::Array {
::ankurah::core::model::js_resultset_map(&self.0, &callback)
}
}
impl ankurah::signals::Signal for #resultset_name {
fn listen(&self, listener: ::ankurah::signals::signal::Listener) -> ::ankurah::signals::signal::ListenerGuard {
self.0.listen(listener).into()
}
fn broadcast_id(&self) -> ::ankurah::signals::broadcast::BroadcastId {
use ::ankurah::signals::Signal;
self.0.broadcast_id()
}
}
}
}
pub fn wasm_changeset_wrapper(changeset_name: &Ident, view_name: &Ident, resultset_name: &Ident) -> TokenStream {
quote! {
#[wasm_bindgen]
pub struct #changeset_name(::ankurah::core::changes::ChangeSet<#view_name>);
#[wasm_bindgen]
impl #changeset_name {
#[wasm_bindgen(getter)]
pub fn resultset(&self) -> #resultset_name {
#resultset_name(self.0.resultset.wrap())
}
#[wasm_bindgen(getter)]
pub fn initial(&self) -> Vec<#view_name> {
self.0.initial()
}
#[wasm_bindgen(getter)]
pub fn added(&self) -> Vec<#view_name> {
self.0.added()
}
#[wasm_bindgen(getter)]
pub fn appeared(&self) -> Vec<#view_name> {
self.0.appeared()
}
#[wasm_bindgen(getter)]
pub fn removed(&self) -> Vec<#view_name> {
self.0.removed()
}
#[wasm_bindgen(getter)]
pub fn updated(&self) -> Vec<#view_name> {
self.0.updated()
}
#[wasm_bindgen(getter)]
#[deprecated(since = "0.7.10", note = "Use appeared, initial, or added instead")]
pub fn adds(&self) -> Vec<#view_name> {
#[allow(deprecated)]
self.0.adds()
}
#[wasm_bindgen(getter)]
#[deprecated(since = "0.7.10", note = "Use removed instead")]
pub fn removes(&self) -> Vec<#view_name> {
#[allow(deprecated)]
self.0.removes()
}
#[wasm_bindgen(getter)]
#[deprecated(since = "0.7.10", note = "Use updated instead")]
pub fn updates(&self) -> Vec<#view_name> {
#[allow(deprecated)]
self.0.updates()
}
}
}
}
pub fn wasm_livequery_wrapper(livequery_name: &Ident, view_name: &Ident, resultset_name: &Ident, changeset_name: &Ident) -> TokenStream {
let subscribe_ts = format!(
r#"export interface {livequery_name} {{
subscribe(callback: (changeset: {changeset_name}) => void, immediate?: boolean): SubscriptionGuard;
updateSelection(selection: string, ...substitution_values: any): Promise<void>;
}}"#
);
quote! {
#[wasm_bindgen(typescript_custom_section)]
const TS_APPEND_CONTENT: &'static str = #subscribe_ts;
#[wasm_bindgen]
pub struct #livequery_name(::ankurah::LiveQuery<#view_name>);
#[wasm_bindgen]
impl #livequery_name {
#[wasm_bindgen(getter)]
pub fn items(&self) -> Vec<#view_name> {
use ::ankurah::signals::Get;
self.0.get()
}
#[wasm_bindgen(getter)]
pub fn signal_id(&self) -> usize {
use ::ankurah::signals::Signal;
self.0.broadcast_id().into()
}
pub fn map(&self, callback: ::ankurah::derive_deps::js_sys::Function) -> ::ankurah::derive_deps::js_sys::Array {
::ankurah::core::model::js_resultset_map(&self.0.resultset(), &callback)
}
#[wasm_bindgen(getter)]
pub fn peek(&self) -> Vec<#view_name> {
use ::ankurah::signals::Peek;
self.0.peek()
}
#[wasm_bindgen(getter)]
pub fn loaded(&self) -> bool { self.0.loaded() }
#[wasm_bindgen(getter)]
pub fn resultset(&self) -> #resultset_name {
#resultset_name(self.0.resultset())
}
#[wasm_bindgen(getter)]
pub fn value(&self) -> #resultset_name {
#resultset_name(self.0.resultset())
}
#[wasm_bindgen(getter)]
pub fn error(&self) -> Option<String> {
use ::ankurah::signals::Get;
self.0.error().map(|e| e.as_ref().map(|e| e.to_string())).get()
}
#[wasm_bindgen(getter, js_name = currentSelection)]
pub fn current_selection(&self) -> String {
use ::ankurah::signals::With;
self.0.selection().with(|(sel, _version)| sel.to_string())
}
#[wasm_bindgen(skip_typescript)]
pub fn subscribe(&self, callback: ::ankurah::derive_deps::js_sys::Function, immediate: Option<bool>) -> ::ankurah::signals::SubscriptionGuard {
::ankurah::core::model::js_livequery_subscribe(
&self.0,
callback,
immediate.unwrap_or(true),
#changeset_name
)
}
#[wasm_bindgen(js_name = updateSelection, variadic, skip_typescript)]
pub async fn update_selection(&self, new_selection: String, substitution_values: &JsValue) -> Result<(), ::wasm_bindgen::JsValue> {
let mut selection = ::ankurah::ankql::parser::parse_selection(new_selection.as_str())?;
selection.predicate =
::ankurah::core::model::js_populate_predicate(selection.predicate, substitution_values)?;
self.0.update_selection_wait(selection)
.await
.map_err(|e| ::wasm_bindgen::JsValue::from(e.to_string()))
}
}
}
}
pub fn wasm_ref_wrapper(ref_name: &Ident, model_name: &Ident, view_name: &Ident) -> TokenStream {
quote! {
#[wasm_bindgen]
pub struct #ref_name(::ankurah::property::Ref<#model_name>);
#[wasm_bindgen]
impl #ref_name {
#[wasm_bindgen]
pub async fn get(&self, ctx: &::ankurah::core::context::Context) -> Result<#view_name, JsValue> {
use ::ankurah::model::Model;
self.0.get(ctx).await.map_err(|e| JsValue::from(e.to_string()))
}
#[wasm_bindgen(getter)]
pub fn id(&self) -> ::ankurah::proto::EntityId {
self.0.id()
}
#[wasm_bindgen(js_name = "from")]
pub fn from_view(view: &#view_name) -> Self {
#ref_name(view.r())
}
}
impl From<::ankurah::property::Ref<#model_name>> for #ref_name {
fn from(r: ::ankurah::property::Ref<#model_name>) -> Self {
#ref_name(r)
}
}
impl From<#ref_name> for ::ankurah::property::Ref<#model_name> {
fn from(r: #ref_name) -> Self {
r.0
}
}
}
}
pub fn wasm_model_namespace(
namespace_struct: &Ident,
model: &crate::model::description::ModelDescription,
view_name: &Ident,
livequery_name: &Ident,
pojo_interface: &Ident,
) -> TokenStream {
let name = model.name();
let ref_field_names: Vec<String> = model.ref_fields().iter().map(|(field, _)| field.ident.as_ref().unwrap().to_string()).collect();
let preprocess_calls: Vec<TokenStream> = ref_field_names
.iter()
.map(|field_name| {
quote! {
::ankurah::core::model::js_preprocess_ref_field(&js_value, #field_name)?;
}
})
.collect();
let static_methods_ts = format!(
r#"export class {name} {{
/** Get a single {name} by ID */
static get(context: Context, id: EntityId): Promise<{view_name}>;
/** Fetch all {name}s that match the predicate */
static fetch(context: Context, selection: string, ...substitution_values: any): Promise<{view_name}[]>;
/** Subscribe to the set of {name}s that match the predicate */
static query(context: Context, selection: string, ...substitution_values: any): {livequery_name};
/** Waits for remote subscription establishment - existing items are Initial, items added after are Add */
static query_nocache(context: Context, selection: string, ...substitution_values: any): {livequery_name};
/** Create a new {name} */
static create(transaction: Transaction, me: {pojo_interface}): Promise<{view_name}>;
/** Create a new {name} within an automatically created and committed transaction. */
static create_one(context: Context, me: {pojo_interface}): Promise<{view_name}>;
}}"#
);
quote! {
#[wasm_bindgen(typescript_custom_section)]
const TS_APPEND_CONTENT: &'static str = #static_methods_ts;
#[wasm_bindgen(js_name = #name, skip_typescript)]
pub struct #namespace_struct {}
fn js_to_model(me: ::wasm_bindgen::JsValue) -> Result<#name, ::wasm_bindgen::JsValue> {
let js_value = me;
#(#preprocess_calls)*
<#name as ::ankurah::core::model::tsify::Tsify>::from_js(js_value)
.map_err(|e| ::wasm_bindgen::JsValue::from_str(&e.to_string()))
}
#[wasm_bindgen(js_class = #name)]
impl #namespace_struct {
pub async fn get (context: &::ankurah::core::context::Context, id: ::ankurah::EntityId) -> Result<#view_name, ::wasm_bindgen::JsValue> {
context.get(id).await.map_err(|e| ::wasm_bindgen::JsValue::from(e.to_string()))
}
#[wasm_bindgen(variadic)]
pub async fn fetch (
context: &::ankurah::core::context::Context,
selection: String,
substitution_values: &JsValue
) -> Result<Vec<#view_name>, ::wasm_bindgen::JsValue> {
let mut selection = ::ankurah::ankql::parser::parse_selection(selection.as_str())?;
selection.predicate =
::ankurah::core::model::js_populate_predicate(selection.predicate, substitution_values)?;
let items = context
.fetch::<#view_name>(selection)
.await
.map_err(|e| ::wasm_bindgen::JsValue::from(e.to_string()))?;
Ok(items)
}
#[wasm_bindgen(variadic)]
pub fn query (context: &ankurah::core::context::Context, selection: String, substitution_values: &JsValue) -> Result<#livequery_name, ::wasm_bindgen::JsValue> {
let mut selection = ::ankurah::ankql::parser::parse_selection(selection.as_str())?;
selection.predicate =
::ankurah::core::model::js_populate_predicate(selection.predicate, substitution_values)?;
let livequery = context.query::<#view_name>(selection)
.map_err(|e| ::wasm_bindgen::JsValue::from(e.to_string()))?;
Ok(#livequery_name(livequery))
}
#[wasm_bindgen(variadic)]
pub fn query_nocache (context: &ankurah::core::context::Context, selection: String, substitution_values: &JsValue) -> Result<#livequery_name, ::wasm_bindgen::JsValue> {
let mut selection = ::ankurah::ankql::parser::parse_selection(selection.as_str())?;
selection.predicate =
::ankurah::core::model::js_populate_predicate(selection.predicate, substitution_values)?;
let args = ::ankurah::MatchArgs { selection, cached: false };
let livequery = context.query::<#view_name>(args)
.map_err(|e| ::wasm_bindgen::JsValue::from(e.to_string()))?;
Ok(#livequery_name(livequery))
}
pub async fn create(transaction: &::ankurah::transaction::Transaction, me: ::wasm_bindgen::JsValue) -> Result<#view_name, ::wasm_bindgen::JsValue> {
use ankurah::Mutable;
let model = js_to_model(me)?;
let mutable_entity = transaction.create(&model).await?;
Ok(mutable_entity.read())
}
pub async fn create_one(context: &::ankurah::core::context::Context, me: ::wasm_bindgen::JsValue) -> Result<#view_name, ::wasm_bindgen::JsValue> {
use ankurah::Mutable;
let tx = context.begin();
let model = js_to_model(me)?;
let mutable_entity = tx.create(&model).await?;
let read = mutable_entity.read();
tx.commit().await.map_err(|e| ::wasm_bindgen::JsValue::from(e.to_string()))?;
Ok(read)
}
}
}
}
pub fn expand_ts_model_type(input: &DeriveInput, interface_name: String) -> syn::Result<proc_macro2::TokenStream> {
let cont = crate::tsify::container::Container::from_derive_input(input)?;
let parser = crate::tsify::parser::Parser::new(&cont);
let mut decl = parser.parse();
decl.set_id(interface_name);
let tokens = crate::tsify::wasm_bindgen::expand(&cont, decl);
cont.check()?;
Ok(tokens)
}