Announcing: AshTypescript

What if you had full stack types for SPAs built with tech like React, Inertia, Vue, Svelte... but all of the power of Ash Framework and Elixir on the backend? Well, you can now. πŸ˜‰. https://hexdocs.pm/ash_typescript/readme.html This is just 0.1.0, but it is the beginning of something big, and frankly something that we've been missing for a long time. Can't wait to see what you all do with it, and a huge shoutout to our newest core team member @Torkan for all of his amazing work!
58 Replies
frankdugan3
frankdugan3β€’3w ago
Super awesome! I see Zod is coming soon. Is some form of subscription support planned as well?
Torkan
Torkanβ€’3w ago
Zod is actually already up and running, I just forgot to update that part of the readme file πŸ˜…
Randall
Randallβ€’3w ago
It would be kinda cool if, instead of running a mix task, one could leverage a custom compiler to automatically keep the generated file up to date. The same way that Phoenix LV does it with the new colocated hooks: https://github.com/phoenixframework/phoenix_live_view/blob/main/lib/mix/tasks/compile/phoenix_live_view.ex
Torkan
Torkanβ€’3w ago
That is on the short term roadmap, it’ll probably be hooked into the same functionality that also detects that you need to run ash.codegen β€”dev etc 😎
Shahryar
Shahryarβ€’3w ago
Hi @Torkan , it includes the open api (ash json api) or just actions and we need still create like this
npx openapi-typescript http://localhost:4000/api/json/open_api -o api-types.ts
npx openapi-typescript http://localhost:4000/api/json/open_api -o api-types.ts
Thank you
ZachDaniel
ZachDanielOPβ€’3w ago
It is unrelated to open api πŸ™‚ This uses its own json-based communication protocol and controller, and generates types for those.
Shahryar
Shahryarβ€’3w ago
Sorry Yes. My main question is that the frontend is currently using HTTP , we can convert the OpenAPI spec to types. But where exactly can this library help me? Since it's working at the resource and domain level, while my frontend interacts via HTTP!
ZachDaniel
ZachDanielOPβ€’3w ago
It produces a typescript client library and a controller that the client library uses that routes to the relevant action So calling it makes a fetch request to your controller that we provide
Shahryar
Shahryarβ€’3w ago
AHhhaaa :))) I just found out. Thank you, so I need react query and axios instead of fetchπŸ˜‚
ZachDaniel
ZachDanielOPβ€’3w ago
We plan to extend it to support that kind of thing πŸ™‚ If you want to try it out and see how they can be connected that would be cool πŸ™‚
Shahryar
Shahryarβ€’3w ago
Yeeh sure! Thank you for your all efforts, it is the good one I'm so stupid I didn't even see RPC. πŸ˜‚
Carlo Minciacchi
Carlo Minciacchiβ€’3w ago
I might be missing something: what do I need to do in my router to declare the /rpc/validate and /rpc/run endpoints?
ZachDaniel
ZachDanielOPβ€’3w ago
Yeah I think the steps are missing @Torkan ^ we need to tell them to create their RPC controller
Torkan
Torkanβ€’3w ago
Ooops, yup, those are indeed missing from the readme πŸ˜… Add this in rpc_controller.ex:
defmodule AshEventsDemoWeb.RpcController do
use AshEventsDemoWeb, :controller

def run(conn, params) do
conn = Ash.PlugHelpers.set_actor(conn, conn.assigns[:current_user])
result = AshTypescript.Rpc.run_action(:ash_events_demo, conn, params)
json(conn, result)
end

def validate(conn, params) do
conn = Ash.PlugHelpers.set_actor(conn, conn.assigns[:current_user])
result = AshTypescript.Rpc.validate_action(:ash_events_demo, conn, params)
json(conn, result)
end
end
defmodule AshEventsDemoWeb.RpcController do
use AshEventsDemoWeb, :controller

def run(conn, params) do
conn = Ash.PlugHelpers.set_actor(conn, conn.assigns[:current_user])
result = AshTypescript.Rpc.run_action(:ash_events_demo, conn, params)
json(conn, result)
end

