Skip to content

Commit 93b660f

Browse files
Merge pull request #338 from specta-rs/router-v2-v2
Router v2 v2
2 parents a12de36 + bddadcf commit 93b660f

File tree

219 files changed

+12369
-3173
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

219 files changed

+12369
-3173
lines changed

.github/dependabot.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,7 @@ updates:
33
- package-ecosystem: "github-actions"
44
directory: "/"
55
schedule:
6-
interval: "weekly"
6+
interval: "weekly"
7+
8+
# TODO: Rust
9+
# TODO: npm

.gitignore

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ Cargo.lock
1212
# System Files
1313
.DS_Store
1414

15-
# Typescript bindings exported by the examples
16-
bindings.ts
17-
1815
# Node
1916
node_modules
2017
.pnpm-debug.log*

Cargo.toml

Lines changed: 40 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,46 @@
1-
[package]
2-
name = "rspc"
3-
description = "A blazing fast and easy to use TRPC server for Rust."
4-
version = "0.3.1"
5-
authors = ["Oscar Beaumont <oscar@otbeaumont.me>"]
6-
edition = "2021"
7-
license = "MIT"
8-
include = ["/src", "/LICENCE", "/README.md"]
9-
repository = "https://github.com/specta-rs/rspc"
10-
documentation = "https://docs.rs/rspc/latest/rspc"
11-
keywords = ["async", "specta", "rust-to-ts", "typescript", "typesafe"]
12-
categories = ["web-programming", "asynchronous"]
13-
14-
# /bin/sh RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features
15-
[package.metadata."docs.rs"]
16-
all-features = true
17-
rustdoc-args = ["--cfg", "docsrs"]
1+
[workspace]
2+
resolver = "2"
3+
members = [
4+
"./crates/*",
5+
"./rspc",
6+
"./integrations/*",
7+
"./examples/core",
8+
"./examples/axum",
9+
"./examples/client",
10+
"./examples/tauri/src-tauri",
11+
"./examples/legacy",
12+
"./examples/binario",
13+
]
1814

19-
[features]
20-
default = []
21-
tracing = ["dep:tracing"]
15+
[workspace.dependencies]
16+
# Private
17+
specta-typescript = { version = "0.0.7", default-features = false }
18+
pin-project-lite = { version = "0.2", default-features = false }
19+
erased-serde = { version = "0.4", default-features = false }
2220

23-
[dependencies]
2421
# Public
25-
serde = { version = "1", features = ["derive"] } # TODO: Remove features
26-
futures = "0.3"
27-
specta = { version = "=2.0.0-rc.20", features = [
28-
"derive",
29-
"serde",
30-
"serde_json",
31-
] } # TODO: Drop all features
32-
specta-util = "0.0.7"
22+
specta = { version = "=2.0.0-rc.20", default-features = false }
23+
serde = { version = "1", default-features = false }
24+
serde_json = { version = "1", default-features = false }
25+
futures = { version = "0.3", default-features = false }
26+
futures-core = { version = "0.3", default-features = false }
27+
futures-util = { version = "0.3", default-features = false }
28+
tracing = { version = "0.1", default-features = false }
3329

34-
# Private
35-
serde-value = "0.7"
36-
erased-serde = "0.4"
37-
38-
# Temporary # TODO: Remove
39-
specta-typescript = { version = "=0.0.7", features = [] }
40-
serde_json = "1.0.133" # TODO: Drop this
41-
thiserror = "2.0.3"
42-
tokio = { version = "1.41.1", features = ["sync", "rt", "macros"] }
43-
tracing = { version = "0.1.40", optional = true }
44-
transient = "0.4.1"
45-
better_any = "0.2.0"
30+
[workspace.lints.clippy]
31+
all = { level = "warn", priority = -1 }
32+
cargo = { level = "warn", priority = -1 }
33+
unwrap_used = { level = "warn", priority = -1 }
34+
panic = { level = "warn", priority = -1 }
35+
todo = { level = "warn", priority = -1 }
36+
panic_in_result_fn = { level = "warn", priority = -1 }
4637

47-
# https://github.com/rust-lang/rust/issues/77125
48-
typeid = "1.0.2"
38+
[patch.crates-io]
39+
specta = { git = "https://github.com/specta-rs/specta", rev = "bf3a0937cceb29eca11df207076b9e1b942ba7bb" }
40+
specta-serde = { git = "https://github.com/specta-rs/specta", rev = "bf3a0937cceb29eca11df207076b9e1b942ba7bb" }
41+
specta-typescript = { git = "https://github.com/specta-rs/specta", rev = "bf3a0937cceb29eca11df207076b9e1b942ba7bb" }
4942

50-
[workspace]
51-
members = ["./crates/*", "./examples", "./examples/axum", "crates/core"]
43+
# specta = { path = "/Users/oscar/Desktop/specta/specta" }
44+
# specta-typescript = { path = "/Users/oscar/Desktop/specta/specta-typescript" }
45+
# specta-serde = { path = "/Users/oscar/Desktop/specta/specta-serde" }
46+
# specta-util = { path = "/Users/oscar/Desktop/specta/specta-util" }

