Skip to content

refactor(forgekeeper): derive RPC types, type GraphQL responses, and gate tests on events - #94

Merged
pedropombeiro merged 3 commits into
mainfrom
refactor/forgekeeper-types
Oct 10, 2026
Merged

pedropombeiro merged 3 commits into
mainfrom
refactor/forgekeeper-types

Conversation

@pedropombeiro

Copy link
Copy Markdown
Owner

What does this PR do and why?

Follow-ups from the forgekeeper review. No behavior changes, apart from stricter RPC schemas.

  • RPC types: derive input, output, and event types from the JSON Schemas with json-schema-to-ts, a type-only dev dependency, so the schemas are the single description of the contract.
  • GraphQL types: pair each query with the shape of its response data. The transports return unknown, and one adapter applies the shape, which removes the any.
  • Tests: hold ordering, concurrency, and cancellation work on promises the test controls, and wait with until, instead of real timers.

The RPC output and event schemas now set additionalProperties: false, so the host rejects fields the server never sends.

How to set up and validate locally

  1. mise run typecheck && mise run test && mise run lint
  2. mise run build forgekeeper, then point an opencode.json plugin entry at packages/forgekeeper/dist. target, watch, refresh, and release answer under /api/rpc/opencode-forgekeeper/.

Because the commits are refactor and test, release-please opens no release for them.

OpenCode types the values of JSON Schema RPC definitions as `unknown`, so
the server and the CLI each cast them to a hand-written shape that
nothing tied to the schemas.

Derive the input, output, and event types from the schemas with
json-schema-to-ts, a type-only dev dependency that adds nothing to the
bundles. Handlers now receive typed input through one adapter, and the
CLI casts to the derived types.

Describe `statuses` and the snapshots as snapshots, and close the
output and event schemas with additionalProperties, so they match what
the server sends.
GitLab's GraphQL transport returned `Record<string, any>`, and GitHub's
returned `Record<string, unknown>`, so each call site cast the response
to the shape it expected, or relied on `any`.

Pair each query with the shape of the `data` it selects. The transports
return `unknown`, because they can only vouch that the response parsed,
and one adapter applies the query's shape. Call sites then read typed
results without casts, and the `any` is gone.
Tests of ordering, concurrency, and cancellation held work in flight
with real timers: `Bun.sleep(1)` and `setTimeout(1)`, plus a settle
helper that slept five times. They passed because the timers were
usually long enough, not because anything told them the work had
finished.

Hold that work on promises that the test settles, and wait for the
condition each test checks with `until`, which counts turns of the event
loop instead of milliseconds. The ordering test now asserts the whole
order, including the task that arrives during a handoff.

Where a test only needs queued promise reactions to run, `flush` names
that, in place of `Bun.sleep(0)`.
@pedropombeiro
pedropombeiro merged commit d790e5f into main Oct 10, 2026
2 checks passed
@pedropombeiro
pedropombeiro deleted the refactor/forgekeeper-types branch October 10, 2026 14:02
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.

1 participant