mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 03:45:06 +03:00
improve overall seeding approach and fix some of the console noise
This commit is contained in:
1 parent
6f0a00e8c0
commit
2b68bcdd64
5 files changed
+190
-112
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import { useMonaco } from '@monaco-editor/react'
|
||||
import { DatabaseZap, Download, Plus, RefreshCw, ShieldCheck, Sparkles } from 'lucide-react'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import { Button, cn, Modal, TextArea_Shadcn_ } from 'ui'
|
||||
import { Admonition } from 'ui-patterns'
|
||||
@@ -28,12 +28,12 @@ import { useTableColumnsQuery } from '@/data/database/table-columns-query'
|
||||
import { useProjectSchemaDDLQuery } from '@/data/rls-sandbox/project-schema-ddl-query'
|
||||
import { useProjectSeedDataQuery } from '@/data/rls-sandbox/project-seed-data-query'
|
||||
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
||||
import { useStaticEffectEvent } from '@/hooks/useStaticEffectEvent'
|
||||
import { getErrorMessage } from '@/lib/get-error-message'
|
||||
import type { TileConfig, TileResult } from '@/lib/rls-sandbox/sandbox-core'
|
||||
import { useSandbox } from '@/lib/rls-sandbox/SandboxProvider'
|
||||
|
||||
const SEED_ROW_LIMIT = 100
|
||||
const DDL_SCHEMAS = ['public']
|
||||
|
||||
export interface LocalTile {
|
||||
id: string
|
||||
@@ -92,28 +92,12 @@ export function RLSPlayground() {
|
||||
onSuccess: ({ sql }) => setAiGeneratedSQL(sql),
|
||||
})
|
||||
|
||||
const { data: schemaDDL, refetch: refetchSchema } = useProjectSchemaDDLQuery(
|
||||
{ projectRef: project?.ref, connectionString: project?.connectionString, schemas: DDL_SCHEMAS },
|
||||
{ enabled: false }
|
||||
)
|
||||
|
||||
const { refetch: refetchSeed } = useProjectSeedDataQuery(
|
||||
{
|
||||
projectRef: project?.ref,
|
||||
connectionString: project?.connectionString,
|
||||
tables: schemaDDL?.rlsStatuses ?? [],
|
||||
rowLimit: SEED_ROW_LIMIT,
|
||||
},
|
||||
{ enabled: false }
|
||||
)
|
||||
|
||||
const { data: usersData } = useUsersInfiniteQuery(
|
||||
{ projectRef: project?.ref, connectionString: project?.connectionString },
|
||||
{ enabled: !!project?.ref }
|
||||
)
|
||||
const users = usersData?.pages.flatMap((p) => p.result) ?? []
|
||||
|
||||
// ── Postgres IntelliSense ──────────────────────────────────────────────────
|
||||
const monaco = useMonaco()
|
||||
const pgInfoRef = useRef<any>(null)
|
||||
|
||||
@@ -134,6 +118,23 @@ export function RLSPlayground() {
|
||||
connectionString: project?.connectionString,
|
||||
})
|
||||
|
||||
const ddlSchemas = useMemo(() => schemas?.map((s) => s.name) ?? [], [schemas])
|
||||
|
||||
const { data: schemaDDL, refetch: refetchSchema } = useProjectSchemaDDLQuery(
|
||||
{ projectRef: project?.ref, connectionString: project?.connectionString, schemas: ddlSchemas },
|
||||
{ enabled: false }
|
||||
)
|
||||
|
||||
const { refetch: refetchSeed } = useProjectSeedDataQuery(
|
||||
{
|
||||
projectRef: project?.ref,
|
||||
connectionString: project?.connectionString,
|
||||
tables: schemaDDL?.rlsStatuses ?? [],
|
||||
rowLimit: SEED_ROW_LIMIT,
|
||||
},
|
||||
{ enabled: false }
|
||||
)
|
||||
|
||||
const isPgInfoReady =
|
||||
isKeywordsSuccess && isFunctionsSuccess && isSchemasSuccess && isTableColumnsSuccess
|
||||
if (isPgInfoReady) {
|
||||
@@ -161,8 +162,11 @@ export function RLSPlayground() {
|
||||
}, [isPgInfoReady, monaco])
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
const syncInProgress = useRef(false)
|
||||
|
||||
const syncSchema = useCallback(async () => {
|
||||
if (!core) return
|
||||
if (!core || syncInProgress.current) return
|
||||
syncInProgress.current = true
|
||||
setSchemaSyncStatus('syncing')
|
||||
try {
|
||||
const { data } = await refetchSchema()
|
||||
@@ -173,9 +177,13 @@ export function RLSPlayground() {
|
||||
} catch (err) {
|
||||
setSchemaSyncStatus('error')
|
||||
toast.error(`Schema sync failed: ${getErrorMessage(err) ?? ''}`)
|
||||
} finally {
|
||||
syncInProgress.current = false
|
||||
}
|
||||
}, [core, refetchSchema])
|
||||
|
||||
const onAutoSync = useStaticEffectEvent(syncSchema)
|
||||
|
||||
const seedFromProject = useCallback(async () => {
|
||||
if (!core) return
|
||||
setSeedStatus('syncing')
|
||||
@@ -298,10 +306,10 @@ export function RLSPlayground() {
|
||||
const isSandboxReady = sandboxStatus === 'ready'
|
||||
|
||||
useEffect(() => {
|
||||
if (isSandboxReady && schemaSyncStatus === 'idle') {
|
||||
syncSchema()
|
||||
if (isSandboxReady && ddlSchemas.length > 0 && schemaSyncStatus === 'idle') {
|
||||
onAutoSync()
|
||||
}
|
||||
}, [isSandboxReady, schemaSyncStatus, syncSchema])
|
||||
}, [isSandboxReady, ddlSchemas.length, schemaSyncStatus, onAutoSync])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { getEntityDefinitionsSql } from '@supabase/pg-meta'
|
||||
import pgMeta, { getEntityDefinitionsSql } from '@supabase/pg-meta'
|
||||
import type { PostgresPolicy } from '@supabase/postgres-meta'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { getDatabasePolicies } from '@/data/database-policies/database-policies-query'
|
||||
import { executeSql } from '@/data/sql/execute-sql-query'
|
||||
import { INTERNAL_SCHEMAS } from '@/hooks/useProtectedSchemas'
|
||||
import { quoteLiteral } from '@/lib/pg-format'
|
||||
import type { UseCustomQueryOptions } from '@/types'
|
||||
|
||||
@@ -14,11 +15,10 @@ export interface RlsTableStatus {
|
||||
rls_forced: boolean
|
||||
}
|
||||
|
||||
export interface CustomRole {
|
||||
name: string
|
||||
}
|
||||
export type CustomRole = z.infer<typeof pgMeta.roles.zod>
|
||||
|
||||
export interface ProjectSchemaDDL {
|
||||
schemas: string[]
|
||||
typeDefinitions: string[]
|
||||
entityDefinitions: string[]
|
||||
functionDefinitions: string[]
|
||||
@@ -27,7 +27,13 @@ export interface ProjectSchemaDDL {
|
||||
customRoles: CustomRole[]
|
||||
}
|
||||
|
||||
const SYSTEM_ROLES = [
|
||||
// Extension-owned / platform-specific schemas whose DDL depends on C extensions,
|
||||
// custom operators, and platform functions that PGlite cannot replicate.
|
||||
// We skip entity/function/type DDL for these but still fetch their policies —
|
||||
// those may reference user tables we do load.
|
||||
const SUPABASE_INTERNAL_SCHEMAS = new Set([...INTERNAL_SCHEMAS, '_realtime'])
|
||||
|
||||
const SYSTEM_ROLES = new Set([
|
||||
'postgres',
|
||||
'anon',
|
||||
'authenticated',
|
||||
@@ -47,17 +53,12 @@ const SYSTEM_ROLES = [
|
||||
'pg_signal_backend',
|
||||
'dashboard_user',
|
||||
'pgbouncer',
|
||||
'authenticator',
|
||||
]
|
||||
])
|
||||
|
||||
const CUSTOM_ROLES_SQL = `
|
||||
SELECT rolname AS name
|
||||
FROM pg_roles
|
||||
WHERE rolname NOT IN (${SYSTEM_ROLES.map(quoteLiteral).join(',')})
|
||||
AND rolname NOT LIKE 'pg_%'
|
||||
AND rolname NOT LIKE 'supabase_%'
|
||||
ORDER BY rolname
|
||||
`
|
||||
const pgMetaRolesList = pgMeta.roles.list()
|
||||
const pgMetaFunctionsZod = pgMeta.functions.list().zod
|
||||
const pgMetaPoliciesZod = pgMeta.policies.list().zod
|
||||
const pgMetaTablesZod = pgMeta.tables.list().zod
|
||||
|
||||
function toSqlList(schemas: string[]) {
|
||||
return schemas.map(quoteLiteral).join(', ')
|
||||
@@ -95,37 +96,6 @@ function getTypeDefinitionsSql(schemas: string[]) {
|
||||
`
|
||||
}
|
||||
|
||||
function getFunctionDefinitionsSql(schemas: string[]) {
|
||||
return `
|
||||
SELECT pg_get_functiondef(p.oid) AS definition
|
||||
FROM pg_proc p
|
||||
JOIN pg_namespace n ON n.oid = p.pronamespace
|
||||
JOIN pg_language l ON l.oid = p.prolang
|
||||
LEFT JOIN pg_depend d ON d.objid = p.oid AND d.deptype = 'e'
|
||||
WHERE n.nspname IN (${toSqlList(schemas)})
|
||||
AND p.prokind = 'f'
|
||||
AND l.lanname IN ('sql', 'plpgsql')
|
||||
AND p.prorettype <> 'pg_catalog.trigger'::regtype
|
||||
AND d.objid IS NULL
|
||||
ORDER BY n.nspname, p.proname
|
||||
`
|
||||
}
|
||||
|
||||
function getRlsStatusSql(schemas: string[]) {
|
||||
return `
|
||||
SELECT
|
||||
n.nspname AS schema,
|
||||
c.relname AS table,
|
||||
c.relrowsecurity AS rls_enabled,
|
||||
c.relforcerowsecurity AS rls_forced
|
||||
FROM pg_class c
|
||||
JOIN pg_namespace n ON c.relnamespace = n.oid
|
||||
WHERE c.relkind = 'r'
|
||||
AND n.nspname IN (${toSqlList(schemas)})
|
||||
ORDER BY n.nspname, c.relname
|
||||
`
|
||||
}
|
||||
|
||||
type Variables = {
|
||||
projectRef?: string
|
||||
connectionString?: string | null
|
||||
@@ -136,7 +106,12 @@ async function getProjectSchemaDDL(
|
||||
{ projectRef, connectionString, schemas }: Variables,
|
||||
signal?: AbortSignal
|
||||
): Promise<ProjectSchemaDDL> {
|
||||
const entitySql = getEntityDefinitionsSql({ schemas })
|
||||
const userSchemas = schemas.filter((s) => !SUPABASE_INTERNAL_SCHEMAS.has(s))
|
||||
|
||||
const entitySql = getEntityDefinitionsSql({ schemas: userSchemas })
|
||||
const functionsSql = pgMeta.functions.list({ includedSchemas: userSchemas }).sql
|
||||
const policiesSql = pgMeta.policies.list({ includedSchemas: schemas }).sql
|
||||
const tablesSql = pgMeta.tables.list({ includedSchemas: userSchemas }).sql
|
||||
|
||||
const [entityResult, policiesResult, rlsResult, rolesResult, functionsResult, typesResult] =
|
||||
await Promise.all([
|
||||
@@ -144,25 +119,23 @@ async function getProjectSchemaDDL(
|
||||
{ projectRef, connectionString, sql: entitySql, queryKey: ['rls-sandbox-ddl'] },
|
||||
signal
|
||||
),
|
||||
getDatabasePolicies({ projectRef, connectionString }, signal),
|
||||
executeSql(
|
||||
{
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql: getRlsStatusSql(schemas),
|
||||
queryKey: ['rls-sandbox-rls'],
|
||||
},
|
||||
{ projectRef, connectionString, sql: policiesSql, queryKey: ['rls-sandbox-policies'] },
|
||||
signal
|
||||
),
|
||||
executeSql(
|
||||
{ projectRef, connectionString, sql: CUSTOM_ROLES_SQL, queryKey: ['rls-sandbox-roles'] },
|
||||
{ projectRef, connectionString, sql: tablesSql, queryKey: ['rls-sandbox-rls'] },
|
||||
signal
|
||||
),
|
||||
executeSql(
|
||||
{ projectRef, connectionString, sql: pgMetaRolesList.sql, queryKey: ['rls-sandbox-roles'] },
|
||||
signal
|
||||
),
|
||||
executeSql(
|
||||
{
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql: getFunctionDefinitionsSql(schemas),
|
||||
sql: functionsSql,
|
||||
queryKey: ['rls-sandbox-functions'],
|
||||
},
|
||||
signal
|
||||
@@ -171,24 +144,36 @@ async function getProjectSchemaDDL(
|
||||
{
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql: getTypeDefinitionsSql(schemas),
|
||||
sql: getTypeDefinitionsSql(userSchemas),
|
||||
queryKey: ['rls-sandbox-types'],
|
||||
},
|
||||
signal
|
||||
),
|
||||
])
|
||||
|
||||
const roles = (rolesResult.result as z.infer<typeof pgMetaRolesList.zod>).filter(
|
||||
(r) => !SYSTEM_ROLES.has(r.name) && !r.name.startsWith('pg_') && !r.name.startsWith('supabase_')
|
||||
)
|
||||
|
||||
const functions = (functionsResult.result as z.infer<typeof pgMetaFunctionsZod>).filter(
|
||||
(f) => (f.language === 'sql' || f.language === 'plpgsql') && f.return_type !== 'trigger'
|
||||
)
|
||||
|
||||
return {
|
||||
schemas: userSchemas,
|
||||
typeDefinitions: (typesResult.result as { definition: string }[]).map((r) => r.definition),
|
||||
entityDefinitions: (entityResult.result[0]?.data?.definitions ?? []).map(
|
||||
(d: { sql: string }) => d.sql
|
||||
),
|
||||
functionDefinitions: (functionsResult.result as { definition: string }[]).map(
|
||||
(r) => r.definition
|
||||
),
|
||||
policies: (policiesResult ?? []) as PostgresPolicy[],
|
||||
rlsStatuses: rlsResult.result as RlsTableStatus[],
|
||||
customRoles: rolesResult.result as CustomRole[],
|
||||
functionDefinitions: functions.map((f) => f.complete_statement),
|
||||
policies: policiesResult.result as z.infer<typeof pgMetaPoliciesZod> as PostgresPolicy[],
|
||||
rlsStatuses: (rlsResult.result as z.infer<typeof pgMetaTablesZod>).map((t) => ({
|
||||
schema: t.schema,
|
||||
table: t.name,
|
||||
rls_enabled: t.rls_enabled,
|
||||
rls_forced: t.rls_forced,
|
||||
})),
|
||||
customRoles: roles,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ async function applyDDLWithRetries(sandbox: Executor, ddlStatements: string[]):
|
||||
export async function applySchema(
|
||||
sandbox: Executor,
|
||||
{
|
||||
schemas,
|
||||
typeDefinitions,
|
||||
entityDefinitions,
|
||||
functionDefinitions,
|
||||
@@ -73,6 +74,17 @@ export async function applySchema(
|
||||
customRoles,
|
||||
}: ProjectSchemaDDL
|
||||
): Promise<void> {
|
||||
for (const schema of schemas) {
|
||||
if (schema === 'public') continue
|
||||
const safe = schema.replace(/"/g, '""')
|
||||
await tryExec(sandbox, `CREATE SCHEMA IF NOT EXISTS "${safe}"`, `schema ${schema}`)
|
||||
await tryExec(
|
||||
sandbox,
|
||||
`GRANT USAGE ON SCHEMA "${safe}" TO anon, authenticated, service_role`,
|
||||
`grant schema ${schema}`
|
||||
)
|
||||
}
|
||||
|
||||
if (customRoles.length > 0) {
|
||||
const checks = customRoles
|
||||
.map(({ name }) => {
|
||||
@@ -87,28 +99,25 @@ export async function applySchema(
|
||||
await applyDDLWithRetries(sandbox, typeDefinitions)
|
||||
await applyDDLWithRetries(sandbox, entityDefinitions)
|
||||
|
||||
await tryExec(
|
||||
sandbox,
|
||||
`GRANT USAGE ON SCHEMA public TO anon, authenticated, service_role`,
|
||||
'grant schema usage'
|
||||
)
|
||||
for (const schema of [...new Set(rlsStatuses.map((t) => t.schema))]) {
|
||||
const safe = schema.replace(/"/g, '""')
|
||||
await tryExec(
|
||||
sandbox,
|
||||
`GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA "${safe}" TO anon, authenticated, service_role`,
|
||||
`grant tables in schema ${schema}`
|
||||
)
|
||||
}
|
||||
|
||||
if (rlsStatuses.length > 0) {
|
||||
const grants = rlsStatuses
|
||||
.map(
|
||||
(t) =>
|
||||
`GRANT SELECT, INSERT, UPDATE, DELETE ON "${t.schema}"."${t.table}" TO anon, authenticated, service_role`
|
||||
)
|
||||
.join('; ')
|
||||
await tryExec(sandbox, grants, 'grant roles on all tables')
|
||||
|
||||
const rlsEnables = rlsStatuses
|
||||
.filter((t) => t.rls_enabled)
|
||||
.map((t) => `ALTER TABLE "${t.schema}"."${t.table}" ENABLE ROW LEVEL SECURITY`)
|
||||
.join('; ')
|
||||
if (rlsEnables) {
|
||||
await tryExec(sandbox, rlsEnables, 'enable RLS on all tables')
|
||||
}
|
||||
for (const { schema, table, rls_enabled, rls_forced } of rlsStatuses) {
|
||||
const actions: string[] = []
|
||||
if (rls_enabled) actions.push('ENABLE ROW LEVEL SECURITY')
|
||||
if (rls_forced) actions.push('FORCE ROW LEVEL SECURITY')
|
||||
if (actions.length === 0) continue
|
||||
await tryExec(
|
||||
sandbox,
|
||||
`ALTER TABLE "${schema}"."${table}" ${actions.join(', ')}`,
|
||||
`RLS on ${schema}.${table}`
|
||||
)
|
||||
}
|
||||
|
||||
// Disable check_function_bodies so functions referencing not-yet-created objects don't abort.
|
||||
|
||||
@@ -8,6 +8,7 @@ worker({
|
||||
return new PGlite({
|
||||
dataDir: 'memory://',
|
||||
extensions: { pgcrypto, uuid_ossp },
|
||||
debug: 0,
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -23,7 +23,6 @@ export interface SandboxCore {
|
||||
destroy(): Promise<void>
|
||||
}
|
||||
|
||||
// All superuser-required setup runs as the PGlite bootstrap user.
|
||||
// ALTER ROLE postgres SUPERUSER succeeds because the bootstrap connection owns the cluster.
|
||||
// Each statement is individual so a single failure cannot abort the rest.
|
||||
const SANDBOX_SETUP_STATEMENTS = [
|
||||
@@ -38,6 +37,18 @@ const SANDBOX_SETUP_STATEMENTS = [
|
||||
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'service_role') THEN
|
||||
CREATE ROLE service_role NOLOGIN;
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'authenticator') THEN
|
||||
CREATE ROLE authenticator NOLOGIN;
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'dashboard_user') THEN
|
||||
CREATE ROLE dashboard_user NOLOGIN;
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'pgbouncer') THEN
|
||||
CREATE ROLE pgbouncer NOLOGIN;
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'supabase_admin') THEN
|
||||
CREATE ROLE supabase_admin NOLOGIN;
|
||||
END IF;
|
||||
END $$`,
|
||||
`ALTER ROLE service_role BYPASSRLS`,
|
||||
`GRANT anon TO postgres WITH ADMIN OPTION`,
|
||||
@@ -56,10 +67,76 @@ const SANDBOX_SETUP_STATEMENTS = [
|
||||
`GRANT EXECUTE ON FUNCTION auth.uid() TO anon, authenticated, service_role`,
|
||||
`GRANT EXECUTE ON FUNCTION auth.role() TO anon, authenticated, service_role`,
|
||||
`GRANT EXECUTE ON FUNCTION auth.email() TO anon, authenticated, service_role`,
|
||||
// Minimal auth table stubs — enough for FK references and policy expressions.
|
||||
// Projects commonly have FKs to auth.users from public schema tables (e.g. profiles),
|
||||
// so without this stub those tables fail to create and their policies can't be tested.
|
||||
`CREATE TABLE IF NOT EXISTS auth.users (
|
||||
instance_id uuid,
|
||||
id uuid NOT NULL PRIMARY KEY,
|
||||
aud varchar(255),
|
||||
role varchar(255),
|
||||
email varchar(255),
|
||||
encrypted_password varchar(255),
|
||||
email_confirmed_at timestamptz,
|
||||
invited_at timestamptz,
|
||||
confirmation_token varchar(255),
|
||||
confirmation_sent_at timestamptz,
|
||||
recovery_token varchar(255),
|
||||
recovery_sent_at timestamptz,
|
||||
email_change_token_new varchar(255),
|
||||
email_change varchar(255),
|
||||
email_change_sent_at timestamptz,
|
||||
last_sign_in_at timestamptz,
|
||||
raw_app_meta_data jsonb,
|
||||
raw_user_meta_data jsonb,
|
||||
is_super_admin boolean,
|
||||
created_at timestamptz,
|
||||
updated_at timestamptz,
|
||||
phone text DEFAULT NULL,
|
||||
phone_confirmed_at timestamptz,
|
||||
phone_change text DEFAULT '',
|
||||
phone_change_token varchar(255) DEFAULT '',
|
||||
phone_change_sent_at timestamptz,
|
||||
confirmed_at timestamptz,
|
||||
email_change_token_current varchar(255) DEFAULT '',
|
||||
email_change_confirm_status smallint DEFAULT 0,
|
||||
banned_until timestamptz,
|
||||
reauthentication_token varchar(255) DEFAULT '',
|
||||
reauthentication_sent_at timestamptz,
|
||||
is_sso_user boolean NOT NULL DEFAULT false,
|
||||
deleted_at timestamptz,
|
||||
is_anonymous boolean NOT NULL DEFAULT false
|
||||
)`,
|
||||
`CREATE TABLE IF NOT EXISTS auth.sessions (
|
||||
id uuid NOT NULL PRIMARY KEY,
|
||||
user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
created_at timestamptz,
|
||||
updated_at timestamptz,
|
||||
factor_id uuid,
|
||||
aal text,
|
||||
not_after timestamptz,
|
||||
refreshed_at timestamp,
|
||||
user_agent text,
|
||||
ip inet,
|
||||
tag text
|
||||
)`,
|
||||
`CREATE TABLE IF NOT EXISTS auth.mfa_factors (
|
||||
id uuid NOT NULL PRIMARY KEY,
|
||||
user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
friendly_name text,
|
||||
factor_type text NOT NULL,
|
||||
status text NOT NULL,
|
||||
created_at timestamptz NOT NULL,
|
||||
updated_at timestamptz NOT NULL,
|
||||
secret text,
|
||||
phone text,
|
||||
last_challenged_at timestamptz,
|
||||
web_authn_credential jsonb,
|
||||
web_authn_aaguid uuid
|
||||
)`,
|
||||
`GRANT SELECT, INSERT, UPDATE, DELETE ON auth.users, auth.sessions, auth.mfa_factors TO anon, authenticated, service_role`,
|
||||
]
|
||||
|
||||
// ── Singleton ────────────────────────────────────────────────────────────────
|
||||
|
||||
let instance: SandboxCore | null = null
|
||||
let initPromise: Promise<SandboxCore> | null = null
|
||||
|
||||
@@ -80,8 +157,6 @@ export function destroySandboxCore(): Promise<void> {
|
||||
return current?.destroy() ?? Promise.resolve()
|
||||
}
|
||||
|
||||
// ── Boot ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
async function boot(): Promise<SandboxCore> {
|
||||
const webWorker = new Worker(new URL('./pglite.worker.ts', import.meta.url), { type: 'module' })
|
||||
const pg = await PGliteWorker.create(webWorker)
|
||||
|
||||
Reference in new issue
Block a user