Skip to content

Commit

Permalink
feat: improve experience with TypeDependencies trait and client sub…
Browse files Browse the repository at this point in the history
…scriptions (#3)
  • Loading branch information
andogq authored May 23, 2024
2 parents 757f39e + d121980 commit 21fbaeb
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 58 deletions.
9 changes: 2 additions & 7 deletions crates/qubit-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,8 @@ pub fn handler(
.into()
}

#[proc_macro_attribute]
pub fn exported_type(
_attrs: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
#[proc_macro_derive(TypeDependencies)]
pub fn derive_type_dependencies(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let s = syn::parse::<Item>(input).unwrap();

let (target_struct, fields) = match s {
Expand All @@ -50,8 +47,6 @@ pub fn exported_type(
};

quote! {
#s

impl qubit::TypeDependencies for #target_struct {
fn get_deps(dependencies: &mut std::collections::BTreeMap<std::string::String, std::string::String>) {
// Short circuit if this type has already been added
Expand Down
3 changes: 2 additions & 1 deletion examples/counter/bindings.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Stream } from "@qubit-rs/client";
type Metadata = { param_a: string, param_b: number, param_c: boolean, more_metadata: Metadata | null, };
type Test = { a: number, b: boolean, };
type User = { name: string, email: string, age: number, metadata: Metadata, };
export type Server = { version: () => Promise<string>, count: () => Promise<number>, countdown: (min: number, max: number) => Stream<number>, array: () => Promise<Array<string>>, user: { get: (_id: string) => Promise<User>, create: (name: string, email: string, age: number) => Promise<User> } };
export type Server = { version: () => Promise<string>, count: () => Promise<number>, countdown: (min: number, max: number) => Stream<number>, array: () => Promise<Array<string>>, user: { get: (_id: string) => Promise<User>, create: (name: string, email: string, age: number) => Promise<User>, list: () => Promise<Array<Test>> } };
19 changes: 14 additions & 5 deletions examples/counter/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ use qubit::*;
use axum::routing::get;
use serde::{Deserialize, Serialize};

#[derive(ts_rs::TS, Clone, Serialize, Deserialize, Debug)]
#[exported_type]
#[derive(ts_rs::TS, Clone, Serialize, Deserialize, Debug, TypeDependencies)]
pub struct Metadata {
param_a: String,
param_b: u32,
Expand All @@ -23,8 +22,7 @@ pub struct Metadata {
more_metadata: Option<Box<Metadata>>,
}

#[derive(ts_rs::TS, Clone, Serialize, Deserialize, Debug)]
#[exported_type]
#[derive(ts_rs::TS, Clone, Serialize, Deserialize, Debug, TypeDependencies)]
pub struct User {
name: String,
email: String,
Expand All @@ -33,6 +31,12 @@ pub struct User {
metadata: Metadata,
}

#[derive(ts_rs::TS, Clone, Serialize, Deserialize, Debug, TypeDependencies)]
pub struct Test {
a: usize,
b: bool,
}

#[derive(Clone, Default)]
pub struct AppCtx {
database: bool,
Expand Down Expand Up @@ -60,7 +64,7 @@ mod user {
}

pub fn create_router() -> Router<AppCtx> {
Router::new().handler(get).handler(create)
Router::new().handler(get).handler(create).handler(list)
}

#[handler]
Expand Down Expand Up @@ -96,6 +100,11 @@ mod user {
},
}
}

#[handler]
async fn list(_ctx: AppCtx) -> Vec<Test> {
todo!()
}
}

struct CountCtx {
Expand Down
26 changes: 20 additions & 6 deletions packages/client/package.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
{
"name": "@qubit-rs/client",
"version": "0.0.4",
"version": "0.0.5",
"description": "",
"type": "module",
"scripts": {
"lint": "tsc",
"build": "tsup ./src --format esm,cjs,iife --dts"
"build": "tsup ./src/index.ts"
},
"files": [
"dist"
],
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"main": "./dist/index.cjs",
"types": "./dist/index.d.cts",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
"import": {
"default": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"require": {
"default": "./dist/index.cjs",
"types": "./dist/index.d.cts"
}
}
},
"author": {
Expand All @@ -27,5 +33,13 @@
"devDependencies": {
"tsup": "^8.0.1",
"typescript": "^5.3.3"
},
"tsup": {
"format": [
"esm",
"cjs"
],
"splitting": true,
"dts": true
}
}
58 changes: 36 additions & 22 deletions packages/client/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function build_client<Server>(client: Client): Server {
}
});

const subscribe: StreamSubscriber<any> = async ({ on_data, on_end, on_error }) => {
const subscribe: StreamSubscriber<any> = ({ on_data, on_end, on_error }) => {
function error(e: Error) {
if (on_error) {
on_error(e);
Expand All @@ -46,33 +46,47 @@ export function build_client<Server>(client: Client): Server {
error(new Error("client does not support subscriptions"));
return () => {};
}
const subscribe = client.subscribe;

// Get the response of the request
const subscription_id = await p;
let count = 0;
let required_count: number | null = null;
// Hoist the unsubscribe function, which is asyncronousely returned
let unsubscribe_inner: Promise<() => void>;

// Result should be a subscription ID
if (typeof subscription_id !== "string" && typeof subscription_id !== "number") {
// TODO: Throw an error
error(new Error("cannot subscribe to subscription"));
return () => {};
// Helper unsubscribe function, which will wait for the internal unsubscribe
// function to be populated before calling it.
const unsubscribe = async () => {
(await unsubscribe_inner)();
}

// Subscribe to incomming requests
const unsubscribe = client.subscribe(subscription_id, (data) => {
if (typeof data === "object" && "close_stream" in data && data.close_stream === subscription_id) {
required_count = data.count;
} else if (on_data) {
count += 1;
on_data(data);
}
// Populate inner unsubscribe with the asynchronous subscription
unsubscribe_inner = (async () => {
// Get the response of the request
const subscription_id = await p;

let count = 0;
let required_count: number | null = null;

if (count === required_count) {
// The expected amount of messages have been recieved, so it is safe to terminate the connection
unsubscribe();
// Result should be a subscription ID
if (typeof subscription_id !== "string" && typeof subscription_id !== "number") {
// TODO: Throw an error
error(new Error("cannot subscribe to subscription"));
return () => {};
}
}, on_end);

// Subscribe to incomming requests
return subscribe(subscription_id, (data) => {
if (typeof data === "object" && "close_stream" in data && data.close_stream === subscription_id) {
required_count = data.count;
} else if (on_data) {
count += 1;
on_data(data);
}

if (count === required_count) {
// The expected amount of messages have been recieved, so it is safe to terminate the connection
unsubscribe();
}
}, on_end);
})();

return unsubscribe;
};
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export type StreamSubscriber<T> = ({ on_data, on_error, on_end }: {
on_data?: (data: T) => void,
on_error?: (error: Error) => void,
on_end?: () => void,
}) => Promise<(() => void)>;
}) => () => void;

export type Stream<T> = {
subscribe: StreamSubscriber<T>,
Expand Down
56 changes: 41 additions & 15 deletions src/builder/ty/dependencies.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,56 @@
use std::collections::{BTreeMap, HashMap};

// TODO: Document this better
/// Since macros cannot lookup other items in the source code, any user types must 'register'
/// themselves into a central repository so that their types can be collated, ensuring that they're
/// only inserted into the generated types once.
pub trait TypeDependencies {
#[allow(unused_variables)]
fn get_deps(dependencies: &mut BTreeMap<String, String>) {}
}

macro_rules! impl_type_dependencies {
($($t:ident<$($generic:ident),*>),*) => {
$(impl<$($generic),*> TypeDependencies for $t<$($generic),*> {})*
$(impl<$($generic),*> TypeDependencies for $t<$($generic),*>
where $($generic: ts_rs::TS + crate::TypeDependencies),*
{
fn get_deps(dependencies: &mut BTreeMap<String, String>) {
$(impl_type_dependencies!(generic: $generic, dependencies);)*
}
})*
};

($($t:ty),*) => {
$(impl TypeDependencies for $t {})*
};

(tuple: $t:ident) => {
impl<$t> TypeDependencies for ($t,)
where $t: ts_rs::TS + crate::TypeDependencies,
{
fn get_deps(dependencies: &mut BTreeMap<String, String>) {
impl_type_dependencies!(generic: $t, dependencies);
}
}
};

(tuple: $t:ident $(, $t_other:ident)*) => {
impl<$t, $($t_other),*> TypeDependencies for ($t, $($t_other),*)
where $t: ts_rs::TS + crate::TypeDependencies,
$($t_other: ts_rs::TS + crate::TypeDependencies),*
{
fn get_deps(dependencies: &mut BTreeMap<String, String>) {
impl_type_dependencies!(generic: $t, dependencies);
$(impl_type_dependencies!(generic: $t_other, dependencies);)*
}
}

impl_type_dependencies!(tuple: $($t_other),*);
};

(generic: $generic:ident, $dependencies:ident) => {
<$generic as TypeDependencies>::get_deps($dependencies)
};
}

impl_type_dependencies!(
Expand All @@ -36,21 +74,9 @@ impl_type_dependencies!(
);
impl_type_dependencies!(
Vec<T>,
Box<T>,
Option<T>,
Result<T, E>,
HashMap<K, V>
);

macro_rules! impl_tuples {
($t:ident) => {
impl<$t> TypeDependencies for ($t,) {}
};

($t:ident $(, $t_other:ident)*) => {
impl<$t, $($t_other),*> TypeDependencies for ($t, $($t_other),*) {}

impl_tuples!($($t_other),*);
};
}

impl_tuples!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
impl_type_dependencies!(tuple: T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
2 changes: 1 addition & 1 deletion src/server/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ where
// Convert into TypeScript
dependencies
.into_iter()
.map(|(name, ty)| format!("type {name} = {ty};"))
.map(|(name, ty)| format!("export type {name} = {ty};"))
.collect::<Vec<_>>()
.join("\n")
};
Expand Down

0 comments on commit 21fbaeb

Please sign in to comment.