crates/binario/Cargo.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "rspc-binario"
3+
description = "Binario support for rspc"
4+
version = "0.0.0"
5+
edition = "2021"
6+
publish = false # TODO: Crate metadata & publish
7+
8+
[dependencies]
9+
binario = "0.0.2"
10+
futures-util.workspace = true
11+
rspc = { path = "../../rspc" }
12+
specta = { workspace = true }
13+
tokio = "1.42.0"
14+
15+
# /bin/sh RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features
16+
[package.metadata."docs.rs"]
17+
all-features = true
18+
rustdoc-args = ["--cfg", "docsrs"]
19+
20+
[lints]
21+
workspace = true

crates/binario/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# rspc 🤝 Binario
2+
3+
[![docs.rs](https://img.shields.io/crates/v/rspc-binario)](https://docs.rs/rspc-binario)
4+
5+
> [!CAUTION]
6+
> This crate is a proof of concept.
7+
>
8+
> It is not intended for production use and will likely remain that way.
9+
10+
Use [Binario](https://github.com/oscartbeaumont/binario) instead of [Serde](https://serde.rs) for serialization and deserialization.
11+
12+
This is a proof of concept to show that rspc has the ability to support any serialization libraries.

crates/binario/src/lib.rs

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
//! rspc-binario: Binario support for rspc
2+
//!
3+
//! TODO:
4+
//! - Support for streaming the result. Right now we encode into a buffer.
5+
//! - `BinarioDeserializeError` should end up as a `ProcedureError::Deserialize` not `ProcedureError::Resolver`
6+
//! - Binario needs impl for `()` for procedures with no input.
7+
//! - Client integration
8+
//! - Cleanup HTTP endpoint on `example-binario`. Maybe don't use HTTP cause Axum's model doesn't mesh with Binario?
9+
//! - Maybe actix-web example to show portability. Might be interesting with the fact that Binario depends on `tokio::AsyncRead`.
10+
//!
11+
#![forbid(unsafe_code)]
12+
#![cfg_attr(docsrs, feature(doc_cfg))]
13+
#![doc(
14+
html_logo_url = "https://github.com/specta-rs/rspc/blob/main/.github/logo.png?raw=true",
15+
html_favicon_url = "https://github.com/specta-rs/rspc/blob/main/.github/logo.png?raw=true"
16+
)]
17+
18+
use std::{error, fmt, marker::PhantomData, pin::Pin};
19+
20+
use binario::{encode, Decode, Encode};
21+
use futures_util::{stream, Stream, StreamExt};
22+
use rspc::{
23+
middleware::Middleware, DynInput, ProcedureError, ProcedureStream, ResolverInput,
24+
ResolverOutput,
25+
};
26+
use specta::{datatype::DataType, Generics, Type, TypeCollection};
27+
use tokio::io::AsyncRead;
28+
29+
enum Repr {
30+
Bytes(Vec<u8>),
31+
Stream(Pin<Box<dyn AsyncRead + Send>>),
32+
}
33+
34+
pub struct BinarioInput(Repr);
35+
36+
impl BinarioInput {
37+
pub fn from_bytes(bytes: Vec<u8>) -> Self {
38+
Self(Repr::Bytes(bytes))
39+
}
40+
41+
pub fn from_stream(stream: impl AsyncRead + Send + 'static) -> Self {
42+
Self(Repr::Stream(Box::pin(stream)))
43+
}
44+
}
45+
46+
pub struct TypedBinarioInput<T>(pub BinarioInput, pub PhantomData<T>);
47+
48+
impl<T: Decode + Type + Send + 'static> ResolverInput for TypedBinarioInput<T> {
49+
fn data_type(types: &mut TypeCollection) -> DataType {
50+
T::inline(types, Generics::Definition)
51+
}
52+
53+
fn from_input(mut input: DynInput) -> Result<Self, ProcedureError> {
54+
Ok(Self(
55+
input.value::<Option<_>>()?.take().expect("unreachable"),
56+
PhantomData,
57+
))
58+
}
59+
}
60+
61+
// TODO: This should probs be a stream not a buffer.
62+
// Binario currently only supports `impl AsyncRead` not `impl Stream`
63+
pub struct BinarioOutput(pub Vec<u8>);
64+
pub struct TypedBinarioOutput<T, M>(pub T, pub PhantomData<fn() -> M>);
65+
66+
pub(crate) mod sealed {
67+
use super::*;
68+
69+
pub trait ValidBinarioOutput<M>: Send + 'static {
70+
type T: Encode + Send + Sync + 'static;
71+
fn data_type(types: &mut TypeCollection) -> DataType;
72+
fn into_stream(
73+
self,
74+
) -> impl Stream<Item = Result<Self::T, ProcedureError>> + Send + 'static;
75+
}
76+
pub enum ValueMarker {}
77+
impl<T: Encode + Type + Send + Sync + 'static> ValidBinarioOutput<ValueMarker> for T {
78+
type T = T;
79+
fn data_type(types: &mut TypeCollection) -> DataType {
80+
T::inline(types, Generics::Definition)
81+
}
82+
fn into_stream(
83+
self,
84+
) -> impl Stream<Item = Result<Self::T, ProcedureError>> + Send + 'static {
85+
stream::once(async move { Ok(self) })
86+
}
87+
}
88+
pub enum StreamMarker {}
89+
impl<S: Stream + Send + Sync + 'static> ValidBinarioOutput<StreamMarker> for rspc::Stream<S>
90+
where
91+
S::Item: Encode + Type + Send + Sync + 'static,
92+
{
93+
type T = S::Item;
94+
95+
fn data_type(types: &mut TypeCollection) -> DataType {
96+
S::Item::inline(types, Generics::Definition)
97+
}
98+
fn into_stream(
99+
self,
100+
) -> impl Stream<Item = Result<Self::T, ProcedureError>> + Send + 'static {
101+
self.0.map(|v| Ok(v))
102+
}
103+
}
104+
}
105+
106+
impl<TError, M: 'static, T: sealed::ValidBinarioOutput<M>> ResolverOutput<TError>
107+
for TypedBinarioOutput<T, M>
108+
{
109+
type T = BinarioOutput;
110+
111+
fn data_type(types: &mut TypeCollection) -> DataType {
112+
T::data_type(types)
113+
}
114+
115+
fn into_stream(self) -> impl Stream<Item = Result<Self::T, ProcedureError>> + Send + 'static {
116+
// TODO: Encoding into a buffer is not how Binario is intended to work but it's how rspc needs it.
117+
self.0.into_stream().then(|v| async move {
118+
let mut buf = Vec::new();
119+
encode(&v?, &mut buf).await.unwrap(); // TODO: Error handling
120+
Ok(BinarioOutput(buf))
121+
})
122+
}
123+
124+
fn into_procedure_stream(
125+
stream: impl Stream<Item = Result<Self::T, ProcedureError>> + Send + 'static,
126+
) -> ProcedureStream {
127+
ProcedureStream::from_stream_value(stream)
128+
}
129+
}
130+
131+
pub fn binario<TError, TCtx, TInput, TResult, M>() -> Middleware<
132+
TError,
133+
TCtx,
134+
TypedBinarioInput<TInput>,
135+
TypedBinarioOutput<TResult, M>,
136+
TCtx,
137+
TInput,
138+
TResult,
139+
>
140+
where
141+
TError: From<DeserializeError> + Send + 'static,
142+
TCtx: Send + 'static,
143+
TInput: Decode + Send + 'static,
144+
TResult: sealed::ValidBinarioOutput<M>,
145+
{
146+
Middleware::new(
147+
move |ctx: TCtx, input: TypedBinarioInput<TInput>, next| async move {
148+
let input = match input.0 .0 {
149+
Repr::Bytes(bytes) => binario::decode::<TInput, _>(bytes.as_slice()).await,
150+
Repr::Stream(stream) => binario::decode::<TInput, _>(stream).await,
151+
}
152+
.map_err(DeserializeError)?;
153+
154+
next.exec(ctx, input)
155+
.await
156+
.map(|v| TypedBinarioOutput(v, PhantomData))
157+
},
158+
)
159+
}
160+
161+
pub struct DeserializeError(pub std::io::Error);
162+
163+
impl fmt::Debug for DeserializeError {
164+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165+
self.0.fmt(f)
166+
}
167+
}
168+
169+
impl fmt::Display for DeserializeError {
170+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171+
self.0.fmt(f)
172+
}
173+
}
174+
175+
impl error::Error for DeserializeError {}

