From 7a763a40b1b773f475e946a25898997578729d3c Mon Sep 17 00:00:00 2001 From: Makisuo Date: Wed, 25 Feb 2026 23:52:55 +0100 Subject: [PATCH 1/2] feat: add @rivetkit/effect package for Effect-TS actor integration Adds first-class Effect-TS support for RivetKit actors with: - RivetActorContext tag for injecting actor context into Effect pipelines - Effect wrappers for all lifecycle hooks and actions - ManagedRuntime support for bringing custom Effect layers - Queue helpers for message processing - Tagged errors (RuntimeExecutionError, StatePersistenceError) - Comprehensive test suite (13 tests) Co-Authored-By: Claude Opus 4.6 --- pnpm-lock.yaml | 199 +++++++++++++++- rivetkit-typescript/packages/effect/README.md | 132 +++++++++++ .../packages/effect/package.json | 45 ++++ .../packages/effect/src/action.ts | 58 +++++ .../packages/effect/src/actor.ts | 192 ++++++++++++++++ .../packages/effect/src/errors.ts | 25 ++ .../packages/effect/src/lifecycle.ts | 214 ++++++++++++++++++ .../packages/effect/src/log.ts | 22 ++ .../packages/effect/src/mod.ts | 24 ++ .../packages/effect/src/queue.ts | 53 +++++ .../packages/effect/src/rivet-actor.ts | 95 ++++++++ .../packages/effect/src/runtime.ts | 119 ++++++++++ .../packages/effect/tests/queue.test.ts | 64 ++++++ .../packages/effect/tests/runtime.test.ts | 49 ++++ .../packages/effect/tests/wrappers.test.ts | 96 ++++++++ .../packages/effect/tsconfig.json | 10 + .../packages/effect/tsup.config.ts | 4 + .../packages/effect/turbo.json | 4 + .../packages/effect/vitest.config.ts | 10 + 19 files changed, 1405 insertions(+), 10 deletions(-) create mode 100644 rivetkit-typescript/packages/effect/README.md create mode 100644 rivetkit-typescript/packages/effect/package.json create mode 100644 rivetkit-typescript/packages/effect/src/action.ts create mode 100644 rivetkit-typescript/packages/effect/src/actor.ts create mode 100644 rivetkit-typescript/packages/effect/src/errors.ts create mode 100644 rivetkit-typescript/packages/effect/src/lifecycle.ts create mode 100644 rivetkit-typescript/packages/effect/src/log.ts create mode 100644 rivetkit-typescript/packages/effect/src/mod.ts create mode 100644 rivetkit-typescript/packages/effect/src/queue.ts create mode 100644 rivetkit-typescript/packages/effect/src/rivet-actor.ts create mode 100644 rivetkit-typescript/packages/effect/src/runtime.ts create mode 100644 rivetkit-typescript/packages/effect/tests/queue.test.ts create mode 100644 rivetkit-typescript/packages/effect/tests/runtime.test.ts create mode 100644 rivetkit-typescript/packages/effect/tests/wrappers.test.ts create mode 100644 rivetkit-typescript/packages/effect/tsconfig.json create mode 100644 rivetkit-typescript/packages/effect/tsup.config.ts create mode 100644 rivetkit-typescript/packages/effect/turbo.json create mode 100644 rivetkit-typescript/packages/effect/vitest.config.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c83b42cac5..3e237e04ec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4148,6 +4148,28 @@ importers: specifier: ^5.5.2 version: 5.9.3 + rivetkit-typescript/packages/effect: + dependencies: + rivetkit: + specifier: workspace:* + version: link:../rivetkit + devDependencies: + '@effect/vitest': + specifier: ^0.27.0 + version: 0.27.0(effect@3.19.19)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.0.7)(typescript@5.9.3))(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0)) + effect: + specifier: ^3.16.0 + version: 3.19.19 + tsup: + specifier: ^8.5.0 + version: 8.5.1(@microsoft/api-extractor@7.53.2(@types/node@25.0.7))(@swc/core@1.15.11(@swc/helpers@0.5.17))(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + typescript: + specifier: ^5.9.2 + version: 5.9.3 + vitest: + specifier: ^3.1.1 + version: 3.2.4(@types/debug@4.1.12)(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.0.7)(typescript@5.9.3))(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) + rivetkit-typescript/packages/framework-base: dependencies: '@tanstack/store': @@ -5839,6 +5861,12 @@ packages: resolution: {tarball: https://pkg.pr.new/rivet-dev/durable-streams/@durable-streams/writer@0323b8bcf1c9b38f1014629e1a8b6c74cc662100} version: 0.0.0 + '@effect/vitest@0.27.0': + resolution: {integrity: sha512-8bM7n9xlMUYw9GqPIVgXFwFm2jf27m/R7psI64PGpwU5+26iwyxp9eAXEsfT5S6lqztYfpQQ1Ubp5o6HfNYzJQ==} + peerDependencies: + effect: ^3.19.0 + vitest: ^3.2.0 + '@emnapi/runtime@1.7.1': resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} @@ -9555,9 +9583,6 @@ packages: '@types/node@20.19.13': resolution: {integrity: sha512-yCAeZl7a0DxgNVteXFHt9+uyFbqXGy/ShC4BlcHkoE0AfGXYv/BUiplV72DjMYXHDBXFjhvr6DD1NiRVfB4j8g==} - '@types/node@20.19.33': - resolution: {integrity: sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==} - '@types/node@22.19.10': resolution: {integrity: sha512-tF5VOugLS/EuDlTBijk0MqABfP8UxgYazTLo3uIn3b4yJgg26QRbVYJYsDtHrjdDUIRfP70+VfhTTc+CE1yskw==} @@ -11461,6 +11486,9 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + effect@3.19.19: + resolution: {integrity: sha512-Yc8U/SVXo2dHnaP7zNBlAo83h/nzSJpi7vph6Hzyl4ulgMBIgPmz3UzOjb9sBgpFE00gC0iETR244sfXDNLHRg==} + electron-to-chromium@1.5.286: resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==} @@ -11872,6 +11900,10 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-check@3.23.2: + resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} + engines: {node: '>=8.0.0'} + fast-copy@3.0.2: resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==} @@ -14650,6 +14682,9 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + qrcode-terminal@0.11.0: resolution: {integrity: sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==} hasBin: true @@ -18241,6 +18276,11 @@ snapshots: '@durable-streams/client': https://pkg.pr.new/rivet-dev/durable-streams/@durable-streams/client@0323b8bcf1c9b38f1014629e1a8b6c74cc662100 fastq: 1.20.1 + '@effect/vitest@0.27.0(effect@3.19.19)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.0.7)(typescript@5.9.3))(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0))': + dependencies: + effect: 3.19.19 + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.0.7)(typescript@5.9.3))(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) + '@emnapi/runtime@1.7.1': dependencies: tslib: 2.8.1 @@ -19556,6 +19596,14 @@ snapshots: '@types/node': 24.7.1 optional: true + '@inquirer/confirm@5.1.21(@types/node@25.0.7)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.0.7) + '@inquirer/type': 3.0.10(@types/node@25.0.7) + optionalDependencies: + '@types/node': 25.0.7 + optional: true + '@inquirer/core@10.3.2(@types/node@20.19.13)': dependencies: '@inquirer/ansi': 1.0.2 @@ -19597,6 +19645,20 @@ snapshots: '@types/node': 24.7.1 optional: true + '@inquirer/core@10.3.2(@types/node@25.0.7)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.0.7) + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.0.7 + optional: true + '@inquirer/figures@1.0.15': {} '@inquirer/type@3.0.10(@types/node@20.19.13)': @@ -19613,6 +19675,11 @@ snapshots: '@types/node': 24.7.1 optional: true + '@inquirer/type@3.0.10(@types/node@25.0.7)': + optionalDependencies: + '@types/node': 25.0.7 + optional: true + '@isaacs/balanced-match@4.0.1': {} '@isaacs/brace-expansion@5.0.1': @@ -22290,11 +22357,6 @@ snapshots: dependencies: undici-types: 6.21.0 - '@types/node@20.19.33': - dependencies: - undici-types: 6.21.0 - optional: true - '@types/node@22.19.10': dependencies: undici-types: 6.21.0 @@ -22582,6 +22644,15 @@ snapshots: msw: 2.12.10(@types/node@24.7.1)(typescript@5.9.3) vite: 5.4.21(@types/node@24.7.1)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) + '@vitest/mocker@3.2.4(msw@2.12.10(@types/node@25.0.7)(typescript@5.9.3))(vite@5.4.21(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0))': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + msw: 2.12.10(@types/node@25.0.7)(typescript@5.9.3) + vite: 5.4.21(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) + '@vitest/pretty-format@2.1.9': dependencies: tinyrainbow: 1.2.0 @@ -24365,7 +24436,7 @@ snapshots: dotenv-expand@11.0.7: dependencies: - dotenv: 16.4.7 + dotenv: 16.6.1 dotenv@16.4.7: {} @@ -24424,6 +24495,11 @@ snapshots: ee-first@1.1.1: {} + effect@3.19.19: + dependencies: + '@standard-schema/spec': 1.1.0 + fast-check: 3.23.2 + electron-to-chromium@1.5.286: {} elysia@1.4.12(@sinclair/typebox@0.34.41)(exact-mirror@0.2.2(@sinclair/typebox@0.34.41))(file-type@21.0.0)(openapi-types@12.1.3)(typescript@5.9.3): @@ -25062,6 +25138,10 @@ snapshots: extend@3.0.2: {} + fast-check@3.23.2: + dependencies: + pure-rand: 6.1.0 + fast-copy@3.0.2: {} fast-decode-uri-component@1.0.1: {} @@ -26108,7 +26188,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.19.33 + '@types/node': 22.19.11 merge-stream: 2.0.0 supports-color: 8.1.1 optional: true @@ -27627,6 +27707,32 @@ snapshots: - '@types/node' optional: true + msw@2.12.10(@types/node@25.0.7)(typescript@5.9.3): + dependencies: + '@inquirer/confirm': 5.1.21(@types/node@25.0.7) + '@mswjs/interceptors': 0.41.2 + '@open-draft/deferred-promise': 2.2.0 + '@types/statuses': 2.0.6 + cookie: 1.1.1 + graphql: 16.12.0 + headers-polyfill: 4.0.3 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + picocolors: 1.1.1 + rettime: 0.10.1 + statuses: 2.0.2 + strict-event-emitter: 0.5.1 + tough-cookie: 6.0.0 + type-fest: 5.4.4 + until-async: 3.0.2 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - '@types/node' + optional: true + muggle-string@0.3.1: {} mute-stream@2.0.0: {} @@ -28469,6 +28575,8 @@ snapshots: punycode@2.3.1: {} + pure-rand@6.1.0: {} + qrcode-terminal@0.11.0: {} qrcode.react@4.2.0(react@19.1.0): @@ -30686,6 +30794,24 @@ snapshots: - supports-color - terser + vite-node@3.2.4(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 5.4.21(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vite-node@5.2.0(@types/node@20.19.13)(jiti@1.21.7)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): dependencies: cac: 6.7.14 @@ -30839,6 +30965,20 @@ snapshots: stylus: 0.62.0 terser: 5.46.0 + vite@5.4.21(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.57.1 + optionalDependencies: + '@types/node': 25.0.7 + fsevents: 2.3.3 + less: 4.4.1 + lightningcss: 1.31.1 + sass: 1.93.2 + stylus: 0.62.0 + terser: 5.46.0 + vite@6.4.1(@types/node@20.19.13)(jiti@1.21.7)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): dependencies: esbuild: 0.25.12 @@ -31151,6 +31291,45 @@ snapshots: - supports-color - terser + vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.0.7)(typescript@5.9.3))(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0): + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(msw@2.12.10(@types/node@25.0.7)(typescript@5.9.3))(vite@5.4.21(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.2.2 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 5.4.21(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) + vite-node: 3.2.4(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.12 + '@types/node': 25.0.7 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vlq@1.0.1: {} vscode-jsonrpc@8.2.0: {} diff --git a/rivetkit-typescript/packages/effect/README.md b/rivetkit-typescript/packages/effect/README.md new file mode 100644 index 0000000000..6067637684 --- /dev/null +++ b/rivetkit-typescript/packages/effect/README.md @@ -0,0 +1,132 @@ +# @rivetkit/effect + +Effect-TS integration for RivetKit actors with typed context access, lifecycle wrappers, and managed runtime support. + +## Installation + +```bash +npm install @rivetkit/effect effect +``` + +## Highlights + +- **Typed context service** via `RivetActorContext` — inject Rivet's `ActorContext` into Effect pipelines +- **Effect wrappers** for all actor hooks (`OnCreate`, `OnWake`, `OnDestroy`, etc.) and actions +- **Queue helpers** for message processing with `Queue.next` and `Queue.nextMultiple` +- **ManagedRuntime support** — bring your own Effect layers via the `runtime` option on `actor()` +- **Tagged errors** — `RuntimeExecutionError` and `StatePersistenceError` for precise error handling + +## Quick Start + +```ts +import { actor, Action, OnCreate, Log } from "@rivetkit/effect"; + +export default actor({ + state: { count: 0 }, + + actions: { + increment: Action.effect(function* (c, amount: number) { + yield* Action.updateState(c, (s) => { s.count += amount }); + yield* Log.info("incremented", { amount }); + return yield* Action.state(c); + }), + }, + + onCreate: OnCreate.effect(function* (c, input) { + yield* Log.info("actor created"); + }), +}); +``` + +## ManagedRuntime + +Bring your own Effect layers (database clients, config, etc.): + +```ts +import { actor, Action } from "@rivetkit/effect"; +import { Layer, ManagedRuntime } from "effect"; + +const AppLayer = Layer.mergeAll(DatabaseService.Default, ConfigService.Default); +const AppRuntime = ManagedRuntime.make(AppLayer); + +export default actor({ + runtime: AppRuntime, + + actions: { + query: Action.effect(function* (c) { + const db = yield* DatabaseService; + return yield* db.query("SELECT 1"); + }), + }, +}); +``` + +## Error Types + +- `RuntimeExecutionError` — wraps unexpected failures during effect execution +- `StatePersistenceError` — wraps failures from `saveState` + +Use tag-based handling: + +```ts +import { Effect } from "effect"; +import { Action } from "@rivetkit/effect"; + +const save = Action.effect(function* (c) { + yield* Action.saveState(c, { debounce: 1000 }).pipe( + Effect.catchTag("StatePersistenceError", (err) => + Effect.log(`Failed to save: ${err.message}`), + ), + ); +}); +``` + +## Queue + +```ts +import { Queue, Action, Log } from "@rivetkit/effect"; + +const processLoop = Action.effect(function* (c) { + const message = yield* Queue.next(c, "tasks", { timeout: 5000 }); + if (message) { + yield* Log.info("Processing", { id: message.id, name: message.name }); + } +}); +``` + +## API Reference + +### Actor Helpers + +| Function | Description | +|---|---| +| `actor(config)` | Creates a RivetKit actor definition with optional `runtime` for ManagedRuntime | +| `RivetActorContext` | Effect Context.Tag for accessing the actor context | +| `Action.effect(fn)` | Wraps a generator as an action handler | +| `Action.state(c)` | Read actor state | +| `Action.updateState(c, fn)` | Mutate actor state | +| `Action.saveState(c, opts)` | Persist state (returns `StatePersistenceError` on failure) | +| `Action.broadcast(c, name, ...args)` | Broadcast to all connections | +| `Action.getConn(c)` | Get the current connection (action context only) | + +### Lifecycle Wrappers + +All lifecycle namespaces expose an `effect` function that wraps generators: + +`OnCreate`, `OnWake`, `OnDestroy`, `OnSleep`, `OnStateChange`, `OnBeforeConnect`, `OnConnect`, `OnDisconnect`, `CreateConnState`, `OnBeforeActionResponse`, `CreateState`, `CreateVars`, `OnRequest`, `OnWebSocket` + +### Queue Helpers + +| Function | Description | +|---|---| +| `Queue.next(c, name, opts?)` | Receive next message from a single queue | +| `Queue.nextMultiple(c, names, opts?)` | Receive messages from multiple queues | + +### Log Helpers + +| Function | Description | +|---|---| +| `Log.info(msg, props?)` | Log at info level (requires `RivetActorContext`) | +| `Log.warn(msg, props?)` | Log at warn level | +| `Log.error(msg, props?)` | Log at error level | +| `Log.debug(msg, props?)` | Log at debug level | diff --git a/rivetkit-typescript/packages/effect/package.json b/rivetkit-typescript/packages/effect/package.json new file mode 100644 index 0000000000..e14ba2feca --- /dev/null +++ b/rivetkit-typescript/packages/effect/package.json @@ -0,0 +1,45 @@ +{ + "name": "@rivetkit/effect", + "version": "2.0.42-rc.1", + "description": "Effect-TS integration for RivetKit actors with typed context, lifecycle wrappers, and managed runtime support", + "license": "Apache-2.0", + "sideEffects": [ + "./dist/chunk-*.js", + "./dist/chunk-*.cjs" + ], + "type": "module", + "files": [ + "dist", + "package.json" + ], + "exports": { + ".": { + "import": { + "types": "./dist/mod.d.mts", + "default": "./dist/mod.mjs" + }, + "require": { + "types": "./dist/mod.d.ts", + "default": "./dist/mod.js" + } + } + }, + "scripts": { + "build": "tsup src/mod.ts", + "check-types": "tsc --noEmit", + "test": "vitest run" + }, + "dependencies": { + "rivetkit": "workspace:^" + }, + "peerDependencies": { + "effect": ">=3.0.0" + }, + "devDependencies": { + "@effect/vitest": "^0.27.0", + "effect": "^3.16.0", + "tsup": "^8.5.0", + "typescript": "^5.9.2", + "vitest": "^3.1.1" + } +} diff --git a/rivetkit-typescript/packages/effect/src/action.ts b/rivetkit-typescript/packages/effect/src/action.ts new file mode 100644 index 0000000000..e190900e64 --- /dev/null +++ b/rivetkit-typescript/packages/effect/src/action.ts @@ -0,0 +1,58 @@ +import { Effect } from "effect"; +import type { ActorContext, ActionContext } from "rivetkit"; +import type { YieldWrap } from "effect/Utils"; +import { provideActorContext } from "./actor.ts"; +import { runPromise } from "./runtime.ts"; + +export { + state, + updateState, + vars, + updateVars, + broadcast, + getLog, + getActorId, + getName, + getKey, + getRegion, + getSchedule, + getConns, + getClient, + getDb, + getKv, + getQueue, + saveState, + waitUntil, + getAbortSignal, + sleep, + destroy, + context, + RivetActorContext, +} from "./actor.ts"; + +export const getConn = ( + c: ActionContext, +) => Effect.succeed(c.conn); + +/** Wraps a generator function as a Rivet action handler with Effect support. Errors propagate normally. */ +export function effect< + TState, + TConnParams, + TConnState, + TVars, + TInput, + AEff = void, + Args extends unknown[] = [], +>( + genFn: ( + c: ActorContext, + ...args: Args + ) => Generator>, AEff, never>, +): (c: ActionContext, ...args: Args) => AEff { + return ((c, ...args) => { + const gen = genFn(c, ...args); + const eff = Effect.gen>, AEff>(() => gen); + const withContext = provideActorContext(eff, c); + return runPromise(withContext, c); + }) as (c: ActionContext, ...args: Args) => AEff; +} diff --git a/rivetkit-typescript/packages/effect/src/actor.ts b/rivetkit-typescript/packages/effect/src/actor.ts new file mode 100644 index 0000000000..94e1870fbb --- /dev/null +++ b/rivetkit-typescript/packages/effect/src/actor.ts @@ -0,0 +1,192 @@ +import { Cause, Context, Effect, Exit } from "effect"; +import type { ActorContext } from "rivetkit"; +import type { YieldWrap } from "effect/Utils"; +import { StatePersistenceError } from "./errors.ts"; +import { runPromise, runPromiseExit } from "./runtime.ts"; + +type AnyActorContext = ActorContext; + +/** + * Context.Tag for injecting Rivet's ActorContext into Effect pipelines. + * + * Uses Context.Tag (not Effect.Service) because the actor context is an + * externally-provided runtime resource injected by the Rivet framework, + * not a service we construct via layers. + */ +export class RivetActorContext extends Context.Tag("@rivetkit/effect/RivetActorContext")< + RivetActorContext, + AnyActorContext +>() {} + +export const provideActorContext = ( + effect: Effect.Effect, + context: unknown, +): Effect.Effect> => + Effect.provideService( + effect as Effect.Effect, + RivetActorContext, + context as AnyActorContext, + ) as Effect.Effect>; + +export const context = (): Effect.Effect< + ActorContext, + never, + RivetActorContext +> => RivetActorContext as any; + +export const state = ( + c: ActorContext, +): Effect.Effect => Effect.succeed(c.state); + +export const updateState = ( + c: ActorContext, + f: (state: TState) => void, +): Effect.Effect => Effect.sync(() => f(c.state)); + +export const vars = ( + c: ActorContext, +): Effect.Effect => Effect.succeed(c.vars); + +export const updateVars = ( + c: ActorContext, + f: (vars: TVars) => void, +): Effect.Effect => Effect.sync(() => f(c.vars)); + +export const broadcast = < + TState, + TConnParams, + TConnState, + TVars, + TInput, + Args extends Array = unknown[], +>( + c: ActorContext, + name: string, + ...args: Args +): Effect.Effect => Effect.sync(() => c.broadcast(name, ...args)); + +export const getLog = ( + c: ActorContext, +) => Effect.succeed(c.log); + +export const getActorId = ( + c: ActorContext, +) => Effect.succeed(c.actorId); + +export const getName = ( + c: ActorContext, +) => Effect.succeed(c.name); + +export const getKey = ( + c: ActorContext, +) => Effect.succeed(c.key); + +export const getRegion = ( + c: ActorContext, +) => Effect.succeed(c.region); + +export const getSchedule = ( + c: ActorContext, +) => Effect.succeed(c.schedule); + +export const getConns = ( + c: ActorContext, +) => Effect.succeed(c.conns); + +export const getClient = ( + c: ActorContext, +) => Effect.succeed(c.client()); + +export const getDb = ( + c: ActorContext, +) => Effect.succeed(c.db); + +export const getKv = ( + c: ActorContext, +) => Effect.succeed(c.kv); + +export const getQueue = ( + c: ActorContext, +) => Effect.succeed((c as unknown as { queue: unknown }).queue); + +export const saveState = ( + c: ActorContext, + opts: Parameters[0], +): Effect.Effect => + Effect.tryPromise({ + try: () => c.saveState(opts), + catch: (error) => + new StatePersistenceError({ + message: "Failed to persist actor state", + cause: error, + }), + }); + +const logRuntimeFailure = (context: unknown, message: string, error: unknown): void => { + if (typeof context !== "object" || context === null) return; + const ctx = context as { log?: { error: (entry: Record) => void } }; + ctx.log?.error({ + msg: message, + error: + error instanceof Error + ? error.message + : typeof error === "string" + ? error + : JSON.stringify(error), + }); +}; + +const runEffectOnActorContext = (c: unknown, effect: Effect.Effect): Promise => { + const withContext = provideActorContext(effect, c); + const effectPromise = runPromise(withContext, c); + const runtimeContext = c as { waitUntil: (promise: Promise) => void }; + runtimeContext.waitUntil( + effectPromise + .then(() => undefined) + .catch((error) => { + logRuntimeFailure(c, "actor effect failed", error); + }), + ); + return effectPromise; +}; + +export const waitUntil = ( + c: ActorContext, + effect: Effect.Effect, +): Effect.Effect => + Effect.sync(() => { + const promise = runPromiseExit(effect, c).then((exit) => { + if (Exit.isFailure(exit)) { + c.log.error({ + msg: "waitUntil effect failed", + cause: Cause.pretty(exit.cause), + }); + } + }); + c.waitUntil(promise); + }); + +export const getAbortSignal = ( + c: ActorContext, +): Effect.Effect => Effect.succeed(c.abortSignal); + +export const sleep = ( + c: ActorContext, +): Effect.Effect => Effect.sync(() => c.sleep()); + +export const destroy = ( + c: ActorContext, +): Effect.Effect => Effect.sync(() => c.destroy()); + +/** Wraps a generator function as a Rivet hook handler with Effect support. Errors propagate normally. */ +export function effect( + genFn: ( + c: ActorContext, + ) => Generator>, AEff, never>, +): (c: ActorContext) => Promise { + return (c) => { + const gen = genFn(c); + const eff = Effect.gen>, AEff>(() => gen); + return runEffectOnActorContext(c, eff); + }; +} diff --git a/rivetkit-typescript/packages/effect/src/errors.ts b/rivetkit-typescript/packages/effect/src/errors.ts new file mode 100644 index 0000000000..07823f584c --- /dev/null +++ b/rivetkit-typescript/packages/effect/src/errors.ts @@ -0,0 +1,25 @@ +import { Cause, Schema } from "effect"; + +export class RuntimeExecutionError extends Schema.TaggedError()( + "RuntimeExecutionError", + { + message: Schema.String, + operation: Schema.optional(Schema.String), + cause: Schema.optional(Schema.Unknown), + }, +) {} + +export const makeRuntimeExecutionError = (operation: string, cause: Cause.Cause) => + new RuntimeExecutionError({ + message: `Effect failed during ${operation}`, + operation, + cause: Cause.pretty(cause), + }); + +export class StatePersistenceError extends Schema.TaggedError()( + "StatePersistenceError", + { + message: Schema.String, + cause: Schema.optional(Schema.Unknown), + }, +) {} diff --git a/rivetkit-typescript/packages/effect/src/lifecycle.ts b/rivetkit-typescript/packages/effect/src/lifecycle.ts new file mode 100644 index 0000000000..d5f0636b6b --- /dev/null +++ b/rivetkit-typescript/packages/effect/src/lifecycle.ts @@ -0,0 +1,214 @@ +import { Cause, Effect, Exit } from "effect"; +import type { + BeforeActionResponseContext, + BeforeConnectContext, + ConnectContext, + Conn, + CreateConnStateContext, + CreateContext, + CreateVarsContext, + DestroyContext, + DisconnectContext, + RequestContext, + SleepContext, + StateChangeContext, + UniversalWebSocket, + WakeContext, + WebSocketContext, +} from "rivetkit"; +import type { YieldWrap } from "effect/Utils"; +import { provideActorContext } from "./actor.ts"; +import { runPromise, runPromiseExit } from "./runtime.ts"; + +const runWithContext = (context: unknown, effect: Effect.Effect): Promise => + runPromise(provideActorContext(effect, context), context); + +const runWithContextExit = ( + context: unknown, + effect: Effect.Effect, +): Promise> => runPromiseExit(provideActorContext(effect, context), context); + +const runGeneratorWithContext = ( + context: unknown, + gen: Generator>, A, never>, +): Promise => + runWithContext( + context, + Effect.gen(() => gen), + ); + +const makeAsyncLifecycle = ( + genFn: (context: C, ...args: Args) => Generator>, AEff, never>, +) => { + return (context: C, ...args: Args): Promise => + runGeneratorWithContext(context, genFn(context, ...args)); +}; + +export namespace OnCreate { + export const effect = ( + genFn: ( + c: CreateContext, + input: TInput, + ) => Generator>, AEff, never>, + ): ((c: CreateContext, input: TInput) => Promise) => + makeAsyncLifecycle(genFn); +} + +export namespace OnWake { + export const effect = ( + genFn: ( + c: WakeContext, + ) => Generator>, AEff, never>, + ): ((c: WakeContext) => Promise) => + makeAsyncLifecycle(genFn); +} + +export namespace OnDestroy { + export const effect = ( + genFn: ( + c: DestroyContext, + ) => Generator>, AEff, never>, + ): ((c: DestroyContext) => Promise) => + makeAsyncLifecycle(genFn); +} + +export namespace OnSleep { + export const effect = ( + genFn: ( + c: SleepContext, + ) => Generator>, AEff, never>, + ): ((c: SleepContext) => Promise) => + makeAsyncLifecycle(genFn); +} + +export namespace OnStateChange { + export function effect( + genFn: ( + c: StateChangeContext, + newState: TState, + ) => Generator>, void, never>, + ): ( + c: StateChangeContext, + newState: TState, + ) => void { + return (c, newState) => { + void runWithContextExit( + c, + Effect.gen(() => genFn(c, newState)), + ).then((exit) => { + if (Exit.isFailure(exit)) { + c.log.error({ + msg: "onStateChange effect failed", + cause: Cause.pretty(exit.cause), + }); + } + }); + }; + } +} + +export namespace OnBeforeConnect { + export const effect = ( + genFn: ( + c: BeforeConnectContext, + params: TConnParams, + ) => Generator>, AEff, never>, + ): ((c: BeforeConnectContext, params: TConnParams) => Promise) => + makeAsyncLifecycle(genFn); +} + +export namespace OnConnect { + export const effect = ( + genFn: ( + c: ConnectContext, + conn: Conn, + ) => Generator>, AEff, never>, + ): (( + c: ConnectContext, + conn: Conn, + ) => Promise) => makeAsyncLifecycle(genFn); +} + +export namespace OnDisconnect { + export const effect = ( + genFn: ( + c: DisconnectContext, + conn: Conn, + ) => Generator>, AEff, never>, + ): (( + c: DisconnectContext, + conn: Conn, + ) => Promise) => makeAsyncLifecycle(genFn); +} + +export namespace CreateConnState { + export const effect = ( + genFn: ( + c: CreateConnStateContext, + params: TConnParams, + ) => Generator>, TConnState, never>, + ): (( + c: CreateConnStateContext, + params: TConnParams, + ) => Promise) => makeAsyncLifecycle(genFn); +} + +export namespace OnBeforeActionResponse { + export const effect = ( + genFn: ( + c: BeforeActionResponseContext, + name: string, + args: unknown[], + output: Out, + ) => Generator>, Out, never>, + ): (( + c: BeforeActionResponseContext, + name: string, + args: unknown[], + output: Out, + ) => Promise) => makeAsyncLifecycle(genFn); +} + +export namespace CreateState { + export const effect = ( + genFn: ( + c: CreateContext, + input: TInput, + ) => Generator>, TState, never>, + ): ((c: CreateContext, input: TInput) => Promise) => + makeAsyncLifecycle(genFn); +} + +export namespace CreateVars { + export const effect = ( + genFn: ( + c: CreateVarsContext, + driverCtx: unknown, + ) => Generator>, TVars, never>, + ): ((c: CreateVarsContext, driverCtx: unknown) => Promise) => + makeAsyncLifecycle(genFn); +} + +export namespace OnRequest { + export const effect = ( + genFn: ( + c: RequestContext, + request: Request, + ) => Generator>, Response, never>, + ): (( + c: RequestContext, + request: Request, + ) => Promise) => makeAsyncLifecycle(genFn); +} + +export namespace OnWebSocket { + export const effect = ( + genFn: ( + c: WebSocketContext, + websocket: UniversalWebSocket, + ) => Generator>, AEff, never>, + ): (( + c: WebSocketContext, + websocket: UniversalWebSocket, + ) => Promise) => makeAsyncLifecycle(genFn); +} diff --git a/rivetkit-typescript/packages/effect/src/log.ts b/rivetkit-typescript/packages/effect/src/log.ts new file mode 100644 index 0000000000..45a6820995 --- /dev/null +++ b/rivetkit-typescript/packages/effect/src/log.ts @@ -0,0 +1,22 @@ +import { Effect } from "effect"; +import { RivetActorContext } from "./actor.ts"; + +type LogLevel = "info" | "warn" | "error" | "debug"; + +const logWithLevel = (level: LogLevel, message: string, props?: Record) => + Effect.gen(function* () { + const ctx = yield* RivetActorContext; + ctx.log[level]({ msg: message, ...props }); + }); + +export const info = (message: string, props?: Record) => + logWithLevel("info", message, props); + +export const warn = (message: string, props?: Record) => + logWithLevel("warn", message, props); + +export const error = (message: string, props?: Record) => + logWithLevel("error", message, props); + +export const debug = (message: string, props?: Record) => + logWithLevel("debug", message, props); diff --git a/rivetkit-typescript/packages/effect/src/mod.ts b/rivetkit-typescript/packages/effect/src/mod.ts new file mode 100644 index 0000000000..bdeb969b10 --- /dev/null +++ b/rivetkit-typescript/packages/effect/src/mod.ts @@ -0,0 +1,24 @@ +export * as Action from "./action.ts"; +export * as Log from "./log.ts"; +export * as Queue from "./queue.ts"; +export * from "./errors.ts"; + +export { + OnCreate, + OnWake, + OnDestroy, + OnSleep, + OnStateChange, + OnBeforeConnect, + OnConnect, + OnDisconnect, + CreateConnState, + OnBeforeActionResponse, + CreateState, + CreateVars, + OnRequest, + OnWebSocket, +} from "./lifecycle.ts"; + +export { RivetActorContext } from "./actor.ts"; +export { actor } from "./rivet-actor.ts"; diff --git a/rivetkit-typescript/packages/effect/src/queue.ts b/rivetkit-typescript/packages/effect/src/queue.ts new file mode 100644 index 0000000000..ba1f35ead1 --- /dev/null +++ b/rivetkit-typescript/packages/effect/src/queue.ts @@ -0,0 +1,53 @@ +import { Effect } from "effect"; +import type { ActorContext } from "rivetkit"; + +export interface QueueReceiveOptions { + count?: number; + timeout?: number; +} + +export interface QueueMessage { + id: bigint; + name: string; + body: unknown; + createdAt: number; +} + +type AnyQueue = { + next: ( + nameOrNames: string | string[], + opts?: QueueReceiveOptions, + ) => Promise; +}; + +/** + * Receives the next message from a single queue. + * Returns undefined if no message available or timeout reached. + */ +export const next = ( + c: ActorContext, + name: string, + opts?: QueueReceiveOptions, +): Effect.Effect => + Effect.promise( + () => + (c as unknown as { queue: AnyQueue }).queue.next(name, opts) as Promise< + QueueMessage | undefined + >, + ); + +/** + * Receives messages from multiple queues. + * Returns messages matching any of the queue names. + */ +export const nextMultiple = ( + c: ActorContext, + names: string[], + opts?: QueueReceiveOptions, +): Effect.Effect => + Effect.promise( + () => + (c as unknown as { queue: AnyQueue }).queue.next(names, opts) as Promise< + QueueMessage[] | undefined + >, + ); diff --git a/rivetkit-typescript/packages/effect/src/rivet-actor.ts b/rivetkit-typescript/packages/effect/src/rivet-actor.ts new file mode 100644 index 0000000000..f39bef1ef3 --- /dev/null +++ b/rivetkit-typescript/packages/effect/src/rivet-actor.ts @@ -0,0 +1,95 @@ +import type { ManagedRuntime } from "effect"; +import { actor as rivetActor, type Actions, type ActorConfigInput, type ActorDefinition } from "rivetkit"; +import { setManagedRuntime } from "./runtime.ts"; + +type ActorRuntime = ManagedRuntime.ManagedRuntime; +type AnyDatabaseProvider = + Parameters[0] extends ActorConfigInput + ? TDatabase + : never; + +const withContextRuntime = ( + fn: (...args: unknown[]) => unknown, + runtime: ActorRuntime, +): ((...args: unknown[]) => unknown) => { + return (...args: unknown[]) => { + setManagedRuntime(args[0], runtime); + return fn(...args); + }; +}; + +const wrapActorConfigWithRuntime = ( + input: Record, + runtime: ActorRuntime, +): Record => { + const wrapped = { ...input }; + + const hookKeys = [ + "onCreate", + "onWake", + "onDestroy", + "onSleep", + "onStateChange", + "onBeforeConnect", + "onConnect", + "onDisconnect", + "createConnState", + "onBeforeActionResponse", + "createState", + "createVars", + "onRequest", + "onWebSocket", + ] as const; + + for (const hookKey of hookKeys) { + const hook = wrapped[hookKey]; + if (typeof hook === "function") { + wrapped[hookKey] = withContextRuntime(hook as (...args: unknown[]) => unknown, runtime); + } + } + + const actions = wrapped.actions; + if (actions && typeof actions === "object") { + const wrappedActions: Record = {}; + for (const [name, action] of Object.entries(actions)) { + wrappedActions[name] = + typeof action === "function" + ? withContextRuntime(action as (...args: unknown[]) => unknown, runtime) + : action; + } + wrapped.actions = wrappedActions; + } + + return wrapped; +}; + +export function actor< + TState, + TConnParams, + TConnState, + TVars, + TInput, + TDatabase extends AnyDatabaseProvider, + TActions extends Actions, +>( + input: ActorConfigInput & { + runtime?: ActorRuntime; + }, +): ActorDefinition { + const { runtime, ...config } = input; + const runtimeAwareConfig = runtime + ? wrapActorConfigWithRuntime(config as Record, runtime) + : config; + + return rivetActor( + runtimeAwareConfig as ActorConfigInput< + TState, + TConnParams, + TConnState, + TVars, + TInput, + TDatabase, + TActions + >, + ) as ActorDefinition; +} diff --git a/rivetkit-typescript/packages/effect/src/runtime.ts b/rivetkit-typescript/packages/effect/src/runtime.ts new file mode 100644 index 0000000000..0ff975785e --- /dev/null +++ b/rivetkit-typescript/packages/effect/src/runtime.ts @@ -0,0 +1,119 @@ +import { Cause, Effect, Exit, Layer, ManagedRuntime, Option } from "effect"; +import { RuntimeExecutionError } from "./errors.ts"; + +export type AnyManagedRuntime = ManagedRuntime.ManagedRuntime; + +export const ActorManagedRuntimeSymbol = Symbol.for("@rivetkit/effect/runtime"); + +const DefaultRuntime = ManagedRuntime.make(Layer.empty); + +type RuntimeCarrier = { + [ActorManagedRuntimeSymbol]?: AnyManagedRuntime; +}; + +const RuntimeContextMap = new WeakMap(); + +export const setManagedRuntime = (context: unknown, runtime: AnyManagedRuntime): void => { + if (typeof context === "object" && context !== null) { + RuntimeContextMap.set(context, runtime); + + const carrier = context as RuntimeCarrier; + try { + if (Object.prototype.hasOwnProperty.call(carrier, ActorManagedRuntimeSymbol)) { + carrier[ActorManagedRuntimeSymbol] = runtime; + return; + } + + if (Object.isExtensible(carrier)) { + Object.defineProperty(carrier, ActorManagedRuntimeSymbol, { + configurable: true, + enumerable: false, + writable: true, + value: runtime, + }); + } + } catch { + // Some runtime context objects may be non-extensible/proxied; WeakMap path still works. + } + } +}; + +export const getManagedRuntime = (context: unknown): AnyManagedRuntime | undefined => { + if (typeof context === "object" && context !== null) { + const fromCarrier = (context as RuntimeCarrier)[ActorManagedRuntimeSymbol]; + if (fromCarrier) return fromCarrier; + return RuntimeContextMap.get(context); + } +}; + +/** + * Last-resort fallback: runs an effect using the empty DefaultRuntime. + * Only works when `R = never` (no unsatisfied requirements). + */ +const runWithCurrentRuntime = (effect: Effect.Effect): Promise => + DefaultRuntime.runPromise(effect).catch((error) => + Promise.reject( + new RuntimeExecutionError({ + message: "Failed to execute effect with current runtime", + operation: "runWithCurrentRuntime", + cause: error, + }), + ), + ); + +/** + * Last-resort fallback: runs an effect to Exit using the empty DefaultRuntime. + * Only works when `R = never` (no unsatisfied requirements). + */ +const runExitWithCurrentRuntime = (effect: Effect.Effect): Promise> => + DefaultRuntime.runPromiseExit(effect).catch((error) => + Promise.reject( + new RuntimeExecutionError({ + message: "Failed to execute effect exit with current runtime", + operation: "runExitWithCurrentRuntime", + cause: error, + }), + ), + ); + +const throwFromCause = (cause: Cause.Cause): never => { + const failure = Cause.failureOption(cause); + if (Option.isSome(failure)) { + throw failure.value; + } + + const defect = Cause.dieOption(cause); + if (Option.isSome(defect)) { + throw defect.value instanceof Error ? defect.value : new Error(String(defect.value)); + } + + throw new Error(Cause.pretty(cause)); +}; + +export const runPromise = (effect: Effect.Effect, context?: unknown): Promise => + runPromiseExit(effect, context).then((exit) => { + if (Exit.isSuccess(exit)) { + return exit.value; + } + return throwFromCause(exit.cause); + }); + +export const runPromiseExit = ( + effect: Effect.Effect, + context?: unknown, +): Promise> => { + const runtime = getManagedRuntime(context); + const execution: Promise> = runtime + ? runtime.runPromiseExit(effect as Effect.Effect) + : runExitWithCurrentRuntime(effect as Effect.Effect); + + return execution.catch((error) => + Exit.die( + new RuntimeExecutionError({ + message: "Runtime execution failed unexpectedly", + operation: "runPromiseExit", + cause: error, + }), + ), + ); +}; diff --git a/rivetkit-typescript/packages/effect/tests/queue.test.ts b/rivetkit-typescript/packages/effect/tests/queue.test.ts new file mode 100644 index 0000000000..d34b56c0ab --- /dev/null +++ b/rivetkit-typescript/packages/effect/tests/queue.test.ts @@ -0,0 +1,64 @@ +import { Effect } from "effect"; +import { describe, expect, it, vi } from "@effect/vitest"; +import * as Queue from "../src/queue.ts"; + +const createContext = (nextFn: (...args: any[]) => Promise) => ({ + queue: { next: nextFn }, +}); + +describe("@rivetkit/effect queue helpers", () => { + it.effect("returns undefined when queue has no next message", () => + Effect.gen(function* () { + const ctx = createContext(vi.fn(async () => undefined)); + const result = yield* Queue.next(ctx as any, "jobs"); + expect(result).toBeUndefined(); + }), + ); + + it.effect("returns a QueueMessage when one is available", () => + Effect.gen(function* () { + const message = { id: 1n, name: "jobs", body: { task: "test" }, createdAt: Date.now() }; + const ctx = createContext(vi.fn(async () => message)); + const result = yield* Queue.next(ctx as any, "jobs"); + expect(result).toEqual(message); + }), + ); + + it.effect("passes options through to queue.next", () => + Effect.gen(function* () { + const nextFn = vi.fn(async () => undefined); + const ctx = createContext(nextFn); + yield* Queue.next(ctx as any, "jobs", { timeout: 5000, count: 3 }); + expect(nextFn).toHaveBeenCalledWith("jobs", { timeout: 5000, count: 3 }); + }), + ); + + it.effect("nextMultiple returns undefined when no messages available", () => + Effect.gen(function* () { + const ctx = createContext(vi.fn(async () => undefined)); + const result = yield* Queue.nextMultiple(ctx as any, ["jobs", "audit"]); + expect(result).toBeUndefined(); + }), + ); + + it.effect("nextMultiple returns array of messages", () => + Effect.gen(function* () { + const messages = [ + { id: 1n, name: "jobs", body: {}, createdAt: Date.now() }, + { id: 2n, name: "audit", body: {}, createdAt: Date.now() }, + ]; + const ctx = createContext(vi.fn(async () => messages)); + const result = yield* Queue.nextMultiple(ctx as any, ["jobs", "audit"]); + expect(result).toEqual(messages); + }), + ); + + it.effect("nextMultiple passes names array to queue.next", () => + Effect.gen(function* () { + const nextFn = vi.fn(async () => undefined); + const ctx = createContext(nextFn); + yield* Queue.nextMultiple(ctx as any, ["jobs", "audit"], { timeout: 1000 }); + expect(nextFn).toHaveBeenCalledWith(["jobs", "audit"], { timeout: 1000 }); + }), + ); +}); diff --git a/rivetkit-typescript/packages/effect/tests/runtime.test.ts b/rivetkit-typescript/packages/effect/tests/runtime.test.ts new file mode 100644 index 0000000000..1011202fbf --- /dev/null +++ b/rivetkit-typescript/packages/effect/tests/runtime.test.ts @@ -0,0 +1,49 @@ +import { Cause, Effect, Exit, Option } from "effect"; +import { describe, expect, it, vi } from "@effect/vitest"; +import type { AnyManagedRuntime } from "../src/runtime.ts"; +import { runPromise, runPromiseExit, setManagedRuntime } from "../src/runtime.ts"; + +describe("@rivetkit/effect runtime", () => { + it("runPromise executes successfully without actor runtime context", async () => { + await expect(runPromise(Effect.succeed("ok"))).resolves.toBe("ok"); + }); + + it("runPromiseExit converts runtime execution rejection into RuntimeExecutionError defect", async () => { + const ctx = {}; + const runtime = { + runPromiseExit: vi.fn(async () => { + throw new Error("runtime crash"); + }), + } as unknown as AnyManagedRuntime; + + setManagedRuntime(ctx, runtime); + + const exit = await runPromiseExit(Effect.succeed("ok"), ctx); + expect(Exit.isFailure(exit)).toBe(true); + + if (Exit.isFailure(exit)) { + const defect = Cause.dieOption(exit.cause); + expect(Option.isSome(defect)).toBe(true); + if (Option.isSome(defect)) { + expect((defect.value as any)?._tag).toBe("RuntimeExecutionError"); + expect((defect.value as any)?.operation).toBe("runPromiseExit"); + } + } + }); + + it("runPromise rejects with RuntimeExecutionError when runtime execution fails", async () => { + const ctx = {}; + const runtime = { + runPromiseExit: vi.fn(async () => { + throw new Error("runtime crash"); + }), + } as unknown as AnyManagedRuntime; + + setManagedRuntime(ctx, runtime); + + await expect(runPromise(Effect.succeed("ok"), ctx)).rejects.toMatchObject({ + _tag: "RuntimeExecutionError", + operation: "runPromiseExit", + }); + }); +}); diff --git a/rivetkit-typescript/packages/effect/tests/wrappers.test.ts b/rivetkit-typescript/packages/effect/tests/wrappers.test.ts new file mode 100644 index 0000000000..638f441d71 --- /dev/null +++ b/rivetkit-typescript/packages/effect/tests/wrappers.test.ts @@ -0,0 +1,96 @@ +import { Effect, Exit } from "effect"; +import { describe, expect, it, vi } from "@effect/vitest"; +import * as Action from "../src/action.ts"; +import * as Actor from "../src/actor.ts"; +import { OnCreate, OnStateChange } from "../src/lifecycle.ts"; + +const createContext = () => { + const waitUntil = vi.fn((promise: Promise) => { + void promise.catch(() => undefined); + }); + + return { + state: { count: 0 }, + vars: {}, + log: { + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + debug: vi.fn(), + }, + actorId: "actor-1", + name: "message", + key: ["key-1"], + region: "test", + schedule: null, + conns: [], + client: vi.fn(() => ({})), + db: {}, + kv: {}, + waitUntil, + abortSignal: new AbortController().signal, + sleep: vi.fn(), + destroy: vi.fn(), + saveState: vi.fn(async () => undefined), + broadcast: vi.fn(), + conn: { state: null }, + }; +}; + +describe("@rivetkit/effect wrappers", () => { + it("Action.effect provides actor context and resolves async result", async () => { + const ctx = createContext(); + const add = Action.effect(function* (c: any, amount: number) { + yield* Action.updateState(c, (s: { count: number }) => { + s.count += amount; + }); + return yield* Action.state(c); + }); + + const state = await add(ctx as any, 3); + expect(state).toEqual({ count: 3 }); + }); + + it.effect("saveState maps promise rejection into StatePersistenceError", () => + Effect.gen(function* () { + const ctx = createContext(); + ctx.saveState = vi.fn(async () => { + throw new Error("write failure"); + }); + const exit = yield* Effect.exit(Actor.saveState(ctx as any, { debounce: 0 } as any)); + expect(Exit.isFailure(exit)).toBe(true); + if (Exit.isFailure(exit)) { + const failure = yield* Effect.succeed(exit.cause.toString()); + expect(failure).toContain("StatePersistenceError"); + } + }), + ); + + it("OnStateChange logs effect failures", async () => { + const ctx = createContext(); + const onStateChange = OnStateChange.effect(function* () { + yield* Effect.fail("state-sync-failed"); + }); + + onStateChange(ctx as any, { count: 1 }); + await new Promise((resolve) => setTimeout(resolve, 0)); + + expect(ctx.log.error).toHaveBeenCalledWith( + expect.objectContaining({ msg: "onStateChange effect failed" }), + ); + }); + + it("OnCreate.effect runs async lifecycle and returns result", async () => { + const ctx = createContext(); + const onCreate = OnCreate.effect(function* (c: any, input: { name: string }) { + yield* Actor.updateState(c, (s: { count: number }) => { + s.count = 42; + }); + return input.name; + }); + + const result = await onCreate(ctx as any, { name: "test-actor" }); + expect(result).toBe("test-actor"); + expect(ctx.state.count).toBe(42); + }); +}); diff --git a/rivetkit-typescript/packages/effect/tsconfig.json b/rivetkit-typescript/packages/effect/tsconfig.json new file mode 100644 index 0000000000..adb4920f67 --- /dev/null +++ b/rivetkit-typescript/packages/effect/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "verbatimModuleSyntax": true, + "allowImportingTsExtensions": true, + "noEmit": true, + "moduleDetection": "force" + }, + "include": ["src", "tests"] +} diff --git a/rivetkit-typescript/packages/effect/tsup.config.ts b/rivetkit-typescript/packages/effect/tsup.config.ts new file mode 100644 index 0000000000..f363b829fd --- /dev/null +++ b/rivetkit-typescript/packages/effect/tsup.config.ts @@ -0,0 +1,4 @@ +import { defineConfig } from "tsup"; +import defaultConfig from "../../../tsup.base.ts"; + +export default defineConfig(defaultConfig); diff --git a/rivetkit-typescript/packages/effect/turbo.json b/rivetkit-typescript/packages/effect/turbo.json new file mode 100644 index 0000000000..29d4cb2625 --- /dev/null +++ b/rivetkit-typescript/packages/effect/turbo.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://turbo.build/schema.json", + "extends": ["//"] +} diff --git a/rivetkit-typescript/packages/effect/vitest.config.ts b/rivetkit-typescript/packages/effect/vitest.config.ts new file mode 100644 index 0000000000..60c8ea6eff --- /dev/null +++ b/rivetkit-typescript/packages/effect/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "vitest/config"; +import defaultConfig from "../../../vitest.base.ts"; + +export default defineConfig({ + ...defaultConfig, + test: { + ...defaultConfig.test, + include: ["tests/**/*.test.ts"], + }, +}); From 5861bbbd94be5bc9ef4ac55387506ab91b24ebae Mon Sep 17 00:00:00 2001 From: Makisuo Date: Thu, 26 Feb 2026 00:06:43 +0100 Subject: [PATCH 2/2] feat: add effect example with counter actor Adds an example at examples/effect/ demonstrating @rivetkit/effect usage: - Counter actor using Action.effect() for all actions - OnCreate lifecycle hook with Effect-TS - Log helpers for structured logging - Batch increment, decrement, and reset operations - React frontend with @rivetkit/react - Integration tests using setupTest from rivetkit/test - Fixes @rivetkit/effect package.json exports to match tsup output --- examples/effect/frontend/App.tsx | 135 +++++++++ examples/effect/frontend/main.tsx | 12 + examples/effect/index.html | 203 +++++++++++++ examples/effect/package.json | 37 +++ examples/effect/src/actors.ts | 108 +++++++ examples/effect/src/server.ts | 6 + examples/effect/tests/counter.test.ts | 93 ++++++ examples/effect/tsconfig.json | 16 + examples/effect/turbo.json | 9 + examples/effect/vite.config.ts | 7 + examples/effect/vitest.config.ts | 10 + pnpm-lock.yaml | 285 ++++++++++++++---- .../packages/effect/package.json | 9 +- 13 files changed, 872 insertions(+), 58 deletions(-) create mode 100644 examples/effect/frontend/App.tsx create mode 100644 examples/effect/frontend/main.tsx create mode 100644 examples/effect/index.html create mode 100644 examples/effect/package.json create mode 100644 examples/effect/src/actors.ts create mode 100644 examples/effect/src/server.ts create mode 100644 examples/effect/tests/counter.test.ts create mode 100644 examples/effect/tsconfig.json create mode 100644 examples/effect/turbo.json create mode 100644 examples/effect/vite.config.ts create mode 100644 examples/effect/vitest.config.ts diff --git a/examples/effect/frontend/App.tsx b/examples/effect/frontend/App.tsx new file mode 100644 index 0000000000..0c4b602634 --- /dev/null +++ b/examples/effect/frontend/App.tsx @@ -0,0 +1,135 @@ +import { createRivetKit } from "@rivetkit/react"; +import { useEffect, useState } from "react"; +import type { registry } from "../src/actors.ts"; + +const { useActor } = createRivetKit(`${location.origin}/api/rivet`); + +export function App() { + const [counterId, setCounterId] = useState("default"); + const [count, setCount] = useState(0); + + const counter = useActor({ + name: "counter", + key: [counterId], + }); + + useEffect(() => { + if (counter.connection) { + counter.connection.getCount().then(setCount); + } + }, [counter.connection]); + + counter.useEvent("newCount", (data: { count: number; updatedBy: string }) => { + setCount(data.count); + }); + + const increment = async (amount: number) => { + if (counter.connection) { + await counter.connection.increment(amount); + } + }; + + const decrement = async (amount: number) => { + if (counter.connection) { + await counter.connection.decrement(amount); + } + }; + + const reset = async () => { + if (counter.connection) { + await counter.connection.reset(); + } + }; + + const batchIncrement = async () => { + if (counter.connection) { + await counter.connection.batchIncrement([1, 2, 3, 4, 5]); + } + }; + + return ( +
+
+
+

Effect Counter

+
+
+ {counter.connection ? 'Connected' : 'Connecting...'} +
+
+ +
+
+ + setCounterId(e.target.value)} + placeholder="Enter counter ID" + className="setting-input" + /> +
+
+ +
+
{count}
+

