feat: @rivetkit/effect SDK#4703
Draft
IGassmann wants to merge 216 commits into
Draft
Conversation
3f26ccf to
9c7bc2a
Compare
08bda7b to
671b6de
Compare
Add examples/effect with the proposed API surface for @rivetkit/effect. This is a design-only example for feedback, not a working implementation.
Move action schemas inline into Actor.make instead of separate Action.make declarations. Reduces boilerplate and eliminates the name-sync problem across three declaration sites.
Remove the Exit-matching create/destroy pattern from the wake scope finalizer. Create and destroy are separate lifecycle events that don't map to scope exit signals.
…re decode into client
… classification table
…, and statusCode fields
…ame MessageTooLong to IncomingMessageTooLong and OutgoingMessageTooLong
…ch logic and refining fallback handling
…eamlined error handling and metadata decoding
…ror reasons Drop reason-specific metadata-derived getters (phase, channel, capacity, operation, size, limit, actorId) in favor of a normalized isRetryable boolean on every reason and an optional retryAfter Duration on ActorRestarting. Top-level RivetError delegates both to the underlying reason, mirroring the AiError/SqlError pattern.
…tryable, and retryAfter on RivetError
…blic on RivetError
…ve handler structure
The raw rivetkit context is now accessible via wakeOptions.rawRivetkitContext, making the Context service redundant.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Introduces
@rivetkit/effect, an Effect-based SDK for Rivet Actors.What's here
examples/effect/— proposed API surfacerivetkit-typescript/packages/effect/—@rivetkit/effectpackageLooking for feedback on the API's design.
Benefits
throw new UserError("...", { code: "..." })). Callers catchunknown. The Effect SDK puts errors in theEchannel:Effect<number, CounterOverflowError>. The client sees the exact union of possible errors at compile time.(c, amount: number)) are TypeScript-only. Types are erased at runtime. A malicious or drifted-version client can send anything. The Effect SDK validates wire data withSchema.decodeUnknownbefore it reaches handler code.Counter.clientdepends onClientin theRchannel. If you forgetLayer.provide, you get a compile error naming the missing runtime dependency. The current SDK'stypeof registrygives type-safe method calls, but doesn't track the Effect runtime dependency.setupTestbut swapping individual services requires the test framework's internals.ScopeandaddFinalizerguarantee cleanup runs even on errors or interruption. The current SDK has lifecycle hooks (onSleep,onDestroy) but they're not compositional. You can't nest scopes or attach finalizers from within action handlers.Schema.Datedecodes an ISO string into aDate,Schema.BigIntdecodes a string into aBigInt. The current SDK has no equivalent — you get whatever JSON gives you.c.queue.iter()). The Effect SDK exposes durable messages as an EffectQueue.Dequeue, giving you batching, concurrent consumers via forked fibers, and composability withStream,Schedule, and other Effect primitives out of the box.${actor}/${action}name,kind: "client" | "server",rpc.system.name/rpc.methodattributes), and user-defined sub-spans inside handlers nest correctly under the SDK's server span.Tasks
Actor.RawRivetkitContextRegistry.toWebHandler(serverless handler)Counter.EventsAPIActor.DbAPI@rivetkit/effectpnpm testinherits prior runs' dead-envoy actor bindings and stalls on the engine's lost-envoy threshold"Open Questions
State initialization
createState(c, input))getOrCreate(key, { createWithInput: { start: 10 } })equivalentState persistence semantics
stateSaveInterval(current SDK batches writes at a configurable interval)?Configuration and timeouts
noSleep,maxQueueSize,maxQueueMessageSizelive?Actor.makeoptions,toLayeroptions, or registry-level config?Connections
createConnState(c, params))c.conn.state)c.conns)onBeforeConnect,onConnect,onDisconnect)Scheduling
c.schedule.after(delayMs, "actionName", ...args))Lifecycle hooks
onCreate,onDestroyonBeforeConnect,onConnect,onDisconnectonStateChange,onBeforeActionResponseActor self-awareness and control
c.key)c.destroy())Low-level handlers
onRequest)onWebSocket)Client ergonomics
counter.increment(5)) vs single-object payload (counter.increment({ amount: 5 }))Access control for events and queues
canSubscribeon events andcanPublishon queues — guards that receive the connection context and return a boolean to allow/deny a specific client. How should these map to the Effect design?Mixing Effect and plain RivetKit actors
Registry.layer?Registry.fromPlain({ chatRoom })could wrap plain actor configs into a Layer for composition, without requiring schema conversionFuture Improvements
Counter.clientprojection with a per-actor service model (Counter.ClientplusCounter.clientLayer). This would make application effects depend on the exact actor clients they use instead of the sharedClient, improving dependency readability, enabling selective test fakes per actor, supporting per-actor endpoint/token/project overrides through layers, and allowing higher-level services to mock or swap one actor client without replacing the entire transport.http_requestwithparent: None), rivetkit's actor instance tree (rooted atactor.action.<name>via@rivetkit/traces, gated byRIVET_EXPERIMENTAL_OTEL), and the Effect SDK tree (the only one that crosses the wire today, viaActionMeta.trace+Tracer.externalSpan). Joining them would require: (1) the engine'sapi-builder/guard-coremiddleware to extracttraceparentand replaceparent: None, plus forwarding context across the engine→envoy hop; (2) rivetkit'sexecuteActionto read the wire trace context (fromActionMetaortraceparent) and parentactor.action.<name>to it; (3) the Effect SDK to either reparent its server span under rivetkit's actionSpan via a context tag, or migrate to@effect/opentelemetryso both sides emit through one OTel pipeline with standard W3C propagation. End state: a single trace from caller through engine through actor host through handler sub-spans.