-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
experimental: algo fetch #2
experimental: algo fetch #2
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! Main feedback is that I think it's preferable to have narrow-scope examples rather than starting an sdk package this early, but happy to hear other opinions.
[package] | ||
name = "algo_fetch" | ||
version = "0.0.1" | ||
authors = ["[email protected]"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way to omit/change this when generating? It also shows up in the README
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a few options, sed
is a good fallback but in this case we may just want to maintain our own OAS for a while. Maybe we can present both at once?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah having a seperately maintained OAS for now seems like it makes the most sense. Long term we can plan on PRing upstream but we can quickly iterate if we just have our own OAS short term
crates/algo_models/tests/js/index.ts
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than adding fetch stuff under algo_models
, we should replicate this structure directly under algo_fetch
crates/algo_sdk/src/main.rs
Outdated
@@ -0,0 +1,19 @@ | |||
// Let's Build advanced sdk functionality here. | |||
// Typescript is a fail, we will have to hand-craft the bindings so just ignore JS/TS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean by "Typescript is a fail"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fail is for automagical binding interfaces in the native targets like the Python example. Mainly to remind my self that "Typescript is not like the others"
This is mainly for UniFFI, I could have misinterpreted the status. I saw the JS library mainly integrated via wasm-pack and only found kotlin, swift, python, ruby
as options in UniFFI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah gotcha. Yeah worth mentioning that Mozilla does have a JS binding generator for Gecko: https://firefox-source-docs.mozilla.org/rust-components/uniffi.html but don't think this includes TypeScript. There's also Neon, but I generally think that it'd be better to ship one lib for node and web rather than having two seperate packages. The main question is how viable using WASM in an npm package intended for web. Top-level await in a ES module to load the WASM seems ideal, but don't have enough domain knowledge to know the full implications of that (i.e. CJS compat). There's also the obvious factor: WASM binary size.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Binary size and FFI overhead are the two big factors for sure! Fully agree on a single library and we have a lot of options to handle regressions, the easiest is enforcing closures on the packages.
We can also enforce behavior in the documentation with dynamic imports:
// Make sure to load the library dynamically in a non-blocking way
const crypto = await import('@algorandfoundation/crypto')
The extreme option is to not support the modern libraries and only produce an ESM package that attaches to globalThis
. This could bypass the bundler entirely but would be very unfamiliar to the vast majority of frontend developers. DHH/Rails had a good article on it a while back but it's not a very popular practice
<>
<script src="https://path-to-source-esm-bundle.js">
</>
Actionable Items
- A simple client/server benchmark for native ed25519 and the wasm bindings would be the lowest hanging fruit and is supported since Node 22 and in Webkit. (Maybe a few heap snapshots?)
- Link the JS bindings to modern frameworks and we can see where the pain points are in bundlers using a bundle-analyzer and testing different strategies, ie FFI Crypto + Native Fetch vs FFI Crypto + FFI Fetch vs monolithic FFI. (could evolve into a JS/TS github action for other projects but is high effort)
Info Dump
Packaging/Performance Overview
Performance for RPC will most likely be difficult/costly and there could be some gains in cryptographic operations like signing. It's still possible we end up with a performance loss until Falcon is a part of the toolkit (speculating due to ed25519 having native options in JS runtimes). We can test with a bundle analyzer + benchmark to inform the decisions, it could even turn into a general strategy/action for TS/JS QA (codecov has some features). The surface area is vast, I will attempt to tackle some of it here (none of it is prescriptive, just an info dump).
ESM vs CJS
ESM has named exports which solve many tree shaking problems of the CJS's module.exports object. It is possible to decorate a CJS module for ESM compatibility with additional package overhead, anecdotally it is hit or miss for tree shaking. Most bundlers will transpile CJS to ESM which we have no guarantee over, IMO it's safer to just ship pure ESM packages at this point. This sufficiently avoids the dual package hazard and bloated bundles since the only time CJS is required is for legacy systems. This is easily testable with a bundle analyzer
Runtimes/Bundlers with Web Renderers
Server Side (Node.js\Wrangler\Bun\etc):
This is by far the happy path for FFI, the async loading and overhead would be fine for pure server apps or server rendered pages. The service wouldn't start until the module is loaded and performance can be improved with vertical/horizontal scaling. Any module should be de-duplicated in this context and only be loaded once, then it is accessed by reference on subsequent imports. The main problem is this requires a KMS endpoint for websites since we would have no client side interactivity. Second to that would be in serverless environments which startup time could be a factor and we don't always have a "hot" service (the module must be reloaded since the server needs to boot).
Client Side aka the Problem (SPA/React/Vue/etc):
Basic React/Vue/etc applications will suffer from an unavoidable UX degradation, at a minimum this will be a up front load time. When factoring in the render cycle and small payloads going over the boundary to FFI, this could lead to cascading impacts in responsiveness and load times. Combine reactivity with immutability then performance and memoization become highly critical.
Immutability/FP was introduced to the JS ecosystem for a marginal gain in performance along with overhead in abstractions/constraints. Since every object is a clone (sometimes frozen) in practice, this effectively forces all other libraries to be functional/stateless if they want to maintain best practices and compatibility. (except for a few rare cases like event emitters/observables/signals which are inherently stateful).
Aside:
Pure ESM and vanilla JS would be viable since it avoids the problem, similar to many frameworks decisions to not support modern JS libraries (Django/Laravel/etc 🤝 HTMX). It's debatable if bundling is even a requirement with ESM support in the browser but it's still very early. See esm.sh and skypack
Meta-Frameworks/Frameworks (Next, Remix, Nuxt, Phoenix, Leptos, Rails, Laravel etc)
Frameworks generally share the same Server Side HTML template/generation but have three approaches for delivering Client Side code which are JS, WASM, and RSC. WASM is probably out of scope but still related, Leptos would just leverage the native Rust library and avoid the problem (late game thing to test). As frameworks provide deeper integration with render libraries, the problems only compound. (Why most just avoid it)
Frameworks that handle render libraries do their best to build|deliver|stream an optimized client bundle (Nuxt, Next, Remix). Most bundlers use a practice called "code splitting" and the more modern Server Components to split the application into "chunks". Usually this is a few core chunks reused across the application and then a closure around "pages" for the rest of the bundle (simplification, some are more advanced). Sometimes it is up to the developer to handle bundling manually. It's mainly down to esbuild+rollup(aka Vite), swc(aka Next.js), and webpack (although they seem to be moved on to rspack).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work and amazing write-up! Sounds like we'll have to make a decision with none of them being perfect, but nothing that completely blocks this effort. Also worth mentioning WASM pack itself does have different targets, but I don't have enough web experience to know how helpful that might be: https://rustwasm.github.io/docs/wasm-pack/commands/build.html#target
Overview