crates/cache/Cargo.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "rspc-cache"
3+
version = "0.0.0"
4+
edition = "2021"
5+
publish = false
6+
7+
[dependencies]
8+
moka = { version = "0.12.8", features = ["sync"] }
9+
pin-project-lite = { workspace = true }
10+
rspc = { path = "../../rspc" }
11+
12+
# /bin/sh RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features
13+
[package.metadata."docs.rs"]
14+
all-features = true
15+
rustdoc-args = ["--cfg", "docsrs"]
16+
17+
[lints]
18+
workspace = true

crates/cache/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# rspc cache
2+
3+
[![docs.rs](https://img.shields.io/crates/v/rspc-cache)](https://docs.rs/rspc-cache)
4+
5+
> [!CAUTION]
6+
> This crate is still a work in progress. You can use it but we can't guarantee that it's API won't change.
7+
8+
Provides a simple way to cache the results of rspc queries with pluggable backends.
9+
10+
Features:
11+
- Simple to use
12+
- Pluggable backends (memory, redis, etc.)
13+
- Configurable cache TTL
14+
15+
## Example
16+
17+
```rust
18+
// TODO: imports
19+
20+
fn todo() -> Router2<Ctx> {
21+
Router2::new()
22+
.setup(CacheState::builder(Memory::new()).mount())
23+
.procedure("my_query", {
24+
<BaseProcedure>::builder()
25+
.with(cache())
26+
.query(|_, _: ()| async {
27+
// if input.some_arg {}
28+
cache_ttl(10);
29+
30+
Ok(SystemTime::now())
31+
})
32+
})
33+
}
34+
```

0 commit comments

Comments
 (0)