文件最后提交记录最后更新时间
5 个月前
2 天前
19 小时前
1 个月前
14 天前
7 个月前
1 个月前
README

@supabase/api

Generated Supabase Management API SDK built directly from the Supabase OpenAPI spec.

The package exposes:

  • @supabase/api for the runtime-specific Promise client helpers plus generated contracts
  • @supabase/api/effect for the Effect-native versioned client plus generated contracts
  • @supabase/api/openapi.json for the raw generated OpenAPI document
  • openApiOperationIdMap for tools that need to join raw OpenAPI operation ids to SDK operation ids

Usage

import { createApiClient } from "@supabase/api";

const client = await createApiClient({ accessToken: "<token>" });

try {
  const projects = await client.v1.listAllProjects();
  const projectConfig = await client.v2.getProjectConfig({ ref: "<project-ref>" });
} finally {
  await client.dispose();
}

Call dispose() when the client is no longer needed so its runtime and client-owned HTTP resources are released.

Operations are namespaced by version, derived from the leading path segment (/v1/... or /v2/...). Same-named operations can coexist under separate namespaces: client.v1.listOrganizationMembers and client.v2.listOrganizationMembers are distinct operations hitting /v1/... and /v2/... respectively.

baseUrl defaults to https://api.supabase.com and accessToken can also come from SUPABASE_ACCESS_TOKEN.

For Effect consumers:

import { Effect } from "effect";
import { makeApiClient } from "@supabase/api/effect";

const program = Effect.gen(function* () {
  const client = yield* makeApiClient({ accessToken: "<token>" });

  const projects = yield* client.v1.listAllProjects();
  const projectConfig = yield* client.v2.getProjectConfig({ ref: "<project-ref>" });

  return { projects, projectConfig };
});

If you want the package to resolve both values from the environment, you can omit the config entirely:

const client = await createApiClient();

Supported environment variables:

  • SUPABASE_API_URL (optional, defaults to https://api.supabase.com)
  • SUPABASE_ACCESS_TOKEN (optional if accessToken is passed explicitly)

The only callable client surface is the versioned namespace:

const projects = await client.v1.listAllProjects();
const projectConfig = await client.v2.getProjectConfig({ ref: "<project-ref>" });

For tools that need the raw generated spec:

import openApiSpec from "@supabase/api/openapi.json";

Binary request bodies

The SDK supports binary request inputs for the Management API routes that use raw eszip bodies or multipart file uploads.

The public binary input contract is:

  • Uint8Array
  • ArrayBuffer
  • Blob

Uint8Array is the canonical byte type. For the full internal contract and encoding rules, see docs/request-body-encoding.md.

Development

Repo-wide quality checks run from the repository root:

pnpm check:all
pnpm fix:all

Package-local checks and development commands run from packages/api:

pnpm types:check
pnpm test            # Run tests
pnpm generate        # Refresh the OpenAPI spec and regenerate the SDK
pnpm generate:check  # Regenerate in place and fail on any resulting diff

Spec pipeline

The spec is built from two upstream OpenAPI documents, {baseUrl}/api/v1-json and {baseUrl}/api/v2-json. They are fetched and merged into a single document (paths and components.schemas are unioned, and info.title is normalized to Supabase API), then overrides from scripts/openapi-overrides.json are applied to the merged document. The result is validated — operation ids must be unique, and version-prefixed operation ids must match the path's leading segment — before being written to src/generated/openapi.json. The merged document keeps only the keys the generator consumes (openapi, info, paths, components.schemas); upstream extras such as servers, tags, and components.securitySchemes are dropped so the snapshot never contains keys a regeneration would remove.

The committed snapshot and the generated modules are also checked against each other offline in ordinary test runs: scripts/generated-output-sync.unit.test.ts re-renders every generated file from the committed snapshot and requires byte equality, and src/generated-contract-sync.unit.test.ts asserts the operation-level bijection. Hand edits to src/generated fail both.

The base URL is resolved in this order:

  1. SUPABASE_API_URL environment variable
  2. scripts/openapi-source.json, a committed sidecar file ({ "baseUrl": ... }) that is rewritten after every successful pnpm generate run
  3. https://api.supabase.com

To refresh from staging instead of production:

SUPABASE_API_URL=https://api.supabase.green pnpm generate

pnpm generate is the single command to regenerate the spec and SDK. pnpm generate:check regenerates in place, formats, and fails if that produces any diff in src/generated or scripts/openapi-source.json — useful for verifying the committed snapshot is still current. If a failed check leaves an unwanted diff, discard it with:

git restore -- src/generated scripts/openapi-source.json

The hourly api-package-sync.yml workflow runs generate against production and opens a PR against develop whenever it detects drift, acting as the automated drift detector for the committed snapshot.

Overrides

scripts/openapi-overrides.json is a JSON-Patch-like array applied to the merged document. It supports:

  • test — assert a value at path before proceeding (as in RFC 6902)
  • add — add a value at path; throws if the key already exists
  • replace — replace the value at path
  • remove — remove the value at path if present

remove is deliberately remove-if-present rather than RFC 6902's strict "must exist" semantics, because the upstream documents differ between environments — staging's v2-json is currently served by two backend variants that disagree about some paths. Entries may carry a $comment field to document why an override exists.

deepObject query parameters

Several v2 list operations declare object-valued query parameters with style: deepObject (page, filter). The client expands each own entry into its own param[key]=value pair, so { page: { size: 100, after: cursor } } reaches the wire as page[size]=100&page[after]=…. Every object-valued query parameter in the spec is deepObject, so the expansion is applied to all of them rather than driven by a per-parameter style recorded in the generated contract.