Skip to content

feat(typespec): add TypeSpec models for Storage, Functions, and PostgREST#53

Draft
grdsdev wants to merge 8 commits into
feat/smithy-modelsfrom
claude/angry-perlman-5baa1c
Draft

feat(typespec): add TypeSpec models for Storage, Functions, and PostgREST#53
grdsdev wants to merge 8 commits into
feat/smithy-modelsfrom
claude/angry-perlman-5baa1c

Conversation

@grdsdev

@grdsdev grdsdev commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds canonical TypeSpec models for Storage, Functions, and PostgREST as a direct parallel to the Smithy models — same APIs, different IDL — to compare the two as source-of-truth candidates for SDK codegen.

  • typespec/storage.tsp — Storage API (buckets, objects, TUS resumable uploads), grouped into interfaces (Buckets, Objects, TusUploads)
  • typespec/functions.tsp — Edge Functions API (one operation per HTTP method on /functions/v1/{functionName})
  • typespec/postgrest.tsp — PostgREST API (table CRUD + RPC), with Record<string> for dynamic query params
  • typespec/openapi/@typespec/openapi3/ — committed generated OpenAPI 3.0 artifacts (no patching needed)

Compile: cd typespec && npm install && npx tsp compile .

Key differences from the Smithy output

Dimension Smithy TypeSpec
Binary content-type (TUS chunk, Functions) application/octet-stream ✓ but format: byte before patching application/octet-stream + format: binary directly via @header contentType
PostgREST row body schema type: string, format: byte (wrong) {} (any JSON value) via unknown
Dynamic query param serialization style: formexplode: true (correct) @query(#{explode: true}) → same correct default
Integer types generic number int32 / int64 with format
Void response codes 200 204 No Content
Schema reuse per-operation wrapper types (GetBucketResponseContent etc.) $ref reuse — no duplicates
Error responses explicit "400" status default catch-all
operationId bare (ListBuckets) namespaced (Buckets_list)
Toolchain Smithy CLI (Java / Gradle) npm install + npx tsp compile

Known limitations (shared with Smithy)

  • Multipart uploads (UploadObject / UpdateObject) — no native multipart support in TypeSpec; Smithy handles this via patch-openapi.py
  • Wildcard paths ({+wildcardPath}) — TypeSpec emits valid OpenAPI (drops the +) but loses greedy-match semantics; Smithy emits invalid OpenAPI (keeps + inside the param name)
  • Realtime — out of scope in both IDLs

grdsdev added 8 commits July 1, 2026 06:40
Adds a Smithy model for the PostgREST HTTP API boundary: From (SELECT),
Insert, Upsert, Update, Delete, and Rpc operations. The model captures
paths, HTTP methods, standard headers (Prefer, Range, Content-Profile,
Accept-Profile), and response headers (Content-Range).

Dynamic query params (filters, column selection, ordering) are captured
via @httpQueryParams Map<String, String> — the query builder in each SDK
populates this map at runtime; Smithy cannot express PostgREST's filter
syntax (id=eq.5, name=like.*foo*) as fixed shapes.

Also ignores the smithy/build/ directory to keep the repo clean.
…REST

Parallel to the Smithy models in smithy/ — same APIs, different IDL — for
comparing the two as source-of-truth candidates for SDK codegen.

Key differences from Smithy output:
- Explicit application/octet-stream content-type on binary bodies (TUS chunk,
  Functions invoke) via @Header contentType, producing format: binary in OpenAPI
  instead of Smithy's format: byte (which required patch-openapi.py to fix)
- PostgREST row bodies use `unknown` (maps to {} in JSON Schema — any JSON
  value) instead of bytes/format: byte, accurately representing user-defined row
  structures
- Dynamic query params use @query(#{explode: true}) so each PostgREST filter
  becomes its own query parameter (?select=*&id=eq.5) rather than a single
  packed value
- Integer fields emit int32/int64 formats; Smithy emits generic number
- Void responses use 204 No Content; Smithy emits 200
- Schemas are reused via $ref; Smithy generates per-operation wrapper types

Compile: cd typespec && npm install && npx tsp compile .
Output:  typespec/openapi/@typespec/openapi3/
- Add named query params: select, order, limit (integer), offset (integer)
  as separate typed members on each table operation instead of collapsing
  everything into one Record<string> params map
- Add columns param to insert, on_conflict param to upsert
- Add filters: Record<string> (explode: true) for dynamic column filters,
  separate from the fixed named params
- Add FilterOperator enum with all 24 PostgREST operators (matches Smithy)
- Split RPC into rpc (POST) + rpcGet (GET) — GET takes args: Record<string>
- Add doc comments to all operations and params
- Regenerate openapi.Supabase.PostgREST.yaml
Add Objects_upload (POST) and Objects_update (PUT) on /{bucketId}/{wildcardPath}
using @multipartBody with cacheControl (optional string) and file (bytes) parts.

TypeSpec's @multipartBody emits correct multipart/form-data with format:binary
natively — no post-generation patching needed, unlike the Smithy pipeline which
required patch-openapi.py to inject these operations manually.
…r bodies

Replace unknown with bytes + explicit @Header contentType: application/octet-stream
on both response models (RowsResponse, InsertResponse) and request bodies.

This makes swift-openapi-generator emit HTTPBody (streaming binary) instead of
OpenAPIValueContainer (any-JSON), matching the Smithy output and letting the SDK
layer decode the raw bytes into whatever Decodable type the caller requested.
Functions: each invoke method (POST/PUT/PATCH/DELETE) now uses @SharedRoute to
express four request body content types in one OpenAPI operation:
  application/json      → OpenAPIValueContainer (any JSON)
  application/octet-stream → HTTPBody (binary)
  text/plain            → String
  application/x-www-form-urlencoded → String

TypeSpec 0.65 @SharedRoute concatenates variant names into the operationId;
patch-openapi.py rewrites them to the canonical short form.

PostgREST: revert request bodies from bytes/octet-stream back to unknown
(→ application/json / OpenAPIValueContainer). PostgREST bodies ARE JSON
(row arrays/objects); only response bodies warrant HTTPBody for raw decoding.
Each invoke method now returns one of three content types depending on what
the function sends back:
  application/json          → any JSON value
  application/octet-stream  → binary
  text/plain                → string

Uses the same @SharedRoute pattern as request bodies: Json/Text/Form variants
return JsonResponse/TextResponse/JsonResponse respectively; Binary returns
OctetStreamResponse. TypeSpec merges them into three response content entries.
…/* content type

Remove @SharedRoute multi-variant approach in favour of a single operation per
HTTP method. A variable contentType header alongside bytes body causes TypeSpec
to emit content-type */* in OpenAPI, which swift-openapi-generator maps to
case any(HTTPBody) - generic enough for JSON, binary, text, event-stream, etc.

This also removes the need for patch-openapi.py entirely.
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.

1 participant