@lpdjs/firestore-repo-service 2.3.3 → 2.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{create-servers-E79G_UgN.d.cts → create-servers-BmgAjUFD.d.cts} +2 -2
- package/dist/{create-servers-DwLmIAUH.d.ts → create-servers-Dv0V1LB8.d.ts} +2 -2
- package/dist/index.cjs +30 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +30 -30
- package/dist/index.js.map +1 -1
- package/dist/{queue-DnTHUopY.d.cts → queue-CmBKfeqG.d.cts} +1 -1
- package/dist/{queue-Dgk4HFSS.d.ts → queue-DLrIuZ7L.d.ts} +1 -1
- package/dist/servers/index.cjs +40 -40
- package/dist/servers/index.cjs.map +1 -1
- package/dist/servers/index.d.cts +3 -3
- package/dist/servers/index.d.ts +3 -3
- package/dist/servers/index.js +40 -40
- package/dist/servers/index.js.map +1 -1
- package/dist/sync/bigquery-storage.cjs +8 -0
- package/dist/sync/bigquery-storage.cjs.map +1 -0
- package/dist/sync/bigquery-storage.d.cts +113 -0
- package/dist/sync/bigquery-storage.d.ts +113 -0
- package/dist/sync/bigquery-storage.js +8 -0
- package/dist/sync/bigquery-storage.js.map +1 -0
- package/dist/sync/bigquery.cjs +7 -7
- package/dist/sync/bigquery.cjs.map +1 -1
- package/dist/sync/bigquery.d.cts +6 -1
- package/dist/sync/bigquery.d.ts +6 -1
- package/dist/sync/bigquery.js +7 -7
- package/dist/sync/bigquery.js.map +1 -1
- package/dist/sync/index.cjs +24 -24
- package/dist/sync/index.cjs.map +1 -1
- package/dist/sync/index.d.cts +4 -4
- package/dist/sync/index.d.ts +4 -4
- package/dist/sync/index.js +24 -24
- package/dist/sync/index.js.map +1 -1
- package/dist/{types-CBwcflkG.d.ts → types-Bhy7MfCj.d.ts} +19 -0
- package/dist/{types-BW592RAZ.d.cts → types-_JSH59Iy.d.cts} +19 -0
- package/package.json +19 -1
package/dist/sync/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/servers/admin/router.ts","../../src/servers/utils/link-base.ts","../../src/servers/auth/firebase-auth.ts","../../src/shared/zod-compat.ts","../../src/sync/constants.ts","../../src/sync/schema-mapper.ts","../../src/sync/serializer.ts","../../src/sync/admin.ts","../../src/sync/ddl-generator.ts","../../src/sync/migration.ts","../../src/sync/queue.ts","../../src/sync/triggers.ts","../../src/sync/worker.ts"],"names":["compilePath","path","paramNames","src","c","_match","name","extractPath","req","raw","idx","MiniRouter","_req","res","err","middleware","handler","method","pattern","matchedRoute","params","route","m","i","enrichedReq","finalHandler","index","next","mw","getLinkBase","staticBasePath","base","project","region","target","service","host","isAuthExtension","value","TYPE_MAP","getTypeName","schema","s","v4Type","v3Type","getInnerType","getShape","SYNC_VERSION_COLUMN","WRAPPER_TYPES","unwrap","current","nullable","inner","LOGICAL_MAP","zodTypeToLogical","flattenSchema","shape","dialect","prefix","parentNullable","excludeSet","columnMap","primaryKey","columns","field","fieldSchema","fullKey","typeName","isNullable","nestedShape","logical","isPK","colName","zodSchemaToColumns","options","exclude","serializeValue","geo","flattenObject","obj","result","key","flatKey","serializeDocument","doc","flat","topLevel","column","page","title","linkBase","body","sendHtml","html","status","sendJson","data","isJsonRequest","createadminsyncServer","repoMapping","adapter","queues","handleMessage","config","repoConfigs","pubsub","topicPrefix","basePath","features","repoInfos","repo","repoCfg","router","ext","mountPath","realm","expected","lb","rows","r","links","configCheckLink","info","expectedCols","actualCols","tableExists","error","e","actualSet","expectedSet","missing","extra","matched","isHealthy","statusBadge","colRows","extraRows","collRef","synced","errors","errorSamples","batchSize","query","lastDoc","snapshot","docId","serialized","msg","queue","errorBlock","consoleBase","tPrefix","checks","msgLower","isApiDisabled","isPermission","isProjectNotFound","isNotFound","exists","topicName","topic","allOk","statusIcon","grouped","renderSection","items","fixHtml","parts","overallBadge","createTableDDL","table","cols","notNull","addColumnsDDL","tableName","generateDDL","statements","repoName","documentKey","tableDef","autoMigrate","existingCols","newCols","SyncQueue","opts","interval","events","batch","upsertsById","deleteIds","evt","existing","a","upserts","DEFAULT_TOPIC_PREFIX","buildDocumentPath","collectionPath","createSyncTriggers","onDocumentCreated","onDocumentUpdated","onDocumentDeleted","triggers","topicCache","getTopic","t","publish","syncEvent","documentPath","event","snap","migratedRepos","ensureMigrated","createSyncWorker","deps","flushIntervalMs","workerOptions","getQueue","q","onFlushError","dlTopicName","dlTopic","createHandler","handlerFn","promises"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Minimal zero-dependency HTTP router for Firebase Functions.\n * Compatible with any Express-like (req, res) handler.\n *\n * Supports:\n * - Named path parameters (e.g. \"/repos/:name/:id\")\n * - GET, POST, DELETE methods\n * - Global middleware (before each route)\n * - 404 / error fallbacks\n *\n * @example\n * ```typescript\n * import { MiniRouter } from \"@lpdjs/firestore-repo-service/servers/admin\";\n *\n * // Create router\n * const router = new MiniRouter();\n *\n * // Add global middleware (executed before every route)\n * router.use(async (req, res, next) => {\n * console.log(`${req.method} ${req.url}`);\n * await next();\n * });\n *\n * // Auth middleware\n * router.use((req, res, next) => {\n * if (!req.headers?.authorization) {\n * res.status(401).send(\"Unauthorized\");\n * return;\n * }\n * next();\n * });\n *\n * // Define routes with path parameters\n * router.get(\"/users\", async (req, res) => {\n * res.json({ users: await getAllUsers() });\n * });\n *\n * router.get(\"/users/:id\", async (req, res) => {\n * const user = await getUser(req.params.id); // Access path params\n * if (!user) {\n * res.status(404).send(\"User not found\");\n * return;\n * }\n * res.json(user);\n * });\n *\n * router.post(\"/users\", async (req, res) => {\n * const user = await createUser(req.body);\n * res.status(201).json(user);\n * });\n *\n * router.delete(\"/users/:id\", async (req, res) => {\n * await deleteUser(req.params.id);\n * res.status(204).end();\n * });\n *\n * // Custom 404 handler\n * router.onNotFound((req, res) => {\n * res.status(404).json({ error: \"Route not found\", path: req.url });\n * });\n *\n * // Custom error handler\n * router.onError((err, req, res) => {\n * console.error(\"Error:\", err);\n * res.status(500).json({ error: \"Internal server error\" });\n * });\n *\n * // Use with Firebase Functions\n * export const api = onRequest(async (req, res) => {\n * await router.handle(req, res);\n * });\n * ```\n */\n\nexport type AnyReq = {\n method?: string;\n url?: string;\n /** Express originalUrl — preserved before any router stripping, contains the full path including the Firebase Functions prefix */\n originalUrl?: string;\n path?: string;\n headers?: Record<string, string | string[] | undefined>;\n body?: unknown;\n query?: Record<string, string | string[] | undefined>;\n};\n\nexport type AnyRes = {\n status: (code: number) => AnyRes;\n set: (key: string, value: string) => AnyRes;\n send: (body: string) => void;\n json: (body: unknown) => void;\n end: () => void;\n};\n\nexport type RouteParams = Record<string, string>;\n\nexport type RouteHandler = (\n req: AnyReq & { params: RouteParams },\n res: AnyRes,\n) => void | Promise<void>;\n\nexport type Middleware = (\n req: AnyReq & { params: RouteParams },\n res: AnyRes,\n next: () => void | Promise<void>,\n) => void | Promise<void>;\n\n// ---------------------------------------------------------------------------\n// Route matching\n// ---------------------------------------------------------------------------\n\ninterface CompiledRoute {\n method: string;\n pattern: RegExp;\n paramNames: string[];\n handler: RouteHandler;\n}\n\nfunction compilePath(path: string): { pattern: RegExp; paramNames: string[] } {\n const paramNames: string[] = [];\n const src = path\n .replace(/[.*+?^${}()|[\\]\\\\]/g, (c) => (c === \":\" ? c : `\\\\${c}`))\n .replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_match, name: string) => {\n paramNames.push(name);\n return \"([^/]+)\";\n });\n\n return { pattern: new RegExp(`^${src}$`), paramNames };\n}\n\nfunction extractPath(req: AnyReq): string {\n const raw = req.path ?? req.url ?? \"/\";\n const idx = raw.indexOf(\"?\");\n return idx === -1 ? raw : raw.slice(0, idx);\n}\n\n// ---------------------------------------------------------------------------\n// Router class\n// ---------------------------------------------------------------------------\n\nexport class MiniRouter {\n private routes: CompiledRoute[] = [];\n private middlewares: Middleware[] = [];\n private notFoundHandler: RouteHandler = (_req, res) => {\n res.status(404).send(\"Not Found\");\n };\n private errorHandler: (err: unknown, req: AnyReq, res: AnyRes) => void = (\n err,\n _req,\n res,\n ) => {\n console.error(\"[MiniRouter]\", err);\n res.status(500).send(\"Internal Server Error\");\n };\n\n // ── Route registration ────────────────────────────────────────────────────\n\n use(middleware: Middleware): this {\n this.middlewares.push(middleware);\n return this;\n }\n\n get(path: string, handler: RouteHandler): this {\n return this.addRoute(\"GET\", path, handler);\n }\n\n post(path: string, handler: RouteHandler): this {\n return this.addRoute(\"POST\", path, handler);\n }\n\n put(path: string, handler: RouteHandler): this {\n return this.addRoute(\"PUT\", path, handler);\n }\n\n patch(path: string, handler: RouteHandler): this {\n return this.addRoute(\"PATCH\", path, handler);\n }\n\n delete(path: string, handler: RouteHandler): this {\n return this.addRoute(\"DELETE\", path, handler);\n }\n\n onNotFound(handler: RouteHandler): this {\n this.notFoundHandler = handler;\n return this;\n }\n\n onError(handler: (err: unknown, req: AnyReq, res: AnyRes) => void): this {\n this.errorHandler = handler;\n return this;\n }\n\n private addRoute(method: string, path: string, handler: RouteHandler): this {\n const { pattern, paramNames } = compilePath(path);\n this.routes.push({\n method: method.toUpperCase(),\n pattern,\n paramNames,\n handler,\n });\n return this;\n }\n\n // ── Dispatch ──────────────────────────────────────────────────────────────\n\n async handle(req: AnyReq, res: AnyRes): Promise<void> {\n const method = (req.method ?? \"GET\").toUpperCase();\n const path = extractPath(req);\n\n // Find matching route\n let matchedRoute: CompiledRoute | null = null;\n let params: RouteParams = {};\n\n for (const route of this.routes) {\n if (route.method !== method) continue;\n const m = path.match(route.pattern);\n if (m) {\n matchedRoute = route;\n params = {};\n route.paramNames.forEach((name, i) => {\n params[name] = decodeURIComponent(m[i + 1] ?? \"\");\n });\n break;\n }\n }\n\n const enrichedReq = Object.assign(req, { params });\n\n // Run middleware chain → then handler\n const handler = matchedRoute ? matchedRoute.handler : this.notFoundHandler;\n\n try {\n await this.runMiddlewareChain(enrichedReq, res, handler);\n } catch (err) {\n this.errorHandler(err, req, res);\n }\n }\n\n private async runMiddlewareChain(\n req: AnyReq & { params: RouteParams },\n res: AnyRes,\n finalHandler: RouteHandler,\n ): Promise<void> {\n let index = 0;\n\n const next = async (): Promise<void> => {\n if (index < this.middlewares.length) {\n const mw = this.middlewares[index++]!;\n await mw(req, res, next);\n } else {\n await finalHandler(req, res);\n }\n };\n\n await next();\n }\n}\n","/**\n * Compute the URL prefix used to build absolute paths from inside a\n * Firebase HTTPS function. Handles three deployment shapes uniformly:\n *\n * 1. **Firebase emulator** (`FUNCTIONS_EMULATOR=true`) — exposes functions at\n * `http://localhost:5001/{project}/{region}/{functionTarget}/...`. The\n * handler receives `req.url` *without* this prefix, so we rebuild it from\n * `GCLOUD_PROJECT`, `FUNCTION_REGION`, `FUNCTION_TARGET`.\n *\n * 2. **Cloud Functions v2 default URL** (`*.cloudfunctions.net/{name}`) —\n * Cloud Run terminates routing at the service name, so links must include\n * the `K_SERVICE` prefix. Detected via the `host` header containing\n * `cloudfunctions.net`.\n *\n * 3. **Custom domain / Hosting rewrite** — the proxy strips the prefix\n * before reaching the handler, so links are relative to the configured\n * `staticBasePath`.\n *\n * @param req The incoming request (needs `headers.host` / `hostname`).\n * @param staticBasePath The user-configured base path (e.g. `\"/api\"`).\n * @returns A path prefix (no trailing slash) suitable for prepending to\n * `req.url` to build a same-function absolute URL.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function getLinkBase(req: any, staticBasePath: string): string {\n const base = staticBasePath === \"/\" ? \"\" : staticBasePath.replace(/\\/$/, \"\");\n\n if (process.env[\"FUNCTIONS_EMULATOR\"] === \"true\") {\n const project =\n process.env[\"GCLOUD_PROJECT\"] ??\n process.env[\"GOOGLE_CLOUD_PROJECT\"] ??\n \"demo-project\";\n const region = process.env[\"FUNCTION_REGION\"] ?? \"us-central1\";\n // FUNCTION_TARGET uses dots (e.g. \"sync.functions.adminsync\") but the\n // emulator URL uses hyphens (\"sync-functions-adminsync\").\n const target = (process.env[\"FUNCTION_TARGET\"] ?? \"\").replace(/\\./g, \"-\");\n return `/${project}/${region}/${target}${base}`;\n }\n\n // Cloud Functions v2: K_SERVICE = function name = URL path prefix.\n // Only add it when accessed via cloudfunctions.net (not custom domains).\n // Cloud Run (Gen 2) lowercases service names, but K_SERVICE may still\n // carry the original mixed-case export name — normalise to lowercase\n // so that generated links match the canonical URL.\n const service = process.env[\"K_SERVICE\"];\n const host: string =\n req?.hostname ?? req?.headers?.[\"host\"] ?? \"\";\n if (service && typeof host === \"string\" && host.includes(\"cloudfunctions.net\")) {\n return `/${service.toLowerCase()}${base}`;\n }\n\n return base;\n}\n","/**\n * Firebase Auth helper for the admin & CRUD servers.\n *\n * Returns an {@link AuthExtension} ready to plug into `servers.admin()` or\n * `servers.crud()`. Supports two transport modes:\n *\n * - **`cookie`** — session cookie pattern (default for admin UIs). Mounts\n * `/__login`, `/__session`, `/__logout` routes; the page lets the user sign\n * in client-side with the Firebase JS SDK and exchanges the resulting ID\n * token for an HttpOnly session cookie via Firebase Admin SDK.\n * - **`bearer`** — verifies `Authorization: Bearer <idToken>` on every request\n * (default for REST APIs). No login routes mounted.\n * - **`both`** — accept either cookie or bearer.\n *\n * The helper is **agnostic** about authorization: pass an `allow` callback\n * returning whatever role/context shape you need. The result is exposed as\n * `req.user.context` to downstream middlewares and route handlers.\n *\n * @example Admin (cookie + role trio)\n * ```ts\n * import { firebaseAuth } from \"@lpdjs/firestore-repo-service/servers/auth\";\n * import { getAuth } from \"firebase-admin/auth\";\n *\n * servers.admin({\n * auth: firebaseAuth({\n * getAuth,\n * mode: \"cookie\",\n * apiKey: process.env.FIREBASE_WEB_API_KEY!,\n * authDomain: process.env.FIREBASE_AUTH_DOMAIN!,\n * allow: ({ email, claims }) => {\n * if (claims.superAdmin) return { role: \"superAdmin\" };\n * if (email?.endsWith(\"@solarpush.io\")) return { role: \"admin\" };\n * if (email) return { role: \"viewer\" };\n * return null;\n * },\n * }),\n * repos: { ... },\n * });\n * ```\n *\n * @example CRUD (bearer + business rules per repo)\n * ```ts\n * servers.crud({\n * auth: firebaseAuth({ getAuth, mode: \"bearer\", allow: (u) => u }),\n * repos: {\n * comments: {\n * repo: repos.comments,\n * rules: {\n * list: () => true,\n * get: ({ user, doc }) => doc.public || doc.authorId === user.uid,\n * create: ({ user }) => !!user.uid,\n * update: ({ user, doc }) => user.uid === doc.authorId,\n * delete: ({ user, doc }) => user.claims.role === \"moderator\",\n * },\n * },\n * },\n * });\n * ```\n */\n\nimport type { AnyReq, Middleware, RouteHandler } from \"../admin/router\";\nimport { getLinkBase } from \"../utils/link-base\";\nimport { renderLoginPage } from \"./login-page\";\nimport {\n createLogoutHandler,\n createSessionHandler,\n parseCookies,\n SESSION_COOKIE_DEFAULT,\n} from \"./session\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Minimal Firebase Admin Auth surface needed by this helper.\n * Avoids a hard import of `firebase-admin/auth` so the package stays\n * decoupled from a specific firebase-admin version.\n */\nexport interface FirebaseAdminAuthLike {\n verifyIdToken(\n idToken: string,\n checkRevoked?: boolean,\n ): Promise<DecodedIdTokenLike>;\n verifySessionCookie(\n sessionCookie: string,\n checkRevoked?: boolean,\n ): Promise<DecodedIdTokenLike>;\n createSessionCookie(\n idToken: string,\n sessionCookieOptions: { expiresIn: number },\n ): Promise<string>;\n revokeRefreshTokens(uid: string): Promise<void>;\n}\n\nexport interface DecodedIdTokenLike {\n uid: string;\n email?: string;\n email_verified?: boolean;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [claim: string]: any;\n}\n\n/** Identity attached to every authenticated request as `req.user`. */\nexport interface AuthUser<TContext = unknown> {\n uid: string;\n email: string | null;\n emailVerified: boolean;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n claims: Record<string, any>;\n /** Result of the user-supplied `allow()` callback. */\n context: TContext;\n}\n\n/** A route descriptor mounted by `firebaseAuth` before the protected chain. */\nexport interface AuthRoute {\n method: \"GET\" | \"POST\";\n path: string;\n handler: RouteHandler;\n}\n\n/**\n * Returned by {@link firebaseAuth}. Servers detect this shape (vs.\n * `BasicAuthConfig` / raw `Middleware`) and mount the routes before pushing\n * the middleware onto the chain.\n */\nexport interface AuthExtension {\n readonly __authExtension: true;\n middleware: Middleware;\n /** Auxiliary routes (login page, session, logout). Empty in pure bearer mode. */\n routes: AuthRoute[];\n /** Path used to redirect unauthenticated browser requests. */\n loginPath: string;\n}\n\nexport type FirebaseAuthMode = \"cookie\" | \"bearer\" | \"both\";\n\n/** Provider configuration for the bundled login page. */\nexport interface FirebaseAuthLoginPageConfig {\n /** Page title. Default: \"Admin sign-in\". */\n title?: string;\n /**\n * Providers shown on the login page.\n * Default: `[\"password\", \"google\"]`.\n */\n providers?: (\"password\" | \"google\")[];\n}\n\nexport interface FirebaseAuthConfig<TContext = unknown> {\n /** Lazy getter for the Firebase Admin Auth instance. */\n getAuth: () => FirebaseAdminAuthLike;\n\n /** Transport mode. Default: `\"cookie\"`. */\n mode?: FirebaseAuthMode;\n\n /**\n * Authorization callback. Receives the verified token claims and returns:\n * - a context object → request is allowed, exposed as `req.user.context`,\n * - `null` → request is rejected (401 / redirect to login).\n *\n * If omitted, the default policy allows any authenticated user with\n * `context = null`.\n */\n allow?: (\n user: Omit<AuthUser, \"context\">,\n ) => TContext | null | Promise<TContext | null>;\n\n // ── Cookie mode options ────────────────────────────────────────────────\n /**\n * Whether to mount the bundled `/__login`, `/__session`, `/__logout`\n * routes. Default: `true` for `cookie`/`both`, `false` for `bearer`.\n */\n loginPage?: boolean | FirebaseAuthLoginPageConfig;\n\n /**\n * Firebase Web API key required by the JS SDK on the login page.\n * Mandatory when `loginPage` is enabled. Find it in your Firebase Console\n * under Project Settings → General → Web app config.\n */\n apiKey?: string;\n\n /**\n * Firebase Auth domain (e.g. `my-project.firebaseapp.com`).\n * Mandatory when `loginPage` is enabled.\n */\n authDomain?: string;\n\n /** Cookie name. Default: `__admin_session`. */\n cookieName?: string;\n\n /** Session cookie TTL in days. Default: `5` (Firebase max is 14). */\n sessionTtlDays?: number;\n\n /**\n * Cookie `Secure` flag. Default: `true`. Set to `false` only for local\n * development over HTTP.\n */\n secureCookie?: boolean;\n\n /** Cookie `SameSite`. Default: `\"Lax\"`. */\n sameSite?: \"Strict\" | \"Lax\" | \"None\";\n\n /**\n * Behaviour when authentication fails or `allow()` returns `null`.\n * - `\"redirect\"` (default in cookie mode) → 302 to the login page,\n * - `\"401\"` (default in bearer mode) → JSON 401 response.\n */\n onUnauthenticated?: \"redirect\" | \"401\";\n\n /**\n * Routes that should bypass the auth middleware (matched against the path\n * after the basePath stripping). The auxiliary login routes are always\n * public regardless of this option.\n */\n publicPaths?: (string | RegExp)[];\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction defaultLoginPage(mode: FirebaseAuthMode): boolean {\n return mode === \"cookie\" || mode === \"both\";\n}\n\nfunction defaultUnauth(mode: FirebaseAuthMode): \"redirect\" | \"401\" {\n return mode === \"bearer\" ? \"401\" : \"redirect\";\n}\n\nfunction pathOf(req: AnyReq): string {\n const raw = req.path ?? req.url ?? \"/\";\n const idx = raw.indexOf(\"?\");\n return idx === -1 ? raw : raw.slice(0, idx);\n}\n\nfunction queryAction(req: AnyReq): string | null {\n const q = (req as { query?: Record<string, unknown> }).query;\n if (q && typeof q.__action === \"string\") return q.__action;\n // Fallback: parse from URL when query parsing isn't done by the runtime.\n const url = req.url ?? \"\";\n const idx = url.indexOf(\"?\");\n if (idx === -1) return null;\n const params = new URLSearchParams(url.slice(idx + 1));\n return params.get(\"__action\");\n}\n\nfunction methodOf(req: AnyReq): string {\n return String(req.method ?? \"GET\").toUpperCase();\n}\n\nfunction isPublic(\n path: string,\n patterns: (string | RegExp)[] | undefined,\n): boolean {\n if (!patterns || patterns.length === 0) return false;\n for (const p of patterns) {\n if (typeof p === \"string\") {\n if (path === p || path.startsWith(p + \"/\")) return true;\n } else if (p.test(path)) {\n return true;\n }\n }\n return false;\n}\n\nfunction wantsHtml(req: AnyReq): boolean {\n const accept = String(req.headers?.accept ?? \"\");\n // Browsers send \"text/html\" early in their Accept header.\n // Fall back: treat GET requests with no Accept as HTML so platforms\n // that strip the header (or send \"*/*\") still get the login page.\n if (accept.includes(\"text/html\")) return true;\n if (!accept || accept === \"*/*\") return methodOf(req) === \"GET\";\n return false;\n}\n\nfunction extractBearer(req: AnyReq): string | null {\n const raw = req.headers?.authorization;\n const header = Array.isArray(raw) ? raw[0] : raw;\n if (!header) return null;\n const m = /^Bearer\\s+(.+)$/i.exec(header);\n return m ? m[1]!.trim() : null;\n}\n\nfunction rejectUnauthenticated(\n req: AnyReq,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n res: any,\n policy: \"redirect\" | \"401\",\n loginPath: string,\n): void {\n if (policy === \"redirect\" && wantsHtml(req)) {\n const target = encodeURIComponent(req.url ?? \"/\");\n res\n .status(302)\n .set(\"Location\", `${loginPath}?next=${target}`)\n .set(\"Cache-Control\", \"no-store\")\n .end();\n return;\n }\n res\n .status(401)\n .set(\"Content-Type\", \"application/json; charset=utf-8\")\n .send(JSON.stringify({ success: false, error: \"Unauthorized\" }));\n}\n\n// ---------------------------------------------------------------------------\n// Public factory\n// ---------------------------------------------------------------------------\n\n/**\n * Build a Firebase Auth extension for use with `servers.admin()` or\n * `servers.crud()`. See module-level docs for the full design and examples.\n */\nexport function firebaseAuth<TContext = unknown>(\n config: FirebaseAuthConfig<TContext>,\n): AuthExtension {\n const mode: FirebaseAuthMode = config.mode ?? \"cookie\";\n const cookieName = config.cookieName ?? SESSION_COOKIE_DEFAULT;\n const ttlDays = config.sessionTtlDays ?? 5;\n const secure = config.secureCookie ?? true;\n const sameSite = config.sameSite ?? \"Lax\";\n const onUnauth = config.onUnauthenticated ?? defaultUnauth(mode);\n const loginEnabled =\n config.loginPage === undefined\n ? defaultLoginPage(mode)\n : config.loginPage !== false;\n\n const loginPath = \"/__login\";\n const sessionPath = \"/__session\";\n const logoutPath = \"/__logout\";\n\n // ── Auxiliary handlers (kept in `routes` for hosting deployments\n // where users can mount them at known paths, AND invoked in-band by the\n // middleware on `?__action=session|logout` so vanilla Cloud Functions\n // — where there is no separate URL prefix per route — work too). ──────\n const sessionHandler = createSessionHandler({\n getAuth: config.getAuth,\n cookieName,\n ttlDays,\n secure,\n sameSite,\n });\n const logoutHandler = createLogoutHandler({\n getAuth: config.getAuth,\n cookieName,\n secure,\n sameSite,\n });\n\n function renderInlineLogin(\n req: AnyReq,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n res: any,\n error: string | null = null,\n ): void {\n // Validate lazily (at request time) so module loading during Firebase CLI\n // analysis doesn't throw before env vars are injected.\n if (!config.apiKey || !config.authDomain) {\n throw new Error(\n \"[firebaseAuth] `apiKey` and `authDomain` are required when `loginPage` is enabled. \" +\n \"Find both in the Firebase Console under Project Settings → General → Web app config.\",\n );\n }\n const pageCfg: FirebaseAuthLoginPageConfig =\n typeof config.loginPage === \"object\" ? config.loginPage : {};\n // Build a same-function absolute URL: the function's external prefix\n // (Cloud Functions name, emulator project/region/target, or \"\" for\n // custom domains) + the in-router request path. The browser otherwise\n // resolves form actions relative to the public URL, which doesn't\n // include the function name on Cloud Functions.\n const prefix = getLinkBase(req, \"/\");\n const inner = req.url ?? \"/\";\n const fullPath = `${prefix}${inner.startsWith(\"/\") ? inner : `/${inner}`}`;\n const sep = fullPath.includes(\"?\") ? \"&\" : \"?\";\n const sessionAction = `${fullPath}${sep}__action=session`;\n const html = renderLoginPage({\n title: pageCfg.title ?? \"Admin sign-in\",\n providers: pageCfg.providers ?? [\"password\", \"google\"],\n apiKey: config.apiKey!,\n authDomain: config.authDomain!,\n sessionPath: sessionAction,\n next: fullPath,\n error,\n });\n res\n .status(200)\n .set(\"Content-Type\", \"text/html; charset=utf-8\")\n .set(\"Cache-Control\", \"no-store\")\n .send(html);\n }\n\n // ── Auxiliary routes ─────────────────────────────────────────────────────\n const routes: AuthRoute[] = [];\n if (loginEnabled) {\n routes.push({\n method: \"GET\",\n path: loginPath,\n handler: (req, res) => {\n const error = (req.query?.error as string | undefined) ?? null;\n renderInlineLogin(req, res, error);\n },\n });\n routes.push({\n method: \"POST\",\n path: sessionPath,\n handler: sessionHandler,\n });\n routes.push({\n method: \"POST\",\n path: logoutPath,\n handler: logoutHandler,\n });\n }\n\n const publicPaths: (string | RegExp)[] = [\n ...(config.publicPaths ?? []),\n loginPath,\n sessionPath,\n logoutPath,\n ];\n\n // ── Middleware ───────────────────────────────────────────────────────────\n const middleware: Middleware = async (req, res, next) => {\n const path = pathOf(req);\n\n // 1. In-band action endpoints (work on ANY URL, no separate route needed).\n // Used by the inline login page since the helper can't know the function's\n // public URL prefix on Cloud Functions.\n if (loginEnabled && methodOf(req) === \"POST\") {\n const action = queryAction(req);\n if (action === \"session\") {\n await sessionHandler(req, res);\n return;\n }\n if (action === \"logout\") {\n await logoutHandler(req, res);\n return;\n }\n }\n\n // 2. Public paths (mounted login routes, user-supplied allowlist).\n if (isPublic(path, publicPaths)) {\n await next();\n return;\n }\n\n let decoded: DecodedIdTokenLike | null = null;\n try {\n const auth = config.getAuth();\n\n // Try bearer first when allowed (cheaper, no cookie parsing).\n if (mode === \"bearer\" || mode === \"both\") {\n const token = extractBearer(req);\n if (token) {\n decoded = await auth.verifyIdToken(token, true);\n }\n }\n\n // Fall back to cookie when allowed.\n if (!decoded && (mode === \"cookie\" || mode === \"both\")) {\n const cookieHeader = req.headers?.cookie;\n const raw = Array.isArray(cookieHeader)\n ? cookieHeader.join(\"; \")\n : cookieHeader;\n const cookies = parseCookies(typeof raw === \"string\" ? raw : \"\");\n const session = cookies[cookieName];\n if (session) {\n decoded = await auth.verifySessionCookie(session, true);\n }\n }\n } catch {\n decoded = null;\n }\n\n if (!decoded) {\n rejectUnauthenticated(req, res);\n return;\n }\n\n const baseUser: Omit<AuthUser, \"context\"> = {\n uid: decoded.uid,\n email: typeof decoded.email === \"string\" ? decoded.email : null,\n emailVerified: !!decoded.email_verified,\n claims: decoded as Record<string, unknown>,\n };\n\n let context: TContext | null;\n try {\n context = config.allow\n ? await config.allow(baseUser)\n : (null as TContext | null);\n } catch {\n context = null;\n }\n\n if (config.allow && context === null) {\n rejectUnauthenticated(req, res);\n return;\n }\n\n (req as AnyReq & { user?: AuthUser<TContext> }).user = {\n ...baseUser,\n context: context as TContext,\n };\n\n await next();\n };\n\n /**\n * Reject according to the configured policy:\n * - cookie/both + GET HTML browser request → render the login page inline\n * on the SAME URL (works on Cloud Functions where there's no separate\n * `/__login` route reachable from the public URL).\n * - bearer mode or non-HTML clients → JSON 401.\n */\n function rejectUnauthenticated(\n req: AnyReq,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n res: any,\n ): void {\n if (\n onUnauth === \"redirect\" &&\n loginEnabled &&\n methodOf(req) === \"GET\" &&\n wantsHtml(req)\n ) {\n renderInlineLogin(req, res, null);\n return;\n }\n res\n .status(401)\n .set(\"Content-Type\", \"application/json; charset=utf-8\")\n .send(JSON.stringify({ success: false, error: \"Unauthorized\" }));\n }\n\n return {\n __authExtension: true,\n middleware,\n routes,\n loginPath,\n };\n}\n\n/**\n * Type guard: detect an {@link AuthExtension} (vs. legacy\n * `BasicAuthConfig` / `Middleware`).\n */\nexport function isAuthExtension(value: unknown): value is AuthExtension {\n return (\n !!value &&\n typeof value === \"object\" &&\n (value as { __authExtension?: unknown }).__authExtension === true\n );\n}\n\n/**\n * Helper for explicitly opening a CRUD operation when the server has\n * `auth` defined (bypasses the default-deny policy).\n *\n * @example\n * ```ts\n * rules: { list: allowAll, get: allowAll }\n * ```\n */\nexport const allowAll = (): true => true;\n","/**\n * Zod introspection utilities — compatible with Zod 4.\n *\n * Centralizes all internal `_zod.def` accesses so the rest of the codebase\n * never touches Zod internals directly. If a future Zod version changes\n * the internal layout, only this file needs updating.\n */\n\nimport type { z } from \"zod\";\n\n// ---------------------------------------------------------------------------\n// Type-name resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Canonical Zod type names used in the codebase.\n * These match the **Zod 3** naming convention (e.g. \"ZodString\") that the\n * form-gen / admin handlers already rely on, so we don't need to rewrite\n * every `case \"ZodString\":` branch.\n */\ntype ZodTypeName =\n | \"ZodString\"\n | \"ZodNumber\"\n | \"ZodBigInt\"\n | \"ZodBoolean\"\n | \"ZodDate\"\n | \"ZodEnum\"\n | \"ZodNativeEnum\"\n | \"ZodLiteral\"\n | \"ZodObject\"\n | \"ZodArray\"\n | \"ZodOptional\"\n | \"ZodNullable\"\n | \"ZodDefault\"\n | \"ZodCoerce\"\n | \"ZodUnion\"\n | \"ZodUndefined\"\n | \"ZodUnknown\"\n | \"ZodAny\"\n | \"ZodRecord\"\n | string;\n\n/**\n * Map from Zod 4 `_zod.def.type` (lowercase) → Zod 3 `_def.typeName` format.\n * Any key not in this map is capitalised as `Zod${Capitalised}`.\n */\nconst TYPE_MAP: Record<string, ZodTypeName> = {\n string: \"ZodString\",\n number: \"ZodNumber\",\n bigint: \"ZodBigInt\",\n boolean: \"ZodBoolean\",\n date: \"ZodDate\",\n enum: \"ZodEnum\",\n nativeEnum: \"ZodNativeEnum\",\n literal: \"ZodLiteral\",\n object: \"ZodObject\",\n array: \"ZodArray\",\n optional: \"ZodOptional\",\n nullable: \"ZodNullable\",\n default: \"ZodDefault\",\n coerce: \"ZodCoerce\",\n union: \"ZodUnion\",\n undefined: \"ZodUndefined\",\n unknown: \"ZodUnknown\",\n any: \"ZodAny\",\n record: \"ZodRecord\",\n};\n\n/**\n * Get the canonical type name of a Zod schema.\n *\n * Works with both Zod 3 (`_def.typeName`) and Zod 4 (`_zod.def.type`).\n * Returns names in Zod-3 style (\"ZodString\", \"ZodObject\" …) for\n * backward-compatible switch/case usage.\n */\nexport function getTypeName(schema: z.ZodType): ZodTypeName {\n const s = schema as any;\n\n // Zod 4 path\n const v4Type: string | undefined = s._zod?.def?.type;\n if (v4Type)\n return (\n TYPE_MAP[v4Type] ??\n `Zod${v4Type.charAt(0).toUpperCase()}${v4Type.slice(1)}`\n );\n\n // Zod 3 fallback\n const v3Type: string | undefined = s._def?.typeName;\n if (v3Type) return v3Type;\n\n return \"\";\n}\n\n// ---------------------------------------------------------------------------\n// Unwrapping wrappers (Optional / Nullable / Default)\n// ---------------------------------------------------------------------------\n\n/**\n * Get the inner schema from a wrapper type (Optional / Nullable / Default).\n */\nexport function getInnerType(schema: z.ZodType): z.ZodType | undefined {\n const s = schema as any;\n\n // Zod 4: _zod.def.innerType\n if (s._zod?.def?.innerType) return s._zod.def.innerType;\n\n // Zod 3 compat: _def.innerType\n if (s._def?.innerType) return s._def.innerType;\n\n return undefined;\n}\n\n/**\n * Get the **element schema** from a `ZodArray`.\n * Returns `undefined` for non-array schemas.\n */\nexport function getArrayElementType(\n schema: z.ZodType,\n): z.ZodType | undefined {\n const s = schema as any;\n\n // Zod 4: _zod.def.element\n if (s._zod?.def?.element) return s._zod.def.element;\n\n // Zod 3: _def.type (ZodArray stores element schema in _def.type)\n if (s._def?.type) return s._def.type;\n\n return undefined;\n}\n\n/**\n * Returns the list of **required** (non-optional) top-level keys of a ZodObject.\n * A key is required when its schema is NOT wrapped in ZodOptional.\n * ZodNullable / ZodDefault fields are still considered required — they must be present.\n */\nexport function getRequiredSchemaKeys(schema: z.ZodObject<any>): string[] {\n const required: string[] = [];\n for (const [key, fieldSchema] of Object.entries(schema.shape)) {\n const typeName = getTypeName(fieldSchema as z.ZodType);\n if (typeName !== \"ZodOptional\") {\n required.push(key);\n }\n }\n return required;\n}\n\n/**\n * Get the default value for a ZodDefault schema.\n * In Zod 3 this was `_def.defaultValue()` (function). In Zod 4 it's a direct value.\n */\nexport function getDefaultValue(schema: z.ZodType): unknown {\n const s = schema as any;\n\n // Zod 4\n if (s._zod?.def?.defaultValue !== undefined) return s._zod.def.defaultValue;\n\n // Zod 3 compat\n const v = s._def?.defaultValue;\n if (typeof v === \"function\") return v();\n return v;\n}\n\n// ---------------------------------------------------------------------------\n// Object shape\n// ---------------------------------------------------------------------------\n\n/**\n * Get the shape of a ZodObject schema (Record<string, ZodType>).\n * Handles both Zod 3 (`_def.shape()` function) and Zod 4 (`.shape` property).\n */\nexport function getShape(schema: z.ZodType): Record<string, z.ZodType> {\n const s = schema as any;\n\n // Direct `.shape` property (Zod 3 & 4 for ZodObject instances)\n if (s.shape && typeof s.shape === \"object\") return s.shape;\n\n // Zod 4: _zod.def.shape\n if (s._zod?.def?.shape && typeof s._zod.def.shape === \"object\")\n return s._zod.def.shape;\n\n // Zod 3 fallback: _def.shape() or _def.shape\n if (s._def?.shape) {\n return typeof s._def.shape === \"function\" ? s._def.shape() : s._def.shape;\n }\n\n return {};\n}\n\n// ---------------------------------------------------------------------------\n// Enum / Literal values\n// ---------------------------------------------------------------------------\n\n/**\n * Get the values of a ZodEnum schema as a string[].\n */\nexport function getEnumValues(schema: z.ZodType): string[] {\n const s = schema as any;\n\n // Zod 4: `.options` or `_zod.def.entries`\n if (Array.isArray(s.options)) return s.options;\n if (s._zod?.def?.entries)\n return Object.values(s._zod.def.entries) as string[];\n\n // Zod 3: `_def.values`\n if (Array.isArray(s._def?.values)) return s._def.values;\n\n return [];\n}\n\n/**\n * Get the value object of a ZodNativeEnum schema.\n */\nexport function getNativeEnumValues(\n schema: z.ZodType,\n): Record<string, unknown> {\n const s = schema as any;\n\n // Zod 4: `_zod.def.entries` or `.enum`\n if (s._zod?.def?.entries) return s._zod.def.entries;\n if (s.enum && typeof s.enum === \"object\") return s.enum;\n\n // Zod 3: `_def.values`\n if (s._def?.values && typeof s._def.values === \"object\") return s._def.values;\n\n return {};\n}\n\n/**\n * Get the value of a ZodLiteral schema.\n */\nexport function getLiteralValue(schema: z.ZodType): unknown {\n const s = schema as any;\n\n // Zod 4: `_zod.def.values[0]`\n if (Array.isArray(s._zod?.def?.values)) return s._zod.def.values[0];\n\n // Zod 3: `_def.value`\n return s._def?.value;\n}\n\n// ---------------------------------------------------------------------------\n// String checks\n// ---------------------------------------------------------------------------\n\n/**\n * Get relevant \"check kinds\" from a ZodString schema.\n * Returns an array of kind strings: \"email\", \"url\", etc.\n */\nexport function getStringChecks(schema: z.ZodType): string[] {\n const s = schema as any;\n const kinds: string[] = [];\n\n // Zod 4: checks with `.format` field\n const v4Checks: any[] | undefined = s._zod?.def?.checks;\n if (Array.isArray(v4Checks)) {\n for (const c of v4Checks) {\n if (c.format) kinds.push(c.format);\n }\n if (kinds.length > 0) return kinds;\n }\n\n // Zod 3: _def.checks with `.kind` field\n const v3Checks: any[] | undefined = s._def?.checks;\n if (Array.isArray(v3Checks)) {\n for (const c of v3Checks) {\n if (c.kind) kinds.push(c.kind);\n }\n }\n\n return kinds;\n}\n","/**\n * Internal constants shared between the worker, queue, schema mapper and\n * SQL adapters.\n */\n\n/**\n * Name of the SQL column that stores the publish-time `version` of each\n * sync event. Used by the worker to discard out-of-order PubSub deliveries\n * (the MERGE only updates rows when the incoming version is strictly\n * greater than the stored one).\n *\n * Two underscores prefix avoids collisions with user-defined fields.\n */\nexport const SYNC_VERSION_COLUMN = \"__sync_version\";\n","import { z } from \"zod\";\nimport {\n getTypeName,\n getInnerType,\n getShape,\n} from \"../shared/zod-compat\";\nimport { SYNC_VERSION_COLUMN } from \"./constants\";\nimport type { SqlColumn, SqlDialect, LogicalType } from \"./types\";\n\nconst WRAPPER_TYPES = new Set([\"ZodOptional\", \"ZodNullable\", \"ZodDefault\"]);\n\nfunction unwrap(schema: z.ZodType): { inner: z.ZodType; nullable: boolean } {\n let current = schema;\n let nullable = false;\n\n for (;;) {\n const name = getTypeName(current);\n if (!WRAPPER_TYPES.has(name)) break;\n if (name === \"ZodOptional\" || name === \"ZodNullable\") nullable = true;\n const inner = getInnerType(current);\n if (!inner) break;\n current = inner;\n }\n\n return { inner: current, nullable };\n}\n\nconst LOGICAL_MAP: Record<string, LogicalType> = {\n ZodString: \"string\",\n ZodNumber: \"number\",\n ZodBigInt: \"bigint\",\n ZodBoolean: \"boolean\",\n ZodDate: \"timestamp\",\n ZodEnum: \"string\",\n ZodNativeEnum: \"string\",\n ZodLiteral: \"string\",\n};\n\nexport function zodTypeToLogical(schema: z.ZodType): LogicalType {\n const { inner } = unwrap(schema);\n return LOGICAL_MAP[getTypeName(inner)] ?? \"json\";\n}\n\nexport interface ZodSchemaToColumnsOptions {\n primaryKey?: string;\n exclude?: string[];\n columnMap?: Record<string, string>;\n}\n\n/**\n * Recursively flatten a Zod object schema into flat SQL columns.\n * Nested objects produce `parent_child` column names.\n * Arrays and non-object complex types become JSON columns.\n */\nfunction flattenSchema(\n shape: Record<string, z.ZodType>,\n dialect: SqlDialect,\n prefix: string,\n parentNullable: boolean,\n excludeSet: Set<string>,\n columnMap: Record<string, string>,\n primaryKey: string | undefined,\n columns: SqlColumn[],\n): void {\n for (const [field, fieldSchema] of Object.entries(shape)) {\n const fullKey = prefix ? `${prefix}__${field}` : field;\n\n // Exclude check on the original (dotless) path and the flattened key\n if (excludeSet.has(field) || excludeSet.has(fullKey)) continue;\n\n const { inner, nullable } = unwrap(fieldSchema);\n const typeName = getTypeName(inner);\n const isNullable = parentNullable || nullable;\n\n // Nested object → recurse to flatten\n if (typeName === \"ZodObject\") {\n const nestedShape = getShape(inner as z.ZodObject<any>);\n flattenSchema(\n nestedShape,\n dialect,\n fullKey,\n isNullable,\n excludeSet,\n columnMap,\n primaryKey,\n columns,\n );\n continue;\n }\n\n // Arrays and other complex types → JSON\n const logical = LOGICAL_MAP[typeName] ?? \"json\";\n const isPK = fullKey === primaryKey || field === primaryKey;\n const colName = columnMap[fullKey] ?? columnMap[field] ?? fullKey;\n\n columns.push({\n name: colName,\n sqlType: dialect.mapType(logical),\n nullable: isPK ? false : isNullable,\n isPrimaryKey: isPK,\n });\n }\n}\n\n/**\n * Convert a Zod object schema into an array of {@link SqlColumn} definitions\n * suitable for SQL table creation.\n *\n * Nested ZodObject fields are recursively flattened into separate columns\n * with underscore-separated names (e.g. `address.street` → `address_street`).\n * Arrays become JSON columns.\n */\nexport function zodSchemaToColumns(\n schema: z.ZodObject<any>,\n dialect: SqlDialect,\n options: ZodSchemaToColumnsOptions = {},\n): SqlColumn[] {\n const { primaryKey, exclude = [], columnMap = {} } = options;\n const excludeSet = new Set(exclude);\n const shape = getShape(schema);\n const columns: SqlColumn[] = [];\n\n flattenSchema(shape, dialect, \"\", false, excludeSet, columnMap, primaryKey, columns);\n\n // Internal version column used by the worker to discard out-of-order\n // PubSub deliveries (added unconditionally — never excluded).\n if (!columns.some((c) => c.name === SYNC_VERSION_COLUMN)) {\n columns.push({\n name: SYNC_VERSION_COLUMN,\n sqlType: dialect.mapType(\"bigint\"),\n nullable: true,\n isPrimaryKey: false,\n description: \"Monotonic publish version (Date.now() ms). Internal.\",\n });\n }\n\n return columns;\n}\n","import type { RepoSyncConfig } from \"./types\";\n\n/**\n * Convert a single Firestore value into a SQL-safe primitive.\n *\n * Complex types (arrays, GeoPoints, binary) become JSON strings.\n * Primitives pass through unchanged.\n * Objects are NOT stringified here — they are flattened by serializeDocument.\n */\nexport function serializeValue(value: unknown): unknown {\n if (value === null || value === undefined) return null;\n\n // Firestore Timestamp (duck-typed: has .toDate())\n if (\n typeof value === \"object\" &&\n typeof (value as Record<string, unknown>).toDate === \"function\"\n ) {\n return ((value as { toDate(): Date }).toDate()).toISOString();\n }\n\n if (value instanceof Date) return value.toISOString();\n\n if (Buffer.isBuffer(value)) return value.toString(\"base64\");\n\n if (value instanceof Uint8Array) {\n return Buffer.from(value).toString(\"base64\");\n }\n\n // Firestore GeoPoint (duck-typed: has latitude & longitude)\n if (\n typeof value === \"object\" &&\n \"latitude\" in (value as object) &&\n \"longitude\" in (value as object)\n ) {\n const geo = value as { latitude: number; longitude: number };\n return JSON.stringify({ lat: geo.latitude, lng: geo.longitude });\n }\n\n // Arrays → JSON (native JSON column in BigQuery)\n if (Array.isArray(value)) {\n return JSON.stringify(value.map(serializeValue));\n }\n\n // string | number | boolean — pass through\n // Plain objects are handled by flattenObject in serializeDocument\n return value;\n}\n\n/**\n * Recursively flatten a nested object into a flat key-value map\n * using underscore-separated keys: `{ address: { street: \"x\" } }` → `{ address_street: \"x\" }`.\n * Arrays and non-plain-object values are serialized as leaves.\n */\nfunction flattenObject(\n obj: Record<string, unknown>,\n prefix: string,\n result: Record<string, unknown>,\n): void {\n for (const [key, value] of Object.entries(obj)) {\n const flatKey = prefix ? `${prefix}__${key}` : key;\n\n if (\n value !== null &&\n value !== undefined &&\n typeof value === \"object\" &&\n !Array.isArray(value) &&\n !(value instanceof Date) &&\n !Buffer.isBuffer(value) &&\n !(value instanceof Uint8Array) &&\n // Not a Firestore Timestamp\n typeof (value as Record<string, unknown>).toDate !== \"function\" &&\n // Not a GeoPoint\n !(\"latitude\" in (value as object) && \"longitude\" in (value as object))\n ) {\n // Plain object → recurse\n flattenObject(value as Record<string, unknown>, flatKey, result);\n } else {\n result[flatKey] = serializeValue(value);\n }\n }\n}\n\n/**\n * Serialize a full Firestore document into a flat object of SQL-safe values.\n *\n * Nested objects are flattened into underscore-separated column names\n * (e.g. `address.street` → `address_street`). Arrays become JSON strings.\n * Applies optional field exclusions and column renames from `options`.\n */\nexport function serializeDocument(\n doc: Record<string, unknown>,\n options?: Pick<RepoSyncConfig, \"exclude\" | \"columnMap\">,\n): Record<string, unknown> {\n const exclude = new Set(options?.exclude);\n const columnMap = options?.columnMap ?? {};\n\n // First flatten the document\n const flat: Record<string, unknown> = {};\n flattenObject(doc, \"\", flat);\n\n // Then apply excludes and column renames\n const result: Record<string, unknown> = {};\n for (const [flatKey, value] of Object.entries(flat)) {\n if (exclude.has(flatKey)) continue;\n // Also check top-level prefix for excludes (e.g. exclude \"address\" removes all address_* cols)\n const topLevel = flatKey.split(\"__\")[0]!;\n if (topLevel !== flatKey && exclude.has(topLevel)) continue;\n const column =\n columnMap[flatKey] ??\n (flatKey.includes(\"__\")\n ? columnMap[flatKey.split(\"__\").pop()!]\n : undefined) ??\n flatKey;\n result[column] = value;\n }\n\n return result;\n}\n","/**\n * Sync Admin — optional HTTP endpoint for inspecting and managing the\n * Firestore → SQL sync pipeline.\n *\n * Features (gated by `featuresFlag`):\n * - **healthCheck** — compare expected Zod-derived columns vs actual SQL columns\n * - **manualSync** — force re-sync all documents in a Firestore collection\n *\n * @example\n * ```typescript\n * const sync = createFirestoreSync(repos, {\n * // …deps, adapter, etc.\n * admin: {\n * auth: { type: \"basic\", username: \"admin\", password: \"secret\" },\n * featuresFlag: { healthCheck: true, manualSync: true },\n * },\n * });\n *\n * export const adminsync = onRequest(sync.adminHandler!);\n * ```\n */\n\nimport { z } from \"zod\";\nimport type { AnyReq, AnyRes, RouteParams } from \"../servers/admin/router\";\nimport { MiniRouter } from \"../servers/admin/router\";\nimport { isAuthExtension } from \"../servers/auth\";\nimport { getLinkBase } from \"../servers/utils/link-base\";\nimport type { SyncQueue } from \"./queue\";\nimport { zodSchemaToColumns } from \"./schema-mapper\";\nimport { serializeDocument } from \"./serializer\";\nimport type {\n adminsyncConfig,\n PubSubClientDep,\n RepoSyncConfig,\n SqlAdapter,\n SyncEvent,\n} from \"./types\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype Req = AnyReq & { params: RouteParams };\n\ninterface RepoInfo {\n name: string;\n schema: z.ZodObject<any> | null;\n documentKey: string;\n tableName: string;\n isGroup: boolean;\n repoCfg: RepoSyncConfig<string> | undefined;\n repo: any;\n}\n\n// ---------------------------------------------------------------------------\n// HTML helpers\n// ---------------------------------------------------------------------------\n\nfunction page(title: string, linkBase: string, body: string): string {\n return `<!DOCTYPE html>\n<html lang=\"en\"><head>\n<meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>${title} — Sync Admin</title>\n<style>\n *{box-sizing:border-box;margin:0;padding:0}\n body{font-family:system-ui,-apple-system,sans-serif;background:#f5f5f5;color:#1a1a1a;padding:2rem}\n a{color:#0969da;text-decoration:none}a:hover{text-decoration:underline}\n h1{margin-bottom:1rem}h2{margin:1.5rem 0 .75rem}\n table{border-collapse:collapse;width:100%;margin-bottom:1rem}\n th,td{text-align:left;padding:.5rem .75rem;border:1px solid #d0d7de}\n th{background:#f6f8fa;font-weight:600}\n tr:nth-child(even){background:#fafbfc}\n .badge{display:inline-block;padding:.15rem .5rem;border-radius:1rem;font-size:.8rem;font-weight:600}\n .badge-ok{background:#dafbe1;color:#1a7f37}\n .badge-warn{background:#fff8c5;color:#9a6700}\n .badge-err{background:#ffebe9;color:#cf222e}\n .btn{display:inline-block;padding:.4rem 1rem;border:1px solid #d0d7de;border-radius:.375rem;\n background:#fff;cursor:pointer;font-size:.85rem;text-decoration:none;color:#1a1a1a}\n .btn:hover{background:#f3f4f6}.btn-primary{background:#0969da;color:#fff;border-color:#0969da}\n .btn-primary:hover{background:#0860ca}\n nav{margin-bottom:1.5rem}nav a{margin-right:1rem}\n .card{background:#fff;border:1px solid #d0d7de;border-radius:.5rem;padding:1.25rem;margin-bottom:1rem}\n pre{background:#f6f8fa;padding:1rem;border-radius:.375rem;overflow-x:auto;font-size:.85rem}\n .muted{color:#656d76;font-size:.85rem}\n</style>\n</head><body>\n<nav><a href=\"${linkBase}/\">← Dashboard</a></nav>\n<h1>${title}</h1>\n${body}\n</body></html>`;\n}\n\nfunction sendHtml(res: AnyRes, html: string, status = 200): void {\n res.status(status).set(\"Content-Type\", \"text/html; charset=utf-8\").send(html);\n}\n\nfunction sendJson(res: AnyRes, data: unknown, status = 200): void {\n res\n .status(status)\n .set(\"Content-Type\", \"application/json\")\n .send(JSON.stringify(data, null, 2));\n}\n\nfunction isJsonRequest(req: AnyReq): boolean {\n const accept = (req.headers?.[\"accept\"] ?? \"\") as string;\n return accept.includes(\"application/json\");\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create the sync admin HTTP handler.\n *\n * @param repoMapping - The configured repository mapping\n * @param adapter - The SQL adapter (e.g. BigQueryAdapter)\n * @param queues - Live queue map from the worker\n * @param handleMessage - Direct SyncEvent processor from the worker\n * @param config - Admin-specific config (auth, basePath, features)\n * @param repoConfigs - Per-repo sync config (tableName, exclude, columnMap…)\n * @param pubsub - PubSub client (needed for configCheck)\n * @param topicPrefix - PubSub topic prefix (needed for configCheck)\n */\nexport function createadminsyncServer(\n repoMapping: Record<string, any>,\n adapter: SqlAdapter,\n queues: Map<string, SyncQueue>,\n handleMessage: (event: SyncEvent) => Promise<void>,\n config: adminsyncConfig,\n repoConfigs: Record<string, RepoSyncConfig<string> | undefined>,\n pubsub?: PubSubClientDep,\n topicPrefix?: string,\n): (req: any, res: any) => Promise<void> {\n const basePath = (config.basePath ?? \"/\").replace(/\\/$/, \"\") || \"\";\n const features = config.featuresFlag ?? {};\n\n // Pre-compute repo info\n const repoInfos: RepoInfo[] = [];\n for (const [name, repo] of Object.entries(repoMapping)) {\n const repoCfg = repoConfigs[name];\n repoInfos.push({\n name,\n schema: (repo as any).schema ?? null,\n documentKey:\n (repo as any)._systemKeys?.[0] ?? (repo as any).documentKey ?? \"docId\",\n tableName: repoCfg?.tableName ?? name,\n isGroup: !!(repo as any)._isGroup,\n repoCfg,\n repo,\n });\n }\n\n const router = new MiniRouter();\n\n // -- Auth middleware -----------------------------------------------------\n if (config.auth) {\n if (isAuthExtension(config.auth)) {\n // Mount auxiliary routes (login page, session, logout) BEFORE pushing\n // the protected middleware so they remain publicly reachable. Use the\n // same convention as `servers.admin()` / `servers.crud()`.\n const ext = config.auth;\n for (const route of ext.routes) {\n const mountPath = `${basePath}${route.path}`;\n if (route.method === \"GET\") router.get(mountPath, route.handler);\n else router.post(mountPath, route.handler);\n }\n router.use(ext.middleware);\n } else if (typeof config.auth === \"function\") {\n router.use(config.auth as any);\n } else {\n const realm = config.auth.realm ?? \"Sync Admin\";\n const expected =\n \"Basic \" +\n Buffer.from(`${config.auth.username}:${config.auth.password}`).toString(\n \"base64\",\n );\n router.use((req, res, next) => {\n const authorization = (req as any).headers?.[\"authorization\"] ?? \"\";\n if (authorization !== expected) {\n res\n .status(401)\n .set(\"WWW-Authenticate\", `Basic realm=\"${realm}\"`)\n .set(\"Content-Type\", \"text/plain\")\n .send(\"Unauthorized\");\n return;\n }\n next();\n });\n }\n }\n\n // -- Dashboard ----------------------------------------------------------\n router.get(`${basePath}/`, (req, res) => {\n const lb = getLinkBase(req, basePath);\n const rows = repoInfos\n .map((r) => {\n const links: string[] = [];\n if (features.healthCheck)\n links.push(`<a class=\"btn\" href=\"${lb}/${r.name}/health\">Health</a>`);\n if (features.manualSync)\n links.push(\n `<a class=\"btn btn-primary\" href=\"${lb}/${r.name}/force-sync\">Force Sync</a>`,\n );\n return `<tr>\n <td><strong>${r.name}</strong></td>\n <td>${r.tableName}</td>\n <td>${r.isGroup ? '<span class=\"badge badge-warn\">group</span>' : '<span class=\"badge badge-ok\">collection</span>'}</td>\n <td>${r.schema ? \"✓\" : \"✗\"}</td>\n <td>${links.join(\" \")}</td>\n </tr>`;\n })\n .join(\"\\n\");\n\n const configCheckLink = features.configCheck\n ? `<p style=\"margin-top:.5rem\"><a class=\"btn\" href=\"${lb}/config-check\">⚙ Config Check</a></p>`\n : \"\";\n\n const html = page(\n \"Sync Dashboard\",\n lb,\n `<div class=\"card\">\n <table>\n <thead><tr><th>Repository</th><th>Table</th><th>Type</th><th>Schema</th><th>Actions</th></tr></thead>\n <tbody>${rows}</tbody>\n </table>\n ${configCheckLink}\n </div>`,\n );\n sendHtml(res, html);\n });\n router.get(`${basePath}`, (req, res) => {\n const lb = getLinkBase(req, basePath);\n res.status(302).set(\"Location\", `${lb}/`).send(\"\");\n });\n\n // -- Health Check -------------------------------------------------------\n if (features.healthCheck) {\n router.get(`${basePath}/:repoName/health`, async (req: Req, res) => {\n const lb = getLinkBase(req, basePath);\n const info = repoInfos.find((r) => r.name === req.params.repoName);\n if (!info) {\n sendHtml(\n res,\n page(\"Not Found\", lb, `<p>Unknown repo: ${req.params.repoName}</p>`),\n 404,\n );\n return;\n }\n if (!info.schema) {\n sendHtml(\n res,\n page(\n \"Health Check\",\n lb,\n `<p class=\"badge badge-warn\">No Zod schema attached to \"${info.name}\"</p>`,\n ),\n );\n return;\n }\n\n const expectedCols = zodSchemaToColumns(info.schema, adapter.dialect, {\n primaryKey: info.documentKey,\n exclude: info.repoCfg?.exclude,\n columnMap: info.repoCfg?.columnMap as\n | Record<string, string>\n | undefined,\n });\n\n let actualCols: string[] = [];\n let tableExists = false;\n let error: string | null = null;\n try {\n tableExists = await adapter.tableExists(info.tableName);\n if (tableExists) {\n actualCols = await adapter.getTableColumns(info.tableName);\n }\n } catch (e: any) {\n error = e?.message ?? String(e);\n }\n\n const actualSet = new Set(actualCols);\n const expectedSet = new Set(expectedCols.map((c) => c.name));\n\n const missing = expectedCols.filter((c) => !actualSet.has(c.name));\n const extra = actualCols.filter((c) => !expectedSet.has(c));\n const matched = expectedCols.filter((c) => actualSet.has(c.name));\n\n const isHealthy = tableExists && missing.length === 0 && !error;\n\n if (isJsonRequest(req)) {\n sendJson(res, {\n repo: info.name,\n table: info.tableName,\n tableExists,\n healthy: isHealthy,\n error,\n columns: {\n expected: expectedCols.map((c) => ({\n name: c.name,\n type: c.sqlType,\n nullable: c.nullable,\n isPrimaryKey: c.isPrimaryKey,\n })),\n actual: actualCols,\n matched: matched.map((c) => c.name),\n missing: missing.map((c) => ({\n name: c.name,\n type: c.sqlType,\n })),\n extra,\n },\n });\n return;\n }\n\n const statusBadge = isHealthy\n ? '<span class=\"badge badge-ok\">Healthy</span>'\n : '<span class=\"badge badge-err\">Unhealthy</span>';\n\n const colRows = expectedCols\n .map((c) => {\n const status = actualSet.has(c.name)\n ? '<span class=\"badge badge-ok\">OK</span>'\n : '<span class=\"badge badge-err\">MISSING</span>';\n return `<tr><td>${c.name}</td><td>${c.sqlType}</td><td>${c.nullable ? \"Yes\" : \"No\"}</td><td>${c.isPrimaryKey ? \"✓\" : \"\"}</td><td>${status}</td></tr>`;\n })\n .join(\"\\n\");\n\n const extraRows = extra\n .map(\n (c) =>\n `<tr><td>${c}</td><td colspan=\"3\" class=\"muted\">not in schema</td><td><span class=\"badge badge-warn\">EXTRA</span></td></tr>`,\n )\n .join(\"\\n\");\n\n const html = page(\n `Health: ${info.name}`,\n lb,\n `<div class=\"card\">\n <p>Table: <code>${info.tableName}</code> ${!tableExists ? '<span class=\"badge badge-err\">NOT FOUND</span>' : statusBadge}</p>\n ${error ? `<p class=\"badge badge-err\">Error: ${error}</p>` : \"\"}\n <h2>Columns</h2>\n <table>\n <thead><tr><th>Column</th><th>SQL Type</th><th>Nullable</th><th>PK</th><th>Status</th></tr></thead>\n <tbody>${colRows}${extraRows}</tbody>\n </table>\n </div>`,\n );\n sendHtml(res, html);\n });\n }\n\n // -- Force Sync ---------------------------------------------------------\n if (features.manualSync) {\n // GET — confirmation page\n router.get(`${basePath}/:repoName/force-sync`, (req: Req, res) => {\n const lb = getLinkBase(req, basePath);\n const info = repoInfos.find((r) => r.name === req.params.repoName);\n if (!info) {\n sendHtml(\n res,\n page(\"Not Found\", lb, `<p>Unknown repo: ${req.params.repoName}</p>`),\n 404,\n );\n return;\n }\n\n const html = page(\n `Force Sync: ${info.name}`,\n lb,\n `<div class=\"card\">\n <p>This will read <strong>all</strong> documents from the <code>${info.name}</code> Firestore collection\n and upsert them into the <code>${info.tableName}</code> SQL table.</p>\n <p class=\"muted\" style=\"margin:.75rem 0\">This may take a while for large collections.</p>\n <form method=\"POST\" action=\"${lb}/${info.name}/force-sync\">\n <button type=\"submit\" class=\"btn btn-primary\">Start Force Sync</button>\n </form>\n </div>`,\n );\n sendHtml(res, html);\n });\n\n // POST — execute\n router.post(`${basePath}/:repoName/force-sync`, async (req: Req, res) => {\n const lb = getLinkBase(req, basePath);\n const info = repoInfos.find((r) => r.name === req.params.repoName);\n if (!info) {\n sendJson(res, { error: `Unknown repo: ${req.params.repoName}` }, 404);\n return;\n }\n\n // Use the repository's collectionGroup or collection query\n const collRef = info.repo.ref;\n if (!collRef) {\n sendJson(\n res,\n { error: `No collection reference for \"${info.name}\"` },\n 400,\n );\n return;\n }\n\n let synced = 0;\n let errors = 0;\n const errorSamples: string[] = [];\n const batchSize = 500;\n const query = collRef.limit(batchSize);\n let lastDoc: any = null;\n\n try {\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const paginatedQuery = lastDoc ? query.startAfter(lastDoc) : query;\n const snapshot = await paginatedQuery.get();\n if (snapshot.empty) break;\n\n for (const doc of snapshot.docs) {\n const data = doc.data() as Record<string, unknown>;\n const docId = String(data[info.documentKey] ?? doc.id);\n const serialized = serializeDocument(data, {\n exclude: info.repoCfg?.exclude,\n columnMap: info.repoCfg?.columnMap,\n });\n\n try {\n await handleMessage({\n operation: \"UPSERT\",\n repoName: info.name,\n docId,\n data: serialized,\n timestamp: new Date().toISOString(),\n });\n synced++;\n } catch (e: any) {\n errors++;\n const msg = e?.message ?? String(e);\n console.error(\n `[ForceSync:${info.name}] doc=${docId} failed:`,\n e,\n );\n if (errorSamples.length < 5) errorSamples.push(`${docId}: ${msg}`);\n }\n }\n\n lastDoc = snapshot.docs[snapshot.docs.length - 1];\n if (snapshot.docs.length < batchSize) break;\n }\n\n // Flush the queue for this repo\n const queue = queues.get(info.name);\n if (queue) await queue.flush();\n } catch (e: any) {\n if (isJsonRequest(req)) {\n sendJson(\n res,\n { error: e?.message ?? String(e), synced, errors },\n 500,\n );\n return;\n }\n sendHtml(\n res,\n page(\n `Force Sync: ${info.name}`,\n lb,\n `<div class=\"card\">\n <p class=\"badge badge-err\">Error: ${e?.message ?? String(e)}</p>\n <p>Synced ${synced} docs before failure (${errors} errors).</p>\n </div>`,\n ),\n 500,\n );\n return;\n }\n\n if (isJsonRequest(req)) {\n sendJson(res, {\n repo: info.name,\n table: info.tableName,\n synced,\n errors,\n ...(errorSamples.length > 0 && { errorSamples }),\n });\n return;\n }\n\n const errorBlock =\n errorSamples.length > 0\n ? `<details style=\"margin-top:1rem\"><summary>First ${errorSamples.length} error(s)</summary>\n <pre style=\"white-space:pre-wrap\">${errorSamples\n .map((s) => s.replace(/[<>&]/g, (c) => `&#${c.charCodeAt(0)};`))\n .join(\"\\n\\n\")}</pre></details>`\n : \"\";\n\n const html = page(\n `Force Sync: ${info.name}`,\n lb,\n `<div class=\"card\">\n <p class=\"badge ${errors > 0 ? \"badge-warn\" : \"badge-ok\"}\">${errors > 0 ? \"Completed with errors\" : \"Complete\"}</p>\n <p>Synced <strong>${synced}</strong> documents to <code>${info.tableName}</code>.</p>\n ${errors > 0 ? `<p class=\"badge badge-warn\">${errors} error(s)</p>` : \"\"}\n ${errorBlock}\n </div>`,\n );\n sendHtml(res, html);\n });\n }\n\n // -- Config Check -------------------------------------------------------\n if (features.configCheck) {\n router.get(`${basePath}/config-check`, async (req, res) => {\n const lb = getLinkBase(req, basePath);\n const project =\n process.env[\"GCLOUD_PROJECT\"] ??\n process.env[\"GOOGLE_CLOUD_PROJECT\"] ??\n process.env[\"GCP_PROJECT\"] ??\n \"unknown\";\n const consoleBase = `https://console.cloud.google.com`;\n const tPrefix = topicPrefix ?? \"firestore-sync\";\n\n interface CheckResult {\n name: string;\n category: \"bigquery\" | \"pubsub\" | \"firestore\";\n status: \"ok\" | \"error\" | \"warn\";\n message: string;\n fix?: { gcloud?: string; console?: string; hint?: string };\n }\n\n const checks: CheckResult[] = [];\n\n // --- BigQuery checks ---\n // 1. General BQ connectivity (try listing tables via a simple exists check)\n try {\n await adapter.tableExists(\"__nonexistent_health_check__\");\n checks.push({\n name: \"BigQuery API\",\n category: \"bigquery\",\n status: \"ok\",\n message: \"BigQuery API is reachable\",\n });\n } catch (e: any) {\n const msg = e?.message ?? String(e);\n const msgLower = msg.toLowerCase();\n const isApiDisabled =\n msgLower.includes(\"disabled\") ||\n msgLower.includes(\"has not been used\") ||\n msgLower.includes(\"accessnotconfigured\");\n const isPermission =\n msgLower.includes(\"permission\") ||\n msg.includes(\"403\") ||\n msgLower.includes(\"access denied\");\n const isProjectNotFound =\n msgLower.includes(\"project\") && msgLower.includes(\"not found\");\n const isNotFound =\n msgLower.includes(\"not found\") || msg.includes(\"404\");\n\n if (isApiDisabled) {\n checks.push({\n name: \"BigQuery API\",\n category: \"bigquery\",\n status: \"error\",\n message: \"BigQuery API is not enabled\",\n fix: {\n gcloud: `gcloud services enable bigquery.googleapis.com --project=${project}`,\n console: `${consoleBase}/apis/library/bigquery.googleapis.com?project=${project}`,\n },\n });\n } else if (isProjectNotFound) {\n checks.push({\n name: \"BigQuery Project\",\n category: \"bigquery\",\n status: \"error\",\n message: msg,\n fix: {\n hint:\n \"The GCP project does not exist or the credentials don't have access to it. \" +\n \"In the Firebase emulator, GCLOUD_PROJECT may override the configured projectId. \" +\n \"Ensure you pass the correct projectId to the BigQuery constructor and have valid credentials.\",\n console: `${consoleBase}/home/dashboard`,\n },\n });\n } else if (isPermission) {\n checks.push({\n name: \"BigQuery API\",\n category: \"bigquery\",\n status: \"error\",\n message: `Permission denied: ${msg}`,\n fix: {\n hint: \"Grant the service account BigQuery Data Editor + BigQuery Job User roles\",\n gcloud: [\n `SA=$(gcloud run services describe YOUR_SERVICE --region=YOUR_REGION --format=\"value(spec.template.spec.serviceAccountName)\" --project=${project})`,\n `gcloud projects add-iam-policy-binding ${project} --member=\"serviceAccount:$SA\" --role=\"roles/bigquery.dataEditor\"`,\n `gcloud projects add-iam-policy-binding ${project} --member=\"serviceAccount:$SA\" --role=\"roles/bigquery.jobUser\"`,\n ].join(\"\\n\"),\n console: `${consoleBase}/iam-admin/iam?project=${project}`,\n },\n });\n } else if (isNotFound) {\n checks.push({\n name: \"BigQuery Dataset\",\n category: \"bigquery\",\n status: \"error\",\n message: `Dataset not found: ${msg}`,\n fix: {\n hint: \"Create the dataset first\",\n gcloud: `bq mk --dataset ${project}:YOUR_DATASET_ID`,\n console: `${consoleBase}/bigquery?project=${project}`,\n },\n });\n } else {\n checks.push({\n name: \"BigQuery API\",\n category: \"bigquery\",\n status: \"ok\",\n message:\n \"BigQuery API is reachable (table lookup returned expected error)\",\n });\n }\n }\n\n // 2. Per-repo table existence\n for (const info of repoInfos) {\n try {\n const exists = await adapter.tableExists(info.tableName);\n checks.push({\n name: `Table: ${info.tableName}`,\n category: \"bigquery\",\n status: exists ? \"ok\" : \"warn\",\n message: exists\n ? `Table \\`${info.tableName}\\` exists`\n : `Table \\`${info.tableName}\\` does not exist yet`,\n ...(!exists && {\n fix: {\n hint: \"Table will be auto-created on first sync if autoMigrate is enabled. Or create it manually.\",\n },\n }),\n });\n } catch (e: any) {\n checks.push({\n name: `Table: ${info.tableName}`,\n category: \"bigquery\",\n status: \"error\",\n message: e?.message ?? String(e),\n });\n }\n }\n\n // --- Pub/Sub checks ---\n if (pubsub) {\n for (const info of repoInfos) {\n const topicName = `${tPrefix}-${info.name}`;\n try {\n // Try to get topic metadata — succeeds if it exists\n const topic = (pubsub as any).topic(topicName);\n if (typeof topic.exists === \"function\") {\n const [exists] = await topic.exists();\n checks.push({\n name: `Topic: ${topicName}`,\n category: \"pubsub\",\n status: exists ? \"ok\" : \"error\",\n message: exists\n ? `Topic \\`${topicName}\\` exists`\n : `Topic \\`${topicName}\\` does not exist`,\n ...(!exists && {\n fix: {\n gcloud: `gcloud pubsub topics create ${topicName} --project=${project}`,\n console: `${consoleBase}/cloudpubsub/topic/list?project=${project}`,\n },\n }),\n });\n } else {\n // Minimal PubSub client — can't check existence, try publishing a no-op\n checks.push({\n name: `Topic: ${topicName}`,\n category: \"pubsub\",\n status: \"warn\",\n message:\n \"Cannot verify topic existence (PubSub client doesn't expose .exists())\",\n fix: {\n gcloud: `gcloud pubsub topics create ${topicName} --project=${project}`,\n console: `${consoleBase}/cloudpubsub/topic/list?project=${project}`,\n hint: \"Ensure the topic exists. It is auto-created by the Firebase emulator but must exist in production.\",\n },\n });\n }\n } catch (e: any) {\n const msg = e?.message ?? String(e);\n const isApiDisabled =\n msg.includes(\"disabled\") || msg.includes(\"has not been used\");\n checks.push({\n name: isApiDisabled ? \"Pub/Sub API\" : `Topic: ${topicName}`,\n category: \"pubsub\",\n status: \"error\",\n message: isApiDisabled ? \"Pub/Sub API is not enabled\" : msg,\n fix: isApiDisabled\n ? {\n gcloud: `gcloud services enable pubsub.googleapis.com --project=${project}`,\n console: `${consoleBase}/apis/library/pubsub.googleapis.com?project=${project}`,\n }\n : {\n gcloud: `gcloud pubsub topics create ${topicName} --project=${project}`,\n console: `${consoleBase}/cloudpubsub/topic/list?project=${project}`,\n },\n });\n // If the API itself is disabled, skip remaining topic checks\n if (isApiDisabled) break;\n }\n }\n } else {\n checks.push({\n name: \"Pub/Sub Client\",\n category: \"pubsub\",\n status: \"warn\",\n message: \"PubSub client not available for config check\",\n });\n }\n\n // --- JSON response ---\n if (isJsonRequest(req)) {\n const allOk = checks.every((c) => c.status === \"ok\");\n sendJson(res, { project, healthy: allOk, checks });\n return;\n }\n\n // --- HTML response ---\n const statusIcon = (s: string) =>\n s === \"ok\"\n ? '<span class=\"badge badge-ok\">OK</span>'\n : s === \"warn\"\n ? '<span class=\"badge badge-warn\">WARN</span>'\n : '<span class=\"badge badge-err\">ERROR</span>';\n\n const grouped = {\n bigquery: checks.filter((c) => c.category === \"bigquery\"),\n pubsub: checks.filter((c) => c.category === \"pubsub\"),\n firestore: checks.filter((c) => c.category === \"firestore\"),\n };\n\n const renderSection = (title: string, items: CheckResult[]) => {\n if (items.length === 0) return \"\";\n const rows = items\n .map((c) => {\n let fixHtml = \"\";\n if (c.fix) {\n const parts: string[] = [];\n if (c.fix.hint) parts.push(`<p class=\"muted\">${c.fix.hint}</p>`);\n if (c.fix.gcloud) parts.push(`<pre>$ ${c.fix.gcloud}</pre>`);\n if (c.fix.console)\n parts.push(\n `<p><a href=\"${c.fix.console}\" target=\"_blank\">Open GCP Console →</a></p>`,\n );\n fixHtml = `<div style=\"margin-top:.5rem\">${parts.join(\"\")}</div>`;\n }\n return `<tr>\n <td>${statusIcon(c.status)}</td>\n <td><strong>${c.name}</strong><br><span class=\"muted\">${c.message}</span>${fixHtml}</td>\n </tr>`;\n })\n .join(\"\\n\");\n return `<h2>${title}</h2>\n <table><thead><tr><th style=\"width:80px\">Status</th><th>Check</th></tr></thead>\n <tbody>${rows}</tbody></table>`;\n };\n\n const allOk = checks.every((c) => c.status === \"ok\");\n const overallBadge = allOk\n ? '<span class=\"badge badge-ok\">All checks passed</span>'\n : '<span class=\"badge badge-warn\">Some issues found</span>';\n\n const html = page(\n \"Config Check\",\n lb,\n `<div class=\"card\">\n <p>Project: <code>${project}</code> ${overallBadge}</p>\n ${renderSection(\"BigQuery\", grouped.bigquery)}\n ${renderSection(\"Pub/Sub\", grouped.pubsub)}\n ${renderSection(\"Firestore\", grouped.firestore)}\n </div>`,\n );\n sendHtml(res, html);\n });\n }\n\n // -- Request handler ----------------------------------------------------\n return async (req: any, res: any): Promise<void> => {\n await router.handle(req, res);\n };\n}\n","/**\n * DDL generator — produces CREATE TABLE / ALTER TABLE statements from\n * SqlColumn definitions and a SqlDialect.\n *\n * `generateDDL()` is the public entry point: it walks a repository mapping,\n * converts each repo's Zod schema to columns, and returns the full DDL\n * as a single string.\n */\n\nimport { z } from \"zod\";\nimport { zodSchemaToColumns } from \"./schema-mapper\";\nimport type {\n GenerateDDLConfig,\n RepoSyncConfig,\n SqlColumn,\n SqlDialect,\n SqlTableDef,\n} from \"./types\";\n\n// ---------------------------------------------------------------------------\n// Low-level DDL helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Generate a CREATE TABLE statement from a table definition.\n * Uses the dialect for identifier quoting and type mapping.\n *\n * NOTE: This produces preview/export DDL with unqualified table names.\n * For actual execution, use `adapter.createTable()` which handles\n * dataset/schema qualification internally.\n */\nexport function createTableDDL(\n dialect: SqlDialect,\n table: SqlTableDef,\n): string {\n const cols = table.columns\n .map((c) => {\n const notNull = c.isPrimaryKey ? \" NOT NULL\" : \"\";\n return ` ${dialect.quoteIdentifier(c.name)} ${c.sqlType}${notNull}`;\n })\n .join(\",\\n\");\n\n return `CREATE TABLE IF NOT EXISTS ${dialect.quoteIdentifier(table.tableName)} (\\n${cols}\\n);`;\n}\n\n/**\n * Generate ALTER TABLE ADD COLUMN statements for columns missing from an\n * existing table.\n *\n * NOTE: This produces preview/export DDL with unqualified table names.\n * For actual execution, use `adapter.addColumns()` which handles\n * dataset/schema qualification internally.\n */\nexport function addColumnsDDL(\n dialect: SqlDialect,\n tableName: string,\n columns: SqlColumn[],\n): string {\n return columns\n .map(\n (c) =>\n `ALTER TABLE ${dialect.quoteIdentifier(tableName)} ADD COLUMN ${dialect.quoteIdentifier(c.name)} ${c.sqlType};`,\n )\n .join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// High-level DDL generation\n// ---------------------------------------------------------------------------\n\n/**\n * Walk a full repository mapping and produce DDL for every repo that has a\n * Zod schema attached.\n *\n * @param repoMapping - Object whose values expose `.schema` (ZodObject)\n * @param dialect - Target SQL dialect\n * @param config - Optional per-repo overrides (table name, exclusions…)\n * @returns Complete DDL string (one CREATE TABLE per repo, separated by newlines)\n */\nexport function generateDDL<M extends Record<string, any>>(\n repoMapping: M,\n dialect: SqlDialect,\n config?: GenerateDDLConfig<NoInfer<M>>,\n): string {\n const statements: string[] = [];\n\n for (const [repoName, repo] of Object.entries(repoMapping) as [\n string,\n any,\n ][]) {\n const schema: z.ZodObject<any> | undefined =\n repo.schema ?? (repo as any)._schema ?? undefined;\n if (!schema) continue;\n\n const repoCfg = (\n config?.repos as Record<string, RepoSyncConfig<string>> | undefined\n )?.[repoName];\n const tableName = repoCfg?.tableName ?? repoName;\n\n // Detect documentKey from repo metadata\n const documentKey: string =\n (repo as any)._systemKeys?.[0] ?? (repo as any).documentKey ?? \"docId\";\n\n const columns = zodSchemaToColumns(schema, dialect, {\n primaryKey: documentKey,\n exclude: repoCfg?.exclude,\n columnMap: repoCfg?.columnMap as Record<string, string> | undefined,\n });\n\n const tableDef: SqlTableDef = { tableName, columns };\n statements.push(createTableDDL(dialect, tableDef));\n }\n\n return statements.join(\"\\n\\n\");\n}\n","/**\n * Migration manager — generates DDL and optionally auto-migrates SQL tables\n * to match the Zod schemas defined in a repository mapping.\n *\n * Migrations are **additive only**: new columns are added, existing columns\n * are never dropped or modified.\n */\n\nimport { z } from \"zod\";\nimport { zodSchemaToColumns } from \"./schema-mapper\";\nimport type {\n GenerateDDLConfig,\n RepoSyncConfig,\n SqlAdapter,\n SqlColumn,\n SqlTableDef,\n} from \"./types\";\n\nexport interface MigrateResult {\n /** Tables that were created from scratch */\n created: string[];\n /** Tables that had columns added */\n altered: string[];\n /** Tables that were already up-to-date */\n upToDate: string[];\n /** Tables skipped (no schema available) */\n skipped: string[];\n}\n\n/**\n * Auto-migrate all repos: create missing tables, add missing columns.\n *\n * @returns Summary of what was done per table.\n */\nexport async function autoMigrate<M extends Record<string, any>>(\n repoMapping: M,\n adapter: SqlAdapter,\n config?: GenerateDDLConfig<NoInfer<M>>,\n): Promise<MigrateResult> {\n const result: MigrateResult = {\n created: [],\n altered: [],\n upToDate: [],\n skipped: [],\n };\n\n for (const [repoName, repo] of Object.entries(repoMapping) as [\n string,\n any,\n ][]) {\n const schema: z.ZodObject<any> | undefined =\n (repo as any).schema ?? undefined;\n if (!schema) {\n result.skipped.push(repoName);\n continue;\n }\n\n const repoCfg = (\n config?.repos as Record<string, RepoSyncConfig<string>> | undefined\n )?.[repoName];\n const tableName = repoCfg?.tableName ?? repoName;\n const documentKey: string =\n (repo as any)._systemKeys?.[0] ?? (repo as any).documentKey ?? \"docId\";\n\n const columns = zodSchemaToColumns(schema, adapter.dialect, {\n primaryKey: documentKey,\n exclude: repoCfg?.exclude,\n columnMap: repoCfg?.columnMap as Record<string, string> | undefined,\n });\n\n const tableDef: SqlTableDef = { tableName, columns };\n const exists = await adapter.tableExists(tableName);\n\n if (!exists) {\n await adapter.createTable(tableDef);\n result.created.push(tableName);\n } else {\n const existingCols = new Set(await adapter.getTableColumns(tableName));\n const newCols: SqlColumn[] = columns.filter(\n (c) => !existingCols.has(c.name),\n );\n\n if (newCols.length > 0) {\n await adapter.addColumns(tableName, newCols);\n result.altered.push(tableName);\n } else {\n result.upToDate.push(tableName);\n }\n }\n }\n\n return result;\n}\n","/**\n * Per-repo in-memory batch buffer.\n *\n * Accumulates {@link SyncEvent}s and flushes them in batches to a\n * {@link SqlAdapter}. On flush failure the events are re-published\n * to PubSub for retry (if a PubSub re-publisher is provided).\n */\n\nimport { SYNC_VERSION_COLUMN } from \"./constants\";\nimport type { SqlAdapter, SyncEvent } from \"./types\";\n\nexport interface SyncQueueOptions {\n /** SQL adapter to flush data to */\n adapter: SqlAdapter;\n /** SQL table name */\n tableName: string;\n /** Primary key column name */\n primaryKey: string;\n /** Max rows per flush (default: 100) */\n batchSize?: number;\n /** Auto-flush interval in ms (default: 5_000). 0 = manual only. */\n flushIntervalMs?: number;\n /** Called on flush failure with the failed events. Typically re-publishes to PubSub. */\n onFlushError?: (events: SyncEvent[], error: unknown) => Promise<void>;\n}\n\n/**\n * In-memory buffer that batches sync events per-repo and flushes them\n * to a SQL adapter.\n */\nexport class SyncQueue {\n private buffer: SyncEvent[] = [];\n private flushing = false;\n private flushPromise: Promise<void> | null = null;\n private timer: ReturnType<typeof setInterval> | null = null;\n\n private readonly adapter: SqlAdapter;\n private readonly tableName: string;\n private readonly primaryKey: string;\n private readonly batchSize: number;\n private readonly onFlushError?: SyncQueueOptions[\"onFlushError\"];\n\n constructor(opts: SyncQueueOptions) {\n this.adapter = opts.adapter;\n this.tableName = opts.tableName;\n this.primaryKey = opts.primaryKey;\n this.batchSize = opts.batchSize ?? 100;\n this.onFlushError = opts.onFlushError;\n\n const interval = opts.flushIntervalMs ?? 5_000;\n if (interval > 0) {\n this.timer = setInterval(() => void this.flush(), interval);\n // Allow the Node process to exit even if the timer is running\n if (typeof this.timer === \"object\" && \"unref\" in this.timer) {\n this.timer.unref();\n }\n }\n }\n\n /** Number of events waiting in the buffer. */\n get size(): number {\n return this.buffer.length;\n }\n\n /** Push one or more events into the buffer. Triggers auto-flush if batchSize reached. */\n enqueue(...events: SyncEvent[]): void {\n this.buffer.push(...events);\n if (this.buffer.length >= this.batchSize) {\n void this.flush();\n }\n }\n\n /**\n * Flush all buffered events to the SQL adapter.\n * Upserts and deletes are batched separately.\n *\n * Concurrent callers (e.g. several PubSub messages handled in parallel\n * inside the same Cloud Function instance with `concurrency > 1`) all\n * await the same in-flight flush and then trigger a follow-up flush if\n * new events were enqueued in the meantime. This guarantees that every\n * caller's event is persisted before its `await flush()` resolves —\n * which is required for safe PubSub ack semantics.\n */\n async flush(): Promise<void> {\n // If another flush is in progress, wait for it to finish first.\n while (this.flushing && this.flushPromise) {\n await this.flushPromise;\n }\n if (this.buffer.length === 0) return;\n\n this.flushing = true;\n this.flushPromise = this._doFlush().finally(() => {\n this.flushing = false;\n this.flushPromise = null;\n });\n await this.flushPromise;\n }\n\n private async _doFlush(): Promise<void> {\n // Drain the buffer atomically\n const batch = this.buffer.splice(0, this.batchSize);\n\n try {\n const upsertsById = new Map<string, Record<string, unknown>>();\n const deleteIds: string[] = [];\n\n for (const evt of batch) {\n if (evt.operation === \"DELETE\") {\n deleteIds.push(evt.docId);\n // A delete supersedes any pending upsert in the same batch.\n upsertsById.delete(evt.docId);\n } else if (evt.data) {\n // Multiple updates to the same doc within a single batch would\n // make BigQuery MERGE error out (\"UPDATE/MERGE must match at\n // most one source row for each target row\"). Keep only the row\n // with the highest __sync_version per docId.\n const existing = upsertsById.get(evt.docId);\n if (!existing) {\n upsertsById.set(evt.docId, evt.data);\n } else {\n const a = Number(existing[SYNC_VERSION_COLUMN] ?? 0);\n const b = Number(evt.data[SYNC_VERSION_COLUMN] ?? 0);\n if (b >= a) upsertsById.set(evt.docId, evt.data);\n }\n }\n }\n\n const upserts = Array.from(upsertsById.values());\n\n if (upserts.length > 0) {\n await this.adapter.upsertRows(this.tableName, upserts, this.primaryKey);\n }\n if (deleteIds.length > 0) {\n await this.adapter.deleteRows(\n this.tableName,\n this.primaryKey,\n deleteIds,\n );\n }\n } catch (err) {\n if (this.onFlushError) {\n // If the error handler also fails, re-throw so the Cloud Function\n // does NOT ack the PubSub message — it will be retried automatically.\n await this.onFlushError(batch, err);\n } else {\n // Re-insert at the front so we retry next flush\n this.buffer.unshift(...batch);\n console.error(`[SyncQueue] Flush failed for ${this.tableName}:`, err);\n }\n }\n }\n\n /** Stop the auto-flush timer and flush remaining events. */\n async shutdown(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n await this.flush();\n }\n}\n","/**\n * Firestore Cloud Function triggers that publish {@link SyncEvent}s to\n * Google Cloud PubSub.\n *\n * Dependencies (`firebase-functions`, `@google-cloud/pubsub`) are injected\n * via the `deps` config property — no lazy loading or module resolution issues.\n *\n * Out-of-order delivery is handled at the application level: every event\n * carries a `version` (publish-time `Date.now()` in ms) which is propagated\n * to SQL as the `__sync_version` column. The worker's MERGE only updates a\n * row when the incoming `version` is strictly greater than the stored one —\n * so a stale event delivered after a newer one is silently skipped.\n *\n * @example\n * ```typescript\n * import { createSyncTriggers } from \"@lpdjs/firestore-repo-service/sync\";\n * import * as firestoreTriggers from \"firebase-functions/v2/firestore\";\n * import { PubSub } from \"@google-cloud/pubsub\";\n *\n * const triggers = createSyncTriggers(repos, {\n * deps: { firestoreTriggers, pubsub: new PubSub() },\n * topicPrefix: \"my-sync\",\n * repos: { users: { exclude: [\"password\"] } },\n * });\n *\n * export const { users_onCreate, users_onUpdate, users_onDelete } = triggers;\n * ```\n */\n\nimport { serializeDocument } from \"./serializer\";\nimport type { RepoSyncConfig, SyncEvent, SyncTriggersConfig } from \"./types\";\n\nconst DEFAULT_TOPIC_PREFIX = \"firestore-sync\";\n\n/**\n * Derive the Firestore document path pattern for a trigger.\n * Returns `null` when the collection path cannot be determined.\n */\nfunction buildDocumentPath(repoName: string, repo: any): string | null {\n const collectionPath: string | undefined =\n (repo as any).ref?.path ?? undefined;\n\n if (!collectionPath) {\n console.warn(\n `[SyncTriggers] Cannot determine collection path for \"${repoName}\". Skipping.`,\n );\n return null;\n }\n\n return `${collectionPath}/{docId}`;\n}\n\n/**\n * Create Firestore Cloud Functions (v2) triggers that publish\n * {@link SyncEvent}s to PubSub for each repository in `repoMapping`.\n *\n * For each non-group repository, three triggers are created:\n * - `{repoName}_onCreate` → publishes an `INSERT` event\n * - `{repoName}_onUpdate` → publishes an `UPSERT` event\n * - `{repoName}_onDelete` → publishes a `DELETE` event\n *\n * Each event carries a monotonic `version` (`Date.now()` in ms) used by\n * the worker to discard out-of-order PubSub deliveries.\n */\nexport function createSyncTriggers<M extends Record<string, any>>(\n repoMapping: M,\n config: SyncTriggersConfig<NoInfer<M>>,\n): Record<string, any> {\n const { onDocumentCreated, onDocumentUpdated, onDocumentDeleted } =\n config.deps.firestoreTriggers;\n const pubsub = config.deps.pubsub;\n\n const topicPrefix = config?.topicPrefix ?? DEFAULT_TOPIC_PREFIX;\n const triggers: Record<string, any> = {};\n\n // Cache topic clients so the publisher reuses the same batching state\n // for a given topic across invocations.\n const topicCache = new Map<string, any>();\n function getTopic(topicName: string): any {\n let t = topicCache.get(topicName);\n if (t) return t;\n t = (pubsub as any).topic(topicName);\n topicCache.set(topicName, t);\n return t;\n }\n\n async function publish(\n topicName: string,\n syncEvent: SyncEvent,\n ): Promise<void> {\n const topic = getTopic(topicName);\n await topic.publishMessage({ json: syncEvent });\n }\n\n for (const [repoName, repo] of Object.entries(repoMapping) as [\n string,\n any,\n ][]) {\n const repoCfg = (\n config?.repos as Record<string, RepoSyncConfig<string>> | undefined\n )?.[repoName];\n\n let documentPath: string | null;\n\n if ((repo as any)._isGroup) {\n if (!repoCfg?.triggerPath) {\n console.warn(\n `[SyncTriggers] Skipping collection-group repo \"${repoName}\". ` +\n \"Provide a triggerPath in the sync repos config for group collections.\",\n );\n continue;\n }\n documentPath = repoCfg.triggerPath;\n } else {\n documentPath = repoCfg?.triggerPath ?? buildDocumentPath(repoName, repo);\n }\n if (!documentPath) continue;\n\n const documentKey: string = (repo as any)._systemKeys?.[0] ?? \"docId\";\n const topicName = `${topicPrefix}-${repoName}`;\n\n triggers[`${repoName}_onCreate`] = onDocumentCreated(\n documentPath,\n async (event: any) => {\n const snap = event.data;\n if (!snap) return;\n\n const data = snap.data() as Record<string, unknown> | undefined;\n if (!data) return;\n\n const docId = String(data[documentKey] ?? snap.id);\n const serialized = serializeDocument(data, {\n exclude: repoCfg?.exclude,\n columnMap: repoCfg?.columnMap,\n });\n\n const syncEvent: SyncEvent = {\n operation: \"INSERT\",\n repoName,\n docId,\n data: serialized,\n timestamp: new Date().toISOString(),\n version: Date.now(),\n };\n\n await publish(topicName, syncEvent);\n },\n );\n\n triggers[`${repoName}_onUpdate`] = onDocumentUpdated(\n documentPath,\n async (event: any) => {\n const snap = event.data?.after;\n if (!snap) return;\n\n const data = snap.data() as Record<string, unknown> | undefined;\n if (!data) return;\n\n const docId = String(data[documentKey] ?? snap.id);\n const serialized = serializeDocument(data, {\n exclude: repoCfg?.exclude,\n columnMap: repoCfg?.columnMap,\n });\n\n const syncEvent: SyncEvent = {\n operation: \"UPSERT\",\n repoName,\n docId,\n data: serialized,\n timestamp: new Date().toISOString(),\n version: Date.now(),\n };\n\n await publish(topicName, syncEvent);\n },\n );\n\n triggers[`${repoName}_onDelete`] = onDocumentDeleted(\n documentPath,\n async (event: any) => {\n const snap = event.data;\n if (!snap) return;\n\n const data = snap.data() as Record<string, unknown> | undefined;\n const docId = String(data?.[documentKey] ?? snap.id);\n\n const syncEvent: SyncEvent = {\n operation: \"DELETE\",\n repoName,\n docId,\n data: null,\n timestamp: new Date().toISOString(),\n version: Date.now(),\n };\n\n await publish(topicName, syncEvent);\n },\n );\n }\n\n return triggers;\n}\n","/**\n * PubSub worker — creates a Cloud Function that receives {@link SyncEvent}\n * messages from PubSub, routes them to per-repo {@link SyncQueue}s, and\n * flushes batches to the configured {@link SqlAdapter}.\n *\n * Dependencies (`firebase-functions`, `@google-cloud/pubsub`) are injected\n * via the `deps` config property.\n */\n\nimport { z } from \"zod\";\nimport { SYNC_VERSION_COLUMN } from \"./constants\";\nimport { SyncQueue } from \"./queue\";\nimport { zodSchemaToColumns } from \"./schema-mapper\";\nimport type {\n RepoSyncConfig,\n SqlAdapter,\n SyncEvent,\n SyncWorkerConfig,\n} from \"./types\";\n\n// ---------------------------------------------------------------------------\n// Migration tracking\n// ---------------------------------------------------------------------------\n\n/** Set of repo names that have already been migrated in this process lifetime. */\nconst migratedRepos = new Set<string>();\n\nasync function ensureMigrated(\n repoName: string,\n adapter: SqlAdapter,\n schema: z.ZodObject<any>,\n tableName: string,\n primaryKey: string,\n exclude?: string[],\n columnMap?: Record<string, string>,\n): Promise<void> {\n if (migratedRepos.has(repoName)) return;\n\n const columns = zodSchemaToColumns(schema, adapter.dialect, {\n primaryKey,\n exclude,\n columnMap,\n });\n\n const exists = await adapter.tableExists(tableName);\n if (!exists) {\n await adapter.createTable({ tableName, columns });\n } else {\n const existing = new Set(await adapter.getTableColumns(tableName));\n const newCols = columns.filter((c) => !existing.has(c.name));\n if (newCols.length > 0) {\n await adapter.addColumns(tableName, newCols);\n }\n }\n\n migratedRepos.add(repoName);\n}\n\n// ---------------------------------------------------------------------------\n// Worker factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a PubSub-triggered Cloud Function that syncs Firestore changes\n * to a SQL database.\n *\n * Returns an object with:\n * - `createHandler` — creates a Cloud Function for a PubSub topic\n * - `handleMessage` — process a SyncEvent directly (for testing)\n * - `queues` — internal SyncQueue map (for testing / manual flush)\n * - `shutdown()` — flush all queues and stop timers\n */\nexport function createSyncWorker<M extends Record<string, any>>(\n repoMapping: M,\n config: SyncWorkerConfig<NoInfer<M>>,\n) {\n const {\n deps,\n adapter,\n batchSize = 100,\n flushIntervalMs = 5_000,\n autoMigrate = false,\n topicPrefix = \"firestore-sync\",\n workerOptions,\n repos: repoConfigs = {} as Record<\n string,\n RepoSyncConfig<string> | undefined\n >,\n } = config;\n\n // Build per-repo queues lazily\n const queues = new Map<string, SyncQueue>();\n\n function getQueue(repoName: string, primaryKey: string): SyncQueue {\n let q = queues.get(repoName);\n if (q) return q;\n\n const repoCfg = repoConfigs[repoName];\n const tableName = repoCfg?.tableName ?? repoName;\n\n // On flush failure → log error + re-publish to PubSub dead-letter.\n // If the DLQ publish also fails, re-throw so the Cloud Function does NOT\n // ack the PubSub message and PubSub retries it automatically.\n const onFlushError = async (\n events: SyncEvent[],\n error: unknown,\n ): Promise<void> => {\n console.error(\n `[SyncWorker] Flush failed for \"${repoName}\" (${events.length} events):`,\n error,\n );\n const dlTopicName = `${topicPrefix}-${repoName}-dlq`;\n const dlTopic = deps.pubsub.topic(dlTopicName);\n const [exists] = await dlTopic.exists();\n if (!exists) {\n await dlTopic.create();\n console.info(`[SyncWorker] Created DLQ topic \"${dlTopicName}\"`);\n }\n for (const evt of events) {\n await dlTopic.publishMessage({ json: evt });\n }\n };\n\n q = new SyncQueue({\n adapter,\n tableName,\n primaryKey,\n batchSize,\n flushIntervalMs,\n onFlushError,\n });\n queues.set(repoName, q);\n return q;\n }\n\n // Message handler (works with or without Cloud Functions wrapper)\n async function handleMessage(syncEvent: SyncEvent): Promise<void> {\n const { repoName } = syncEvent;\n const repo = (repoMapping as Record<string, any>)[repoName];\n if (!repo) {\n console.warn(`[SyncWorker] Unknown repo \"${repoName}\", skipping event`);\n return;\n }\n\n const documentKey: string =\n (repo as any)._systemKeys?.[0] ?? (repo as any).documentKey ?? \"docId\";\n\n const repoCfg = repoConfigs[repoName];\n const columnMap = repoCfg?.columnMap as Record<string, string> | undefined;\n // The primaryKey for BigQuery must use the mapped column name (e.g. docId → user_id)\n const primaryKey = columnMap?.[documentKey] ?? documentKey;\n\n if (autoMigrate) {\n const schema: z.ZodObject<any> | undefined =\n (repo as any).schema ?? undefined;\n if (schema) {\n const tableName = repoCfg?.tableName ?? repoName;\n await ensureMigrated(\n repoName,\n adapter,\n schema,\n tableName,\n documentKey,\n repoCfg?.exclude,\n columnMap,\n );\n }\n }\n\n const queue = getQueue(repoName, primaryKey);\n\n // Stamp the row with the publish version so the SQL adapter can skip\n // stale (out-of-order) updates. Force-sync events without a version\n // fall back to the wall clock — still monotonic per-process.\n if (syncEvent.data) {\n syncEvent.data[SYNC_VERSION_COLUMN] = syncEvent.version ?? Date.now();\n }\n\n queue.enqueue(syncEvent);\n }\n\n // Cloud Function v2 PubSub handler (sync — deps are already available)\n function createHandler(topicName: string) {\n const handlerFn = async (event: any) => {\n const data: SyncEvent = event.data?.message?.json ?? event.data?.json;\n if (!data) {\n console.warn(\"[SyncWorker] Received empty PubSub message\");\n return;\n }\n await handleMessage(data);\n // Flush so data is persisted before the Cloud Function container shuts down.\n // SyncQueue.flush() coalesces concurrent callers so when `concurrency > 1`\n // every parallel handler awaits the same in-flight MERGE — guaranteeing\n // each PubSub message is only acked once its event reached BigQuery.\n // Force-sync (admin) handles its own flush after the batch loop.\n const q = queues.get(data.repoName);\n if (q) await q.flush();\n };\n\n if (workerOptions) {\n return deps.pubsubHandler.onMessagePublished(\n { topic: topicName, ...workerOptions },\n handlerFn,\n );\n }\n return deps.pubsubHandler.onMessagePublished(topicName, handlerFn);\n }\n\n return {\n /** Process a SyncEvent directly (for testing or custom PubSub integration). */\n handleMessage,\n /** Create a Cloud Function handler for a specific PubSub topic. */\n createHandler,\n /** Internal queue map (for testing). */\n queues,\n /** Flush all queues and stop timers. */\n async shutdown(): Promise<void> {\n const promises: Promise<void>[] = [];\n for (const q of queues.values()) {\n promises.push(q.shutdown());\n }\n await Promise.all(promises);\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/servers/admin/router.ts","../../src/servers/utils/link-base.ts","../../src/servers/auth/firebase-auth.ts","../../src/shared/zod-compat.ts","../../src/sync/constants.ts","../../src/sync/schema-mapper.ts","../../src/sync/serializer.ts","../../src/sync/admin.ts","../../src/sync/ddl-generator.ts","../../src/sync/migration.ts","../../src/sync/queue.ts","../../src/sync/triggers.ts","../../src/sync/adapters/bigquery-types.ts","../../src/sync/worker.ts"],"names":["compilePath","path","paramNames","src","c","_match","name","extractPath","req","raw","idx","MiniRouter","_req","res","err","middleware","handler","method","pattern","matchedRoute","params","route","m","i","enrichedReq","finalHandler","index","next","mw","getLinkBase","staticBasePath","base","project","region","target","service","host","isAuthExtension","value","TYPE_MAP","getTypeName","schema","s","v4Type","v3Type","getInnerType","getShape","SYNC_VERSION_COLUMN","WRAPPER_TYPES","unwrap","current","nullable","inner","LOGICAL_MAP","zodTypeToLogical","flattenSchema","shape","dialect","prefix","parentNullable","excludeSet","columnMap","primaryKey","columns","field","fieldSchema","fullKey","typeName","isNullable","nestedShape","logical","isPK","colName","zodSchemaToColumns","options","exclude","serializeValue","geo","flattenObject","obj","result","key","flatKey","serializeDocument","doc","flat","topLevel","column","page","title","linkBase","body","sendHtml","html","status","sendJson","data","isJsonRequest","createadminsyncServer","repoMapping","adapter","queues","handleMessage","config","repoConfigs","pubsub","topicPrefix","basePath","features","repoInfos","repo","repoCfg","router","ext","mountPath","realm","expected","lb","rows","r","links","configCheckLink","info","expectedCols","actualCols","tableExists","error","e","actualSet","expectedSet","missing","extra","matched","isHealthy","statusBadge","colRows","extraRows","collRef","synced","errors","errorSamples","batchSize","query","lastDoc","snapshot","docId","serialized","msg","queue","errorBlock","consoleBase","tPrefix","checks","msgLower","isApiDisabled","isPermission","isProjectNotFound","isNotFound","exists","topicName","topic","allOk","statusIcon","grouped","renderSection","items","fixHtml","parts","overallBadge","createTableDDL","table","cols","notNull","addColumnsDDL","tableName","generateDDL","statements","repoName","documentKey","tableDef","autoMigrate","existingCols","newCols","SyncQueue","opts","interval","events","batch","upsertsById","deleteIds","evt","existing","a","upserts","DEFAULT_TOPIC_PREFIX","buildDocumentPath","collectionPath","createSyncTriggers","onDocumentCreated","onDocumentUpdated","onDocumentDeleted","triggers","topicCache","getTopic","t","publish","syncEvent","documentPath","event","snap","normalizeBigQueryType","type","upper","isBigQueryTypeCompatible","desired","b","migratedRepos","SchemaTypeMismatchError","existingType","desiredType","ensureMigrated","col","createSyncWorker","deps","flushIntervalMs","workerOptions","getQueue","q","onFlushError","dlTopicName","dlTopic","createHandler","handlerFn","promises"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Minimal zero-dependency HTTP router for Firebase Functions.\n * Compatible with any Express-like (req, res) handler.\n *\n * Supports:\n * - Named path parameters (e.g. \"/repos/:name/:id\")\n * - GET, POST, DELETE methods\n * - Global middleware (before each route)\n * - 404 / error fallbacks\n *\n * @example\n * ```typescript\n * import { MiniRouter } from \"@lpdjs/firestore-repo-service/servers/admin\";\n *\n * // Create router\n * const router = new MiniRouter();\n *\n * // Add global middleware (executed before every route)\n * router.use(async (req, res, next) => {\n * console.log(`${req.method} ${req.url}`);\n * await next();\n * });\n *\n * // Auth middleware\n * router.use((req, res, next) => {\n * if (!req.headers?.authorization) {\n * res.status(401).send(\"Unauthorized\");\n * return;\n * }\n * next();\n * });\n *\n * // Define routes with path parameters\n * router.get(\"/users\", async (req, res) => {\n * res.json({ users: await getAllUsers() });\n * });\n *\n * router.get(\"/users/:id\", async (req, res) => {\n * const user = await getUser(req.params.id); // Access path params\n * if (!user) {\n * res.status(404).send(\"User not found\");\n * return;\n * }\n * res.json(user);\n * });\n *\n * router.post(\"/users\", async (req, res) => {\n * const user = await createUser(req.body);\n * res.status(201).json(user);\n * });\n *\n * router.delete(\"/users/:id\", async (req, res) => {\n * await deleteUser(req.params.id);\n * res.status(204).end();\n * });\n *\n * // Custom 404 handler\n * router.onNotFound((req, res) => {\n * res.status(404).json({ error: \"Route not found\", path: req.url });\n * });\n *\n * // Custom error handler\n * router.onError((err, req, res) => {\n * console.error(\"Error:\", err);\n * res.status(500).json({ error: \"Internal server error\" });\n * });\n *\n * // Use with Firebase Functions\n * export const api = onRequest(async (req, res) => {\n * await router.handle(req, res);\n * });\n * ```\n */\n\nexport type AnyReq = {\n method?: string;\n url?: string;\n /** Express originalUrl — preserved before any router stripping, contains the full path including the Firebase Functions prefix */\n originalUrl?: string;\n path?: string;\n headers?: Record<string, string | string[] | undefined>;\n body?: unknown;\n query?: Record<string, string | string[] | undefined>;\n};\n\nexport type AnyRes = {\n status: (code: number) => AnyRes;\n set: (key: string, value: string) => AnyRes;\n send: (body: string) => void;\n json: (body: unknown) => void;\n end: () => void;\n};\n\nexport type RouteParams = Record<string, string>;\n\nexport type RouteHandler = (\n req: AnyReq & { params: RouteParams },\n res: AnyRes,\n) => void | Promise<void>;\n\nexport type Middleware = (\n req: AnyReq & { params: RouteParams },\n res: AnyRes,\n next: () => void | Promise<void>,\n) => void | Promise<void>;\n\n// ---------------------------------------------------------------------------\n// Route matching\n// ---------------------------------------------------------------------------\n\ninterface CompiledRoute {\n method: string;\n pattern: RegExp;\n paramNames: string[];\n handler: RouteHandler;\n}\n\nfunction compilePath(path: string): { pattern: RegExp; paramNames: string[] } {\n const paramNames: string[] = [];\n const src = path\n .replace(/[.*+?^${}()|[\\]\\\\]/g, (c) => (c === \":\" ? c : `\\\\${c}`))\n .replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_match, name: string) => {\n paramNames.push(name);\n return \"([^/]+)\";\n });\n\n return { pattern: new RegExp(`^${src}$`), paramNames };\n}\n\nfunction extractPath(req: AnyReq): string {\n const raw = req.path ?? req.url ?? \"/\";\n const idx = raw.indexOf(\"?\");\n return idx === -1 ? raw : raw.slice(0, idx);\n}\n\n// ---------------------------------------------------------------------------\n// Router class\n// ---------------------------------------------------------------------------\n\nexport class MiniRouter {\n private routes: CompiledRoute[] = [];\n private middlewares: Middleware[] = [];\n private notFoundHandler: RouteHandler = (_req, res) => {\n res.status(404).send(\"Not Found\");\n };\n private errorHandler: (err: unknown, req: AnyReq, res: AnyRes) => void = (\n err,\n _req,\n res,\n ) => {\n console.error(\"[MiniRouter]\", err);\n res.status(500).send(\"Internal Server Error\");\n };\n\n // ── Route registration ────────────────────────────────────────────────────\n\n use(middleware: Middleware): this {\n this.middlewares.push(middleware);\n return this;\n }\n\n get(path: string, handler: RouteHandler): this {\n return this.addRoute(\"GET\", path, handler);\n }\n\n post(path: string, handler: RouteHandler): this {\n return this.addRoute(\"POST\", path, handler);\n }\n\n put(path: string, handler: RouteHandler): this {\n return this.addRoute(\"PUT\", path, handler);\n }\n\n patch(path: string, handler: RouteHandler): this {\n return this.addRoute(\"PATCH\", path, handler);\n }\n\n delete(path: string, handler: RouteHandler): this {\n return this.addRoute(\"DELETE\", path, handler);\n }\n\n onNotFound(handler: RouteHandler): this {\n this.notFoundHandler = handler;\n return this;\n }\n\n onError(handler: (err: unknown, req: AnyReq, res: AnyRes) => void): this {\n this.errorHandler = handler;\n return this;\n }\n\n private addRoute(method: string, path: string, handler: RouteHandler): this {\n const { pattern, paramNames } = compilePath(path);\n this.routes.push({\n method: method.toUpperCase(),\n pattern,\n paramNames,\n handler,\n });\n return this;\n }\n\n // ── Dispatch ──────────────────────────────────────────────────────────────\n\n async handle(req: AnyReq, res: AnyRes): Promise<void> {\n const method = (req.method ?? \"GET\").toUpperCase();\n const path = extractPath(req);\n\n // Find matching route\n let matchedRoute: CompiledRoute | null = null;\n let params: RouteParams = {};\n\n for (const route of this.routes) {\n if (route.method !== method) continue;\n const m = path.match(route.pattern);\n if (m) {\n matchedRoute = route;\n params = {};\n route.paramNames.forEach((name, i) => {\n params[name] = decodeURIComponent(m[i + 1] ?? \"\");\n });\n break;\n }\n }\n\n const enrichedReq = Object.assign(req, { params });\n\n // Run middleware chain → then handler\n const handler = matchedRoute ? matchedRoute.handler : this.notFoundHandler;\n\n try {\n await this.runMiddlewareChain(enrichedReq, res, handler);\n } catch (err) {\n this.errorHandler(err, req, res);\n }\n }\n\n private async runMiddlewareChain(\n req: AnyReq & { params: RouteParams },\n res: AnyRes,\n finalHandler: RouteHandler,\n ): Promise<void> {\n let index = 0;\n\n const next = async (): Promise<void> => {\n if (index < this.middlewares.length) {\n const mw = this.middlewares[index++]!;\n await mw(req, res, next);\n } else {\n await finalHandler(req, res);\n }\n };\n\n await next();\n }\n}\n","/**\n * Compute the URL prefix used to build absolute paths from inside a\n * Firebase HTTPS function. Handles three deployment shapes uniformly:\n *\n * 1. **Firebase emulator** (`FUNCTIONS_EMULATOR=true`) — exposes functions at\n * `http://localhost:5001/{project}/{region}/{functionTarget}/...`. The\n * handler receives `req.url` *without* this prefix, so we rebuild it from\n * `GCLOUD_PROJECT`, `FUNCTION_REGION`, `FUNCTION_TARGET`.\n *\n * 2. **Cloud Functions v2 default URL** (`*.cloudfunctions.net/{name}`) —\n * Cloud Run terminates routing at the service name, so links must include\n * the `K_SERVICE` prefix. Detected via the `host` header containing\n * `cloudfunctions.net`.\n *\n * 3. **Custom domain / Hosting rewrite** — the proxy strips the prefix\n * before reaching the handler, so links are relative to the configured\n * `staticBasePath`.\n *\n * @param req The incoming request (needs `headers.host` / `hostname`).\n * @param staticBasePath The user-configured base path (e.g. `\"/api\"`).\n * @returns A path prefix (no trailing slash) suitable for prepending to\n * `req.url` to build a same-function absolute URL.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function getLinkBase(req: any, staticBasePath: string): string {\n const base = staticBasePath === \"/\" ? \"\" : staticBasePath.replace(/\\/$/, \"\");\n\n if (process.env[\"FUNCTIONS_EMULATOR\"] === \"true\") {\n const project =\n process.env[\"GCLOUD_PROJECT\"] ??\n process.env[\"GOOGLE_CLOUD_PROJECT\"] ??\n \"demo-project\";\n const region = process.env[\"FUNCTION_REGION\"] ?? \"us-central1\";\n // FUNCTION_TARGET uses dots (e.g. \"sync.functions.adminsync\") but the\n // emulator URL uses hyphens (\"sync-functions-adminsync\").\n const target = (process.env[\"FUNCTION_TARGET\"] ?? \"\").replace(/\\./g, \"-\");\n return `/${project}/${region}/${target}${base}`;\n }\n\n // Cloud Functions v2: K_SERVICE = function name = URL path prefix.\n // Only add it when accessed via cloudfunctions.net (not custom domains).\n // Cloud Run (Gen 2) lowercases service names, but K_SERVICE may still\n // carry the original mixed-case export name — normalise to lowercase\n // so that generated links match the canonical URL.\n const service = process.env[\"K_SERVICE\"];\n const host: string =\n req?.hostname ?? req?.headers?.[\"host\"] ?? \"\";\n if (service && typeof host === \"string\" && host.includes(\"cloudfunctions.net\")) {\n return `/${service.toLowerCase()}${base}`;\n }\n\n return base;\n}\n","/**\n * Firebase Auth helper for the admin & CRUD servers.\n *\n * Returns an {@link AuthExtension} ready to plug into `servers.admin()` or\n * `servers.crud()`. Supports two transport modes:\n *\n * - **`cookie`** — session cookie pattern (default for admin UIs). Mounts\n * `/__login`, `/__session`, `/__logout` routes; the page lets the user sign\n * in client-side with the Firebase JS SDK and exchanges the resulting ID\n * token for an HttpOnly session cookie via Firebase Admin SDK.\n * - **`bearer`** — verifies `Authorization: Bearer <idToken>` on every request\n * (default for REST APIs). No login routes mounted.\n * - **`both`** — accept either cookie or bearer.\n *\n * The helper is **agnostic** about authorization: pass an `allow` callback\n * returning whatever role/context shape you need. The result is exposed as\n * `req.user.context` to downstream middlewares and route handlers.\n *\n * @example Admin (cookie + role trio)\n * ```ts\n * import { firebaseAuth } from \"@lpdjs/firestore-repo-service/servers/auth\";\n * import { getAuth } from \"firebase-admin/auth\";\n *\n * servers.admin({\n * auth: firebaseAuth({\n * getAuth,\n * mode: \"cookie\",\n * apiKey: process.env.FIREBASE_WEB_API_KEY!,\n * authDomain: process.env.FIREBASE_AUTH_DOMAIN!,\n * allow: ({ email, claims }) => {\n * if (claims.superAdmin) return { role: \"superAdmin\" };\n * if (email?.endsWith(\"@solarpush.io\")) return { role: \"admin\" };\n * if (email) return { role: \"viewer\" };\n * return null;\n * },\n * }),\n * repos: { ... },\n * });\n * ```\n *\n * @example CRUD (bearer + business rules per repo)\n * ```ts\n * servers.crud({\n * auth: firebaseAuth({ getAuth, mode: \"bearer\", allow: (u) => u }),\n * repos: {\n * comments: {\n * repo: repos.comments,\n * rules: {\n * list: () => true,\n * get: ({ user, doc }) => doc.public || doc.authorId === user.uid,\n * create: ({ user }) => !!user.uid,\n * update: ({ user, doc }) => user.uid === doc.authorId,\n * delete: ({ user, doc }) => user.claims.role === \"moderator\",\n * },\n * },\n * },\n * });\n * ```\n */\n\nimport type { AnyReq, Middleware, RouteHandler } from \"../admin/router\";\nimport { getLinkBase } from \"../utils/link-base\";\nimport { renderLoginPage } from \"./login-page\";\nimport {\n createLogoutHandler,\n createSessionHandler,\n parseCookies,\n SESSION_COOKIE_DEFAULT,\n} from \"./session\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Minimal Firebase Admin Auth surface needed by this helper.\n * Avoids a hard import of `firebase-admin/auth` so the package stays\n * decoupled from a specific firebase-admin version.\n */\nexport interface FirebaseAdminAuthLike {\n verifyIdToken(\n idToken: string,\n checkRevoked?: boolean,\n ): Promise<DecodedIdTokenLike>;\n verifySessionCookie(\n sessionCookie: string,\n checkRevoked?: boolean,\n ): Promise<DecodedIdTokenLike>;\n createSessionCookie(\n idToken: string,\n sessionCookieOptions: { expiresIn: number },\n ): Promise<string>;\n revokeRefreshTokens(uid: string): Promise<void>;\n}\n\nexport interface DecodedIdTokenLike {\n uid: string;\n email?: string;\n email_verified?: boolean;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [claim: string]: any;\n}\n\n/** Identity attached to every authenticated request as `req.user`. */\nexport interface AuthUser<TContext = unknown> {\n uid: string;\n email: string | null;\n emailVerified: boolean;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n claims: Record<string, any>;\n /** Result of the user-supplied `allow()` callback. */\n context: TContext;\n}\n\n/** A route descriptor mounted by `firebaseAuth` before the protected chain. */\nexport interface AuthRoute {\n method: \"GET\" | \"POST\";\n path: string;\n handler: RouteHandler;\n}\n\n/**\n * Returned by {@link firebaseAuth}. Servers detect this shape (vs.\n * `BasicAuthConfig` / raw `Middleware`) and mount the routes before pushing\n * the middleware onto the chain.\n */\nexport interface AuthExtension {\n readonly __authExtension: true;\n middleware: Middleware;\n /** Auxiliary routes (login page, session, logout). Empty in pure bearer mode. */\n routes: AuthRoute[];\n /** Path used to redirect unauthenticated browser requests. */\n loginPath: string;\n}\n\nexport type FirebaseAuthMode = \"cookie\" | \"bearer\" | \"both\";\n\n/** Provider configuration for the bundled login page. */\nexport interface FirebaseAuthLoginPageConfig {\n /** Page title. Default: \"Admin sign-in\". */\n title?: string;\n /**\n * Providers shown on the login page.\n * Default: `[\"password\", \"google\"]`.\n */\n providers?: (\"password\" | \"google\")[];\n}\n\nexport interface FirebaseAuthConfig<TContext = unknown> {\n /** Lazy getter for the Firebase Admin Auth instance. */\n getAuth: () => FirebaseAdminAuthLike;\n\n /** Transport mode. Default: `\"cookie\"`. */\n mode?: FirebaseAuthMode;\n\n /**\n * Authorization callback. Receives the verified token claims and returns:\n * - a context object → request is allowed, exposed as `req.user.context`,\n * - `null` → request is rejected (401 / redirect to login).\n *\n * If omitted, the default policy allows any authenticated user with\n * `context = null`.\n */\n allow?: (\n user: Omit<AuthUser, \"context\">,\n ) => TContext | null | Promise<TContext | null>;\n\n // ── Cookie mode options ────────────────────────────────────────────────\n /**\n * Whether to mount the bundled `/__login`, `/__session`, `/__logout`\n * routes. Default: `true` for `cookie`/`both`, `false` for `bearer`.\n */\n loginPage?: boolean | FirebaseAuthLoginPageConfig;\n\n /**\n * Firebase Web API key required by the JS SDK on the login page.\n * Mandatory when `loginPage` is enabled. Find it in your Firebase Console\n * under Project Settings → General → Web app config.\n */\n apiKey?: string;\n\n /**\n * Firebase Auth domain (e.g. `my-project.firebaseapp.com`).\n * Mandatory when `loginPage` is enabled.\n */\n authDomain?: string;\n\n /** Cookie name. Default: `__admin_session`. */\n cookieName?: string;\n\n /** Session cookie TTL in days. Default: `5` (Firebase max is 14). */\n sessionTtlDays?: number;\n\n /**\n * Cookie `Secure` flag. Default: `true`. Set to `false` only for local\n * development over HTTP.\n */\n secureCookie?: boolean;\n\n /** Cookie `SameSite`. Default: `\"Lax\"`. */\n sameSite?: \"Strict\" | \"Lax\" | \"None\";\n\n /**\n * Behaviour when authentication fails or `allow()` returns `null`.\n * - `\"redirect\"` (default in cookie mode) → 302 to the login page,\n * - `\"401\"` (default in bearer mode) → JSON 401 response.\n */\n onUnauthenticated?: \"redirect\" | \"401\";\n\n /**\n * Routes that should bypass the auth middleware (matched against the path\n * after the basePath stripping). The auxiliary login routes are always\n * public regardless of this option.\n */\n publicPaths?: (string | RegExp)[];\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction defaultLoginPage(mode: FirebaseAuthMode): boolean {\n return mode === \"cookie\" || mode === \"both\";\n}\n\nfunction defaultUnauth(mode: FirebaseAuthMode): \"redirect\" | \"401\" {\n return mode === \"bearer\" ? \"401\" : \"redirect\";\n}\n\nfunction pathOf(req: AnyReq): string {\n const raw = req.path ?? req.url ?? \"/\";\n const idx = raw.indexOf(\"?\");\n return idx === -1 ? raw : raw.slice(0, idx);\n}\n\nfunction queryAction(req: AnyReq): string | null {\n const q = (req as { query?: Record<string, unknown> }).query;\n if (q && typeof q.__action === \"string\") return q.__action;\n // Fallback: parse from URL when query parsing isn't done by the runtime.\n const url = req.url ?? \"\";\n const idx = url.indexOf(\"?\");\n if (idx === -1) return null;\n const params = new URLSearchParams(url.slice(idx + 1));\n return params.get(\"__action\");\n}\n\nfunction methodOf(req: AnyReq): string {\n return String(req.method ?? \"GET\").toUpperCase();\n}\n\nfunction isPublic(\n path: string,\n patterns: (string | RegExp)[] | undefined,\n): boolean {\n if (!patterns || patterns.length === 0) return false;\n for (const p of patterns) {\n if (typeof p === \"string\") {\n if (path === p || path.startsWith(p + \"/\")) return true;\n } else if (p.test(path)) {\n return true;\n }\n }\n return false;\n}\n\nfunction wantsHtml(req: AnyReq): boolean {\n const accept = String(req.headers?.accept ?? \"\");\n // Browsers send \"text/html\" early in their Accept header.\n // Fall back: treat GET requests with no Accept as HTML so platforms\n // that strip the header (or send \"*/*\") still get the login page.\n if (accept.includes(\"text/html\")) return true;\n if (!accept || accept === \"*/*\") return methodOf(req) === \"GET\";\n return false;\n}\n\nfunction extractBearer(req: AnyReq): string | null {\n const raw = req.headers?.authorization;\n const header = Array.isArray(raw) ? raw[0] : raw;\n if (!header) return null;\n const m = /^Bearer\\s+(.+)$/i.exec(header);\n return m ? m[1]!.trim() : null;\n}\n\nfunction rejectUnauthenticated(\n req: AnyReq,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n res: any,\n policy: \"redirect\" | \"401\",\n loginPath: string,\n): void {\n if (policy === \"redirect\" && wantsHtml(req)) {\n const target = encodeURIComponent(req.url ?? \"/\");\n res\n .status(302)\n .set(\"Location\", `${loginPath}?next=${target}`)\n .set(\"Cache-Control\", \"no-store\")\n .end();\n return;\n }\n res\n .status(401)\n .set(\"Content-Type\", \"application/json; charset=utf-8\")\n .send(JSON.stringify({ success: false, error: \"Unauthorized\" }));\n}\n\n// ---------------------------------------------------------------------------\n// Public factory\n// ---------------------------------------------------------------------------\n\n/**\n * Build a Firebase Auth extension for use with `servers.admin()` or\n * `servers.crud()`. See module-level docs for the full design and examples.\n */\nexport function firebaseAuth<TContext = unknown>(\n config: FirebaseAuthConfig<TContext>,\n): AuthExtension {\n const mode: FirebaseAuthMode = config.mode ?? \"cookie\";\n const cookieName = config.cookieName ?? SESSION_COOKIE_DEFAULT;\n const ttlDays = config.sessionTtlDays ?? 5;\n const secure = config.secureCookie ?? true;\n const sameSite = config.sameSite ?? \"Lax\";\n const onUnauth = config.onUnauthenticated ?? defaultUnauth(mode);\n const loginEnabled =\n config.loginPage === undefined\n ? defaultLoginPage(mode)\n : config.loginPage !== false;\n\n const loginPath = \"/__login\";\n const sessionPath = \"/__session\";\n const logoutPath = \"/__logout\";\n\n // ── Auxiliary handlers (kept in `routes` for hosting deployments\n // where users can mount them at known paths, AND invoked in-band by the\n // middleware on `?__action=session|logout` so vanilla Cloud Functions\n // — where there is no separate URL prefix per route — work too). ──────\n const sessionHandler = createSessionHandler({\n getAuth: config.getAuth,\n cookieName,\n ttlDays,\n secure,\n sameSite,\n });\n const logoutHandler = createLogoutHandler({\n getAuth: config.getAuth,\n cookieName,\n secure,\n sameSite,\n });\n\n function renderInlineLogin(\n req: AnyReq,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n res: any,\n error: string | null = null,\n ): void {\n // Validate lazily (at request time) so module loading during Firebase CLI\n // analysis doesn't throw before env vars are injected.\n if (!config.apiKey || !config.authDomain) {\n throw new Error(\n \"[firebaseAuth] `apiKey` and `authDomain` are required when `loginPage` is enabled. \" +\n \"Find both in the Firebase Console under Project Settings → General → Web app config.\",\n );\n }\n const pageCfg: FirebaseAuthLoginPageConfig =\n typeof config.loginPage === \"object\" ? config.loginPage : {};\n // Build a same-function absolute URL: the function's external prefix\n // (Cloud Functions name, emulator project/region/target, or \"\" for\n // custom domains) + the in-router request path. The browser otherwise\n // resolves form actions relative to the public URL, which doesn't\n // include the function name on Cloud Functions.\n const prefix = getLinkBase(req, \"/\");\n const inner = req.url ?? \"/\";\n const fullPath = `${prefix}${inner.startsWith(\"/\") ? inner : `/${inner}`}`;\n const sep = fullPath.includes(\"?\") ? \"&\" : \"?\";\n const sessionAction = `${fullPath}${sep}__action=session`;\n const html = renderLoginPage({\n title: pageCfg.title ?? \"Admin sign-in\",\n providers: pageCfg.providers ?? [\"password\", \"google\"],\n apiKey: config.apiKey!,\n authDomain: config.authDomain!,\n sessionPath: sessionAction,\n next: fullPath,\n error,\n });\n res\n .status(200)\n .set(\"Content-Type\", \"text/html; charset=utf-8\")\n .set(\"Cache-Control\", \"no-store\")\n .send(html);\n }\n\n // ── Auxiliary routes ─────────────────────────────────────────────────────\n const routes: AuthRoute[] = [];\n if (loginEnabled) {\n routes.push({\n method: \"GET\",\n path: loginPath,\n handler: (req, res) => {\n const error = (req.query?.error as string | undefined) ?? null;\n renderInlineLogin(req, res, error);\n },\n });\n routes.push({\n method: \"POST\",\n path: sessionPath,\n handler: sessionHandler,\n });\n routes.push({\n method: \"POST\",\n path: logoutPath,\n handler: logoutHandler,\n });\n }\n\n const publicPaths: (string | RegExp)[] = [\n ...(config.publicPaths ?? []),\n loginPath,\n sessionPath,\n logoutPath,\n ];\n\n // ── Middleware ───────────────────────────────────────────────────────────\n const middleware: Middleware = async (req, res, next) => {\n const path = pathOf(req);\n\n // 1. In-band action endpoints (work on ANY URL, no separate route needed).\n // Used by the inline login page since the helper can't know the function's\n // public URL prefix on Cloud Functions.\n if (loginEnabled && methodOf(req) === \"POST\") {\n const action = queryAction(req);\n if (action === \"session\") {\n await sessionHandler(req, res);\n return;\n }\n if (action === \"logout\") {\n await logoutHandler(req, res);\n return;\n }\n }\n\n // 2. Public paths (mounted login routes, user-supplied allowlist).\n if (isPublic(path, publicPaths)) {\n await next();\n return;\n }\n\n let decoded: DecodedIdTokenLike | null = null;\n try {\n const auth = config.getAuth();\n\n // Try bearer first when allowed (cheaper, no cookie parsing).\n if (mode === \"bearer\" || mode === \"both\") {\n const token = extractBearer(req);\n if (token) {\n decoded = await auth.verifyIdToken(token, true);\n }\n }\n\n // Fall back to cookie when allowed.\n if (!decoded && (mode === \"cookie\" || mode === \"both\")) {\n const cookieHeader = req.headers?.cookie;\n const raw = Array.isArray(cookieHeader)\n ? cookieHeader.join(\"; \")\n : cookieHeader;\n const cookies = parseCookies(typeof raw === \"string\" ? raw : \"\");\n const session = cookies[cookieName];\n if (session) {\n decoded = await auth.verifySessionCookie(session, true);\n }\n }\n } catch {\n decoded = null;\n }\n\n if (!decoded) {\n rejectUnauthenticated(req, res);\n return;\n }\n\n const baseUser: Omit<AuthUser, \"context\"> = {\n uid: decoded.uid,\n email: typeof decoded.email === \"string\" ? decoded.email : null,\n emailVerified: !!decoded.email_verified,\n claims: decoded as Record<string, unknown>,\n };\n\n let context: TContext | null;\n try {\n context = config.allow\n ? await config.allow(baseUser)\n : (null as TContext | null);\n } catch {\n context = null;\n }\n\n if (config.allow && context === null) {\n rejectUnauthenticated(req, res);\n return;\n }\n\n (req as AnyReq & { user?: AuthUser<TContext> }).user = {\n ...baseUser,\n context: context as TContext,\n };\n\n await next();\n };\n\n /**\n * Reject according to the configured policy:\n * - cookie/both + GET HTML browser request → render the login page inline\n * on the SAME URL (works on Cloud Functions where there's no separate\n * `/__login` route reachable from the public URL).\n * - bearer mode or non-HTML clients → JSON 401.\n */\n function rejectUnauthenticated(\n req: AnyReq,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n res: any,\n ): void {\n if (\n onUnauth === \"redirect\" &&\n loginEnabled &&\n methodOf(req) === \"GET\" &&\n wantsHtml(req)\n ) {\n renderInlineLogin(req, res, null);\n return;\n }\n res\n .status(401)\n .set(\"Content-Type\", \"application/json; charset=utf-8\")\n .send(JSON.stringify({ success: false, error: \"Unauthorized\" }));\n }\n\n return {\n __authExtension: true,\n middleware,\n routes,\n loginPath,\n };\n}\n\n/**\n * Type guard: detect an {@link AuthExtension} (vs. legacy\n * `BasicAuthConfig` / `Middleware`).\n */\nexport function isAuthExtension(value: unknown): value is AuthExtension {\n return (\n !!value &&\n typeof value === \"object\" &&\n (value as { __authExtension?: unknown }).__authExtension === true\n );\n}\n\n/**\n * Helper for explicitly opening a CRUD operation when the server has\n * `auth` defined (bypasses the default-deny policy).\n *\n * @example\n * ```ts\n * rules: { list: allowAll, get: allowAll }\n * ```\n */\nexport const allowAll = (): true => true;\n","/**\n * Zod introspection utilities — compatible with Zod 4.\n *\n * Centralizes all internal `_zod.def` accesses so the rest of the codebase\n * never touches Zod internals directly. If a future Zod version changes\n * the internal layout, only this file needs updating.\n */\n\nimport type { z } from \"zod\";\n\n// ---------------------------------------------------------------------------\n// Type-name resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Canonical Zod type names used in the codebase.\n * These match the **Zod 3** naming convention (e.g. \"ZodString\") that the\n * form-gen / admin handlers already rely on, so we don't need to rewrite\n * every `case \"ZodString\":` branch.\n */\ntype ZodTypeName =\n | \"ZodString\"\n | \"ZodNumber\"\n | \"ZodBigInt\"\n | \"ZodBoolean\"\n | \"ZodDate\"\n | \"ZodEnum\"\n | \"ZodNativeEnum\"\n | \"ZodLiteral\"\n | \"ZodObject\"\n | \"ZodArray\"\n | \"ZodOptional\"\n | \"ZodNullable\"\n | \"ZodDefault\"\n | \"ZodCoerce\"\n | \"ZodUnion\"\n | \"ZodUndefined\"\n | \"ZodUnknown\"\n | \"ZodAny\"\n | \"ZodRecord\"\n | string;\n\n/**\n * Map from Zod 4 `_zod.def.type` (lowercase) → Zod 3 `_def.typeName` format.\n * Any key not in this map is capitalised as `Zod${Capitalised}`.\n */\nconst TYPE_MAP: Record<string, ZodTypeName> = {\n string: \"ZodString\",\n number: \"ZodNumber\",\n bigint: \"ZodBigInt\",\n boolean: \"ZodBoolean\",\n date: \"ZodDate\",\n enum: \"ZodEnum\",\n nativeEnum: \"ZodNativeEnum\",\n literal: \"ZodLiteral\",\n object: \"ZodObject\",\n array: \"ZodArray\",\n optional: \"ZodOptional\",\n nullable: \"ZodNullable\",\n default: \"ZodDefault\",\n coerce: \"ZodCoerce\",\n union: \"ZodUnion\",\n undefined: \"ZodUndefined\",\n unknown: \"ZodUnknown\",\n any: \"ZodAny\",\n record: \"ZodRecord\",\n};\n\n/**\n * Get the canonical type name of a Zod schema.\n *\n * Works with both Zod 3 (`_def.typeName`) and Zod 4 (`_zod.def.type`).\n * Returns names in Zod-3 style (\"ZodString\", \"ZodObject\" …) for\n * backward-compatible switch/case usage.\n */\nexport function getTypeName(schema: z.ZodType): ZodTypeName {\n const s = schema as any;\n\n // Zod 4 path\n const v4Type: string | undefined = s._zod?.def?.type;\n if (v4Type)\n return (\n TYPE_MAP[v4Type] ??\n `Zod${v4Type.charAt(0).toUpperCase()}${v4Type.slice(1)}`\n );\n\n // Zod 3 fallback\n const v3Type: string | undefined = s._def?.typeName;\n if (v3Type) return v3Type;\n\n return \"\";\n}\n\n// ---------------------------------------------------------------------------\n// Unwrapping wrappers (Optional / Nullable / Default)\n// ---------------------------------------------------------------------------\n\n/**\n * Get the inner schema from a wrapper type (Optional / Nullable / Default).\n */\nexport function getInnerType(schema: z.ZodType): z.ZodType | undefined {\n const s = schema as any;\n\n // Zod 4: _zod.def.innerType\n if (s._zod?.def?.innerType) return s._zod.def.innerType;\n\n // Zod 3 compat: _def.innerType\n if (s._def?.innerType) return s._def.innerType;\n\n return undefined;\n}\n\n/**\n * Get the **element schema** from a `ZodArray`.\n * Returns `undefined` for non-array schemas.\n */\nexport function getArrayElementType(\n schema: z.ZodType,\n): z.ZodType | undefined {\n const s = schema as any;\n\n // Zod 4: _zod.def.element\n if (s._zod?.def?.element) return s._zod.def.element;\n\n // Zod 3: _def.type (ZodArray stores element schema in _def.type)\n if (s._def?.type) return s._def.type;\n\n return undefined;\n}\n\n/**\n * Returns the list of **required** (non-optional) top-level keys of a ZodObject.\n * A key is required when its schema is NOT wrapped in ZodOptional.\n * ZodNullable / ZodDefault fields are still considered required — they must be present.\n */\nexport function getRequiredSchemaKeys(schema: z.ZodObject<any>): string[] {\n const required: string[] = [];\n for (const [key, fieldSchema] of Object.entries(schema.shape)) {\n const typeName = getTypeName(fieldSchema as z.ZodType);\n if (typeName !== \"ZodOptional\") {\n required.push(key);\n }\n }\n return required;\n}\n\n/**\n * Get the default value for a ZodDefault schema.\n * In Zod 3 this was `_def.defaultValue()` (function). In Zod 4 it's a direct value.\n */\nexport function getDefaultValue(schema: z.ZodType): unknown {\n const s = schema as any;\n\n // Zod 4\n if (s._zod?.def?.defaultValue !== undefined) return s._zod.def.defaultValue;\n\n // Zod 3 compat\n const v = s._def?.defaultValue;\n if (typeof v === \"function\") return v();\n return v;\n}\n\n// ---------------------------------------------------------------------------\n// Object shape\n// ---------------------------------------------------------------------------\n\n/**\n * Get the shape of a ZodObject schema (Record<string, ZodType>).\n * Handles both Zod 3 (`_def.shape()` function) and Zod 4 (`.shape` property).\n */\nexport function getShape(schema: z.ZodType): Record<string, z.ZodType> {\n const s = schema as any;\n\n // Direct `.shape` property (Zod 3 & 4 for ZodObject instances)\n if (s.shape && typeof s.shape === \"object\") return s.shape;\n\n // Zod 4: _zod.def.shape\n if (s._zod?.def?.shape && typeof s._zod.def.shape === \"object\")\n return s._zod.def.shape;\n\n // Zod 3 fallback: _def.shape() or _def.shape\n if (s._def?.shape) {\n return typeof s._def.shape === \"function\" ? s._def.shape() : s._def.shape;\n }\n\n return {};\n}\n\n// ---------------------------------------------------------------------------\n// Enum / Literal values\n// ---------------------------------------------------------------------------\n\n/**\n * Get the values of a ZodEnum schema as a string[].\n */\nexport function getEnumValues(schema: z.ZodType): string[] {\n const s = schema as any;\n\n // Zod 4: `.options` or `_zod.def.entries`\n if (Array.isArray(s.options)) return s.options;\n if (s._zod?.def?.entries)\n return Object.values(s._zod.def.entries) as string[];\n\n // Zod 3: `_def.values`\n if (Array.isArray(s._def?.values)) return s._def.values;\n\n return [];\n}\n\n/**\n * Get the value object of a ZodNativeEnum schema.\n */\nexport function getNativeEnumValues(\n schema: z.ZodType,\n): Record<string, unknown> {\n const s = schema as any;\n\n // Zod 4: `_zod.def.entries` or `.enum`\n if (s._zod?.def?.entries) return s._zod.def.entries;\n if (s.enum && typeof s.enum === \"object\") return s.enum;\n\n // Zod 3: `_def.values`\n if (s._def?.values && typeof s._def.values === \"object\") return s._def.values;\n\n return {};\n}\n\n/**\n * Get the value of a ZodLiteral schema.\n */\nexport function getLiteralValue(schema: z.ZodType): unknown {\n const s = schema as any;\n\n // Zod 4: `_zod.def.values[0]`\n if (Array.isArray(s._zod?.def?.values)) return s._zod.def.values[0];\n\n // Zod 3: `_def.value`\n return s._def?.value;\n}\n\n// ---------------------------------------------------------------------------\n// String checks\n// ---------------------------------------------------------------------------\n\n/**\n * Get relevant \"check kinds\" from a ZodString schema.\n * Returns an array of kind strings: \"email\", \"url\", etc.\n */\nexport function getStringChecks(schema: z.ZodType): string[] {\n const s = schema as any;\n const kinds: string[] = [];\n\n // Zod 4: checks with `.format` field\n const v4Checks: any[] | undefined = s._zod?.def?.checks;\n if (Array.isArray(v4Checks)) {\n for (const c of v4Checks) {\n if (c.format) kinds.push(c.format);\n }\n if (kinds.length > 0) return kinds;\n }\n\n // Zod 3: _def.checks with `.kind` field\n const v3Checks: any[] | undefined = s._def?.checks;\n if (Array.isArray(v3Checks)) {\n for (const c of v3Checks) {\n if (c.kind) kinds.push(c.kind);\n }\n }\n\n return kinds;\n}\n","/**\n * Internal constants shared between the worker, queue, schema mapper and\n * SQL adapters.\n */\n\n/**\n * Name of the SQL column that stores the publish-time `version` of each\n * sync event. Used by the worker to discard out-of-order PubSub deliveries\n * (the MERGE only updates rows when the incoming version is strictly\n * greater than the stored one).\n *\n * Two underscores prefix avoids collisions with user-defined fields.\n */\nexport const SYNC_VERSION_COLUMN = \"__sync_version\";\n","import { z } from \"zod\";\nimport {\n getTypeName,\n getInnerType,\n getShape,\n} from \"../shared/zod-compat\";\nimport { SYNC_VERSION_COLUMN } from \"./constants\";\nimport type { SqlColumn, SqlDialect, LogicalType } from \"./types\";\n\nconst WRAPPER_TYPES = new Set([\"ZodOptional\", \"ZodNullable\", \"ZodDefault\"]);\n\nfunction unwrap(schema: z.ZodType): { inner: z.ZodType; nullable: boolean } {\n let current = schema;\n let nullable = false;\n\n for (;;) {\n const name = getTypeName(current);\n if (!WRAPPER_TYPES.has(name)) break;\n if (name === \"ZodOptional\" || name === \"ZodNullable\") nullable = true;\n const inner = getInnerType(current);\n if (!inner) break;\n current = inner;\n }\n\n return { inner: current, nullable };\n}\n\nconst LOGICAL_MAP: Record<string, LogicalType> = {\n ZodString: \"string\",\n ZodNumber: \"number\",\n ZodBigInt: \"bigint\",\n ZodBoolean: \"boolean\",\n ZodDate: \"timestamp\",\n ZodEnum: \"string\",\n ZodNativeEnum: \"string\",\n ZodLiteral: \"string\",\n};\n\nexport function zodTypeToLogical(schema: z.ZodType): LogicalType {\n const { inner } = unwrap(schema);\n return LOGICAL_MAP[getTypeName(inner)] ?? \"json\";\n}\n\nexport interface ZodSchemaToColumnsOptions {\n primaryKey?: string;\n exclude?: string[];\n columnMap?: Record<string, string>;\n}\n\n/**\n * Recursively flatten a Zod object schema into flat SQL columns.\n * Nested objects produce `parent_child` column names.\n * Arrays and non-object complex types become JSON columns.\n */\nfunction flattenSchema(\n shape: Record<string, z.ZodType>,\n dialect: SqlDialect,\n prefix: string,\n parentNullable: boolean,\n excludeSet: Set<string>,\n columnMap: Record<string, string>,\n primaryKey: string | undefined,\n columns: SqlColumn[],\n): void {\n for (const [field, fieldSchema] of Object.entries(shape)) {\n const fullKey = prefix ? `${prefix}__${field}` : field;\n\n // Exclude check on the original (dotless) path and the flattened key\n if (excludeSet.has(field) || excludeSet.has(fullKey)) continue;\n\n const { inner, nullable } = unwrap(fieldSchema);\n const typeName = getTypeName(inner);\n const isNullable = parentNullable || nullable;\n\n // Nested object → recurse to flatten\n if (typeName === \"ZodObject\") {\n const nestedShape = getShape(inner as z.ZodObject<any>);\n flattenSchema(\n nestedShape,\n dialect,\n fullKey,\n isNullable,\n excludeSet,\n columnMap,\n primaryKey,\n columns,\n );\n continue;\n }\n\n // Arrays and other complex types → JSON\n const logical = LOGICAL_MAP[typeName] ?? \"json\";\n const isPK = fullKey === primaryKey || field === primaryKey;\n const colName = columnMap[fullKey] ?? columnMap[field] ?? fullKey;\n\n columns.push({\n name: colName,\n sqlType: dialect.mapType(logical),\n nullable: isPK ? false : isNullable,\n isPrimaryKey: isPK,\n });\n }\n}\n\n/**\n * Convert a Zod object schema into an array of {@link SqlColumn} definitions\n * suitable for SQL table creation.\n *\n * Nested ZodObject fields are recursively flattened into separate columns\n * with underscore-separated names (e.g. `address.street` → `address_street`).\n * Arrays become JSON columns.\n */\nexport function zodSchemaToColumns(\n schema: z.ZodObject<any>,\n dialect: SqlDialect,\n options: ZodSchemaToColumnsOptions = {},\n): SqlColumn[] {\n const { primaryKey, exclude = [], columnMap = {} } = options;\n const excludeSet = new Set(exclude);\n const shape = getShape(schema);\n const columns: SqlColumn[] = [];\n\n flattenSchema(shape, dialect, \"\", false, excludeSet, columnMap, primaryKey, columns);\n\n // Internal version column used by the worker to discard out-of-order\n // PubSub deliveries (added unconditionally — never excluded).\n if (!columns.some((c) => c.name === SYNC_VERSION_COLUMN)) {\n columns.push({\n name: SYNC_VERSION_COLUMN,\n sqlType: dialect.mapType(\"bigint\"),\n nullable: true,\n isPrimaryKey: false,\n description: \"Monotonic publish version (Date.now() ms). Internal.\",\n });\n }\n\n return columns;\n}\n","import type { RepoSyncConfig } from \"./types\";\n\n/**\n * Convert a single Firestore value into a SQL-safe primitive.\n *\n * Complex types (arrays, GeoPoints, binary) become JSON strings.\n * Primitives pass through unchanged.\n * Objects are NOT stringified here — they are flattened by serializeDocument.\n */\nexport function serializeValue(value: unknown): unknown {\n if (value === null || value === undefined) return null;\n\n // Firestore Timestamp (duck-typed: has .toDate())\n if (\n typeof value === \"object\" &&\n typeof (value as Record<string, unknown>).toDate === \"function\"\n ) {\n return ((value as { toDate(): Date }).toDate()).toISOString();\n }\n\n if (value instanceof Date) return value.toISOString();\n\n if (Buffer.isBuffer(value)) return value.toString(\"base64\");\n\n if (value instanceof Uint8Array) {\n return Buffer.from(value).toString(\"base64\");\n }\n\n // Firestore GeoPoint (duck-typed: has latitude & longitude)\n if (\n typeof value === \"object\" &&\n \"latitude\" in (value as object) &&\n \"longitude\" in (value as object)\n ) {\n const geo = value as { latitude: number; longitude: number };\n return JSON.stringify({ lat: geo.latitude, lng: geo.longitude });\n }\n\n // Arrays → JSON (native JSON column in BigQuery)\n if (Array.isArray(value)) {\n return JSON.stringify(value.map(serializeValue));\n }\n\n // string | number | boolean — pass through\n // Plain objects are handled by flattenObject in serializeDocument\n return value;\n}\n\n/**\n * Recursively flatten a nested object into a flat key-value map\n * using underscore-separated keys: `{ address: { street: \"x\" } }` → `{ address_street: \"x\" }`.\n * Arrays and non-plain-object values are serialized as leaves.\n */\nfunction flattenObject(\n obj: Record<string, unknown>,\n prefix: string,\n result: Record<string, unknown>,\n): void {\n for (const [key, value] of Object.entries(obj)) {\n const flatKey = prefix ? `${prefix}__${key}` : key;\n\n if (\n value !== null &&\n value !== undefined &&\n typeof value === \"object\" &&\n !Array.isArray(value) &&\n !(value instanceof Date) &&\n !Buffer.isBuffer(value) &&\n !(value instanceof Uint8Array) &&\n // Not a Firestore Timestamp\n typeof (value as Record<string, unknown>).toDate !== \"function\" &&\n // Not a GeoPoint\n !(\"latitude\" in (value as object) && \"longitude\" in (value as object))\n ) {\n // Plain object → recurse\n flattenObject(value as Record<string, unknown>, flatKey, result);\n } else {\n result[flatKey] = serializeValue(value);\n }\n }\n}\n\n/**\n * Serialize a full Firestore document into a flat object of SQL-safe values.\n *\n * Nested objects are flattened into underscore-separated column names\n * (e.g. `address.street` → `address_street`). Arrays become JSON strings.\n * Applies optional field exclusions and column renames from `options`.\n */\nexport function serializeDocument(\n doc: Record<string, unknown>,\n options?: Pick<RepoSyncConfig, \"exclude\" | \"columnMap\">,\n): Record<string, unknown> {\n const exclude = new Set(options?.exclude);\n const columnMap = options?.columnMap ?? {};\n\n // First flatten the document\n const flat: Record<string, unknown> = {};\n flattenObject(doc, \"\", flat);\n\n // Then apply excludes and column renames\n const result: Record<string, unknown> = {};\n for (const [flatKey, value] of Object.entries(flat)) {\n if (exclude.has(flatKey)) continue;\n // Also check top-level prefix for excludes (e.g. exclude \"address\" removes all address_* cols)\n const topLevel = flatKey.split(\"__\")[0]!;\n if (topLevel !== flatKey && exclude.has(topLevel)) continue;\n const column =\n columnMap[flatKey] ??\n (flatKey.includes(\"__\")\n ? columnMap[flatKey.split(\"__\").pop()!]\n : undefined) ??\n flatKey;\n result[column] = value;\n }\n\n return result;\n}\n","/**\n * Sync Admin — optional HTTP endpoint for inspecting and managing the\n * Firestore → SQL sync pipeline.\n *\n * Features (gated by `featuresFlag`):\n * - **healthCheck** — compare expected Zod-derived columns vs actual SQL columns\n * - **manualSync** — force re-sync all documents in a Firestore collection\n *\n * @example\n * ```typescript\n * const sync = createFirestoreSync(repos, {\n * // …deps, adapter, etc.\n * admin: {\n * auth: { type: \"basic\", username: \"admin\", password: \"secret\" },\n * featuresFlag: { healthCheck: true, manualSync: true },\n * },\n * });\n *\n * export const adminsync = onRequest(sync.adminHandler!);\n * ```\n */\n\nimport { z } from \"zod\";\nimport type { AnyReq, AnyRes, RouteParams } from \"../servers/admin/router\";\nimport { MiniRouter } from \"../servers/admin/router\";\nimport { isAuthExtension } from \"../servers/auth\";\nimport { getLinkBase } from \"../servers/utils/link-base\";\nimport type { SyncQueue } from \"./queue\";\nimport { zodSchemaToColumns } from \"./schema-mapper\";\nimport { serializeDocument } from \"./serializer\";\nimport type {\n adminsyncConfig,\n PubSubClientDep,\n RepoSyncConfig,\n SqlAdapter,\n SyncEvent,\n} from \"./types\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype Req = AnyReq & { params: RouteParams };\n\ninterface RepoInfo {\n name: string;\n schema: z.ZodObject<any> | null;\n documentKey: string;\n tableName: string;\n isGroup: boolean;\n repoCfg: RepoSyncConfig<string> | undefined;\n repo: any;\n}\n\n// ---------------------------------------------------------------------------\n// HTML helpers\n// ---------------------------------------------------------------------------\n\nfunction page(title: string, linkBase: string, body: string): string {\n return `<!DOCTYPE html>\n<html lang=\"en\"><head>\n<meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>${title} — Sync Admin</title>\n<style>\n *{box-sizing:border-box;margin:0;padding:0}\n body{font-family:system-ui,-apple-system,sans-serif;background:#f5f5f5;color:#1a1a1a;padding:2rem}\n a{color:#0969da;text-decoration:none}a:hover{text-decoration:underline}\n h1{margin-bottom:1rem}h2{margin:1.5rem 0 .75rem}\n table{border-collapse:collapse;width:100%;margin-bottom:1rem}\n th,td{text-align:left;padding:.5rem .75rem;border:1px solid #d0d7de}\n th{background:#f6f8fa;font-weight:600}\n tr:nth-child(even){background:#fafbfc}\n .badge{display:inline-block;padding:.15rem .5rem;border-radius:1rem;font-size:.8rem;font-weight:600}\n .badge-ok{background:#dafbe1;color:#1a7f37}\n .badge-warn{background:#fff8c5;color:#9a6700}\n .badge-err{background:#ffebe9;color:#cf222e}\n .btn{display:inline-block;padding:.4rem 1rem;border:1px solid #d0d7de;border-radius:.375rem;\n background:#fff;cursor:pointer;font-size:.85rem;text-decoration:none;color:#1a1a1a}\n .btn:hover{background:#f3f4f6}.btn-primary{background:#0969da;color:#fff;border-color:#0969da}\n .btn-primary:hover{background:#0860ca}\n nav{margin-bottom:1.5rem}nav a{margin-right:1rem}\n .card{background:#fff;border:1px solid #d0d7de;border-radius:.5rem;padding:1.25rem;margin-bottom:1rem}\n pre{background:#f6f8fa;padding:1rem;border-radius:.375rem;overflow-x:auto;font-size:.85rem}\n .muted{color:#656d76;font-size:.85rem}\n</style>\n</head><body>\n<nav><a href=\"${linkBase}/\">← Dashboard</a></nav>\n<h1>${title}</h1>\n${body}\n</body></html>`;\n}\n\nfunction sendHtml(res: AnyRes, html: string, status = 200): void {\n res.status(status).set(\"Content-Type\", \"text/html; charset=utf-8\").send(html);\n}\n\nfunction sendJson(res: AnyRes, data: unknown, status = 200): void {\n res\n .status(status)\n .set(\"Content-Type\", \"application/json\")\n .send(JSON.stringify(data, null, 2));\n}\n\nfunction isJsonRequest(req: AnyReq): boolean {\n const accept = (req.headers?.[\"accept\"] ?? \"\") as string;\n return accept.includes(\"application/json\");\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create the sync admin HTTP handler.\n *\n * @param repoMapping - The configured repository mapping\n * @param adapter - The SQL adapter (e.g. BigQueryAdapter)\n * @param queues - Live queue map from the worker\n * @param handleMessage - Direct SyncEvent processor from the worker\n * @param config - Admin-specific config (auth, basePath, features)\n * @param repoConfigs - Per-repo sync config (tableName, exclude, columnMap…)\n * @param pubsub - PubSub client (needed for configCheck)\n * @param topicPrefix - PubSub topic prefix (needed for configCheck)\n */\nexport function createadminsyncServer(\n repoMapping: Record<string, any>,\n adapter: SqlAdapter,\n queues: Map<string, SyncQueue>,\n handleMessage: (event: SyncEvent) => Promise<void>,\n config: adminsyncConfig,\n repoConfigs: Record<string, RepoSyncConfig<string> | undefined>,\n pubsub?: PubSubClientDep,\n topicPrefix?: string,\n): (req: any, res: any) => Promise<void> {\n const basePath = (config.basePath ?? \"/\").replace(/\\/$/, \"\") || \"\";\n const features = config.featuresFlag ?? {};\n\n // Pre-compute repo info\n const repoInfos: RepoInfo[] = [];\n for (const [name, repo] of Object.entries(repoMapping)) {\n const repoCfg = repoConfigs[name];\n repoInfos.push({\n name,\n schema: (repo as any).schema ?? null,\n documentKey:\n (repo as any)._systemKeys?.[0] ?? (repo as any).documentKey ?? \"docId\",\n tableName: repoCfg?.tableName ?? name,\n isGroup: !!(repo as any)._isGroup,\n repoCfg,\n repo,\n });\n }\n\n const router = new MiniRouter();\n\n // -- Auth middleware -----------------------------------------------------\n if (config.auth) {\n if (isAuthExtension(config.auth)) {\n // Mount auxiliary routes (login page, session, logout) BEFORE pushing\n // the protected middleware so they remain publicly reachable. Use the\n // same convention as `servers.admin()` / `servers.crud()`.\n const ext = config.auth;\n for (const route of ext.routes) {\n const mountPath = `${basePath}${route.path}`;\n if (route.method === \"GET\") router.get(mountPath, route.handler);\n else router.post(mountPath, route.handler);\n }\n router.use(ext.middleware);\n } else if (typeof config.auth === \"function\") {\n router.use(config.auth as any);\n } else {\n const realm = config.auth.realm ?? \"Sync Admin\";\n const expected =\n \"Basic \" +\n Buffer.from(`${config.auth.username}:${config.auth.password}`).toString(\n \"base64\",\n );\n router.use((req, res, next) => {\n const authorization = (req as any).headers?.[\"authorization\"] ?? \"\";\n if (authorization !== expected) {\n res\n .status(401)\n .set(\"WWW-Authenticate\", `Basic realm=\"${realm}\"`)\n .set(\"Content-Type\", \"text/plain\")\n .send(\"Unauthorized\");\n return;\n }\n next();\n });\n }\n }\n\n // -- Dashboard ----------------------------------------------------------\n router.get(`${basePath}/`, (req, res) => {\n const lb = getLinkBase(req, basePath);\n const rows = repoInfos\n .map((r) => {\n const links: string[] = [];\n if (features.healthCheck)\n links.push(`<a class=\"btn\" href=\"${lb}/${r.name}/health\">Health</a>`);\n if (features.manualSync)\n links.push(\n `<a class=\"btn btn-primary\" href=\"${lb}/${r.name}/force-sync\">Force Sync</a>`,\n );\n return `<tr>\n <td><strong>${r.name}</strong></td>\n <td>${r.tableName}</td>\n <td>${r.isGroup ? '<span class=\"badge badge-warn\">group</span>' : '<span class=\"badge badge-ok\">collection</span>'}</td>\n <td>${r.schema ? \"✓\" : \"✗\"}</td>\n <td>${links.join(\" \")}</td>\n </tr>`;\n })\n .join(\"\\n\");\n\n const configCheckLink = features.configCheck\n ? `<p style=\"margin-top:.5rem\"><a class=\"btn\" href=\"${lb}/config-check\">⚙ Config Check</a></p>`\n : \"\";\n\n const html = page(\n \"Sync Dashboard\",\n lb,\n `<div class=\"card\">\n <table>\n <thead><tr><th>Repository</th><th>Table</th><th>Type</th><th>Schema</th><th>Actions</th></tr></thead>\n <tbody>${rows}</tbody>\n </table>\n ${configCheckLink}\n </div>`,\n );\n sendHtml(res, html);\n });\n router.get(`${basePath}`, (req, res) => {\n const lb = getLinkBase(req, basePath);\n res.status(302).set(\"Location\", `${lb}/`).send(\"\");\n });\n\n // -- Health Check -------------------------------------------------------\n if (features.healthCheck) {\n router.get(`${basePath}/:repoName/health`, async (req: Req, res) => {\n const lb = getLinkBase(req, basePath);\n const info = repoInfos.find((r) => r.name === req.params.repoName);\n if (!info) {\n sendHtml(\n res,\n page(\"Not Found\", lb, `<p>Unknown repo: ${req.params.repoName}</p>`),\n 404,\n );\n return;\n }\n if (!info.schema) {\n sendHtml(\n res,\n page(\n \"Health Check\",\n lb,\n `<p class=\"badge badge-warn\">No Zod schema attached to \"${info.name}\"</p>`,\n ),\n );\n return;\n }\n\n const expectedCols = zodSchemaToColumns(info.schema, adapter.dialect, {\n primaryKey: info.documentKey,\n exclude: info.repoCfg?.exclude,\n columnMap: info.repoCfg?.columnMap as\n | Record<string, string>\n | undefined,\n });\n\n let actualCols: string[] = [];\n let tableExists = false;\n let error: string | null = null;\n try {\n tableExists = await adapter.tableExists(info.tableName);\n if (tableExists) {\n actualCols = await adapter.getTableColumns(info.tableName);\n }\n } catch (e: any) {\n error = e?.message ?? String(e);\n }\n\n const actualSet = new Set(actualCols);\n const expectedSet = new Set(expectedCols.map((c) => c.name));\n\n const missing = expectedCols.filter((c) => !actualSet.has(c.name));\n const extra = actualCols.filter((c) => !expectedSet.has(c));\n const matched = expectedCols.filter((c) => actualSet.has(c.name));\n\n const isHealthy = tableExists && missing.length === 0 && !error;\n\n if (isJsonRequest(req)) {\n sendJson(res, {\n repo: info.name,\n table: info.tableName,\n tableExists,\n healthy: isHealthy,\n error,\n columns: {\n expected: expectedCols.map((c) => ({\n name: c.name,\n type: c.sqlType,\n nullable: c.nullable,\n isPrimaryKey: c.isPrimaryKey,\n })),\n actual: actualCols,\n matched: matched.map((c) => c.name),\n missing: missing.map((c) => ({\n name: c.name,\n type: c.sqlType,\n })),\n extra,\n },\n });\n return;\n }\n\n const statusBadge = isHealthy\n ? '<span class=\"badge badge-ok\">Healthy</span>'\n : '<span class=\"badge badge-err\">Unhealthy</span>';\n\n const colRows = expectedCols\n .map((c) => {\n const status = actualSet.has(c.name)\n ? '<span class=\"badge badge-ok\">OK</span>'\n : '<span class=\"badge badge-err\">MISSING</span>';\n return `<tr><td>${c.name}</td><td>${c.sqlType}</td><td>${c.nullable ? \"Yes\" : \"No\"}</td><td>${c.isPrimaryKey ? \"✓\" : \"\"}</td><td>${status}</td></tr>`;\n })\n .join(\"\\n\");\n\n const extraRows = extra\n .map(\n (c) =>\n `<tr><td>${c}</td><td colspan=\"3\" class=\"muted\">not in schema</td><td><span class=\"badge badge-warn\">EXTRA</span></td></tr>`,\n )\n .join(\"\\n\");\n\n const html = page(\n `Health: ${info.name}`,\n lb,\n `<div class=\"card\">\n <p>Table: <code>${info.tableName}</code> ${!tableExists ? '<span class=\"badge badge-err\">NOT FOUND</span>' : statusBadge}</p>\n ${error ? `<p class=\"badge badge-err\">Error: ${error}</p>` : \"\"}\n <h2>Columns</h2>\n <table>\n <thead><tr><th>Column</th><th>SQL Type</th><th>Nullable</th><th>PK</th><th>Status</th></tr></thead>\n <tbody>${colRows}${extraRows}</tbody>\n </table>\n </div>`,\n );\n sendHtml(res, html);\n });\n }\n\n // -- Force Sync ---------------------------------------------------------\n if (features.manualSync) {\n // GET — confirmation page\n router.get(`${basePath}/:repoName/force-sync`, (req: Req, res) => {\n const lb = getLinkBase(req, basePath);\n const info = repoInfos.find((r) => r.name === req.params.repoName);\n if (!info) {\n sendHtml(\n res,\n page(\"Not Found\", lb, `<p>Unknown repo: ${req.params.repoName}</p>`),\n 404,\n );\n return;\n }\n\n const html = page(\n `Force Sync: ${info.name}`,\n lb,\n `<div class=\"card\">\n <p>This will read <strong>all</strong> documents from the <code>${info.name}</code> Firestore collection\n and upsert them into the <code>${info.tableName}</code> SQL table.</p>\n <p class=\"muted\" style=\"margin:.75rem 0\">This may take a while for large collections.</p>\n <form method=\"POST\" action=\"${lb}/${info.name}/force-sync\">\n <button type=\"submit\" class=\"btn btn-primary\">Start Force Sync</button>\n </form>\n </div>`,\n );\n sendHtml(res, html);\n });\n\n // POST — execute\n router.post(`${basePath}/:repoName/force-sync`, async (req: Req, res) => {\n const lb = getLinkBase(req, basePath);\n const info = repoInfos.find((r) => r.name === req.params.repoName);\n if (!info) {\n sendJson(res, { error: `Unknown repo: ${req.params.repoName}` }, 404);\n return;\n }\n\n // Use the repository's collectionGroup or collection query\n const collRef = info.repo.ref;\n if (!collRef) {\n sendJson(\n res,\n { error: `No collection reference for \"${info.name}\"` },\n 400,\n );\n return;\n }\n\n let synced = 0;\n let errors = 0;\n const errorSamples: string[] = [];\n const batchSize = 500;\n const query = collRef.limit(batchSize);\n let lastDoc: any = null;\n\n try {\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const paginatedQuery = lastDoc ? query.startAfter(lastDoc) : query;\n const snapshot = await paginatedQuery.get();\n if (snapshot.empty) break;\n\n for (const doc of snapshot.docs) {\n const data = doc.data() as Record<string, unknown>;\n const docId = String(data[info.documentKey] ?? doc.id);\n const serialized = serializeDocument(data, {\n exclude: info.repoCfg?.exclude,\n columnMap: info.repoCfg?.columnMap,\n });\n\n try {\n await handleMessage({\n operation: \"UPSERT\",\n repoName: info.name,\n docId,\n data: serialized,\n timestamp: new Date().toISOString(),\n });\n synced++;\n } catch (e: any) {\n errors++;\n const msg = e?.message ?? String(e);\n console.error(\n `[ForceSync:${info.name}] doc=${docId} failed:`,\n e,\n );\n if (errorSamples.length < 5) errorSamples.push(`${docId}: ${msg}`);\n }\n }\n\n lastDoc = snapshot.docs[snapshot.docs.length - 1];\n if (snapshot.docs.length < batchSize) break;\n }\n\n // Flush the queue for this repo\n const queue = queues.get(info.name);\n if (queue) await queue.flush();\n } catch (e: any) {\n if (isJsonRequest(req)) {\n sendJson(\n res,\n { error: e?.message ?? String(e), synced, errors },\n 500,\n );\n return;\n }\n sendHtml(\n res,\n page(\n `Force Sync: ${info.name}`,\n lb,\n `<div class=\"card\">\n <p class=\"badge badge-err\">Error: ${e?.message ?? String(e)}</p>\n <p>Synced ${synced} docs before failure (${errors} errors).</p>\n </div>`,\n ),\n 500,\n );\n return;\n }\n\n if (isJsonRequest(req)) {\n sendJson(res, {\n repo: info.name,\n table: info.tableName,\n synced,\n errors,\n ...(errorSamples.length > 0 && { errorSamples }),\n });\n return;\n }\n\n const errorBlock =\n errorSamples.length > 0\n ? `<details style=\"margin-top:1rem\"><summary>First ${errorSamples.length} error(s)</summary>\n <pre style=\"white-space:pre-wrap\">${errorSamples\n .map((s) => s.replace(/[<>&]/g, (c) => `&#${c.charCodeAt(0)};`))\n .join(\"\\n\\n\")}</pre></details>`\n : \"\";\n\n const html = page(\n `Force Sync: ${info.name}`,\n lb,\n `<div class=\"card\">\n <p class=\"badge ${errors > 0 ? \"badge-warn\" : \"badge-ok\"}\">${errors > 0 ? \"Completed with errors\" : \"Complete\"}</p>\n <p>Synced <strong>${synced}</strong> documents to <code>${info.tableName}</code>.</p>\n ${errors > 0 ? `<p class=\"badge badge-warn\">${errors} error(s)</p>` : \"\"}\n ${errorBlock}\n </div>`,\n );\n sendHtml(res, html);\n });\n }\n\n // -- Config Check -------------------------------------------------------\n if (features.configCheck) {\n router.get(`${basePath}/config-check`, async (req, res) => {\n const lb = getLinkBase(req, basePath);\n const project =\n process.env[\"GCLOUD_PROJECT\"] ??\n process.env[\"GOOGLE_CLOUD_PROJECT\"] ??\n process.env[\"GCP_PROJECT\"] ??\n \"unknown\";\n const consoleBase = `https://console.cloud.google.com`;\n const tPrefix = topicPrefix ?? \"firestore-sync\";\n\n interface CheckResult {\n name: string;\n category: \"bigquery\" | \"pubsub\" | \"firestore\";\n status: \"ok\" | \"error\" | \"warn\";\n message: string;\n fix?: { gcloud?: string; console?: string; hint?: string };\n }\n\n const checks: CheckResult[] = [];\n\n // --- BigQuery checks ---\n // 1. General BQ connectivity (try listing tables via a simple exists check)\n try {\n await adapter.tableExists(\"__nonexistent_health_check__\");\n checks.push({\n name: \"BigQuery API\",\n category: \"bigquery\",\n status: \"ok\",\n message: \"BigQuery API is reachable\",\n });\n } catch (e: any) {\n const msg = e?.message ?? String(e);\n const msgLower = msg.toLowerCase();\n const isApiDisabled =\n msgLower.includes(\"disabled\") ||\n msgLower.includes(\"has not been used\") ||\n msgLower.includes(\"accessnotconfigured\");\n const isPermission =\n msgLower.includes(\"permission\") ||\n msg.includes(\"403\") ||\n msgLower.includes(\"access denied\");\n const isProjectNotFound =\n msgLower.includes(\"project\") && msgLower.includes(\"not found\");\n const isNotFound =\n msgLower.includes(\"not found\") || msg.includes(\"404\");\n\n if (isApiDisabled) {\n checks.push({\n name: \"BigQuery API\",\n category: \"bigquery\",\n status: \"error\",\n message: \"BigQuery API is not enabled\",\n fix: {\n gcloud: `gcloud services enable bigquery.googleapis.com --project=${project}`,\n console: `${consoleBase}/apis/library/bigquery.googleapis.com?project=${project}`,\n },\n });\n } else if (isProjectNotFound) {\n checks.push({\n name: \"BigQuery Project\",\n category: \"bigquery\",\n status: \"error\",\n message: msg,\n fix: {\n hint:\n \"The GCP project does not exist or the credentials don't have access to it. \" +\n \"In the Firebase emulator, GCLOUD_PROJECT may override the configured projectId. \" +\n \"Ensure you pass the correct projectId to the BigQuery constructor and have valid credentials.\",\n console: `${consoleBase}/home/dashboard`,\n },\n });\n } else if (isPermission) {\n checks.push({\n name: \"BigQuery API\",\n category: \"bigquery\",\n status: \"error\",\n message: `Permission denied: ${msg}`,\n fix: {\n hint: \"Grant the service account BigQuery Data Editor + BigQuery Job User roles\",\n gcloud: [\n `SA=$(gcloud run services describe YOUR_SERVICE --region=YOUR_REGION --format=\"value(spec.template.spec.serviceAccountName)\" --project=${project})`,\n `gcloud projects add-iam-policy-binding ${project} --member=\"serviceAccount:$SA\" --role=\"roles/bigquery.dataEditor\"`,\n `gcloud projects add-iam-policy-binding ${project} --member=\"serviceAccount:$SA\" --role=\"roles/bigquery.jobUser\"`,\n ].join(\"\\n\"),\n console: `${consoleBase}/iam-admin/iam?project=${project}`,\n },\n });\n } else if (isNotFound) {\n checks.push({\n name: \"BigQuery Dataset\",\n category: \"bigquery\",\n status: \"error\",\n message: `Dataset not found: ${msg}`,\n fix: {\n hint: \"Create the dataset first\",\n gcloud: `bq mk --dataset ${project}:YOUR_DATASET_ID`,\n console: `${consoleBase}/bigquery?project=${project}`,\n },\n });\n } else {\n checks.push({\n name: \"BigQuery API\",\n category: \"bigquery\",\n status: \"ok\",\n message:\n \"BigQuery API is reachable (table lookup returned expected error)\",\n });\n }\n }\n\n // 2. Per-repo table existence\n for (const info of repoInfos) {\n try {\n const exists = await adapter.tableExists(info.tableName);\n checks.push({\n name: `Table: ${info.tableName}`,\n category: \"bigquery\",\n status: exists ? \"ok\" : \"warn\",\n message: exists\n ? `Table \\`${info.tableName}\\` exists`\n : `Table \\`${info.tableName}\\` does not exist yet`,\n ...(!exists && {\n fix: {\n hint: \"Table will be auto-created on first sync if autoMigrate is enabled. Or create it manually.\",\n },\n }),\n });\n } catch (e: any) {\n checks.push({\n name: `Table: ${info.tableName}`,\n category: \"bigquery\",\n status: \"error\",\n message: e?.message ?? String(e),\n });\n }\n }\n\n // --- Pub/Sub checks ---\n if (pubsub) {\n for (const info of repoInfos) {\n const topicName = `${tPrefix}-${info.name}`;\n try {\n // Try to get topic metadata — succeeds if it exists\n const topic = (pubsub as any).topic(topicName);\n if (typeof topic.exists === \"function\") {\n const [exists] = await topic.exists();\n checks.push({\n name: `Topic: ${topicName}`,\n category: \"pubsub\",\n status: exists ? \"ok\" : \"error\",\n message: exists\n ? `Topic \\`${topicName}\\` exists`\n : `Topic \\`${topicName}\\` does not exist`,\n ...(!exists && {\n fix: {\n gcloud: `gcloud pubsub topics create ${topicName} --project=${project}`,\n console: `${consoleBase}/cloudpubsub/topic/list?project=${project}`,\n },\n }),\n });\n } else {\n // Minimal PubSub client — can't check existence, try publishing a no-op\n checks.push({\n name: `Topic: ${topicName}`,\n category: \"pubsub\",\n status: \"warn\",\n message:\n \"Cannot verify topic existence (PubSub client doesn't expose .exists())\",\n fix: {\n gcloud: `gcloud pubsub topics create ${topicName} --project=${project}`,\n console: `${consoleBase}/cloudpubsub/topic/list?project=${project}`,\n hint: \"Ensure the topic exists. It is auto-created by the Firebase emulator but must exist in production.\",\n },\n });\n }\n } catch (e: any) {\n const msg = e?.message ?? String(e);\n const isApiDisabled =\n msg.includes(\"disabled\") || msg.includes(\"has not been used\");\n checks.push({\n name: isApiDisabled ? \"Pub/Sub API\" : `Topic: ${topicName}`,\n category: \"pubsub\",\n status: \"error\",\n message: isApiDisabled ? \"Pub/Sub API is not enabled\" : msg,\n fix: isApiDisabled\n ? {\n gcloud: `gcloud services enable pubsub.googleapis.com --project=${project}`,\n console: `${consoleBase}/apis/library/pubsub.googleapis.com?project=${project}`,\n }\n : {\n gcloud: `gcloud pubsub topics create ${topicName} --project=${project}`,\n console: `${consoleBase}/cloudpubsub/topic/list?project=${project}`,\n },\n });\n // If the API itself is disabled, skip remaining topic checks\n if (isApiDisabled) break;\n }\n }\n } else {\n checks.push({\n name: \"Pub/Sub Client\",\n category: \"pubsub\",\n status: \"warn\",\n message: \"PubSub client not available for config check\",\n });\n }\n\n // --- JSON response ---\n if (isJsonRequest(req)) {\n const allOk = checks.every((c) => c.status === \"ok\");\n sendJson(res, { project, healthy: allOk, checks });\n return;\n }\n\n // --- HTML response ---\n const statusIcon = (s: string) =>\n s === \"ok\"\n ? '<span class=\"badge badge-ok\">OK</span>'\n : s === \"warn\"\n ? '<span class=\"badge badge-warn\">WARN</span>'\n : '<span class=\"badge badge-err\">ERROR</span>';\n\n const grouped = {\n bigquery: checks.filter((c) => c.category === \"bigquery\"),\n pubsub: checks.filter((c) => c.category === \"pubsub\"),\n firestore: checks.filter((c) => c.category === \"firestore\"),\n };\n\n const renderSection = (title: string, items: CheckResult[]) => {\n if (items.length === 0) return \"\";\n const rows = items\n .map((c) => {\n let fixHtml = \"\";\n if (c.fix) {\n const parts: string[] = [];\n if (c.fix.hint) parts.push(`<p class=\"muted\">${c.fix.hint}</p>`);\n if (c.fix.gcloud) parts.push(`<pre>$ ${c.fix.gcloud}</pre>`);\n if (c.fix.console)\n parts.push(\n `<p><a href=\"${c.fix.console}\" target=\"_blank\">Open GCP Console →</a></p>`,\n );\n fixHtml = `<div style=\"margin-top:.5rem\">${parts.join(\"\")}</div>`;\n }\n return `<tr>\n <td>${statusIcon(c.status)}</td>\n <td><strong>${c.name}</strong><br><span class=\"muted\">${c.message}</span>${fixHtml}</td>\n </tr>`;\n })\n .join(\"\\n\");\n return `<h2>${title}</h2>\n <table><thead><tr><th style=\"width:80px\">Status</th><th>Check</th></tr></thead>\n <tbody>${rows}</tbody></table>`;\n };\n\n const allOk = checks.every((c) => c.status === \"ok\");\n const overallBadge = allOk\n ? '<span class=\"badge badge-ok\">All checks passed</span>'\n : '<span class=\"badge badge-warn\">Some issues found</span>';\n\n const html = page(\n \"Config Check\",\n lb,\n `<div class=\"card\">\n <p>Project: <code>${project}</code> ${overallBadge}</p>\n ${renderSection(\"BigQuery\", grouped.bigquery)}\n ${renderSection(\"Pub/Sub\", grouped.pubsub)}\n ${renderSection(\"Firestore\", grouped.firestore)}\n </div>`,\n );\n sendHtml(res, html);\n });\n }\n\n // -- Request handler ----------------------------------------------------\n return async (req: any, res: any): Promise<void> => {\n await router.handle(req, res);\n };\n}\n","/**\n * DDL generator — produces CREATE TABLE / ALTER TABLE statements from\n * SqlColumn definitions and a SqlDialect.\n *\n * `generateDDL()` is the public entry point: it walks a repository mapping,\n * converts each repo's Zod schema to columns, and returns the full DDL\n * as a single string.\n */\n\nimport { z } from \"zod\";\nimport { zodSchemaToColumns } from \"./schema-mapper\";\nimport type {\n GenerateDDLConfig,\n RepoSyncConfig,\n SqlColumn,\n SqlDialect,\n SqlTableDef,\n} from \"./types\";\n\n// ---------------------------------------------------------------------------\n// Low-level DDL helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Generate a CREATE TABLE statement from a table definition.\n * Uses the dialect for identifier quoting and type mapping.\n *\n * NOTE: This produces preview/export DDL with unqualified table names.\n * For actual execution, use `adapter.createTable()` which handles\n * dataset/schema qualification internally.\n */\nexport function createTableDDL(\n dialect: SqlDialect,\n table: SqlTableDef,\n): string {\n const cols = table.columns\n .map((c) => {\n const notNull = c.isPrimaryKey ? \" NOT NULL\" : \"\";\n return ` ${dialect.quoteIdentifier(c.name)} ${c.sqlType}${notNull}`;\n })\n .join(\",\\n\");\n\n return `CREATE TABLE IF NOT EXISTS ${dialect.quoteIdentifier(table.tableName)} (\\n${cols}\\n);`;\n}\n\n/**\n * Generate ALTER TABLE ADD COLUMN statements for columns missing from an\n * existing table.\n *\n * NOTE: This produces preview/export DDL with unqualified table names.\n * For actual execution, use `adapter.addColumns()` which handles\n * dataset/schema qualification internally.\n */\nexport function addColumnsDDL(\n dialect: SqlDialect,\n tableName: string,\n columns: SqlColumn[],\n): string {\n return columns\n .map(\n (c) =>\n `ALTER TABLE ${dialect.quoteIdentifier(tableName)} ADD COLUMN ${dialect.quoteIdentifier(c.name)} ${c.sqlType};`,\n )\n .join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// High-level DDL generation\n// ---------------------------------------------------------------------------\n\n/**\n * Walk a full repository mapping and produce DDL for every repo that has a\n * Zod schema attached.\n *\n * @param repoMapping - Object whose values expose `.schema` (ZodObject)\n * @param dialect - Target SQL dialect\n * @param config - Optional per-repo overrides (table name, exclusions…)\n * @returns Complete DDL string (one CREATE TABLE per repo, separated by newlines)\n */\nexport function generateDDL<M extends Record<string, any>>(\n repoMapping: M,\n dialect: SqlDialect,\n config?: GenerateDDLConfig<NoInfer<M>>,\n): string {\n const statements: string[] = [];\n\n for (const [repoName, repo] of Object.entries(repoMapping) as [\n string,\n any,\n ][]) {\n const schema: z.ZodObject<any> | undefined =\n repo.schema ?? (repo as any)._schema ?? undefined;\n if (!schema) continue;\n\n const repoCfg = (\n config?.repos as Record<string, RepoSyncConfig<string>> | undefined\n )?.[repoName];\n const tableName = repoCfg?.tableName ?? repoName;\n\n // Detect documentKey from repo metadata\n const documentKey: string =\n (repo as any)._systemKeys?.[0] ?? (repo as any).documentKey ?? \"docId\";\n\n const columns = zodSchemaToColumns(schema, dialect, {\n primaryKey: documentKey,\n exclude: repoCfg?.exclude,\n columnMap: repoCfg?.columnMap as Record<string, string> | undefined,\n });\n\n const tableDef: SqlTableDef = { tableName, columns };\n statements.push(createTableDDL(dialect, tableDef));\n }\n\n return statements.join(\"\\n\\n\");\n}\n","/**\n * Migration manager — generates DDL and optionally auto-migrates SQL tables\n * to match the Zod schemas defined in a repository mapping.\n *\n * Migrations are **additive only**: new columns are added, existing columns\n * are never dropped or modified.\n */\n\nimport { z } from \"zod\";\nimport { zodSchemaToColumns } from \"./schema-mapper\";\nimport type {\n GenerateDDLConfig,\n RepoSyncConfig,\n SqlAdapter,\n SqlColumn,\n SqlTableDef,\n} from \"./types\";\n\nexport interface MigrateResult {\n /** Tables that were created from scratch */\n created: string[];\n /** Tables that had columns added */\n altered: string[];\n /** Tables that were already up-to-date */\n upToDate: string[];\n /** Tables skipped (no schema available) */\n skipped: string[];\n}\n\n/**\n * Auto-migrate all repos: create missing tables, add missing columns.\n *\n * @returns Summary of what was done per table.\n */\nexport async function autoMigrate<M extends Record<string, any>>(\n repoMapping: M,\n adapter: SqlAdapter,\n config?: GenerateDDLConfig<NoInfer<M>>,\n): Promise<MigrateResult> {\n const result: MigrateResult = {\n created: [],\n altered: [],\n upToDate: [],\n skipped: [],\n };\n\n for (const [repoName, repo] of Object.entries(repoMapping) as [\n string,\n any,\n ][]) {\n const schema: z.ZodObject<any> | undefined =\n (repo as any).schema ?? undefined;\n if (!schema) {\n result.skipped.push(repoName);\n continue;\n }\n\n const repoCfg = (\n config?.repos as Record<string, RepoSyncConfig<string>> | undefined\n )?.[repoName];\n const tableName = repoCfg?.tableName ?? repoName;\n const documentKey: string =\n (repo as any)._systemKeys?.[0] ?? (repo as any).documentKey ?? \"docId\";\n\n const columns = zodSchemaToColumns(schema, adapter.dialect, {\n primaryKey: documentKey,\n exclude: repoCfg?.exclude,\n columnMap: repoCfg?.columnMap as Record<string, string> | undefined,\n });\n\n const tableDef: SqlTableDef = { tableName, columns };\n const exists = await adapter.tableExists(tableName);\n\n if (!exists) {\n await adapter.createTable(tableDef);\n result.created.push(tableName);\n } else {\n const existingCols = new Set(await adapter.getTableColumns(tableName));\n const newCols: SqlColumn[] = columns.filter(\n (c) => !existingCols.has(c.name),\n );\n\n if (newCols.length > 0) {\n await adapter.addColumns(tableName, newCols);\n result.altered.push(tableName);\n } else {\n result.upToDate.push(tableName);\n }\n }\n }\n\n return result;\n}\n","/**\n * Per-repo in-memory batch buffer.\n *\n * Accumulates {@link SyncEvent}s and flushes them in batches to a\n * {@link SqlAdapter}. On flush failure the events are re-published\n * to PubSub for retry (if a PubSub re-publisher is provided).\n */\n\nimport { SYNC_VERSION_COLUMN } from \"./constants\";\nimport type { SqlAdapter, SyncEvent } from \"./types\";\n\nexport interface SyncQueueOptions {\n /** SQL adapter to flush data to */\n adapter: SqlAdapter;\n /** SQL table name */\n tableName: string;\n /** Primary key column name */\n primaryKey: string;\n /** Max rows per flush (default: 100) */\n batchSize?: number;\n /** Auto-flush interval in ms (default: 5_000). 0 = manual only. */\n flushIntervalMs?: number;\n /** Called on flush failure with the failed events. Typically re-publishes to PubSub. */\n onFlushError?: (events: SyncEvent[], error: unknown) => Promise<void>;\n}\n\n/**\n * In-memory buffer that batches sync events per-repo and flushes them\n * to a SQL adapter.\n */\nexport class SyncQueue {\n private buffer: SyncEvent[] = [];\n private flushing = false;\n private flushPromise: Promise<void> | null = null;\n private timer: ReturnType<typeof setInterval> | null = null;\n\n private readonly adapter: SqlAdapter;\n private readonly tableName: string;\n private readonly primaryKey: string;\n private readonly batchSize: number;\n private readonly onFlushError?: SyncQueueOptions[\"onFlushError\"];\n\n constructor(opts: SyncQueueOptions) {\n this.adapter = opts.adapter;\n this.tableName = opts.tableName;\n this.primaryKey = opts.primaryKey;\n this.batchSize = opts.batchSize ?? 100;\n this.onFlushError = opts.onFlushError;\n\n const interval = opts.flushIntervalMs ?? 5_000;\n if (interval > 0) {\n this.timer = setInterval(() => void this.flush(), interval);\n // Allow the Node process to exit even if the timer is running\n if (typeof this.timer === \"object\" && \"unref\" in this.timer) {\n this.timer.unref();\n }\n }\n }\n\n /** Number of events waiting in the buffer. */\n get size(): number {\n return this.buffer.length;\n }\n\n /** Push one or more events into the buffer. Triggers auto-flush if batchSize reached. */\n enqueue(...events: SyncEvent[]): void {\n this.buffer.push(...events);\n if (this.buffer.length >= this.batchSize) {\n void this.flush();\n }\n }\n\n /**\n * Flush all buffered events to the SQL adapter.\n * Upserts and deletes are batched separately.\n *\n * Concurrent callers (e.g. several PubSub messages handled in parallel\n * inside the same Cloud Function instance with `concurrency > 1`) all\n * await the same in-flight flush and then trigger a follow-up flush if\n * new events were enqueued in the meantime. This guarantees that every\n * caller's event is persisted before its `await flush()` resolves —\n * which is required for safe PubSub ack semantics.\n */\n async flush(): Promise<void> {\n // If another flush is in progress, wait for it to finish first.\n while (this.flushing && this.flushPromise) {\n await this.flushPromise;\n }\n if (this.buffer.length === 0) return;\n\n this.flushing = true;\n this.flushPromise = this._doFlush().finally(() => {\n this.flushing = false;\n this.flushPromise = null;\n });\n await this.flushPromise;\n }\n\n private async _doFlush(): Promise<void> {\n // Drain the buffer atomically\n const batch = this.buffer.splice(0, this.batchSize);\n\n try {\n const upsertsById = new Map<string, Record<string, unknown>>();\n const deleteIds: string[] = [];\n\n for (const evt of batch) {\n if (evt.operation === \"DELETE\") {\n deleteIds.push(evt.docId);\n // A delete supersedes any pending upsert in the same batch.\n upsertsById.delete(evt.docId);\n } else if (evt.data) {\n // Multiple updates to the same doc within a single batch would\n // make BigQuery MERGE error out (\"UPDATE/MERGE must match at\n // most one source row for each target row\"). Keep only the row\n // with the highest __sync_version per docId.\n const existing = upsertsById.get(evt.docId);\n if (!existing) {\n upsertsById.set(evt.docId, evt.data);\n } else {\n const a = Number(existing[SYNC_VERSION_COLUMN] ?? 0);\n const b = Number(evt.data[SYNC_VERSION_COLUMN] ?? 0);\n if (b >= a) upsertsById.set(evt.docId, evt.data);\n }\n }\n }\n\n const upserts = Array.from(upsertsById.values());\n\n if (upserts.length > 0) {\n await this.adapter.upsertRows(this.tableName, upserts, this.primaryKey);\n }\n if (deleteIds.length > 0) {\n await this.adapter.deleteRows(\n this.tableName,\n this.primaryKey,\n deleteIds,\n );\n }\n } catch (err) {\n if (this.onFlushError) {\n // If the error handler also fails, re-throw so the Cloud Function\n // does NOT ack the PubSub message — it will be retried automatically.\n await this.onFlushError(batch, err);\n } else {\n // Re-insert at the front so we retry next flush\n this.buffer.unshift(...batch);\n console.error(`[SyncQueue] Flush failed for ${this.tableName}:`, err);\n }\n }\n }\n\n /** Stop the auto-flush timer and flush remaining events. */\n async shutdown(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n await this.flush();\n }\n}\n","/**\n * Firestore Cloud Function triggers that publish {@link SyncEvent}s to\n * Google Cloud PubSub.\n *\n * Dependencies (`firebase-functions`, `@google-cloud/pubsub`) are injected\n * via the `deps` config property — no lazy loading or module resolution issues.\n *\n * Out-of-order delivery is handled at the application level: every event\n * carries a `version` (publish-time `Date.now()` in ms) which is propagated\n * to SQL as the `__sync_version` column. The worker's MERGE only updates a\n * row when the incoming `version` is strictly greater than the stored one —\n * so a stale event delivered after a newer one is silently skipped.\n *\n * @example\n * ```typescript\n * import { createSyncTriggers } from \"@lpdjs/firestore-repo-service/sync\";\n * import * as firestoreTriggers from \"firebase-functions/v2/firestore\";\n * import { PubSub } from \"@google-cloud/pubsub\";\n *\n * const triggers = createSyncTriggers(repos, {\n * deps: { firestoreTriggers, pubsub: new PubSub() },\n * topicPrefix: \"my-sync\",\n * repos: { users: { exclude: [\"password\"] } },\n * });\n *\n * export const { users_onCreate, users_onUpdate, users_onDelete } = triggers;\n * ```\n */\n\nimport { serializeDocument } from \"./serializer\";\nimport type { RepoSyncConfig, SyncEvent, SyncTriggersConfig } from \"./types\";\n\nconst DEFAULT_TOPIC_PREFIX = \"firestore-sync\";\n\n/**\n * Derive the Firestore document path pattern for a trigger.\n * Returns `null` when the collection path cannot be determined.\n */\nfunction buildDocumentPath(repoName: string, repo: any): string | null {\n const collectionPath: string | undefined =\n (repo as any).ref?.path ?? undefined;\n\n if (!collectionPath) {\n console.warn(\n `[SyncTriggers] Cannot determine collection path for \"${repoName}\". Skipping.`,\n );\n return null;\n }\n\n return `${collectionPath}/{docId}`;\n}\n\n/**\n * Create Firestore Cloud Functions (v2) triggers that publish\n * {@link SyncEvent}s to PubSub for each repository in `repoMapping`.\n *\n * For each non-group repository, three triggers are created:\n * - `{repoName}_onCreate` → publishes an `INSERT` event\n * - `{repoName}_onUpdate` → publishes an `UPSERT` event\n * - `{repoName}_onDelete` → publishes a `DELETE` event\n *\n * Each event carries a monotonic `version` (`Date.now()` in ms) used by\n * the worker to discard out-of-order PubSub deliveries.\n */\nexport function createSyncTriggers<M extends Record<string, any>>(\n repoMapping: M,\n config: SyncTriggersConfig<NoInfer<M>>,\n): Record<string, any> {\n const { onDocumentCreated, onDocumentUpdated, onDocumentDeleted } =\n config.deps.firestoreTriggers;\n const pubsub = config.deps.pubsub;\n\n const topicPrefix = config?.topicPrefix ?? DEFAULT_TOPIC_PREFIX;\n const triggers: Record<string, any> = {};\n\n // Cache topic clients so the publisher reuses the same batching state\n // for a given topic across invocations.\n const topicCache = new Map<string, any>();\n function getTopic(topicName: string): any {\n let t = topicCache.get(topicName);\n if (t) return t;\n t = (pubsub as any).topic(topicName);\n topicCache.set(topicName, t);\n return t;\n }\n\n async function publish(\n topicName: string,\n syncEvent: SyncEvent,\n ): Promise<void> {\n const topic = getTopic(topicName);\n await topic.publishMessage({ json: syncEvent });\n }\n\n for (const [repoName, repo] of Object.entries(repoMapping) as [\n string,\n any,\n ][]) {\n const repoCfg = (\n config?.repos as Record<string, RepoSyncConfig<string>> | undefined\n )?.[repoName];\n\n let documentPath: string | null;\n\n if ((repo as any)._isGroup) {\n if (!repoCfg?.triggerPath) {\n console.warn(\n `[SyncTriggers] Skipping collection-group repo \"${repoName}\". ` +\n \"Provide a triggerPath in the sync repos config for group collections.\",\n );\n continue;\n }\n documentPath = repoCfg.triggerPath;\n } else {\n documentPath = repoCfg?.triggerPath ?? buildDocumentPath(repoName, repo);\n }\n if (!documentPath) continue;\n\n const documentKey: string = (repo as any)._systemKeys?.[0] ?? \"docId\";\n const topicName = `${topicPrefix}-${repoName}`;\n\n triggers[`${repoName}_onCreate`] = onDocumentCreated(\n documentPath,\n async (event: any) => {\n const snap = event.data;\n if (!snap) return;\n\n const data = snap.data() as Record<string, unknown> | undefined;\n if (!data) return;\n\n const docId = String(data[documentKey] ?? snap.id);\n const serialized = serializeDocument(data, {\n exclude: repoCfg?.exclude,\n columnMap: repoCfg?.columnMap,\n });\n\n const syncEvent: SyncEvent = {\n operation: \"INSERT\",\n repoName,\n docId,\n data: serialized,\n timestamp: new Date().toISOString(),\n version: Date.now(),\n };\n\n await publish(topicName, syncEvent);\n },\n );\n\n triggers[`${repoName}_onUpdate`] = onDocumentUpdated(\n documentPath,\n async (event: any) => {\n const snap = event.data?.after;\n if (!snap) return;\n\n const data = snap.data() as Record<string, unknown> | undefined;\n if (!data) return;\n\n const docId = String(data[documentKey] ?? snap.id);\n const serialized = serializeDocument(data, {\n exclude: repoCfg?.exclude,\n columnMap: repoCfg?.columnMap,\n });\n\n const syncEvent: SyncEvent = {\n operation: \"UPSERT\",\n repoName,\n docId,\n data: serialized,\n timestamp: new Date().toISOString(),\n version: Date.now(),\n };\n\n await publish(topicName, syncEvent);\n },\n );\n\n triggers[`${repoName}_onDelete`] = onDocumentDeleted(\n documentPath,\n async (event: any) => {\n const snap = event.data;\n if (!snap) return;\n\n const data = snap.data() as Record<string, unknown> | undefined;\n const docId = String(data?.[documentKey] ?? snap.id);\n\n const syncEvent: SyncEvent = {\n operation: \"DELETE\",\n repoName,\n docId,\n data: null,\n timestamp: new Date().toISOString(),\n version: Date.now(),\n };\n\n await publish(topicName, syncEvent);\n },\n );\n }\n\n return triggers;\n}\n","/**\n * BigQuery type-name utilities shared by the legacy `BigQueryAdapter` (DML\n * MERGE) and the new `BigQueryStorageAdapter` (Storage Write API).\n *\n * - {@link normalizeBigQueryType} canonicalises BigQuery type strings so that\n * `INTEGER`/`INT64`, `FLOAT`/`FLOAT64`, `BOOLEAN`/`BOOL`, etc. compare\n * equal during schema-drift detection.\n * - {@link isBigQueryTypeCompatible} returns whether a desired type can\n * safely keep an existing column of another type — only widenings that\n * BigQuery accepts via `ALTER COLUMN SET DATA TYPE` are allowed.\n */\n\n/**\n * Canonicalise a BigQuery type name returned by `getMetadata().schema`\n * (which may use legacy aliases like `INTEGER` or `FLOAT`) so that it can\n * be compared against the type produced by `BigQueryDialect.mapType()`.\n */\nexport function normalizeBigQueryType(type: string): string {\n const upper = type.toUpperCase();\n switch (upper) {\n case \"INTEGER\":\n return \"INT64\";\n case \"FLOAT\":\n return \"FLOAT64\";\n case \"BOOLEAN\":\n return \"BOOL\";\n default:\n return upper;\n }\n}\n\n/**\n * Whether `desired` is the same as, or a safe widening of, `existing`.\n * The widenings mirror what BigQuery allows via\n * `ALTER COLUMN x SET DATA TYPE …` — see\n * https://cloud.google.com/bigquery/docs/managing-table-schemas#change_column_types\n */\nexport function isBigQueryTypeCompatible(\n existing: string,\n desired: string,\n): boolean {\n const a = normalizeBigQueryType(existing);\n const b = normalizeBigQueryType(desired);\n if (a === b) return true;\n\n const widenings: Record<string, string[]> = {\n INT64: [\"NUMERIC\", \"BIGNUMERIC\", \"FLOAT64\"],\n NUMERIC: [\"BIGNUMERIC\", \"FLOAT64\"],\n DATE: [\"DATETIME\", \"TIMESTAMP\"],\n DATETIME: [\"TIMESTAMP\"],\n };\n return widenings[a]?.includes(b) ?? false;\n}\n","/**\n * PubSub worker — creates a Cloud Function that receives {@link SyncEvent}\n * messages from PubSub, routes them to per-repo {@link SyncQueue}s, and\n * flushes batches to the configured {@link SqlAdapter}.\n *\n * Dependencies (`firebase-functions`, `@google-cloud/pubsub`) are injected\n * via the `deps` config property.\n */\n\nimport { z } from \"zod\";\nimport { isBigQueryTypeCompatible } from \"./adapters/bigquery-types\";\nimport { SYNC_VERSION_COLUMN } from \"./constants\";\nimport { SyncQueue } from \"./queue\";\nimport { zodSchemaToColumns } from \"./schema-mapper\";\nimport type {\n RepoSyncConfig,\n SqlAdapter,\n SyncEvent,\n SyncWorkerConfig,\n} from \"./types\";\n\n// ---------------------------------------------------------------------------\n// Migration tracking\n// ---------------------------------------------------------------------------\n\n/** Set of repo names that have already been migrated in this process lifetime. */\nconst migratedRepos = new Set<string>();\n\n/**\n * Thrown by {@link ensureMigrated} when an existing column has a SQL type\n * that is not compatible with what the current Zod schema would produce.\n *\n * BigQuery (and Storage Write CDC in particular) cannot silently coerce\n * incompatible types. We fail fast so the user fixes the schema explicitly\n * (rename the column, recreate the table, or add a transform) instead of\n * losing events to an infinite DLQ loop.\n */\nexport class SchemaTypeMismatchError extends Error {\n constructor(\n readonly tableName: string,\n readonly column: string,\n readonly existingType: string,\n readonly desiredType: string,\n ) {\n super(\n `Schema drift detected on \\`${tableName}\\`: column \\`${column}\\` has ` +\n `type ${existingType} in BigQuery but the current Zod schema maps ` +\n `it to ${desiredType}. BigQuery cannot safely convert between these ` +\n `types — to resolve, either (a) keep the BigQuery type and add a ` +\n `transform in your repo to coerce values, (b) rename the field in ` +\n `your Zod schema (creates a new column), or (c) drop & recreate ` +\n `the table.`,\n );\n this.name = \"SchemaTypeMismatchError\";\n }\n}\n\nasync function ensureMigrated(\n repoName: string,\n adapter: SqlAdapter,\n schema: z.ZodObject<any>,\n tableName: string,\n primaryKey: string,\n exclude?: string[],\n columnMap?: Record<string, string>,\n): Promise<void> {\n if (migratedRepos.has(repoName)) return;\n\n const columns = zodSchemaToColumns(schema, adapter.dialect, {\n primaryKey,\n exclude,\n columnMap,\n });\n\n const exists = await adapter.tableExists(tableName);\n if (!exists) {\n await adapter.createTable({ tableName, columns });\n } else {\n // Prefer typed lookup when the adapter supports it: this lets us detect\n // type drift (e.g. number → string) and fail fast with an explicit\n // error, instead of every flush failing inside BigQuery with a cryptic\n // cast error and looping forever via the DLQ.\n if (adapter.getTableColumnsWithTypes) {\n const existing = await adapter.getTableColumnsWithTypes(tableName);\n const missing: typeof columns = [];\n for (const col of columns) {\n const existingType = existing.get(col.name);\n if (existingType === undefined) {\n missing.push(col);\n continue;\n }\n // Only meaningful for BigQuery dialects; other dialects can opt-in\n // by implementing getTableColumnsWithTypes with their own tokens.\n if (\n adapter.dialect.name === \"bigquery\" &&\n !isBigQueryTypeCompatible(existingType, col.sqlType)\n ) {\n throw new SchemaTypeMismatchError(\n tableName,\n col.name,\n existingType,\n col.sqlType,\n );\n }\n }\n if (missing.length > 0) {\n await adapter.addColumns(tableName, missing);\n await adapter.onSchemaChange?.(tableName);\n }\n } else {\n const existing = new Set(await adapter.getTableColumns(tableName));\n const newCols = columns.filter((c) => !existing.has(c.name));\n if (newCols.length > 0) {\n await adapter.addColumns(tableName, newCols);\n await adapter.onSchemaChange?.(tableName);\n }\n }\n }\n\n migratedRepos.add(repoName);\n}\n\n// ---------------------------------------------------------------------------\n// Worker factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a PubSub-triggered Cloud Function that syncs Firestore changes\n * to a SQL database.\n *\n * Returns an object with:\n * - `createHandler` — creates a Cloud Function for a PubSub topic\n * - `handleMessage` — process a SyncEvent directly (for testing)\n * - `queues` — internal SyncQueue map (for testing / manual flush)\n * - `shutdown()` — flush all queues and stop timers\n */\nexport function createSyncWorker<M extends Record<string, any>>(\n repoMapping: M,\n config: SyncWorkerConfig<NoInfer<M>>,\n) {\n const {\n deps,\n adapter,\n batchSize = 100,\n flushIntervalMs = 5_000,\n autoMigrate = false,\n topicPrefix = \"firestore-sync\",\n workerOptions,\n repos: repoConfigs = {} as Record<\n string,\n RepoSyncConfig<string> | undefined\n >,\n } = config;\n\n // Build per-repo queues lazily\n const queues = new Map<string, SyncQueue>();\n\n function getQueue(repoName: string, primaryKey: string): SyncQueue {\n let q = queues.get(repoName);\n if (q) return q;\n\n const repoCfg = repoConfigs[repoName];\n const tableName = repoCfg?.tableName ?? repoName;\n\n // On flush failure → log error + re-publish to PubSub dead-letter.\n // If the DLQ publish also fails, re-throw so the Cloud Function does NOT\n // ack the PubSub message and PubSub retries it automatically.\n const onFlushError = async (\n events: SyncEvent[],\n error: unknown,\n ): Promise<void> => {\n console.error(\n `[SyncWorker] Flush failed for \"${repoName}\" (${events.length} events):`,\n error,\n );\n const dlTopicName = `${topicPrefix}-${repoName}-dlq`;\n const dlTopic = deps.pubsub.topic(dlTopicName);\n const [exists] = await dlTopic.exists();\n if (!exists) {\n await dlTopic.create();\n console.info(`[SyncWorker] Created DLQ topic \"${dlTopicName}\"`);\n }\n for (const evt of events) {\n await dlTopic.publishMessage({ json: evt });\n }\n };\n\n q = new SyncQueue({\n adapter,\n tableName,\n primaryKey,\n batchSize,\n flushIntervalMs,\n onFlushError,\n });\n queues.set(repoName, q);\n return q;\n }\n\n // Message handler (works with or without Cloud Functions wrapper)\n async function handleMessage(syncEvent: SyncEvent): Promise<void> {\n const { repoName } = syncEvent;\n const repo = (repoMapping as Record<string, any>)[repoName];\n if (!repo) {\n console.warn(`[SyncWorker] Unknown repo \"${repoName}\", skipping event`);\n return;\n }\n\n const documentKey: string =\n (repo as any)._systemKeys?.[0] ?? (repo as any).documentKey ?? \"docId\";\n\n const repoCfg = repoConfigs[repoName];\n const columnMap = repoCfg?.columnMap as Record<string, string> | undefined;\n // The primaryKey for BigQuery must use the mapped column name (e.g. docId → user_id)\n const primaryKey = columnMap?.[documentKey] ?? documentKey;\n\n if (autoMigrate) {\n const schema: z.ZodObject<any> | undefined =\n (repo as any).schema ?? undefined;\n if (schema) {\n const tableName = repoCfg?.tableName ?? repoName;\n await ensureMigrated(\n repoName,\n adapter,\n schema,\n tableName,\n documentKey,\n repoCfg?.exclude,\n columnMap,\n );\n }\n }\n\n const queue = getQueue(repoName, primaryKey);\n\n // Stamp the row with the publish version so the SQL adapter can skip\n // stale (out-of-order) updates. Force-sync events without a version\n // fall back to the wall clock — still monotonic per-process.\n if (syncEvent.data) {\n syncEvent.data[SYNC_VERSION_COLUMN] = syncEvent.version ?? Date.now();\n }\n\n queue.enqueue(syncEvent);\n }\n\n // Cloud Function v2 PubSub handler (sync — deps are already available)\n function createHandler(topicName: string) {\n const handlerFn = async (event: any) => {\n const data: SyncEvent = event.data?.message?.json ?? event.data?.json;\n if (!data) {\n console.warn(\"[SyncWorker] Received empty PubSub message\");\n return;\n }\n await handleMessage(data);\n // Flush so data is persisted before the Cloud Function container shuts down.\n // SyncQueue.flush() coalesces concurrent callers so when `concurrency > 1`\n // every parallel handler awaits the same in-flight MERGE — guaranteeing\n // each PubSub message is only acked once its event reached BigQuery.\n // Force-sync (admin) handles its own flush after the batch loop.\n const q = queues.get(data.repoName);\n if (q) await q.flush();\n };\n\n if (workerOptions) {\n return deps.pubsubHandler.onMessagePublished(\n { topic: topicName, ...workerOptions },\n handlerFn,\n );\n }\n return deps.pubsubHandler.onMessagePublished(topicName, handlerFn);\n }\n\n return {\n /** Process a SyncEvent directly (for testing or custom PubSub integration). */\n handleMessage,\n /** Create a Cloud Function handler for a specific PubSub topic. */\n createHandler,\n /** Internal queue map (for testing). */\n queues,\n /** Flush all queues and stop timers. */\n async shutdown(): Promise<void> {\n const promises: Promise<void>[] = [];\n for (const q of queues.values()) {\n promises.push(q.shutdown());\n }\n await Promise.all(promises);\n },\n };\n}\n"]}
|