firebase-triggers
v2.0.0
Published
TypeScript decorators to assign Firebase triggers to regular methods
Downloads
61
Maintainers
Readme
Conteúdo
Instalação
npm install --save firebase-triggers
Uso
Antes de fazer o deploy para o Firebase Functions você precisa exportar os métodos no ponto de entrada da sua aplicação.
O método getFirebaseFunctionListToExport()
retorna um objeto com a lista de métodos encontrados na aplicação.
Itere o objeto exportando cada método individualmente com o nome da propriedade como no exemplo abaixo:
import 'reflect-metadata';
import { getFirebaseFunctionListToExport } from 'firebase-triggers';
// Obtém as "Cloud Functions" encontradas no código de exporta cada uma
const list = getFirebaseFunctionListToExport();
for (const key in list) {
exports[key] = list[key];
}
As Cloud Functions serão exportadas de forma agrupada por classe.
Supondo que você tenha uma classe UserCtrl
com os métodos update()
e list()
, esses métodos serão exportados com os nomes user-update e user-list respectivamente no Cloud Functions.
Exemplo simples
import 'reflect-metadata';
import { ParamsOf } from 'firebase-functions/lib/common/params';
import { FirestoreEvent, QueryDocumentSnapshot } from 'firebase-functions/v2/firestore';
import { Request } from 'firebase-functions/lib/common/providers/https';
import { Response } from 'firebase-functions';
import { getFirebaseFunctionListToExport, onFirestoreCreate, onRequest } from 'firebase-triggers';
class MyCtrl {
@onFirestoreCreate('todo/{id}')
docWrite(event: FirestoreEvent<QueryDocumentSnapshot, ParamsOf<string>>) {
const id = event.params.uid;
const data = event.data.data();
console.log(`New task ${id} added: ${data.title} at ${data.time}`);
}
@onRequest('hello-world')
httpRequest(request: Request, response: Response) {
response.send('Hello World!');
}
}
// Obtém as "Cloud Functions" encontradas no código de exporta cada uma
const list = getFirebaseFunctionListToExport();
for (const key in list) {
exports[key] = list[key];
}
Decorators
Para definir gatilhos do Firebase Functions, basta adicionar o decorator desejado sobre o método, não esquecendo-se de importar o decorator:
Ex: import { onRequest } from 'firebase-triggers';
@onFirebaseUserCreate()
Adicione o decorator @onFirebaseUserCreate()
em um método para ser executado sempre que um novo usuário for criado no Firebase Authentication.
import { onFirebaseUserCreate } from 'firebase-triggers';
import { AuthBlockingEvent } from 'firebase-functions/v2/identity';
import { EventContext } from 'firebase-functions';
class UserCtrl {
@onFirebaseUserCreate()
onCreate(event: AuthBlockingEvent) {
console.log(`${event.data.displayName} joined us`);
}
}
@onFirebaseUserDelete()
Adicione o decorator @onFirebaseUserDelete()
em um método para ser executado sempre que um usuário for removido do Firebase Authentication.
import { onFirebaseUserDelete } from 'firebase-triggers';
import { UserRecord } from 'firebase-functions/auth';
import { EventContext } from 'firebase-functions';
class UserCtrl {
@onFirebaseUserDelete()
onDelete(user: UserRecord, context: EventContext) {
console.log(`${user.displayName} left us`);
}
}
@onFirestoreCreate()
Adicione o decorator @onFirestoreCreate()
em um método para ser executado sempre que um novo documento for criado no Firestore, na collection definida parâmetro do decorator.
import { FirestoreEvent, QueryDocumentSnapshot } from 'firebase-functions/v2/firestore';
import { ParamsOf } from 'firebase-functions/lib/common/params';
import { onFirestoreCreate } from 'firebase-triggers';
class TodoCtrl {
@onFirestoreCreate('todo/{id}')
onCreate(event: FirestoreEvent<QueryDocumentSnapshot, ParamsOf<string>>) {
// Pega um objeto representando o documento. ex: { title: 'Lavar a louça', time: '12:00' }
const newValue = event.data.data();
// acessar um determinado campo como faria com qualquer propriedade JS
const title = newValue.title;
const time = newValue.time;
console.log(`New task added: ${title} at ${time}`);
}
}
@onFirestoreUpdate()
Adicione o decorator @onFirestoreUpdate()
em um método para ser executado sempre que um documento existente for alterado no Firestore, na collection definida parâmetro no decorator.
import { FirestoreEvent, QueryDocumentSnapshot } from 'firebase-functions/v2/firestore';
import { Change } from 'firebase-functions';
import { ParamsOf } from 'firebase-functions/lib/common/params';
import { onFirestoreUpdate } from 'firebase-triggers';
class TodoCtrl {
@onFirestoreUpdate('todo/{id}')
onUpdate(event: FirestoreEvent<Change<QueryDocumentSnapshot>, ParamsOf<string>>) {
// Pega um objeto representando o documento. ex: { title: 'Lavar a louça', time: '12:00' }
const newValue = event.data.after.data();
// ...ou valor anterior a esta atualização(update)
const previousValue = event.data.before.data();
// acessar um determinado campo como faria com qualquer propriedade JS
const newTitle = newValue.title;
const oldTitle = previousValue.title;
console.log(`Changed the title from "${oldTitle}" to "${newTitle}"`);
}
}
@onFirestoreDelete()
Adicione o decorator @onFirestoreDelete()
em um método para ser executado sempre que um documento for removido do Firestore, na collection definida parâmetro no decorator.
import { FirestoreEvent, QueryDocumentSnapshot } from 'firebase-functions/v2/firestore';
import { ParamsOf } from 'firebase-functions/lib/common/params';
import { onFirestoreDelete } from 'firebase-triggers';
class TodoCtrl {
@onFirestoreDelete('todo/{id}')
onDelete(event: FirestoreEvent<QueryDocumentSnapshot, ParamsOf<string>>) {
// Pega um objeto representando o documento. ex: { title: 'Lavar a louça', time: '12:00' }
const oldValue = event.data.data();
// acessar um determinado campo como faria com qualquer propriedade JS
const title = oldValue.title;
console.log(`Task "${title}" removed`);
}
}
@onFirestoreWrite()
Adicione o decorator onFirestoreWrite()
em um método para ser executado sempre que um documento for criado, alterado ou removido do Firestore, na collection definida como parâmetro do decorator.
import { FirestoreEvent, QueryDocumentSnapshot } from 'firebase-functions/v2/firestore';
import { Change } from 'firebase-functions';
import { ParamsOf } from 'firebase-functions/lib/common/params';
import { onFirestoreWrite } from 'firebase-triggers';
class TodoCtrl {
@onFirestoreWrite('todo/{id}')
onWrite(event: FirestoreEvent<Change<DocumentSnapshot>, ParamsOf<string>>) {
// Pega um objeto com o valor do documento atual. Se o documento não existir, ele foi removido.
const newDocument = event.data.after.exists ? event.data.after.data() : null;
// Pega um objeto com o valor do documento anterior (para uma atualização ou remoção (update ou delete)
const oldDocument = event.data.before.exists ? event.data.before.data() : null;
if (!newDocument) {
const title = oldDocument.title;
console.log(`Task "${title}" removed`);
return;
}
if (!oldDocument) {
const title = newDocument.title;
const time = newDocument.time;
console.log(`New task added: ${title} at ${time}`);
return;
}
const newTitle = newDocument.title;
const oldTitle = oldDocument.title;
console.log(`Changed the title from "${oldTitle}" to "${newTitle}"`);
}
}
@onPubSubPublish()
Adicione o decorator @onPubSubPublish()
em um método para ser executado sempre que for feita uma publicação via PubSub no tópico definido como parâmetro no decorator.
import { CloudEvent } from 'firebase-functions/lib/v2/core';
import { MessagePublishedData } from 'firebase-functions/v2/pubsub';
import { onPubSubPublish } from 'firebase-triggers';
class SampleCtrl {
@onPubSubPublish('my-topic')
doSomething(event: CloudEvent<MessagePublishedData<any>>) {
const publishedData = message.json;
console.log('Data published via PubSub on my-topic:', publishedData);
}
}
@onPubSubSchedule()
Adicione o decorator @onPubSubSchedule()
em um método para ser executado de forma temporizada de acordo com o intervalo definido no parâmetro no decorator seguindo os padrões do cron.
O fuso horário padrão é America/Los_Angeles.
Como alternativa pode informar um fuso horário diferente da seguinte forma: @onPubSubSchedule({ interval: '* * * * *', timezone: 'America/Araguaina' })
.
Para entender melhor como definir o horário usando o padrão do cron veja um exemplo no site https://crontab.guru.
import { onPubSubSchedule } from 'firebase-triggers';
import { ScheduledEvent } from 'firebase-functions/v2/scheduler';
class TimerCtrl {
@onPubSubSchedule('0 5 * * *')
everyDayAtFiveAM(event: ScheduledEvent) {
console.log('Method executed every day at 5 AM');
}
}
@onRequest()
Adicione o decorator @onRequest()
em um método para ser executado sempre que uma requisição HTTP for feita para o endereço do projeto no Cloud Functions seguido do nome de classe e do método, usando camelCase e ignorando o sufixo Ctrl
da nomenclatura das classes de controle.
Ex: Considerando o código abaixo, onde o nome da classe é UserCtrl
e o método é nomeado como profile()
, logo a URL externa para a requisição HTTP seria https://us-central1-project-name.cloudfunctions.net/user-profile
.
import { Request } from 'firebase-functions/lib/common/providers/https';
import { Response } from 'firebase-functions';
import { onRequest } from 'firebase-triggers';
class UserCtrl {
/*
* Este método será exportado como "user-profile" no Cloud Functions
* ex: https://us-central1-project-name.cloudfunctions.net/user-profile
*/
@onRequest()
async profile(request: Request, response: Response) {
const profile = await loadProfile(request.body.id);
response.json(profile);
}
/*
* Este método será exportado como "hello" no Cloud Functions
* ex: https://us-central1-project-name.cloudfunctions.net/hello
*/
@onRequest('hello')
async sample(request: Request, response: Response) {
response.send('Hello World!');
}
}
ste método também aceita um parâmetro, que quando informado, passa a ser o nome da função no Cloud Functions e também o sufixo da URL para requisição.
Considerando o exemplo acima, se o decorator fosse declarado com parâmetro 'api' (ex: @onRequest('api')
), neste caso a URL externa para a requisição HTTP seria https://us-central1-project-name.cloudfunctions.net/api
, ignorando a regra de nomenclatura das classes de controle.
@GET(), @POST(), @PUT(), @PATCH(), @DELETE()
Os decorators @GET()
, @POST()
, @PUT()
, @PATCH()
e @DELETE()
funcionam praticamente da mesma forma que o decorator @onRequest()
, com a diferença que cada um responde a um método HTTP exclusivo.
Segue abaixo uma simulação de REST de dados de usuário usando os decorators citados.
Neste caso a URL externa para a requisição HTTP seria https://us-central1-project-name.cloudfunctions.net/users
, ignorando a regra de nomenclatura das classes de controle.
import { Request } from 'firebase-functions/lib/common/providers/https';
import { Response } from 'firebase-functions';
import { GET, POST, PUT, PATCH, DELETE } from 'firebase-triggers';
class UserCtrl {
@GET('users')
async get(request: Request, response: Response) {
response.json([]);
}
@POST('users')
async post(request: Request, response: Response) {
response.status(201).send();
}
@PUT('users')
async put(request: Request, response: Response) {
response.status(201).send();
}
@PATCH('users')
async patch(request: Request, response: Response) {
response.status(201).send();
}
@DELETE('users')
async del(request: Request, response: Response) {
response.status(201).send();
}
}
Validação de esquema
As requisições que usam o decorator @onRequest()
podem ser validadas através de arquivos de schema que devem estar na pasta schema
com o nome exato da função que será exportada para o Cloud Functions.
Se o arquivo existir a validação será feita.
Usando os decorators @GET()
, @POST()
, @PUT()
, @PATCH()
ou @DELETE()
, é necessário acrescentar um sufixo separado por um underline no nome do arquivo de schema para cada necessidade.
Exemplos:
user-update.json
(Aplicado no uso do @onRequest() sem especificar o método HTTP)
user-update_GET.json
(Aplicado em requisições HTTP do tipo GET)
user-update_POST.json
(Aplicado em requisições HTTP do tipo POST)
user-update_PUT.json
(Aplicado em requisições HTTP do tipo PUT)
user-update_PATCH.json
(Aplicado em requisições HTTP do tipo PATCH)
user-update_DELETE.json
(Aplicado em requisições HTTP do tipo DELETE)
Também é possível no lado do cliente visualizar os arquivos de schema adicionando o sufixo /schema.json
na URL do método exportado.
Você pode usar o site jsonschema.net para gerar seus próprios schemas JSON.
@onCall()
Adicione o decorator @onCall()
em um método para que seja possível chamá-lo direto do Firebase SDK.
Chamam isso de Callable methods.
O nome do método receberá o prefixo do nome da classe, usando camelCase e ignorando o sufixo Ctrl
da nomenclatura das classes de controle.
import { onCall } from 'firebase-triggers';
import { CallableRequest } from 'firebase-functions/lib/common/providers/https';
class TodoCtrl {
@onCall()
add(event: CallableRequest) {
console.log('Add new todo', event.data);
}
}
@onStorageArchive(), @onStorageDelete(), @onStorageFinalize(), @onStorageMetadataUpdate()
Adicione o decorator onStorageArchive()
em um método para que seja executado sempre que um item for arquivado no Cloud Storage.
Adicione o decorator onStorageDelete()
em um método para que seja executado sempre que um item for removido do Cloud Storage.
Adicione o decorator onStorageFinalize()
em um método para que seja executado sempre que o upload de um item for concluído no Cloud Storage.
Adicione o decorator onStorageMetadataUpdate()
em um método para que seja executado sempre que os metadados de um item forem atualizados no Cloud Storage.
Caso o bucket não seja informado, o método será executado para todos os buckets. Veja Cloud Storage Events.
import {onStorageArchive, onStorageDelete, onStorageFinalize, onStorageMetadataUpdate } from 'firebase-triggers';
import { StorageEvent } from 'firebase-functions/v2/storage';
class TodoCtrl {
@onStorageArchive('bucket-name')
archive(event: StorageEvent) {
console.log(`File ${event.data.name} archived`);
}
@onStorageDelete('bucket-name')
del(event: StorageEvent) {
console.log(`File ${event.data.name} deleted`);
}
@onStorageFinalize('bucket-name')
uploaded(event: StorageEvent) {
console.log(`File ${event.data.name} uploaded`);
}
@onStorageMetadataUpdate('bucket-name')
updateMetadata(event: StorageEvent) {
console.log(`File ${event.data.name} updated`);
}
}
Opções de tempo de execução
O Cloud Functions para Firebase permite selecionar opções de tempo de execução, como a versão do tempo de execução do Node.js e o tempo limite por função, alocação de memória e instâncias de função mínima/máxima.
Como prática recomendada, essas opções (exceto para a versão Node.js) devem ser definidas em um objeto de configuração dentro do código da função. Este objeto RuntimeOptions é a fonte da verdade para as opções de tempo de execução da sua função e substituirá as opções definidas por meio de qualquer outro método (como por meio do console do Google Cloud ou gcloud CLI).
Para definir configurações de tempo de execução em uma Cloud Function você pode informar opcionalmente um parâmetro adicional no decorator desejado com as configurações que quer definir, incluindo a regiões de implantação da função. Veja alguns exemplos abaixo:
import 'reflect-metadata';
import { getFirebaseFunctionListToExport, onFirestoreCreate, onRequest } from 'firebase-triggers';
class MyCtrl {
@onFirestoreCreate({
document: 'todo/{id}',
database: 'my-database-name',
namespace: 'some-namespace',
retry: false,
region: 'us-east1',
omit: false,
memory: '128MiB',
timeoutSeconds: 60,
minInstances: 2,
maxInstances: 4,
concurrency: 100,
cpu: 0.5,
vpcConnectorEgressSettings: 'ALL_TRAFFIC',
ingressSettings: 'ALLOW_ALL',
invoker: 'public',
labels: { someKey: 'my-label-value'},
preserveExternalChanges: false,
})
docWrite(event) {
const data = event.data.data();
console.log(`New task added: ${data.title} at ${data.time}`);
}
@onRequest({ path: 'hello-world', region: 'us-east1' })
httpRequest(request, response) {
response.send('Hello World!');
}
}