Bun/Hono & Next.js Issues
I copied the counter actor quickstart from the bun / hono getting started.
and then used the react package on my nextjs project to test it out.
ran into a variety of issues.
1. originally used
cors({ origin: "*" }) but kept getting cors errors on my nextjs console. The only resolution was this:
cors({ origin: origin => origin ?? ' ',
credentials: true
})
2. then I get these errors in the nextjs console:
POST http://localhost:8000/registry/actors/resolve 500 (Internal Server Error)
level=ERROR msg=failed to resolve actor ID error=Error: Internal error. Read the actor logs for more details.
WebSocket connection to 'ws://localhost:8000/registry/actors/connect/websocket' failed http-client-driver.ts:146
level=WARN msg="socket error"
log.ts:53 level=WARN msg="socket closed" code=1006 reason="" wasClean=false
log.ts:53 level=WARN msg="failed to reconnect" attempt=2 error="Error: Closed"
And on my server:
level=WARN msg="internal error" error="Failed to load actor state: Error: ENOENT: no such file or directory, open '/home/nicka/.local/share/rivetkit/server-.....'" stack="Error: Failed to load actor state: Error: ENOENT: no such file or directory,
So for context, I've plugged this into an existing bun/hono server.
An oversimplified example of how im using my server:
const { client, hono, handler } = registry.createServer()
const hono = new Hono()
app.use(cors({
origin: origin => origin ?? ' ',
credentials: true
}))
app.onError(errorHandler)
app.notFound(notFoundHandler)
app.route('/registry', hono)
// example rivet endpoints for counter (omitted)
// other routes (omitted)
Bun.serve({
...options,
routes: staticRoutes,
fetch: (req, server) => {
const ip = server.requestIP(req);
return app.fetch(req, { ...env, ip, server });
},
})
any ideas what im getting wrong? Assumed it was because I wasnt using the default rivet 'serve' but figured mounting the /registry route solves that??99 Replies
Looking in to this
I have a hunch this is a bun compatability issue. We need to start running our unit tests against bun.
Well I ran into similar issues using node / hono because I suspected it was bun too.
I copied the examples exactly and even used default "serve".
But my server crashed.
It keeps saying that it can't find my actors / failed to load.
Which is odd because I tried using the memory driver and that crashed my server too
I was able to reproduce it with your code, working on fixing a few bugs including this rn
Okay awesome!! Keep me posted π
And if there's anything I can do to help, lmk
thx!
just cut 0.9.6 which fixes this
lmk if you run in to more issues!
@Nathan hmmm still running into issues. its not picking up any actors
even my terminal logs say this:
level=INFO msg="rivetkit started" driver=file-system studio="https://studio.rivet.gg/?t=oPKviXR5VXTk7COAZ9mto1AYJsS8zDr6" actors=0 data=/home/nicka/.local/share/rivetkit/server-cb42d385
shouldnt it show at least one actor if I've done this:
import { setup } from '@rivetkit/actor'
import { counter } from './counter'
export const registry = setup({
use: { counter },
})
Ok finally got it working but had to do the following:
Still get this error in nextjs console:
Access to fetch at 'http://localhost:8080/registry/actors/resolve' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
Fix:
app.use(cors({
origin: origin => origin ?? '*',
credentials: true
}))
// this fix is what made my actors discoverable
import { createBunWebSocket } from 'hono/bun'
const { upgradeWebSocket, websocket } =
createBunWebSocket<ServerWebSocket>()
const { client, hono } = registry.createServer({
getUpgradeWebSocket: () => upgradeWebSocket,
})ah actors are the actual created actors, not the actor definitions. i'll fix that up to be more clear.
so it seems like without using built in serve, the websocket stuff has to be configured somewhat manually
yes it looks like you figured it out. we need to write up a guide on this, it's incredibly annoying
this is why we have a default
serve even though i much prefer letting people control their own serveyea i mean im sure the default is good for most, but i poked around at the code and saw that it uses hono's node server & websocket injector
which almost defeats the purpose of using this with bun
yep haha
i don't think it's hard to drop in an if statement to use bun if possible, just haven't had the time to test it appropriately
honestly, I think it might just be as simple as doing
if(typeof Bun !== undefined) {}
I've seen this done in alot of hono's middleware packages
yep it probably is. it's the testing that's more annoying.
stuff like that has a habit of breaking a bunch of different targets.
the other bit β need to use:
but i'm not sure if that'll break tree shaking for other targets β haven't looked in to it yet
ah thats true!
well I'll try to get this fully running - and once i do, ill do my best to send over all the code that worked & maybe a brief write up so you can plug it into the docs
im really eager to try this will vercel ai sdk as a custom store
oh sick, let me know if you have any more issues/annoying thigns you run in to
appreciate all the help
prs are welcome too
perfect man thank you for the help as well!!
(and yea i thought about contributing. slammed this week, but ill def revisit when things slow down)
now that i have it working (minimally), i can see alot of potential with this. so great work man.
ill keep you updated!
@Nathan - any tips for getting registry types in nextjs with a monorepo setup?
Ive isolated all my actor modules + registry export in its own package.
import { setup } from '@rivetkit/actor'
import { counter } from './actors/counter'
export const createRegistry = () => {
return setup({
use: { counter },
})
}
export type Registry = ReturnType<typeof createRegistry>
export const registry: Registry = createRegistry()
but my generated .d.ts for registry is coming out as unknown so im not getting types when i import it on the client.
Works fine on the the server because i dont really need strict typing here since im just exposing a route,
I considered creating my own intricately typed interface of a Registry as map between actor names & their types, but that seemed super gnarly lol
The ActorInstance takes like 8 required genericsThe ActorInstance takes like 8 required genericsit's a little nuts haha, every day i look at it and try to figure out what i can simplify specifically
Registry? i think i'm missing some context on how this is different than the examples we provideactually no types from rivetkit are being surfaced.
When i run a tsup build (with the option to generate d.ts file) on my registry file that I pasted above,
I get this
declare const createRegistry: unknown;
type Registry = ReturnType<typeof createRegistry>;
declare const registry: Registry;
export { registry, createRegistry, Registry };
Originally i just tried to yolo it and do
export type Registry = typeof registry.
but then i got this:
The inferred type of 'registry' cannot be named without a reference to This is likely not portable. A type annotation is necessary.
for context im using turborepo, bun latest (1.2.19) and im using bun's latest install feature for monorepos (linker = "isolated")
regarding the examples you mentioned - i noticed you imported the registry directly from the sever on the frontend, I was trying to isolate my registry in an actual package.
I switched to your approach (importing registry directly from server)
but keep getting this ts error when trying to do createClient<typeof registry>
Property '#private' in type 'Registry' refers to a different member that cannot be accessed from within type 'Registry'this usually means you have different versions of rivetkit installed
check your lockfile to see what versions are in use
great catch. forgot to upgrade my rivetkit react after you pushed that fix for the actors package.
all good now thanks!!
sweet
np
ok making progress - but now running into issues getting params from lifecycle events
heres my client code:
const [token, setToken] = useState<string | null>(null)
const { getToken, userId } = useAuth()
const counter = useActor({
name: 'counter',
key: [instanceKey],
params: { token, userId },
enabled: !!token,
})
useEffect(() => {
getToken().then(setToken)
}, [getToken])
im debugging inside of my actor
onAuth: ({ intents, params }) => {
console.log('onAuth', { intents, params })
return {}
},
and I keep getting:
onAuth {
intents: Set(3) {
"get",
"create",
"connect",
},
params: undefined,
}
its logging multiple times which tells me that the react state update (when token gets set) is triggering a reconnection.
So I'd expect the last log to show paramslet me take a look, one moment
i'm fairly certin this is an issue with the redis client since we test this in the vanilla js client iirc
gotcha. any suggestions to navigate this?
Im trying to load in user specific state so im going to wrap this whole thing in clerk auth
give me a moment to pinpoint the issue. i'll cut a release in a sec
π«‘
in the meantime, you can try using the client directly to see if that passes the params correctly:
this could also be an issue with the routing layer too, tbd
from earlier:
afaik this needs the extra headers, but it should through an error instead of fail silently iirc. https://www.rivet.gg/docs/general/cors/#required-headers-for-rivet
Rivet
Cross-Origin Resource Sharing - Rivet
Cross-Origin Resource Sharing (CORS) is a security mechanism that allows a web application running at one origin to access resources from a different origin. Without CORS, browsers block cross-origin HTTP requests by default as a security measure.
yep it worked doing it through the vanilla client
ok cool, give me a sec
oh wait, can you also try:
since ws and stateless differ in impl
so i updated my cors config. no difference really.
I see the clerk jwt on the cookie in the request object, but params dont come through when using the react client
yeah i see the issue
i'm cutting a release
hold tight, was fixing another bug i found rq
no worries. also caught something else, but might be my misunderstanding of lifecycle events:
// actor - backend
onConnect: c => {
c.broadcast('init', 'hello world')
},
// react
counter.useEvent('init', (msg: string) => {
toast.success(msg)
})
I removed the conditional "enabled" flag on my useActor instance. it connects instantly, but the toast never fires
no rush btw, i know youre working on the fix.
just figured id drop this here too while youre workingah interesting, i don't think we test that right now
is this blocking or can i throw this on the for @jog1t to fix tomorrow? i think a workaround would be to use
setTimeout(() => ..., 0) to schedule it on the next js tickyea no rush on this one at all.
im heading to bed now anyway haha
I did want clarity on one more thing though - useActor only takes params but no input.
The vanilla client accepts input.
So how would I get input injected into hooks like onCreate that only recieve 'input' and not 'params' π€
GitHub
rivet/site/src/content/docs/actors/input.mdx at main Β· rivet-gg/rivet
π© The open-source alternative to Durable Objects. Rivet Actors provide long-lived processes with durable state, realtime, and scalability. - rivet-gg/rivet
I did want clarity on one more thing though - useActor only takes params but no input.this needs to be fixed too. i'll set up a second issue for that
awesome. im done for the night.
Thanks so much for your help
actually i'll ship that one rn that's easy
https://github.com/rivet-gg/rivetkit/releases/tag/v0.9.7 should be set
@Nathan man love the fast turnaround. i upgraded and heres what i tested so far
createInput is working nicely β
I've set in on the client and my onCreate hook is receiving it
onAuth hook is finally catching params but they are a JSON string, not an object.
Assuming this is intentional?
I dont mind parsing on my side at all, but I did expect it to be an object lol
also is there a way to change log level from rivetkit/actor ?
Request object is undefined in onBeforeConnect, but available in onAuth
It would be nice to get access to that because I'm using clerk's backend sdk helpers to do alot of heavy lifting with auth checks.
IE
in onAuth I can do this:
const res = await clerk.authenticateRequest(options.request)
if (!res.isAuthenticated) {
throw new Error('Invalid authentication')
}
const auth = res.toAuth()
// custom util that strips unserializable properties
const strippedAuth = stripAuthObject(auth)
return { token: res.token, ...strippedAuth }
and I get back alot of really useful auth info that I can reuse
But .... clerk also has this feature:
const auth = res.toAuth()
// check permission / role
auth.has(.....)
The problem is, I cant return this in onAuth because its not serializable.
So I'm assuming this is when using onBeforeConnect is necessary, because I can assign auth.has to an ephemeral variable and re-use it across actions right?
If thats the right approach, then having request context available in onBeforeConnect would be super helpfulonAuth hook is finally catching params but they are a JSON string, not an object.oof, that's not intentional. our react integration is not tested in ci, only the vanilla client.
Request object is undefined in onBeforeConnect, but available in onAuthi think that's doable
because I can assign auth.has to an ephemeral variable and re-use it across actions right?that's correct (eg either
createVars if it's actor-specific)
at some point we plan on setting up a clerk example, but i haven't worked with it personally.
is auth/auth.has specific to each user?
i wish there was some way to reconstruct it from a raw json object. the ideal situation is:
- verify the user in onAuth
- return the token/whatever in onAuth
- store the res you have above in createVars
ok to clarify, a few things you're waiting on:
- broadcast events from onConnect
- request in onBeforeConnect
- fix onAuth params being a string
let me do the onbeforeconnect issue real quick since that sounds blocking, the other two are assigned for @jog1t to tackle today
@jog1t
https://linear.app/rivet-gg/issue/KIT-188/fix-onauth-conn-params-being-a-string-with-the-react-driver
https://linear.app/rivet-gg/issue/KIT-187/pass-request-in-onbeforeconnect-and-related-lifecycle-events
https://linear.app/rivet-gg/issue/KIT-185/fix-broadcasting-in-onconnectYea perfect!! If you can get those things, I think I'd be able to actually contribute a clerk example on your repo
hold on β does this work:
seeing it as already implemented

