Skip to content

fix(storage): document 200 responses for vector bucket CRUD endpoints - #1270

Open
grdsdev wants to merge 12 commits into
masterfrom
feat/vector-bucket-openapi
Open

fix(storage): document 200 responses for vector bucket CRUD endpoints#1270
grdsdev wants to merge 12 commits into
masterfrom
feat/vector-bucket-openapi

Conversation

@grdsdev

@grdsdev grdsdev commented Jul 28, 2026

Copy link
Copy Markdown

Context

The SDK team is starting to use the generated OpenAPI spec to generate idiomatic API clients for the Supabase SDKs. We're starting with vector buckets for testing. This PR is the first of several that will make the fastify-generated OpenAPI spec ready for client generation, split by route family to keep each one reviewable.

Summary

First slice of #1215 (too large to review as one PR), split by route family. This PR covers only the vector bucket routes.

  • Adds vectorBucketSchema, getVectorBucketResponseSchema, listVectorBucketsResponseSchema (mirroring bucketSchema/objectSchema), and wires them into create-bucket/delete-bucket/get-bucket/list-buckets so the exported spec has success schemas for the vector CRUD endpoints, not just error responses.
  • Adds src/http/routes/openapi-transform.ts: derives stable operationIds from each route's existing config.operation, names swagger components by $id instead of def-N, folds duplicate trailing-slash paths, and documents a generic 4xx (doc-only, covering 403 etc.) for any route without one. Prerequisite infra for generating a usable client from any route family, starting here with vector.

Deferred to follow-up PRs: bucket/object/iceberg/render/tus/cdn/health route documentation, error-handler.ts formatter support, apikey.ts, typespec tooling additions.

Test plan

  • tsc -noEmit — no errors
  • biome check on touched files — clean
  • vitest run on openapi-transform.test.ts — 5/5 passing

The vector bucket routes (create/get/list/delete) only ever documented
error responses, so the exported OpenAPI spec had no success schema at
all for them - unusable for SDK generation. Adds a vectorBucketSchema
component (mirroring bucketSchema/objectSchema) plus response wrappers
for get/list, and explicit `type: 'null'` 200s for the void
create/delete responses so generators don't try to decode an empty
body as JSON.

Also wires createOpenApiTransform/dedupeTrailingSlashPaths/
nameSchemaByDollarId into the swagger build (assigns stable
operationIds from each route's config.operation, names swagger
components by $id, and folds duplicate trailing-slash paths), and
documents the 403 response every JWT-guarded route (including vector)
can return.

First slice of a larger OpenAPI/SDK-generation PR, split up by route
family to keep review scoped.
@grdsdev
grdsdev requested a review from a team as a code owner July 28, 2026 09:13
@coveralls

coveralls commented Jul 28, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 30385224947

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Coverage increased (+0.09%) to 80.395%

