Skip to content

feat(openrpc): JSON-RPC 2.0 handler, link, and OpenRPC generator - #2351

Open
dinwwwh wants to merge 8 commits into
mainfrom
claude/openrpc-no-streaming-ao11pp
Open

dinwwwh wants to merge 8 commits into
mainfrom
claude/openrpc-no-streaming-ao11pp

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Adds @orpc/openrpc. It serves an oRPC router as a JSON-RPC 2.0 API over HTTP and WebSocket, and describes it in an OpenRPC 1.3 document. Streaming, files, and server-sent notifications are out of scope.

Refs #541

Server

OpenRPCHandler for fetch, node, fastify, aws-lambda, and websocket.

  • Methods and params: each procedure is a method named after its path joined by ., such as planet.find. An object input is sent as by-name params, any other input as [x]. Empty params (absent, [], or {}) become undefined or {}, whichever the input schema accepts.
  • Batches and notifications: a batch runs its calls concurrently and is limited by maxBatchSize (default 100). Notifications run but are never answered.
  • Errors: the protocol errors -32700, -32600, -32601 and -32603. An ORPCError is mapped to a JSON-RPC code through errorCodeMap (default COMMON_JSON_RPC_ERROR_CODE_MAP, where BAD_REQUEST is -32602), and its data carries { code, defined, data } so clients can restore it.
  • How it is built: StandardOpenRPCHandler extends StandardHandler and does the JSON-RPC wire handling itself, with no envelope plugin.
    • Routing interceptors, from plugins or the option, run once per HTTP request or WebSocket message. CORS, request limit, compression and response headers therefore apply to the HTTP request, and a routing interceptor can answer the whole request (for example an auth guard).
    • Each call then runs through core StandardHandler as its own sub-request, with its own tracing span, interceptors and clientInterceptors. getJsonRpcCall(request) reads the call.
  • Content type: requests must be application/json (or application/json-rpc); anything else gets 415, which also protects against CSRF.
  • WebSocket:
    • Messages go through handleMessage(): each message is a request or a batch, answered on the same socket. When the socket closes, its in-flight calls are aborted.
    • When a message is refused as a whole, each of its calls gets the error under its own id, so clients that match responses by id are always answered.
    • It works through .upgrade(), or .message()/.close() for Bun, Cloudflare Hibernation, uWebSockets and crossws.

Client

OpenRPCLink for fetch and websocket.

  • It calls any JSON-RPC 2.0 server and restores ORPCErrors, typesafe errors included.
  • Request ids come from SequentialIdGenerator: base-36 strings of a BigInt counter, as in @standard-server/peer.
  • JSON-RPC errors arrive with status 200, so the error's cause gets the HTTP status of its error code. That lets the Retry plugin retry only server errors.
  • The WebSocket transport matches responses to requests by id, and connects again on the next call after the socket closes.

OpenRPC document

  • OpenRPCGenerator:
    • Params (by-name or by-position), results, and errors grouped by JSON-RPC code.
    • Shared components, examples, and a per-method spec override.
    • filter and errorCodeMap options.
    • Procedures with event iterators are skipped.
  • openrpc() meta: summary, description, tags, examples and spec. It falls back to the openapi() meta.
  • The handler's discovery: { spec, specPath } option: serves the document through rpc.discover and GET /openrpc.json. If spec throws an ORPCError, both answer with that error.

Change to @orpc/openapi

OpenAPIComponentRegistry is now exported, and it accepts any document with components.schemas (OpenAPIComponentRegistryDocument), so the OpenRPC generator can reuse it. OpenAPI's behavior is unchanged.

Docs

New pages under /docs/openrpc: overview, handler, link, adapters, and specification.

  • Wiring: the API reference, the comparison table, the landing-page features, the README tables, and skills/orpc/SKILL.md.
  • Adapters overview: now says HTTP and WebSocket adapters also serve OpenRPC.

