feat(service,features,web): the account's integrations on oRPC
Merged
CH
1d ago+1365+1365+0123456789012345678901234567890123456789-1217-1217-0123456789012345678901234567890123456789
W5 of the oRPC migration. This moves account/integrations and its settle, connect, tools, cancel, disconnect and rename routes onto the hosted oRPC router, server and client together. account/integrations/connected is the OAuth browser callback, a 303 redirect, and stays on its HttpRouter group unchanged. settle was missing from the plan's list; the orchestrator approved including it. HostedIntegrationsClient on main now calls it for a cookie caller.
What changed
- Contract. New
packages/features/src/contract/integrations.ts, registered under theintegrationskey.- The provider routes declare
/api/account/integrations/{provider}/<action>. - The POSTs use
inputStructure: "detailed", because a POST's query (connection,add) is not merged into compact input.tools(a GET) stays compact. provideris read as a plain string, so an unknown provider is still the service's 404not-foundand not a 400.- Every output is its wire schema flipped, except the statuses read and
settle. Those declare the desktop's lenient read schema (integrationStatusesReadSchema, moved here from the client), so a newer service's status or provider still reads on an older desktop, as before.
- The provider routes declare
- Server. New
integrations-procedures.tscarries the old endpoints' logic nearly unchanged.- Caller middleware sits on the resource implementer (
hosted.integrations.use(accountMiddleware(…))), so it runs before input validation. integrations-app.tsnow holds only theconnectedendpoint and what both modules share (seams, sign-in window, Connect's sign-in result).HostedRouterSeamsgainsIntegrationsProcedureSeams, andDEPLOYMENT_SEAMSspreadsintegrationsAppSeams().
- Caller middleware sits on the resource implementer (
- Body limits (shared with W3–W17).
- oRPC's documented
RequestLimitHandlerPlugininorpc/handler.ts, atHOSTED_BODY_CEILING_BYTES(4.5 MB). That is Vercel's own body limit and above the largest current cap (a task Start, about 4.2 MB). The plugin'sPAYLOAD_TOO_LARGEis encoded as 413{"error":"request-too-large"}. - The per-procedure
bodyLimitandexactInput/exactBodyInputhelpers are W4's (#3900), which landed first; this PR uses them as they are. - Rename uses
bodyLimit(2048)andexactInputover a detailed input that still reads its query. That isexactBodyInput's shape plus a typed query, since the account a rename is about is in its query.
- oRPC's documented
- Client.
integration-client.tsasks every call throughaskHosted.unavailablemaps toUNAVAILABLEand everything else toUNANSWERED, as before. ThereadAnswercopy and the five path builders inservice-paths.tsare deleted. - Rewrites. The five provider rows are marked
orpc: true;api-rewrites.jsonandvercel.jsonare regenerated.
Tests
integration-client.test.tspasses unchanged overfakeCloudApi, including the exact query strings and main's new cookie-settle cases.integrations-app.test.ts(30 cases) now drives the oRPC router for every migrated path, and the old group forconnected. The assertions are untouched; only the harness and the request paths changed (/api/account/integrations/slack/connectinstead of the flattened…/connect?provider=slack). The harness now sendscontent-type: application/jsonwith a body.- New tests:
- Refusals byte for byte: 401, 404, 405 and 503, each bare and with no
cache-control. - An unsigned caller sending a malformed body is still answered 401.
- A rename over its cap answers 413, and one with an excess key answers 400.
handler.test.ts: a streamed body past the ceiling with no stated length answers 413.orpc-route.test.ts: a parameterized integrations path goes through the committed table, the dispatcher and the deployed route (401, and 405 on the wrong method).
- Refusals byte for byte: 401, 404, 405 and 503, each bare and with no
models-procedures.test.tsnow buildshostedFetch({ models: modelsProcedures(…) }), following the shared convention.
Known wire differences (accepted for every sibling PR)
- Ordering. The caller is now resolved before the provider is read. An unsigned request to an unknown provider gets 401 where it used to get 404. Signed-in callers see the same answers as before.
- Content-type and non-JSON bodies. A rename body sent without
content-type: application/json, or one that is not JSON at all, is refused 400 before the procedure runs. Every client of ours sends the header. - Chunked bodies. A rename body sent chunked with no
content-lengthis bounded by the handler-wide ceiling, then refused by its schema (400), not 413.
Checks run locally
- Typecheck:
features,service,host,web. - Vitest: the
features,hostandserviceprojects, and thewebproject (all passing). pnpm lint,pnpm knip,pnpm discarded-effect,scripts/repository-checks.sh, and thefunction-rewrites.ts --check.pnpm check:affectedwas not run (it gets OOM-killed in the sandbox); CI runs the whole.
No JSON-schema golden changed.
Made with Luke