Details

  • Coverage increased (+0.09%) from the base build.
  • Patch coverage: 2 uncovered changes across 1 file (69 of 71 lines covered, 97.18%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
src/http/routes/openapi-transform.ts 63 61 96.83%
Total (3 files) 71 69 97.18%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 12892
Covered Lines: 10799
Line Coverage: 83.77%
Relevant Branches: 7587
Covered Branches: 5665
Branch Coverage: 74.67%
Branches in Coverage %: Yes
Coverage Strength: 409.87 hits per line

💛 - Coveralls

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Beyond the inline finding, I also checked vectorBucketSchema's use of the OpenAPI-style nullable: true keyword on creationTime (rather than the type: ['integer', 'null'] / anyOf pattern used everywhere else in src/storage/schemas) — it's a docs-only response schema and AJV ignores the unrecognized nullable keyword rather than erroring, so it doesn't break validation, just an inconsistency worth normalizing at some point.

Extended reasoning...

Confirmed via grep that every other nullable field in src/storage/schemas (bucket.ts, object.ts, upload.ts, multipart.ts) uses type: [x, 'null'] or anyOf, never the OpenAPI 3.0 'nullable' keyword. Since this schema is only used for response documentation/serialization (not request validation) and AJV in this codebase's default config doesn't run in strict mode that would throw on unknown keywords, this doesn't cause a functional bug — just a style inconsistency, not delaying approval on its own.

Comment thread src/http/plugins/jwt.ts Outdated
The onRoute hook was mutating routeOptions.schema.response directly,
which Fastify compiles into a real fast-json-stringify serializer
independent of docs generation. Iceberg's routes use a custom error
formatter ({ error: { message, type, code } }, no top-level
statusCode/message/code), so any 403 there (wrong-role JWT) would fail
serialization against errorSchema's required fields and come back as
a 500 instead. openapi-transform.ts's defaultErrorResponse already
documents a generic 4xx (covering 403) doc-only for every route, so no
replacement is needed.
* leak collisions into each other when generated in the same process (see export-docs.ts).
*/
export function createOpenApiTransform() {
const seenIds = new Set<string>()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This makes ordering important so maintenance problem. If we change route ordering in a file, it will break SDKs. I would suggest adding explicit config directly to routes for documentation and validate for uniqueness. Auto generated head routes could receive Head suffix

config: {
  operation: ROUTE_OPERATIONS.GET_AUTH_OBJECT,
	openapiOperation: "yourid"
}

Comment thread src/http/routes/openapi-transform.ts Outdated
Comment thread src/http/routes/openapi-transform.ts Outdated
… fields

- operationId generation no longer silently resolves collisions by
  registration order. Routes can now set config.operationId to pin an
  explicit id; exposeHeadRoutes-derived HEAD routes get a deterministic
  Head suffix; any other collision throws instead of picking a winner
  based on file order, since that would make generated SDK method names
  depend on ordering that has nothing to do with the API contract.
- documentMultipartUploadBody no longer marks cacheControl as required -
  the uploader defaults it to 'no-cache' when omitted.
Comment thread src/http/routes/openapi-transform.ts Outdated
Comment thread src/http/routes/openapi-transform.ts Outdated
Comment thread src/http/routes/openapi-transform.ts Outdated
Comment thread src/http/routes/openapi-transform.test.ts Outdated
grdsdev and others added 5 commits July 28, 2026 13:58
Sets config.operationId (createVectorBucket/deleteVectorBucket/
getVectorBucket/listVectorBuckets) on each vector bucket route instead
of relying on the id derived from config.operation, so the generated
SDK method name is pinned to the route's own definition rather than
implicit derivation. Declares the field on FastifyContextConfig
(src/http/plugins/log-request.ts, alongside the existing `operation`
field) so it's usable/typed anywhere a route config is set.
Co-authored-by: ferhat elmas <elmas.ferhat@gmail.com>
documentMultipartUploadBody only documented cacheControl/metadata/file,
missing two fields the upload handler actually accepts (see
src/storage/uploader.ts): userMetadata (alias for metadata) and
contentType (overrides the auto-detected mime type).
Keeps the config.operationId mechanism (openapi-transform.ts,
FastifyContextConfig in log-request.ts) but reverts pinning it on the
4 vector bucket routes - the derived id from config.operation is fine
for these, no need to override it.
- Removes documentMultipartUploadBody/MULTIPART_UPLOAD_OPERATIONS -
  deferred to a follow-up PR, which should also cover the raw
  (non-multipart) upload body per review feedback.
- defaultErrorResponse no longer defaults iceberg-subtree routes to
  errorSchema's flat {statusCode, error, message, code} shape - iceberg's
  own setErrorHandler formatter returns {error: {message, type, code}}
  instead. Detected by path prefix rather than tags/config.operation,
  since some iceberg routes (iceberg/bucket.ts) reuse the same tag and
  ROUTE_OPERATIONS constants as the unrelated storage-bucket routes.
  Leaves iceberg 4xx responses undocumented for now rather than
  documenting the wrong shape; real docs need their own schema.
…erationIds

CI caught this: the hard-throw-on-collision added per review feedback
crashes OpenAPI doc generation for the WHOLE app the moment any
pre-existing duplicate ROUTE_OPERATIONS usage is encountered - not
just within vector's scope. tus/index.ts alone has 3 such pairs (POST
/ vs POST /*, PUT vs PATCH /*, OPTIONS / vs OPTIONS /*, each also
duplicated across its default/_signed variants), and object routes
have more.

Warn and leave the colliding route without an operationId instead -
no worse than the state before this transform existed, and doesn't
require auditing every legacy route family from this PR. Each
occurrence is its own follow-up (config.operationId fix), same as
iceberg's.
…napi-transform

Adds a test per vector bucket route (Get/List/Create/Delete) asserting
the transform preserves each route's own success response ($ref for
Get/List, null-body for Create/Delete) and the shared error envelope
(sharedErrorResponseSchemas) untouched, while still assigning the
correct operationId.
grdsdev added a commit that referenced this pull request Jul 28, 2026
…oad routes

Documents both request-body shapes createObject/updateObject/
uploadSignedObject accept (see src/storage/uploader.ts): multipart/
form-data (cacheControl/metadata/userMetadata/contentType/file fields)
and the raw upload variant, where the file bytes are the entire body
and the same metadata is carried via Cache-Control/Content-Type/
x-metadata headers instead - the x-metadata header is base64-encoded
JSON there, unlike the plain JSON-encoded multipart field.

Uses schema.body.content (@fastify/swagger's supported multi-content-
type body escape hatch, documented for responses in its README, same
code path for requests) instead of the single consumes+body shorthand,
since the two variants have genuinely different body schemas.

Addresses the "we also support raw uploads" follow-up from #1270's
review (#1270 (comment)).
grdsdev added 2 commits July 28, 2026 14:50
Uses type: ['integer', 'null'] instead of the OpenAPI-only nullable
keyword, matching the anyOf/type-array pattern every other nullable
field in src/storage/schemas uses (bucket.ts, object.ts). Addressed
per the extended-reasoning note in the earlier Claude review - docs-only
field, no functional change, just consistency.
Standardizing on the OpenAPI nullable keyword going forward instead of
the type-array/anyOf pattern used elsewhere in src/storage/schemas.
Migrating bucket.ts/object.ts's existing fields to match is left for a
follow-up PR - this only reverts vector.ts back to nullable: true.
@grdsdev
grdsdev requested a review from ferhatelmas July 28, 2026 20:08
@grdsdev
grdsdev changed the base branch from master to feat/openapi-3-2-0 July 29, 2026 21:27
@grdsdev
grdsdev changed the base branch from feat/openapi-3-2-0 to master July 29, 2026 22:10
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