Known limitations

  • Transport: no streaming (event iterators), files, or server-to-client notifications.
  • Logging in batches: logging plugins such as Pino log once per HTTP request, so the calls of a batch share one request log.
  • rpc.discover: it runs only inside routing interceptors, with no per-call span or interceptors.
  • WebSocket link:
    • It has no reconnect/connectOnInit options.
    • Aborting a call stops waiting for it on the client, but JSON-RPC cannot cancel it on the server.

Testing

  • Unit tests sit next to each module.
  • Generator scenarios are in packages/openrpc/tests, mirroring the OpenAPI suite.
  • End-to-end tests in tests/openrpc run over Hono/fetch, Node HTTP and WebSocket, plus a test that mounts 13 real handler plugins on OpenRPCHandler.

These all pass locally:

  • pnpm vitest run packages/openrpc packages/openapi tests/openrpc tests/plugins
  • pnpm lint
  • pnpm type:check
  • pnpm docs:validate
  • pnpm exec sherif

🤖 Generated with Claude Code

https://claude.ai/code/session_01ETTyGHAmagRW7GK4kevsX5

Add @orpc/openrpc, which serves oRPC routers as a JSON-RPC 2.0 API over
HTTP and WebSocket and describes them in OpenRPC documents. Streaming,
files, and server-sent notifications are not supported.

- OpenRPCHandler (fetch, node, fastify, aws-lambda, websocket): single
  calls, batches, and notifications, with the JSON-RPC protocol errors
  and ORPCErrors mapped to JSON-RPC error codes (errorCodeMap)
- OpenRPCLink (fetch, websocket): calls any JSON-RPC 2.0 server and
  restores ORPCErrors, typesafe errors included
- OpenRPCGenerator and openrpc() meta: OpenRPC 1.3 documents with
  by-name or by-position params, results, errors, components, examples,
  and a spec override, falling back to openapi() meta
- OpenRPCDiscoveryHandlerPlugin: rpc.discover and a GET document endpoint
- export OpenAPIComponentRegistry from @orpc/openapi for reuse
- docs, including an OpenRPC adapters page, README tables, and tests
  mirroring the OpenAPI suite

Refs #541

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ETTyGHAmagRW7GK4kevsX5
- Add StandardOpenRPCHandler, so the five adapters share the codec and envelope wiring
- Work out once per procedure whether its input accepts undefined or {} for empty params
- Share the result-or-error check between the envelope and isJsonRpcResponse
- Name the protocol codes in COMMON_JSON_RPC_ERROR_CODE_MAP, and drop a dead fallback entry
- Convert the output schema and read the openrpc() meta once per method
- Drop the unused @standard-server/fetch and @orpc/zod dependencies

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ETTyGHAmagRW7GK4kevsX5
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
orpc 7a92315 Commit Preview URL

Branch Preview URL
Oct 31 2026, 02:48 AM

@pkg-pr-new

pkg-pr-new Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
More templates

@orpc/ai-sdk

npm i https://pkg.pr.new/middleapi/orpc/@orpc/ai-sdk@2351

@orpc/arktype

npm i https://pkg.pr.new/middleapi/orpc/@orpc/arktype@2351

@orpc/bun

npm i https://pkg.pr.new/middleapi/orpc/@orpc/bun@2351

@orpc/client

npm i https://pkg.pr.new/middleapi/orpc/@orpc/client@2351

@orpc/cloudflare

npm i https://pkg.pr.new/middleapi/orpc/@orpc/cloudflare@2351

@orpc/contract

npm i https://pkg.pr.new/middleapi/orpc/@orpc/contract@2351

@orpc/effect

npm i https://pkg.pr.new/middleapi/orpc/@orpc/effect@2351

@orpc/evlog

npm i https://pkg.pr.new/middleapi/orpc/@orpc/evlog@2351

@orpc/hibernation

npm i https://pkg.pr.new/middleapi/orpc/@orpc/hibernation@2351

