feat(features,service,web): automations on the hosted oRPC router
W4 of the oRPC migration plan: moves automations, automations/automation, automations/run and automations/runs onto the hosted oRPC router, server and client together. automations/webhook stays exactly as it was: its route, its segment rewrite and its address, which the client still only displays.
What changed
- Contract (
packages/features/src/contract/automations.ts): seven procedures on today's public paths:list,create(201),read,change,remove,runandruns. Each output is its wire schema flipped.- JSON bodies use
exactInput, and path params plus a body useexactBodyInputwithinputStructure: "detailed". Both are added tocontract/base.tswith the shared names. They refuse an excess key, as the oldreadEitherdid, and the existing "body names a key it should not" test still holds. - The list's query stays lenient, as it was.
runusesoutputStructure: "detailed"to keep answering 201 for a new run and 200 for a run its key already made.
- JSON bodies use
- Server (
automations-procedures.ts, which replacesautomations-app.ts):- The handler bodies are the old ones.
accountMiddlewareandbodyLimit(MAXIMUM_BODY_BYTES)sit on the resource (hosted.automations.use(…)), so they run before input validation, in the old order. An unsigned caller gets 401 and an oversized body gets 413, whatever they sent.runadds its own tighterbodyLimit.bodyLimit(maxBytes)is added toorpc/base.tswith the shared name and signature, ahead of W5.- A path id that is not a UUID is still
not-found. - The old
HttpRoutergroup and its package export are deleted.
- Web: the four route files now
export default orpcRoute. The three parameterizedSEGMENT_REWRITESrows are markedorpc: true, andapi-rewrites.jsonandvercel.jsonwere regenerated.HostedRouterSeamsis now an intersection, andDEPLOYMENT_SEAMSgainseveOrigin. - Client (
automation-client.ts): every call goes throughaskHosted(hostedClient.automations.*).HostedRefusedandHostedUnansweredmap to the sameAUTOMATION_CALL_FAILUREs,unreachedincluded. The public interface and the callers inpackages/hostare unchanged. TheaccountCallplumbing,readAnswerandjsonOfare deleted.
Tests
automations-app.test.tsbecomesautomations-procedures.test.tsand runs throughhostedFetch({ automations: automationsProcedures(seams) }).- Its assertions are unchanged. The only fixture change is that the installations list grows from 16 to 20 for one more test.
- Its request helper now sends
content-length, which the platform hands every function.
- New test: an unsigned caller with a malformed body gets 401
invalid-token; a list filter named twice gets 400invalid-request; an oversized body gets 413request-too-large. apps/web/tests/orpc-route.test.ts:/api/automations/{id}/runsandPATCH /api/automations/{id}go end to end through the committed rewrite table, the dispatcher andorpcRoute. An unsigned call answers the old bytes:{"error":"invalid-token"},application/json, and nocache-control.automation-client.test.tspasses unchanged overfakeCloudApi.models-procedures.test.tsnow goes throughhostedFetch({ models: modelsProcedures(…) }), the shared convention.automation-webhook-app.test.tsmakes its automations through the new procedures.
Accepted gaps (shared across W3–W17)
-
A body that is not JSON at all, or one sent without
content-type: application/json, is refused 400 before any procedure runs, even for an unsigned caller. Every client of ours sends JSON. -
The body is checked against its schema before the handler looks the automation up. A malformed
PATCHorrunon an automation the caller cannot reach now gets 400, where it used to get 404. -
bodyLimitreads onlyContent-Length. Until W5 adds the handler-levelRequestLimitHandlerPlugin, a chunked body with no declared length is read whole before validation.
Checks run locally
Typecheck passes for features, service, host and web. Vitest passes for the touched suites and the whole features suite. pnpm lint (oxlint), pnpm knip, pnpm discarded-effect, scripts/repository-checks.sh and function-rewrites.ts --check all pass.
Made with Luke