Skip to content

Commit 958fa27

Browse files
committed
feat: Add rpc-methods package
- Add rpc-methods package for internal RPC calls - Consolidate PromiseCallbacks type into utils package
1 parent f143db3 commit 958fa27

22 files changed

+843
-14
lines changed

packages/extension/src/kernel-integration/VatWorkerClient.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { makePromiseKit } from '@endo/promise-kit';
2-
import type { PromiseKit } from '@endo/promise-kit';
32
import { isObject } from '@metamask/utils';
43
import {
54
isVatCommandReply,
@@ -24,15 +23,13 @@ import type {
2423
PostMessageEnvelope,
2524
PostMessageTarget,
2625
} from '@ocap/streams/browser';
27-
import type { Logger } from '@ocap/utils';
26+
import type { Logger, PromiseCallbacks } from '@ocap/utils';
2827
import { makeCounter, makeLogger } from '@ocap/utils';
2928

3029
// Appears in the docs.
3130
// eslint-disable-next-line @typescript-eslint/no-unused-vars
3231
import type { ExtensionVatWorkerServer } from './VatWorkerServer.ts';
3332

34-
type PromiseCallbacks<Resolve = unknown> = Omit<PromiseKit<Resolve>, 'promise'>;
35-
3633
export type VatWorkerClientStream = PostMessageDuplexStream<
3734
MessageEvent<VatWorkerServiceReply>,
3835
PostMessageEnvelope<VatWorkerServiceCommand>

packages/kernel/src/VatHandle.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { makePromiseKit } from '@endo/promise-kit';
88
import { VatDeletedError, StreamReadError } from '@ocap/errors';
99
import type { VatStore } from '@ocap/store';
1010
import type { DuplexStream } from '@ocap/streams';
11-
import type { Logger } from '@ocap/utils';
11+
import type { Logger, PromiseCallbacks } from '@ocap/utils';
1212
import { makeLogger, makeCounter } from '@ocap/utils';
1313

1414
import type { Kernel } from './Kernel.ts';
@@ -22,7 +22,6 @@ import { kser } from './services/kernel-marshal.ts';
2222
import type { KernelStore } from './store/index.ts';
2323
import { parseRef } from './store/utils/parse-ref.ts';
2424
import type {
25-
PromiseCallbacks,
2625
VatId,
2726
VatConfig,
2827
VRef,

packages/kernel/src/messages/message-resolver.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { makePromiseKit } from '@endo/promise-kit';
22
import { makeCounter } from '@ocap/utils';
3-
4-
import type { PromiseCallbacks } from '../types.ts';
3+
import type { PromiseCallbacks } from '@ocap/utils';
54

65
export class MessageResolver {
76
readonly #prefix: string;

packages/kernel/src/types.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { Message } from '@agoric/swingset-liveslots';
22
import type { CapData } from '@endo/marshal';
3-
import type { PromiseKit } from '@endo/promise-kit';
43
import {
54
define,
65
is,
@@ -200,11 +199,6 @@ export const VatMessageIdStruct = define<VatMessageId>(
200199
isVatMessageId,
201200
);
202201

203-
export type PromiseCallbacks<Resolve = unknown> = Omit<
204-
PromiseKit<Resolve>,
205-
'promise'
206-
>;
207-
208202
export type VatWorkerService = {
209203
/**
210204
* Launch a new worker with a specific vat id.

packages/rpc-methods/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
[Unreleased]: https://github.com/MetaMask/ocap-kernel/

packages/rpc-methods/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# `@ocap/rpc-methods`
2+
3+
Utilities for implementing JSON-RPC methods
4+
5+
## Installation
6+
7+
`yarn add @ocap/rpc-methods`
8+
9+
or
10+
11+
`npm install @ocap/rpc-methods`
12+
13+
## Contributing
14+
15+
This package is part of a monorepo. Instructions for contributing can be found in the [monorepo README](https://github.com/MetaMask/ocap-kernel#readme).

packages/rpc-methods/package.json

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
{
2+
"name": "@ocap/rpc-methods",
3+
"version": "0.0.0",
4+
"private": true,
5+
"description": "Utilities for implementing JSON-RPC methods",
6+
"homepage": "https://github.com/MetaMask/ocap-kernel/tree/main/packages/rpc-methods#readme",
7+
"bugs": {
8+
"url": "https://github.com/MetaMask/ocap-kernel/issues"
9+
},
10+
"repository": {
11+
"type": "git",
12+
"url": "https://github.com/MetaMask/ocap-kernel.git"
13+
},
14+
"type": "module",
15+
"exports": {
16+
".": {
17+
"import": {
18+
"types": "./dist/index.d.mts",
19+
"default": "./dist/index.mjs"
20+
},
21+
"require": {
22+
"types": "./dist/index.d.cts",
23+
"default": "./dist/index.cjs"
24+
}
25+
},
26+
"./package.json": "./package.json"
27+
},
28+
"files": [
29+
"dist/"
30+
],
31+
"scripts": {
32+
"build": "ts-bridge --project tsconfig.build.json --clean",
33+
"build:docs": "typedoc",
34+
"changelog:validate": "../../scripts/validate-changelog.sh @ocap/rpc-methods",
35+
"clean": "rimraf --glob './*.tsbuildinfo' ./.eslintcache ./coverage ./dist",
36+
"lint": "yarn lint:eslint && yarn lint:misc --check && yarn constraints && yarn lint:dependencies",
37+
"lint:dependencies": "depcheck",
38+
"lint:eslint": "eslint . --cache",
39+
"lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write && yarn constraints --fix && yarn lint:dependencies",
40+
"lint:misc": "prettier --no-error-on-unmatched-pattern '**/*.json' '**/*.md' '**/*.html' '!**/CHANGELOG.old.md' '**/*.yml' '!.yarnrc.yml' '!merged-packages/**' --ignore-path ../../.gitignore",
41+
"publish:preview": "yarn npm publish --tag preview",
42+
"test": "vitest run --config vitest.config.ts",
43+
"test:clean": "yarn test --no-cache --coverage.clean",
44+
"test:dev": "yarn test --mode development",
45+
"test:verbose": "yarn test --reporter verbose",
46+
"test:watch": "vitest --config vitest.config.ts"
47+
},
48+
"dependencies": {
49+
"@endo/promise-kit": "^1.1.6",
50+
"@metamask/superstruct": "^3.2.0",
51+
"@metamask/utils": "^11.3.0",
52+
"@ocap/utils": "workspace:^"
53+
},
54+
"devDependencies": {
55+
"@arethetypeswrong/cli": "^0.17.3",
56+
"@metamask/auto-changelog": "^4.0.0",
57+
"@metamask/eslint-config": "^14.0.0",
58+
"@metamask/eslint-config-nodejs": "^14.0.0",
59+
"@metamask/eslint-config-typescript": "^14.0.0",
60+
"@ocap/test-utils": "workspace:^",
61+
"@ts-bridge/cli": "^0.6.2",
62+
"@ts-bridge/shims": "^0.1.1",
63+
"@typescript-eslint/eslint-plugin": "^8.26.1",
64+
"@typescript-eslint/parser": "^8.26.1",
65+
"@typescript-eslint/utils": "^8.26.1",
66+
"@vitest/eslint-plugin": "^1.1.25",
67+
"depcheck": "^1.4.7",
68+
"eslint": "^9.12.0",
69+
"eslint-config-prettier": "^9.1.0",
70+
"eslint-import-resolver-typescript": "^3.8.4",
71+
"eslint-plugin-import-x": "^4.3.1",
72+
"eslint-plugin-jsdoc": "^50.3.1",
73+
"eslint-plugin-n": "^17.11.1",
74+
"eslint-plugin-prettier": "^5.2.1",
75+
"eslint-plugin-promise": "^7.1.0",
76+
"prettier": "^3.3.3",
77+
"rimraf": "^6.0.1",
78+
"typedoc": "^0.27.9",
79+
"typescript": "~5.8.2",
80+
"typescript-eslint": "^8.26.1",
81+
"vite": "^6.0.11",
82+
"vitest": "^3.0.5"
83+
},
84+
"engines": {
85+
"node": "^20 || >=22"
86+
}
87+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { string } from '@metamask/superstruct';
2+
import { jsonrpc2 } from '@metamask/utils';
3+
import { describe, it, vi, expect } from 'vitest';
4+
5+
import { RpcClient } from './RpcClient.ts';
6+
7+
const getMethods = () =>
8+
({
9+
method1: {
10+
method: 'method1',
11+
result: string(),
12+
},
13+
}) as const;
14+
15+
describe('RpcClient', () => {
16+
describe('constructor', () => {
17+
it('should create a new RpcClient', () => {
18+
const client = new RpcClient(getMethods(), vi.fn(), 'test');
19+
expect(client).toBeInstanceOf(RpcClient);
20+
});
21+
});
22+
23+
describe('call', () => {
24+
it('should call a method', async () => {
25+
const client = new RpcClient(getMethods(), vi.fn(), 'test');
26+
const resultP = client.call('method1', 'test');
27+
client.handleResponse('test:1', {
28+
jsonrpc: jsonrpc2,
29+
id: 'test:1',
30+
result: 'test',
31+
});
32+
33+
expect(await resultP).toBe('test');
34+
});
35+
36+
it('should throw an error for error responses', async () => {
37+
const client = new RpcClient(getMethods(), vi.fn(), 'test');
38+
const resultP = client.call('method1', 'test');
39+
client.handleResponse('test:1', {
40+
jsonrpc: jsonrpc2,
41+
id: 'test:1',
42+
error: {
43+
code: -32000,
44+
message: 'test error',
45+
},
46+
});
47+
48+
await expect(resultP).rejects.toThrow('test error');
49+
});
50+
51+
it('should throw an error for invalid results', async () => {
52+
const client = new RpcClient(getMethods(), vi.fn(), 'test');
53+
const resultP = client.call('method1', 'test');
54+
client.handleResponse('test:1', {
55+
jsonrpc: jsonrpc2,
56+
id: 'test:1',
57+
result: 42,
58+
});
59+
await expect(resultP).rejects.toThrow(
60+
'Invalid result: Expected a string, but received: 42',
61+
);
62+
});
63+
64+
it('should throw an error for invalid responses', async () => {
65+
const client = new RpcClient(getMethods(), vi.fn(), 'test');
66+
const resultP = client.call('method1', 'test');
67+
client.handleResponse('test:1', 'invalid');
68+
await expect(resultP).rejects.toThrow('Invalid JSON-RPC response:');
69+
});
70+
});
71+
72+
describe('handleResponse', () => {
73+
it('should log an error if the message id is not found', () => {
74+
const client = new RpcClient(getMethods(), vi.fn(), 'test');
75+
const logError = vi.spyOn(console, 'error');
76+
client.handleResponse('test:1', 'test');
77+
expect(logError).toHaveBeenCalledWith(
78+
'No unresolved message with id "test:1".',
79+
);
80+
});
81+
});
82+
83+
describe('rejectAll', () => {
84+
it('should reject all unresolved messages', async () => {
85+
const client = new RpcClient(getMethods(), vi.fn(), 'test');
86+
const p1 = client.call('method1', 'test');
87+
const p2 = client.call('method1', 'test');
88+
client.rejectAll(new Error('test error'));
89+
await expect(p1).rejects.toThrow('test error');
90+
await expect(p2).rejects.toThrow('test error');
91+
});
92+
});
93+
});

0 commit comments

Comments
 (0)