docs: manually surface OAuth docs (#41194)

This commit is contained in:
Katerina Skroumpelou authored and GitHub committed 2025-12-09 14:33:08 +00:00
1 parent 9c244df9f1
commit a552194fb9
5 files changed
+596 -51

No files matched your search

+259
View File
@@ -0,0 +1,259 @@
/**
* Find methods in typeSpec.json that are NOT documented in supabase_js_v2.yml
*
* Usage: pnpm tsx scripts/find-undocumented.ts
*
* Note: Run `pnpm prebuild` first to generate typeSpec.json
*/
import { existsSync, readFileSync } from 'fs'
import yaml from 'js-yaml'
import { dirname, join } from 'path'
import { fileURLToPath } from 'url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const SPEC_DIR = join(__dirname, '../spec')
const GENERATED_DIR = join(__dirname, '../features/docs/generated')
interface YamlFunction {
id: string
$ref?: string
}
interface YamlSpec {
functions: YamlFunction[]
}
interface TypeSpecModule {
name: string
methods: Record<string, unknown>
}
// Same normalization as Reference.typeSpec.ts
function normalizeRefPath(path: string) {
return path.replace(/\.index(?=\.|$)/g, '').replace(/\.+/g, '.')
}
// Categorize a method path
function categorizeMethod(methodPath: string): 'public' | 'constructor' | 'error' | 'internal' {
const parts = methodPath.split('.')
const methodName = parts[parts.length - 1]
const className = parts[parts.length - 2]
// Internal/private methods start with _
if (methodName.startsWith('_')) {
return 'internal'
}
// Error class constructors
if (className?.endsWith('Error') && methodName === 'constructor') {
return 'error'
}
// Other constructors
if (methodName === 'constructor') {
return 'constructor'
}
return 'public'
}
// Dynamically detect re-exports from typeSpec data
// If a class exists in both @supabase/supabase-js and another @supabase/* package,
// prefer the other package (which is the original source)
function buildReexportMap(typeSpecModules: TypeSpecModule[]): Map<string, string> {
const classToPackages = new Map<string, Set<string>>()
for (const mod of typeSpecModules) {
for (const methodPath of Object.keys(mod.methods)) {
const parts = methodPath.split('.')
const pkg = parts[0]
const className = parts[1]
if (pkg?.startsWith('@supabase/') && className) {
if (!classToPackages.has(className)) {
classToPackages.set(className, new Set())
}
classToPackages.get(className)!.add(pkg)
}
}
}
// For classes in multiple packages, map supabase-js to the original package
const reexportMap = new Map<string, string>()
for (const [className, packages] of classToPackages) {
if (packages.has('@supabase/supabase-js') && packages.size > 1) {
// Find the original package (not supabase-js)
for (const pkg of packages) {
if (pkg !== '@supabase/supabase-js') {
reexportMap.set(className, pkg)
break
}
}
}
}
return reexportMap
}
// Get the "canonical" path (prefer original package over supabase-js re-exports)
function getCanonicalPath(methodPath: string, reexportMap: Map<string, string>): string {
if (methodPath.startsWith('@supabase/supabase-js.')) {
const parts = methodPath.split('.')
const className = parts[1]
const originalPkg = reexportMap.get(className)
if (originalPkg) {
return methodPath.replace('@supabase/supabase-js', originalPkg)
}
}
return methodPath
}
// Check if typeSpec.json exists
const typeSpecPath = join(GENERATED_DIR, 'typeSpec.json')
if (!existsSync(typeSpecPath)) {
console.error('ERROR: typeSpec.json not found!')
console.error('Run `pnpm prebuild` first to generate it.')
process.exit(1)
}
// Load typeSpec.json and get all method paths
const typeSpecModules: TypeSpecModule[] = JSON.parse(readFileSync(typeSpecPath, 'utf8'))
const allMethods: string[] = []
for (const mod of typeSpecModules) {
for (const methodPath of Object.keys(mod.methods)) {
allMethods.push(methodPath)
}
}
// Build re-export map dynamically from typeSpec data
const reexportMap = buildReexportMap(typeSpecModules)
// Load YAML and get all documented $ref values (normalized)
const yamlPath = join(SPEC_DIR, 'supabase_js_v2.yml')
const spec = yaml.load(readFileSync(yamlPath, 'utf8')) as YamlSpec
const documentedRefs = new Set<string>()
for (const fn of spec.functions) {
if (fn.$ref) {
// Store both raw and normalized versions
documentedRefs.add(fn.$ref)
documentedRefs.add(normalizeRefPath(fn.$ref))
}
}
// Find undocumented methods and deduplicate
const seenCanonical = new Set<string>()
const undocumented: string[] = []
for (const method of allMethods) {
// Get canonical path first (prefer original packages over supabase-js re-exports)
const canonical = getCanonicalPath(method, reexportMap)
// Skip if already processed this canonical path
if (seenCanonical.has(canonical)) {
continue
}
seenCanonical.add(canonical)
// Check if documented - check both raw path AND canonical path
if (
documentedRefs.has(method) ||
documentedRefs.has(normalizeRefPath(method)) ||
documentedRefs.has(canonical) ||
documentedRefs.has(normalizeRefPath(canonical))
) {
continue
}
// Use canonical path for output
undocumented.push(canonical)
}
// Categorize
const publicMethods: string[] = []
const constructors: string[] = []
const errorConstructors: string[] = []
const internalMethods: string[] = []
for (const method of undocumented) {
switch (categorizeMethod(method)) {
case 'public':
publicMethods.push(method)
break
case 'constructor':
constructors.push(method)
break
case 'error':
errorConstructors.push(method)
break
case 'internal':
internalMethods.push(method)
break
}
}
// Group public methods by package
const publicByPackage = new Map<string, string[]>()
for (const method of publicMethods) {
const pkg = method.split('.')[0]
if (!publicByPackage.has(pkg)) {
publicByPackage.set(pkg, [])
}
publicByPackage.get(pkg)!.push(method)
}
// Output public methods (the interesting ones)
console.log('╔═══════════════════════════════════════════════════════════════╗')
console.log('║ UNDOCUMENTED PUBLIC METHODS ║')
console.log('╚═══════════════════════════════════════════════════════════════╝')
for (const [pkg, methods] of publicByPackage) {
console.log(`\n=== ${pkg} ===`)
methods.sort().forEach((m) => {
const shortName = m.replace(pkg + '.', '')
console.log(` - ${shortName}`)
})
}
// Summary sections for other categories
console.log('\n╔═══════════════════════════════════════════════════════════════╗')
console.log('║ SKIPPED CATEGORIES ║')
console.log('╚═══════════════════════════════════════════════════════════════╝')
console.log(`\n[Constructors] (${constructors.length} items)`)
constructors
.sort()
.slice(0, 5)
.forEach((m) => console.log(` - ${m}`))
if (constructors.length > 5) console.log(` ... and ${constructors.length - 5} more`)
console.log(`\n[Error Constructors] (${errorConstructors.length} items)`)
errorConstructors
.sort()
.slice(0, 5)
.forEach((m) => console.log(` - ${m}`))
if (errorConstructors.length > 5) console.log(` ... and ${errorConstructors.length - 5} more`)
console.log(`\n[Internal/Private Methods] (${internalMethods.length} items)`)
internalMethods
.sort()
.slice(0, 5)
.forEach((m) => console.log(` - ${m}`))
if (internalMethods.length > 5) console.log(` ... and ${internalMethods.length - 5} more`)
// Final summary
const totalUndocumented = undocumented.length
const documentedCount = allMethods.length - totalUndocumented
const percentage = ((documentedCount / allMethods.length) * 100).toFixed(1)
console.log('\n╔═══════════════════════════════════════════════════════════════╗')
console.log('║ SUMMARY ║')
console.log('╚═══════════════════════════════════════════════════════════════╝')
console.log(` Total methods in TypeSpec: ${allMethods.length}`)
console.log(` Documented in YAML: ${documentedCount}`)
console.log(` Undocumented (deduplicated): ${totalUndocumented}`)
console.log(` - Public methods: ${publicMethods.length} ← focus here`)
console.log(` - Constructors: ${constructors.length}`)
console.log(` - Error constructors: ${errorConstructors.length}`)
console.log(` - Internal methods: ${internalMethods.length}`)
console.log(` Coverage: ${percentage}%`)
+103
View File
@@ -0,0 +1,103 @@
/**
* Cross-check IDs between common-client-libs-sections.json and supabase_js_v2.yml
*
* Reports:
* 1. Functions in sections but NOT in YAML
* 2. Groups (isFunc: false) in sections but NOT in YAML
* 3. IDs in YAML but NOT in sections
*
* Usage: pnpm tsx scripts/validate-references.ts
*/
import { readFileSync } from 'fs'
import yaml from 'js-yaml'
import { dirname, join } from 'path'
import { fileURLToPath } from 'url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const SPEC_DIR = join(__dirname, '../spec')
interface Section {
id?: string
type: string
isFunc?: boolean
items?: Section[]
}
interface YamlSpec {
functions: Array<{ id: string }>
}
// Flatten sections, extracting all function-type entries
function flattenSections(sections: Section[]): { functions: string[]; groups: string[] } {
const functions: string[] = []
const groups: string[] = []
function recurse(items: Section[]) {
for (const item of items) {
if (item.type === 'function' && item.id) {
if (item.isFunc === false) {
groups.push(item.id)
} else {
functions.push(item.id)
}
}
if (item.items) {
recurse(item.items)
}
}
}
recurse(sections)
return { functions, groups }
}
// Main
const sectionsPath = join(SPEC_DIR, 'common-client-libs-sections.json')
const yamlPath = join(SPEC_DIR, 'supabase_js_v2.yml')
const sections: Section[] = JSON.parse(readFileSync(sectionsPath, 'utf8'))
const spec = yaml.load(readFileSync(yamlPath, 'utf8')) as YamlSpec
const { functions: sectionFunctions, groups: sectionGroups } = flattenSections(sections)
const yamlIds = new Set(spec.functions.map((f) => f.id))
const sectionFunctionSet = new Set(sectionFunctions)
const sectionGroupSet = new Set(sectionGroups)
const allSectionIds = new Set([...sectionFunctions, ...sectionGroups])
// Find mismatches
const functionsNotInYaml = sectionFunctions.filter((id) => !yamlIds.has(id))
const groupsNotInYaml = sectionGroups.filter((id) => !yamlIds.has(id))
const yamlNotInSections = [...yamlIds].filter((id) => !allSectionIds.has(id))
// Output
console.log('=== Functions in sections but NOT in YAML ===')
if (functionsNotInYaml.length === 0) {
console.log('(none)')
} else {
functionsNotInYaml.forEach((id) => console.log(`- ${id}`))
}
console.log('\n=== Groups (isFunc: false) in sections but NOT in YAML ===')
if (groupsNotInYaml.length === 0) {
console.log('(none)')
} else {
groupsNotInYaml.forEach((id) => console.log(`- ${id}`))
}
console.log('\n=== IDs in YAML but NOT in sections ===')
if (yamlNotInSections.length === 0) {
console.log('(none)')
} else {
yamlNotInSections.forEach((id) => console.log(`- ${id}`))
}
console.log(
`\nSummary: ${functionsNotInYaml.length} functions missing, ${groupsNotInYaml.length} groups missing, ${yamlNotInSections.length} orphaned`
)
// Exit with error code if any mismatches
if (functionsNotInYaml.length > 0 || groupsNotInYaml.length > 0 || yamlNotInSections.length > 0) {
process.exit(1)
}
@@ -0,0 +1,87 @@
/**
* Validate that YAML $ref values exist in typeSpec.json
*
* Checks: supabase_js_v2.yml $ref → typeSpec.json (generated from combined.json)
*
* Usage: pnpm tsx scripts/validate-typespec-refs.ts
*
* Note: Run `pnpm prebuild` first to generate typeSpec.json
*/
import { existsSync, readFileSync } from 'fs'
import yaml from 'js-yaml'
import { dirname, join } from 'path'
import { fileURLToPath } from 'url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const SPEC_DIR = join(__dirname, '../spec')
const GENERATED_DIR = join(__dirname, '../features/docs/generated')
interface YamlFunction {
id: string
$ref?: string
}
interface YamlSpec {
functions: YamlFunction[]
}
interface TypeSpecModule {
name: string
methods: Record<string, unknown>
}
// Same normalization as Reference.typeSpec.ts
function normalizeRefPath(path: string) {
return path.replace(/\.index(?=\.|$)/g, '').replace(/\.+/g, '.')
}
// Check if typeSpec.json exists
const typeSpecPath = join(GENERATED_DIR, 'typeSpec.json')
if (!existsSync(typeSpecPath)) {
console.error('ERROR: typeSpec.json not found!')
console.error('Run `pnpm prebuild` first to generate it.')
process.exit(1)
}
// Load typeSpec.json and extract all valid method paths
const typeSpecModules: TypeSpecModule[] = JSON.parse(readFileSync(typeSpecPath, 'utf8'))
const validRefs = new Set<string>()
for (const mod of typeSpecModules) {
for (const methodPath of Object.keys(mod.methods)) {
validRefs.add(methodPath)
}
}
// Load YAML and extract $ref values
const yamlPath = join(SPEC_DIR, 'supabase_js_v2.yml')
const spec = yaml.load(readFileSync(yamlPath, 'utf8')) as YamlSpec
const yamlRefs: Array<{ id: string; ref: string }> = []
for (const fn of spec.functions) {
if (fn.$ref) {
yamlRefs.push({ id: fn.id, ref: fn.$ref })
}
}
// Find invalid refs - check both raw and normalized (matches runtime behavior)
const invalidRefs = yamlRefs.filter(
({ ref }) => !validRefs.has(ref) && !validRefs.has(normalizeRefPath(ref))
)
const validCount = yamlRefs.length - invalidRefs.length
// Output
console.log('=== YAML $ref NOT found in TypeSpec ===')
if (invalidRefs.length === 0) {
console.log('(none)')
} else {
invalidRefs.forEach(({ id, ref }) => console.log(`- ${ref} (id: ${id})`))
}
console.log(`\n=== Valid refs: ${validCount} | Invalid refs: ${invalidRefs.length} ===`)
// Exit with error code if any invalid refs
if (invalidRefs.length > 0) {
process.exit(1)
}
@@ -658,6 +658,13 @@
"product": "auth",
"type": "function"
},
{
"id": "auth-js-gotrueclient-initialize",
"title": "Initialize client session",
"slug": "auth-initialize",
"product": "auth",
"type": "function"
},
{
"id": "auth-mfa-api",
"title": "Auth MFA",
@@ -705,6 +712,58 @@
"slug": "auth-mfa-getauthenticatorassurancelevel",
"product": "auth",
"type": "function"
},
{
"id": "auth-js-gotruemfaapi-listfactors",
"title": "List all factors for current user",
"slug": "auth-mfa-listfactors",
"product": "auth",
"type": "function"
}
]
},
{
"id": "oauth-server-api",
"isFunc": false,
"title": "OAuth Server",
"slug": "auth-admin-oauth-server",
"product": "auth-admin",
"type": "function",
"items": [
{
"id": "auth-js-authoauthserverapi-getauthorizationdetails",
"title": "Get authorization details",
"slug": "auth-admin-oauth-getauthorizationdetails",
"product": "auth-admin",
"type": "function"
},
{
"id": "auth-js-authoauthserverapi-approveauthorization",
"title": "Approve authorization",
"slug": "auth-admin-oauth-approveauthorization",
"product": "auth-admin",
"type": "function"
},
{
"id": "auth-js-authoauthserverapi-denyauthorization",
"title": "Deny authorization",
"slug": "auth-admin-oauth-denyauthorization",
"product": "auth-admin",
"type": "function"
},
{
"id": "auth-js-authoauthserverapi-listgrants",
"title": "List grants",
"slug": "auth-admin-oauth-listgrants",
"product": "auth-admin",
"type": "function"
},
{
"id": "auth-js-authoauthserverapi-revokegrant",
"title": "Revoke grant",
"slug": "auth-admin-oauth-revokegrant",
"product": "auth-admin",
"type": "function"
}
]
},
@@ -770,6 +829,13 @@
"product": "auth-admin",
"type": "function"
},
{
"id": "auth-js-gotrueadminapi-signout",
"title": "Sign out a user (admin)",
"slug": "auth-admin-signout",
"product": "auth-admin",
"type": "function"
},
{
"id": "mfa-list-factors",
"title": "List all factors for a user",
@@ -783,6 +849,66 @@
"slug": "auth-admin-mfa-deletefactor",
"product": "auth-admin",
"type": "function"
},
{
"id": "mfa-list-factors-admin",
"title": "List all factors for a user (admin)",
"slug": "auth-admin-mfa-listfactors-admin",
"product": "auth-admin",
"type": "function"
},
{
"id": "oauth-admin-api",
"isFunc": false,
"title": "OAuth Admin",
"slug": "auth-admin-oauth-admin",
"product": "auth-admin",
"type": "function",
"items": [
{
"id": "auth-js-gotrueadminoauthapi-listclients",
"title": "List OAuth clients",
"slug": "auth-admin-oauth-listclients",
"product": "auth-admin",
"type": "function"
},
{
"id": "auth-js-gotrueadminoauthapi-getclient",
"title": "Get OAuth client",
"slug": "auth-admin-oauth-getclient",
"product": "auth-admin",
"type": "function"
},
{
"id": "auth-js-gotrueadminoauthapi-createclient",
"title": "Create OAuth client",
"slug": "auth-admin-oauth-createclient",
"product": "auth-admin",
"type": "function"
},
{
"id": "auth-js-gotrueadminoauthapi-updateclient",
"title": "Update OAuth client",
"slug": "auth-admin-oauth-updateclient",
"product": "auth-admin",
"type": "function"
},
{
"id": "auth-js-gotrueadminoauthapi-deleteclient",
"title": "Delete OAuth client",
"slug": "auth-admin-oauth-deleteclient",
"product": "auth-admin",
"type": "function"
},
{
"id": "auth-js-gotrueadminoauthapi-regenerateclientsecret",
"title": "Regenerate client secret",
"slug": "auth-admin-oauth-regenerateclientsecret",
"product": "auth-admin",
"type": "function"
}
]
}
]
}
+21 -51
View File
@@ -2254,6 +2254,8 @@ functions:
"error": null
}
```
- id: auth-js-gotrueclient-initialize
$ref: '@supabase/auth-js.GoTrueClient.initialize'
- id: auth-mfa-api
title: 'Overview'
notes: |
@@ -3225,8 +3227,9 @@ functions:
{ ban_duration: '100y' }
)
```
- id: auth-js-gotrueadminapi-signout
$ref: '@supabase/auth-js.GoTrueAdminApi.signOut'
- id: mfa-list-factors-admin
title: 'mfa.listFactors()'
$ref: '@supabase/auth-js.GoTrueAdminMFAApi.listFactors'
examples:
- id: list-factors
@@ -7645,78 +7648,45 @@ functions:
- id: supabase-js-supabaseclient-schema
title: SupabaseClient.schema()
$ref: '@supabase/supabase-js.SupabaseClient.schema'
- id: auth-js-gotrueadminapi-constructor
title: new GoTrueAdminApi()
$ref: '@supabase/auth-js.GoTrueAdminApi.constructor'
examples:
- id: auth-js-gotrueadminapi-constructor-example-1
name: Example 1
code: |-
```ts
import { GoTrueAdminApi } from '@supabase/auth-js'
- id: oauth-server-api
title: 'OAuth Server API'
notes: |
The OAuth Server API allows you to build custom OAuth consent screens for your application.
Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
const admin = new GoTrueAdminApi({
url: 'https://xyzcompany.supabase.co/auth/v1',
headers: { Authorization: `Bearer ${process.env.SUPABASE_SERVICE_ROLE_KEY}` },
})
```
- id: auth-js-gotrueadminapi-signout
title: GoTrueAdminApi.signOut()
$ref: '@supabase/auth-js.GoTrueAdminApi.signOut'
- id: auth-js-gotrueclient-constructor
title: new GoTrueClient()
$ref: '@supabase/auth-js.GoTrueClient.constructor'
examples:
- id: auth-js-gotrueclient-constructor-example-1
name: Example 1
code: |-
```ts
import { GoTrueClient } from '@supabase/auth-js'
const auth = new GoTrueClient({
url: 'https://xyzcompany.supabase.co/auth/v1',
headers: { apikey: 'public-anon-key' },
storageKey: 'supabase-auth',
})
```
- id: auth-js-gotrueclient-initialize
title: GoTrueClient.initialize()
$ref: '@supabase/auth-js.GoTrueClient.initialize'
- id: auth-js-gotrueclient-isthrowonerrorenabled
title: GoTrueClient.isThrowOnErrorEnabled()
$ref: '@supabase/auth-js.GoTrueClient.isThrowOnErrorEnabled'
- id: auth-js-authoauthserverapi-approveauthorization
title: AuthOAuthServerApi.approveAuthorization()
$ref: '@supabase/auth-js.AuthOAuthServerApi.approveAuthorization'
- id: auth-js-authoauthserverapi-denyauthorization
title: AuthOAuthServerApi.denyAuthorization()
$ref: '@supabase/auth-js.AuthOAuthServerApi.denyAuthorization'
- id: auth-js-authoauthserverapi-getauthorizationdetails
title: AuthOAuthServerApi.getAuthorizationDetails()
$ref: '@supabase/auth-js.AuthOAuthServerApi.getAuthorizationDetails'
- id: auth-js-authoauthserverapi-listgrants
$ref: '@supabase/auth-js.AuthOAuthServerApi.listGrants'
- id: auth-js-authoauthserverapi-revokegrant
title: AuthOAuthServerApi.revokeGrant()
$ref: '@supabase/auth-js.AuthOAuthServerApi.revokeGrant'
- id: oauth-admin-api
title: 'OAuth Admin API'
notes: |
The OAuth Admin API allows you to manage OAuth clients programmatically.
Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
These functions should only be called on a server. Never expose your `service_role` key in the browser.
- id: auth-js-gotrueadminoauthapi-createclient
title: GoTrueAdminOAuthApi.createClient()
$ref: '@supabase/auth-js.GoTrueAdminOAuthApi.createClient'
- id: auth-js-gotrueadminoauthapi-deleteclient
title: GoTrueAdminOAuthApi.deleteClient()
$ref: '@supabase/auth-js.GoTrueAdminOAuthApi.deleteClient'
- id: auth-js-gotrueadminoauthapi-getclient
title: GoTrueAdminOAuthApi.getClient()
$ref: '@supabase/auth-js.GoTrueAdminOAuthApi.getClient'
- id: auth-js-gotrueadminoauthapi-listclients
title: GoTrueAdminOAuthApi.listClients()
$ref: '@supabase/auth-js.GoTrueAdminOAuthApi.listClients'
- id: auth-js-gotrueadminoauthapi-regenerateclientsecret
title: GoTrueAdminOAuthApi.regenerateClientSecret()
$ref: '@supabase/auth-js.GoTrueAdminOAuthApi.regenerateClientSecret'
- id: auth-js-gotrueadminoauthapi-updateclient
title: GoTrueAdminOAuthApi.updateClient()
$ref: '@supabase/auth-js.GoTrueAdminOAuthApi.updateClient'
- id: auth-js-gotruemfaapi-listfactors
title: GoTrueMFAApi.listFactors()
$ref: '@supabase/auth-js.GoTrueMFAApi.listFactors'
- id: postgrest-js-postgrestbuilder-constructor
title: new PostgrestBuilder()
$ref: '@supabase/postgrest-js.PostgrestBuilder.constructor'
examples:
- id: postgrest-js-postgrestbuilder-constructor-example-1