ivipbase
v1.5.3
Published
Componentes principais compartilhados do iVipBase, sem necessidade de instalação manual
Downloads
96
Readme
iVipBase realtime database (EM DESENVOLVIMENTO)
Um motor e servidor de banco de dados NoSQL rápido, de baixo consumo de memória, transacional, com suporte a índices e consultas para node.js e navegador, com notificações em tempo real para alterações de dados. Suporta o armazenamento de objetos JSON, arrays, números, strings, booleanos, datas, bigints e dados binários (ArrayBuffer).
Inspirado por (e amplamente compatível com) o banco de dados em tempo real do Firebase e AceBase, com funcionalidades adicionais e menos fragmentação/duplicação de dados. Capaz de armazenar até 2^48 (281 trilhões) de nós de objeto em um arquivo de banco de dados binário que teoricamente pode crescer até um tamanho máximo de 8 petabytes.
O iVipBase foi concebido para fornecer serviços de controle e gerenciamento de dados, com ênfase em outros serviços como autenticação, armazenamento em nuvem, funções de nuvem e extensões de nuvem, de maneira rápida e menos burocrática possível. A proposta é oferecer aos desenvolvedores do projeto alternativas, autonomia e/ou evitar encargos financeiros associados ao uso de serviços, em contraste com o Firebase.
Com o iVipBase, os usuários têm a opção de armazenar e consumir dados localmente, em sua própria máquina ou servidor virtual. Além disso, há a possibilidade de contar com serviços remotos nos servidores da iVipServices (ainda em fase de planejamento). Em outras palavras, o iVipBase proporciona aos projetos a liberdade de tomar decisões quanto aos serviços de controle e gerenciamento de dados, oferecendo flexibilidade e escolha em relação aos recursos adicionais disponíveis.
O iVipBase é fácil de configurar e pode ser executado em qualquer lugar: na nuvem, NAS, servidor local, PC/Mac, Raspberry Pi, no navegador, onde você quiser.
Índice
- iVipBase realtime database (EM DESENVOLVIMENTO)
initializeApp
- InicializaçãogetDatabase
- API banco de dadosget
- Carregando dadosset
- Armazenando dadosupdate
- Atualizando dadosremove
,null
- Removendo dadospush
- Gerando chaves exclusivasArray
- Usando matrizescount
- Contando filhosinclude
,exclude
- Limitar o carregamento de dados aninhadosforEach
- Iterando filhosTypeScript
- Afirmando tipos de dadoson
,off
- Monitorando alterações de dados em tempo realobserve
- Observe alterações de valor em tempo realQuery
- Consultando dadosreflect
- API de reflexão
getAuth
- API de autenticaçãoready
- Evento de inicializaçãocreateUserWithEmailAndPassword
- Criar usuário com e-mail e senhacreateUserWithUsernameAndPassword
- Criar usuário com nome de usuário e senhasignInWithEmailAndPassword
- Login com e-mail e senhasignInWithUsernameAndPassword
- Login com nome de usuário e senhasignInWithToken
- Login com tokensignOut
- LogoutonAuthStateChanged
- Observar mudanças de autenticaçãoonIdTokenChanged
- Observar mudanças de tokenupdateCurrentUser
- Atualizar usuário atualsendPasswordResetEmail
- Enviar e-mail de redefinição de senhaapplyActionCode
- Aplicar código de açãocheckActionCode
- Verificar código de açãoconfirmPasswordReset
- Confirmar redefinição de senhaverifyPasswordResetCode
- Verificar código de redefinição de senhaUser
- Informações do usuário autenticadoUser.accessToken
- Token de acessoUser.providerData
- Dados do provedorUser.updateProfile
- Atualizar perfilUser.updateEmail
- Atualizar e-mailUser.updatePassword
- Atualizar senhaUser.updateUsername
- Atualizar nome de usuárioUser.sendEmailVerification
- Enviar verificação de e-mailUser.delete
- Deletar usuárioUser.getIdToken
- Obter token de IDUser.getIdTokenResult
- Obter resultado do token de IDUser.reload
- Recarregar usuárioUser.toJSON
- Converter para JSONUser.fromJSON
- Converter de JSON
getStorage
- API de armazenamento em nuvemready
- Evento de inicializaçãoref
- Referência de armazenamentoput
- Enviar arquivoputString
- Enviar stringgetDownloadURL
- Obter URL de downloaddelete
- Deletar arquivo/diretóriolistAll
- Listar todos arquivos e diretórioslist
- Listar arquivos e diretóriosgetMetadata
- Obter metadadosgetBlob
- Obter blobgetBytes
- Obter bytesgetStream
- Obter streamgetBuffer
- Obter buffer
getFunctions
- API para funções de nuvemgetExtensions
- API para extensões de nuvemgetOptimized
- API para otimização de processosCustomStorage
- Armazenamento personalizadoRules
- Regras de segurança
Começando
O iVipBase está dividido em dois pacotes:
- ivipbase: mecanismo de banco de dados iVipBase local, ponto de extremidade do servidor para permitir conexões remotas. Inclui autenticação e autorização de usuário integradas, suporta o uso de provedores externos OAuth, como Facebook e Google (github, npm)
- ivipbase-core: funcionalidades compartilhadas, dependência do pacote acima (github, npm)
Por favor, relate qualquer erro ou comportamento inesperado que encontrar criando uma issue no Github.
Pré-requisitos
O iVipBase é projetado para ser executado em um ambiente Node.js, também é possível usar bancos de dados iVipBase no navegador! Para executar o iVipBase no navegador, basta incluir um arquivo de script e você estará pronto! Consulte iVipBase no navegador para mais informações e exemplos de código!
Instalação
Todos os repositórios do iVipBase estão disponíveis no npm. Você só precisa instalar um deles, dependendo de suas necessidades:
Criar um banco de dados local
Se você deseja usar um banco de dados iVipBase local em seu projeto, instale o pacote ivipbase.
npm install ivipbase
Em seguida, crie (abra) seu banco de dados:
import { initializeApp, getDatabase } from "ivipbase";
const app = initializeApp({ dbname: "my_db" });
const db = getDatabase(app);
db.ready(() => {
// Do stuff
});
Experimente o iVipBase no seu navegador
Se você quiser experimentar o iVipBase em execução no Node.js, basta abri-lo no RunKit e seguir os exemplos. Se você quiser experimentar a versão do iVipBase para o navegador, abra google.com em uma nova guia (o GitHub não permite que scripts entre sites sejam carregados) e execute o trecho de código abaixo para usá-lo imediatamente no console do seu navegador.
Para experimentar o iVipBase no RunKit:
const { initializeApp, getDatabase } = require("ivipbase");
const app = initializeApp({ dbname: "my_db" });
const db = getDatabase(app);
db.ready(async () => {
await db.ref("test").set({ text: "This is my first iVipBase test in RunKit" });
const snap = await db.ref("test/text").get();
console.log(`value of "test/text": ` + snap.val());
});
Para experimentar o iVipBase no console do navegador:
await fetch("https://cdn.jsdelivr.net/npm/ivipbase@latest/dist/browser.min.js")
.then((response) => response.text())
.then((text) => eval(text));
if (!ivipbase) {
throw "iVipBase not loaded!";
}
const app = ivipbase.initializeApp({ dbname: "my_db" });
const db = ivipbase.getDatabase(app);
db.ready(async () => {
await db.ref("test").set({ text: "This is my first iVipBase test in the browser" });
const snap = await db.ref("test/text").get();
console.log(`value of "test/text": ` + snap.val());
});
initializeApp
- Inicialização
Para iniciar a aplicação, é essencial empregar a função initializeApp
e configurar suas opções. Essa função é responsável por criar uma instância da classe IvipBaseApp
, oferecendo a flexibilidade de personalizar suas configurações. Vale notar que algumas definições podem variar de acordo com o contexto da aplicação, especificamente no que se refere à distinção entre aplicações web e de servidor.
Em cenários voltados para aplicações web, determinadas configurações direcionadas à criação de servidores podem não ser aplicáveis. No contexto de aplicações web, as definições se concentram principalmente em aspectos como armazenamento personalizado (utilizando CustomStorage
ou configurações pré-existentes para o ambiente web) ou uso remoto.
Para a utilização remota, torna-se obrigatório especificar as definições de host
, port
e dbname
. Além disso, é fundamental que uma instância de IvipBaseApp
configurada como servidor esteja em execução para estabelecer a conexão entre o servidor e o cliente. Sem essa instância em execução, as definições remotas não serão eficazes.
Uso
/**
* Inicializa o IvipBaseApp com as opções fornecidas.
* @param options Objeto JSON contendo configurações para personalizar o aplicativo.
* @returns Uma instância de IvipBaseApp.
*/
function initializeApp(options: Record<string, any>): IvipBaseApp;
| Parâmetro | Tipo | Descrição |
| --------- | -------- | -------------------------------------------------------------------------------------------- |
| options | object
| Objeto (instância IvipBaseSettings
) contendo configurações para personalizar o aplicativo. |
Contexto de aplicação cliente
As configurações no contexto de aplicação cliente são permitidas apenas para personalização do armazenamento (utilizando CustomStorage
ou configurações já existentes para o ambiente cliente) ou para uso remoto. Como mencionado anteriormente, no caso do uso remoto, as definições de host
, port
e dbname
tornam-se obrigatórias para estabelecer a comunicação com o servidor.
Opções do Objeto options
| Propriedade | Tipo | Descrição |
| ----------- | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| name | string
| O nome do aplicativo. |
| dbname | string
| Array<string>
| O nome do banco de dados ou uma lista de nomes de multiplos bancos de dados |
| logLevel | "log" \| "warn" \| "error"
| O nível de log para o aplicativo. |
| storage | CustomStorage
| DataStorageSettings
| Configurações de armazenamento para o aplicativo. Consulte armazenamento presonalizado com CustomStorage
|
| host | string \| undefined
| O endereço do host, se aplicável. |
| port | number \| undefined
| O número da porta, se aplicável. |
Certifique-se de ajustar o exemplo de uso para refletir o formato do objeto options
esperado:
import { initializeApp } from "ivipbase";
const configuracoesApp = {
host: "0.0.0.0",
port: 8080,
dbname: "bancoCustomizado",
logLevel: "error",
// ... outras opções
};
const app = initializeApp(configuracoesApp);
app.ready(()=>{
console.log("App iniciado!");
});
Contexto de aplicação servidor
Para o contexto de uma aplicação servidor, é necessário definir opções associadas ao funcionamento do aplicativo servidor, permitindo a comunicação remota com a aplicação web (cliente). Especificamente, a opção isServer
deve ser configurada para indicar que a aplicação é um servidor, juntamente com host
, port
, storage
e dbname
(ou database
). Explore também outras opções adicionais disponíveis para personalizar a aplicação.
Opções do Objeto options
| Propriedade | Tipo | Descrição |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| isServer | boolean
| Define se será uma aplicação remota ou servidor. |
| email | object
| Configurações de e-mail para habilitar o envio de e-mails pelo iVipServer, por exemplo, para boas-vindas, redefinição de senhas, notificações de logins, etc. |
| logLevel | "verbose" \| "log" \| "warn" \| "error"
| Nível de mensagens registradas no console. |
| host | string
| IP ou nome do host para iniciar o servidor. |
| port | number
| Número da porta em que o servidor estará ouvindo. |
| dbname | string
| Array<string>
| Nome do banco de dados a ser usado ou uma lista de nomes de multiplos bancos de dados |
| database | { name: string; description?: string; defineRules?: Object }
| Array<{ name: string; description?: string; defineRules?: Object }>
| Configurações para múltiplos bancos de dados. |
| storage | CustomStorage
| DataStorageSettings
| MongodbSettings
| JsonFileStorageSettings
| Configurações de armazenamento para o aplicativo. Consulte armazenamento presonalizado com CustomStorage
|
| maxPayloadSize | string
| Tamanho máximo permitido para dados enviados, por exemplo, para atualizar nós. O padrão é '10mb'. |
| allowOrigin | string
| Valor a ser usado para o cabeçalho CORS Access-Control-Allow-Origin. O padrão é '*'. |
| trustProxy | boolean
| Quando atrás de um servidor de proxy confiável, req.ip e req.hostname serão definidos corretamente. |
| authentication | object
| Configurações que definem se e como a autenticação é utilizada. |
| init | (server: any) => Promise<void>
| Função de inicialização que é executada antes do servidor adicionar o middleware 404 e começar a ouvir chamadas recebidas. Utilize esta função de retorno de chamada para estender o servidor com rotas personalizadas, adicionar regras de validação de dados, aguardar eventos externos, etc. |
| defineRules | object
| Dados iniciais para regras de acesso de banco de dados. |
Opções do Objeto options.email
| Propriedade | Tipo | Descrição |
| ------------ | ------------------------------------------------------------------------ | ---------------------------------------------------------------- |
| server | object
| Configurações do servidor de e-mail. |
| prepareModel | (request: any) => { title: string; subject: string; message: string; }
| Função opcional para preparar o modelo de e-mail antes do envio. |
Opções do Objeto options.email.server
| Propriedade | Tipo | Descrição |
| ----------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| host | string
| O nome do host ou endereço IP ao qual se conectar (o padrão é ‘localhost’). |
| port | number
| A porta à qual se conectar (o padrão é 587 se seguro for falso ou 465 se verdadeiro). |
| type | "login" \| "oauth2"
| O tipo de autenticação, padrão é ‘login’, outra opção é ‘oauth2’. |
| user | string
| O nome de usuário de login. |
| pass | string
| A senha do usuário se o login normal for usado. |
| secure | boolean
| Indica se a conexão usará TLS ao conectar-se ao servidor. Se for falso (o padrão), então o TLS será usado se o servidor suportar a extensão STARTTLS. Na maioria dos casos, defina esse valor como verdadeiro se você estiver se conectando à porta 465. Para a porta 587 ou 25, mantenha-o falso. |
Opções do Objeto options.authentication
| Propriedade | Tipo | Descrição |
| -------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| enabled | boolean
| Se a autorização deve ser habilitada. Sem autorização, o banco de dados inteiro pode ser lido e gravado por qualquer pessoa (não recomendado). |
| allowUserSignup | boolean
| Se a criação de novos usuários é permitida para qualquer pessoa ou apenas para o administrador. |
| newUserRateLimit | number
| Quantos novos usuários podem se inscrever por hora por endereço IP. Não implementado ainda. |
| tokensExpire | number
| Quantos minutos antes dos tokens de acesso expirarem. 0 para sem expiração. |
| defaultAccessRule | "deny" \| "allow" \| "auth"
| Quando o servidor é executado pela primeira vez, quais padrões usar para gerar o arquivo rules.json. Opções são: 'auth' (acesso apenas autenticado ao banco de dados, padrão), 'deny' (negar acesso a qualquer pessoa, exceto o usuário administrador), 'allow' (permitir acesso a qualquer pessoa). |
| defaultAdminPassword | string \| undefined
| Quando o servidor é executado pela primeira vez, qual senha usar para o usuário administrador. Se não fornecida, uma senha gerada será usada e mostrada UMA VEZ na saída do console. |
| separateDb | boolean \| "v2"
| Se deve usar um banco de dados separado para autenticação e logs. 'v2' armazenará dados em auth.db, o que AINDA NÃO FOI TESTADO! |
Certifique-se de ajustar o exemplo de uso para refletir o formato do objeto options
esperado:
import { initializeApp, MongodbSettings } from "ivipbase";
const configuracoesApp = {
isServer: true,
host: "0.0.0.0",
port: 8080,
dbname: "bancoCustomizado",
logLevel: "error",
allowOrigin: "*",
storage: new MongodbSettings({
host: "0.0.0.0",
port: 27017,
username: "admin",
password: "1234",
}),
// ... outras opções
};
const app = initializeApp(configuracoesApp);
app.ready(()=>{
console.log("App iniciado!");
});
NOTA: A opção logLevel
especifica quanto de informação deve ser gravado nos logs do console. Os valores possíveis são: 'verbose'
, 'log'
(padrão), 'warn'
e 'error'
(apenas erros são registrados)
Multiplos bancos de dados
No IVIPBASE também é possível criar múltiplos bancos de dados em uma única instância do servidor. Para isso, basta definir uma série de configurações para cada banco de dados que deseja criar na definição options.database
. Abaixo, segue um exemplo de como criar múltiplos bancos de dados:
import { initializeApp } from "ivipbase";
const configuracoesApp = {
isServer: true,
host: "0.0.0.0",
port: 8080,
// ... outras opções
database: [{
name: "developer",
description: "Banco de dados de desenvolvedor"
}, {
name: "production",
description: "Banco de dados de produção"
}]
};
const app = initializeApp(configuracoesApp);
getDatabase
- API banco de dados
A API é semelhante à do banco de dados em tempo real do Firebase e AceBase, com adições. Para utilizá-la, será necessário empregar a função getDatabase
. Essa função é responsável por configurar a API de consumo com as predefinições no initializeApp
. Requer um parâmetro, no qual você pode inserir a instância obtida por meio da função initializeApp
, criando uma instância da classe IvipBaseApp
ou uma string do nome da aplicação específica. Caso o parâmetro não seja fornecido, o getDatabase
considerará a primeira aplicação criada ou a aplicação padrão, se houver. Abaixo, seguem dois exemplos de uso do getDatabase
:
import { initializeApp, getDatabase } from "ivipbase";
const app = initializeApp({
dbname: "mydb", // Cria ou abre um banco de dados com o nome "mydb"
logLevel: "log",
// ... outras opções
});
const db = getDatabase(app);
db.ready(() => {
// o banco de dados está pronto para uso!
});
Neste exemplo, o getDatabase
considera a aplicação padrão ou a primeira aplicação criada com um nome definido.
import { getDatabase } from "ivipbase";
const db = getDatabase();
db.ready(() => {
// o banco de dados está pronto para uso!
});
Em caso de multiplos bancos de dados, você pode especificar o nome do banco de dados que deseja acessar:
import { getDatabase } from "ivipbase";
const db = getDatabase("developer");
db.ready(() => {
// o banco de dados está pronto para uso!
});
Nota: Nesse caso, só funcionará se você tiver definido e/ou criado um banco de dados com o nome "developer" ou "production" ao inicializar o aplicativo. Caso contrário, o primeiro banco de dados criado será considerado.
get
- Carregando dados
Execute .get
em uma referência para obter o valor armazenado atualmente. É a abreviação da sintaxe do Firebase de .once("value")
.
const snapshot = await db.ref("game/config").get();
if (snapshot.exists()) {
config = snapshot.val();
} else {
config = defaultGameConfig; // use defaults
}
Observação: ao carregar dados, o valor atualmente armazenado será agrupado e retornado em um objeto DataSnapshot
. Use snapshot.exists()
para determinar se o nó existe, snapshot.val()
para obter o valor.
set
- Armazenando dados
Definindo o valor de um nó, substituindo se existir:
const ref = await db.ref('game/config').set({
name: 'Name of the game',
max_players: 10
});
// stored at /game/config
Observação: ao armazenar dados, não importa se o caminho de destino e/ou os caminhos pai já existem. Se você armazenar dados em 'chats/somechatid/messages/msgid/receipts', qualquer nó inexistente será criado nesse caminho.
update
- Atualizando dados
A atualização do valor de um nó mescla o valor armazenado com o novo objeto. Se o nó de destino não existir, ele será criado com o valor passado.
const ref = await db.ref("game/config").update({
description: "The coolest game in the history of mankind",
});
// config was updated, now get the value (ref points to 'game/config')
const snapshot = await ref.get();
const config = snapshot.val();
// `config` now has properties "name", "max_players" and "description"
remove
, null
- Removendo dados
Você pode remover
dados com o remove método
db.ref('animals/dog')
.remove()
.then(() => { /* removed successfully */ )};
A remoção de dados também pode ser feita definindo ou atualizando seu valor para null
. Qualquer propriedade que tenha um valor nulo será removida do nó do objeto pai.
// Remove by setting it to null
db.ref('animals/dog')
.set(null)
.then(ref => { /* dog property removed */ )};
// Or, update its parent with a null value for 'dog' property
db.ref('animals')
.update({ dog: null })
.then(ref => { /* dog property removed */ )};
push
- Gerando chaves exclusivas
Para todos os dados genéricos adicionados, você precisa criar chaves que sejam exclusivas e que não entrem em conflito com chaves geradas por outros clientes. Para fazer isso, você pode gerar chaves exclusivas com push
. Nos bastidores, push usa cuid para gerar chaves que são garantidamente exclusivas e classificáveis no tempo.
db.ref('users')
.push({
name: 'Ewout',
country: 'The Netherlands'
})
.then(userRef => {
// user is saved, userRef points to something
// like 'users/jld2cjxh0000qzrmn831i7rn'
};
O exemplo acima gera a chave exclusiva e armazena o objeto imediatamente. Você também pode optar por gerar a chave, mas armazenar o valor posteriormente.
const postRef = db.ref('posts').push();
console.log(`About to add a new post with key "${postRef.key}"..`);
// ... do stuff ...
postRef.set({
title: 'My first post'
})
.then(ref => {
console.log(`Saved post "${postRef.key}"`);
};
OBSERVAÇÃO: essa abordagem é recomendada se você quiser adicionar vários objetos novos de uma vez, porque uma única atualização tem um desempenho muito mais rápido:
const newMessages = {};
// We got messages from somewhere else (eg imported from file or other db)
messages.forEach(message => {
const ref = db.ref('messages').push();
newMessages[ref.key] = message;
})
console.log(`About to add multiple messages in 1 update operation`);
db.ref('messages').update(newMessages)
.then(ref => {
console.log(`Added all messages at once`);
};
Array
- Usando matrizes
IvipBase suporta armazenamento de arrays, mas há algumas ressalvas ao trabalhar com eles. Por exemplo, você não pode remover ou inserir itens que não estejam no final do array. Os arrays IvipBase funcionam como uma pilha, você pode adicionar e remover do topo, não de dentro. No entanto, é possível editar entradas individuais ou substituir todo o array. A maneira mais segura de editar arrays é com transaction
, que exige que todos os dados sejam carregados e armazenados novamente. Em muitos casos, é mais sensato usar coleções de objetos.
Você pode usar matrizes com segurança quando:
- O número de itens é pequeno e finito, o que significa que você pode estimar o número médio típico de itens nele.
- Não há necessidade de recuperar/editar itens individuais usando seu caminho armazenado. Se você reordenar os itens em uma matriz, seus caminhos mudam (por exemplo, de
"playlist/songs[4]"
para"playlist/songs[1]")
- As entradas armazenadas são pequenas e não possuem muitos dados aninhados (strings pequenas ou objetos simples, por exemplo: chat/members com matriz de IDs de usuário
['ewout','john','pete']
) - A coleção não precisa ser editada com frequência.
Use coleções de objetos quando:
- A coleção continua crescendo (por exemplo: conteúdo gerado pelo usuário)
- O caminho dos itens é importante e de preferência não muda, por exemplo,
"playlist/songs[4]"
pode apontar para uma entrada diferente se o array for editado. Ao usar uma coleção de objetos,playlist/songs/jld2cjxh0000qzrmn831i7rn
sempre se referirá ao mesmo item. - As entradas armazenadas são grandes (por exemplo, strings/blobs/objetos grandes com muitos dados aninhados) Você precisa editar a coleção com frequência.
Dito isto, veja como trabalhar com arrays com segurança:
// Store an array with 2 songs:
await db.ref('playlist/songs').set([
{ id: 13535, title: 'Daughters', artist: 'John Mayer' },
{ id: 22345, title: 'Crazy', artist: 'Gnarls Barkley' }
]);
// Editing an array safely:
await db.ref('playlist/songs').transaction(snap => {
const songs = snap.val();
// songs is instanceof Array
// Add a song:
songs.push({ id: 7855, title: 'Formidable', artist: 'Stromae' });
// Edit the second song:
songs[1].title += ' (Live)';
// Remove the first song:
songs.splice(0, 1);
// Store the edited array:
return songs;
});
Se você não alterar a ordem das entradas em um array, é seguro usá-las em caminhos referenciados:
// Update a single array entry:
await db.ref('playlist/songs[4]/title').set('Blue on Black');
// Or:
await db.ref('playlist/songs[4]').update({ title: 'Blue on Black') };
// Or:
await db.ref('playlist/songs').update({
4: { title: 'Blue on Black', artist: 'Kenny Wayne Shepherd' }
})
// Get value of single array entry:
let snap = await db.ref('playlist/songs[2]').get();
// Get selected entries with an include filter (like you'd use with object collections)
let snap = await db.ref('playlist/songs').get({ include: [0, 5, 8] });
let songs = snap.val();
// NOTE: songs is instanceof PartialArray, which is an object with properties '0', '5', '8'
NOTA: você NÃO PODE usar ref.push()
para adicionar entradas a um array! push só pode ser usado em coleções de objetos porque gera IDs filho exclusivos, como "jpx0k53u0002ecr7s354c51l"
(que obviamente não é um índice de array válido)
Para resumir: use arrays SOMENTE se usar uma coleção de objetos parecer um exagero e seja muito cauteloso! Adicionar e remover itens só pode ser feito de/para o FIM de um array, a menos que você reescreva o array inteiro. Isso significa que você terá que saber antecipadamente quantas entradas seu array possui para poder adicionar novas entradas, o que não é realmente desejável na maioria das situações. Se você sentir necessidade de usar um array porque a ordem das entradas é importante para você ou seu aplicativo: considere usar uma coleção de objetos e adicione uma 'ordem' propriedade às entradas nas quais realizar uma classificação.
count
- Contando filhos
Para descobrir rapidamente quantos filhos um nó específico possui, use o count método em um DataReference
:
const messageCount = await db.ref('chat/mensagens'< a i=9>).count();
include
, exclude
- Limitar o carregamento de dados aninhados
Se a estrutura do seu banco de dados estiver usando aninhamento (por exemplo, armazenando postagens em 'users/someuser/posts'
em vez de em 'posts'
), talvez você queira limitar a quantidade de dados que você estão recuperando na maioria dos casos. Por exemplo: se você deseja obter os detalhes de um usuário, mas não deseja carregar todos os dados aninhados, você pode limitar explicitamente a recuperação de dados aninhados passando exclude
, include e/ou child_objects
opções para .get
:
// Exclude specific nested data:
db.ref('users/someuser')
.get({ exclude: ['posts', 'comments'] })
.then(snap => {
// snapshot contains all properties of 'someuser' except
// 'users/someuser/posts' and 'users/someuser/comments'
});
// Include specific nested data:
db.ref('users/someuser/posts')
.get({ include: ['*/title', '*/posted'] })
.then(snap => {
// snapshot contains all posts of 'someuser', but each post
// only contains 'title' and 'posted' properties
});
// Combine include & exclude:
db.ref('users/someuser')
.get({ exclude: ['comments'], include: ['posts/*/title'] })
.then(snap => {
// snapshot contains all user data without the 'comments' collection,
// and each object in the 'posts' collection only contains a 'title' property.
});
OBSERVAÇÃO: isso permite que você faça o que o Firebase não consegue: armazenar seus dados em locais lógicos e obter apenas os dados de seu interesse, rapidamente.
forEach
- Iterando filhos
Para iterar todos os filhos de uma coleção de objetos sem carregar todos os dados na memória de uma só vez, você pode usar forEach
que transmite cada filho e executa uma função de retorno de chamada com um instantâneo de seus dados. Se a função de retorno de chamada retornar false
, a iteração será interrompida. Se o retorno de chamada retornar um Promise, a iteração aguardará a resolução antes de carregar o próximo filho.
Os filhos a serem iterados são determinados no início da função. Como forEach
não bloqueia a leitura/gravação da coleção, é possível que os dados sejam alterados durante a iteração. Os filhos adicionados durante a iteração serão ignorados, os filhos removidos serão ignorados.
Também é possível carregar dados seletivamente para cada filho, utilizando o mesmo objeto de opções disponível pararef.get(options)
Exemplos:
// Transmita todos os 'books', um de cada vez (carrega todos os dados de cada 'books'):
await db.ref('books').forEach(bookSnapshot => {
const book = bookSnapshot.val();
console.log(`Got book "${book.title}": "${book.description}"`);
});
// Agora faça o mesmo, mas carregue apenas 'title' e 'description' de cada 'books':
await db.ref('books').forEach(
{ include: ['title', 'description'] },
bookSnapshot => {
const book = bookSnapshot.val();
console.log(`Got book "${book.title}": "${book.description}"`);
}
);
TypeScript
- Afirmando tipos de dados
Se estiver usando TypeScript, você pode passar um parâmetro de tipo para a maioria dos métodos de recuperação de dados que declararão o tipo do valor retornado. Observe que você é responsável por garantir que o valor corresponda ao tipo declarado em tempo de execução.
Exemplos:
const snapshot = await db.ref<MyClass>('users/someuser/posts').get<MyClass>();
// ^ parâmetro de tipo pode ir aqui, ^ aqui,
if (snapshot.exists()) {
config = snapshot.val<MyClass>();
// ^ ou aqui
}
// Um parâmetro de tipo também pode ser usado para afirmar o tipo de um parâmetro de retorno de chamada
await db.ref('users/someuser/posts')
.transaction<MyClass>(snapshot => {
const posts = snapshot.val(); // postagens são do tipo MyClass
return posts;
})
// Ou ao iterar sobre filhos
await db.ref('users').forEach<UserClass>(userSnapshot => {
const user = snapshot.val(); // o usuário é do tipo UserClass
})
on
, off
- Monitorando alterações de dados em tempo real
Você pode assinar eventos de dados para receber notificações em tempo real à medida que o nó monitorado é alterado. Quando conectado a um servidor iVipBase remoto, os eventos serão enviados aos clientes por meio de uma conexão websocket. Os eventos suportados são:
'value'
: acionado quando o valor de um nó muda (incluindo alterações em qualquer valor filho)'child_added'
: acionado quando um nó filho é adicionado, o retorno de chamada contém um instantâneo do nó filho adicionado'child_changed'
: acionado quando o valor de um nó filho é alterado, o retorno de chamada contém um instantâneo do nó filho alterado'child_removed'
: acionado quando um nó filho é removido, o retorno de chamada contém um instantâneo do nó filho removido'mutated'
: acionado quando qualquer propriedade aninhada de um nó é alterada, o retorno de chamada contém um instantâneo e uma referência da mutação exata.'mutations'
: comomutated
, mas dispara com uma matriz de todas as mutações causadas por uma única atualização do banco de dados.'notify_*'
: versão apenas para notificação dos eventos acima sem dados, consulte "Notificar apenas eventos" abaixo
// Usando retorno de chamada de evento
db.ref('users')
.on('child_added', userSnapshot => {
// dispara para todos os filhos atuais,
// e para cada novo usuário a partir de então
});
// Para poder cancelar a assinatura mais tarde:
function userAdded(userSnapshot) { /* ... */ }
db.ref('users').on('child_added', userAdded);
// Cancele a inscrição mais tarde com .off:
db.ref('users').off('child_added', userAdded);
iVipBase usa as mesmas assinaturas de método .on
e .off
do Firebase e Acebase, mas também oferece outra maneira de assinar os eventos usando o retornado. EventStream
você pode subscribe
. Ter uma assinatura ajuda a cancelar mais facilmente a inscrição nos eventos posteriormente. Além disso, subscribe os retornos de chamada são acionados apenas para eventos futuros por padrão, ao contrário do .on retorno de chamada, que também é acionado para valores atuais de eventos 'value'
e 'child_added'
:
// Usando .subscribe
const addSubscription = db.ref('users')
.on('child_added')
.subscribe(newUserSnapshot => {
// .subscribe only fires for new children from now on
});
const removeSubscription = db.ref('users')
.on('child_removed')
.subscribe(removedChildSnapshot => {
//removedChildSnapshot contém os dados removidos
// NOTA: snapshot.exists() retornará falso,
// e snapshot.val() contém o valor filho removido
});
const changesSubscription = db.ref('users')
.on('child_changed')
.subscribe(updatedUserSnapshot => {
// Obteve um novo valor para um objeto de usuário atualizado
});
// Interrompendo todas as assinaturas posteriormente:
addSubscription.stop();
removeSubscription.stop();
changesSubscription.stop();
Se você quiser usar .subscribe
enquanto também obtém retornos de chamada de dados existentes, passe true
como argumento de retorno de chamada:
db.ref('users/some_user')
.on('value', true) //passando true triggers .subscribe retorno de chamada para o valor atual também
.subscribe(userSnapshot => {
// Obteve o valor atual (1ª chamada) ou o novo valor (2ª + chamada) para some_user
});
O EventStream
retornado por .on
também pode ser usado para subscribe
mais de uma vez:
const newPostStream = db.ref('posts').on('child_added');
const subscription1 = newPostStream.subscribe(childSnapshot => { /* faça alguma coisa */ });
const subscription2 = newPostStream.subscribe(childSnapshot => { /* faço outra coisa */ });
// Para interromper a assinatura de 1:
subscription1.stop();
// ou, para interromper todas as assinaturas ativas:
newPostStream.stop();
Se estiver usando TypeScript, você pode passar um parâmetro de tipo para .on
ou para .subscribe
para declarar o tipo do valor armazenado no instantâneo. Este tipo não é verificado pelo TypeScript; é sua responsabilidade garantir que o valor armazenado corresponda à sua afirmação.
const newPostStream = db.ref('posts').on<MyClass>('child_added');
const subscription1 = newPostStream.subscribe(childSnapshot => {
const child = childSnapshot.val(); // filho é do tipo MyClass
});
const subscription2 = newPostStream.subscribe<MyOtherClass>(childSnapshot => {
const child = childSnapshot.val(); // filho é do tipo MyOtherClass
// .subscribe substituiu o parâmetro de tipo de .on
});
*
, $
- Utilizando variáveis e curingas em caminhos de assinatura
Também é possível se inscrever em eventos usando curingas e variáveis no caminho:
// Utilizando curingas:
db.ref('usuários/*/posts')
.on('child_added')
.subscribe(snap => {
// Isso será acionado para cada postagem adicionada por qualquer usuário,
// então, para o nosso exemplo .push, este será o resultado:
// snap.ref.vars === { 0: "ewout" }
const vars = snap.ref.vars;
console.log(`Nova postagem adicionada pelo usuário "${vars[0]}"`);
});
db.ref('usuários/ewout/posts').push({ title: 'nova postagem' });
// Utilizando variáveis nomeadas:
db.ref('usuários/$userid/posts/$postid/title')
.on('value')
.subscribe(snap => {
// Isso será acionado para cada novo 'title' de postagem ou alterado,
// então, para o nosso exemplo .push abaixo, este será o resultado:
// snap.ref.vars === { 0: "ewout", 1: "jpx0k53u0002ecr7s354c51l", userid: "ewout", postid: (...), $userid: (...), $postid: (...) }
// O ID do usuário estará em vars[0], vars.userid e vars.$userid
const title = snap.val();
const vars = snap.ref.vars; // contém os valores das variáveis no caminho
console.log(`O título da postagem ${vars.postid} pelo usuário ${vars.userid} foi definido como: "${title}"`);
});
db.ref('usuários/ewout/posts').push({ title: 'nova postagem' });
// Ou uma combinação:
db.ref('usuários/*/posts/$postid/title')
.on('value')
.subscribe(snap => {
// snap.ref.vars === { 0: 'ewout', 1: "jpx0k53u0002ecr7s354c51l", postid: "jpx0k53u0002ecr7s354c51l", $postid: (...) }
});
db.ref('usuários/ewout/posts').push({ título: 'nova postagem' });
notify_
- Notificar apenas eventos
Além dos eventos mencionados acima, você também pode assinar seus notify_
equivalentes que fazem o mesmo, mas com uma referência aos dados alterados em vez de um instantâneo. Isto é bastante útil se você deseja monitorar alterações, mas não está interessado nos valores reais. Isso também economiza recursos do servidor e resulta na transferência de menos dados do servidor. Ex: notify_child_changed
executará seu retorno de chamada com uma referência ao nó alterado:
ref.on('notify_child_changed', childRef => {
console.log(`child "${childRef.key}" changed`);
})
activated
- Aguarde a ativação dos eventos
Em algumas situações, é útil aguardar que os manipuladores de eventos estejam ativos antes de modificar os dados. Por exemplo, se quiser que um evento seja acionado para alterações que você está prestes a fazer, você deve certificar-se de que a assinatura está ativa antes de realizar as atualizações.
const subscription = db.ref('usuários')
.on('child_added')
.subscribe(snap => { /*...*/ });
// Utilize a promise ativada
subscription.activated()
.then(() => {
// Agora temos certeza de que a assinatura está ativa,
// adicionar um novo usuário acionará o retorno de chamada .subscribe
db.ref('usuários').push({ nome: 'Ewout' });
})
.catch(err => {
// Acesso ao caminho negado pelo servidor?
console.error(`Assinatura cancelada: ${err.message}`);
});
Se você quiser lidar com alterações no estado da assinatura depois que ela foi ativada (por exemplo, porque os direitos de acesso do lado do servidor foram alterados), forneça uma função de retorno de chamada para a chamada activated
:
subscription.activated((activated, cancelReason) => {
if (!activated) {
// Access to path denied by server?
console.error(`Subscription canceled: ${cancelReason}`);
}
});
context
- Obtenha o contexto desencadeador dos eventos
Em alguns casos, é benéfico saber o que (e/ou quem) acionou o disparo de um evento de dados, para que você possa escolher o que deseja fazer com as atualizações de dados. Agora é possível passar informações de contexto com todos os update
, set
, remove
e transaction
operações, que serão repassadas para qualquer evento acionado nos caminhos afetados (em qualquer cliente conectado!)
Imagine a seguinte situação: você tem um editor de documentos que permite que várias pessoas editem ao mesmo tempo. Ao carregar um documento você atualiza sua last_accessed
propriedade:
// Carregar documento e inscrever-se para alterações
db.ref('usuários/ewout/documentos/some_id').on('value', snap => {
// Documento carregado ou alterado. Exibir seu conteúdo
const documento = snap.val();
exibirDocumento(documento);
});
// Definir last_accessed como o horário atual
db.ref('usuários/ewout/documentos/some_id').update({ last_accessed: new Date() });
Isso acionará o evento value
DUAS VEZES e fará com que o documento seja renderizado DUAS VEZES. Além disso, se qualquer outro usuário abrir o mesmo documento, ele será acionado novamente, mesmo que não seja necessário redesenhar!
Para evitar isso, você pode passar informações contextuais com a atualização:
// Carregar documento e assinar alterações (contexto sensível!)
db.ref('usuários/ewout/documentos/some_id')
.on('value', snap => {
// Documento carregado ou alterado.
const contexto = snap.context();
if (contexto.redesenhar === false) {
// Não é necessário redesenhar!
return;
}
// Exibir seu conteúdo
const documento = snap.val();
exibirDocumento(documento);
});
// Definir last_accessed para o tempo atual, com contexto
db.ref('usuários/ewout/documentos/some_id')
.context({ redesenhar: false }) // evita redesenhos!
.update({ last_accessed: new Date() });
mutated
, mutations
- Rastreamento de alterações de dados
Esses eventos são usados principalmente pelo iVipBase nos bastidores para atualizar automaticamente os valores na memória com mutações remotas. É possível usar esses eventos sozinho, mas eles exigem alguns detalhes adicionais e provavelmente será melhor usar os métodos mencionados acima.
Dito isto, veja como usá-los:
Se você deseja monitorar o valor de um nó específico, mas não deseja obter todo o seu novo valor toda vez que uma pequena mutação é feita nele, assine a opção mutated
. Este evento só é acionado quando os dados de destino estão realmente sendo alterados. Isso permite que você mantenha uma cópia em cache de seus dados na memória (ou banco de dados de cache) e replique todas as alterações feitas nele:
const chatRef = db.ref('chats/chat_id');
// Obter valor atual
const chat = (await chatRef.get()).val();
// Inscrever-se no evento de mutação
chatRef.on('mutated', snap => {
const mutatedPath = snap.ref.path; // 'chats/chat_id/messages/message_id'
const propertyTrail =
// ['messages', 'message_id']
mutatedPath.slice(chatRef.path.length + 1).split('/');
// Navegar até o alvo da propriedade de bate-papo na memória:
let targetObject = propertyTrail.slice(0,-1).reduce((target, prop) => target[prop], chat);
// targetObject === chat.messages
const targetProperty = propertyTrail.slice(-1)[0]; // O último item no array
// targetProperty === 'message_id'
// Atualizar o valor do nosso bate-papo na memória:
const newValue = snap.val(); // { sender: 'Ewout', text: '...' }
if (newValue === null) {
// Remover
delete targetObject[targetProperty]; // delete chat.messages.message_id
}
else {
// Definir ou atualizar
targetObject[targetProperty] = newValue; // chat.messages.message_id = newValue
}
});
// Adicionar uma nova mensagem para acionar o manipulador de eventos acima
chatRef.child('messages').push({
sender: 'Ewout',
text: 'Enviando uma mensagem para você'
});
NOTA: se você estiver conectado a um servidor iVipBase remoto e a conexão for perdida, é importante que você sempre obtenha o valor mais recente ao reconectar, pois você pode ter perdido eventos de mutação.
O evento mutations
faz o mesmo que mutated
, mas será acionado no caminho da assinatura com uma matriz de todas as mutações causadas por uma única atualização do banco de dados . A melhor maneira de lidar com essas mutações é iterá-las usando snapshot.forEach
:
chatRef.on('mutations', snap => {
snap.forEach(mutationSnap => {
handleMutation(mutationSnap);
});
})
observe
- Observe alterações de valor em tempo real
Agora você pode observar o valor em tempo real de um caminho e (por exemplo) vinculá-lo à sua UI. ref.observe()
retorna um Observable RxJS que pode ser usado para observar atualizações neste nó e seus filhos. Ele não retorna instantâneos, então você pode vincular o observável diretamente à sua UI. O valor observado é atualizado internamente usando a opção mutated
evento do banco de dados. Todas as mutações do banco de dados são aplicadas automaticamente ao valor na memória e acionam o observável para emitir o novo valor.
<!-- In your Angular view template: -->
<ng-container *ngIf="liveChat | async as chat">
<h3>{{ chat.title }}</h3>
<p>Chat was started by {{ chat.startedBy }}</p>
<div class="messages">
<Message *ngFor="let item of chat.messages | keyvalue" [message]="item.value"></Message>
</div>
</ng-container>
Observe que para usar *ngFor
do Angular em uma coleção de objetos, você deve usar o canal keyvalue
.
// No seu componente Angular:
ngOnInit() {
this.liveChat = this.db.ref('chats/chat_id').observe();
}
Ou, se você quiser monitorar as atualizações por conta própria, faça a inscrição e o cancelamento:
ngOnInit() {
this.observer = this.db.ref('chats/chat_id').observe().subscribe(chat => {
this.chat = chat;
});
}
ngOnDestroy() {
// NÃO se esqueça de cancelar a inscrição!
this.observer.unsubscribe();
}
NOTA: os objetos retornados no observável são atualizados apenas no downstream - quaisquer alterações feitas localmente não serão atualizadas no banco de dados.
Query
- Consultando dados
Ao executar uma consulta, todos os nós filhos do caminho referenciado serão comparados com os critérios definidos e retornados em qualquer ordem sort
solicitada. A paginação de resultados também é suportada, portanto você pode skip
e take
qualquer número de resultados.
Para filtrar resultados, diversas instruções filter(key, operator, compare)
podem ser adicionadas. Os resultados filtrados devem corresponder a todas as condições definidas (E lógico). Os operadores de consulta suportados são:
"<"
: o valor deve ser menor quecompare"<="
: o valor deve ser menor ou igual acompare"=="
: o valor deve ser igual acompare"!="
: o valor não deve ser igual acompare">"
: o valor deve ser maior quecompare">="
: o valor deve ser maior ou igual acompare"exists"
: key deve existir"!exists"
: key não deve existir"between"
: o valor deve estar entre os 2 valores no compare array (compare[0] <= valor <= compare[1] ). Se compare[0] > compare[1], seus valores serão trocados"!between"
: o valor não deve estar entre os 2 valores em compare array (valor < compare[0] ou valor > compare[1]). Se compare[0] > compare[1], seus valores serão trocados"like"
: o valor deve ser uma string e deve corresponder ao padrão fornecido compare. Os padrões não diferenciam maiúsculas de minúsculas e podem conter curingas _ para 0 ou mais caracteres e ?"Th?"< a i=5> para 1 caractere. (padrão corresponde a "The", não "That"; padrão "Th_" corresponde a "the" e "That")"!like"
: o valor deve ser uma st