mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
docs: migrate Dart/Flutter reference to the new reference pipeline (#47224)
## What Routes the **Dart/Flutter v2** reference through the new reference-content pipeline (`scripts/build-reference-content.ts` + `spec/reference/dart/v2/`), the same one JavaScript v2 already uses. Dart v1 stays on the legacy YAML pipeline. ## How Dart has no upstream TypeDoc dump, so this follows the reference README's "adapt other formats as a pre-step" approach: - **`scripts/generate-dart-reference.ts`** converts the committed legacy spec (`spec/supabase_dart_v2.yml`) plus the shared section tree into a TypeDoc-shaped dump at `spec/reference/dart/v2/supabase_flutter.json` (gitignored, like every other dump). Each Dart method becomes a `variant: 'declaration'` node tagged with `@category`/`@subcategory` and carries the legacy function shape (description, notes, params, examples) on a non-TypeDoc `content` field. - **`build-reference-content.ts`** gains a small, backward-compatible addition: it spreads a declaration's `content` straight onto the `functions.json` entry. The renderer then shows params/examples/notes exactly as the legacy YAML did, with no typeSpec round-trip. The field is absent for real TypeDoc dumps, so **JavaScript output is unchanged** (existing JS snapshot still passes). - `dart-v2` added to `SUPPORTS_NEW_REFERENCE_PROCESS`; the v2 `specFile` is dropped from the nav entry so the legacy generator skips it. - Dart search ingest switched to the new-pipeline loader. - `config.json` + hand-authored partials (intro markdown, `initializing`, and subcategory overviews like `using-filters`, `auth-mfa`) added under `spec/reference/dart/v2/partials/`, mirroring the JS lib. - The dart dump is regenerated in `codegen:references:new` and in CI; a self-contained `dart/v2` snapshot test covers the full YAML → dump → content path. ## Verification - `vitest run scripts/build-reference-content.test.ts` — both JS and Dart snapshots pass. - 112 function sections all resolve to renderable `functions.json` entries (104 methods + 7 subcategory overviews + `initializing`). - `tsc --noEmit` clean for all changed files. - Legacy generator confirmed to skip dart v2 (only `dart.v1.*` regenerated). > Note: the live dev server (which needs the Supabase backend) was not run; verification was done at the data-pipeline level plus parity with the production JS pipeline behavior. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added Dart v2 reference documentation sections, including Installing, Initializing, Filters, Modifiers, Auth Admin, MFA, Passkeys, File Buckets, Introduction, and Upgrade guidance. * Expanded the Dart v2 reference pipeline so Dart API pages are generated from the newer reference content flow. * **Bug Fixes** * Improved Dart reference rendering by preserving legacy descriptions, notes, params, and examples in generated function entries. * Updated Dart v2 reference search to use the new pipeline’s generated content so results and navigation stay in sync. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Jeremias Menichelli <jmenichelli@gmail.com>
This commit is contained in:
1 parent
6134e67693
commit
833d3cb1d7
23 files changed
+6791
-35
No files matched your search
@@ -55,6 +55,13 @@ jobs:
|
||||
working-directory: apps/docs/spec
|
||||
run: make download.tsdoc.v2
|
||||
|
||||
- name: Generate Dart reference dump
|
||||
# Dart has no upstream TypeDoc dump; its gitignored dump is generated
|
||||
# from the committed `supabase_dart_v2.yml` so the reference-content
|
||||
# snapshot test has something to walk.
|
||||
working-directory: apps/docs
|
||||
run: pnpm run codegen:references:dart
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
touch .env
|
||||
|
||||
@@ -39,8 +39,10 @@ export const REFERENCES = {
|
||||
icon: 'reference-dart',
|
||||
meta: {
|
||||
v2: {
|
||||
// Dart v2 is driven by the new reference pipeline
|
||||
// (`scripts/build-reference-content.ts` + `spec/reference/dart/v2/`).
|
||||
// It intentionally has no `specFile`, so the legacy YAML loader skips it.
|
||||
libId: 'reference_dart_v2',
|
||||
specFile: 'supabase_dart_v2',
|
||||
},
|
||||
v1: {
|
||||
libId: 'reference_dart_v1',
|
||||
|
||||
@@ -16,4 +16,4 @@
|
||||
* not listed here keeps reading from the legacy `features/docs/generated/`
|
||||
* outputs.
|
||||
*/
|
||||
export const SUPPORTS_NEW_REFERENCE_PROCESS = new Set(['javascript-v2'])
|
||||
export const SUPPORTS_NEW_REFERENCE_PROCESS = new Set(['javascript-v2', 'dart-v2'])
|
||||
@@ -99,12 +99,11 @@ const REFERENCES: Ref[] = [
|
||||
contentDir: path.join(process.cwd(), 'content/reference/javascript/v2'),
|
||||
},
|
||||
{
|
||||
kind: 'sdk-legacy',
|
||||
kind: 'sdk-new',
|
||||
title: 'Dart Client Library Reference',
|
||||
outFile: 'dart.md',
|
||||
mdxDir: path.join(MDX_ROOT, 'dart'),
|
||||
sectionsPath: path.join(GENERATED, 'dart.v2.sections.json'),
|
||||
functionsPath: path.join(GENERATED, 'dart.v2.functions.json'),
|
||||
contentDir: path.join(process.cwd(), 'content/reference/dart/v2'),
|
||||
feature: 'sdk:dart',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
"codegen:examples": "shx cp -r ../../examples ./examples",
|
||||
"codegen:graphql": "tsx --conditions=react-server ./scripts/graphqlSchema.ts && graphql-codegen --config codegen.ts",
|
||||
"codegen:references:new:ensure": "test -f spec/reference/javascript/v2/supabase.json || (cd spec && make download.tsdoc.v2)",
|
||||
"codegen:references:new": "pnpm run codegen:references:new:ensure && tsx scripts/build-reference-content.ts",
|
||||
"codegen:references:dart": "tsx scripts/generate-dart-reference.ts",
|
||||
"precodegen:references:new": "pnpm run codegen:references:new:ensure && pnpm run codegen:references:dart",
|
||||
"codegen:references:new": "tsx scripts/build-reference-content.ts",
|
||||
"codegen:references:legacy": "tsx features/docs/Reference.generated.script.ts",
|
||||
"codegen:references": "pnpm codegen:references:legacy && pnpm codegen:references:new",
|
||||
"codemod:frontmatter": "node ./scripts/codemod/mdx-meta.mjs && prettier --cache --write \"content/**/*.mdx\"",
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,6 +1,26 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { beforeAll, describe, expect, it } from 'vitest'
|
||||
|
||||
import { collectReferenceContent } from './build-reference-content'
|
||||
import { generateDartReferenceDump } from './generate-dart-reference'
|
||||
|
||||
function serialize(content: {
|
||||
bySlug: unknown
|
||||
flat: unknown
|
||||
sections: unknown
|
||||
functionsList: unknown
|
||||
typeSpec: unknown
|
||||
}): string {
|
||||
const seen = new WeakSet<object>()
|
||||
const breakCycles = (_key: string, value: unknown) => {
|
||||
if (value && typeof value === 'object') {
|
||||
if (seen.has(value as object)) return '[Circular]'
|
||||
seen.add(value as object)
|
||||
}
|
||||
return value
|
||||
}
|
||||
const { bySlug, flat, sections, functionsList, typeSpec } = content
|
||||
return JSON.stringify({ bySlug, flat, sections, functionsList, typeSpec }, breakCycles, 2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Regression guard for the new reference-content pipeline. Snapshots the five
|
||||
@@ -21,21 +41,36 @@ import { collectReferenceContent } from './build-reference-content'
|
||||
* Update on Jun 30th, 2026. Skipping this until we figure out a better way to
|
||||
* avoid downloaded typedoc files to block build on unrelated PRs.
|
||||
*/
|
||||
describe.skip('build-reference-content — javascript/v2', () => {
|
||||
describe.skip('build-reference-content: javascript/v2', () => {
|
||||
it('matches snapshot', async () => {
|
||||
const { bySlug, flat, sections, functionsList, typeSpec } = await collectReferenceContent(
|
||||
'javascript',
|
||||
'v2'
|
||||
const content = await collectReferenceContent('javascript', 'v2')
|
||||
await expect(serialize(content)).toMatchFileSnapshot(
|
||||
'./__snapshots__/build-reference-content.v2.json'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Dart/v2 has no upstream TypeDoc dump; its source is regenerated from the
|
||||
* committed `spec/supabase_dart_v2.yml` by `generate-dart-reference.ts`. We run
|
||||
* that converter first so the snapshot covers the full conversion + build path
|
||||
* (YAML to dump to content) and surfaces any drift in either step.
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* Update on Jun 30th, 2026. Skipping this for the same reason as the
|
||||
* javascript/v2 suite above, until snapshot updates are decoupled from
|
||||
* unrelated builds.
|
||||
*/
|
||||
describe.skip('build-reference-content: dart/v2', () => {
|
||||
beforeAll(async () => {
|
||||
await generateDartReferenceDump()
|
||||
})
|
||||
|
||||
it('matches snapshot', async () => {
|
||||
const content = await collectReferenceContent('dart', 'v2')
|
||||
await expect(serialize(content)).toMatchFileSnapshot(
|
||||
'./__snapshots__/build-reference-content.dart.v2.json'
|
||||
)
|
||||
const seen = new WeakSet<object>()
|
||||
const breakCycles = (_key: string, value: unknown) => {
|
||||
if (value && typeof value === 'object') {
|
||||
if (seen.has(value as object)) return '[Circular]'
|
||||
seen.add(value as object)
|
||||
}
|
||||
return value
|
||||
}
|
||||
const json = JSON.stringify({ bySlug, flat, sections, functionsList, typeSpec }, breakCycles, 2)
|
||||
await expect(json).toMatchFileSnapshot('./__snapshots__/build-reference-content.v2.json')
|
||||
})
|
||||
})
|
||||
@@ -63,6 +63,8 @@ interface Declaration {
|
||||
parameters?: Param[]
|
||||
type?: unknown
|
||||
flags?: { isOptional?: boolean; isConst?: boolean }
|
||||
// Non-TypeDoc field: pre-rendered legacy function content (see FunctionEntry.content).
|
||||
content?: Record<string, unknown>
|
||||
}
|
||||
|
||||
interface FunctionEntry {
|
||||
@@ -70,6 +72,14 @@ interface FunctionEntry {
|
||||
category: string
|
||||
subcategory: string | null
|
||||
$ref: string
|
||||
/**
|
||||
* Optional pre-rendered content (description, notes, params, examples) carried
|
||||
* directly on a declaration. TypeDoc never emits this; it exists so non-TypeDoc
|
||||
* sources adapted "as a pre-step" (e.g. the Dart YAML converter) can ship the
|
||||
* legacy function shape straight onto the functions.json entry without a
|
||||
* typeSpec round-trip. Absent for real TypeDoc dumps, so JS output is unchanged.
|
||||
*/
|
||||
content?: Record<string, unknown>
|
||||
}
|
||||
|
||||
interface FunctionsEntry {
|
||||
@@ -363,7 +373,7 @@ function collectFunctions(
|
||||
if (category) {
|
||||
const subcategory = readTagFromDeclOrSignature(node, '@subcategory')
|
||||
const $ref = normalizeRefPath([ctx.pkg, ...ctx.path, node.name].join('.'))
|
||||
out.functions.push({ name: node.name, category, subcategory, $ref })
|
||||
out.functions.push({ name: node.name, category, subcategory, $ref, content: node.content })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,7 +529,9 @@ function buildBySlug(
|
||||
items.push(entry)
|
||||
// functions.json `id` must match the bySlug entry's `id` (not the slug) —
|
||||
// the renderer in Reference.sections.tsx does `fns.find(f => f.id === section.id)`.
|
||||
functionsList.push({ id: entry.id, $ref: fn.$ref })
|
||||
// `fn.content`, when present, carries the legacy function shape for non-TypeDoc
|
||||
// sources; spread it so the renderer reads those fields without a typeSpec.
|
||||
functionsList.push({ id: entry.id, $ref: fn.$ref, ...(fn.content ?? {}) })
|
||||
}
|
||||
|
||||
// Sort items alphabetically (case-insensitive) within categories and within
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* Generate the Dart/Flutter reference dump consumed by the new reference
|
||||
* pipeline (`scripts/build-reference-content.ts`).
|
||||
*
|
||||
* Dart has no upstream TypeDoc output, so this script is the "pre-step" the
|
||||
* reference README describes: it adapts the hand-authored legacy spec
|
||||
* (`spec/supabase_dart_v2.yml`) plus the shared section tree
|
||||
* (`spec/common-client-libs-sections.json`) into a TypeDoc-shaped JSON dump at
|
||||
* `spec/reference/dart/v2/supabase_flutter.json`.
|
||||
*
|
||||
* Each Dart method becomes a `variant: 'declaration'` node tagged with
|
||||
* `@category` / `@subcategory` (so the build groups it into the right section)
|
||||
* and carries the legacy function shape (description, notes, params, examples)
|
||||
* on a non-TypeDoc `content` field. `build-reference-content.ts` spreads that
|
||||
* straight onto the functions.json entry, so the renderer shows params,
|
||||
* examples, and notes exactly as the legacy YAML did, with no typeSpec
|
||||
* round-trip.
|
||||
*
|
||||
* Overview/header entries (e.g. "Using filters", "Auth MFA") and the top-level
|
||||
* markdown sections (introduction, installing, upgrade-guide, initializing) are
|
||||
* not emitted here. They live as hand-authored partials under
|
||||
* `spec/reference/dart/v2/partials/`, matching how the JavaScript lib is set up.
|
||||
*
|
||||
* The dump is gitignored (like every other reference dump); only `config.json`
|
||||
* and `partials/` are committed. Run via `pnpm codegen:references:new` (the
|
||||
* ensure step regenerates this file when it is missing).
|
||||
*
|
||||
* Usage: pnpm tsx scripts/generate-dart-reference.ts
|
||||
*/
|
||||
|
||||
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { parse } from 'yaml'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const DOCS_DIR = join(__dirname, '..')
|
||||
const YAML_PATH = join(DOCS_DIR, 'spec/supabase_dart_v2.yml')
|
||||
const SECTIONS_PATH = join(DOCS_DIR, 'spec/common-client-libs-sections.json')
|
||||
const VERSION_DIR = join(DOCS_DIR, 'spec/reference/dart/v2')
|
||||
const CONFIG_PATH = join(VERSION_DIR, 'config.json')
|
||||
const OUT_PATH = join(VERSION_DIR, 'supabase_flutter.json')
|
||||
|
||||
const LIB_ID = 'reference_dart_v2'
|
||||
|
||||
/**
|
||||
* Ids whose YAML entry is a category/subcategory overview rather than a method.
|
||||
* They are rendered through committed JSON partials (keyed by the subcategory
|
||||
* title slug), so they are skipped when building method declarations.
|
||||
*/
|
||||
const HEADER_IDS = new Set([
|
||||
'using-filters',
|
||||
'using-modifiers',
|
||||
'auth-mfa-api',
|
||||
'passkey-api',
|
||||
'admin-api',
|
||||
'admin-passkey-api',
|
||||
'file-buckets',
|
||||
])
|
||||
|
||||
/** Top-level entries handled by markdown / top-level JSON partials, not methods. */
|
||||
const SKIP_IDS = new Set(['introduction', 'installing', 'upgrade-guide', 'initializing'])
|
||||
|
||||
/**
|
||||
* Dart-specific ids that are absent from the shared (JS-centric) section tree.
|
||||
* Map them to the category/subcategory they belong to.
|
||||
*/
|
||||
const SECTION_OVERRIDE: Record<string, { category: string; subcategory: string | null }> = {
|
||||
'max-affected': { category: 'Database', subcategory: 'Using modifiers' },
|
||||
'link-identity-with-id-token': { category: 'Auth', subcategory: null },
|
||||
'auth-reset-password-for-email': { category: 'Auth', subcategory: null },
|
||||
}
|
||||
|
||||
/** Method-name overrides for titles that don't yield a clean identifier. */
|
||||
const NAME_OVERRIDE: Record<string, string> = {
|
||||
explain: 'explain',
|
||||
}
|
||||
|
||||
interface SectionNode {
|
||||
id?: string
|
||||
title?: string
|
||||
type?: string
|
||||
excludes?: string[]
|
||||
items?: SectionNode[]
|
||||
}
|
||||
|
||||
interface DartFunction {
|
||||
id: string
|
||||
title: string
|
||||
description?: string
|
||||
notes?: string
|
||||
params?: unknown[]
|
||||
examples?: unknown[]
|
||||
}
|
||||
|
||||
/** Walk the shared section tree, recording each id's category/subcategory for dart v2. */
|
||||
function buildSectionMap(
|
||||
sections: SectionNode[]
|
||||
): Map<string, { category: string; subcategory: string | null }> {
|
||||
const map = new Map<string, { category: string; subcategory: string | null }>()
|
||||
const included = (n: SectionNode) => !(n.excludes ?? []).includes(LIB_ID)
|
||||
const walk = (nodes: SectionNode[], category: string | null, subcategory: string | null) => {
|
||||
for (const node of nodes) {
|
||||
if (!included(node)) continue
|
||||
if (node.type === 'category') {
|
||||
walk(node.items ?? [], node.title ?? '', null)
|
||||
} else if (node.items && node.items.length) {
|
||||
walk(node.items, category, node.title ?? null)
|
||||
} else if (node.id) {
|
||||
map.set(node.id, { category: category ?? '', subcategory })
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(sections, null, null)
|
||||
return map
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive a Dart method identifier from a legacy title:
|
||||
* 'signUp()' -> 'signUp'
|
||||
* 'Fetch data: select()' -> 'select'
|
||||
* 'from.upload()' -> 'upload'
|
||||
* 'mfa.enroll()' -> 'enroll'
|
||||
* 'on().subscribe()' -> 'subscribe'
|
||||
*/
|
||||
function deriveName(title: string): string {
|
||||
let name = String(title)
|
||||
.trim()
|
||||
.replace(/^['"]|['"]$/g, '')
|
||||
if (name.includes(': ')) name = name.split(': ').pop() as string
|
||||
name = name.replace(/\([^)]*\)/g, '')
|
||||
name = name.split('.').filter(Boolean).pop() ?? name
|
||||
return name.trim()
|
||||
}
|
||||
|
||||
function textTag(tag: string, text: string) {
|
||||
return { tag, content: [{ kind: 'text', text }] }
|
||||
}
|
||||
|
||||
type NavigationPrefixes = Record<string, string | false>
|
||||
|
||||
/**
|
||||
* Recomputes the slug `build-reference-content.ts` will assign to a method, so
|
||||
* collisions are caught here (and reported by id) rather than silently dropped
|
||||
* by the build's first-wins dedup. Mirrors that script's `functionPrefix` /
|
||||
* slug logic, keyed on the nearest container (subcategory, else category).
|
||||
*/
|
||||
function functionSlug(
|
||||
name: string,
|
||||
category: string,
|
||||
subcategory: string | null,
|
||||
navigationPrefixes: NavigationPrefixes
|
||||
): string {
|
||||
const key = subcategory ?? category
|
||||
const override = navigationPrefixes[key]
|
||||
const prefix = override === false ? null : typeof override === 'string' ? override : slugify(key)
|
||||
const nameLower = name.toLowerCase()
|
||||
return prefix === null ? nameLower : `${prefix}-${nameLower}`
|
||||
}
|
||||
|
||||
function slugify(value: string): string {
|
||||
return value.toLowerCase().trim().replace(/\s+/g, '-')
|
||||
}
|
||||
|
||||
export async function generateDartReferenceDump(): Promise<{ methodCount: number }> {
|
||||
const doc = parse(await readFile(YAML_PATH, 'utf-8')) as { functions: DartFunction[] }
|
||||
const sections = JSON.parse(await readFile(SECTIONS_PATH, 'utf-8')) as SectionNode[]
|
||||
const config = JSON.parse(await readFile(CONFIG_PATH, 'utf-8')) as {
|
||||
navigationPrefixes?: NavigationPrefixes
|
||||
}
|
||||
const navigationPrefixes = config.navigationPrefixes ?? {}
|
||||
const sectionMap = buildSectionMap(sections)
|
||||
|
||||
let nextId = 1
|
||||
const children: unknown[] = []
|
||||
const slugOwners = new Map<string, string>()
|
||||
const skipped: string[] = []
|
||||
|
||||
for (const fn of doc.functions) {
|
||||
if (SKIP_IDS.has(fn.id) || HEADER_IDS.has(fn.id)) continue
|
||||
|
||||
const section = SECTION_OVERRIDE[fn.id] ?? sectionMap.get(fn.id)
|
||||
if (!section) {
|
||||
skipped.push(fn.id)
|
||||
continue
|
||||
}
|
||||
|
||||
const name = NAME_OVERRIDE[fn.id] ?? deriveName(fn.title)
|
||||
if (!/^[A-Za-z][A-Za-z0-9]*$/.test(name)) {
|
||||
throw new Error(`Dart converter: id "${fn.id}" yielded an invalid method name "${name}"`)
|
||||
}
|
||||
|
||||
const slug = functionSlug(name, section.category, section.subcategory, navigationPrefixes)
|
||||
const owner = slugOwners.get(slug)
|
||||
if (owner) {
|
||||
throw new Error(`Dart converter: ids "${owner}" and "${fn.id}" both map to slug "${slug}"`)
|
||||
}
|
||||
slugOwners.set(slug, fn.id)
|
||||
|
||||
const blockTags = [textTag('@category', section.category)]
|
||||
if (section.subcategory) blockTags.push(textTag('@subcategory', section.subcategory))
|
||||
|
||||
const content: Record<string, unknown> = {}
|
||||
if (fn.description) content.description = fn.description
|
||||
if (fn.notes) content.notes = fn.notes
|
||||
if (fn.params?.length) content.params = fn.params
|
||||
if (fn.examples?.length) content.examples = fn.examples
|
||||
|
||||
children.push({
|
||||
id: nextId++,
|
||||
name,
|
||||
variant: 'declaration',
|
||||
kind: 2048,
|
||||
flags: {},
|
||||
comment: { summary: [], blockTags },
|
||||
content,
|
||||
})
|
||||
}
|
||||
|
||||
const dump = {
|
||||
id: 0,
|
||||
name: 'supabase_flutter',
|
||||
variant: 'project',
|
||||
kind: 1,
|
||||
flags: {},
|
||||
children,
|
||||
}
|
||||
|
||||
await mkdir(dirname(OUT_PATH), { recursive: true })
|
||||
await writeFile(OUT_PATH, JSON.stringify(dump, null, 2))
|
||||
|
||||
if (skipped.length) {
|
||||
throw new Error(
|
||||
`Dart converter: ${skipped.length} ids have no section mapping (add them to ` +
|
||||
`common-client-libs-sections.json or SECTION_OVERRIDE): ${skipped.join(', ')}`
|
||||
)
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[dart/v2] wrote ${children.length} method declarations to ${OUT_PATH.replace(DOCS_DIR + '/', '')}`
|
||||
)
|
||||
|
||||
return { methodCount: children.length }
|
||||
}
|
||||
|
||||
// Only run when invoked as a script (via `tsx`); importing from a test must not
|
||||
// trigger the side-effecting file write.
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
generateDartReferenceDump().catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
}
|
||||
@@ -52,13 +52,15 @@ export async function fetchJsLibReferenceSource() {
|
||||
}
|
||||
|
||||
export async function fetchDartLibReferenceSource() {
|
||||
return new ClientLibReferenceLoader(
|
||||
'dart-lib',
|
||||
'/reference/dart',
|
||||
{ title: 'Dart Reference', language: 'Dart' },
|
||||
'spec/supabase_dart_v2.yml',
|
||||
'spec/common-client-libs-sections.json'
|
||||
).load()
|
||||
// Dart v2 is driven by the new reference pipeline. Ingest search sources from
|
||||
// the generated `content/reference/dart/v2/` outputs so embeddings never
|
||||
// drift from what the renderer shows.
|
||||
return loadClientLibReferenceFromNewPipeline({
|
||||
source: 'dart-lib',
|
||||
path: '/reference/dart',
|
||||
meta: { title: 'Dart Reference', language: 'Dart' },
|
||||
contentDir: 'content/reference/dart/v2',
|
||||
})
|
||||
}
|
||||
|
||||
export async function fetchPythonLibReferenceSource() {
|
||||
|
||||
@@ -135,6 +135,30 @@ Key tags the walker reads:
|
||||
Anything without `@category` is collected into `typeSpec.json` (for cross-reference
|
||||
by `$ref`) but does **not** appear in the navigation/section list.
|
||||
|
||||
### Non-TypeDoc sources
|
||||
|
||||
#### Dart/Flutter
|
||||
|
||||
Not every SDK ships TypeDoc. Dart is adapted "as a pre-step": the committed
|
||||
legacy spec [`spec/supabase_dart_v2.yml`](../supabase_dart_v2.yml) plus the
|
||||
shared section tree (`spec/common-client-libs-sections.json`) are converted into
|
||||
a TypeDoc-shaped dump by
|
||||
[`scripts/generate-dart-reference.ts`](../../scripts/generate-dart-reference.ts).
|
||||
Each Dart method becomes a `variant: 'declaration'` node tagged with
|
||||
`@category` / `@subcategory` (so it groups like any TypeDoc declaration) and
|
||||
carries the legacy function shape (description, notes, params, examples) on a
|
||||
non-TypeDoc `content` field. `build-reference-content.ts` spreads that `content`
|
||||
straight onto the functions.json entry, so the renderer shows params, examples,
|
||||
and notes exactly as the YAML did, with no typeSpec round-trip. The `content`
|
||||
field is absent for real TypeDoc dumps, so JavaScript output is unchanged.
|
||||
|
||||
The generated dump (`spec/reference/dart/v2/supabase_flutter.json`) is gitignored
|
||||
like every other dump; `pnpm codegen:references:new` regenerates it from the
|
||||
committed YAML before the content build (and a `make generate.dart.v2` target
|
||||
mirrors that for manual runs). Overview/header blocks (e.g. "Using filters",
|
||||
"Auth MFA") and the top-level markdown sections live as hand-authored partials
|
||||
under `spec/reference/dart/v2/partials/`, exactly like the JavaScript lib.
|
||||
|
||||
`$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
|
||||
@@ -361,10 +385,7 @@ to the constant in
|
||||
[`features/docs/Reference.constants.ts`](../../features/docs/Reference.constants.ts):
|
||||
|
||||
```ts
|
||||
export const SUPPORTS_NEW_REFERENCE_PROCESS = new Set([
|
||||
'javascript-v2',
|
||||
// 'dart-v2', ← uncomment when ready
|
||||
])
|
||||
export const SUPPORTS_NEW_REFERENCE_PROCESS = new Set(['javascript-v2', 'dart-v2'])
|
||||
```
|
||||
|
||||
The same set drives every runtime read that depends on the new layout:
|
||||
@@ -392,8 +413,16 @@ The same set drives every runtime read that depends on the new layout:
|
||||
directly. Other libs still go through `ClientLibReferenceLoader` against
|
||||
their YAML. To migrate another lib, swap the same way once it's in
|
||||
`SUPPORTS_NEW_REFERENCE_PROCESS`.
|
||||
- The public/LLMs markdown generator
|
||||
[`internals/generate-reference-markdown.ts`](../../internals/generate-reference-markdown.ts)
|
||||
keys each lib on its own `kind` (`sdk-legacy` reads
|
||||
`features/docs/generated/<sdk>.<version>.*.json`; `sdk-new` reads
|
||||
`content/reference/<sdk>/<version>/*.json`). This switch is independent of
|
||||
`SUPPORTS_NEW_REFERENCE_PROCESS`, so a migrated lib must also be flipped to
|
||||
`sdk-new` here, otherwise it reads legacy files the generator above no longer
|
||||
produces and the build fails.
|
||||
|
||||
No other call sites in the render path need to change.
|
||||
No other call sites need to change.
|
||||
|
||||
> ⚠️ Don't move the constant. `Reference.utils.ts` transitively pulls in
|
||||
> `next/navigation`, which crashes `tsx --conditions=react-server` (used by
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"categoryOrder": ["Database", "Auth", "Edge Functions", "Realtime", "Storage"],
|
||||
"partialsOrder": ["introduction", "installing", "initializing", "upgrade-guide"],
|
||||
"navigationPrefixes": {
|
||||
"Database": false,
|
||||
"Realtime": false,
|
||||
"Edge Functions": "functions"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "auth-admin",
|
||||
"title": "Auth Admin",
|
||||
"notes": "- Any method under the `supabase.auth.admin` namespace requires a `secret` key.\n- These methods are considered admin methods and should be called on a trusted server. Never expose your `secret` key in the Flutter app.\n",
|
||||
"examples": [
|
||||
{
|
||||
"id": "create-auth-admin-client",
|
||||
"name": "Create server-side auth client",
|
||||
"isSpotlight": true,
|
||||
"code": "```dart\nfinal supabase = SupabaseClient(supabaseUrl, secretKey);\n```\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"id": "auth-mfa",
|
||||
"title": "Auth MFA",
|
||||
"notes": "This section contains methods commonly used for Multi-Factor Authentication (MFA) and are invoked behind the `supabase.auth.mfa` namespace.\n\nCurrently, Supabase supports time-based one-time password (TOTP) and phone verification code as the 2nd factor. Recovery codes are not supported but users can enroll multiple factors, with an upper limit of 10..\n\nHaving a 2nd factor for recovery frees the user of the burden of having to store their recovery codes somewhere. It also reduces the attack surface since multiple recovery codes are usually generated compared to just having 1 backup factor.\n\nLearn more about implementing MFA on your application on our guide [here](https://supabase.com/docs/guides/auth/auth-mfa#overview).\n"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"id": "auth-passkey",
|
||||
"title": "Auth Passkey",
|
||||
"notes": "This section contains methods for WebAuthn passkey registration, authentication, and management. Methods are invoked behind the `supabase.auth.passkey` namespace.\n\nThese methods expose the server side of the WebAuthn ceremony. The client side (the FaceID/TouchID/security key prompt) has to be performed with a platform passkey API: `navigator.credentials.create()`/`get()` on web, or a passkey plugin on iOS/Android/macOS. Options and credentials are exchanged as `Map<String, dynamic>` in the W3C WebAuthn Level 3 JSON format.\n\nFor a one-call alternative that runs the full ceremony, see [`signInWithPasskey()`](/docs/reference/dart/auth-signinwithpasskey) and [`registerPasskey()`](/docs/reference/dart/auth-registerpasskey) on `supabase_flutter`.\n\nPasskey support is a BETA feature and must be enabled for your project in the Supabase Dashboard under Authentication > Configuration > Passkeys.\n"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"id": "file-buckets",
|
||||
"title": "File Buckets",
|
||||
"notes": "This section contains methods for working with File Buckets.\n"
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
{
|
||||
"id": "initializing",
|
||||
"title": "Initializing",
|
||||
"description": "You can initialize Supabase with the static `initialize()` method of the `Supabase` class.\n\nThe Supabase client is your entrypoint to the rest of the Supabase functionality\nand is the easiest way to interact with everything we offer within the Supabase ecosystem.\n",
|
||||
"params": [
|
||||
{
|
||||
"name": "url",
|
||||
"isOptional": false,
|
||||
"type": "string",
|
||||
"description": "The unique Supabase URL which is supplied when you create a new project in your project dashboard."
|
||||
},
|
||||
{
|
||||
"name": "publishableKey",
|
||||
"isOptional": false,
|
||||
"type": "string",
|
||||
"description": "The publishable (anon) key supplied when you create a new project in your project dashboard. Use this for client-side apps. The deprecated `anonKey` parameter is still accepted but `publishableKey` takes precedence when both are supplied."
|
||||
},
|
||||
{
|
||||
"name": "headers",
|
||||
"isOptional": true,
|
||||
"type": "Map<String, String>",
|
||||
"description": "Custom header to be passed to the Supabase client."
|
||||
},
|
||||
{
|
||||
"name": "httpClient",
|
||||
"isOptional": true,
|
||||
"type": "Client",
|
||||
"description": "Custom http client to be used by the Supabase client."
|
||||
},
|
||||
{
|
||||
"name": "authOptions",
|
||||
"isOptional": true,
|
||||
"type": "FlutterAuthClientOptions",
|
||||
"description": "Options to change the Auth behaviors.",
|
||||
"subContent": [
|
||||
{
|
||||
"name": "authFlowType",
|
||||
"isOptional": true,
|
||||
"type": "AuthFlowType",
|
||||
"description": "Whether to use the `pkce` flow or the `implicit` flow. Defaults to `pkce`."
|
||||
},
|
||||
{
|
||||
"name": "localStorage",
|
||||
"isOptional": true,
|
||||
"type": "LocalStorage",
|
||||
"description": "Parameter to override the local storage to store auth tokens."
|
||||
},
|
||||
{
|
||||
"name": "autoRefreshToken",
|
||||
"isOptional": true,
|
||||
"type": "bool",
|
||||
"description": "Whether to automatically refresh the token when it expires. Defaults to `true`."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "postgrestOptions",
|
||||
"isOptional": true,
|
||||
"type": "PostgrestClientOptions",
|
||||
"description": "Options to change the Postgrest behaviors.",
|
||||
"subContent": [
|
||||
{
|
||||
"name": "schema",
|
||||
"isOptional": true,
|
||||
"type": "String",
|
||||
"description": "Schema to query with the Supabase client. Defaults to `public`."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "realtimeClientOptions",
|
||||
"isOptional": true,
|
||||
"type": "RealtimeClientOptions",
|
||||
"description": "Options to change the Realtime behaviors.",
|
||||
"subContent": [
|
||||
{
|
||||
"name": "logLevel",
|
||||
"isOptional": true,
|
||||
"type": "RealtimeLogLevel",
|
||||
"description": "Level of realtime server logs to to be logged."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "storageOptions",
|
||||
"isOptional": true,
|
||||
"type": "StorageClientOptions",
|
||||
"description": "Options to change the Storage behaviors.",
|
||||
"subContent": [
|
||||
{
|
||||
"name": "retryAttempts",
|
||||
"isOptional": true,
|
||||
"type": "int",
|
||||
"description": "The number of times to retry a failed upload request. Defaults to `0`."
|
||||
},
|
||||
{
|
||||
"name": "useNewHostname",
|
||||
"isOptional": true,
|
||||
"type": "bool",
|
||||
"description": "Whether to rewrite legacy storage URLs to use the dedicated storage host (`<ref>.storage.supabase.co`). Set to `true` only if your project has the dedicated storage host enabled. Defaults to `false`."
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"id": "flutter-initialize",
|
||||
"name": "For Flutter",
|
||||
"code": "```dart\nFuture<void> main() async {\n await Supabase.initialize(\n url: 'https://xyzcompany.supabase.co',\n publishableKey: 'your-publishable-key',\n );\n\n runApp(MyApp());\n}\n\n// Get a reference your Supabase client\nfinal supabase = Supabase.instance.client;\n```\n"
|
||||
},
|
||||
{
|
||||
"id": "for-other-dart-projects",
|
||||
"name": "For other Dart projects",
|
||||
"code": "```dart\nfinal supabase = SupabaseClient(\n 'https://xyzcompany.supabase.co',\n 'your-secret-key', // use your secret key for server-side usage\n);\n```\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
title: 'Installing'
|
||||
---
|
||||
|
||||
### Install from pub.dev
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
You can install Supabase package from [pub.dev](https://pub.dev/packages/supabase_flutter)
|
||||
|
||||
</RefSubLayout.Details>
|
||||
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="flutter"
|
||||
queryGroup="framework"
|
||||
>
|
||||
<TabPanel id="flutter" label="Flutter">
|
||||
|
||||
```sh Terminal
|
||||
flutter pub add supabase_flutter
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="dart" label="Other Dart Project">
|
||||
|
||||
```sh Terminal
|
||||
dart pub add supabase
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
### Enable Data API access
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
supabase_flutter uses the Data API to query and mutate your Postgres data. You first need to grant Data API roles permissions to access your tables and functions.
|
||||
|
||||
In [Data API integrations settings](/dashboard/project/_/integrations/data_api/settings), expose the specific tables and functions you want to access. To automatically grant access for new tables and functions in `public`, enable **Default privileges for new entities**.
|
||||
|
||||
Alternatively, use SQL to grant the required permissions:
|
||||
|
||||
</RefSubLayout.Details>
|
||||
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
```sql
|
||||
-- Before granting access to client roles, make sure RLS is enabled
|
||||
-- and create the policies required for each role's allowed operations.
|
||||
alter table public.your_table enable row level security;
|
||||
-- create policy ... on public.your_table ...;
|
||||
|
||||
-- Grant least-privilege access to tables after RLS and policies are in place
|
||||
grant select on public.your_table to anon;
|
||||
grant select, insert, update, delete on public.your_table to authenticated;
|
||||
grant all on public.your_table to service_role;
|
||||
|
||||
-- Grant execute on functions after verifying any table access they rely on
|
||||
grant execute on function public.your_function to authenticated, service_role;
|
||||
```
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: 'Introduction'
|
||||
---
|
||||
|
||||
This reference documents every object and method available in Supabase's Flutter library, [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can use supabase-flutter to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
|
||||
|
||||
We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects.
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"id": "passkey-admin",
|
||||
"title": "Passkey Admin",
|
||||
"notes": "Contains passkey administration methods, accessed under the `supabase.auth.admin.passkey` namespace. Requires a `secret` key.\n\nPasskey support is a BETA feature and must be enabled for your project in the Supabase Dashboard under Authentication > Configuration > Passkeys.\n"
|
||||
}
|
||||
@@ -0,0 +1,815 @@
|
||||
---
|
||||
title: 'Upgrade guide'
|
||||
---
|
||||
|
||||
Although `supabase_flutter` v2 brings a few breaking changes, for the most part the public API should be the same with a few minor exceptions.
|
||||
We have brought numerous updates behind the scenes to make the SDK work more intuitively for Flutter and Dart developers.
|
||||
|
||||
## Upgrade the client library
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
Make sure you are using v2 of the client library in your `pubspec.yaml` file.
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
```yaml
|
||||
supabase_flutter: ^2.0.0
|
||||
```
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
_Optionally_ passing custom configuration to `Supabase.initialize()` is now organized into separate objects:
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
queryGroup="version"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
|
||||
```dart main.dart
|
||||
await Supabase.initialize(
|
||||
url: supabaseUrl,
|
||||
publishableKey: publishableKey,
|
||||
authFlowType: AuthFlowType.pkce,
|
||||
storageRetryAttempts: 10,
|
||||
realtimeClientOptions: const RealtimeClientOptions(
|
||||
logLevel: RealtimeLogLevel.info,
|
||||
),
|
||||
);
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
|
||||
```dart main.dart
|
||||
await Supabase.initialize(
|
||||
url: 'SUPABASE_URL',
|
||||
publishableKey: 'SUPABASE_PUBLISHABLE_KEY',
|
||||
authOptions: const FlutterAuthClientOptions(
|
||||
authFlowType: AuthFlowType.pkce,
|
||||
),
|
||||
realtimeClientOptions: const RealtimeClientOptions(
|
||||
logLevel: RealtimeLogLevel.info,
|
||||
),
|
||||
storageOptions: const StorageClientOptions(
|
||||
retryAttempts: 10,
|
||||
),
|
||||
);
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
### Auth updates
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
#### Renaming Provider to OAuthProvider
|
||||
|
||||
`Provider` enum is renamed to `OAuthProvider`.
|
||||
Previously the `Provider` symbol often collided with classes in the [provider](https://pub.dev/packages/provider) package and developers needed to add import prefixes to avoid collisions.
|
||||
With the new update, developers can use Supabase and Provider in the same codebase without any import prefixes.
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
queryGroup="version"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
```dart
|
||||
await supabase.auth.signInWithOAuth(
|
||||
Provider.google,
|
||||
);
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
```dart
|
||||
await supabase.auth.signInWithOAuth(
|
||||
OAuthProvider.google,
|
||||
);
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
#### Sign in with Apple method deprecated
|
||||
|
||||
We have removed the [sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple) dependency in v2.
|
||||
This is because not every developer needs to sign in with Apple, and we want to reduce the number of dependencies in the library.
|
||||
|
||||
With v2, you can import [sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple) as a separate dependency if you need to sign in with Apple.
|
||||
We have also added `auth.generateRawNonce()` method to easily generate a secure nonce.
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
queryGroup="version"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
```dart
|
||||
await supabase.auth.signInWithApple();
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
```dart
|
||||
Future<AuthResponse> signInWithApple() async {
|
||||
final rawNonce = supabase.auth.generateRawNonce();
|
||||
final hashedNonce = sha256.convert(utf8.encode(rawNonce)).toString();
|
||||
|
||||
final credential = await SignInWithApple.getAppleIDCredential(
|
||||
scopes: [
|
||||
AppleIDAuthorizationScopes.email,
|
||||
AppleIDAuthorizationScopes.fullName,
|
||||
],
|
||||
nonce: hashedNonce,
|
||||
);
|
||||
|
||||
final idToken = credential.identityToken;
|
||||
if (idToken == null) {
|
||||
throw const AuthException(
|
||||
'Could not find ID Token from generated credential.',
|
||||
);
|
||||
}
|
||||
|
||||
return signInWithIdToken(
|
||||
provider: OAuthProvider.apple,
|
||||
idToken: idToken,
|
||||
nonce: rawNonce,
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
#### Initialization does not await for session refresh
|
||||
|
||||
In v1, `Supabase.initialize()` would await for the session to be refreshed before returning.
|
||||
This caused delays in the app's launch time, especially when the app is opened in a poor network environment.
|
||||
|
||||
In v2, `Supabase.initialize()` returns immediately after obtaining the session from the local storage, which makes the app launch faster.
|
||||
Because of this, there is no guarantee that the session is valid when the app starts.
|
||||
|
||||
If you need to make sure the session is valid, you can access the `isExpired` getter to check if the session is valid.
|
||||
If the session is expired, you can listen to the `onAuthStateChange` event and wait for a new `tokenRefreshed` event to be fired.
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
queryGroup="version"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
```dart
|
||||
// Session is valid, no check required
|
||||
final session = supabase.auth.currentSession;
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
```dart
|
||||
final session = supabase.auth.currentSession;
|
||||
|
||||
// Check if the session is valid.
|
||||
final isSessionExpired = session?.isExpired;
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
#### Removing Flutter Webview dependency for OAuth sign in
|
||||
|
||||
In v1, on iOS you could pass a `BuildContext` to the `signInWithOAuth()` method to launch the OAuth flow in a Flutter Webview.
|
||||
|
||||
In v2, we have dropped the [webview_flutter](https://pub.dev/packages/webview_flutter) dependency in v2 to allow you to have full control over the UI of the OAuth flow.
|
||||
We now have [native support for Google and Apple sign in](/docs/reference/dart/auth-signinwithidtoken), so opening an external browser is no longer needed on iOS.
|
||||
|
||||
Because of this update, we no longer need the `context` parameter, so we have removed the `context` parameter from the `signInWithOAuth()` method.
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
queryGroup="version"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
```dart
|
||||
// Opens a webview on iOS.
|
||||
await supabase.auth.signInWithOAuth(
|
||||
Provider.github,
|
||||
authScreenLaunchMode: LaunchMode.inAppWebView,
|
||||
context: context,
|
||||
);
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
```dart
|
||||
// Opens in app webview on iOS.
|
||||
await supabase.auth.signInWithOAuth(
|
||||
OAuthProvider.github,
|
||||
authScreenLaunchMode: LaunchMode.inAppWebView,
|
||||
);
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
#### PKCE is the default auth flow type
|
||||
|
||||
[PKCE flow](https://supabase.com/blog/supabase-auth-sso-pkce#introducing-pkce), which is a more secure method for obtaining sessions from deep links, is now the default auth flow for any authentication involving deep links.
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
queryGroup="version"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
```dart
|
||||
await Supabase.initialize(
|
||||
url: 'SUPABASE_URL',
|
||||
publishableKey: 'SUPABASE_PUBLISHABLE_KEY',
|
||||
authFlowType: AuthFlowType.implicit, // set to implicit by default
|
||||
);
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
```dart
|
||||
await Supabase.initialize(
|
||||
url: 'SUPABASE_URL',
|
||||
publishableKey: 'SUPABASE_PUBLISHABLE_KEY',
|
||||
authOptions: FlutterAuthClientOptions(
|
||||
authFlowType: AuthFlowType.pkce, // set to pkce by default
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
#### Auth callback host name parameter removed
|
||||
|
||||
`Supabase.initialize()` no longer has the `authCallbackUrlHostname` parameter.
|
||||
The `supabase_flutter` SDK will automatically detect auth callback URLs and handle them internally.
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
queryGroup="version"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
```dart
|
||||
await Supabase.initialize(
|
||||
url: 'SUPABASE_URL',
|
||||
publishableKey: 'SUPABASE_PUBLISHABLE_KEY',
|
||||
authCallbackUrlHostname: 'auth-callback',
|
||||
);
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
```dart
|
||||
await Supabase.initialize(
|
||||
url: 'SUPABASE_URL',
|
||||
publishableKey: 'SUPABASE_PUBLISHABLE_KEY',
|
||||
);
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
#### SupabaseAuth class removed
|
||||
|
||||
The `SupabaseAuth` had an `initialSession` member, which was used to obtain the initial session upon app start.
|
||||
This is now removed, and `currentSession` should be used to access the session at any time.
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
queryGroup="version"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
```dart
|
||||
// Use `initialSession` to obtain the initial session when the app starts.
|
||||
final initialSession = await SupabaseAuth.initialSession;
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
```dart
|
||||
// Use `currentSession` to access the session at any time.
|
||||
final initialSession = await supabase.auth.currentSession;
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
### Data methods
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
#### Insert and return data
|
||||
|
||||
We made the query builder immutable, which means you can reuse the same query object to chain multiple filters and get the expected outcome.
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
queryGroup="version"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
```dart
|
||||
// If you declare a query and chain filters on it
|
||||
final myQuery = supabase.from('my_table').select();
|
||||
|
||||
final foo = await myQuery.eq('some_col', 'foo');
|
||||
|
||||
// The `eq` filter above is applied in addition to the following filter
|
||||
final bar = await myQuery.eq('another_col', 'bar');
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
```dart
|
||||
// Now you can declare a query and reuse it.
|
||||
final myQuery = supabase.from('my_table').select();
|
||||
|
||||
final foo = await myQuery.eq('some_col', 'foo');
|
||||
|
||||
// The `eq` filter above is not applied to the following result
|
||||
final bar = await myQuery.eq('another_col', 'bar');
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
#### Renaming is and in filter
|
||||
|
||||
Because `is` and `in` are [reserved keywords](https://dart.dev/languages/keywords) in Dart, v1 used `is_` and `in_` as query filter names.
|
||||
Users found the underscore confusing, so the query filters are now renamed to `isFilter` and `inFilter`.
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
queryGroup="version"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
```dart
|
||||
final data = await supabase
|
||||
.from('users')
|
||||
.select()
|
||||
.is_('status', null);
|
||||
|
||||
final data = await supabase
|
||||
.from('users')
|
||||
.select()
|
||||
.in_('status', ['ONLINE', 'OFFLINE']);
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
```dart
|
||||
final data = await supabase
|
||||
.from('users')
|
||||
.select()
|
||||
.isFilter('status', null);
|
||||
|
||||
final data = await supabase
|
||||
.from('users')
|
||||
.select()
|
||||
.inFilter('status', ['ONLINE', 'OFFLINE']);
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
#### Deprecate FetchOption in favor of `count()` and `head()` methods
|
||||
|
||||
`FetchOption()` on `.select()` is now deprecated, and new `.count()` and `head()` methods are added to the query builder.
|
||||
|
||||
`count()` on `.select()` performs the select while also getting the count value, and `.count()` directly on `.from()` performs a head request resulting in only fetching the count value.
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
queryGroup="version"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
```dart
|
||||
// Request with count option
|
||||
final res = await supabase.from('cities').select(
|
||||
'name',
|
||||
const FetchOptions(
|
||||
count: CountOption.exact,
|
||||
),
|
||||
);
|
||||
|
||||
final data = res.data;
|
||||
final count = res.count;
|
||||
|
||||
// Request with count and head option
|
||||
// obtains the count value without fetching the data.
|
||||
final res = await supabase.from('cities').select(
|
||||
'name',
|
||||
const FetchOptions(
|
||||
count: CountOption.exact,
|
||||
head: true,
|
||||
),
|
||||
);
|
||||
|
||||
final count = res.count;
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
```dart
|
||||
// Request with count option
|
||||
final res = await supabase
|
||||
.from('cities')
|
||||
.select('name')
|
||||
.count(); // CountOption.exact is the default value
|
||||
|
||||
final data = res.data;
|
||||
final int count = res.count;
|
||||
|
||||
// `.count()` directly on `.from()` performs a head request,
|
||||
// obtaining the count value without fetching the data.
|
||||
final int count = await supabase
|
||||
.from('cities')
|
||||
.count(); // CountOption.exact is the default value
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
#### PostgREST error codes
|
||||
|
||||
The `PostgrestException` instance thrown by the API methods has a `code` property. In v1, the `code` property contained the http status code.
|
||||
|
||||
In v2, the `code` property contains the [PostgREST error code](https://postgrest.org/en/stable/references/errors.html), which is more useful for debugging.
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
queryGroup="version"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
```dart
|
||||
try {
|
||||
await supabase.from('countries').select();
|
||||
} on PostgrestException catch (error) {
|
||||
error.code; // Contains http status code
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
```dart
|
||||
try {
|
||||
await supabase.from('countries').select();
|
||||
} on PostgrestException catch (error) {
|
||||
error.code; // Contains PostgREST error code
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
### Realtime methods
|
||||
|
||||
Realtime methods contains the biggest breaking changes. Most of these changes are to make the interface more type safe.
|
||||
|
||||
We have removed the `.on()` method and replaced it with `.onPostgresChanges()`, `.onBroadcast()`, and three different presence methods.
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
#### Postgres Changes
|
||||
|
||||
Use the new `.onPostgresChanges()` method to listen to realtime changes in the database.
|
||||
|
||||
In v1, filters were not strongly typed because they took a `String` type. In v2, `filter` takes an object. Its properties are strictly typed to catch type errors.
|
||||
|
||||
The payload of the callback is now typed as well. In `v1`, the payload was returned as `dynamic`. It is now returned as a `PostgresChangePayload` object. The object contains the `oldRecord` and `newRecord` properties for accessing the data before and after the change.
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
queryGroup="version"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
|
||||
```dart
|
||||
supabase.channel('my_channel').on(
|
||||
RealtimeListenTypes.postgresChanges,
|
||||
ChannelFilter(
|
||||
event: '*',
|
||||
schema: 'public',
|
||||
table: 'messages',
|
||||
filter: 'room_id=eq.200',
|
||||
),
|
||||
(dynamic payload, [ref]) {
|
||||
final Map<String, dynamic> newRecord = payload['new'];
|
||||
final Map<String, dynamic> oldRecord = payload['old'];
|
||||
},
|
||||
).subscribe();
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
|
||||
```dart
|
||||
supabase.channel('my_channel')
|
||||
.onPostgresChanges(
|
||||
event: PostgresChangeEvent.all,
|
||||
schema: 'public',
|
||||
table: 'messages',
|
||||
filter: PostgresChangeFilter(
|
||||
type: PostgresChangeFilterType.eq,
|
||||
column: 'room_id',
|
||||
value: 200,
|
||||
),
|
||||
callback: (PostgresChangePayload payload) {
|
||||
final Map<String, dynamic> newRecord = payload.newRecord;
|
||||
final Map<String, dynamic> oldRecord = payload.oldRecord;
|
||||
})
|
||||
.subscribe();
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
#### Broadcast
|
||||
|
||||
Broadcast now uses the dedicated `.onBroadcast()` method, rather than the generic `.on()` method.
|
||||
Because the method is specific to broadcast, it takes fewer properties.
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
queryGroup="version"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
|
||||
```dart
|
||||
supabase.channel('my_channel').on(
|
||||
RealtimeListenTypes.broadcast,
|
||||
ChannelFilter(
|
||||
event: 'position',
|
||||
),
|
||||
(dynamic payload, [ref]) {
|
||||
print(payload);
|
||||
},
|
||||
).subscribe();
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
|
||||
```dart
|
||||
supabase
|
||||
.channel('my_channel')
|
||||
.onBroadcast(
|
||||
event: 'position',
|
||||
callback: (Map<String, dynamic> payload) {
|
||||
print(payload);
|
||||
})
|
||||
.subscribe();
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
#### Presence
|
||||
|
||||
Realtime Presence gets three different methods for listening to three different presence events: `sync`, `join`, and `leave`.
|
||||
This allows the callback to be strictly typed.
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
queryGroup="version"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
|
||||
```dart
|
||||
final channel = supabase.channel('room1');
|
||||
|
||||
channel.on(
|
||||
RealtimeListenTypes.presence,
|
||||
ChannelFilter(event: 'sync'),
|
||||
(payload, [ref]) {
|
||||
print('Synced presence state: ${channel.presenceState()}');
|
||||
},
|
||||
).on(
|
||||
RealtimeListenTypes.presence,
|
||||
ChannelFilter(event: 'join'),
|
||||
(payload, [ref]) {
|
||||
print('Newly joined presences $payload');
|
||||
},
|
||||
).on(
|
||||
RealtimeListenTypes.presence,
|
||||
ChannelFilter(event: 'leave'),
|
||||
(payload, [ref]) {
|
||||
print('Newly left presences: $payload');
|
||||
},
|
||||
).subscribe(
|
||||
(status, [error]) async {
|
||||
if (status == 'SUBSCRIBED') {
|
||||
await channel.track({'online_at': DateTime.now().toIso8601String()});
|
||||
}
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
|
||||
```dart
|
||||
final channel = supabase.channel('room1');
|
||||
|
||||
channel.onPresenceSync(
|
||||
(payload) {
|
||||
print('Synced presence state: ${channel.presenceState()}');
|
||||
},
|
||||
).onPresenceJoin(
|
||||
(payload) {
|
||||
print('Newly joined presences $payload');
|
||||
},
|
||||
).onPresenceLeave(
|
||||
(payload) {
|
||||
print('Newly left presences: $payload');
|
||||
},
|
||||
).subscribe(
|
||||
(status, error) async {
|
||||
if (status == RealtimeSubscribeStatus.subscribed) {
|
||||
await channel
|
||||
.track({'online_at': DateTime.now().toIso8601String()});
|
||||
}
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"id": "using-filters",
|
||||
"title": "Using filters",
|
||||
"description": "Filters allow you to only return rows that match certain conditions.\n\nFilters can be used on `select()`, `update()`, `upsert()`, and `delete()` queries.\n\nIf a Database function returns a table response, you can also apply filters.\n",
|
||||
"examples": [
|
||||
{
|
||||
"id": "applying-filters",
|
||||
"name": "Applying Filters",
|
||||
"description": "Filters must be applied after any of `select()`, `update()`, `upsert()`,\n`delete()`, and `rpc()` and before\n[modifiers](/docs/reference/dart/using-modifiers).\n",
|
||||
"code": "```dart\nfinal data = await supabase\n .from('cities')\n .select('name, country_id')\n .eq('name', 'The Shire'); // Correct\n\nfinal data = await supabase\n .from('cities')\n .eq('name', 'The Shire') // Incorrect\n .select('name, country_id');\n```\n"
|
||||
},
|
||||
{
|
||||
"id": "chaining-filters",
|
||||
"name": "Chaining Filters",
|
||||
"description": "Filters can be chained together to produce advanced queries as shown in the example code.\n",
|
||||
"code": "```dart\nfinal data = await supabase\n .from('cities')\n .select('name, country_id')\n .gte('population', 1000)\n .lt('population', 10000)\n```\n"
|
||||
},
|
||||
{
|
||||
"id": "conditional-chaining",
|
||||
"name": "Conditional Chaining",
|
||||
"description": "Filters can be built up one step at a time and then executed as shown in the example code.\n",
|
||||
"code": "```dart\nfinal filterByName = null;\nfinal filterPopLow = 1000;\nfinal filterPopHigh = 10000;\n\nvar query = supabase\n .from('cities')\n .select('name, country_id');\n\nif (filterByName != null) query = query.eq('name', filterByName);\nif (filterPopLow != null) query = query.gte('population', filterPopLow);\nif (filterPopHigh != null) query = query.lt('population', filterPopHigh);\n\nfinal data = await query;\n```\n"
|
||||
},
|
||||
{
|
||||
"id": "filter-by-value-within-json-column",
|
||||
"name": "Filter by values within a JSON column",
|
||||
"data": {
|
||||
"sql": "```sql\ncreate table\n users (\n id int8 primary key,\n name text,\n address jsonb\n );\n\ninsert into\n users (id, name, address)\nvalues\n (1, 'Michael', '{ \"postcode\": 90210 }'),\n (2, 'Jane', null);\n```\n"
|
||||
},
|
||||
"response": "```json\n[\n {\n 'id': 1,\n 'name': 'Michael',\n 'address': {\n 'postcode': 90210\n }\n },\n]\n```\n",
|
||||
"code": "```dart\nfinal data = await supabase\n .from('users')\n .select()\n .eq('address->postcode', 90210);\n```\n"
|
||||
},
|
||||
{
|
||||
"id": "filter-referenced-tables",
|
||||
"name": "Filter Referenced Tables",
|
||||
"code": "```dart\nfinal data = await supabase\n .from('orchestral_sections')\n .select('''\n name,\n instruments!inner (\n name\n )\n ''')\n .eq('instruments.name', 'flute');\n```\n",
|
||||
"data": {
|
||||
"sql": "```sql\ncreate table\n orchestral_sections (id int8 primary key, name text);\ncreate table\n instruments (\n id int8 primary key,\n section_id int8 not null references orchestral_sections,\n name text\n );\n\ninsert into\n orchestral_sections (id, name)\nvalues\n (1, 'strings'),\n (2, 'woodwinds');\ninsert into\n instruments (id, section_id, name)\nvalues\n (1, 2, 'flute'),\n (2, 1, 'violin');\n```\n"
|
||||
},
|
||||
"response": "```json\n[\n {\n 'name': 'strings',\n 'instruments': [\n {\n 'name': 'flute'\n }\n ]\n },\n]\n```\n",
|
||||
"description": "You can filter on referenced tables in your `select()` query using dot\nnotation.\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"id": "using-modifiers",
|
||||
"title": "Using modifiers",
|
||||
"description": "Filters work on the row level. That is, they allow you to return rows that\nonly match certain conditions without changing the shape of the rows.\nModifiers are everything that don't fit that definition—allowing you to\nchange the format of the response (e.g., returning a CSV string).\n\nModifiers must be specified after filters. Some modifiers only apply for\nqueries that return rows (e.g., `select()` or `rpc()` on a function that\nreturns a table response).\n"
|
||||
}
|
||||
Reference in new issue
Block a user