Skip to content

Commit 6e0dfcb

Browse files
committed
fix(rivetkit): bind methods through createWriteThroughProxy
1 parent b18ec51 commit 6e0dfcb

15 files changed

Lines changed: 674 additions & 316 deletions

File tree

pnpm-lock.yaml

Lines changed: 115 additions & 100 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rivetkit-typescript/packages/rivetkit/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,13 +168,14 @@
168168
"actor-config-schema-gen": "tsx scripts/actor-config-schema-gen.ts"
169169
},
170170
"dependencies": {
171-
"@rivet-dev/agent-os-core": "^0.1.1",
172171
"@hono/node-server": "^1.18.2",
173172
"@hono/node-ws": "^1.1.1",
174173
"@hono/zod-openapi": "^1.1.5",
174+
"@rivet-dev/agent-os-core": "^0.1.1",
175175
"@rivetkit/bare-ts": "^0.6.2",
176176
"@rivetkit/engine-cli": "workspace:*",
177177
"@rivetkit/engine-envoy-protocol": "workspace:*",
178+
"@rivetkit/on-change": "6.0.1",
178179
"@rivetkit/rivetkit-napi": "workspace:*",
179180
"@rivetkit/rivetkit-wasm": "workspace:*",
180181
"@rivetkit/traces": "workspace:*",
@@ -192,10 +193,10 @@
192193
"zod": "^4.1.0"
193194
},
194195
"devDependencies": {
196+
"@biomejs/biome": "^2.3",
195197
"@copilotkit/llmock": "^1.6.0",
196198
"@rivet-dev/agent-os-common": "*",
197199
"@rivet-dev/agent-os-pi": "^0.1.1",
198-
"@biomejs/biome": "^2.3",
199200
"@standard-schema/spec": "^1.0.0",
200201
"@types/invariant": "^2",
201202
"@types/node": "^22.13.1",

rivetkit-typescript/packages/rivetkit/src/client/actor-conn.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
import { assertUnreachable, stringifyError } from "@/common/utils";
2323
import type { UniversalWebSocket } from "@/common/websocket-interface";
2424
import type { EngineControlClient } from "@/engine-client/driver";
25+
import type { CborSerializable } from "@/common/encoding";
2526
import {
2627
decodeCborCompat,
2728
deserializeWithEncoding,
@@ -1269,7 +1270,7 @@ export class ActorConnRaw {
12691270
name: msg.body.val.name,
12701271
args: bufferToArrayBuffer(
12711272
encodeCborCompat(
1272-
msg.body.val.args,
1273+
msg.body.val.args as CborSerializable,
12731274
),
12741275
),
12751276
},

rivetkit-typescript/packages/rivetkit/src/client/actor-handle.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
} from "@/common/client-protocol-zod";
2424
import { deconstructError } from "@/common/utils";
2525
import type { EngineControlClient } from "@/engine-client/driver";
26+
import type { CborSerializable } from "@/common/encoding";
2627
import { decodeCborCompat, deserializeWithEncoding, encodeCborCompat } from "@/serde";
2728
import { bufferToArrayBuffer } from "@/utils";
2829
import type {
@@ -332,7 +333,7 @@ export class ActorHandleRaw {
332333
args,
333334
}),
334335
requestToBare: (args): protocol.HttpActionRequest => ({
335-
args: bufferToArrayBuffer(encodeCborCompat(args)),
336+
args: bufferToArrayBuffer(encodeCborCompat(args as CborSerializable)),
336337
}),
337338
responseFromJson: (json): Response => json.output as Response,
338339
responseFromBare: (bare): Response =>

rivetkit-typescript/packages/rivetkit/src/client/queue.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
type HttpQueueSendResponse as HttpQueueSendResponseJson,
1313
HttpQueueSendResponseSchema,
1414
} from "@/common/client-protocol-zod";
15+
import type { CborSerializable } from "@/common/encoding";
1516
import { decodeCborCompat, encodeCborCompat } from "@/serde";
1617
import { bufferToArrayBuffer } from "@/utils";
1718
import { sendHttpRequest } from "./utils";
@@ -111,7 +112,7 @@ export function createQueueSender(
111112
}),
112113
requestToBare: (value): protocol.HttpQueueSendRequest => ({
113114
name: value.name ?? name,
114-
body: bufferToArrayBuffer(encodeCborCompat(value.body)),
115+
body: bufferToArrayBuffer(encodeCborCompat(value.body as CborSerializable)),
115116
wait: value.wait ?? false,
116117
timeout:
117118
value.timeout !== undefined ? BigInt(value.timeout) : null,

rivetkit-typescript/packages/rivetkit/src/common/encoding.ts

Lines changed: 230 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,168 @@ const JSON_COMPAT_BIGINT = "$BigInt";
1313
const JSON_COMPAT_ARRAY_BUFFER = "$ArrayBuffer";
1414
const JSON_COMPAT_UINT8_ARRAY = "$Uint8Array";
1515
const JSON_COMPAT_UNDEFINED = "$Undefined";
16+
const JSON_COMPAT_SET = "$Set";
17+
18+
/**
19+
* Recursive type representing all values that can be serialized via CBOR
20+
* (cbor-x). Matches the supported CBOR tag set: primitives, BigInt (tags 2/3),
21+
* Date (tags 0/1), Error (tag 27), TypedArrays (tags 64-82), ArrayBuffer,
22+
* Map (tag 259), Set (custom $Set encoding), arrays, and plain objects.
23+
*/
24+
export type CborSerializable =
25+
| string
26+
| number
27+
| boolean
28+
| null
29+
| undefined
30+
| bigint
31+
| Date
32+
| Error
33+
| ArrayBuffer
34+
| Uint8Array
35+
| Uint8ClampedArray
36+
| Uint16Array
37+
| Uint32Array
38+
| BigUint64Array
39+
| Int8Array
40+
| Int16Array
41+
| Int32Array
42+
| BigInt64Array
43+
| Float32Array
44+
| Float64Array
45+
| CborSerializable[]
46+
| Map<CborSerializable, CborSerializable>
47+
| Set<CborSerializable>
48+
| { [key: string]: CborSerializable };
49+
50+
function isTypedArray(value: unknown): boolean {
51+
return (
52+
value instanceof Uint8ClampedArray ||
53+
value instanceof Uint16Array ||
54+
value instanceof Uint32Array ||
55+
value instanceof BigUint64Array ||
56+
value instanceof Int8Array ||
57+
value instanceof Int16Array ||
58+
value instanceof Int32Array ||
59+
value instanceof BigInt64Array ||
60+
value instanceof Float32Array ||
61+
value instanceof Float64Array
62+
);
63+
}
64+
65+
/**
66+
* Recursively validates that a value is CBOR serializable. Throws TypeError
67+
* with a descriptive message for non-serializable values.
68+
*/
69+
export function assertCborSerializable(
70+
value: unknown,
71+
path = "",
72+
): asserts value is CborSerializable {
73+
if (
74+
value === null ||
75+
value === undefined ||
76+
typeof value === "string" ||
77+
typeof value === "number" ||
78+
typeof value === "boolean" ||
79+
typeof value === "bigint"
80+
) {
81+
return;
82+
}
83+
84+
if (typeof value === "function") {
85+
throw new TypeError(
86+
`Value at ${path || "root"} is a function and is not CBOR serializable`,
87+
);
88+
}
89+
90+
if (typeof value === "symbol") {
91+
throw new TypeError(
92+
`Value at ${path || "root"} is a symbol and is not CBOR serializable`,
93+
);
94+
}
95+
96+
if (
97+
value instanceof Date ||
98+
value instanceof Error ||
99+
value instanceof ArrayBuffer ||
100+
value instanceof Uint8Array ||
101+
isTypedArray(value)
102+
) {
103+
return;
104+
}
105+
106+
if (value instanceof RegExp) {
107+
throw new TypeError(
108+
`Value at ${path || "root"} is a RegExp and is not CBOR serializable`,
109+
);
110+
}
111+
112+
if (value instanceof WeakMap) {
113+
throw new TypeError(
114+
`Value at ${path || "root"} is a WeakMap and is not CBOR serializable`,
115+
);
116+
}
117+
118+
if (value instanceof WeakSet) {
119+
throw new TypeError(
120+
`Value at ${path || "root"} is a WeakSet and is not CBOR serializable`,
121+
);
122+
}
123+
124+
if (value instanceof WeakRef) {
125+
throw new TypeError(
126+
`Value at ${path || "root"} is a WeakRef and is not CBOR serializable`,
127+
);
128+
}
129+
130+
if (value instanceof Promise) {
131+
throw new TypeError(
132+
`Value at ${path || "root"} is a Promise and is not CBOR serializable`,
133+
);
134+
}
135+
136+
if (value instanceof Map) {
137+
for (const [k, v] of value.entries()) {
138+
assertCborSerializable(k, `${path || "root"}.key(${String(k)})`);
139+
assertCborSerializable(v, `${path || "root"}.value(${String(k)})`);
140+
}
141+
return;
142+
}
143+
144+
if (value instanceof Set) {
145+
let index = 0;
146+
for (const item of value.values()) {
147+
assertCborSerializable(item, `${path || "root"}.set[${index}]`);
148+
index++;
149+
}
150+
return;
151+
}
152+
153+
if (Array.isArray(value)) {
154+
for (let i = 0; i < value.length; i++) {
155+
assertCborSerializable(value[i], `${path || "root"}[${i}]`);
156+
}
157+
return;
158+
}
159+
160+
if (isPlainObject(value)) {
161+
for (const key in value) {
162+
assertCborSerializable(
163+
value[key as keyof typeof value],
164+
path ? `${path}.${key}` : key,
165+
);
166+
}
167+
return;
168+
}
169+
170+
const typeName =
171+
typeof value === "object" && value !== null
172+
? value.constructor?.name ?? typeof value
173+
: typeof value;
174+
throw new TypeError(
175+
`Value at ${path || "root"} of type "${typeName}" is not CBOR serializable`,
176+
);
177+
}
16178

17179
export const EncodingSchema = z.enum(["json", "cbor", "bare"]);
18180

@@ -112,38 +274,95 @@ function isPlainObject(value: unknown): value is Record<string, unknown> {
112274
return proto === Object.prototype || proto === null;
113275
}
114276

115-
export function encodeJsonCompatValue(input: any): any {
277+
export function encodeJsonCompatValue(input: CborSerializable): unknown {
278+
// Primitives
279+
if (input === null) {
280+
return input;
281+
}
116282
if (input === undefined) {
117283
return [JSON_COMPAT_UNDEFINED, 0];
118284
}
285+
if (
286+
typeof input === "string" ||
287+
typeof input === "number" ||
288+
typeof input === "boolean"
289+
) {
290+
return input;
291+
}
119292
if (typeof input === "bigint") {
120293
return [JSON_COMPAT_BIGINT, input.toString()];
121294
}
295+
296+
// Binary types with custom encoding
122297
if (input instanceof ArrayBuffer) {
123298
return [JSON_COMPAT_ARRAY_BUFFER, base64EncodeArrayBuffer(input)];
124299
}
125300
if (input instanceof Uint8Array) {
126301
return [JSON_COMPAT_UINT8_ARRAY, base64EncodeUint8Array(input)];
127302
}
303+
304+
// TypedArrays pass through for cbor-x native handling
305+
if (isTypedArray(input)) {
306+
return input;
307+
}
308+
309+
// Date and Error pass through for cbor-x native handling
310+
if (input instanceof Date || input instanceof Error) {
311+
return input;
312+
}
313+
314+
// Set uses custom tag encoding
315+
if (input instanceof Set) {
316+
const encoded = [...input.values()].map((v) =>
317+
encodeJsonCompatValue(v as CborSerializable),
318+
);
319+
return [JSON_COMPAT_SET, encoded];
320+
}
321+
322+
// Map recurses into keys and values
323+
if (input instanceof Map) {
324+
const encoded = new Map<unknown, unknown>();
325+
for (const [k, v] of input.entries()) {
326+
encoded.set(
327+
encodeJsonCompatValue(k as CborSerializable),
328+
encodeJsonCompatValue(v as CborSerializable),
329+
);
330+
}
331+
return encoded;
332+
}
333+
334+
// Arrays
128335
if (Array.isArray(input)) {
129-
const encoded = input.map((value) => encodeJsonCompatValue(value));
336+
const encoded = input.map((value) =>
337+
encodeJsonCompatValue(value as CborSerializable),
338+
);
130339
if (
131340
encoded.length === 2 &&
132341
typeof encoded[0] === "string" &&
133-
encoded[0].startsWith("$")
342+
(encoded[0] as string).startsWith("$")
134343
) {
135344
return ["$" + encoded[0], encoded[1]];
136345
}
137346
return encoded;
138347
}
348+
349+
// Plain objects
139350
if (isPlainObject(input)) {
140351
const encoded: Record<string, unknown> = {};
141352
for (const [key, value] of Object.entries(input)) {
142-
encoded[key] = encodeJsonCompatValue(value);
353+
encoded[key] = encodeJsonCompatValue(value as CborSerializable);
143354
}
144355
return encoded;
145356
}
146-
return input;
357+
358+
// Not serializable
359+
const typeName =
360+
typeof input === "object" && input !== null
361+
? (input as object).constructor?.name ?? typeof input
362+
: typeof input;
363+
throw new TypeError(
364+
`Value of type "${typeName}" is not CBOR serializable`,
365+
);
147366
}
148367

149368
export interface JsonCompatReviveOptions {
@@ -182,6 +401,12 @@ export function reviveJsonCompatValue(
182401
if (input[0] === JSON_COMPAT_UNDEFINED) {
183402
return undefined;
184403
}
404+
if (input[0] === JSON_COMPAT_SET) {
405+
const items = (input[1] as unknown[]).map((v) =>
406+
reviveJsonCompatValue(v, options),
407+
);
408+
return new Set(items);
409+
}
185410
if (input[0].startsWith("$$")) {
186411
return [
187412
input[0].substring(1),

rivetkit-typescript/packages/rivetkit/src/common/router.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
HEADER_ACTOR_ID,
77
HEADER_ACTOR_KEY,
88
} from "@/common/actor-router-consts";
9-
import type { Encoding } from "@/common/encoding";
9+
import type { CborSerializable, Encoding } from "@/common/encoding";
1010
import {
1111
getRequestEncoding,
1212
getRequestExposeInternalError,
@@ -111,7 +111,7 @@ export function handleRouteError(error: unknown, c: HonoContext) {
111111
code: value.code,
112112
message: value.message,
113113
metadata: value.metadata
114-
? bufferToArrayBuffer(encodeCborCompat(value.metadata))
114+
? bufferToArrayBuffer(encodeCborCompat(value.metadata as CborSerializable))
115115
: null,
116116
actor: value.actor
117117
? {

0 commit comments

Comments
 (0)