Skip to content

Firestore → SQL Sync

Répliquez automatiquement vos collections Firestore vers une base SQL (BigQuery, etc.) via Cloud Pub/Sub.

Architecture

Firestore Triggers → Cloud Pub/Sub → Worker → Base SQL
      (onCreate/onUpdate/onDelete)           (BigQuery, etc.)

Chaque modification de document dans Firestore publie un message sur un topic Pub/Sub dédié au repo. Un worker s'abonne à ces topics, regroupe les changements en batch, et les flush vers SQL.

Démarrage rapide

typescript
import { createFirestoreSync } from "@lpdjs/firestore-repo-service/sync";
import { BigQueryAdapter } from "@lpdjs/firestore-repo-service/sync/bigquery";
import { BigQuery } from "@google-cloud/bigquery";
import { PubSub } from "@google-cloud/pubsub";
import * as firestoreTriggers from "firebase-functions/v2/firestore";
import * as pubsubHandler from "firebase-functions/v2/pubsub";
import { onRequest } from "firebase-functions/v2/https";

const sync = createFirestoreSync(repos, {
  deps: { firestoreTriggers, pubsubHandler, pubsub: new PubSub() },
  adapter: new BigQueryAdapter({
    bigquery: new BigQuery({ projectId: "my-project" }),
    datasetId: "firestore_sync",
  }),
  topicPrefix: "firestore-sync",
  autoMigrate: true,
  admin: {
    onRequest,
    httpsOptions: { invoker: "public" },
    auth: { type: "basic", username: "admin", password: "secret" },
    featuresFlag: {
      healthCheck: true,
      manualSync: true,
      viewQueue: true,
      configCheck: true,
    },
  },
  repos: {
    users: {
      exclude: ["sensitiveField"],
      columnMap: { docId: "user_id" },
      tableName: "users",
    },
    posts: { columnMap: { docId: "post_id" } },
  },
});

// Export des triggers + handlers PubSub
export const {
  users_onCreate,
  users_onUpdate,
  users_onDelete,
  sync_users,
  posts_onCreate,
  posts_onUpdate,
  posts_onDelete,
  sync_posts,
  adminsync,
} = sync.functions;

Configuration

createFirestoreSync(repos, config)

Le wrapper unifié qui crée les triggers, les workers et le serveur admin optionnel.

OptionTypeDéfautDescription
depsSyncDepsrequisDépendances Firebase Functions + PubSub
adapterSqlAdapterrequisAdaptateur SQL (ex: BigQueryAdapter)
topicPrefixstring"firestore-sync"Préfixe des topics Pub/Sub
batchSizenumber100Nombre max de lignes par flush
flushIntervalMsnumber5000Intervalle de flush en ms
autoMigratebooleanfalseCréer/migrer les tables automatiquement
adminadminsyncConfigConfiguration optionnelle de l'admin
reposTypedRepoSyncConfigsSurcharges par repo

Dépendances (deps)

Tous les modules Firebase/GCP sont injectés — la librairie ne les importe jamais directement :

typescript
deps: {
  firestoreTriggers, // firebase-functions/v2/firestore
  pubsubHandler,     // firebase-functions/v2/pubsub
  pubsub: new PubSub({ projectId: "my-project" }),
}

Config par repo (repos)

OptionTypeDescription
tableNamestringNom de la table SQL (par défaut : nom du repo)
excludestring[]Champs à exclure du SQL
columnMapRecord<string, string>Renommage champs → colonnes SQL
triggerPathstringObligatoire pour les collection groups — pattern du chemin document

Collection Groups (triggerPath)

Pour les repos avec isGroup: true, vous devez fournir un triggerPath :

typescript
repos: {
  comments: {
    triggerPath: "posts/{postId}/comments/{docId}",
    tableName: "comments",
  },
}

Cela indique à Firebase où écouter les changements de documents car les collection groups couvrent plusieurs chemins.

Adaptateur BigQuery

typescript
import { BigQueryAdapter } from "@lpdjs/firestore-repo-service/sync/bigquery";
import { BigQuery } from "@google-cloud/bigquery";

const adapter = new BigQueryAdapter({
  bigquery: new BigQuery({ projectId: "my-project" }),
  datasetId: "firestore_sync",
});

L'adaptateur gère :

  • Création de tables via DDL
  • Insertions en streaming
  • Upserts via MERGE
  • Suppression par clé primaire
  • Introspection du schéma (pour les health checks)