that'd be incredibly helpful β thank you!
hold on let me try it

:/
let me dig in to it one moment
if you log the entire object of the onBeforeConnect's options arg - it shows request as an available property, its just undefined.
but it does show params correctly

let me see where that's getting lost
we have hundreds of tests and you've already found at least 2 new edge cases
i do this alot lol
π
working on a fix give me a bit
yes! just add
_LOG_LEVEL env var
available logs levels:
broadcast events from onConnect might be a tricky one, for now i'd suggest going with a separate action just to be called right after you connect to the actor@jog1t that's fine ! That wasn't a huge priority for me. Thanks for letting me know
I think having access to request object in onBeforeConnect would be a bigger priority
i'm pushing that fix (sort of) right now, lot of meetings today
but i have it fixed
ππ½
I stalked the github release. π
onBeforeConnect working smoothly.
Auth checks are a breeze
sweet! sorry it took so long to get out β been spread thin today
@Nathan do you have a contributing guide you want followed? I was thinking of throwing together a quick clerk demo over the next couple days
if youre in need of any additional examples / content
a clerk example would be incredibly helpful. no contribution guide atm, but you can follow the better auth guide for reference https://github.com/rivet-gg/rivetkit/tree/main/examples/better-auth-external-db
most of the examples follow a pretty consistent format
GitHub
rivetkit/examples/better-auth-external-db at main Β· rivet-gg/rivetkit
π§° The open-source alternative to Durable Objects. Easily self-hostable and works with your infrastructure. - rivet-gg/rivetkit
What's the proper way to connect to actor from front end when using cloudflare workers? I keep getting cors issues but not configuration seems to solve it, I'm probably missing something
I'm using the default template with hono trying to subscribe to events
@wing try adjusting the cors settings: https://github.com/rivet-gg/rivetkit/blob/0433897ee37478366f1426061674db94a873f5e1/examples/game/src/backend/server.ts#L3
GitHub
rivetkit/examples/game/src/backend/server.ts at 0433897ee37478366f1...
π§° The open-source alternative to Durable Objects. Easily self-hostable and works with your infrastructure. - rivet-gg/rivetkit
Sorry I just realized I asked this in a random thread lol
I'm using cloudflare and I tried variations of this with no luck so far
what error do you have? What specific CORS issue?
Oh I see! Instead of
Do this:
It needs to match your frontend server port, in this case itβs localhost:8787
My react vite front end is running on 1337 and I believe rivet is on 8787
for dev purposes you can set it to all origins by:
let me know if that works for you
Just tried that and it's not working either π€
it seems like cors it not even getting hit som how if I try logging in the callback
If I remove the hono server and use createServerHandler it works, but using hono with createServer doesn't
Mind sharing a repo, or a zip with the project? Will take a look!
Quick question - createWithInput vs connection params - when would you recommend one over the other?
I guess im still not fully clear what the difference is lifecycle wise
I'm prototyping some chat features and experimenting with sharing client side data with actors.
So I'd like to understand - for best practices - what types of data are best for each
input - think of it like the data that you pass into constructor in plain ol' classes
connection params - treat it similar to how onAuth works
ahhh okay thats kind of what i was thinking.
Thanks for explaining!
no worries!
are there any thoughts on allowing direct access to input from createVars hook?
hm.. good question, not sure what was the orignal reasoning of the order here, so I'll ask @Nathan (the father of rivetkit) for help
ok cool. its not super critical for me.
But I know its available in
createState and I usually treat createVars the same (where I mentally expect to have access to input too) - since I use that hook to intialize non-serializable stateyep, sounds resonable!
ok ran it something else - not sure if the error is rivetkit or something else.
But maybe you might have an idea that would help simplify my debugging.
Im testing aborting llm streams (through ai sdk) inside of actions.
Essentially I have an action that runs a stream and broadcasts back stream parts.
Works perfect in normal scenarios.
For context, I've set an abort controller in c.vars.
I have another action called stopStream which basically just calls c.vars.signal.abort()
every time I trigger the abort stream action while in an active stream, that action that handles the stream runs twice.
i have debug logs sprinkled all over the action - before the stream starts & after.
Im even passing a unique stream id into the action from the client & logging it at the start of the action.
When i go through my server logs - I see two unique runs on that action - and two different stream ids at the start of each run.
Which is odd.
is there any default retry logic built into the rpc client that I should consider?
Or any other thoughts on what could be causing my action to run 2x?
additionally is there a smoother way to use abort signals between client X actor without having to rig it up through vars & custom actions?
do you have repro repo? maybe?
I can try to put one together, but its hooked into alot of biolerplate code already
have you tried calling abort signal on the action itself? i think it can be passed but not sure if its implemented fully
let me try to setup an example repo
I'll do my best to put one together - just wasnt sure if it was something obvious.
But interesting - will try to also pass a signal directly
quite interesting case!
@jog1t so just tried sending a signal as an action param (from client rpc). it doesnt work.
logging the value of signal in the action just returns an empty object
is there a way to send it through underlying 'fetch' options on the client?
let me dig into the internals, will come back with something π
@jog1t discovered something else interesting
digging in to this β i believe it's just a typing issue right now. i think you can manually cast
ctx.input to whatever type you need in createVarsfor testing purposes, instead of using an abort controller in c.vars and aborting it via a custom action-
I just set AbortSignal.timeout(2000) on the ai stream options and i didnt run into any issues or duplicate action runs.
So it seems to be something off possibly with the way im using abort controllers in the actor vars and aborting them from one action while my stream runs in another action
@Nathan if you look at the msg I just sent, I think using an abort controller as a var might be causing the issue
will look into it, thanks!
GitHub
Add more granular types to
InitContext Β· Issue #1180 Β· rivet-gg...Currently uses InitContext which does not define many times that are available during init. For example, you cannot access ctx.input in createVars even though it exists. We need to replace InitCont...
thanks @jog1t and @Nathan
Im also not super confident I implemented the abort logic properly anyway.
I might need to dispose of the controller instance after each run too.
will keep testing on my end as well
also (forgive my limited knowledge of abort signals) - but even if I were to pass an abort controller's signal to the actor via input ctx - wouldnt I need to dispose of the controller instance after each stream?
if that is true, wouldnt I then need to pass a fresh controller signal on each request (via the action parameters) ?
i'm 99% sure you can't pass abort controller signal through input, as input needs to be json serializable
makes sense.
So that narrows it down to either a) being able to pass signals through actions b) getting my original method to work using c.vars + custom abort action
I ended up resolving the issue.
now im trying to compare state updates - when onStateChange hook fires - can I compare prevState with newState using (c.state, newState)? or does c.state already represent new state?
I tested yesterday but logging c.state within onStateChange seems to already reflect the new state.
c.state is already the new state. i have a PR in to include previous state in onstateChange as a third (actually the second) parameter , and answered a PR question around it for @jog1t . when that gets accepted then you can compare newState to previous state. I actually needed that in my project so created the PR, would be nice to do c.broadcast in just one place as opposed to every action, and also filter on state change on a fine grained property level