Skip to content

feat(sdk): add defineHttpAdapter for declarative gateway HTTP adapters#1173

Draft
Mistat wants to merge 6 commits into
mainfrom
feat/http-adapter
Draft

feat(sdk): add defineHttpAdapter for declarative gateway HTTP adapters#1173
Mistat wants to merge 6 commits into
mainfrom
feat/http-adapter

Conversation

@Mistat
Copy link
Copy Markdown
Contributor

@Mistat Mistat commented May 14, 2026

Summary

  • Adds a code-first API (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 new httpAdapter.files glob in defineConfig().
  • At deploy time the SDK bundles each adapter's input / output function into a standalone IIFE JS string (ES2017, no Node imports, no async) and embeds it on the Application proto.
  • Requires the workspace-level feature flag 20260413_platform_filter_router to be enabled before adapter routes (/f/<path>) serve traffic. Until then the gateway returns 404 — documented in docs/services/http-adapter.md.

How to use

1. Register the adapter directory in tailor.config.ts

import { defineConfig } from "@tailor-platform/sdk";

export default defineConfig({
  name: "my-app",
  httpAdapter: {
    files: ["adapters/**/*.ts"],
  },
});

2. Define one adapter per file

The adapter must be the default export, and name must be a string literal matching ^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$.

// adapters/get-user.ts
import { defineHttpAdapter } from "@tailor-platform/sdk";

export default defineHttpAdapter({
  name: "get-user",
  pathPattern: "/users/*",
  methods: ["GET"],
  input: (req) => ({
    query: `query GetUser($id: ID!) { user(id: $id) { id name email } }`,
    variables: { id: req.path.split("/")[2] },
  }),
  output: (resp) => ({
    statusCode: 200,
    headers: { "content-type": "application/json" },
    body: JSON.stringify(resp.data?.user ?? null),
  }),
});
  • input is required: receives { method, path, headers, query, body } and must return { query, variables?, operationName? }.
  • output is 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

  • Literal segments must match exactly: /users/list matches only /users/list.
  • A * in the middle matches exactly one segment: /api/*/users matches /api/v1/users.
  • A trailing * matches all remaining segments: /api/* matches /api/v1/users/123.

4. Runtime constraints (gateway-side Sobek sandbox)

input and output are bundled to JS and executed in the gateway's sandbox. The following are not available:

  • async / await and top-level await (build error)
  • Node built-ins: fs, path, crypto, http, os, process, etc. (build error)
  • fetch, setTimeout, setInterval, and other browser globals

Each bundled script is capped at 256 KB (error) with a warning at 64 KB.

5. Deploy

pnpm tailor deploy bundles the adapters and uploads them with the Application. Requests to https://<domain>/f/<pathPattern> will dispatch to the adapter once the per-workspace feature flag 20260413_platform_filter_router is enabled.

Notes

  • Consumes the platform-side tailor.v1.HttpAdapter proto (renamed from GatewayFilter upstream; tailor-proto regenerated in this PR — see commit 83d0d21). The Application field is http_adapters / fields are input_script / output_script.
  • Priority field 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}.tsdefineHttpAdapter() user API
  • cli/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 loader
  • cli/services/application.ts — wires httpAdapterService into defineApplication() / loadApplication() and runs the bundler
  • cli/commands/deploy/{application,deploy}.ts — populates Application.httpAdapters from bundled adapters and includes them in change-set diff
  • types/http-adapter.ts — function-signature types (input/output), reusing the zinfer-generated config types
  • types/http-adapter.generated.ts — auto-generated by zinfer
  • utils/brand.ts, cli/cache/bundle-cache.ts — new brand kind and bundle-cache kinds
  • types/app-config.tshttpAdapter?: HttpAdapterServiceInput
  • docs/services/http-adapter.md, .changeset/feat-http-adapter.md

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-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 14, 2026

🦋 Changeset detected

Latest commit: 83d0d21

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@tailor-platform/sdk Minor
@tailor-platform/create-sdk Minor

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

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 14, 2026

⚡ pkg.pr.new

@tailor-platform/sdk

pnpm add https://pkg.pr.new/@tailor-platform/sdk@83d0d21
pnpm dlx https://pkg.pr.new/@tailor-platform/sdk@83d0d21 --help

@tailor-platform/create-sdk

pnpm add https://pkg.pr.new/@tailor-platform/create-sdk@83d0d21
pnpm dlx https://pkg.pr.new/@tailor-platform/create-sdk@83d0d21 my-app

commit: 83d0d21

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>
@github-actions

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>
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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/output into 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.

Comment on lines +151 to +162
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;
Comment thread packages/sdk/src/cli/commands/deploy/application.ts Outdated
Comment thread packages/sdk/docs/services/http-adapter.md
Comment thread packages/sdk/src/types/http-adapter.ts
Comment thread packages/sdk/src/cli/services/application.ts
Comment thread packages/sdk/src/cli/services/http-adapter/detector.ts
- 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>
@github-actions

This comment has been minimized.

};

export type HttpAdapterInputResult = {
query: string;
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.

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 {
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.

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
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.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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;
Copy link
Copy Markdown
Contributor

@toiroakr toiroakr May 25, 2026

Choose a reason for hiding this comment

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

binaries is not supported by now?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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")[];
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.

Is it necessary to support OPTIONS or HEAD?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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 HEAD returns the same headers as GET without 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)?$/, "");
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.

(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",
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.

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>
@github-actions
Copy link
Copy Markdown

Code Metrics Report (packages/sdk)

main (a0658d0) #1173 (01222a4) +/-
Coverage 63.6% 63.6% -0.1%
Code to Test Ratio 1:0.4 1:0.4 -0.1
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%)

Files Coverage +/- Status
packages/sdk/src/cli/cache/bundle-cache.ts 100.0% 0.0% modified
packages/sdk/src/cli/commands/deploy/application.ts 72.9% -7.9% modified
packages/sdk/src/cli/commands/deploy/deploy.ts 82.6% +0.0% modified
packages/sdk/src/cli/services/application.ts 82.6% -2.3% modified
packages/sdk/src/cli/services/http-adapter/bundler.ts 87.0% +87.0% added
packages/sdk/src/cli/services/http-adapter/detector.ts 91.1% +91.1% added
packages/sdk/src/cli/services/http-adapter/service.ts 27.4% +27.4% added
packages/sdk/src/configure/services/http-adapter/http-adapter.ts 100.0% +100.0% added
packages/sdk/src/parser/service/http-adapter/index.ts 0.0% 0.0% added
packages/sdk/src/parser/service/http-adapter/schema.ts 100.0% +100.0% added
packages/sdk/src/types/app-config.ts 0.0% 0.0% modified
packages/sdk/src/types/http-adapter.ts 0.0% 0.0% added
packages/sdk/src/utils/brand.ts 100.0% 0.0% modified

SDK Configure Bundle Size

main (a0658d0) #1173 (01222a4) +/-
configure-index-size 18KB 19.17KB 1.17KB
dependency-chunks-size 33.52KB 33.52KB 0KB
total-bundle-size 51.51KB 52.69KB 1.18KB

Runtime Performance

main (a0658d0) #1173 (01222a4) +/-
Generate Median 2,800ms 2,584ms -216ms
Generate Max 2,815ms 2,733ms -82ms
Apply Build Median 2,817ms 2,602ms -215ms
Apply Build Max 2,985ms 2,644ms -341ms

Type Performance (instantiations)

main (a0658d0) #1173 (01222a4) +/-
tailordb-basic 35,130 35,180 50
tailordb-optional 3,841 3,841 0
tailordb-relation 7,428 7,428 0
tailordb-validate 2,566 2,566 0
tailordb-hooks 5,767 5,767 0
tailordb-object 12,136 12,136 0
tailordb-enum 2,462 2,462 0
resolver-basic 9,424 9,424 0
resolver-nested 26,111 26,111 0
resolver-array 18,187 18,187 0
executor-schedule 4,234 4,234 0
executor-webhook 873 873 0
executor-record 8,166 8,166 0
executor-resolver 4,369 4,369 0
executor-operation-function 869 869 0
executor-operation-gql 869 869 0
executor-operation-webhook 888 888 0
executor-operation-workflow 1,714 1,714 0

Reported by octocov

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.

3 participants