Current Count

+
+ +
+ + + + +
+ +
+ + +
+ +
+

This example uses @rivetkit/effect for all actor logic.

+

Actions use Effect.gen for composable, type-safe operations with structured logging.

+
+
+
+ ); +} diff --git a/examples/effect/frontend/main.tsx b/examples/effect/frontend/main.tsx new file mode 100644 index 0000000000..372f49c622 --- /dev/null +++ b/examples/effect/frontend/main.tsx @@ -0,0 +1,12 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { App } from "./App.tsx"; + +const root = document.getElementById("root"); +if (!root) throw new Error("Root element not found"); + +createRoot(root).render( + + + +); diff --git a/examples/effect/index.html b/examples/effect/index.html new file mode 100644 index 0000000000..8df5cffa7d --- /dev/null +++ b/examples/effect/index.html @@ -0,0 +1,203 @@ + + + + + + Effect Counter Example + + + +
+ + + diff --git a/examples/effect/package.json b/examples/effect/package.json new file mode 100644 index 0000000000..7165818c29 --- /dev/null +++ b/examples/effect/package.json @@ -0,0 +1,37 @@ +{ + "name": "effect", + "version": "2.0.21", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "check-types": "tsc --noEmit", + "test": "vitest run", + "build": "vite build && vite build --mode server", + "start": "srvx --static=public/ dist/server.js" + }, + "devDependencies": { + "@types/node": "^22.13.9", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@vitejs/plugin-react": "^4.2.0", + "tsx": "^3.12.7", + "typescript": "^5.5.2", + "vite": "^5.0.0", + "vite-plugin-srvx": "^1.0.2", + "vitest": "^3.1.1" + }, + "dependencies": { + "@hono/node-server": "^1.19.7", + "@hono/node-ws": "^1.3.0", + "@rivetkit/effect": "workspace:*", + "@rivetkit/react": "^2.0.38", + "effect": "^3.16.0", + "hono": "^4.11.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "rivetkit": "^2.0.38", + "srvx": "^0.10.0" + }, + "license": "MIT" +} diff --git a/examples/effect/src/actors.ts b/examples/effect/src/actors.ts new file mode 100644 index 0000000000..f461065b68 --- /dev/null +++ b/examples/effect/src/actors.ts @@ -0,0 +1,108 @@ +import { Effect } from "effect"; +import { setup, event } from "rivetkit"; +import { actor, Action, OnCreate, Log } from "@rivetkit/effect"; + +// A counter actor using Effect-TS for all actions and lifecycle hooks +export const counter = actor({ + state: { + count: 0, + lastUpdatedBy: "" as string, + }, + events: { + newCount: event<{ count: number; updatedBy: string }>(), + }, + + // Use Effect for lifecycle hooks + onCreate: OnCreate.effect(function* (c) { + yield* Log.info("Counter actor created", { actorId: c.actorId }); + }), + + actions: { + // Use Effect.gen for action logic with typed errors and composition + increment: Action.effect(function* (c, amount: number) { + yield* Action.updateState(c, (s) => { + s.count += amount; + s.lastUpdatedBy = "increment"; + }); + + const state = yield* Action.state(c); + yield* Action.broadcast(c, "newCount", { + count: state.count, + updatedBy: state.lastUpdatedBy, + }); + yield* Log.info("Counter incremented", { amount, newCount: state.count }); + + return state.count; + }), + + decrement: Action.effect(function* (c, amount: number) { + yield* Action.updateState(c, (s) => { + s.count -= amount; + s.lastUpdatedBy = "decrement"; + }); + + const state = yield* Action.state(c); + yield* Action.broadcast(c, "newCount", { + count: state.count, + updatedBy: state.lastUpdatedBy, + }); + yield* Log.info("Counter decremented", { amount, newCount: state.count }); + + return state.count; + }), + + // Demonstrates Effect composition — reset validates before updating + reset: Action.effect(function* (c) { + const state = yield* Action.state(c); + + // Skip if already zero + if (state.count === 0) { + yield* Log.debug("Counter already at zero, skipping reset"); + return 0; + } + + yield* Action.updateState(c, (s) => { + s.count = 0; + s.lastUpdatedBy = "reset"; + }); + + yield* Action.broadcast(c, "newCount", { count: 0, updatedBy: "reset" }); + yield* Log.info("Counter reset to zero"); + + return 0; + }), + + getCount: Action.effect(function* (c) { + const state = yield* Action.state(c); + return state.count; + }), + + // Demonstrates using Effect.all for parallel operations + batchIncrement: Action.effect(function* (c, amounts: number[]) { + const total = amounts.reduce((sum, a) => sum + a, 0); + + yield* Action.updateState(c, (s) => { + s.count += total; + s.lastUpdatedBy = "batchIncrement"; + }); + + const state = yield* Action.state(c); + yield* Action.broadcast(c, "newCount", { + count: state.count, + updatedBy: state.lastUpdatedBy, + }); + yield* Log.info("Batch increment applied", { + operations: amounts.length, + total, + newCount: state.count, + }); + + return state.count; + }), + }, +}); + +// Register actors +export const registry = setup({ + use: { counter }, +}); diff --git a/examples/effect/src/server.ts b/examples/effect/src/server.ts new file mode 100644 index 0000000000..95c8895f94 --- /dev/null +++ b/examples/effect/src/server.ts @@ -0,0 +1,6 @@ +import { Hono } from "hono"; +import { registry } from "./actors.ts"; + +const app = new Hono(); +app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); +export default app; diff --git a/examples/effect/tests/counter.test.ts b/examples/effect/tests/counter.test.ts new file mode 100644 index 0000000000..c587a9b4fb --- /dev/null +++ b/examples/effect/tests/counter.test.ts @@ -0,0 +1,93 @@ +import { setupTest } from "rivetkit/test"; +import { describe, expect, test } from "vitest"; +import { registry } from "../src/actors.ts"; + +describe("effect counter actor", () => { + test("increment counter", async (ctx) => { + const { client } = await setupTest(ctx, registry); + + const counter = client.counter.getOrCreate(["test-1"]); + + const count = await counter.increment(5); + expect(count).toBe(5); + + const count2 = await counter.increment(3); + expect(count2).toBe(8); + }); + + test("decrement counter", async (ctx) => { + const { client } = await setupTest(ctx, registry); + + const counter = client.counter.getOrCreate(["test-2"]); + + await counter.increment(10); + const count = await counter.decrement(3); + expect(count).toBe(7); + }); + + test("reset counter", async (ctx) => { + const { client } = await setupTest(ctx, registry); + + const counter = client.counter.getOrCreate(["test-3"]); + + await counter.increment(42); + const count = await counter.reset(); + expect(count).toBe(0); + + // Resetting when already zero should still return 0 + const count2 = await counter.reset(); + expect(count2).toBe(0); + }); + + test("getCount returns current value", async (ctx) => { + const { client } = await setupTest(ctx, registry); + + const counter = client.counter.getOrCreate(["test-4"]); + + const initial = await counter.getCount(); + expect(initial).toBe(0); + + await counter.increment(7); + const after = await counter.getCount(); + expect(after).toBe(7); + }); + + test("batchIncrement applies sum of amounts", async (ctx) => { + const { client } = await setupTest(ctx, registry); + + const counter = client.counter.getOrCreate(["test-5"]); + + const count = await counter.batchIncrement([1, 2, 3, 4, 5]); + expect(count).toBe(15); + + const count2 = await counter.batchIncrement([10, 20]); + expect(count2).toBe(45); + }); + + test("state persists across client instances for same key", async (ctx) => { + const { client } = await setupTest(ctx, registry); + + const counter1 = client.counter.getOrCreate(["shared"]); + await counter1.increment(100); + + const counter2 = client.counter.getOrCreate(["shared"]); + const count = await counter2.getCount(); + expect(count).toBe(100); + }); + + test("different keys have independent state", async (ctx) => { + const { client } = await setupTest(ctx, registry); + + const counterA = client.counter.getOrCreate(["a"]); + const counterB = client.counter.getOrCreate(["b"]); + + await counterA.increment(10); + await counterB.increment(20); + + const countA = await counterA.getCount(); + const countB = await counterB.getCount(); + + expect(countA).toBe(10); + expect(countB).toBe(20); + }); +}); diff --git a/examples/effect/tsconfig.json b/examples/effect/tsconfig.json new file mode 100644 index 0000000000..0551447a84 --- /dev/null +++ b/examples/effect/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": ["esnext", "dom"], + "jsx": "react-jsx", + "module": "esnext", + "moduleResolution": "bundler", + "types": ["node", "vite/client"], + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "allowImportingTsExtensions": true, + "rewriteRelativeImportExtensions": true + }, + "include": ["src/**/*", "frontend/**/*", "tests/**/*"] +} diff --git a/examples/effect/turbo.json b/examples/effect/turbo.json new file mode 100644 index 0000000000..2e3463fc08 --- /dev/null +++ b/examples/effect/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "dependsOn": ["@rivetkit/effect#build", "@rivetkit/react#build", "rivetkit#build"] + } + } +} diff --git a/examples/effect/vite.config.ts b/examples/effect/vite.config.ts new file mode 100644 index 0000000000..06dae893f5 --- /dev/null +++ b/examples/effect/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import srvx from "vite-plugin-srvx"; + +export default defineConfig({ + plugins: [react(), ...srvx({ entry: "src/server.ts" })], +}); diff --git a/examples/effect/vitest.config.ts b/examples/effect/vitest.config.ts new file mode 100644 index 0000000000..f913a97abd --- /dev/null +++ b/examples/effect/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + server: { + port: 5173, + }, + test: { + include: ["tests/**/*.test.ts"], + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3e237e04ec..226dee6cc6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1135,6 +1135,67 @@ importers: specifier: ^3.1.1 version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.10)(@vitest/ui@3.1.1)(less@4.4.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@22.19.10)(typescript@5.9.3))(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) + examples/effect: + dependencies: + '@hono/node-server': + specifier: ^1.19.7 + version: 1.19.9(hono@4.11.9) + '@hono/node-ws': + specifier: ^1.3.0 + version: 1.3.0(@hono/node-server@1.19.9(hono@4.11.9))(hono@4.11.9) + '@rivetkit/effect': + specifier: workspace:* + version: link:../../rivetkit-typescript/packages/effect + '@rivetkit/react': + specifier: workspace:* + version: link:../../rivetkit-typescript/packages/react + effect: + specifier: ^3.16.0 + version: 3.19.19 + hono: + specifier: ^4.11.3 + version: 4.11.9 + react: + specifier: 19.1.0 + version: 19.1.0 + react-dom: + specifier: 19.1.0 + version: 19.1.0(react@19.1.0) + rivetkit: + specifier: workspace:* + version: link:../../rivetkit-typescript/packages/rivetkit + srvx: + specifier: ^0.10.0 + version: 0.10.0 + devDependencies: + '@types/node': + specifier: ^22.13.9 + version: 22.19.11 + '@types/react': + specifier: ^19 + version: 19.2.13 + '@types/react-dom': + specifier: ^19 + version: 19.2.3(@types/react@19.2.13) + '@vitejs/plugin-react': + specifier: ^4.2.0 + version: 4.7.0(vite@5.4.21(@types/node@22.19.11)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0)) + tsx: + specifier: ^3.12.7 + version: 3.14.0 + typescript: + specifier: ^5.5.2 + version: 5.9.3 + vite: + specifier: ^5.0.0 + version: 5.4.21(@types/node@22.19.11)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) + vite-plugin-srvx: + specifier: ^1.0.2 + version: 1.0.2(srvx@0.10.0)(vite@5.4.21(@types/node@22.19.11)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0)) + vitest: + specifier: ^3.1.1 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.11)(less@4.4.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@22.19.11)(typescript@5.9.3))(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) + examples/elysia: dependencies: '@hono/node-server': @@ -4156,19 +4217,22 @@ importers: devDependencies: '@effect/vitest': specifier: ^0.27.0 - version: 0.27.0(effect@3.19.19)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.0.7)(typescript@5.9.3))(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0)) + version: 0.27.0(effect@3.19.19)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.11)(less@4.4.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@22.19.11)(typescript@5.9.3))(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0)) + '@types/node': + specifier: ^22.10.5 + version: 22.19.11 effect: specifier: ^3.16.0 version: 3.19.19 tsup: specifier: ^8.5.0 - version: 8.5.1(@microsoft/api-extractor@7.53.2(@types/node@25.0.7))(@swc/core@1.15.11(@swc/helpers@0.5.17))(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + version: 8.5.1(@microsoft/api-extractor@7.53.2(@types/node@22.19.11))(@swc/core@1.15.11(@swc/helpers@0.5.17))(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) typescript: specifier: ^5.9.2 version: 5.9.3 vitest: specifier: ^3.1.1 - version: 3.2.4(@types/debug@4.1.12)(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.0.7)(typescript@5.9.3))(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.11)(less@4.4.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@22.19.11)(typescript@5.9.3))(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) rivetkit-typescript/packages/framework-base: dependencies: @@ -18276,10 +18340,10 @@ snapshots: '@durable-streams/client': https://pkg.pr.new/rivet-dev/durable-streams/@durable-streams/client@0323b8bcf1c9b38f1014629e1a8b6c74cc662100 fastq: 1.20.1 - '@effect/vitest@0.27.0(effect@3.19.19)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.0.7)(typescript@5.9.3))(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0))': + '@effect/vitest@0.27.0(effect@3.19.19)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.11)(less@4.4.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@22.19.11)(typescript@5.9.3))(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0))': dependencies: effect: 3.19.19 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.0.7)(typescript@5.9.3))(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.11)(less@4.4.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@22.19.11)(typescript@5.9.3))(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) '@emnapi/runtime@1.7.1': dependencies: @@ -19588,20 +19652,20 @@ snapshots: '@types/node': 22.19.10 optional: true - '@inquirer/confirm@5.1.21(@types/node@24.7.1)': + '@inquirer/confirm@5.1.21(@types/node@22.19.11)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.7.1) - '@inquirer/type': 3.0.10(@types/node@24.7.1) + '@inquirer/core': 10.3.2(@types/node@22.19.11) + '@inquirer/type': 3.0.10(@types/node@22.19.11) optionalDependencies: - '@types/node': 24.7.1 + '@types/node': 22.19.11 optional: true - '@inquirer/confirm@5.1.21(@types/node@25.0.7)': + '@inquirer/confirm@5.1.21(@types/node@24.7.1)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.0.7) - '@inquirer/type': 3.0.10(@types/node@25.0.7) + '@inquirer/core': 10.3.2(@types/node@24.7.1) + '@inquirer/type': 3.0.10(@types/node@24.7.1) optionalDependencies: - '@types/node': 25.0.7 + '@types/node': 24.7.1 optional: true '@inquirer/core@10.3.2(@types/node@20.19.13)': @@ -19631,32 +19695,32 @@ snapshots: '@types/node': 22.19.10 optional: true - '@inquirer/core@10.3.2(@types/node@24.7.1)': + '@inquirer/core@10.3.2(@types/node@22.19.11)': dependencies: '@inquirer/ansi': 1.0.2 '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@24.7.1) + '@inquirer/type': 3.0.10(@types/node@22.19.11) cli-width: 4.1.0 mute-stream: 2.0.0 signal-exit: 4.1.0 wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 24.7.1 + '@types/node': 22.19.11 optional: true - '@inquirer/core@10.3.2(@types/node@25.0.7)': + '@inquirer/core@10.3.2(@types/node@24.7.1)': dependencies: '@inquirer/ansi': 1.0.2 '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.0.7) + '@inquirer/type': 3.0.10(@types/node@24.7.1) cli-width: 4.1.0 mute-stream: 2.0.0 signal-exit: 4.1.0 wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.0.7 + '@types/node': 24.7.1 optional: true '@inquirer/figures@1.0.15': {} @@ -19670,14 +19734,14 @@ snapshots: '@types/node': 22.19.10 optional: true - '@inquirer/type@3.0.10(@types/node@24.7.1)': + '@inquirer/type@3.0.10(@types/node@22.19.11)': optionalDependencies: - '@types/node': 24.7.1 + '@types/node': 22.19.11 optional: true - '@inquirer/type@3.0.10(@types/node@25.0.7)': + '@inquirer/type@3.0.10(@types/node@24.7.1)': optionalDependencies: - '@types/node': 25.0.7 + '@types/node': 24.7.1 optional: true '@isaacs/balanced-match@4.0.1': {} @@ -19759,7 +19823,7 @@ snapshots: dependencies: '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 22.19.10 + '@types/node': 22.19.11 '@types/yargs': 15.0.19 chalk: 4.1.2 @@ -19978,6 +20042,15 @@ snapshots: - '@types/node' optional: true + '@microsoft/api-extractor-model@7.31.2(@types/node@22.19.11)': + dependencies: + '@microsoft/tsdoc': 0.15.1 + '@microsoft/tsdoc-config': 0.17.1 + '@rushstack/node-core-library': 5.17.1(@types/node@22.19.11) + transitivePeerDependencies: + - '@types/node' + optional: true + '@microsoft/api-extractor-model@7.31.2(@types/node@24.7.1)': dependencies: '@microsoft/tsdoc': 0.15.1 @@ -20052,6 +20125,25 @@ snapshots: - '@types/node' optional: true + '@microsoft/api-extractor@7.53.2(@types/node@22.19.11)': + dependencies: + '@microsoft/api-extractor-model': 7.31.2(@types/node@22.19.11) + '@microsoft/tsdoc': 0.15.1 + '@microsoft/tsdoc-config': 0.17.1 + '@rushstack/node-core-library': 5.17.1(@types/node@22.19.11) + '@rushstack/rig-package': 0.6.0 + '@rushstack/terminal': 0.19.2(@types/node@22.19.11) + '@rushstack/ts-command-line': 5.1.2(@types/node@22.19.11) + lodash: 4.17.23 + minimatch: 10.0.3 + resolve: 1.22.11 + semver: 7.5.4 + source-map: 0.6.1 + typescript: 5.8.2 + transitivePeerDependencies: + - '@types/node' + optional: true + '@microsoft/api-extractor@7.53.2(@types/node@24.7.1)': dependencies: '@microsoft/api-extractor-model': 7.31.2(@types/node@24.7.1) @@ -21481,6 +21573,20 @@ snapshots: '@types/node': 22.19.10 optional: true + '@rushstack/node-core-library@5.17.1(@types/node@22.19.11)': + dependencies: + ajv: 8.13.0 + ajv-draft-04: 1.0.0(ajv@8.13.0) + ajv-formats: 3.0.1(ajv@8.13.0) + fs-extra: 11.3.3 + import-lazy: 4.0.0 + jju: 1.4.0 + resolve: 1.22.11 + semver: 7.5.4 + optionalDependencies: + '@types/node': 22.19.11 + optional: true + '@rushstack/node-core-library@5.17.1(@types/node@24.7.1)': dependencies: ajv: 8.13.0 @@ -21519,6 +21625,11 @@ snapshots: '@types/node': 22.19.10 optional: true + '@rushstack/problem-matcher@0.1.1(@types/node@22.19.11)': + optionalDependencies: + '@types/node': 22.19.11 + optional: true + '@rushstack/problem-matcher@0.1.1(@types/node@24.7.1)': optionalDependencies: '@types/node': 24.7.1 @@ -21565,6 +21676,15 @@ snapshots: '@types/node': 22.19.10 optional: true + '@rushstack/terminal@0.19.2(@types/node@22.19.11)': + dependencies: + '@rushstack/node-core-library': 5.17.1(@types/node@22.19.11) + '@rushstack/problem-matcher': 0.1.1(@types/node@22.19.11) + supports-color: 8.1.1 + optionalDependencies: + '@types/node': 22.19.11 + optional: true + '@rushstack/terminal@0.19.2(@types/node@24.7.1)': dependencies: '@rushstack/node-core-library': 5.17.1(@types/node@24.7.1) @@ -21612,6 +21732,16 @@ snapshots: - '@types/node' optional: true + '@rushstack/ts-command-line@5.1.2(@types/node@22.19.11)': + dependencies: + '@rushstack/terminal': 0.19.2(@types/node@22.19.11) + '@types/argparse': 1.0.38 + argparse: 1.0.10 + string-argv: 0.3.2 + transitivePeerDependencies: + - '@types/node' + optional: true + '@rushstack/ts-command-line@5.1.2(@types/node@24.7.1)': dependencies: '@rushstack/terminal': 0.19.2(@types/node@24.7.1) @@ -22378,7 +22508,7 @@ snapshots: '@types/pg@8.16.0': dependencies: - '@types/node': 22.19.10 + '@types/node': 22.19.11 pg-protocol: 1.11.0 pg-types: 2.2.0 @@ -22412,7 +22542,7 @@ snapshots: '@types/sax@1.2.7': dependencies: - '@types/node': 22.19.10 + '@types/node': 22.19.11 '@types/semver@7.7.1': {} @@ -22551,6 +22681,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitejs/plugin-react@4.7.0(vite@5.4.21(@types/node@22.19.11)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 5.4.21(@types/node@22.19.11)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) + transitivePeerDependencies: + - supports-color + '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@20.19.13)(jiti@1.21.7)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@babel/core': 7.29.0 @@ -22635,23 +22777,23 @@ snapshots: msw: 2.12.10(@types/node@22.19.10)(typescript@5.9.3) vite: 5.4.21(@types/node@22.19.10)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) - '@vitest/mocker@3.2.4(msw@2.12.10(@types/node@24.7.1)(typescript@5.9.3))(vite@5.4.21(@types/node@24.7.1)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0))': + '@vitest/mocker@3.2.4(msw@2.12.10(@types/node@22.19.11)(typescript@5.9.3))(vite@5.4.21(@types/node@22.19.11)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - msw: 2.12.10(@types/node@24.7.1)(typescript@5.9.3) - vite: 5.4.21(@types/node@24.7.1)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) + msw: 2.12.10(@types/node@22.19.11)(typescript@5.9.3) + vite: 5.4.21(@types/node@22.19.11)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) - '@vitest/mocker@3.2.4(msw@2.12.10(@types/node@25.0.7)(typescript@5.9.3))(vite@5.4.21(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0))': + '@vitest/mocker@3.2.4(msw@2.12.10(@types/node@24.7.1)(typescript@5.9.3))(vite@5.4.21(@types/node@24.7.1)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - msw: 2.12.10(@types/node@25.0.7)(typescript@5.9.3) - vite: 5.4.21(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) + msw: 2.12.10(@types/node@24.7.1)(typescript@5.9.3) + vite: 5.4.21(@types/node@24.7.1)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) '@vitest/pretty-format@2.1.9': dependencies: @@ -27681,9 +27823,9 @@ snapshots: - '@types/node' optional: true - msw@2.12.10(@types/node@24.7.1)(typescript@5.9.3): + msw@2.12.10(@types/node@22.19.11)(typescript@5.9.3): dependencies: - '@inquirer/confirm': 5.1.21(@types/node@24.7.1) + '@inquirer/confirm': 5.1.21(@types/node@22.19.11) '@mswjs/interceptors': 0.41.2 '@open-draft/deferred-promise': 2.2.0 '@types/statuses': 2.0.6 @@ -27707,9 +27849,9 @@ snapshots: - '@types/node' optional: true - msw@2.12.10(@types/node@25.0.7)(typescript@5.9.3): + msw@2.12.10(@types/node@24.7.1)(typescript@5.9.3): dependencies: - '@inquirer/confirm': 5.1.21(@types/node@25.0.7) + '@inquirer/confirm': 5.1.21(@types/node@24.7.1) '@mswjs/interceptors': 0.41.2 '@open-draft/deferred-promise': 2.2.0 '@types/statuses': 2.0.6 @@ -30203,6 +30345,36 @@ snapshots: - tsx - yaml + tsup@8.5.1(@microsoft/api-extractor@7.53.2(@types/node@22.19.11))(@swc/core@1.15.11(@swc/helpers@0.5.17))(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2): + dependencies: + bundle-require: 5.1.0(esbuild@0.27.3) + cac: 6.7.14 + chokidar: 4.0.3 + consola: 3.4.2 + debug: 4.4.3 + esbuild: 0.27.3 + fix-dts-default-cjs-exports: 1.0.1 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0)(yaml@2.8.2) + resolve-from: 5.0.0 + rollup: 4.57.1 + source-map: 0.7.6 + sucrase: 3.35.1 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tree-kill: 1.2.2 + optionalDependencies: + '@microsoft/api-extractor': 7.53.2(@types/node@22.19.11) + '@swc/core': 1.15.11(@swc/helpers@0.5.17) + postcss: 8.5.6 + typescript: 5.9.3 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + tsup@8.5.1(@microsoft/api-extractor@7.53.2(@types/node@24.7.1))(@swc/core@1.15.11(@swc/helpers@0.5.17))(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2): dependencies: bundle-require: 5.1.0(esbuild@0.27.3) @@ -30776,13 +30948,13 @@ snapshots: - supports-color - terser - vite-node@3.2.4(@types/node@24.7.1)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0): + vite-node@3.2.4(@types/node@22.19.11)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 5.4.21(@types/node@24.7.1)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) + vite: 5.4.21(@types/node@22.19.11)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) transitivePeerDependencies: - '@types/node' - less @@ -30794,13 +30966,13 @@ snapshots: - supports-color - terser - vite-node@3.2.4(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0): + vite-node@3.2.4(@types/node@24.7.1)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 5.4.21(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) + vite: 5.4.21(@types/node@24.7.1)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) transitivePeerDependencies: - '@types/node' - less @@ -30885,6 +31057,11 @@ snapshots: srvx: 0.10.0 vite: 5.4.21(@types/node@22.19.10)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) + vite-plugin-srvx@1.0.2(srvx@0.10.0)(vite@5.4.21(@types/node@22.19.11)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0)): + dependencies: + srvx: 0.10.0 + vite: 5.4.21(@types/node@22.19.11)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) + vite-plugin-srvx@1.0.2(srvx@0.10.0)(vite@6.4.1(@types/node@22.19.10)(jiti@1.21.7)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): dependencies: srvx: 0.10.0 @@ -30951,13 +31128,13 @@ snapshots: stylus: 0.62.0 terser: 5.46.0 - vite@5.4.21(@types/node@24.7.1)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0): + vite@5.4.21(@types/node@22.19.11)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0): dependencies: esbuild: 0.21.5 postcss: 8.5.6 rollup: 4.57.1 optionalDependencies: - '@types/node': 24.7.1 + '@types/node': 22.19.11 fsevents: 2.3.3 less: 4.4.1 lightningcss: 1.31.1 @@ -30965,13 +31142,13 @@ snapshots: stylus: 0.62.0 terser: 5.46.0 - vite@5.4.21(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0): + vite@5.4.21(@types/node@24.7.1)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0): dependencies: esbuild: 0.21.5 postcss: 8.5.6 rollup: 4.57.1 optionalDependencies: - '@types/node': 25.0.7 + '@types/node': 24.7.1 fsevents: 2.3.3 less: 4.4.1 lightningcss: 1.31.1 @@ -31252,11 +31429,11 @@ snapshots: - supports-color - terser - vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.7.1)(less@4.4.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@24.7.1)(typescript@5.9.3))(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.11)(less@4.4.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@22.19.11)(typescript@5.9.3))(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(msw@2.12.10(@types/node@24.7.1)(typescript@5.9.3))(vite@5.4.21(@types/node@24.7.1)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0)) + '@vitest/mocker': 3.2.4(msw@2.12.10(@types/node@22.19.11)(typescript@5.9.3))(vite@5.4.21(@types/node@22.19.11)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -31274,12 +31451,12 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 5.4.21(@types/node@24.7.1)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) - vite-node: 3.2.4(@types/node@24.7.1)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) + vite: 5.4.21(@types/node@22.19.11)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) + vite-node: 3.2.4(@types/node@22.19.11)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 - '@types/node': 24.7.1 + '@types/node': 22.19.11 transitivePeerDependencies: - less - lightningcss @@ -31291,11 +31468,11 @@ snapshots: - supports-color - terser - vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@25.0.7)(typescript@5.9.3))(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.7.1)(less@4.4.1)(lightningcss@1.31.1)(msw@2.12.10(@types/node@24.7.1)(typescript@5.9.3))(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(msw@2.12.10(@types/node@25.0.7)(typescript@5.9.3))(vite@5.4.21(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0)) + '@vitest/mocker': 3.2.4(msw@2.12.10(@types/node@24.7.1)(typescript@5.9.3))(vite@5.4.21(@types/node@24.7.1)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -31313,12 +31490,12 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 5.4.21(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) - vite-node: 3.2.4(@types/node@25.0.7)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) + vite: 5.4.21(@types/node@24.7.1)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) + vite-node: 3.2.4(@types/node@24.7.1)(less@4.4.1)(lightningcss@1.31.1)(sass@1.93.2)(stylus@0.62.0)(terser@5.46.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 - '@types/node': 25.0.7 + '@types/node': 24.7.1 transitivePeerDependencies: - less - lightningcss diff --git a/rivetkit-typescript/packages/effect/package.json b/rivetkit-typescript/packages/effect/package.json index e14ba2feca..7bdf44ef5f 100644 --- a/rivetkit-typescript/packages/effect/package.json +++ b/rivetkit-typescript/packages/effect/package.json @@ -15,12 +15,12 @@ "exports": { ".": { "import": { - "types": "./dist/mod.d.mts", - "default": "./dist/mod.mjs" - }, - "require": { "types": "./dist/mod.d.ts", "default": "./dist/mod.js" + }, + "require": { + "types": "./dist/mod.d.cts", + "default": "./dist/mod.cjs" } } }, @@ -37,6 +37,7 @@ }, "devDependencies": { "@effect/vitest": "^0.27.0", + "@types/node": "^22.10.5", "effect": "^3.16.0", "tsup": "^8.5.0", "typescript": "^5.9.2",