def validate(conn, params) do
conn = Ash.PlugHelpers.set_actor(conn, conn.assigns[:current_user])
result = AshTypescript.Rpc.validate_action(:ash_events_demo, conn, params)
json(conn, result)
end
end
Replace the module namespace and :ash_events_demo with your actual app name And in router.ex:
post "/rpc/run", RpcController, :run
post "/rpc/validate", RpcController, :validate
post "/rpc/run", RpcController, :run
post "/rpc/validate", RpcController, :validate
If you're already assigning the actor in a plug earlier in your pipeline, then you can skip the calls to Ash.PlugHelpers.set_actor
ZachDaniel
ZachDanielOPβ€’3w ago
@Torkan can you add that to the setup guide? Ill republish docs after Replace AshEventsDemo with MyApp
Torkan
Torkanβ€’3w ago
Yup, working on it now There, just pushed a full overhaul of the readme, usage-rules & the internal docs
ZachDaniel
ZachDanielOPβ€’3w ago
Will publish later
Torkan
Torkanβ€’3w ago
I should probably also clean up the repo I used for the live demo on Saturday a little, and then we can reference that as an example implementation
Carlo Minciacchi
Carlo Minciacchiβ€’3w ago
Thank you ❀️
Torkan
Torkanβ€’3w ago
Np, and pls let me know if you run into any issues! I'll also shortly add support for overriding the currently hardcoded usage of the native fetch function, and that you can specify any fetch options if needed
ZachDaniel
ZachDanielOPβ€’3w ago
@Torkan I think the big win there would be one mega dumb example using none of the fancy stuff, and then your integrated inertia-backed example could be the upgraded version etc.
Torkan
Torkanβ€’3w ago
100% agreed!
ZachDaniel
ZachDanielOPβ€’3w ago
Also @Torkan I'm pretty sure just mix ash.codegen --dev works and we should encourage people to do that? Or maybe it doesn't matter? But ideally we have basically a single loop just "make some changes -> mix ash.codegen --dev -> repeat -> when done, mix ash.codegen make_feature_x"
Torkan
Torkanβ€’3w ago
Yup, it's just not hooked up atm πŸ˜… (PRs welcome) (nvm, fixed)
ZachDaniel
ZachDanielOPβ€’3w ago
Oh I thought I added it but maybe it was on that branch I threw away
Torkan
Torkanβ€’3w ago
Ah yes, you did setup a PR that included that change way back, I just never merged it, so my bad really πŸ˜… Custom HTTP client support incoming btw There, just pushed it
DonkeyKong
DonkeyKongβ€’3w ago
another amazing tool in the ever growing Ash toolkit, kudos to the team! quick question, my current project uses AshPostgres.LTree type and works fine for JSON API generation. But with ash_typescript, mix ash_typescript.codegen --dry_run throws an error.
** (RuntimeError) Unknown type: Elixir.AshPostgres.Ltree
(ash_typescript 0.1.0) lib/ash_typescript/codegen.ex:325: AshTypescript.Codegen.generate_ash_type_alias/
** (RuntimeError) Unknown type: Elixir.AshPostgres.Ltree
(ash_typescript 0.1.0) lib/ash_typescript/codegen.ex:325: AshTypescript.Codegen.generate_ash_type_alias/
I tried a custom wrapper and that did not take. Any pointers on how to use ash_typescript with non-standard types?
ZachDaniel
ZachDanielOPβ€’3w ago
Hmm...how did you try it? **try to wrap it
gevera
geveraβ€’3w ago
This is a very promising. It opens up a whole plethora of possibilities, like using LiveSvelte in Ash with fully typed client, a thing that I was missing so bad in my project. Not anymore! Just wow!. Also, it reminds me of SvelteKit's remote functions. But having BEAM as foundations with Ashes declarative approach its just a blessing
Torkan
Torkanβ€’3w ago
Did you follow the instructions in README.md for custom types? Yeah, using it with LiveSvelte, LiveVue etc should be fairly straight-forward, typed queries would be the main thing to use in those settings I think, since you usually want to run everything through the event handlers of your liveviews
DonkeyKong
DonkeyKongβ€’3w ago
thanks @Torkan , @Zach generation worked after 1. I cloned this as a custom type in the project: https://github.com/ash-project/ash_postgres/blob/v2.6.15/lib/types/ltree.ex 2. followed these steps: https://hexdocs.pm/ash_typescript/readme.html#custom-types, specifically added this:
def typescript_type_name, do: "AshTypes.Ltree"
def typescript_type_name, do: "AshTypes.Ltree"
3. and added this snippet in config.exs:
# Custom type imports
import_into_generated: [
%{
import_name: "AshTypes",
file: "./ash-types.ts"
}
],
# Custom type imports
import_into_generated: [
%{
import_name: "AshTypes",
file: "./ash-types.ts"
}
],
GitHub
ash_postgres/lib/types/ltree.ex at v2.6.15 Β· ash-project/ash_postgres
The PostgreSQL data layer for Ash Framework. Contribute to ash-project/ash_postgres development by creating an account on GitHub.
Torkan
Torkanβ€’3w ago
Awesome! I'm currently adding native support for it in ash_typescript now as well, so soon you won't have to create that wrapper 😎 Just pushed an update to main with ltree support @DonkeyKong, if you could try it out and confirm it works as needed that would be great 😎
RootCA
RootCAβ€’3w ago
It works with TanStack Query / Table! (super) quick demo video: https://www.youtube.com/watch?v=brqbxS2vpzY
Torkan
Torkanβ€’3w ago
Nice! πŸ”₯ If that demo is from a public repo that could work as a reference example it would be awesome if we could reference it, then I won't have to build one myself πŸ˜‚
RootCA
RootCAβ€’3w ago
I'm tidying it up, will have something shareable Wednesday evening!
Torkan
Torkanβ€’3w ago
Excellent! Thanks for making the effort!
RootCA
RootCAβ€’3w ago
Thank you for doing the hard part!
DonkeyKong
DonkeyKongβ€’3w ago
amazing work @Torkan , much better DX, thank you πŸ™
Torkan
Torkanβ€’3w ago
Released 0.1.2 now, which contains the LTree support and makes the typescript codegen hook into ash.codegen --dev
ZachDaniel
ZachDanielOPβ€’3w ago
@Torkan so I think the next awesome milestone would be to make the igniter installer set everything up and create a basic index.ts file that is all hooked up, perhaps with a page that tells users what to do when they open it. Like "go create some resources and hook them up X way" etc. Maybe behind a flag like mix igniter.install ash_typescript --setup and then on the ash-hq.org installer we can add a React SPA preset that does that
Torkan
Torkanβ€’3w ago
Yup, agreed!
Carlo Minciacchi
Carlo Minciacchiβ€’3w ago
The updated README is brilliant, thank you
Deltore
Deltoreβ€’3w ago
this is INSANE
Randall
Randallβ€’3w ago
Would it be a lot of work to perform the RPC over a websocket?
Torkan
Torkanβ€’3w ago
Yes and no (it depends on what you want/need) πŸ˜… With http/2, there is very little to be gained in terms of performance/overhead though. Are there other reasons why you'd prefer/need to do it over WS instead HTTP? Technically, there is nothing stopping you from setting up a channel and just call AshTypeScript.Rpc.run_action with the incoming payloads really, but you'd have to manually ensure that you set the types for the various action results yourself though on the client
Randall
Randallβ€’3w ago
Assuming you already have a socket, why perform more requests? The, albeit minimal, overhead per request from fetching the user/tenant/w/e can also be avoided. Perhaps it's more about separating the transport from the actions/types.
Torkan
Torkanβ€’3w ago
Well, mainly because there are no huge benefits in avoiding performing more requests πŸ˜… It's totally possible to use the building blocks provided by ash_typescript yourself to do it though, it's just not very straightforward/currently possible to make a "ready for use" setup using WS as the transport protocol So it's a little outside of the scope of the extension itself currently in terms of what is supported out of the box.. We are however looking into implementing the concept of a typed phoenix channel at some point 😎 I wouldn't worry too much about the performance hit of validating the actor & tenant etc, I don't think I've ever heard something along the lines of "We tried using a http api for our backend, but it couldn't handle the load so we had to move over to websockets instead" πŸ˜…
ZachDaniel
ZachDanielOPβ€’3w ago
I think it's perfectly reasonable to want to do this over a web socket and I think we could provide some tools to do it But maybe something lower level that requires rigging etc
Torkan
Torkanβ€’3w ago
Yeah, it's totally doable with what's provided as-is, but you'd have to plug it all together yourself Regarding what is provided, I'm thinking of types for the expected payload for an action, types for inferring the result of an action (based on the fields requested in the payload), and functionality on the backend to run the action It's hard to make a more generalized approach that could be automatically setup for you and be used straight after installing though, but it's definitely something that should be taken into consideration for typed channels But it currently involves a lot of scaffolding/boilerplate, so not something I would recommend πŸ˜… Hm, I might have thought of a sort of abstraction that might remove the need for a lot of that boilerplate now though, I’ll try implementing it later tonight hopefully @Randall Released 0.2.0 last night, that also adds generated functions that can be used with a channel.. see the project readme for setup
Randall
Randallβ€’3w ago
Cool, nice that it's possible. I do wonder about the API a bit. Couple things at first glance: 1. The channel is definitely a required parameter. Perhaps it should be the first positional arg then? E.g. createTodoChannel(channel, { ... }). 2. Why callbacks, rather than a promise? Generating additional functions like this is fine, I imagine the bundler takes care of tree shaking here. Sorry if you felt pressured by my question earlier, I didn't mean for this to feel like a priority of some sort; it was mostly a question out of curiousity.
Torkan
Torkanβ€’3w ago
Well, phoenix channels are callback-based, so it's hard to get around that fact πŸ˜…
Randall
Randallβ€’3w ago
Yeah, it's a bit rough there. I know LiveView also does a bit of work to "Promise"-ify stuff like pushEventTo By the looks of it they something similar to this:
new Promise((resolve, reject) => {
channel.push(event, payload)
.receive('ok', resolve)
.receive('error', reject)
.receive('timeout', () => reject(new Error('timeout')))
})
new Promise((resolve, reject) => {
channel.push(event, payload)
.receive('ok', resolve)
.receive('error', reject)
.receive('timeout', () => reject(new Error('timeout')))
})
In assets/js/phoenix_live_view/view.js:pushWithReply/3
Torkan
Torkanβ€’3w ago
How so? LV only uses addEventListener & no-reply events AFAIK Ah right, didn't see the last part there πŸ˜…
Randall
Randallβ€’3w ago
It does in hooks. See view_hook.ts:
* If the query selector returns more than one element it will send the event to all of them,
* even if all the elements are in the same LiveComponent or LiveView. Because of this,
* if no callback is passed, a promise is returned that matches the return value of
* [`Promise.allSettled()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled#return_value).
* Individual fulfilled values are of the format `{ reply, ref }`, where `reply` is the server's reply.
* If the query selector returns more than one element it will send the event to all of them,
* even if all the elements are in the same LiveComponent or LiveView. Because of this,
* if no callback is passed, a promise is returned that matches the return value of
* [`Promise.allSettled()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled#return_value).
* Individual fulfilled values are of the format `{ reply, ref }`, where `reply` is the server's reply.
Torkan
Torkanβ€’3w ago
Right, I'll take it into consideration 😎
RootCA
RootCAβ€’3w ago
As promised, a demonstration of AshTypescript using Tanstack table and Tanstack query (plus a LiveView for comparison): https://github.com/ChristianAlexander/ash_typescript_demo
GitHub
GitHub - ChristianAlexander/ash_typescript_demo: A demonstration of...
A demonstration of the Ash Typescript package, showing React and LiveView access to the same resources - ChristianAlexander/ash_typescript_demo
Torkan
Torkanβ€’3w ago
Awesome work, thanks for sharing! πŸ₯³ I think this repo is a perfect example of how low the barrier of entry can be in terms of utilizing ash_typescript in a regular Phoenix/LiveView application, where you just need to mount a few pages using React (or another front-end framework). Is it okay if I reference it in ash_typescript's readme?
RootCA
RootCAβ€’3w ago
Use it in whatever way you’d like!

Did you find this page helpful?