SimpleORM + Supabase NEW
Integre seu projeto Delphi com Supabase usando o mesmo padrao SimpleORM que voce ja conhece. Zero dependencias externas.
Introducao
A integracao SimpleORM + Supabase permite conectar sua aplicacao Delphi diretamente ao Supabase,
um backend-as-a-service baseado em PostgreSQL. O driver traduz operacoes SQL geradas pelo
TSimpleDAO<T> em chamadas HTTP para a API PostgREST do Supabase.
A integracao e composta por 3 componentes independentes:
| Componente | Unit | Responsabilidade |
|---|---|---|
TSimpleQuerySupabase | SimpleQuerySupabase.pas | Driver de query — traduz SQL para chamadas REST PostgREST |
TSimpleSupabaseAuth | SimpleSupabaseAuth.pas | Autenticacao — SignIn, SignUp, SignOut, RefreshToken, auto-refresh |
TSimpleSupabaseRealtime | SimpleSupabaseRealtime.pas | Realtime — monitoramento de mudancas via polling com callbacks |
System.Net.HttpClient
nativo do Delphi. Nao e necessario instalar SDKs, DLLs ou componentes de terceiros.
Instalacao
Via Boss (recomendado)
boss install academiadocodigo/SimpleORM
Manual
Adicione o diretorio src/ ao Library Path do Delphi e inclua as units necessarias na clausula uses:
uses
SimpleInterface, // Interfaces (iSimpleQuery, iSimpleDAO, iSimpleSupabaseAuth, etc.)
SimpleTypes, // Tipos (TSQLType, TSupabaseRealtimeEvent, etc.)
SimpleDAO, // TSimpleDAO<T>
SimpleAttributes, // Atributos de entidade ([Tabela], [Campo], [PK], etc.)
SimpleQuerySupabase, // Driver Supabase (TSimpleQuerySupabase)
SimpleSupabaseAuth, // Autenticacao (TSimpleSupabaseAuth)
SimpleSupabaseRealtime; // Realtime (TSimpleSupabaseRealtime)
SimpleQuerySupabase, SimpleDAO, SimpleAttributes e
SimpleInterface. Auth e Realtime sao opcionais.
Configuracao Basica
TSimpleQuerySupabase oferece 3 construtores para diferentes cenarios de uso:
1. Basico — URL + API Key
Usa a service_role key como token de autorizacao. Acesso total, sem Row Level Security.
var
LQuery: iSimpleQuery;
begin
LQuery := TSimpleQuerySupabase.New(
'https://abcdefghij.supabase.co', // URL do projeto
'eyJhbGciOiJIUzI1NiIsInR5cCI6...' // service_role key
);
end;
2. Com Token JWT Estatico
Util quando voce ja possui um JWT obtido por outro meio (ex: login externo).
var
LQuery: iSimpleQuery;
begin
LQuery := TSimpleQuerySupabase.New(
'https://abcdefghij.supabase.co', // URL do projeto
'eyJhbGciOiJIUzI1NiIsInR5cCI6...', // anon key
'eyJhbGciOiJIUzI1NiIsInR5cCI6...' // JWT token do usuario
);
end;
3. Com Objeto Auth (recomendado para RLS)
O token e gerenciado automaticamente pelo TSimpleSupabaseAuth, incluindo auto-refresh.
var
LAuth: iSimpleSupabaseAuth;
LQuery: iSimpleQuery;
begin
LAuth := TSimpleSupabaseAuth.New(
'https://abcdefghij.supabase.co',
'eyJhbGciOiJIUzI1NiIsInR5cCI6...' // anon key
);
LAuth.SignIn('usuario@email.com', 'senha123');
LQuery := TSimpleQuerySupabase.New(
'https://abcdefghij.supabase.co',
'eyJhbGciOiJIUzI1NiIsInR5cCI6...', // anon key
LAuth // objeto auth
);
end;
Onde encontrar URL e API Keys
No painel do Supabase:
| Dado | Onde encontrar | Uso |
|---|---|---|
| Project URL | Settings > API > Project URL | Primeiro parametro de todos os construtores |
| anon key | Settings > API > Project API Keys > anon/public | Acesso publico com RLS ativo |
| service_role key | Settings > API > Project API Keys > service_role | Acesso total, ignora RLS |
service_role key tem acesso total ao banco.
NUNCA exponha esta chave em aplicacoes cliente distribuidas. Use anon key + Auth para aplicacoes de usuario final.
CRUD Completo
O driver Supabase funciona como qualquer outro driver SimpleORM. Voce usa TSimpleDAO<T>
normalmente — o driver traduz o SQL gerado em chamadas HTTP para a API PostgREST.
Entidade de Exemplo
Todos os exemplos CRUD usam esta entidade:
type
[Tabela('produto')]
TProduto = class
private
FId: Integer;
FNome: String;
FPreco: Double;
FAtivo: Boolean;
published
[Campo('id'), PK, AutoInc]
property Id: Integer read FId write FId;
[Campo('nome'), NotNull]
property Nome: String read FNome write FNome;
[Campo('preco')]
property Preco: Double read FPreco write FPreco;
[Campo('ativo')]
property Ativo: Boolean read FAtivo write FAtivo;
end;
Criando o DAO
var
LQuery: iSimpleQuery;
LDAO: iSimpleDAO<TProduto>;
begin
LQuery := TSimpleQuerySupabase.New(
'https://abcdefghij.supabase.co',
'sua-api-key-aqui'
);
LDAO := TSimpleDAO<TProduto>.New(LQuery);
end;
Insert
Insere um novo registro na tabela do Supabase.
var
LProduto: TProduto;
begin
LProduto := TProduto.Create;
try
LProduto.Nome := 'Notebook Dell';
LProduto.Preco := 4599.90;
LProduto.Ativo := True;
LDAO.Insert(LProduto);
Writeln('Produto inserido com sucesso!');
finally
LProduto.Free;
end;
end;
O que acontece internamente
| Etapa | Detalhe |
|---|---|
SQL gerado por TSimpleSQL | INSERT INTO produto (nome, preco, ativo) VALUES (:nome, :preco, :ativo) |
| Metodo HTTP | POST |
| URL | https://abcdefghij.supabase.co/rest/v1/produto |
| Body JSON | {"nome":"Notebook Dell","preco":4599.90,"ativo":true} |
| Headers | apikey: <key>, Authorization: Bearer <token>, Prefer: return=representation |
[AutoInc] sao excluidos automaticamente do INSERT.
O id e gerado pelo banco de dados do Supabase (PostgreSQL).
Find (Select)
Buscar todos os registros
var
LList: TObjectList<TProduto>;
I: Integer;
begin
LList := LDAO.Find;
try
for I := 0 to LList.Count - 1 do
Writeln(LList[I].Nome, ' - R$ ', LList[I].Preco:0:2);
finally
LList.Free;
end;
end;
// SQL gerado: SELECT * FROM produto
// HTTP: GET /rest/v1/produto?select=*
Buscar por ID (chave primaria)
var
LProduto: TProduto;
begin
LProduto := TProduto.Create;
try
LProduto.Id := 42;
LDAO.Find(LProduto);
Writeln('Encontrado: ', LProduto.Nome);
finally
LProduto.Free;
end;
end;
// SQL gerado: SELECT * FROM produto WHERE id = :id
// HTTP: GET /rest/v1/produto?id=eq.42
Buscar com filtro Where
var
LList: TObjectList<TProduto>;
begin
LList := LDAO
.SQL
.Where('ativo = :ativo')
.&End
.Find;
try
Writeln('Produtos ativos: ', LList.Count);
finally
LList.Free;
end;
end;
// SQL gerado: SELECT * FROM produto WHERE ativo = :ativo
// HTTP: GET /rest/v1/produto?ativo=eq.1
Buscar com campos especificos
var
LList: TObjectList<TProduto>;
begin
LList := LDAO
.SQL
.Fields('id, nome')
.&End
.Find;
try
Writeln('Total: ', LList.Count);
finally
LList.Free;
end;
end;
// SQL gerado: SELECT id, nome FROM produto
// HTTP: GET /rest/v1/produto?select=id,nome
WHERE campo = :param
para o formato PostgREST campo=eq.valor. Multiplas condicoes com AND
sao separadas por & na URL.
Update
Atualiza um registro existente. O WHERE e gerado automaticamente com base na chave primaria ([PK]).
var
LProduto: TProduto;
begin
LProduto := TProduto.Create;
try
LProduto.Id := 42;
// Buscar o produto atual
LDAO.Find(LProduto);
// Alterar campos
LProduto.Nome := 'Notebook Dell Atualizado';
LProduto.Preco := 3999.90;
// Salvar
LDAO.Update(LProduto);
Writeln('Produto atualizado!');
finally
LProduto.Free;
end;
end;
O que acontece internamente
| Etapa | Detalhe |
|---|---|
| SQL gerado | UPDATE produto SET nome = :nome, preco = :preco, ativo = :ativo WHERE id = :id |
| Metodo HTTP | PATCH |
| URL | https://abcdefghij.supabase.co/rest/v1/produto?id=eq.42 |
| Body JSON | {"nome":"Notebook Dell Atualizado","preco":3999.90,"ativo":true} |
Delete
Remove um registro pela chave primaria.
var
LProduto: TProduto;
begin
LProduto := TProduto.Create;
try
LProduto.Id := 42;
LDAO.Delete(LProduto);
Writeln('Produto removido!');
finally
LProduto.Free;
end;
end;
O que acontece internamente
| Etapa | Detalhe |
|---|---|
| SQL gerado | DELETE FROM produto WHERE id = :id |
| Metodo HTTP | DELETE |
| URL | https://abcdefghij.supabase.co/rest/v1/produto?id=eq.42 |
| Body | (vazio) |
[SoftDelete('campo')], o
TSimpleDAO gera um UPDATE SET campo = 1 em vez de DELETE.
O driver traduz normalmente para PATCH.
Paginacao
Use Skip e Take para paginar resultados. O driver converte para limit e offset na URL.
var
LList: TObjectList<TProduto>;
begin
// Pagina 1: primeiros 10 registros
LList := LDAO
.SQL
.Skip(0)
.Take(10)
.&End
.Find;
try
Writeln('Pagina 1: ', LList.Count, ' registros');
finally
LList.Free;
end;
// Pagina 2: registros 11 a 20
LList := LDAO
.SQL
.Skip(10)
.Take(10)
.&End
.Find;
try
Writeln('Pagina 2: ', LList.Count, ' registros');
finally
LList.Free;
end;
// Pagina 3: registros 21 a 30
LList := LDAO
.SQL
.Skip(20)
.Take(10)
.&End
.Find;
try
Writeln('Pagina 3: ', LList.Count, ' registros');
finally
LList.Free;
end;
end;
Mapeamento de paginacao
| SimpleORM | SQL Gerado (depende do SQLType) | URL PostgREST |
|---|---|---|
.Skip(0).Take(10) | LIMIT 10 OFFSET 0 (MySQL) | ?limit=10&offset=0 |
.Skip(10).Take(10) | LIMIT 10 OFFSET 10 | ?limit=10&offset=10 |
.Skip(0).Take(50) | FIRST 50 SKIP 0 (Firebird) | ?limit=50&offset=0 |
FIRST/SKIP (Firebird), LIMIT/OFFSET (MySQL/SQLite), e
OFFSET ROWS FETCH NEXT (Oracle) e converte todos para limit/offset do PostgREST.
Batch Operations
Operacoes em lote executam a mesma acao para cada item de uma lista.
var
LList: TObjectList<TProduto>;
LProduto: TProduto;
begin
LList := TObjectList<TProduto>.Create;
try
// Criar varios produtos
LProduto := TProduto.Create;
LProduto.Nome := 'Produto A';
LProduto.Preco := 100.00;
LProduto.Ativo := True;
LList.Add(LProduto);
LProduto := TProduto.Create;
LProduto.Nome := 'Produto B';
LProduto.Preco := 200.00;
LProduto.Ativo := True;
LList.Add(LProduto);
LProduto := TProduto.Create;
LProduto.Nome := 'Produto C';
LProduto.Preco := 300.00;
LProduto.Ativo := True;
LList.Add(LProduto);
// Inserir todos de uma vez
LDAO.InsertBatch(LList);
Writeln('3 produtos inseridos!');
// UpdateBatch e DeleteBatch funcionam da mesma forma:
// LDAO.UpdateBatch(LList);
// LDAO.DeleteBatch(LList);
finally
LList.Free;
end;
end;
StartTransaction, Commit e Rollback sao no-ops
(retornam Self sem fazer nada). Cada POST/PATCH/DELETE
e executado individualmente. Se um item falhar no meio do batch, os anteriores ja foram persistidos.
SQL para REST — Mapeamento Completo
O TSimpleQuerySupabase parseia o SQL gerado pelo TSimpleSQL<T>
e traduz cada operacao para a chamada HTTP equivalente da API PostgREST do Supabase.
Operacoes CRUD
| Operacao SQL | Metodo HTTP | URL PostgREST | Body |
|---|---|---|---|
INSERT INTO tabela (c1, c2) VALUES (:c1, :c2) |
POST |
/rest/v1/tabela |
{"c1":"v1","c2":"v2"} |
SELECT * FROM tabela |
GET |
/rest/v1/tabela?select=* |
(vazio) |
SELECT c1, c2 FROM tabela |
GET |
/rest/v1/tabela?select=c1,c2 |
(vazio) |
SELECT * FROM tabela WHERE pk = :pk |
GET |
/rest/v1/tabela?pk=eq.valor |
(vazio) |
SELECT * FROM tabela WHERE c1 = :c1 AND c2 = :c2 |
GET |
/rest/v1/tabela?c1=eq.v1&c2=eq.v2 |
(vazio) |
SELECT * FROM tabela LIMIT 10 OFFSET 5 |
GET |
/rest/v1/tabela?limit=10&offset=5 |
(vazio) |
UPDATE tabela SET c1 = :c1 WHERE pk = :pk |
PATCH |
/rest/v1/tabela?pk=eq.valor |
{"c1":"novo_valor"} |
DELETE FROM tabela WHERE pk = :pk |
DELETE |
/rest/v1/tabela?pk=eq.valor |
(vazio) |
Headers enviados em todas as requisicoes
| Header | Valor | Descricao |
|---|---|---|
Content-Type | application/json | Formato do body |
apikey | Sua API key do Supabase | Identificacao do projeto |
Authorization | Bearer <token> | Token de autenticacao (auth token, JWT estatico, ou API key) |
Prefer | return=representation | Retornar o registro afetado na resposta |
Prioridade do token de autorizacao
O header Authorization e preenchido seguindo esta prioridade:
| Prioridade | Condicao | Token Usado |
|---|---|---|
| 1 (maior) | FAuth atribuido e IsAuthenticated = True | FAuth.Token (com auto-refresh) |
| 2 | FToken nao vazio | JWT estatico passado no construtor |
| 3 (menor) | Nenhum dos acima | FAPIKey (api key do projeto) |
Conversao de tipos no JSON
| Tipo Delphi (TParam.DataType) | Tipo JSON | Exemplo |
|---|---|---|
ftInteger, ftSmallint, ftWord, ftLargeint, ftAutoInc, ftShortint | Number (inteiro) | 42 |
ftFloat, ftCurrency, ftBCD, ftFMTBcd, ftExtended, ftSingle | Number (decimal) | 4599.90 |
ftBoolean | Boolean | true |
ftString, ftWideString, outros | String | "texto" |
Null / Empty | null | null |
Autenticacao
O TSimpleSupabaseAuth integra com o GoTrue (servico de autenticacao do Supabase).
Ele gerencia SignIn, SignUp, SignOut, tokens JWT e auto-refresh transparente.
API Key (sem autenticacao de usuario)
A forma mais simples de conectar. Usa a service_role key diretamente.
Nao passa por Row Level Security. Ideal para scripts, migracao de dados, ou back-office.
var
LQuery: iSimpleQuery;
LDAO: iSimpleDAO<TProduto>;
begin
// service_role key = acesso total
LQuery := TSimpleQuerySupabase.New(
'https://abcdefghij.supabase.co',
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJl' +
'ZiI6ImFiY2RlZmdoaWoiLCJyb2xlIjoic2VydmljZV9yb2xlIiwiaWF0IjoxNjk5MDAwMDAwfQ...'
);
LDAO := TSimpleDAO<TProduto>.New(LQuery);
// LDAO tem acesso total a tabela produto, sem restricoes RLS
end;
service_role key ignora TODAS as politicas RLS.
Use apenas em ambientes confiáveis (servidor, scripts internos).
JWT Token Estatico
Quando voce ja tem um JWT obtido por outro sistema de autenticacao (SSO, Auth0, Firebase, etc.), passe-o diretamente no terceiro parametro.
var
LQuery: iSimpleQuery;
LToken: String;
begin
LToken := ObterTokenDoSistemaExterno(); // Seu metodo personalizado
LQuery := TSimpleQuerySupabase.New(
'https://abcdefghij.supabase.co',
'eyJ...anonKey...', // anon key
LToken // JWT do sistema externo
);
end;
Voce tambem pode alterar o token apos a criacao usando o metodo Token:
var
LQuery: iSimpleQuery;
begin
LQuery := TSimpleQuerySupabase.New(
'https://abcdefghij.supabase.co',
'eyJ...anonKey...'
);
// Definir token depois
(LQuery as TSimpleQuerySupabase).Token('novo-jwt-aqui');
end;
SignIn / SignUp
SignIn — Login com email e senha
var
LAuth: iSimpleSupabaseAuth;
begin
LAuth := TSimpleSupabaseAuth.New(
'https://abcdefghij.supabase.co',
'eyJ...anonKey...'
);
LAuth.SignIn('usuario@email.com', 'minhaSenha123');
if LAuth.IsAuthenticated then
begin
Writeln('Login realizado com sucesso!');
Writeln('Token: ', Copy(LAuth.Token, 1, 50), '...');
Writeln('User: ', LAuth.User);
Writeln('Expira em: ', DateTimeToStr(LAuth.ExpiresAt));
end;
end;
// HTTP: POST /auth/v1/token?grant_type=password
// Body: {"email":"usuario@email.com","password":"minhaSenha123"}
SignUp — Criar nova conta
var
LAuth: iSimpleSupabaseAuth;
begin
LAuth := TSimpleSupabaseAuth.New(
'https://abcdefghij.supabase.co',
'eyJ...anonKey...'
);
LAuth.SignUp('novo@email.com', 'senhaSegura456');
if LAuth.IsAuthenticated then
Writeln('Conta criada e logado automaticamente!')
else
Writeln('Conta criada. Verifique seu email para confirmar.');
end;
// HTTP: POST /auth/v1/signup
// Body: {"email":"novo@email.com","password":"senhaSegura456"}
SignUp retorna o usuario mas IsAuthenticated pode ser
False ate que o email seja confirmado.
SignOut
Encerra a sessao do usuario. O token, refresh token, dados do usuario e data de expiracao sao limpos.
var
LAuth: iSimpleSupabaseAuth;
begin
LAuth := TSimpleSupabaseAuth.New(
'https://abcdefghij.supabase.co',
'eyJ...anonKey...'
);
LAuth.SignIn('usuario@email.com', 'senha123');
Writeln('Autenticado: ', LAuth.IsAuthenticated); // True
LAuth.SignOut;
Writeln('Autenticado: ', LAuth.IsAuthenticated); // False
Writeln('Token: "', LAuth.Token, '"'); // "" (vazio)
Writeln('User: "', LAuth.User, '"'); // "" (vazio)
end;
// HTTP: POST /auth/v1/logout
// Header: Authorization: Bearer <access_token>
Refresh Token
Renova o access token usando o refresh token obtido no SignIn.
var
LAuth: iSimpleSupabaseAuth;
begin
LAuth := TSimpleSupabaseAuth.New(
'https://abcdefghij.supabase.co',
'eyJ...anonKey...'
);
LAuth.SignIn('usuario@email.com', 'senha123');
// ... muito tempo depois, o token pode ter expirado ...
LAuth.RefreshToken;
Writeln('Novo token: ', Copy(LAuth.Token, 1, 50), '...');
Writeln('Nova expiracao: ', DateTimeToStr(LAuth.ExpiresAt));
end;
// HTTP: POST /auth/v1/token?grant_type=refresh_token
// Body: {"refresh_token":"seu-refresh-token-aqui"}
RefreshToken requer que SignIn tenha sido
chamado antes (para ter um refresh token disponivel). Chamar sem refresh token levanta excecao:
Supabase Auth: no refresh token available. Call SignIn first.
Auto-Refresh
O TSimpleSupabaseAuth renova o token automaticamente quando voce acessa a
propriedade Token. Se faltam menos de 30 segundos para expirar, o refresh
e executado de forma transparente.
var
LAuth: iSimpleSupabaseAuth;
LToken: String;
begin
LAuth := TSimpleSupabaseAuth.New(
'https://abcdefghij.supabase.co',
'eyJ...anonKey...'
);
LAuth.SignIn('usuario@email.com', 'senha123');
// Em qualquer momento posterior:
LToken := LAuth.Token;
// Se faltam < 30 segundos para expirar:
// 1. Chama RefreshToken internamente
// 2. Obtem novo access_token e refresh_token
// 3. Atualiza ExpiresAt
// 4. Retorna o NOVO token
// Senao:
// Retorna o token atual sem nenhuma chamada HTTP
end;
Fluxo interno do Auto-Refresh
| Condicao | Acao |
|---|---|
Token nao vazio E ExpiresAt > 0 E Now >= ExpiresAt - 30s | Chama RefreshToken automaticamente |
Token nao vazio E ExpiresAt > 0 E Now < ExpiresAt - 30s | Retorna token atual (nenhuma chamada HTTP) |
Token vazio | Retorna string vazia (nao autenticado) |
iSimpleSupabaseAuth, o driver chama FAuth.Token em CADA requisicao HTTP.
Isso significa que o auto-refresh acontece transparentemente a cada operacao CRUD.
Row Level Security (RLS)
RLS permite que o Supabase filtre automaticamente registros com base no usuario autenticado.
Para ativar RLS com SimpleORM, use anon key + TSimpleSupabaseAuth.
Exemplo: cada usuario ve apenas seus proprios dados
Tabela tarefa no Supabase com RLS ativado e politica:
-- SQL no Supabase Dashboard (SQL Editor):
CREATE POLICY "Usuarios veem suas tarefas"
ON tarefa FOR SELECT
USING (auth.uid() = user_id);
CREATE POLICY "Usuarios inserem suas tarefas"
ON tarefa FOR INSERT
WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Usuarios editam suas tarefas"
ON tarefa FOR UPDATE
USING (auth.uid() = user_id);
CREATE POLICY "Usuarios deletam suas tarefas"
ON tarefa FOR DELETE
USING (auth.uid() = user_id);
Codigo Delphi com RLS
var
LAuth: iSimpleSupabaseAuth;
LQuery: iSimpleQuery;
LDAO: iSimpleDAO<TTarefa>;
LList: TObjectList<TTarefa>;
begin
// 1. Autenticar com anon key
LAuth := TSimpleSupabaseAuth.New(
'https://abcdefghij.supabase.co',
'eyJ...anonKey...' // anon key (NAO service_role)
);
LAuth.SignIn('joao@email.com', 'senha123');
// 2. Criar query com auth
LQuery := TSimpleQuerySupabase.New(
'https://abcdefghij.supabase.co',
'eyJ...anonKey...',
LAuth
);
// 3. Usar DAO normalmente
LDAO := TSimpleDAO<TTarefa>.New(LQuery);
// Este Find retorna APENAS tarefas do joao@email.com
// O Supabase aplica a politica RLS automaticamente
LList := LDAO.Find;
try
Writeln('Tarefas de Joao: ', LList.Count);
finally
LList.Free;
end;
end;
anon key. A service_role
key ignora todas as politicas RLS. Se voce esta usando service_role key e nao
viu dados filtrados, troque para anon key + Auth.
Realtime
O TSimpleSupabaseRealtime monitora mudancas em tabelas do Supabase via polling.
Uma thread em background consulta periodicamente as tabelas inscritas e dispara callbacks
quando novos registros sao detectados.
Configuracao
Crie uma instancia de TSimpleSupabaseRealtime com URL, API key e intervalo de polling opcional.
var
LRealtime: iSimpleSupabaseRealtime;
begin
// Intervalo padrao: 2000ms (2 segundos)
LRealtime := TSimpleSupabaseRealtime.New(
'https://abcdefghij.supabase.co',
'eyJ...anonKey...'
);
// Ou com intervalo personalizado: 5000ms (5 segundos)
LRealtime := TSimpleSupabaseRealtime.New(
'https://abcdefghij.supabase.co',
'eyJ...anonKey...',
5000
);
end;
Callbacks Globais
Callbacks globais sao disparados para QUALQUER tabela inscrita. Use aEvent.Table para identificar a origem.
var
LRealtime: iSimpleSupabaseRealtime;
begin
LRealtime := TSimpleSupabaseRealtime.New(
'https://abcdefghij.supabase.co',
'eyJ...apiKey...'
);
// Callback para INSERTs em qualquer tabela inscrita
LRealtime.OnInsert(
procedure(aEvent: TSupabaseRealtimeEvent)
begin
Writeln('[INSERT] Tabela: ', aEvent.Table);
Writeln(' Novo registro: ', aEvent.NewRecord);
Writeln(' Tipo: ', Ord(aEvent.EventType));
end
);
// Callback para UPDATEs em qualquer tabela inscrita
LRealtime.OnUpdate(
procedure(aEvent: TSupabaseRealtimeEvent)
begin
Writeln('[UPDATE] Tabela: ', aEvent.Table);
Writeln(' Registro atualizado: ', aEvent.NewRecord);
end
);
// Callback para DELETEs em qualquer tabela inscrita
LRealtime.OnDelete(
procedure(aEvent: TSupabaseRealtimeEvent)
begin
Writeln('[DELETE] Tabela: ', aEvent.Table);
Writeln(' Registro removido: ', aEvent.OldRecord);
end
);
// Inscrever em tabelas e conectar
LRealtime
.Subscribe('produto')
.Subscribe('cliente')
.Connect;
end;
TThread.Queue,
o que significa que eles executam na thread principal (main thread). E seguro atualizar
componentes visuais (VCL/FMX) diretamente dentro dos callbacks.
Callbacks por Tabela
Use OnChange para registrar um callback especifico para uma tabela.
Este callback e disparado para QUALQUER tipo de evento (INSERT, UPDATE, DELETE) naquela tabela.
var
LRealtime: iSimpleSupabaseRealtime;
begin
LRealtime := TSimpleSupabaseRealtime.New(
'https://abcdefghij.supabase.co',
'eyJ...apiKey...'
);
// Callback especifico para a tabela 'produto'
LRealtime.OnChange('produto',
procedure(aEvent: TSupabaseRealtimeEvent)
begin
Writeln('Mudanca na tabela PRODUTO!');
Writeln(' Tipo: ', Ord(aEvent.EventType));
Writeln(' Dados: ', aEvent.NewRecord);
end
);
// Callback especifico para a tabela 'pedido'
LRealtime.OnChange('pedido',
procedure(aEvent: TSupabaseRealtimeEvent)
begin
Writeln('Mudanca na tabela PEDIDO!');
case aEvent.EventType of
TSupabaseEventType.setInsert: Writeln(' Novo pedido criado!');
TSupabaseEventType.setUpdate: Writeln(' Pedido atualizado!');
TSupabaseEventType.setDelete: Writeln(' Pedido removido!');
end;
end
);
LRealtime
.Subscribe('produto')
.Subscribe('pedido')
.Connect;
end;
Subscribe / Unsubscribe
Subscribe — Inscrever em tabelas
Registra uma tabela para monitoramento. Pode ser chamado multiplas vezes (fluent interface).
LRealtime
.Subscribe('produto')
.Subscribe('cliente')
.Subscribe('pedido')
.Subscribe('categoria');
// Inscrever a mesma tabela duas vezes nao causa duplicacao
LRealtime.Subscribe('produto'); // Ignorado — ja inscrito
Unsubscribe — Cancelar inscricao
Remove uma tabela do monitoramento. Se a tabela nao esta inscrita, nada acontece.
// Parar de monitorar 'pedido'
LRealtime.Unsubscribe('pedido');
// Nenhum efeito se nao esta inscrito
LRealtime.Unsubscribe('tabela_inexistente');
Subscribe e
Unsubscribe mesmo apos Connect. As mudancas sao aplicadas
no proximo ciclo de polling (thread-safe via TCriticalSection).
Connect / Disconnect
Connect
Inicia a thread de polling. A partir deste ponto, os callbacks comecam a ser disparados.
LRealtime
.Subscribe('produto')
.OnInsert(
procedure(aEvent: TSupabaseRealtimeEvent)
begin
Writeln('Novo produto: ', aEvent.NewRecord);
end
)
.Connect;
Writeln('Monitoramento ativo. Pressione ENTER para parar...');
Readln;
Disconnect
Para a thread de polling. Aguarda a thread finalizar (WaitFor) e libera os recursos.
// Parar monitoramento
LRealtime.Disconnect;
Writeln('Monitoramento encerrado.');
// Chamar Disconnect novamente nao causa erro
LRealtime.Disconnect; // No-op, ja desconectado
TSimpleSupabaseRealtime for
destruido (referencia de interface zerada), o Disconnect e chamado automaticamente
no destrutor.
Verificar estado de conexao
if LRealtime.IsConnected then
Writeln('Realtime esta ativo')
else
Writeln('Realtime esta parado');
Intervalo de Polling
O intervalo de polling define a cada quantos milissegundos a thread consulta as tabelas.
No construtor
// Polling a cada 500ms (muito responsivo, mais consumo de rede)
LRealtime := TSimpleSupabaseRealtime.New(
'https://abcdefghij.supabase.co',
'eyJ...apiKey...',
500
);
// Polling a cada 10 segundos (menos consumo, mais lento para detectar mudancas)
LRealtime := TSimpleSupabaseRealtime.New(
'https://abcdefghij.supabase.co',
'eyJ...apiKey...',
10000
);
Apos criacao
// Alterar intervalo (antes de Connect)
LRealtime.PollInterval(3000); // 3 segundos
Recomendacoes de intervalo
| Intervalo | Uso Recomendado |
|---|---|
500ms | Chat, colaboracao em tempo real (alta carga de rede) |
1000ms - 2000ms | Dashboards, notificacoes (padrao, bom equilibrio) |
5000ms - 10000ms | Sincronizacao de dados, background jobs (baixa carga) |
30000ms+ | Verificacao periodica, tarefas agendadas |
Token de autenticacao para Realtime
Se as tabelas monitoradas estao protegidas por RLS, passe o token de autenticacao:
var
LAuth: iSimpleSupabaseAuth;
LRealtime: iSimpleSupabaseRealtime;
begin
LAuth := TSimpleSupabaseAuth.New(url, anonKey);
LAuth.SignIn('user@email.com', 'pass');
LRealtime := TSimpleSupabaseRealtime.New(url, anonKey);
LRealtime.Token(LAuth.Token);
LRealtime
.Subscribe('tarefa')
.OnInsert(
procedure(aEvent: TSupabaseRealtimeEvent)
begin
Writeln('Nova tarefa: ', aEvent.NewRecord);
end
)
.Connect;
end;
Referencia de API
TSimpleQuerySupabase
Driver de query que implementa iSimpleQuery. Traduz SQL para chamadas REST PostgREST.
Unit: SimpleQuerySupabase.pas
Construtores
| Metodo | Parametros | Retorno | Descricao |
|---|---|---|---|
New |
aBaseURL, aAPIKey: String |
iSimpleQuery |
Cria driver com URL e API key. Token de autorizacao = API key. |
New |
aBaseURL, aAPIKey, aToken: String |
iSimpleQuery |
Cria driver com JWT estatico para autorizacao. |
New |
aBaseURL, aAPIKey: String; aAuth: iSimpleSupabaseAuth |
iSimpleQuery |
Cria driver com objeto auth. Token e obtido automaticamente com auto-refresh. |
Metodos iSimpleQuery
| Metodo | Retorno | Descricao |
|---|---|---|
SQL | TStrings | Retorna o TStringList interno para definir o SQL. |
Params | TParams | Retorna os parametros da query. |
ExecSQL | iSimpleQuery | Executa INSERT/UPDATE/DELETE. Parseia SQL, converte para HTTP, envia requisicao. |
DataSet | TDataSet | Retorna o TClientDataSet interno com resultados do ultimo Open. |
Open(aSQL: String) | iSimpleQuery | Define SQL e abre (executa SELECT). Resultado fica no DataSet. |
Open | iSimpleQuery | Abre com SQL ja definido via SQL.Text. |
StartTransaction | iSimpleQuery | No-op. REST e stateless. |
Commit | iSimpleQuery | No-op. REST e stateless. |
Rollback | iSimpleQuery | No-op. REST e stateless. |
&EndTransaction | iSimpleQuery | Delega para Commit (no-op). |
InTransaction | Boolean | Sempre retorna False. |
SQLType | TSQLType | Retorna TSQLType.MySQL (padrao). |
RowsAffected | Integer | Retorna -1 (nao disponivel via REST). |
Metodos adicionais
| Metodo | Parametros | Retorno | Descricao |
|---|---|---|---|
Token |
aValue: String |
iSimpleQuery |
Define/altera o token JWT usado na autorizacao. |
Metodos protegidos (parsing de SQL)
| Metodo | Descricao |
|---|---|
ExtractTableName | Extrai nome da tabela do SQL (apos FROM, INTO, ou UPDATE). |
DetectOperation | Detecta operacao: INSERT, UPDATE, DELETE ou SELECT. |
ExtractPKFieldName | Extrai o nome do campo PK da clausula WHERE. |
ExtractPKValue | Extrai o valor da PK dos parametros. |
ExtractInsertFields | Extrai nomes dos campos do INSERT (entre parenteses). |
ExtractSelectFields | Extrai campos do SELECT (entre SELECT e FROM). |
ExtractWhereFilters | Converte WHERE SQL para filtros PostgREST (campo=eq.valor). |
ExtractPagination | Detecta e extrai Skip/Take de qualquer dialeto SQL. |
ExtractUpdateFields | Extrai campos do SET no UPDATE. |
BuildSupabaseURL | Monta URL: baseURL + /rest/v1/ + tabela. |
ParamsToJSON | Converte TParams para JSON object, respeitando tipos. |
TSimpleSupabaseAuth
Gerenciador de autenticacao. Implementa iSimpleSupabaseAuth.
Unit: SimpleSupabaseAuth.pas
Construtor
| Metodo | Parametros | Retorno | Descricao |
|---|---|---|---|
New |
aBaseURL, aAPIKey: String |
iSimpleSupabaseAuth |
Cria instancia com URL do projeto e API key (tipicamente anon key). |
Metodos de autenticacao
| Metodo | Parametros | Retorno | Descricao |
|---|---|---|---|
SignIn |
aEmail, aPassword: String |
iSimpleSupabaseAuth |
Login com email/senha. Endpoint: /auth/v1/token?grant_type=password |
SignUp |
aEmail, aPassword: String |
iSimpleSupabaseAuth |
Criar conta. Endpoint: /auth/v1/signup |
SignOut |
(nenhum) | iSimpleSupabaseAuth |
Logout. Limpa todos os tokens e dados do usuario. Endpoint: /auth/v1/logout |
RefreshToken |
(nenhum) | iSimpleSupabaseAuth |
Renova JWT via refresh token. Endpoint: /auth/v1/token?grant_type=refresh_token |
Propriedades (somente leitura)
| Propriedade | Tipo | Descricao |
|---|---|---|
Token |
String |
Access token JWT. Chama auto-refresh se faltam < 30s para expirar. |
User |
String |
Dados do usuario em formato JSON (retorno de user do Supabase Auth). |
IsAuthenticated |
Boolean |
True se Token nao esta vazio E ExpiresAt > Now. |
ExpiresAt |
TDateTime |
Data/hora de expiracao do token. Calculada via Now + expires_in da resposta. |
Endpoints Supabase Auth utilizados
| Operacao | Metodo HTTP | Endpoint |
|---|---|---|
| SignIn | POST | /auth/v1/token?grant_type=password |
| SignUp | POST | /auth/v1/signup |
| SignOut | POST | /auth/v1/logout |
| RefreshToken | POST | /auth/v1/token?grant_type=refresh_token |
TSimpleSupabaseRealtime
Monitoramento de mudancas via polling. Implementa iSimpleSupabaseRealtime.
Unit: SimpleSupabaseRealtime.pas
Construtor
| Metodo | Parametros | Retorno | Descricao |
|---|---|---|---|
New |
aBaseURL, aAPIKey: String; aPollIntervalMs: Integer = 2000 |
iSimpleSupabaseRealtime |
Cria instancia com intervalo de polling (padrao 2 segundos). |
Metodos de inscricao
| Metodo | Parametros | Retorno | Descricao |
|---|---|---|---|
Subscribe |
aTable: String |
iSimpleSupabaseRealtime |
Inscreve para monitorar tabela. Ignora se ja inscrito. Thread-safe. |
Unsubscribe |
aTable: String |
iSimpleSupabaseRealtime |
Cancela inscricao. Ignora se nao inscrito. Thread-safe. |
Metodos de callback
| Metodo | Parametros | Retorno | Descricao |
|---|---|---|---|
OnInsert |
aCallback: TSupabaseRealtimeCallback |
iSimpleSupabaseRealtime |
Callback global para INSERTs em qualquer tabela inscrita. |
OnUpdate |
aCallback: TSupabaseRealtimeCallback |
iSimpleSupabaseRealtime |
Callback global para UPDATEs em qualquer tabela inscrita. |
OnDelete |
aCallback: TSupabaseRealtimeCallback |
iSimpleSupabaseRealtime |
Callback global para DELETEs em qualquer tabela inscrita. |
OnChange |
aTable: String; aCallback: TSupabaseRealtimeCallback |
iSimpleSupabaseRealtime |
Callback especifico para uma tabela (disparado em qualquer tipo de evento). |
Metodos de controle
| Metodo | Parametros | Retorno | Descricao |
|---|---|---|---|
Connect |
(nenhum) | iSimpleSupabaseRealtime |
Inicia thread de polling. No-op se ja conectado. |
Disconnect |
(nenhum) | iSimpleSupabaseRealtime |
Para thread. Chama Terminate + WaitFor. No-op se nao conectado. |
IsConnected |
(nenhum) | Boolean |
Retorna True se a thread de polling esta ativa. |
Token |
aValue: String |
iSimpleSupabaseRealtime |
Define token JWT para autorizacao nas requisicoes de polling. |
PollInterval |
aMs: Integer |
iSimpleSupabaseRealtime |
Altera intervalo de polling em milissegundos. |
Como o polling funciona internamente
| Etapa | Descricao |
|---|---|
| 1 | Thread consulta cada tabela inscrita via GET /rest/v1/tabela?order=id.desc&limit=10 |
| 2 | Se LastKnownId existe, adiciona filtro &id=gt.lastId para buscar apenas novos |
| 3 | Registros encontrados sao iterados em ordem cronologica (mais antigo primeiro) |
| 4 | Para cada registro, cria TSupabaseRealtimeEvent e dispara callbacks via TThread.Queue |
| 5 | Atualiza LastKnownId com o maior ID encontrado |
| 6 | Dorme pelo intervalo de polling e repete |
TSupabaseRealtimeEvent
Record que representa um evento realtime. Passado como parametro nos callbacks.
Unit: SimpleTypes.pas
Campos
| Campo | Tipo | Descricao |
|---|---|---|
Table |
String |
Nome da tabela onde o evento ocorreu. |
EventType |
TSupabaseEventType |
Tipo do evento: setInsert, setUpdate, ou setDelete. |
OldRecord |
String |
JSON do registro antes da alteracao (vazio para INSERTs). |
NewRecord |
String |
JSON do registro apos a alteracao (vazio para DELETEs). |
TSupabaseEventType (enum)
| Valor | Descricao |
|---|---|
setInsert | Novo registro inserido. |
setUpdate | Registro atualizado. |
setDelete | Registro removido. |
TSupabaseRealtimeCallback (tipo)
TSupabaseRealtimeCallback = reference to procedure(aEvent: TSupabaseRealtimeEvent);
Procedimento anonimo que recebe um evento realtime. Usado em OnInsert,
OnUpdate, OnDelete e OnChange.
Exemplos Completos
Exemplos compilaveis prontos para copiar e executar. Substitua URL e API key pelos seus valores.
App Console Basico — CRUD
Exemplo minimo de CRUD com Supabase. Nao usa autenticacao nem realtime.
program SupabaseBasico;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
System.Generics.Collections,
SimpleInterface,
SimpleDAO,
SimpleAttributes,
SimpleQuerySupabase;
type
[Tabela('produto')]
TProduto = class
private
FId: Integer;
FNome: String;
FPreco: Double;
published
[Campo('id'), PK, AutoInc]
property Id: Integer read FId write FId;
[Campo('nome'), NotNull]
property Nome: String read FNome write FNome;
[Campo('preco')]
property Preco: Double read FPreco write FPreco;
end;
const
SUPA_URL = 'https://abcdefghij.supabase.co'; // Altere para sua URL
SUPA_KEY = 'eyJ...sua-service-role-key...'; // Altere para sua key
var
LQuery: iSimpleQuery;
LDAO: iSimpleDAO<TProduto>;
LProduto: TProduto;
LList: TObjectList<TProduto>;
I: Integer;
begin
try
LQuery := TSimpleQuerySupabase.New(SUPA_URL, SUPA_KEY);
LDAO := TSimpleDAO<TProduto>.New(LQuery);
// === INSERT ===
Writeln('--- INSERT ---');
LProduto := TProduto.Create;
try
LProduto.Nome := 'Teclado Mecanico';
LProduto.Preco := 299.90;
LDAO.Insert(LProduto);
Writeln('Inserido: ', LProduto.Nome);
finally
LProduto.Free;
end;
// === FIND ALL ===
Writeln('');
Writeln('--- FIND ALL ---');
LList := LDAO.Find;
try
for I := 0 to LList.Count - 1 do
Writeln(LList[I].Id, ' | ', LList[I].Nome, ' | R$ ', LList[I].Preco:0:2);
finally
LList.Free;
end;
// === UPDATE ===
Writeln('');
Writeln('--- UPDATE ---');
LList := LDAO.Find;
try
if LList.Count > 0 then
begin
LProduto := LList[0];
LProduto.Nome := LProduto.Nome + ' (atualizado)';
LProduto.Preco := LProduto.Preco + 50;
LDAO.Update(LProduto);
Writeln('Atualizado: ', LProduto.Nome);
end;
finally
LList.Free;
end;
// === PAGINACAO ===
Writeln('');
Writeln('--- PAGINACAO (5 por pagina) ---');
LList := LDAO.SQL.Skip(0).Take(5).&End.Find;
try
Writeln('Pagina 1: ', LList.Count, ' registros');
finally
LList.Free;
end;
// === DELETE ===
Writeln('');
Writeln('--- DELETE ---');
LList := LDAO.Find;
try
if LList.Count > 0 then
begin
LProduto := LList[LList.Count - 1];
Writeln('Removendo: ', LProduto.Nome);
LDAO.Delete(LProduto);
Writeln('Removido com sucesso!');
end;
finally
LList.Free;
end;
Writeln('');
Writeln('Concluido!');
except
on E: Exception do
Writeln('ERRO: ', E.Message);
end;
Readln;
end.
App com Autenticacao
Exemplo com SignIn, verificacao de token e CRUD com RLS.
program SupabaseAuth;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
System.Generics.Collections,
SimpleInterface,
SimpleDAO,
SimpleAttributes,
SimpleQuerySupabase,
SimpleSupabaseAuth;
type
[Tabela('tarefa')]
TTarefa = class
private
FId: Integer;
FTitulo: String;
FConcluida: Boolean;
FUserId: String;
published
[Campo('id'), PK, AutoInc]
property Id: Integer read FId write FId;
[Campo('titulo'), NotNull]
property Titulo: String read FTitulo write FTitulo;
[Campo('concluida')]
property Concluida: Boolean read FConcluida write FConcluida;
[Campo('user_id')]
property UserId: String read FUserId write FUserId;
end;
const
SUPA_URL = 'https://abcdefghij.supabase.co';
ANON_KEY = 'eyJ...sua-anon-key...';
var
LAuth: iSimpleSupabaseAuth;
LQuery: iSimpleQuery;
LDAO: iSimpleDAO<TTarefa>;
LTarefa: TTarefa;
LList: TObjectList<TTarefa>;
I: Integer;
begin
try
// === AUTENTICACAO ===
Writeln('--- AUTENTICACAO ---');
LAuth := TSimpleSupabaseAuth.New(SUPA_URL, ANON_KEY);
LAuth.SignIn('usuario@email.com', 'minhaSenha123');
if not LAuth.IsAuthenticated then
begin
Writeln('Falha no login!');
Readln;
Exit;
end;
Writeln('Login OK!');
Writeln('Token: ', Copy(LAuth.Token, 1, 40), '...');
Writeln('User: ', LAuth.User);
Writeln('Expira: ', DateTimeToStr(LAuth.ExpiresAt));
// === CRIAR DAO COM AUTH (RLS ativo) ===
LQuery := TSimpleQuerySupabase.New(SUPA_URL, ANON_KEY, LAuth);
LDAO := TSimpleDAO<TTarefa>.New(LQuery);
// === INSERT ===
Writeln('');
Writeln('--- INSERIR TAREFA ---');
LTarefa := TTarefa.Create;
try
LTarefa.Titulo := 'Estudar SimpleORM + Supabase';
LTarefa.Concluida := False;
LDAO.Insert(LTarefa);
Writeln('Tarefa criada: ', LTarefa.Titulo);
finally
LTarefa.Free;
end;
// === FIND (retorna apenas tarefas deste usuario via RLS) ===
Writeln('');
Writeln('--- MINHAS TAREFAS ---');
LList := LDAO.Find;
try
Writeln('Total: ', LList.Count);
for I := 0 to LList.Count - 1 do
begin
if LList[I].Concluida then
Writeln(' [X] ', LList[I].Titulo)
else
Writeln(' [ ] ', LList[I].Titulo);
end;
finally
LList.Free;
end;
// === VERIFICAR AUTO-REFRESH ===
Writeln('');
Writeln('--- TOKEN (com auto-refresh) ---');
Writeln('Token atual: ', Copy(LAuth.Token, 1, 40), '...');
Writeln('(Se faltavam < 30s, foi renovado automaticamente)');
// === SIGNOUT ===
Writeln('');
Writeln('--- SIGNOUT ---');
LAuth.SignOut;
Writeln('Autenticado: ', LAuth.IsAuthenticated); // False
except
on E: Exception do
Writeln('ERRO: ', E.Message);
end;
Readln;
end.
App com Realtime
Exemplo que monitora mudancas em tabelas e exibe notificacoes no console.
program SupabaseRealtime;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
SimpleInterface,
SimpleTypes,
SimpleSupabaseRealtime;
const
SUPA_URL = 'https://abcdefghij.supabase.co';
SUPA_KEY = 'eyJ...sua-api-key...';
var
LRealtime: iSimpleSupabaseRealtime;
begin
try
Writeln('=== SimpleORM Supabase Realtime ===');
Writeln('');
LRealtime := TSimpleSupabaseRealtime.New(SUPA_URL, SUPA_KEY, 2000);
// Callback global para INSERTs
LRealtime.OnInsert(
procedure(aEvent: TSupabaseRealtimeEvent)
begin
Writeln('[INSERT] ', aEvent.Table, ': ', aEvent.NewRecord);
end
);
// Callback global para UPDATEs
LRealtime.OnUpdate(
procedure(aEvent: TSupabaseRealtimeEvent)
begin
Writeln('[UPDATE] ', aEvent.Table, ': ', aEvent.NewRecord);
end
);
// Callback global para DELETEs
LRealtime.OnDelete(
procedure(aEvent: TSupabaseRealtimeEvent)
begin
Writeln('[DELETE] ', aEvent.Table, ': ', aEvent.OldRecord);
end
);
// Callback especifico para tabela 'produto'
LRealtime.OnChange('produto',
procedure(aEvent: TSupabaseRealtimeEvent)
begin
Writeln(' >> Mudanca especifica em PRODUTO detectada!');
end
);
// Inscrever em tabelas
LRealtime
.Subscribe('produto')
.Subscribe('cliente')
.Subscribe('pedido');
// Iniciar monitoramento
LRealtime.Connect;
Writeln('Monitorando tabelas: produto, cliente, pedido');
Writeln('Intervalo de polling: 2 segundos');
Writeln('');
Writeln('Faca INSERTs/UPDATEs/DELETEs no Supabase Dashboard');
Writeln('e veja as notificacoes aqui.');
Writeln('');
Writeln('Pressione ENTER para encerrar...');
Readln;
// Parar monitoramento
LRealtime.Disconnect;
Writeln('Monitoramento encerrado.');
except
on E: Exception do
Writeln('ERRO: ', E.Message);
end;
end.
App Completa (CRUD + Auth + Realtime)
Exemplo completo que demonstra todos os 3 componentes trabalhando juntos.
program SupabaseCompleto;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
System.Generics.Collections,
SimpleInterface,
SimpleTypes,
SimpleDAO,
SimpleAttributes,
SimpleQuerySupabase,
SimpleSupabaseAuth,
SimpleSupabaseRealtime;
type
[Tabela('produto')]
TProduto = class
private
FId: Integer;
FNome: String;
FPreco: Double;
FAtivo: Boolean;
published
[Campo('id'), PK, AutoInc]
property Id: Integer read FId write FId;
[Campo('nome'), NotNull]
property Nome: String read FNome write FNome;
[Campo('preco')]
property Preco: Double read FPreco write FPreco;
[Campo('ativo')]
property Ativo: Boolean read FAtivo write FAtivo;
end;
const
SUPA_URL = 'https://abcdefghij.supabase.co'; // Altere
ANON_KEY = 'eyJ...sua-anon-key...'; // Altere
USER_EMAIL = 'admin@empresa.com'; // Altere
USER_PASS = 'senhaSegura123'; // Altere
var
LAuth: iSimpleSupabaseAuth;
LQuery: iSimpleQuery;
LDAO: iSimpleDAO<TProduto>;
LRealtime: iSimpleSupabaseRealtime;
LProduto: TProduto;
LList: TObjectList<TProduto>;
I: Integer;
begin
try
Writeln('========================================');
Writeln(' SimpleORM + Supabase - App Completa');
Writeln('========================================');
Writeln('');
// ============================================================
// PASSO 1: Autenticacao
// ============================================================
Writeln('[1/4] Autenticacao...');
LAuth := TSimpleSupabaseAuth.New(SUPA_URL, ANON_KEY);
LAuth.SignIn(USER_EMAIL, USER_PASS);
if not LAuth.IsAuthenticated then
begin
Writeln(' FALHA: Login nao realizado');
Readln;
Exit;
end;
Writeln(' OK - Logado como: ', USER_EMAIL);
Writeln(' Token expira em: ', DateTimeToStr(LAuth.ExpiresAt));
// ============================================================
// PASSO 2: Configurar DAO com Auth
// ============================================================
Writeln('');
Writeln('[2/4] Configurando DAO...');
LQuery := TSimpleQuerySupabase.New(SUPA_URL, ANON_KEY, LAuth);
LDAO := TSimpleDAO<TProduto>.New(LQuery);
Writeln(' OK - DAO configurado com autenticacao');
// ============================================================
// PASSO 3: Configurar Realtime
// ============================================================
Writeln('');
Writeln('[3/4] Configurando Realtime...');
LRealtime := TSimpleSupabaseRealtime.New(SUPA_URL, ANON_KEY, 2000);
LRealtime.Token(LAuth.Token);
LRealtime.OnInsert(
procedure(aEvent: TSupabaseRealtimeEvent)
begin
Writeln(' [REALTIME INSERT] ', aEvent.Table, ': ', aEvent.NewRecord);
end
);
LRealtime.OnChange('produto',
procedure(aEvent: TSupabaseRealtimeEvent)
begin
Writeln(' [REALTIME CHANGE] Produto mudou!');
end
);
LRealtime
.Subscribe('produto')
.Connect;
Writeln(' OK - Monitorando tabela produto');
// ============================================================
// PASSO 4: Operacoes CRUD
// ============================================================
Writeln('');
Writeln('[4/4] Executando CRUD...');
// INSERT
Writeln('');
Writeln(' --- INSERT ---');
LProduto := TProduto.Create;
try
LProduto.Nome := 'Monitor 4K';
LProduto.Preco := 2199.00;
LProduto.Ativo := True;
LDAO.Insert(LProduto);
Writeln(' Inserido: ', LProduto.Nome);
finally
LProduto.Free;
end;
LProduto := TProduto.Create;
try
LProduto.Nome := 'Mouse Gamer';
LProduto.Preco := 189.90;
LProduto.Ativo := True;
LDAO.Insert(LProduto);
Writeln(' Inserido: ', LProduto.Nome);
finally
LProduto.Free;
end;
// FIND ALL
Writeln('');
Writeln(' --- FIND ALL ---');
LList := LDAO.Find;
try
Writeln(' Total: ', LList.Count, ' registros');
for I := 0 to LList.Count - 1 do
Writeln(' ', LList[I].Id:4, ' | ', LList[I].Nome:30, ' | R$ ', LList[I].Preco:8:2);
finally
LList.Free;
end;
// PAGINACAO
Writeln('');
Writeln(' --- PAGINACAO ---');
LList := LDAO.SQL.Skip(0).Take(3).&End.Find;
try
Writeln(' Primeiros 3: ', LList.Count, ' registros');
finally
LList.Free;
end;
// UPDATE
Writeln('');
Writeln(' --- UPDATE ---');
LList := LDAO.Find;
try
if LList.Count > 0 then
begin
LProduto := LList[0];
LProduto.Preco := LProduto.Preco * 0.9; // 10% desconto
LDAO.Update(LProduto);
Writeln(' Atualizado: ', LProduto.Nome, ' novo preco: R$ ', LProduto.Preco:0:2);
end;
finally
LList.Free;
end;
// DELETE
Writeln('');
Writeln(' --- DELETE ---');
LList := LDAO.Find;
try
if LList.Count > 0 then
begin
LProduto := LList[LList.Count - 1];
Writeln(' Removendo: ', LProduto.Nome);
LDAO.Delete(LProduto);
Writeln(' Removido!');
end;
finally
LList.Free;
end;
// ============================================================
// RESULTADO FINAL
// ============================================================
Writeln('');
Writeln('========================================');
Writeln(' Operacoes concluidas!');
Writeln(' Auth: ', LAuth.IsAuthenticated);
Writeln(' Realtime: ', LRealtime.IsConnected);
Writeln('========================================');
Writeln('');
Writeln('Pressione ENTER para encerrar...');
Readln;
// Cleanup
LRealtime.Disconnect;
LAuth.SignOut;
except
on E: Exception do
begin
Writeln('ERRO: ', E.Message);
Readln;
end;
end;
end.
Troubleshooting
Erros HTTP comuns
| Codigo | Mensagem | Causa | Solucao |
|---|---|---|---|
401 |
Supabase HTTP 401: ... |
API key invalida ou token expirado | Verifique a API key no Dashboard (Settings > API). Se usando auth, verifique se IsAuthenticated = True. |
403 |
Supabase HTTP 403: ... |
RLS bloqueou o acesso | Use service_role key (ignora RLS) ou configure politicas RLS corretas para o usuario. |
404 |
Supabase HTTP 404: ... |
Tabela nao existe ou URL errada | Verifique se a tabela existe no Dashboard. Verifique se o nome no [Tabela('nome')] esta correto (case-sensitive). |
400 |
Supabase HTTP 400: ... |
Body JSON invalido ou campo inexistente | Verifique se os nomes em [Campo('nome')] correspondem as colunas da tabela no Supabase. |
409 |
Supabase HTTP 409: ... |
Conflito de chave unica (registro duplicado) | O registro com essa PK ou constraint UNIQUE ja existe. Use Update em vez de Insert. |
Problemas comuns
Transacoes nao funcionam
StartTransaction, Commit e Rollback sao no-ops.
InTransaction sempre retorna False. Cada operacao e executada
individualmente. Se voce precisa de transacoes atomicas, use um driver local (FireDAC, UniDAC).
RLS bloqueando acesso — vejo 0 registros
Checklist de diagnostico:
| Verificacao | Como resolver |
|---|---|
Usando anon key (nao service_role)? | RLS so se aplica com anon key. Com service_role, RLS e ignorado. |
| Usuario esta autenticado? | Verifique LAuth.IsAuthenticated antes de usar o DAO. |
| Politica RLS existe para SELECT? | No Dashboard, va em Authentication > Policies e verifique se ha uma politica para SELECT. |
Coluna user_id esta preenchida? | Inserts devem incluir user_id = auth.uid() na politica ou na aplicacao. |
Token expirou e auto-refresh nao funcionou
O auto-refresh falha silenciosamente para nao quebrar a aplicacao. Possiveis causas:
| Causa | Solucao |
|---|---|
| Rede indisponivel | O token atual e retornado. A proxima chamada tentara refresh novamente. |
| Refresh token invalido (usuario removido) | Chame SignIn novamente para obter novos tokens. |
| Refresh token expirou (sessao muito longa) | Supabase tem limite de vida para refresh tokens. Chame SignIn novamente. |
Realtime nao detecta mudancas
| Causa | Solucao |
|---|---|
Esqueceu de chamar Connect | Sempre chame .Connect apos .Subscribe e configurar callbacks. |
Tabela nao tem coluna id | O polling usa ?order=id.desc. A tabela deve ter uma coluna id numerica. |
| RLS bloqueando leitura | Passe o token via LRealtime.Token(LAuth.Token). |
| Intervalo muito longo | Reduza o polling: LRealtime.PollInterval(1000). |
| App console fecha imediatamente | Adicione Readln apos Connect para manter o programa aberto. |
RowsAffected retorna -1
RowsAffected sempre retorna -1.
Use Find apos operacoes para verificar o resultado.
Nomes de campos case-sensitive
aTableName.ToLower, LParam.Name.ToLower). Certifique-se de que seus
atributos [Campo('nome')] correspondam ao nome real da coluna no banco.
Erro de compilacao: Undeclared identifier 'TSimpleQuerySupabase'
Adicione SimpleQuerySupabase na clausula uses do seu programa.
Se usando Boss, verifique se o pacote esta instalado: boss install academiadocodigo/SimpleORM.
Erro: Supabase Auth: no refresh token available
Voce chamou RefreshToken sem ter feito SignIn antes.
O refresh token e obtido automaticamente durante o SignIn.
Chame LAuth.SignIn(email, senha) primeiro.