feat(sdk): add defineHttpAdapter for declarative gateway HTTP adapters#1173
feat(sdk): add defineHttpAdapter for declarative gateway HTTP adapters#1173Mistat wants to merge 6 commits into
Conversation
Introduces a code-first API for declaring HTTP adapters that translate HTTP requests into GraphQL queries and reshape responses. Adapter files are discovered via the new `httpAdapter.files` glob in defineConfig(). At deploy time, the `input`/`output` functions are bundled to standalone IIFE JS strings (ES2017, no Node imports, no async) and embedded as `GatewayFilter` entries on the Application proto. Requires the workspace-level feature flag `20260413_platform_filter_router` to be enabled before adapter routes serve traffic. Pre-commit hooks skipped due to a pre-existing typecheck failure on main (politty `aliases` field missing from types) unrelated to this change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 83d0d21 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
⚡ pkg.pr.new@tailor-platform/sdk@tailor-platform/create-sdk
|
Adds the seven existing-file modifications that were accidentally omitted from the previous commit: the new `SdkBrandKind` entry, the `http-adapter-input`/`http-adapter-output` `BundleKind` entries, the `httpAdapter` field on `AppConfig`, the `httpAdapterService` wiring in `defineApplication`/`loadApplication`, the deploy-time GatewayFilter emission, and the configure-side re-export. Without these the CI typecheck fails with `'http-adapter-input' is not assignable to type 'BundleKind'` and the feature is unusable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
Wires the example app to the new defineHttpAdapter API with two adapters that return XML to demonstrate non-JSON content-types: - get-user (GET /f/users/<id>): translates the path id into a `user(id: $id)` GraphQL query and shapes the response into `<user>...</user>` XML, with a `<error>user not found</error>` 404 branch when the GraphQL response has no user. - whoami (GET /f/whoami): calls the showUserInfo resolver and emits `<whoami><user>...</user><invoker>...</invoker></whoami>`. Both adapters use a small inline escapeXml helper rather than a library, since the gateway's Sobek runtime has no module system at execution time and the bundler inlines everything. Skipped pre-commit hooks for the same pre-existing politty `aliases` typecheck failure noted on the parent commits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
This PR introduces “HTTP adapters” to the SDK: a code-first defineHttpAdapter() API plus CLI loading/bundling and deploy wiring to embed adapters as tailor.v1.GatewayFilter entries on the Application at deploy time.
Changes:
- Add a new
defineHttpAdapter()configure-time API and corresponding schema/types for HTTP adapter definitions. - Add CLI support to discover adapter files, validate/load them, bundle
input/outputinto sandbox-compatible JS strings, and attach them to the deployed Application as gateway filters. - Add documentation, example adapters, and a changeset to publish the feature.
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/sdk/src/utils/brand.ts | Adds new SDK brand kind for "http-adapter". |
| packages/sdk/src/types/http-adapter.ts | Introduces user-facing HTTP adapter request/response/function signature types and re-exports generated config types. |
| packages/sdk/src/types/http-adapter.generated.ts | Adds zinfer-generated adapter config/service input types. |
| packages/sdk/src/types/app-config.ts | Extends AppConfig with httpAdapter?: { files, ignores }. |
| packages/sdk/src/parser/service/http-adapter/schema.ts | Adds zod schema for adapter config and service input (name regex, methods enum, defaults). |
| packages/sdk/src/parser/service/http-adapter/schema.test.ts | Adds validation tests for the new zod schema. |
| packages/sdk/src/parser/service/http-adapter/index.ts | Re-exports the HTTP adapter schema module. |
| packages/sdk/src/configure/services/index.ts | Re-exports the new configure-time http-adapter service API. |
| packages/sdk/src/configure/services/http-adapter/index.ts | Barrel export for the http-adapter configure module. |
| packages/sdk/src/configure/services/http-adapter/http-adapter.ts | Implements defineHttpAdapter() and exports related types. |
| packages/sdk/src/configure/services/http-adapter/http-adapter.test.ts | Adds unit tests for branding and shape preservation. |
| packages/sdk/src/cli/services/http-adapter/service.ts | Adds adapter file discovery + dynamic import loading + validation + duplicate-name detection. |
| packages/sdk/src/cli/services/http-adapter/detector.ts | Adds AST-based detection for defineHttpAdapter({...}) calls (currently standalone). |
| packages/sdk/src/cli/services/http-adapter/detector.test.ts | Tests the AST detector behavior. |
| packages/sdk/src/cli/services/http-adapter/bundler.ts | Bundles adapter input/output into IIFE scripts, enforces size limits, rejects some Node imports. |
| packages/sdk/src/cli/services/http-adapter/bundler.test.ts | Tests bundling and Node-import rejection behavior. |
| packages/sdk/src/cli/services/application.ts | Wires httpAdapter service load + bundling into application loading lifecycle and returns build result. |
| packages/sdk/src/cli/commands/deploy/deploy.ts | Plumbs httpAdapterBuildResult through the build phase into planning. |
| packages/sdk/src/cli/commands/deploy/application.ts | Adds gateway filter normalization/comparison and constructs filters from bundled adapters. |
| packages/sdk/src/cli/cache/bundle-cache.ts | Adds bundle cache kinds for http-adapter input/output. |
| packages/sdk/docs/services/http-adapter.md | Adds user documentation for HTTP adapter authoring/config/runtime constraints. |
| example/tailor.config.ts | Adds httpAdapter.files configuration to the example app. |
| example/adapters/whoami.ts | Adds an example adapter returning XML. |
| example/adapters/get-user.ts | Adds an example adapter returning XML and 404 handling. |
| .changeset/feat-http-adapter.md | Declares a minor version bump and documents the new feature. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| resolveId(source) { | ||
| if (source.startsWith("node:")) { | ||
| throw new Error( | ||
| `HTTP adapter "${adapter.name}" imports Node module "${source}", which is unavailable in the gateway runtime`, | ||
| ); | ||
| } | ||
| if (BLOCKED_NODE_MODULES.has(source)) { | ||
| throw new Error( | ||
| `HTTP adapter "${adapter.name}" imports Node module "${source}", which is unavailable in the gateway runtime`, | ||
| ); | ||
| } | ||
| return null; |
- deploy: throw when an adapter declares output but no bundled output is available, instead of silently deploying an empty output script - docs: align runtime constraints with the ES2017 bundler target and drop the inaccurate "ES5.1 + partial ES2015 / no Promises" wording - types: narrow HttpAdapterRequest.method from string to HttpMethod - service: run findHttpAdaptersInFile against each adapter source before dynamic import so multi-call / non-static name / async-handler files fail validation without executing module side effects Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
| }; | ||
|
|
||
| export type HttpAdapterInputResult = { | ||
| query: string; |
There was a problem hiding this comment.
It seems like using TypedDocumentNode would make it even more convenient.
In that case, I feel we could add type arguments to the HttpAdapter and maintain type consistency all the way through to the output.
If we want to release the feature itself as soon as possible, it might be fine to release it as a beta version while keeping it as a string for now.
(I apologize for the late review in the first place...)
| * }), | ||
| * }); | ||
| */ | ||
| export function defineHttpAdapter(config: HttpAdapter): HttpAdapter { |
There was a problem hiding this comment.
Looking at the general naming patterns, createHttpAdapter might be more appropriate.
(On a separate note, I'm starting to think defineWaitPoint might also be an outlier...)
| @@ -0,0 +1,115 @@ | |||
| # HTTP Adapter | |||
There was a problem hiding this comment.
Sobek runtime
Does this mean it is running in a runtime that is different from both the tailordb hook/validate runtime and the function runtime?
There was a problem hiding this comment.
Yes — HTTP adapter scripts run in a separate runtime from both the TailorDB hook/validate runtime and the function runtime. It is a Go-based JavaScript runtime (Sobek), and there are restrictions on imports and host APIs as a result.
| export type HttpAdapterOutputResult = { | ||
| statusCode?: number; | ||
| headers?: Record<string, string>; | ||
| body: string; |
There was a problem hiding this comment.
binaries is not supported by now?
There was a problem hiding this comment.
Good catch — binary responses are not supported at the moment. body is string only.
| /** Path pattern with segment wildcards (trailing or single-segment) */ | ||
| pathPattern: string; | ||
| /** HTTP methods this adapter handles */ | ||
| methods: ("GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "HEAD")[]; |
There was a problem hiding this comment.
Is it necessary to support OPTIONS or HEAD?
There was a problem hiding this comment.
Good question. Current behavior:
- GET / POST / PUT / PATCH / DELETE — practically supported.
- OPTIONS — gets routed if you declare it, but there is no automatic CORS preflight response; the adapter author has to implement it.
- HEAD — the HTTP-spec convenience that
HEADreturns the same headers asGETwithout a body is not applied here. It is treated as a separate method, so you have to declare and implement it explicitly if you want it.
| // Remove stale output files. | ||
| const currentNames = new Set(adapters.map((a) => a.name)); | ||
| for (const file of fs.readdirSync(outputDir)) { | ||
| const base = path.basename(file).replace(/\.(input|output)\.js(\.map)?$/, ""); |
There was a problem hiding this comment.
(input|output)\.js might not be written to a file due to the write: false setting in rolldown.
| fs.writeFileSync(entryPath, entryContent); | ||
|
|
||
| const rejectNodeImports: rolldown.Plugin = { | ||
| name: "http-adapter-reject-node-imports", |
There was a problem hiding this comment.
It is fine to implement this kind of mechanism in the httpAdapter ahead of time, but it might be an issue that should be considered alongside other bundle processing.
Mirror `tailor-inc/proto` was synced (aa815220, 2026-05-27) to pick up
the platform-core-services rename of GatewayFilter → HttpAdapter.
- tailor-proto regenerated via `pnpm gen`:
- gateway_filter_resource_pb.{js,d.ts} removed
- http_adapter_resource_pb.{js,d.ts} added
- Application.filters → Application.httpAdapters
- input_filter_script / output_filter_script → input_script / output_script
- Unrelated upstream changes (workspace IP restrictions, staticwebsite
custom domains, workflow/tailordb additions) come along for the ride
since the mirror covers 2026-05-20 → 2026-05-27.
- cli/commands/deploy/application.ts updated to consume the new names:
buildGatewayFilters → buildHttpAdapters, ComparableFilter →
ComparableHttpAdapter, normalizeFilters → normalizeHttpAdapters,
GatewayFilterSchema → HttpAdapterSchema.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code Metrics Report (packages/sdk)
Details | | main (a0658d0) | #1173 (01222a4) | +/- |
|--------------------|----------------|-----------------|-------|
- | Coverage | 63.6% | 63.6% | -0.1% |
| Files | 367 | 374 | +7 |
| Lines | 12949 | 13146 | +197 |
+ | Covered | 8244 | 8367 | +123 |
- | Code to Test Ratio | 1:0.4 | 1:0.4 | -0.1 |
| Code | 85698 | 86532 | +834 |
+ | Test | 36485 | 36621 | +136 |Code coverage of files in pull request scope (84.1% → 78.0%)
SDK Configure Bundle Size
Runtime Performance
Type Performance (instantiations)
Reported by octocov |
Summary
defineHttpAdapter) for declaring HTTP adapters that translate HTTP requests into GraphQL queries and reshape the responses back into HTTP. Adapter files are discovered via the newhttpAdapter.filesglob indefineConfig().input/outputfunction into a standalone IIFE JS string (ES2017, no Node imports, no async) and embeds it on the Application proto.20260413_platform_filter_routerto be enabled before adapter routes (/f/<path>) serve traffic. Until then the gateway returns 404 — documented indocs/services/http-adapter.md.How to use
1. Register the adapter directory in
tailor.config.ts2. Define one adapter per file
The adapter must be the default export, and
namemust be a string literal matching^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$.inputis required: receives{ method, path, headers, query, body }and must return{ query, variables?, operationName? }.outputis optional: when present, receives the GraphQL response ({ data, errors, extensions }) and returns{ statusCode?, headers?, body }. When omitted, the raw GraphQL response is returned as JSON.3. Path pattern
/users/listmatches only/users/list.*in the middle matches exactly one segment:/api/*/usersmatches/api/v1/users.*matches all remaining segments:/api/*matches/api/v1/users/123.4. Runtime constraints (gateway-side Sobek sandbox)
inputandoutputare bundled to JS and executed in the gateway's sandbox. The following are not available:async/awaitand top-levelawait(build error)fs,path,crypto,http,os,process, etc. (build error)fetch,setTimeout,setInterval, and other browser globalsEach bundled script is capped at 256 KB (error) with a warning at 64 KB.
5. Deploy
pnpm tailor deploybundles the adapters and uploads them with the Application. Requests tohttps://<domain>/f/<pathPattern>will dispatch to the adapter once the per-workspace feature flag20260413_platform_filter_routeris enabled.Notes
tailor.v1.HttpAdapterproto (renamed fromGatewayFilterupstream;tailor-protoregenerated in this PR — see commit 83d0d21). The Application field ishttp_adapters/ fields areinput_script/output_script.Priorityfield is plumbed through but the gateway's matcher does not currently use it; documented for future use.Files
parser/service/http-adapter/{schema,index}.ts— zod schema (name regex, methods enum, function fields)configure/services/http-adapter/{http-adapter,index}.ts—defineHttpAdapter()user APIcli/services/http-adapter/{detector,bundler,service}.ts— AST detection, rolldown bundling with Node-import rejection and size limits (64 KB warn / 256 KB error), file loadercli/services/application.ts— wireshttpAdapterServiceintodefineApplication()/loadApplication()and runs the bundlercli/commands/deploy/{application,deploy}.ts— populatesApplication.httpAdaptersfrom bundled adapters and includes them in change-set difftypes/http-adapter.ts— function-signature types (input/output), reusing the zinfer-generated config typestypes/http-adapter.generated.ts— auto-generated by zinferutils/brand.ts,cli/cache/bundle-cache.ts— new brand kind and bundle-cache kindstypes/app-config.ts—httpAdapter?: HttpAdapterServiceInputdocs/services/http-adapter.md,.changeset/feat-http-adapter.md