@orpc/json-schema

npm i https://pkg.pr.new/middleapi/orpc/@orpc/json-schema@2351

@orpc/experimental-lock

npm i https://pkg.pr.new/middleapi/orpc/@orpc/experimental-lock@2351

@orpc/mcp

npm i https://pkg.pr.new/middleapi/orpc/@orpc/mcp@2351

@orpc/msw

npm i https://pkg.pr.new/middleapi/orpc/@orpc/msw@2351

@orpc/nest

npm i https://pkg.pr.new/middleapi/orpc/@orpc/nest@2351

@orpc/next

npm i https://pkg.pr.new/middleapi/orpc/@orpc/next@2351

@orpc/node

npm i https://pkg.pr.new/middleapi/orpc/@orpc/node@2351

@orpc/openapi

npm i https://pkg.pr.new/middleapi/orpc/@orpc/openapi@2351

@orpc/openrpc

npm i https://pkg.pr.new/middleapi/orpc/@orpc/openrpc@2351

@orpc/opentelemetry

npm i https://pkg.pr.new/middleapi/orpc/@orpc/opentelemetry@2351

@orpc/pinia-colada

npm i https://pkg.pr.new/middleapi/orpc/@orpc/pinia-colada@2351

@orpc/pino

npm i https://pkg.pr.new/middleapi/orpc/@orpc/pino@2351

@orpc/publisher

npm i https://pkg.pr.new/middleapi/orpc/@orpc/publisher@2351

@orpc/ratelimit

npm i https://pkg.pr.new/middleapi/orpc/@orpc/ratelimit@2351

@orpc/server

npm i https://pkg.pr.new/middleapi/orpc/@orpc/server@2351

@orpc/shared

npm i https://pkg.pr.new/middleapi/orpc/@orpc/shared@2351

@orpc/swr

npm i https://pkg.pr.new/middleapi/orpc/@orpc/swr@2351

@orpc/tanstack-query

npm i https://pkg.pr.new/middleapi/orpc/@orpc/tanstack-query@2351

@orpc/trpc

npm i https://pkg.pr.new/middleapi/orpc/@orpc/trpc@2351

@orpc/valibot

npm i https://pkg.pr.new/middleapi/orpc/@orpc/valibot@2351

@orpc/zod

npm i https://pkg.pr.new/middleapi/orpc/@orpc/zod@2351

commit: 7a92315

@codspeed

codspeed Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

1 warning

⚠️ 5 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

✅ 28 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing claude/openrpc-no-streaming-ao11pp (7a92315) with main (cf05b51)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@codecov

codecov Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.77324% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
packages/openrpc/src/openrpc-generator.ts 98.94% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — two minor suggestions inline.

Reviewed changes

  • New package @orpc/openrpc: JSON-RPC 2.0 server handlers (fetch, node, fastify, aws-lambda, websocket), client links (fetch, websocket), an OpenRPC 1.3 generator, openrpc() metadata, and an rpc.discover plugin, all built on the existing StandardHandler / StandardLink.
  • JSON-RPC envelope plugin: parses single, batch, and notification requests, runs each call as its own sub-request (so interceptors/tracing/rate limiting run once per call), and maps protocol codes plus ORPCErrors via errorCodeMap.
  • @orpc/openapi change: OpenAPIComponentRegistry is now exported and accepts any document with components.schemas (OpenAPIComponentRegistryDocument), letting the generator reuse it; OpenAPI behavior is unchanged.
  • Docs/wiring: new /docs/openrpc pages plus README, comparison-table, landing-page, and skill updates.

The JSON-RPC conformance path (request validation, batches, notifications, id handling, error codes) and the WebSocket client transport were checked against the spec and the existing tests and look correct. Both findings below are edge cases, not blockers.

Pullfrog  | Fix it ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@pullfrog

pullfrog Bot commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

