# Second try of making a new better process for SDK automation Instead of building a new pipeline. We will take the lessons learned form round 1, plus the good design and improvement on DX quality for drop-in file as a single step required from SDK team and produce almost identical set of files as used right now to render using the current pipeline. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * New reference-content pipeline producing per-library reference artifacts and integrating into prebuilds, search ingestion, and rendering (type-aware examples). * **Documentation** * Added comprehensive JavaScript SDK v2 reference content and partials (Auth MFA, passkeys, admin, TypeScript support, filters, modifiers, Installing, Initializing, Buckets, etc.). * **Tests & CI** * Added regression snapshot test and updated workflows to refresh reference snapshots and ensure spec downloads. * **Chores** * Updated ignore rules, build scripts, Makefile targets, and package lifecycle hooks. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Katerina Skroumpelou <mandarini@users.noreply.github.com> Co-authored-by: Katerina Skroumpelou <sk.katherine@gmail.com>
Reference content pipeline
This directory feeds the new reference-docs pipeline driven by
scripts/build-reference-content.ts.
The setup lets you drop TypeDoc dumps, layer hand-authored content via
partials, and tweak ordering through config.json. Currently it only consumes
TypeDoc JSON output, but other formats can be adapted
to the same shape as a pre-step.
The legacy pipeline (the spec/supabase_*_v*.yml files plus
spec/common-client-libs-sections.json driven by
features/docs/Reference.generated.script.ts) still exists for SDKs that
haven't migrated yet — see "Routing to the new pipeline" below for how a lib
opts in.
Pipeline at a glance
Upstream supabase-js packages publish TypeDoc JSON at
https://supabase.github.io/<pkg>/v2/spec.json
│
│ (1) cd apps/docs/spec && make download.tsdoc.v2
▼
spec/reference/<lib>/<ver>/ ← gitignored *.json
├── *.json ← downloaded TypeDoc dumps (committed: config.json
├── config.json ← hand-authored + partials/)
└── partials/ ← hand-authored
│
│ (2) pnpm codegen:references:new
│ (= ensure dumps + build-reference-content.ts)
▼
content/reference/<lib>/<ver>/ ← gitignored (all 5 files)
├── bySlug.json
├── flat.json
├── sections.json
├── functions.json
└── typeSpec.json
│
│ (3) Next.js render
▼
/docs/reference/<lib>/<ver>/<slug>
Both spec/reference/<lib>/<ver>/*.json (the source dumps) and the entire
content/reference/ tree (the build output) are gitignored — only
hand-authored files inside spec/reference/<lib>/<ver>/ (config.json and
partials/) are committed. Everything regenerates on pnpm dev / pnpm build
via predev / prebuild, so a clean checkout just works.
Directory layout
spec/reference/
└── <library>/ e.g. javascript
└── <version>/ e.g. v2
├── *.json TypeDoc spec files (GITIGNORED — downloaded)
├── config.json Optional. Filters and ordering — see below (committed)
└── partials/ Optional. Per-section content (committed)
├── *.mdx | *.md Markdown partials (with frontmatter)
└── *.json Rich function-type partials (description, examples, …)
Library and version names come straight from the folder names. Anything you drop
under spec/reference/<lib>/<ver>/ is picked up automatically by the build
script — there is no separate manifest to update inside this directory.
The output will be sent to the content/reference/<lib>/<ver>/ directory
(also gitignored — see "Outputs" below).
Source: TypeDoc JSON specs
Each top-level .json file (except config.json) is a TypeDoc dump of one
package. These files are gitignored — they're downloaded build artifacts,
not source. Only hand-authored files (config.json and partials/) are
tracked. To refresh the dumps locally:
cd apps/docs/spec && make download.tsdoc.v2
pnpm dev / pnpm build runs codegen:references:ensure automatically as
part of predev / prebuild. That script checks for
spec/reference/javascript/v2/supabase.json and, if missing, invokes
make download.tsdoc.v2. So a clean checkout just works — the dumps land on
first build and are reused on subsequent runs. To force a refresh after
supabase-js ships a release, run the make target by hand (or delete one of
the .json files and let the next build re-fetch).
CI runs the same path: .github/workflows/docs-tests.yml calls
make download.tsdoc.v2 before tests, and docs-js-libs-update.yml opens a
PR with regenerated snapshots when supabase-js publishes a new version.
The build walks every declaration in those dumps and harvests anything tagged
with @category (and optionally @subcategory):
// spec/reference/javascript/v2/gotrue.json (excerpt)
{
"name": "@supabase/auth-js", // ← package name, becomes the $ref prefix
"variant": "project",
"kind": 1,
"children": [
{
"kind": 128,
"name": "GoTrueClient",
"children": [
{
"kind": 2048,
"name": "linkIdentity",
"comment": {
"blockTags": [
{ "tag": "@category", "content": [{ "kind": "text", "text": "Auth" }] },
{ "tag": "@subcategory", "content": [{ "kind": "text", "text": "Auth MFA" }] },
],
},
"signatures": [
{
/* params + return type */
},
],
},
],
},
],
}
Key tags the walker reads:
@category— required for a declaration to appear in the listing. Becomes theproductand groups items insections.json.@subcategory— optional. Nests the declaration under a sub-grouping inside the category, with its own header.
Anything without @category is collected into typeSpec.json (for cross-reference
by $ref) but does not appear in the navigation/section list.
$ref values are constructed as <package>.<module…>.<class…>.<member>,
following TypeDoc's module (kind 2), namespace (kind 4), class (kind 128) and
interface (kind 256) nesting. The index module segment is stripped via
normalizeRefPath (so @supabase/storage-js.index.StorageClient.foo becomes
@supabase/storage-js.StorageClient.foo).
Partials
Partials enrich the rendered output with content TypeDoc can't supply (intro paragraphs, code examples, etc.). The filename (without extension) is the routing key:
- Matches a subcategory title slug (e.g.
using-filters.jsonfor the "Using filters" subcategory) → attached to that subcategory. - Matches a category title slug (e.g.
auth.jsonfor the "Auth" category) → attached to that category. - Anything else → emitted at the top of the page, before any category
(this is how
introduction.mdx/installing.mdxwork).
The slug check is title.toLowerCase().replace(/\s+/g, '-'), so
"Auth Admin" → auth-admin, "File Buckets" → file-buckets, etc.
.mdx / .md partials (markdown)
Frontmatter is parsed via gray-matter. Only title and ref are read:
---
title: Initializing
ref: '@supabase/supabase-js.SupabaseClient.constructor' # optional — see below
---
Body content goes here.
- Without
ref: emits atype: 'markdown'entry. Its body is also written toapps/docs/content/reference/<library>/<version>/<name>.mdxso the renderer's routed loader (getRefMarkdownForLib) can serve it. - With
ref: emits atype: 'function'entry plus afunctions.jsonentry{id: <name>, $ref: <ref>}. The renderer pairs that withtypeSpec.jsonto show the method signature. This is howinitializing.mdxlinks to theSupabaseClientconstructor.
If the filename matches a category/subcategory, the markdown entry is added as a separate sub-section at the top of that section's items (the subcategory header itself is untouched).
.json partials (function-type)
JSON partials carry rich content (description, notes, examples) that gets
rendered into the page. The full body is poured into functions.json:
// spec/reference/javascript/v2/partials/using-filters.json
{
"id": "using-filters",
"title": "Using Filters",
"description": "Filters allow you to only return rows that match …",
"examples": [{ "id": "applying-filters", "name": "Applying Filters", "code": "```ts\n…\n```" }]
}
Routing semantics differ from markdown partials:
- Matches a category/subcategory → enriches that section's header.
No separate sub-section is emitted. The entry is keyed in
functions.jsonunder the matched section's slug (e.g.auth-admin,using-filters— whichever slug the header resolves to, afternavigationPrefixes), so the renderer'sfns.find(f => f.id === section.id)resolves to the partial body and the subcategory header renders with the description/examples in place. - Top-level (no match) → emitted as its own
type: 'function'section.
This is why auth-admin.json (matching the auth-admin subcategory slug)
shows up as the Auth Admin heading with notes and examples, instead of as a
sibling "Overview" block.
config.json
Every option is optional. Default behavior: include everything, in spec order, alphabetical-within-section.
{
// Categories (matched on the literal @category text, case-sensitive) to
// drop entirely. Functions in these categories are filtered out before
// grouping, so they disappear from bySlug / flat / sections / functions.json
// (but stay in typeSpec.json — partials may still reference them via $ref).
"excludeCategories": ["Initializing"],
// Declaration names (matched on the source identifier, case-sensitive) to
// drop. Useful for hiding noise like `constructor` while still allowing
// partials to link to the constructor's $ref.
"excludeDefinitions": ["constructor"],
// Order categories should appear in `sections.json` / `flat.json`. Anything
// not listed here keeps its discovery order at the end.
"categoryOrder": ["Database", "Auth", "Edge Functions", "Realtime", "Storage"],
// Order top-level partials. Anything not listed here falls back to
// alphabetical order at the end.
"partialsOrder": ["introduction", "installing", "typescript-support"],
// Customize the navigation slug used as a prefix for a category or
// subcategory. Keys are the literal @category / @subcategory text
// (case-sensitive). Values:
// string → use this in place of the default slugified title.
// e.g. "Edge Functions": "functions" turns the function slug
// "edge-functions-invoke" into "functions-invoke".
// false → drop the prefix entirely from child function slugs.
// e.g. "Using modifiers": false turns "using-modifiers-explain"
// into "explain". The category/subcategory header itself still
// needs a navigable slug, so its entry slug falls back to the
// slugified title ("using-modifiers").
// absent → use the slugified title (default behavior).
"navigationPrefixes": {
"Database": false,
"Using filters": false,
"Using modifiers": false,
"Edge Functions": "functions",
},
}
Slug shape
A function's slug is ${prefix}-${name} where prefix is its nearest container
— its @subcategory if it has one, otherwise its @category. For example a
PostgREST eq method tagged @category Database @subcategory Using filters:
- without config:
using-filters-eq - with
"Using filters": false:eq - with
"Using filters": "filters":filters-eq
The category and subcategory header entries (the rows that show up in
navigation) get the resolved prefix too — "Edge Functions": "functions"
makes the Edge Functions category render at /functions, not /edge-functions.
false falls back to the title slug for the header (a header needs a stable,
navigable slug).
Within each category, the script always:
- Emits the category header.
- Emits direct functions (no
@subcategory) alphabetically by name. - Emits each subcategory (alphabetical), each followed by its functions (alphabetical).
So categoryOrder lets you sort across categories; within a category, order
is fixed.
Outputs
pnpm tsx scripts/build-reference-content.ts writes five files per
<library>/<version> to apps/docs/content/reference/<library>/<version>/:
| File | Shape | Used for |
|---|---|---|
bySlug.json |
Record<slug, Entry> |
Slug → section lookup |
flat.json |
Entry[] (Object.values(bySlug)) |
Linear iteration in the renderer |
sections.json |
Entry[] with nested items[] |
Sidebar navigation tree |
functions.json |
Array<{id, $ref?, …}> |
Rendered description / examples / signature lookup |
typeSpec.json |
{methods, variables} keyed by $ref |
Parameter and return-type display |
The build also writes <partial-name>.mdx files for each markdown partial into
the same per-version output directory — the page renderer's routed loader
(getRefMarkdownForLib) reads those bodies directly, no manual copy needed.
The whole apps/docs/content/reference/ tree is gitignored — outputs are
regenerated by prebuild (pnpm codegen:references:new, which also runs as
part of predev).
Regenerating the snapshot test
CI gates this pipeline through a single snapshot test at
scripts/build-reference-content.test.ts.
It runs collectReferenceContent('javascript', 'v2') and snapshots all five
derived artifacts (bySlug, flat, sections, functionsList, typeSpec)
into __snapshots__/build-reference-content.test.ts.snap. Any change in the
output — new methods, slug shape, type signatures, section ordering — surfaces
as a snapshot diff in the PR, which is the human-reviewable preview of what
the renderer will see.
The snapshot is the only committable artifact this pipeline produces (the
TypeDoc dumps and content/reference/ outputs are gitignored), so it doubles as
the change log for upstream and pipeline-logic changes.
Re-run with --update whenever you:
- Edit
scripts/build-reference-content.ts(extraction or grouping logic). - Change a lib's
config.json(excludeCategories,excludeDefinitions,categoryOrder,partialsOrder,navigationPrefixes). - Add, remove, or edit files under
partials/. - Pull a refreshed TypeDoc dump (
make download.tsdoc.v2) — typically because supabase-js shipped a release. Thedocs-js-libs-update.ymlworkflow does this automatically and opens a PR with the refreshed snapshot.
cd apps/docs && npx vitest run --update scripts/build-reference-content.test.ts
Inspect the resulting diff before committing — a clean, additive diff (new methods, new entries) is the expected shape; large renames or removals are worth a second look.
Local failures in other tests are expected — don't panic. Vitest picks up every
*.test.tsinapps/docs, even when you target one file. Tests that hit the Supabase backend (app/api/graphql/tests/errors*.test.ts, theerrors.collection.test.tssuite) will fail withfetch failed/ timeouts unless you've runpnpm supabase startfirst, and any*.smoke.test.tsfile will hit livesupabase.com/docsURLs that depend on the current prod deploy. The only result that matters here is thebuild-reference-contentline — if that's green and the.snapfile updated, you're done. CI runs with the local Supabase stack up and excludes smoke tests, so those failures won't follow your PR.
Routing to the new pipeline
By default, the runtime in
features/docs/Reference.generated.singleton.ts
keeps reading the legacy features/docs/generated/<sdk>.<version>.*.json
files. To route a lib through the new outputs, add its ${sdk}-${version} key
to the constant in
features/docs/Reference.constants.ts:
export const SUPPORTS_NEW_REFERENCE_PROCESS = new Set([
'javascript-v2',
// 'dart-v2', ← uncomment when ready
])
The same set drives every runtime read that depends on the new layout:
- The four lib-version-keyed JSON getters in
Reference.generated.singleton.ts(getFunctionsList,getReferenceSections,getFlattenedSections,getSectionsBySlug) pick betweenfeatures/docs/generated/<sdk>.<version>.*.jsonandcontent/reference/<sdk>/<version>/*.json. - The MDX loader in
Reference.mdx.tsx(getRefMarkdownForLib) picks betweendocs/ref/<libPath>/[<version>/]<id>.mdxandcontent/reference/<libPath>/<version>/<id>.mdx. - The legacy section-generation script
Reference.generated.script.tsfilters out libs in the set, so nosupabase_<lib>_v<ver>.ymlis read for them. Migrated libs therefore drop theirspecFilefield fromcontent/navigation.references.ts(see the JS v2 entry for an example). - Search/embeddings ingest in
scripts/search/sources/index.ts:fetchJsLibReferenceSource()callsloadClientLibReferenceFromNewPipeline()which readscontent/reference/javascript/v2/{sections,functions,typeSpec}.jsondirectly. Other libs still go throughClientLibReferenceLoaderagainst their YAML. To migrate another lib, swap the same way once it's inSUPPORTS_NEW_REFERENCE_PROCESS.
No other call sites in the render path need to change.
⚠️ Don't move the constant.
Reference.utils.tstransitively pulls innext/navigation, which crashestsx --conditions=react-server(used bypnpm build:llms). The constant lives in its own no-dep file (Reference.constants.ts) so server-only scripts can import it without dragging the Next runtime in.
Adding a new lib/version (checklist)
- Wire up the TypeDoc download in
spec/Makefile. Copy thedownload.tsdoc.v2target, adapt the URLs and output paths tospec/reference/<lib>/<ver>/. Make sure every public method in the upstream source has an@categorytag. One JSON per package is fine — the walker handles multiple files. The dumps themselves are gitignored; onlyconfig.jsonandpartials/get tracked. - (Optional) add
config.jsonwithexcludeCategories,excludeDefinitions,categoryOrder,partialsOrder. - (Optional) add
partials/with intro markdown and per-section rich content (see "Partials" above). - Register the lib/version in
features/docs/Reference.constants.ts'sSUPPORTS_NEW_REFERENCE_PROCESSso the runtime reads the new outputs. - Run the build:
This auto-downloads missing dumps via
cd apps/docs && pnpm codegen:references:newcodegen:references:ensureand then runsbuild-reference-content.ts. Inspect the five files undercontent/reference/<lib>/<ver>/. The log line prints declaration / function / subcategory / category counts. - Verify the rendered page at
/docs/reference/<lib>/<ver>in dev. If subcategory bodies are empty, check that the partial filename matches the subcategory title slug exactly (title.toLowerCase().replace(/\s+/g, '-')).