Skip to content

Commit c313f20

Browse files
MasterPtatoNathanFlurry
authored andcommitted
feat: serverless
1 parent 9749b77 commit c313f20

File tree

15 files changed

+319
-43
lines changed

15 files changed

+319
-43
lines changed

examples/next-js/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"private": true,
55
"scripts": {
66
"dev": "next dev",
7+
"build": "next build",
78
"start": "next start",
89
"lint": "next lint"
910
},
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
import { toNextHandler } from "@rivetkit/next-js";
22
import { registry } from "@/rivet/registry";
33

4-
export const { GET, POST, HEAD, PATCH, OPTIONS } = toNextHandler(registry);
4+
export const { GET, POST, PUT, PATCH, HEAD, OPTIONS } = toNextHandler(registry);

packages/next-js/src/mod.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,29 @@ export const toNextHandler = (
66
) => {
77
// Don't run server locally since we're using the fetch handler directly
88
inputConfig.disableServer = true;
9+
inputConfig.disableActorDriver = true;
910

10-
const { fetch } = registry.start(inputConfig);
11+
const { fetch } = registry.startServerless(inputConfig);
12+
13+
const fetchWrapper = async (
14+
request: Request,
15+
{ params }: { params: Promise<{ all: string[] }> },
16+
) => {
17+
const { all } = await params;
18+
19+
const newUrl = new URL(request.url);
20+
newUrl.pathname = all.join("/");
21+
const newReq = new Request(newUrl, request);
22+
23+
return await fetch(newReq);
24+
};
1125

1226
return {
13-
GET: fetch,
14-
POST: fetch,
15-
PATCH: fetch,
16-
HEAD: fetch,
17-
OPTIONS: fetch,
27+
GET: fetchWrapper,
28+
POST: fetchWrapper,
29+
PUT: fetchWrapper,
30+
PATCH: fetchWrapper,
31+
HEAD: fetchWrapper,
32+
OPTIONS: fetchWrapper,
1833
};
1934
};

packages/rivetkit/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,8 @@
188188
"tsx": "^4.19.4",
189189
"typescript": "^5.7.3",
190190
"vitest": "^3.1.1",
191-
"ws": "^8.18.1"
191+
"ws": "^8.18.1",
192+
"bufferutil": "^4.0.9"
192193
},
193194
"peerDependencies": {
194195
"@hono/node-server": "^1.14.0",

packages/rivetkit/scripts/dump-openapi.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ function main() {
3838
registryConfig,
3939
driverConfig,
4040
managerDriver,
41+
undefined,
4142
);
4243

4344
const openApiDoc = openapi.getOpenAPIDocument({

packages/rivetkit/src/actor/driver.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { Context as HonoContext } from "hono";
12
import type { CachedSerializer } from "@/actor/protocol/serde";
23
import type { AnyClient } from "@/client/client";
34
import type { ManagerDriver } from "@/manager/driver";
@@ -45,6 +46,10 @@ export interface ActorDriver {
4546
sleep?(actorId: string): Promise<void>;
4647

4748
shutdown?(immediate: boolean): Promise<void>;
49+
50+
// Serverless
51+
/** This handles the serverless start request. This should manage the lifecycle of the runner tied to the request lifecycle. */
52+
serverlessHandleStart?(c: HonoContext): Promise<Response>;
4853
}
4954

5055
export enum ConnectionReadyState {

packages/rivetkit/src/common/versioned-data.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ export class VersionedDataHandler<T> {
6363
}
6464

6565
private embedVersion(data: VersionedData<Uint8Array>): Uint8Array {
66-
const versionBytes = new Uint8Array(4);
67-
new DataView(versionBytes.buffer).setUint32(0, data.version, true);
66+
const versionBytes = new Uint8Array(2);
67+
new DataView(versionBytes.buffer).setUint16(0, data.version, true);
6868

6969
const result = new Uint8Array(versionBytes.length + data.data.length);
7070
result.set(versionBytes);
@@ -74,15 +74,15 @@ export class VersionedDataHandler<T> {
7474
}
7575

7676
private extractVersion(bytes: Uint8Array): VersionedData<Uint8Array> {
77-
if (bytes.length < 4) {
77+
if (bytes.length < 2) {
7878
throw new Error("Invalid versioned data: too short");
7979
}
8080

81-
const version = new DataView(bytes.buffer, bytes.byteOffset).getUint32(
81+
const version = new DataView(bytes.buffer, bytes.byteOffset).getUint16(
8282
0,
8383
true,
8484
);
85-
const data = bytes.slice(4);
85+
const data = bytes.slice(2);
8686

8787
return { version, data };
8888
}

packages/rivetkit/src/driver-test-suite/mod.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ export async function createTestRuntime(
199199
registry.config,
200200
config,
201201
managerDriver,
202+
undefined,
202203
);
203204

204205
// Inject WebSocket

packages/rivetkit/src/drivers/engine/actor-driver.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import type {
44
} from "@rivetkit/engine-runner";
55
import { Runner } from "@rivetkit/engine-runner";
66
import * as cbor from "cbor-x";
7+
import type { Context as HonoContext } from "hono";
8+
import { streamSSE } from "hono/streaming";
79
import { WSContext } from "hono/ws";
810
import invariant from "invariant";
911
import { lookupInRegistry } from "@/actor/definition";
@@ -63,6 +65,9 @@ export class EngineActorDriver implements ActorDriver {
6365
#actorRouter: ActorRouter;
6466
#version: number = 1; // Version for the runner protocol
6567

68+
#runnerStarted: PromiseWithResolvers<undefined> = Promise.withResolvers();
69+
#runnerStopped: PromiseWithResolvers<undefined> = Promise.withResolvers();
70+
6671
constructor(
6772
registryConfig: RegistryConfig,
6873
runConfig: RunConfig,
@@ -111,6 +116,8 @@ export class EngineActorDriver implements ActorDriver {
111116
runnerName: this.#config.runnerName,
112117
});
113118
}
119+
120+
this.#runnerStarted.resolve(undefined);
114121
},
115122
onDisconnected: () => {
116123
logger().warn({
@@ -120,7 +127,9 @@ export class EngineActorDriver implements ActorDriver {
120127
});
121128
hasDisconnected = true;
122129
},
123-
onShutdown: () => {},
130+
onShutdown: () => {
131+
this.#runnerStopped.resolve(undefined);
132+
},
124133
fetch: this.#runnerFetch.bind(this),
125134
websocket: this.#runnerWebSocket.bind(this),
126135
onActorStart: this.#runnerOnActorStart.bind(this),
@@ -381,4 +390,17 @@ export class EngineActorDriver implements ActorDriver {
381390
logger().info({ msg: "stopping engine actor driver" });
382391
await this.#runner.shutdown(immediate);
383392
}
393+
394+
async serverlessHandleStart(c: HonoContext): Promise<Response> {
395+
await this.#runnerStarted.promise;
396+
397+
return streamSSE(c, async (stream) => {
398+
// Runner id should be set if the runner started
399+
const runnerId = this.#runner.runnerId;
400+
invariant(runnerId, "runnerId not set");
401+
stream.writeSSE({ data: runnerId });
402+
403+
return this.#runnerStopped.promise;
404+
});
405+
}
384406
}

packages/rivetkit/src/drivers/file-system/actor.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Hono, MiddlewareHandler } from "hono";
2+
import { streamSSE } from "hono/streaming";
13
import type { GenericConnGlobalState } from "@/actor/generic-conn-driver";
24
import { loggerWithoutContext } from "@/actor/log";
35
import type { AnyClient } from "@/client/client";

0 commit comments

Comments
 (0)