Authentification

  • Production (Cloud Run / Cloud Functions) : les credentials sont automatiques via ADC — passez juste projectId
  • Développement local : lancez gcloud auth application-default login

Sync Admin

L'endpoint admin optionnel fournit une interface web pour surveiller et gérer le pipeline de sync.

Fonctionnalités

FonctionnalitéFlagDescription
Health CheckhealthCheckCompare le schéma attendu (Zod) vs les colonnes SQL réelles
Force SyncmanualSyncRe-synchronise tous les documents d'une collection Firestore
View QueuesviewQueueInspecte les éléments en attente dans la queue par repo
Config CheckconfigCheckVérifie APIs GCP, topics, tables et IAM — avec commandes gcloud pour corriger

Configuration

typescript
admin: {
  auth: {
    type: "basic",
    realm: "Sync Admin",
    username: "admin",
    password: process.env.SYNC_ADMIN_PASSWORD!,
  },
  basePath: "/",
  featuresFlag: {
    healthCheck: true,
    manualSync: true,
    viewQueue: true,
    configCheck: true,
  },
}

Config Check

L'endpoint /config-check vérifie votre configuration GCP :

  • BigQuery API — est-elle activée et accessible ?
  • Tables BigQuery — chaque table de repo existe-t-elle ?
  • Topics Pub/Sub — chaque topic {topicPrefix}-{repoName} existe-t-il ?

Pour chaque problème détecté, il affiche :

  • Une commande gcloud pour corriger
  • Un lien direct vers la Console GCP

Supporte Accept: application/json pour un usage programmatique.

Déploiement de l'admin

Le handler admin est auto-wrappé quand onRequest est fourni dans la config. Passez httpsOptions pour configurer la Cloud Function (invoker, memory, region, etc.) :

typescript
admin: {
  onRequest,
  httpsOptions: { invoker: "public", memory: "512MiB" },
  auth: { type: "basic", username: "admin", password: "secret" },
  featuresFlag: { healthCheck: true, configCheck: true },
}

Le handler est alors disponible dans sync.functions.adminsync — déjà wrappé comme Cloud Function.

Si vous omettez onRequest, le handler brut est exposé et vous le wrappez manuellement :

typescript
import { onRequest } from "firebase-functions/v2/https";

export const adminsync = onRequest({ invoker: "public" }, sync.adminHandler!);

Functions générées

createFirestoreSync génère ces Cloud Functions :

FonctionTypeRôle
{repo}_onCreateTrigger FirestorePublie UPSERT à la création
{repo}_onUpdateTrigger FirestorePublie UPSERT à la modification
{repo}_onDeleteTrigger FirestorePublie DELETE à la suppression
sync_{repo}Handler PubSubTraite les messages et flush vers SQL
adminsyncHandler HTTPInterface admin (si admin configuré)

Mapping des schémas

Les schémas Zod sont automatiquement mappés vers les types SQL :

Type ZodType BigQuery
z.string()STRING
z.number()FLOAT64
z.bigint()INT64
z.boolean()BOOL
z.date()TIMESTAMP
z.object() / z.array()JSON

Adaptateur SQL personnalisé

Implémentez l'interface SqlAdapter pour d'autres bases de données :

typescript
import type {
  SqlAdapter,
  SqlDialect,
  SqlColumn,
  SqlTableDef,
} from "@lpdjs/firestore-repo-service/sync";

class MyAdapter implements SqlAdapter {
  get dialect(): SqlDialect {
    /* ... */
  }
  async tableExists(tableName: string): Promise<boolean> {
    /* ... */
  }
  async getTableColumns(tableName: string): Promise<string[]> {
    /* ... */
  }
  async createTable(table: SqlTableDef): Promise<void> {
    /* ... */
  }
  async insertRows(
    tableName: string,
    rows: Record<string, unknown>[],
  ): Promise<void> {
    /* ... */
  }
  async upsertRows(
    tableName: string,
    rows: Record<string, unknown>[],
    primaryKey: string,
  ): Promise<void> {
    /* ... */
  }
  async deleteRows(
    tableName: string,
    primaryKey: string,
    ids: string[],
  ): Promise<void> {
    /* ... */
  }
  async executeRaw(sql: string): Promise<void> {
    /* ... */
  }
}