Two minor, non-blocking findings to accompany the review above (the inline anchors could not be attached — tooling limited this run to a single review).

1. OpenRPCGenerator: example params reference a parameter the method does not declare — packages/openrpc/src/openrpc-generator.ts:189-191

Methods that declare no params (no .input(), or an unconstrained input such as z.any()/z.void()) get params: [], but an example that supplies input is still emitted as a by-position param, because this branch is selected whenever paramStructure !== 'by-name' — which also covers the no-params case. The example then references an input Content Descriptor that method.params does not contain.

Trigger: oc.meta(openrpc({ examples: [{ name: 'e', input: 1 }] })), or oc.input(z.any()).meta(openrpc({ examples: [{ name: 'e', input: { a: 1 } }] })) → method params: [], but examples: [{ name: 'e', params: [{ name: 'input', value: 1 }] }].

Suggested fix: only emit example params the method declares. The existing example.input === undefined guard already handles the omitted-input case; the no-params case should also emit params: [].

2. WebSocket handler: a Blob message queued through .upgrade() before close is not aborted — packages/openrpc/src/adapters/websocket/openrpc-handler.ts:44-48, with close() at :77-79 and upgrade() at :92-95

The per-socket AbortController is created lazily in message(), while close() aborts and deletes it. upgrade()'s listener awaits loadBytes for Blob frames before calling message(); if the socket closes during that await, close() finds no controller, and the deferred message() installs a fresh, never-aborted one. The procedure then runs against a non-aborted signal and its response is written to a closed socket, contradicting the documented "when the socket closes, the signal of its in-flight calls is aborted".

Trigger: a binary JSON-RPC frame via .upgrade() where the socket closes before the Blob finishes loading (text frames are unaffected — the deferred callback runs in a microtask before the close macrotask). Suggested fix: mark the socket as closed (or retain its controller across close()) so a call starting after the close is aborted or skipped.

For reference, the rest of the implementation checks out: the JSON-RPC request/batch/notification/id handling, the error-code mapping, the WebSocket client transport, and the generator's params/result/error/component shapes all match the spec and the tests.

Pullfrog  | View workflow run | via Pullfrog | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Request ids are now base-36 strings of a BigInt counter, like @standard-server/peer,
so they never lose precision after Number.MAX_SAFE_INTEGER calls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ETTyGHAmagRW7GK4kevsX5

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

Re-reviewed the delta since the prior pullfrog review (41b08e8): one commit (5106de1) that changes how the client link generates JSON-RPC request ids.

  • Base-36 string request ids: OpenRPCLinkCodec now draws ids from a per-codec SequentialIdGenerator (base-36 of a BigInt counter) instead of an incrementing JS number, so they stay unique past Number.MAX_SAFE_INTEGER. JsonRpcId already accepts strings, the handler echoes the id verbatim, and the WebSocket transport matches responses by the same string key — the wire-format change is internal and spec-compliant.
  • Tests and docs aligned: the fetch, websocket, and standard link-codec tests and the link.mdx example now use string ids; the response-matching test now rejects a numeric id when a string id was sent (correct per the spec's "same value" rule while the server still accepts both).

No open pullfrog threads were present to retire (the prior review's inline notes never landed as GitHub comments), and nothing in this commit touches the two pre-existing edge cases carried forward in the PR summary.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

…, not a plugin

Routing interceptors now run once per HTTP request or WebSocket message, and each call
still runs through StandardHandler as its own sub-request, with its own tracing span,
interceptors, and client interceptors. This replaces OpenRPCEnvelopeHandlerPlugin and its
hard-coded list of plugin names, so plugins without ordering hints, and the
routingInterceptors option, no longer run once per call.

- A routing interceptor can answer or reject the whole request
- Discovery is the handler's discovery option instead of OpenRPCDiscoveryHandlerPlugin
- WebSocket messages go through handleMessage instead of a synthetic POST request,
  which also answers plugin errors while reading a message

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ETTyGHAmagRW7GK4kevsX5

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

Re-reviewed the delta since the prior pullfrog review (5106de1): one commit (276c48c) that moves the JSON-RPC wire handling into the handler and folds discovery into a handler option.

  • Wire handling moved into StandardOpenRPCHandler: the ~openrpc envelope plugin is gone; the handler now overrides StandardHandler.handle, owns the prefix/method dispatch, per-call sub-request creation, batch/notification/maxBatchSize semantics, header merging, and the ORPCError → reject mapping. It manually runs the plugin init chain in its constructor and passes only interceptors/clientInterceptors to super, so routing interceptors run once per HTTP request/message and each call still gets its own StandardHandler pass (span, interceptors, client interceptors).
  • Discovery via option: OpenRPCDiscoveryHandlerPlugin and the @orpc/openrpc/plugins export are removed; discovery: { spec, specPath? } now answers rpc.discover and GET {prefix}/openrpc.json. spec can throw ORPCError to refuse (GET returns the error's status; rpc.discover returns a JSON-RPC error at 200).
  • WebSocket handler: .message() now calls handleMessage (pre-parsed message) instead of .handle() with a marked request; behaviour of the per-socket abort controller is unchanged.
  • Tests/docs: unit tests were reorganized (codec suite, new discovery suite, reworked handler suite using os.lazy to exercise the single-vs-batch rethrow split), and the all-plugins OpenRPC coverage moved to tests/openrpc/plugins.test.ts. Docs/README now describe the routing-intercepting layers instead of the old plugin.

I verified the full delta against StandardHandler, the plugin init/route contracts (rethrow, request-limit, cors, response-headers, ratelimit), and the WebSocket/fetch/node adapters, then ran pnpm vitest run packages/openrpc/src/adapters/standard tests/openrpc (198 passed). The context/headers/prefix propagation into per-call sub-requests checks out, and the rethrow routing-interceptor semantics (single call rethrows, batch rejects after all calls run) follow from the new flow.

The two pre-existing minor edge cases carried forward in the summary (generator params: [] example; WebSocket lazy-AbortController race on a queued Blob frame) live in lines this commit did not touch, so they are not re-raised here.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

A WebSocket client matches responses by id, so when a routing interceptor answers,
declines, or a plugin rejects a whole message, each of its calls now gets the error
under its own id instead of a single null-id error or no reply.

- rpc.discover rethrows unexpected spec errors, like any other call
- Request-level plugins sort around core's tracing plugin, as in StandardHandler,
  so CORS answers preflights before logging plugins see them
- Document that rpc.discover only runs inside routing interceptors

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ETTyGHAmagRW7GK4kevsX5

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — one minor note inline.

Reviewed changes

Re-reviewed the delta since the prior pullfrog review (276c48c): one commit (9f53db8) that makes a refused WebSocket message answer each of its calls.

  • Per-call refusal over WebSocket: handleMessage now answers each call of a message a routing interceptor answers or declines (or a plugin rejects) with the error under its own id, via the new answerEachCall, instead of a single null-id error or no reply.
  • rpc.discover rethrows unexpected spec errors: discover no longer wraps non-ORPCErrors with toORPCError; a single rpc.discover propagates them, while a batch still answers -32603 and reports an unhandled rejection.
  • Plugin ordering around tracing: the constructor adds a ~tracing placeholder to sortPlugins, so plugins ordered before/after core's tracing plugin keep their relative order (for example CORS answers preflights before Pino sees them).
  • Docs and tests: handler.mdx and the JSDoc document the per-call WebSocket refusal and that rpc.discover only runs inside routing interceptors; tests cover the refusal paths, the rpc.discover rethrow, and the CORS/Pino ordering.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

}
catch (error) {
if (!(error instanceof ORPCError)) {
throw error

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rethrowing unexpected spec errors makes a single rpc.discover hang over WebSocket: handleMessage → dispatch → call rethrows for a non-batch message, so no response is ever sent and the client waits until the socket closes (plus an unhandled rejection), whereas a normal procedure's unexpected error is answered with -32603. The HTTP path is covered by the new test, but there is no WebSocket equivalent.

Technical details
# `rpc.discover` with an unexpected `spec` error never answers over WebSocket

## Affected sites
- `packages/openrpc/src/adapters/standard/openrpc-handler.ts:335-337` — `discover` rethrows a non-`ORPCError` instead of encoding it.
- `packages/openrpc/src/adapters/standard/openrpc-handler.ts:289-302` — `call` rethrows when `batch` is false, so the throw escapes `dispatch`.
- `packages/openrpc/src/adapters/standard/openrpc-handler.ts:157-190` — `handleMessage` has no catch around `intercept`, so it rejects and `OpenRPCHandler.message()` never sends a reply.
- `packages/openrpc/src/adapters/websocket/transport.ts:43-63` — a pending client call settles only on a matching `id` response or socket close (no timeout), so it hangs.

## Required outcome
- A single `rpc.discover` over WebSocket whose `spec` throws a non-`ORPCError` should still produce a reply (for example `-32603` under the call's `id`), matching how `StandardHandler` turns an unexpected procedure error into an error response rather than throwing.

## Open questions for the human
- Is rethrowing intended for the WebSocket path, or only for HTTP (where a 500 surfaces the bug)? If intended, a WS caller of `rpc.discover` has no way to observe the failure.

- Refuse each call of a WebSocket message by the final answer (not matched, or a
  status of 400 or more), so a routing interceptor that refuses after next() is
  answered per call too, without a dispatched flag or a reject callback
- Encode errors with the codec's encodeError instead of a second serializer and error code map
- Resolve the discovery spec once for rpc.discover and the GET endpoint
- Build a call's sub-request only when it runs through StandardHandler
- Move the discovery tests into the handler tests, and drop duplicated tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ETTyGHAmagRW7GK4kevsX5

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues in this delta. The refactor is behavior-preserving apart from one WebSocket fix, and the 188 affected tests plus the package type-check pass locally. One prior minor thread (the rpc.discover-over-WebSocket hang) remains open and is not addressed by these commits.

Reviewed changes

Re-reviewed the delta since the prior pullfrog review (9f53db8): one commit (dc2a367, "refactor(openrpc): simplify StandardOpenRPCHandler").

  • Handler delegates JSON-RPC error encoding to the codec: StandardOpenRPCHandler drops its own serializer/errorCodeMap fields; reject and discover now call openrpcCodec.encodeError, so error encoding lives in one place. encodeError's return type is narrowed to carry body.error: JsonRpcErrorObject, and protocolError is narrowed to the error variant of JsonRpcResponse.
  • WebSocket refusal logic in handleMessage simplified: the dispatched flag and the reject override threaded into dispatch are gone. handleMessage inspects the routing result directly — unmatched → -32601 per call, status >= 400 → per-call error, status < 400 → the result as-is. This also covers the previously unhandled case where a routing interceptor calls next() and then returns an error response (new test).
  • Discovery split into a shared resolveSpec: discover (POST) and serveSpec (GET) now share one helper that returns the document or the ORPCError spec threw. The standalone discovery test file was folded into openrpc-handler.test.ts.
  • Test adjustments: the link-codec sequential-id test no longer re-asserts the base-36 format (covered by @orpc/shared's id.test.ts), the discovery batch-rethrow case folds into the single-call rethrow test, and the ambiguous request-limit-413 plugin test is removed.

Pullfrog  | Fix it ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

…ages

- Add build.config.ts and run obuild on prepack
- Annotate COMMON_JSON_RPC_ERROR_CODE_MAP for isolatedDeclarations
- Mark side-effect-free module-level calls as pure

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ETTyGHAmagRW7GK4kevsX5

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants