@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.
Files changed (37) hide show
  1. package/dist/{create-servers-E79G_UgN.d.cts → create-servers-BmgAjUFD.d.cts} +2 -2
  2. package/dist/{create-servers-DwLmIAUH.d.ts → create-servers-Dv0V1LB8.d.ts} +2 -2
  3. package/dist/index.cjs +30 -30
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +3 -3
  6. package/dist/index.d.ts +3 -3
  7. package/dist/index.js +30 -30
  8. package/dist/index.js.map +1 -1
  9. package/dist/{queue-DnTHUopY.d.cts → queue-CmBKfeqG.d.cts} +1 -1
  10. package/dist/{queue-Dgk4HFSS.d.ts → queue-DLrIuZ7L.d.ts} +1 -1
  11. package/dist/servers/index.cjs +40 -40
  12. package/dist/servers/index.cjs.map +1 -1
  13. package/dist/servers/index.d.cts +3 -3
  14. package/dist/servers/index.d.ts +3 -3
  15. package/dist/servers/index.js +40 -40
  16. package/dist/servers/index.js.map +1 -1
  17. package/dist/sync/bigquery-storage.cjs +8 -0
  18. package/dist/sync/bigquery-storage.cjs.map +1 -0
  19. package/dist/sync/bigquery-storage.d.cts +113 -0
  20. package/dist/sync/bigquery-storage.d.ts +113 -0
  21. package/dist/sync/bigquery-storage.js +8 -0
  22. package/dist/sync/bigquery-storage.js.map +1 -0
  23. package/dist/sync/bigquery.cjs +7 -7
  24. package/dist/sync/bigquery.cjs.map +1 -1
  25. package/dist/sync/bigquery.d.cts +6 -1
  26. package/dist/sync/bigquery.d.ts +6 -1
  27. package/dist/sync/bigquery.js +7 -7
  28. package/dist/sync/bigquery.js.map +1 -1
  29. package/dist/sync/index.cjs +24 -24
  30. package/dist/sync/index.cjs.map +1 -1
  31. package/dist/sync/index.d.cts +4 -4
  32. package/dist/sync/index.d.ts +4 -4
  33. package/dist/sync/index.js +24 -24
  34. package/dist/sync/index.js.map +1 -1
  35. package/dist/{types-CBwcflkG.d.ts → types-Bhy7MfCj.d.ts} +19 -0
  36. package/dist/{types-BW592RAZ.d.cts → types-_JSH59Iy.d.cts} +19 -0
  37. package/package.json +19 -1
@@ -1,7 +1,7 @@
1
- import { a as SqlAdapter, S as SyncEvent, e as adminsyncConfig, R as RepoSyncConfig, P as PubSubClientDep, b as SqlDialect, d as SqlColumn, c as SqlTableDef, G as GenerateDDLConfig, L as LogicalType, f as SyncTriggersConfig, g as SyncWorkerConfig } from '../types-BW592RAZ.cjs';
2
- export { A as AdminHttpsOptions, F as FirestoreSyncConfig, h as FirestoreTriggersDep, O as OrFactory, i as PubSubHandlerDep, j as SyncDeps, k as SyncOperation, l as SyncWorkerOptions, m as adminsyncBasicAuth, n as adminsyncFeaturesFlag } from '../types-BW592RAZ.cjs';
3
- import { S as SyncQueue } from '../queue-DnTHUopY.cjs';
4
- export { a as SyncQueueOptions } from '../queue-DnTHUopY.cjs';
1
+ import { a as SqlAdapter, S as SyncEvent, e as adminsyncConfig, R as RepoSyncConfig, P as PubSubClientDep, b as SqlDialect, d as SqlColumn, c as SqlTableDef, G as GenerateDDLConfig, L as LogicalType, f as SyncTriggersConfig, g as SyncWorkerConfig } from '../types-_JSH59Iy.cjs';
2
+ export { A as AdminHttpsOptions, F as FirestoreSyncConfig, h as FirestoreTriggersDep, O as OrFactory, i as PubSubHandlerDep, j as SyncDeps, k as SyncOperation, l as SyncWorkerOptions, m as adminsyncBasicAuth, n as adminsyncFeaturesFlag } from '../types-_JSH59Iy.cjs';
3
+ import { S as SyncQueue } from '../queue-CmBKfeqG.cjs';
4
+ export { a as SyncQueueOptions } from '../queue-CmBKfeqG.cjs';
5
5
  import { z } from 'zod';
6
6
  import * as firebase_functions_core from 'firebase-functions/core';
7
7
  import * as firebase_functions_pubsub from 'firebase-functions/pubsub';
@@ -1,7 +1,7 @@
1
- import { a as SqlAdapter, S as SyncEvent, e as adminsyncConfig, R as RepoSyncConfig, P as PubSubClientDep, b as SqlDialect, d as SqlColumn, c as SqlTableDef, G as GenerateDDLConfig, L as LogicalType, f as SyncTriggersConfig, g as SyncWorkerConfig } from '../types-CBwcflkG.js';
2
- export { A as AdminHttpsOptions, F as FirestoreSyncConfig, h as FirestoreTriggersDep, O as OrFactory, i as PubSubHandlerDep, j as SyncDeps, k as SyncOperation, l as SyncWorkerOptions, m as adminsyncBasicAuth, n as adminsyncFeaturesFlag } from '../types-CBwcflkG.js';
3
- import { S as SyncQueue } from '../queue-Dgk4HFSS.js';
4
- export { a as SyncQueueOptions } from '../queue-Dgk4HFSS.js';
1
+ import { a as SqlAdapter, S as SyncEvent, e as adminsyncConfig, R as RepoSyncConfig, P as PubSubClientDep, b as SqlDialect, d as SqlColumn, c as SqlTableDef, G as GenerateDDLConfig, L as LogicalType, f as SyncTriggersConfig, g as SyncWorkerConfig } from '../types-Bhy7MfCj.js';
2
+ export { A as AdminHttpsOptions, F as FirestoreSyncConfig, h as FirestoreTriggersDep, O as OrFactory, i as PubSubHandlerDep, j as SyncDeps, k as SyncOperation, l as SyncWorkerOptions, m as adminsyncBasicAuth, n as adminsyncFeaturesFlag } from '../types-Bhy7MfCj.js';
3
+ import { S as SyncQueue } from '../queue-DLrIuZ7L.js';
4
+ export { a as SyncQueueOptions } from '../queue-DLrIuZ7L.js';
5
5
  import { z } from 'zod';
6
6
  import * as firebase_functions_core from 'firebase-functions/core';
7
7
  import * as firebase_functions_pubsub from 'firebase-functions/pubsub';
@@ -1,4 +1,4 @@
1
- function te(n){let e=[],t=n.replace(/[.*+?^${}()|[\]\\]/g,s=>s===":"?s:`\\${s}`).replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g,(s,r)=>(e.push(r),"([^/]+)"));return {pattern:new RegExp(`^${t}$`),paramNames:e}}function ne(n){let e=n.path??n.url??"/",t=e.indexOf("?");return t===-1?e:e.slice(0,t)}var Z=class{constructor(){this.routes=[];this.middlewares=[];this.notFoundHandler=(e,t)=>{t.status(404).send("Not Found");};this.errorHandler=(e,t,s)=>{console.error("[MiniRouter]",e),s.status(500).send("Internal Server Error");};}use(e){return this.middlewares.push(e),this}get(e,t){return this.addRoute("GET",e,t)}post(e,t){return this.addRoute("POST",e,t)}put(e,t){return this.addRoute("PUT",e,t)}patch(e,t){return this.addRoute("PATCH",e,t)}delete(e,t){return this.addRoute("DELETE",e,t)}onNotFound(e){return this.notFoundHandler=e,this}onError(e){return this.errorHandler=e,this}addRoute(e,t,s){let{pattern:r,paramNames:u}=te(t);return this.routes.push({method:e.toUpperCase(),pattern:r,paramNames:u,handler:s}),this}async handle(e,t){let s=(e.method??"GET").toUpperCase(),r=ne(e),u=null,h={};for(let k of this.routes){if(k.method!==s)continue;let y=r.match(k.pattern);if(y){u=k,h={},k.paramNames.forEach((f,c)=>{h[f]=decodeURIComponent(y[c+1]??"");});break}}let x=Object.assign(e,{params:h}),l=u?u.handler:this.notFoundHandler;try{await this.runMiddlewareChain(x,t,l);}catch(k){this.errorHandler(k,e,t);}}async runMiddlewareChain(e,t,s){let r=0,u=async()=>{if(r<this.middlewares.length){let h=this.middlewares[r++];await h(e,t,u);}else await s(e,t);};await u();}};function O(n,e){let t=e==="/"?"":e.replace(/\/$/,"");if(process.env.FUNCTIONS_EMULATOR==="true"){let u=process.env.GCLOUD_PROJECT??process.env.GOOGLE_CLOUD_PROJECT??"demo-project",h=process.env.FUNCTION_REGION??"us-central1",x=(process.env.FUNCTION_TARGET??"").replace(/\./g,"-");return `/${u}/${h}/${x}${t}`}let s=process.env.K_SERVICE,r=n?.hostname??n?.headers?.host??"";return s&&typeof r=="string"&&r.includes("cloudfunctions.net")?`/${s.toLowerCase()}${t}`:t}function B(n){return !!n&&typeof n=="object"&&n.__authExtension===true}var se={string:"ZodString",number:"ZodNumber",bigint:"ZodBigInt",boolean:"ZodBoolean",date:"ZodDate",enum:"ZodEnum",nativeEnum:"ZodNativeEnum",literal:"ZodLiteral",object:"ZodObject",array:"ZodArray",optional:"ZodOptional",nullable:"ZodNullable",default:"ZodDefault",coerce:"ZodCoerce",union:"ZodUnion",undefined:"ZodUndefined",unknown:"ZodUnknown",any:"ZodAny",record:"ZodRecord"};function M(n){let e=n,t=e._zod?.def?.type;if(t)return se[t]??`Zod${t.charAt(0).toUpperCase()}${t.slice(1)}`;let s=e._def?.typeName;return s||""}function G(n){let e=n;if(e._zod?.def?.innerType)return e._zod.def.innerType;if(e._def?.innerType)return e._def.innerType}function H(n){let e=n;return e.shape&&typeof e.shape=="object"?e.shape:e._zod?.def?.shape&&typeof e._zod.def.shape=="object"?e._zod.def.shape:e._def?.shape?typeof e._def.shape=="function"?e._def.shape():e._def.shape:{}}var D="__sync_version";var ie=new Set(["ZodOptional","ZodNullable","ZodDefault"]);function Q(n){let e=n,t=false;for(;;){let s=M(e);if(!ie.has(s))break;(s==="ZodOptional"||s==="ZodNullable")&&(t=true);let r=G(e);if(!r)break;e=r;}return {inner:e,nullable:t}}var J={ZodString:"string",ZodNumber:"number",ZodBigInt:"bigint",ZodBoolean:"boolean",ZodDate:"timestamp",ZodEnum:"string",ZodNativeEnum:"string",ZodLiteral:"string"};function ae(n){let{inner:e}=Q(n);return J[M(e)]??"json"}function W(n,e,t,s,r,u,h,x){for(let[l,k]of Object.entries(n)){let y=t?`${t}__${l}`:l;if(r.has(l)||r.has(y))continue;let{inner:f,nullable:c}=Q(k),d=M(f),a=s||c;if(d==="ZodObject"){let i=H(f);W(i,e,y,a,r,u,h,x);continue}let o=J[d]??"json",p=y===h||l===h,b=u[y]??u[l]??y;x.push({name:b,sqlType:e.mapType(o),nullable:p?false:a,isPrimaryKey:p});}}function E(n,e,t={}){let{primaryKey:s,exclude:r=[],columnMap:u={}}=t,h=new Set(r),x=H(n),l=[];return W(x,e,"",false,h,u,s,l),l.some(k=>k.name===D)||l.push({name:D,sqlType:e.mapType("bigint"),nullable:true,isPrimaryKey:false,description:"Monotonic publish version (Date.now() ms). Internal."}),l}function K(n){if(n==null)return null;if(typeof n=="object"&&typeof n.toDate=="function")return n.toDate().toISOString();if(n instanceof Date)return n.toISOString();if(Buffer.isBuffer(n))return n.toString("base64");if(n instanceof Uint8Array)return Buffer.from(n).toString("base64");if(typeof n=="object"&&"latitude"in n&&"longitude"in n){let e=n;return JSON.stringify({lat:e.latitude,lng:e.longitude})}return Array.isArray(n)?JSON.stringify(n.map(K)):n}function V(n,e,t){for(let[s,r]of Object.entries(n)){let u=e?`${e}__${s}`:s;r!=null&&typeof r=="object"&&!Array.isArray(r)&&!(r instanceof Date)&&!Buffer.isBuffer(r)&&!(r instanceof Uint8Array)&&typeof r.toDate!="function"&&!("latitude"in r&&"longitude"in r)?V(r,u,t):t[u]=K(r);}}function j(n,e){let t=new Set(e?.exclude),s=e?.columnMap??{},r={};V(n,"",r);let u={};for(let[h,x]of Object.entries(r)){if(t.has(h))continue;let l=h.split("__")[0];if(l!==h&&t.has(l))continue;let k=s[h]??(h.includes("__")?s[h.split("__").pop()]:void 0)??h;u[k]=x;}return u}function N(n,e,t){return `<!DOCTYPE html>
1
+ function re(n){let e=[],t=n.replace(/[.*+?^${}()|[\]\\]/g,r=>r===":"?r:`\\${r}`).replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g,(r,s)=>(e.push(s),"([^/]+)"));return {pattern:new RegExp(`^${t}$`),paramNames:e}}function se(n){let e=n.path??n.url??"/",t=e.indexOf("?");return t===-1?e:e.slice(0,t)}var q=class{constructor(){this.routes=[];this.middlewares=[];this.notFoundHandler=(e,t)=>{t.status(404).send("Not Found");};this.errorHandler=(e,t,r)=>{console.error("[MiniRouter]",e),r.status(500).send("Internal Server Error");};}use(e){return this.middlewares.push(e),this}get(e,t){return this.addRoute("GET",e,t)}post(e,t){return this.addRoute("POST",e,t)}put(e,t){return this.addRoute("PUT",e,t)}patch(e,t){return this.addRoute("PATCH",e,t)}delete(e,t){return this.addRoute("DELETE",e,t)}onNotFound(e){return this.notFoundHandler=e,this}onError(e){return this.errorHandler=e,this}addRoute(e,t,r){let{pattern:s,paramNames:d}=re(t);return this.routes.push({method:e.toUpperCase(),pattern:s,paramNames:d,handler:r}),this}async handle(e,t){let r=(e.method??"GET").toUpperCase(),s=se(e),d=null,h={};for(let k of this.routes){if(k.method!==r)continue;let y=s.match(k.pattern);if(y){d=k,h={},k.paramNames.forEach((l,c)=>{h[l]=decodeURIComponent(y[c+1]??"");});break}}let w=Object.assign(e,{params:h}),p=d?d.handler:this.notFoundHandler;try{await this.runMiddlewareChain(w,t,p);}catch(k){this.errorHandler(k,e,t);}}async runMiddlewareChain(e,t,r){let s=0,d=async()=>{if(s<this.middlewares.length){let h=this.middlewares[s++];await h(e,t,d);}else await r(e,t);};await d();}};function _(n,e){let t=e==="/"?"":e.replace(/\/$/,"");if(process.env.FUNCTIONS_EMULATOR==="true"){let d=process.env.GCLOUD_PROJECT??process.env.GOOGLE_CLOUD_PROJECT??"demo-project",h=process.env.FUNCTION_REGION??"us-central1",w=(process.env.FUNCTION_TARGET??"").replace(/\./g,"-");return `/${d}/${h}/${w}${t}`}let r=process.env.K_SERVICE,s=n?.hostname??n?.headers?.host??"";return r&&typeof s=="string"&&s.includes("cloudfunctions.net")?`/${r.toLowerCase()}${t}`:t}function B(n){return !!n&&typeof n=="object"&&n.__authExtension===true}var ce={string:"ZodString",number:"ZodNumber",bigint:"ZodBigInt",boolean:"ZodBoolean",date:"ZodDate",enum:"ZodEnum",nativeEnum:"ZodNativeEnum",literal:"ZodLiteral",object:"ZodObject",array:"ZodArray",optional:"ZodOptional",nullable:"ZodNullable",default:"ZodDefault",coerce:"ZodCoerce",union:"ZodUnion",undefined:"ZodUndefined",unknown:"ZodUnknown",any:"ZodAny",record:"ZodRecord"};function Z(n){let e=n,t=e._zod?.def?.type;if(t)return ce[t]??`Zod${t.charAt(0).toUpperCase()}${t.slice(1)}`;let r=e._def?.typeName;return r||""}function Q(n){let e=n;if(e._zod?.def?.innerType)return e._zod.def.innerType;if(e._def?.innerType)return e._def.innerType}function H(n){let e=n;return e.shape&&typeof e.shape=="object"?e.shape:e._zod?.def?.shape&&typeof e._zod.def.shape=="object"?e._zod.def.shape:e._def?.shape?typeof e._def.shape=="function"?e._def.shape():e._def.shape:{}}var D="__sync_version";var de=new Set(["ZodOptional","ZodNullable","ZodDefault"]);function W(n){let e=n,t=false;for(;;){let r=Z(e);if(!de.has(r))break;(r==="ZodOptional"||r==="ZodNullable")&&(t=true);let s=Q(e);if(!s)break;e=s;}return {inner:e,nullable:t}}var J={ZodString:"string",ZodNumber:"number",ZodBigInt:"bigint",ZodBoolean:"boolean",ZodDate:"timestamp",ZodEnum:"string",ZodNativeEnum:"string",ZodLiteral:"string"};function ue(n){let{inner:e}=W(n);return J[Z(e)]??"json"}function V(n,e,t,r,s,d,h,w){for(let[p,k]of Object.entries(n)){let y=t?`${t}__${p}`:p;if(s.has(p)||s.has(y))continue;let{inner:l,nullable:c}=W(k),u=Z(l),a=r||c;if(u==="ZodObject"){let i=H(l);V(i,e,y,a,s,d,h,w);continue}let o=J[u]??"json",f=y===h||p===h,b=d[y]??d[p]??y;w.push({name:b,sqlType:e.mapType(o),nullable:f?false:a,isPrimaryKey:f});}}function P(n,e,t={}){let{primaryKey:r,exclude:s=[],columnMap:d={}}=t,h=new Set(s),w=H(n),p=[];return V(w,e,"",false,h,d,r,p),p.some(k=>k.name===D)||p.push({name:D,sqlType:e.mapType("bigint"),nullable:true,isPrimaryKey:false,description:"Monotonic publish version (Date.now() ms). Internal."}),p}function K(n){if(n==null)return null;if(typeof n=="object"&&typeof n.toDate=="function")return n.toDate().toISOString();if(n instanceof Date)return n.toISOString();if(Buffer.isBuffer(n))return n.toString("base64");if(n instanceof Uint8Array)return Buffer.from(n).toString("base64");if(typeof n=="object"&&"latitude"in n&&"longitude"in n){let e=n;return JSON.stringify({lat:e.latitude,lng:e.longitude})}return Array.isArray(n)?JSON.stringify(n.map(K)):n}function Y(n,e,t){for(let[r,s]of Object.entries(n)){let d=e?`${e}__${r}`:r;s!=null&&typeof s=="object"&&!Array.isArray(s)&&!(s instanceof Date)&&!Buffer.isBuffer(s)&&!(s instanceof Uint8Array)&&typeof s.toDate!="function"&&!("latitude"in s&&"longitude"in s)?Y(s,d,t):t[d]=K(s);}}function j(n,e){let t=new Set(e?.exclude),r=e?.columnMap??{},s={};Y(n,"",s);let d={};for(let[h,w]of Object.entries(s)){if(t.has(h))continue;let p=h.split("__")[0];if(p!==h&&t.has(p))continue;let k=r[h]??(h.includes("__")?r[h.split("__").pop()]:void 0)??h;d[k]=w;}return d}function N(n,e,t){return `<!DOCTYPE html>
2
2
  <html lang="en"><head>
3
3
  <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
4
4
  <title>${n} \u2014 Sync Admin</title>
@@ -28,64 +28,64 @@ function te(n){let e=[],t=n.replace(/[.*+?^${}()|[\]\\]/g,s=>s===":"?s:`\\${s}`)
28
28
  <nav><a href="${e}/">\u2190 Dashboard</a></nav>
29
29
  <h1>${n}</h1>
30
30
  ${t}
31
- </body></html>`}function _(n,e,t=200){n.status(t).set("Content-Type","text/html; charset=utf-8").send(e);}function z(n,e,t=200){n.status(t).set("Content-Type","application/json").send(JSON.stringify(e,null,2));}function F(n){return (n.headers?.accept??"").includes("application/json")}function ce(n,e,t,s,r,u,h,x){let l=(r.basePath??"/").replace(/\/$/,"")||"",k=r.featuresFlag??{},y=[];for(let[c,d]of Object.entries(n)){let a=u[c];y.push({name:c,schema:d.schema??null,documentKey:d._systemKeys?.[0]??d.documentKey??"docId",tableName:a?.tableName??c,isGroup:!!d._isGroup,repoCfg:a,repo:d});}let f=new Z;if(r.auth)if(B(r.auth)){let c=r.auth;for(let d of c.routes){let a=`${l}${d.path}`;d.method==="GET"?f.get(a,d.handler):f.post(a,d.handler);}f.use(c.middleware);}else if(typeof r.auth=="function")f.use(r.auth);else {let c=r.auth.realm??"Sync Admin",d="Basic "+Buffer.from(`${r.auth.username}:${r.auth.password}`).toString("base64");f.use((a,o,p)=>{if((a.headers?.authorization??"")!==d){o.status(401).set("WWW-Authenticate",`Basic realm="${c}"`).set("Content-Type","text/plain").send("Unauthorized");return}p();});}return f.get(`${l}/`,(c,d)=>{let a=O(c,l),o=y.map(i=>{let S=[];return k.healthCheck&&S.push(`<a class="btn" href="${a}/${i.name}/health">Health</a>`),k.manualSync&&S.push(`<a class="btn btn-primary" href="${a}/${i.name}/force-sync">Force Sync</a>`),`<tr>
31
+ </body></html>`}function O(n,e,t=200){n.status(t).set("Content-Type","text/html; charset=utf-8").send(e);}function z(n,e,t=200){n.status(t).set("Content-Type","application/json").send(JSON.stringify(e,null,2));}function F(n){return (n.headers?.accept??"").includes("application/json")}function le(n,e,t,r,s,d,h,w){let p=(s.basePath??"/").replace(/\/$/,"")||"",k=s.featuresFlag??{},y=[];for(let[c,u]of Object.entries(n)){let a=d[c];y.push({name:c,schema:u.schema??null,documentKey:u._systemKeys?.[0]??u.documentKey??"docId",tableName:a?.tableName??c,isGroup:!!u._isGroup,repoCfg:a,repo:u});}let l=new q;if(s.auth)if(B(s.auth)){let c=s.auth;for(let u of c.routes){let a=`${p}${u.path}`;u.method==="GET"?l.get(a,u.handler):l.post(a,u.handler);}l.use(c.middleware);}else if(typeof s.auth=="function")l.use(s.auth);else {let c=s.auth.realm??"Sync Admin",u="Basic "+Buffer.from(`${s.auth.username}:${s.auth.password}`).toString("base64");l.use((a,o,f)=>{if((a.headers?.authorization??"")!==u){o.status(401).set("WWW-Authenticate",`Basic realm="${c}"`).set("Content-Type","text/plain").send("Unauthorized");return}f();});}return l.get(`${p}/`,(c,u)=>{let a=_(c,p),o=y.map(i=>{let S=[];return k.healthCheck&&S.push(`<a class="btn" href="${a}/${i.name}/health">Health</a>`),k.manualSync&&S.push(`<a class="btn btn-primary" href="${a}/${i.name}/force-sync">Force Sync</a>`),`<tr>
32
32
  <td><strong>${i.name}</strong></td>
33
33
  <td>${i.tableName}</td>
34
34
  <td>${i.isGroup?'<span class="badge badge-warn">group</span>':'<span class="badge badge-ok">collection</span>'}</td>
35
35
  <td>${i.schema?"\u2713":"\u2717"}</td>
36
36
  <td>${S.join(" ")}</td>
37
37
  </tr>`}).join(`
38
- `),p=k.configCheck?`<p style="margin-top:.5rem"><a class="btn" href="${a}/config-check">\u2699 Config Check</a></p>`:"",b=N("Sync Dashboard",a,`<div class="card">
38
+ `),f=k.configCheck?`<p style="margin-top:.5rem"><a class="btn" href="${a}/config-check">\u2699 Config Check</a></p>`:"",b=N("Sync Dashboard",a,`<div class="card">
39
39
  <table>
40
40
  <thead><tr><th>Repository</th><th>Table</th><th>Type</th><th>Schema</th><th>Actions</th></tr></thead>
41
41
  <tbody>${o}</tbody>
42
42
  </table>
43
- ${p}
44
- </div>`);_(d,b);}),f.get(`${l}`,(c,d)=>{let a=O(c,l);d.status(302).set("Location",`${a}/`).send("");}),k.healthCheck&&f.get(`${l}/:repoName/health`,async(c,d)=>{let a=O(c,l),o=y.find(m=>m.name===c.params.repoName);if(!o){_(d,N("Not Found",a,`<p>Unknown repo: ${c.params.repoName}</p>`),404);return}if(!o.schema){_(d,N("Health Check",a,`<p class="badge badge-warn">No Zod schema attached to "${o.name}"</p>`));return}let p=E(o.schema,e.dialect,{primaryKey:o.documentKey,exclude:o.repoCfg?.exclude,columnMap:o.repoCfg?.columnMap}),b=[],i=false,S=null;try{i=await e.tableExists(o.tableName),i&&(b=await e.getTableColumns(o.tableName));}catch(m){S=m?.message??String(m);}let v=new Set(b),T=new Set(p.map(m=>m.name)),A=p.filter(m=>!v.has(m.name)),P=b.filter(m=>!T.has(m)),I=p.filter(m=>v.has(m.name)),g=i&&A.length===0&&!S;if(F(c)){z(d,{repo:o.name,table:o.tableName,tableExists:i,healthy:g,error:S,columns:{expected:p.map(m=>({name:m.name,type:m.sqlType,nullable:m.nullable,isPrimaryKey:m.isPrimaryKey})),actual:b,matched:I.map(m=>m.name),missing:A.map(m=>({name:m.name,type:m.sqlType})),extra:P}});return}let w=g?'<span class="badge badge-ok">Healthy</span>':'<span class="badge badge-err">Unhealthy</span>',R=p.map(m=>{let L=v.has(m.name)?'<span class="badge badge-ok">OK</span>':'<span class="badge badge-err">MISSING</span>';return `<tr><td>${m.name}</td><td>${m.sqlType}</td><td>${m.nullable?"Yes":"No"}</td><td>${m.isPrimaryKey?"\u2713":""}</td><td>${L}</td></tr>`}).join(`
45
- `),C=P.map(m=>`<tr><td>${m}</td><td colspan="3" class="muted">not in schema</td><td><span class="badge badge-warn">EXTRA</span></td></tr>`).join(`
43
+ ${f}
44
+ </div>`);O(u,b);}),l.get(`${p}`,(c,u)=>{let a=_(c,p);u.status(302).set("Location",`${a}/`).send("");}),k.healthCheck&&l.get(`${p}/:repoName/health`,async(c,u)=>{let a=_(c,p),o=y.find(m=>m.name===c.params.repoName);if(!o){O(u,N("Not Found",a,`<p>Unknown repo: ${c.params.repoName}</p>`),404);return}if(!o.schema){O(u,N("Health Check",a,`<p class="badge badge-warn">No Zod schema attached to "${o.name}"</p>`));return}let f=P(o.schema,e.dialect,{primaryKey:o.documentKey,exclude:o.repoCfg?.exclude,columnMap:o.repoCfg?.columnMap}),b=[],i=false,S=null;try{i=await e.tableExists(o.tableName),i&&(b=await e.getTableColumns(o.tableName));}catch(m){S=m?.message??String(m);}let R=new Set(b),v=new Set(f.map(m=>m.name)),A=f.filter(m=>!R.has(m.name)),E=b.filter(m=>!v.has(m)),I=f.filter(m=>R.has(m.name)),g=i&&A.length===0&&!S;if(F(c)){z(u,{repo:o.name,table:o.tableName,tableExists:i,healthy:g,error:S,columns:{expected:f.map(m=>({name:m.name,type:m.sqlType,nullable:m.nullable,isPrimaryKey:m.isPrimaryKey})),actual:b,matched:I.map(m=>m.name),missing:A.map(m=>({name:m.name,type:m.sqlType})),extra:E}});return}let x=g?'<span class="badge badge-ok">Healthy</span>':'<span class="badge badge-err">Unhealthy</span>',T=f.map(m=>{let L=R.has(m.name)?'<span class="badge badge-ok">OK</span>':'<span class="badge badge-err">MISSING</span>';return `<tr><td>${m.name}</td><td>${m.sqlType}</td><td>${m.nullable?"Yes":"No"}</td><td>${m.isPrimaryKey?"\u2713":""}</td><td>${L}</td></tr>`}).join(`
45
+ `),C=E.map(m=>`<tr><td>${m}</td><td colspan="3" class="muted">not in schema</td><td><span class="badge badge-warn">EXTRA</span></td></tr>`).join(`
46
46
  `),$=N(`Health: ${o.name}`,a,`<div class="card">
47
- <p>Table: <code>${o.tableName}</code> ${i?w:'<span class="badge badge-err">NOT FOUND</span>'}</p>
47
+ <p>Table: <code>${o.tableName}</code> ${i?x:'<span class="badge badge-err">NOT FOUND</span>'}</p>
48
48
  ${S?`<p class="badge badge-err">Error: ${S}</p>`:""}
49
49
  <h2>Columns</h2>
50
50
  <table>
51
51
  <thead><tr><th>Column</th><th>SQL Type</th><th>Nullable</th><th>PK</th><th>Status</th></tr></thead>
52
- <tbody>${R}${C}</tbody>
52
+ <tbody>${T}${C}</tbody>
53
53
  </table>
54
- </div>`);_(d,$);}),k.manualSync&&(f.get(`${l}/:repoName/force-sync`,(c,d)=>{let a=O(c,l),o=y.find(b=>b.name===c.params.repoName);if(!o){_(d,N("Not Found",a,`<p>Unknown repo: ${c.params.repoName}</p>`),404);return}let p=N(`Force Sync: ${o.name}`,a,`<div class="card">
54
+ </div>`);O(u,$);}),k.manualSync&&(l.get(`${p}/:repoName/force-sync`,(c,u)=>{let a=_(c,p),o=y.find(b=>b.name===c.params.repoName);if(!o){O(u,N("Not Found",a,`<p>Unknown repo: ${c.params.repoName}</p>`),404);return}let f=N(`Force Sync: ${o.name}`,a,`<div class="card">
55
55
  <p>This will read <strong>all</strong> documents from the <code>${o.name}</code> Firestore collection
56
56
  and upsert them into the <code>${o.tableName}</code> SQL table.</p>
57
57
  <p class="muted" style="margin:.75rem 0">This may take a while for large collections.</p>
58
58
  <form method="POST" action="${a}/${o.name}/force-sync">
59
59
  <button type="submit" class="btn btn-primary">Start Force Sync</button>
60
60
  </form>
61
- </div>`);_(d,p);}),f.post(`${l}/:repoName/force-sync`,async(c,d)=>{let a=O(c,l),o=y.find(g=>g.name===c.params.repoName);if(!o){z(d,{error:`Unknown repo: ${c.params.repoName}`},404);return}let p=o.repo.ref;if(!p){z(d,{error:`No collection reference for "${o.name}"`},400);return}let b=0,i=0,S=[],v=500,T=p.limit(v),A=null;try{for(;;){let R=await(A?T.startAfter(A):T).get();if(R.empty)break;for(let C of R.docs){let $=C.data(),m=String($[o.documentKey]??C.id),L=j($,{exclude:o.repoCfg?.exclude,columnMap:o.repoCfg?.columnMap});try{await s({operation:"UPSERT",repoName:o.name,docId:m,data:L,timestamp:new Date().toISOString()}),b++;}catch(U){i++;let ee=U?.message??String(U);console.error(`[ForceSync:${o.name}] doc=${m} failed:`,U),S.length<5&&S.push(`${m}: ${ee}`);}}if(A=R.docs[R.docs.length-1],R.docs.length<v)break}let g=t.get(o.name);g&&await g.flush();}catch(g){if(F(c)){z(d,{error:g?.message??String(g),synced:b,errors:i},500);return}_(d,N(`Force Sync: ${o.name}`,a,`<div class="card">
61
+ </div>`);O(u,f);}),l.post(`${p}/:repoName/force-sync`,async(c,u)=>{let a=_(c,p),o=y.find(g=>g.name===c.params.repoName);if(!o){z(u,{error:`Unknown repo: ${c.params.repoName}`},404);return}let f=o.repo.ref;if(!f){z(u,{error:`No collection reference for "${o.name}"`},400);return}let b=0,i=0,S=[],R=500,v=f.limit(R),A=null;try{for(;;){let T=await(A?v.startAfter(A):v).get();if(T.empty)break;for(let C of T.docs){let $=C.data(),m=String($[o.documentKey]??C.id),L=j($,{exclude:o.repoCfg?.exclude,columnMap:o.repoCfg?.columnMap});try{await r({operation:"UPSERT",repoName:o.name,docId:m,data:L,timestamp:new Date().toISOString()}),b++;}catch(U){i++;let oe=U?.message??String(U);console.error(`[ForceSync:${o.name}] doc=${m} failed:`,U),S.length<5&&S.push(`${m}: ${oe}`);}}if(A=T.docs[T.docs.length-1],T.docs.length<R)break}let g=t.get(o.name);g&&await g.flush();}catch(g){if(F(c)){z(u,{error:g?.message??String(g),synced:b,errors:i},500);return}O(u,N(`Force Sync: ${o.name}`,a,`<div class="card">
62
62
  <p class="badge badge-err">Error: ${g?.message??String(g)}</p>
63
63
  <p>Synced ${b} docs before failure (${i} errors).</p>
64
- </div>`),500);return}if(F(c)){z(d,{repo:o.name,table:o.tableName,synced:b,errors:i,...S.length>0&&{errorSamples:S}});return}let P=S.length>0?`<details style="margin-top:1rem"><summary>First ${S.length} error(s)</summary>
65
- <pre style="white-space:pre-wrap">${S.map(g=>g.replace(/[<>&]/g,w=>`&#${w.charCodeAt(0)};`)).join(`
64
+ </div>`),500);return}if(F(c)){z(u,{repo:o.name,table:o.tableName,synced:b,errors:i,...S.length>0&&{errorSamples:S}});return}let E=S.length>0?`<details style="margin-top:1rem"><summary>First ${S.length} error(s)</summary>
65
+ <pre style="white-space:pre-wrap">${S.map(g=>g.replace(/[<>&]/g,x=>`&#${x.charCodeAt(0)};`)).join(`
66
66
 
67
67
  `)}</pre></details>`:"",I=N(`Force Sync: ${o.name}`,a,`<div class="card">
68
68
  <p class="badge ${i>0?"badge-warn":"badge-ok"}">${i>0?"Completed with errors":"Complete"}</p>
69
69
  <p>Synced <strong>${b}</strong> documents to <code>${o.tableName}</code>.</p>
70
70
  ${i>0?`<p class="badge badge-warn">${i} error(s)</p>`:""}
71
- ${P}
72
- </div>`);_(d,I);})),k.configCheck&&f.get(`${l}/config-check`,async(c,d)=>{let a=O(c,l),o=process.env.GCLOUD_PROJECT??process.env.GOOGLE_CLOUD_PROJECT??process.env.GCP_PROJECT??"unknown",p="https://console.cloud.google.com",b=x??"firestore-sync",i=[];try{await e.tableExists("__nonexistent_health_check__"),i.push({name:"BigQuery API",category:"bigquery",status:"ok",message:"BigQuery API is reachable"});}catch(g){let w=g?.message??String(g),R=w.toLowerCase(),C=R.includes("disabled")||R.includes("has not been used")||R.includes("accessnotconfigured"),$=R.includes("permission")||w.includes("403")||R.includes("access denied"),m=R.includes("project")&&R.includes("not found"),L=R.includes("not found")||w.includes("404");C?i.push({name:"BigQuery API",category:"bigquery",status:"error",message:"BigQuery API is not enabled",fix:{gcloud:`gcloud services enable bigquery.googleapis.com --project=${o}`,console:`${p}/apis/library/bigquery.googleapis.com?project=${o}`}}):m?i.push({name:"BigQuery Project",category:"bigquery",status:"error",message:w,fix:{hint:"The GCP project does not exist or the credentials don't have access to it. In the Firebase emulator, GCLOUD_PROJECT may override the configured projectId. Ensure you pass the correct projectId to the BigQuery constructor and have valid credentials.",console:`${p}/home/dashboard`}}):$?i.push({name:"BigQuery API",category:"bigquery",status:"error",message:`Permission denied: ${w}`,fix:{hint:"Grant the service account BigQuery Data Editor + BigQuery Job User roles",gcloud:[`SA=$(gcloud run services describe YOUR_SERVICE --region=YOUR_REGION --format="value(spec.template.spec.serviceAccountName)" --project=${o})`,`gcloud projects add-iam-policy-binding ${o} --member="serviceAccount:$SA" --role="roles/bigquery.dataEditor"`,`gcloud projects add-iam-policy-binding ${o} --member="serviceAccount:$SA" --role="roles/bigquery.jobUser"`].join(`
73
- `),console:`${p}/iam-admin/iam?project=${o}`}}):L?i.push({name:"BigQuery Dataset",category:"bigquery",status:"error",message:`Dataset not found: ${w}`,fix:{hint:"Create the dataset first",gcloud:`bq mk --dataset ${o}:YOUR_DATASET_ID`,console:`${p}/bigquery?project=${o}`}}):i.push({name:"BigQuery API",category:"bigquery",status:"ok",message:"BigQuery API is reachable (table lookup returned expected error)"});}for(let g of y)try{let w=await e.tableExists(g.tableName);i.push({name:`Table: ${g.tableName}`,category:"bigquery",status:w?"ok":"warn",message:w?`Table \`${g.tableName}\` exists`:`Table \`${g.tableName}\` does not exist yet`,...!w&&{fix:{hint:"Table will be auto-created on first sync if autoMigrate is enabled. Or create it manually."}}});}catch(w){i.push({name:`Table: ${g.tableName}`,category:"bigquery",status:"error",message:w?.message??String(w)});}if(h)for(let g of y){let w=`${b}-${g.name}`;try{let R=h.topic(w);if(typeof R.exists=="function"){let[C]=await R.exists();i.push({name:`Topic: ${w}`,category:"pubsub",status:C?"ok":"error",message:C?`Topic \`${w}\` exists`:`Topic \`${w}\` does not exist`,...!C&&{fix:{gcloud:`gcloud pubsub topics create ${w} --project=${o}`,console:`${p}/cloudpubsub/topic/list?project=${o}`}}});}else i.push({name:`Topic: ${w}`,category:"pubsub",status:"warn",message:"Cannot verify topic existence (PubSub client doesn't expose .exists())",fix:{gcloud:`gcloud pubsub topics create ${w} --project=${o}`,console:`${p}/cloudpubsub/topic/list?project=${o}`,hint:"Ensure the topic exists. It is auto-created by the Firebase emulator but must exist in production."}});}catch(R){let C=R?.message??String(R),$=C.includes("disabled")||C.includes("has not been used");if(i.push({name:$?"Pub/Sub API":`Topic: ${w}`,category:"pubsub",status:"error",message:$?"Pub/Sub API is not enabled":C,fix:$?{gcloud:`gcloud services enable pubsub.googleapis.com --project=${o}`,console:`${p}/apis/library/pubsub.googleapis.com?project=${o}`}:{gcloud:`gcloud pubsub topics create ${w} --project=${o}`,console:`${p}/cloudpubsub/topic/list?project=${o}`}}),$)break}}else i.push({name:"Pub/Sub Client",category:"pubsub",status:"warn",message:"PubSub client not available for config check"});if(F(c)){let g=i.every(w=>w.status==="ok");z(d,{project:o,healthy:g,checks:i});return}let S=g=>g==="ok"?'<span class="badge badge-ok">OK</span>':g==="warn"?'<span class="badge badge-warn">WARN</span>':'<span class="badge badge-err">ERROR</span>',v={bigquery:i.filter(g=>g.category==="bigquery"),pubsub:i.filter(g=>g.category==="pubsub"),firestore:i.filter(g=>g.category==="firestore")},T=(g,w)=>{if(w.length===0)return "";let R=w.map(C=>{let $="";if(C.fix){let m=[];C.fix.hint&&m.push(`<p class="muted">${C.fix.hint}</p>`),C.fix.gcloud&&m.push(`<pre>$ ${C.fix.gcloud}</pre>`),C.fix.console&&m.push(`<p><a href="${C.fix.console}" target="_blank">Open GCP Console \u2192</a></p>`),$=`<div style="margin-top:.5rem">${m.join("")}</div>`;}return `<tr>
71
+ ${E}
72
+ </div>`);O(u,I);})),k.configCheck&&l.get(`${p}/config-check`,async(c,u)=>{let a=_(c,p),o=process.env.GCLOUD_PROJECT??process.env.GOOGLE_CLOUD_PROJECT??process.env.GCP_PROJECT??"unknown",f="https://console.cloud.google.com",b=w??"firestore-sync",i=[];try{await e.tableExists("__nonexistent_health_check__"),i.push({name:"BigQuery API",category:"bigquery",status:"ok",message:"BigQuery API is reachable"});}catch(g){let x=g?.message??String(g),T=x.toLowerCase(),C=T.includes("disabled")||T.includes("has not been used")||T.includes("accessnotconfigured"),$=T.includes("permission")||x.includes("403")||T.includes("access denied"),m=T.includes("project")&&T.includes("not found"),L=T.includes("not found")||x.includes("404");C?i.push({name:"BigQuery API",category:"bigquery",status:"error",message:"BigQuery API is not enabled",fix:{gcloud:`gcloud services enable bigquery.googleapis.com --project=${o}`,console:`${f}/apis/library/bigquery.googleapis.com?project=${o}`}}):m?i.push({name:"BigQuery Project",category:"bigquery",status:"error",message:x,fix:{hint:"The GCP project does not exist or the credentials don't have access to it. In the Firebase emulator, GCLOUD_PROJECT may override the configured projectId. Ensure you pass the correct projectId to the BigQuery constructor and have valid credentials.",console:`${f}/home/dashboard`}}):$?i.push({name:"BigQuery API",category:"bigquery",status:"error",message:`Permission denied: ${x}`,fix:{hint:"Grant the service account BigQuery Data Editor + BigQuery Job User roles",gcloud:[`SA=$(gcloud run services describe YOUR_SERVICE --region=YOUR_REGION --format="value(spec.template.spec.serviceAccountName)" --project=${o})`,`gcloud projects add-iam-policy-binding ${o} --member="serviceAccount:$SA" --role="roles/bigquery.dataEditor"`,`gcloud projects add-iam-policy-binding ${o} --member="serviceAccount:$SA" --role="roles/bigquery.jobUser"`].join(`
73
+ `),console:`${f}/iam-admin/iam?project=${o}`}}):L?i.push({name:"BigQuery Dataset",category:"bigquery",status:"error",message:`Dataset not found: ${x}`,fix:{hint:"Create the dataset first",gcloud:`bq mk --dataset ${o}:YOUR_DATASET_ID`,console:`${f}/bigquery?project=${o}`}}):i.push({name:"BigQuery API",category:"bigquery",status:"ok",message:"BigQuery API is reachable (table lookup returned expected error)"});}for(let g of y)try{let x=await e.tableExists(g.tableName);i.push({name:`Table: ${g.tableName}`,category:"bigquery",status:x?"ok":"warn",message:x?`Table \`${g.tableName}\` exists`:`Table \`${g.tableName}\` does not exist yet`,...!x&&{fix:{hint:"Table will be auto-created on first sync if autoMigrate is enabled. Or create it manually."}}});}catch(x){i.push({name:`Table: ${g.tableName}`,category:"bigquery",status:"error",message:x?.message??String(x)});}if(h)for(let g of y){let x=`${b}-${g.name}`;try{let T=h.topic(x);if(typeof T.exists=="function"){let[C]=await T.exists();i.push({name:`Topic: ${x}`,category:"pubsub",status:C?"ok":"error",message:C?`Topic \`${x}\` exists`:`Topic \`${x}\` does not exist`,...!C&&{fix:{gcloud:`gcloud pubsub topics create ${x} --project=${o}`,console:`${f}/cloudpubsub/topic/list?project=${o}`}}});}else i.push({name:`Topic: ${x}`,category:"pubsub",status:"warn",message:"Cannot verify topic existence (PubSub client doesn't expose .exists())",fix:{gcloud:`gcloud pubsub topics create ${x} --project=${o}`,console:`${f}/cloudpubsub/topic/list?project=${o}`,hint:"Ensure the topic exists. It is auto-created by the Firebase emulator but must exist in production."}});}catch(T){let C=T?.message??String(T),$=C.includes("disabled")||C.includes("has not been used");if(i.push({name:$?"Pub/Sub API":`Topic: ${x}`,category:"pubsub",status:"error",message:$?"Pub/Sub API is not enabled":C,fix:$?{gcloud:`gcloud services enable pubsub.googleapis.com --project=${o}`,console:`${f}/apis/library/pubsub.googleapis.com?project=${o}`}:{gcloud:`gcloud pubsub topics create ${x} --project=${o}`,console:`${f}/cloudpubsub/topic/list?project=${o}`}}),$)break}}else i.push({name:"Pub/Sub Client",category:"pubsub",status:"warn",message:"PubSub client not available for config check"});if(F(c)){let g=i.every(x=>x.status==="ok");z(u,{project:o,healthy:g,checks:i});return}let S=g=>g==="ok"?'<span class="badge badge-ok">OK</span>':g==="warn"?'<span class="badge badge-warn">WARN</span>':'<span class="badge badge-err">ERROR</span>',R={bigquery:i.filter(g=>g.category==="bigquery"),pubsub:i.filter(g=>g.category==="pubsub"),firestore:i.filter(g=>g.category==="firestore")},v=(g,x)=>{if(x.length===0)return "";let T=x.map(C=>{let $="";if(C.fix){let m=[];C.fix.hint&&m.push(`<p class="muted">${C.fix.hint}</p>`),C.fix.gcloud&&m.push(`<pre>$ ${C.fix.gcloud}</pre>`),C.fix.console&&m.push(`<p><a href="${C.fix.console}" target="_blank">Open GCP Console \u2192</a></p>`),$=`<div style="margin-top:.5rem">${m.join("")}</div>`;}return `<tr>
74
74
  <td>${S(C.status)}</td>
75
75
  <td><strong>${C.name}</strong><br><span class="muted">${C.message}</span>${$}</td>
76
76
  </tr>`}).join(`
77
77
  `);return `<h2>${g}</h2>
78
78
  <table><thead><tr><th style="width:80px">Status</th><th>Check</th></tr></thead>
79
- <tbody>${R}</tbody></table>`},P=i.every(g=>g.status==="ok")?'<span class="badge badge-ok">All checks passed</span>':'<span class="badge badge-warn">Some issues found</span>',I=N("Config Check",a,`<div class="card">
80
- <p>Project: <code>${o}</code> ${P}</p>
81
- ${T("BigQuery",v.bigquery)}
82
- ${T("Pub/Sub",v.pubsub)}
83
- ${T("Firestore",v.firestore)}
84
- </div>`);_(d,I);}),async(c,d)=>{await f.handle(c,d);}}function Y(n,e){let t=e.columns.map(s=>{let r=s.isPrimaryKey?" NOT NULL":"";return ` ${n.quoteIdentifier(s.name)} ${s.sqlType}${r}`}).join(`,
79
+ <tbody>${T}</tbody></table>`},E=i.every(g=>g.status==="ok")?'<span class="badge badge-ok">All checks passed</span>':'<span class="badge badge-warn">Some issues found</span>',I=N("Config Check",a,`<div class="card">
80
+ <p>Project: <code>${o}</code> ${E}</p>
81
+ ${v("BigQuery",R.bigquery)}
82
+ ${v("Pub/Sub",R.pubsub)}
83
+ ${v("Firestore",R.firestore)}
84
+ </div>`);O(u,I);}),async(c,u)=>{await l.handle(c,u);}}function X(n,e){let t=e.columns.map(r=>{let s=r.isPrimaryKey?" NOT NULL":"";return ` ${n.quoteIdentifier(r.name)} ${r.sqlType}${s}`}).join(`,
85
85
  `);return `CREATE TABLE IF NOT EXISTS ${n.quoteIdentifier(e.tableName)} (
86
86
  ${t}
87
- );`}function de(n,e,t){return t.map(s=>`ALTER TABLE ${n.quoteIdentifier(e)} ADD COLUMN ${n.quoteIdentifier(s.name)} ${s.sqlType};`).join(`
88
- `)}function ue(n,e,t){let s=[];for(let[r,u]of Object.entries(n)){let h=u.schema??u._schema??void 0;if(!h)continue;let x=t?.repos?.[r],l=x?.tableName??r,k=u._systemKeys?.[0]??u.documentKey??"docId",y=E(h,e,{primaryKey:k,exclude:x?.exclude,columnMap:x?.columnMap}),f={tableName:l,columns:y};s.push(Y(e,f));}return s.join(`
87
+ );`}function pe(n,e,t){return t.map(r=>`ALTER TABLE ${n.quoteIdentifier(e)} ADD COLUMN ${n.quoteIdentifier(r.name)} ${r.sqlType};`).join(`
88
+ `)}function fe(n,e,t){let r=[];for(let[s,d]of Object.entries(n)){let h=d.schema??d._schema??void 0;if(!h)continue;let w=t?.repos?.[s],p=w?.tableName??s,k=d._systemKeys?.[0]??d.documentKey??"docId",y=P(h,e,{primaryKey:k,exclude:w?.exclude,columnMap:w?.columnMap}),l={tableName:p,columns:y};r.push(X(e,l));}return r.join(`
89
89
 
90
- `)}async function le(n,e,t){let s={created:[],altered:[],upToDate:[],skipped:[]};for(let[r,u]of Object.entries(n)){let h=u.schema??void 0;if(!h){s.skipped.push(r);continue}let x=t?.repos?.[r],l=x?.tableName??r,k=u._systemKeys?.[0]??u.documentKey??"docId",y=E(h,e.dialect,{primaryKey:k,exclude:x?.exclude,columnMap:x?.columnMap}),f={tableName:l,columns:y};if(!await e.tableExists(l))await e.createTable(f),s.created.push(l);else {let d=new Set(await e.getTableColumns(l)),a=y.filter(o=>!d.has(o.name));a.length>0?(await e.addColumns(l,a),s.altered.push(l)):s.upToDate.push(l);}}return s}var q=class{constructor(e){this.buffer=[];this.flushing=false;this.flushPromise=null;this.timer=null;this.adapter=e.adapter,this.tableName=e.tableName,this.primaryKey=e.primaryKey,this.batchSize=e.batchSize??100,this.onFlushError=e.onFlushError;let t=e.flushIntervalMs??5e3;t>0&&(this.timer=setInterval(()=>{this.flush();},t),typeof this.timer=="object"&&"unref"in this.timer&&this.timer.unref());}get size(){return this.buffer.length}enqueue(...e){this.buffer.push(...e),this.buffer.length>=this.batchSize&&this.flush();}async flush(){for(;this.flushing&&this.flushPromise;)await this.flushPromise;this.buffer.length!==0&&(this.flushing=true,this.flushPromise=this._doFlush().finally(()=>{this.flushing=false,this.flushPromise=null;}),await this.flushPromise);}async _doFlush(){let e=this.buffer.splice(0,this.batchSize);try{let t=new Map,s=[];for(let u of e)if(u.operation==="DELETE")s.push(u.docId),t.delete(u.docId);else if(u.data){let h=t.get(u.docId);if(!h)t.set(u.docId,u.data);else {let x=Number(h[D]??0);Number(u.data[D]??0)>=x&&t.set(u.docId,u.data);}}let r=Array.from(t.values());r.length>0&&await this.adapter.upsertRows(this.tableName,r,this.primaryKey),s.length>0&&await this.adapter.deleteRows(this.tableName,this.primaryKey,s);}catch(t){this.onFlushError?await this.onFlushError(e,t):(this.buffer.unshift(...e),console.error(`[SyncQueue] Flush failed for ${this.tableName}:`,t));}}async shutdown(){this.timer&&(clearInterval(this.timer),this.timer=null),await this.flush();}};var pe="firestore-sync";function fe(n,e){let t=e.ref?.path??void 0;return t?`${t}/{docId}`:(console.warn(`[SyncTriggers] Cannot determine collection path for "${n}". Skipping.`),null)}function ge(n,e){let{onDocumentCreated:t,onDocumentUpdated:s,onDocumentDeleted:r}=e.deps.firestoreTriggers,u=e.deps.pubsub,h=e?.topicPrefix??pe,x={},l=new Map;function k(f){let c=l.get(f);return c||(c=u.topic(f),l.set(f,c),c)}async function y(f,c){await k(f).publishMessage({json:c});}for(let[f,c]of Object.entries(n)){let d=e?.repos?.[f],a;if(c._isGroup){if(!d?.triggerPath){console.warn(`[SyncTriggers] Skipping collection-group repo "${f}". Provide a triggerPath in the sync repos config for group collections.`);continue}a=d.triggerPath;}else a=d?.triggerPath??fe(f,c);if(!a)continue;let o=c._systemKeys?.[0]??"docId",p=`${h}-${f}`;x[`${f}_onCreate`]=t(a,async b=>{let i=b.data;if(!i)return;let S=i.data();if(!S)return;let v=String(S[o]??i.id),T=j(S,{exclude:d?.exclude,columnMap:d?.columnMap}),A={operation:"INSERT",repoName:f,docId:v,data:T,timestamp:new Date().toISOString(),version:Date.now()};await y(p,A);}),x[`${f}_onUpdate`]=s(a,async b=>{let i=b.data?.after;if(!i)return;let S=i.data();if(!S)return;let v=String(S[o]??i.id),T=j(S,{exclude:d?.exclude,columnMap:d?.columnMap}),A={operation:"UPSERT",repoName:f,docId:v,data:T,timestamp:new Date().toISOString(),version:Date.now()};await y(p,A);}),x[`${f}_onDelete`]=r(a,async b=>{let i=b.data;if(!i)return;let S=i.data(),v=String(S?.[o]??i.id),T={operation:"DELETE",repoName:f,docId:v,data:null,timestamp:new Date().toISOString(),version:Date.now()};await y(p,T);});}return x}var X=new Set;async function me(n,e,t,s,r,u,h){if(X.has(n))return;let x=E(t,e.dialect,{primaryKey:r,exclude:u,columnMap:h});if(!await e.tableExists(s))await e.createTable({tableName:s,columns:x});else {let k=new Set(await e.getTableColumns(s)),y=x.filter(f=>!k.has(f.name));y.length>0&&await e.addColumns(s,y);}X.add(n);}function he(n,e){let{deps:t,adapter:s,batchSize:r=100,flushIntervalMs:u=5e3,autoMigrate:h=false,topicPrefix:x="firestore-sync",workerOptions:l,repos:k={}}=e,y=new Map;function f(a,o){let p=y.get(a);if(p)return p;let i=k[a]?.tableName??a,S=async(v,T)=>{console.error(`[SyncWorker] Flush failed for "${a}" (${v.length} events):`,T);let A=`${x}-${a}-dlq`,P=t.pubsub.topic(A),[I]=await P.exists();I||(await P.create(),console.info(`[SyncWorker] Created DLQ topic "${A}"`));for(let g of v)await P.publishMessage({json:g});};return p=new q({adapter:s,tableName:i,primaryKey:o,batchSize:r,flushIntervalMs:u,onFlushError:S}),y.set(a,p),p}async function c(a){let{repoName:o}=a,p=n[o];if(!p){console.warn(`[SyncWorker] Unknown repo "${o}", skipping event`);return}let b=p._systemKeys?.[0]??p.documentKey??"docId",i=k[o],S=i?.columnMap,v=S?.[b]??b;if(h){let A=p.schema??void 0;if(A){let P=i?.tableName??o;await me(o,s,A,P,b,i?.exclude,S);}}let T=f(o,v);a.data&&(a.data[D]=a.version??Date.now()),T.enqueue(a);}function d(a){let o=async p=>{let b=p.data?.message?.json??p.data?.json;if(!b){console.warn("[SyncWorker] Received empty PubSub message");return}await c(b);let i=y.get(b.repoName);i&&await i.flush();};return l?t.pubsubHandler.onMessagePublished({topic:a,...l},o):t.pubsubHandler.onMessagePublished(a,o)}return {handleMessage:c,createHandler:d,queues:y,async shutdown(){let a=[];for(let o of y.values())a.push(o.shutdown());await Promise.all(a);}}}export{q as SyncQueue,de as addColumnsDDL,le as autoMigrate,ge as createSyncTriggers,he as createSyncWorker,Y as createTableDDL,ce as createadminsyncServer,ue as generateDDL,j as serializeDocument,K as serializeValue,E as zodSchemaToColumns,ae as zodTypeToLogical};//# sourceMappingURL=index.js.map
90
+ `)}async function ge(n,e,t){let r={created:[],altered:[],upToDate:[],skipped:[]};for(let[s,d]of Object.entries(n)){let h=d.schema??void 0;if(!h){r.skipped.push(s);continue}let w=t?.repos?.[s],p=w?.tableName??s,k=d._systemKeys?.[0]??d.documentKey??"docId",y=P(h,e.dialect,{primaryKey:k,exclude:w?.exclude,columnMap:w?.columnMap}),l={tableName:p,columns:y};if(!await e.tableExists(p))await e.createTable(l),r.created.push(p);else {let u=new Set(await e.getTableColumns(p)),a=y.filter(o=>!u.has(o.name));a.length>0?(await e.addColumns(p,a),r.altered.push(p)):r.upToDate.push(p);}}return r}var M=class{constructor(e){this.buffer=[];this.flushing=false;this.flushPromise=null;this.timer=null;this.adapter=e.adapter,this.tableName=e.tableName,this.primaryKey=e.primaryKey,this.batchSize=e.batchSize??100,this.onFlushError=e.onFlushError;let t=e.flushIntervalMs??5e3;t>0&&(this.timer=setInterval(()=>{this.flush();},t),typeof this.timer=="object"&&"unref"in this.timer&&this.timer.unref());}get size(){return this.buffer.length}enqueue(...e){this.buffer.push(...e),this.buffer.length>=this.batchSize&&this.flush();}async flush(){for(;this.flushing&&this.flushPromise;)await this.flushPromise;this.buffer.length!==0&&(this.flushing=true,this.flushPromise=this._doFlush().finally(()=>{this.flushing=false,this.flushPromise=null;}),await this.flushPromise);}async _doFlush(){let e=this.buffer.splice(0,this.batchSize);try{let t=new Map,r=[];for(let d of e)if(d.operation==="DELETE")r.push(d.docId),t.delete(d.docId);else if(d.data){let h=t.get(d.docId);if(!h)t.set(d.docId,d.data);else {let w=Number(h[D]??0);Number(d.data[D]??0)>=w&&t.set(d.docId,d.data);}}let s=Array.from(t.values());s.length>0&&await this.adapter.upsertRows(this.tableName,s,this.primaryKey),r.length>0&&await this.adapter.deleteRows(this.tableName,this.primaryKey,r);}catch(t){this.onFlushError?await this.onFlushError(e,t):(this.buffer.unshift(...e),console.error(`[SyncQueue] Flush failed for ${this.tableName}:`,t));}}async shutdown(){this.timer&&(clearInterval(this.timer),this.timer=null),await this.flush();}};var me="firestore-sync";function he(n,e){let t=e.ref?.path??void 0;return t?`${t}/{docId}`:(console.warn(`[SyncTriggers] Cannot determine collection path for "${n}". Skipping.`),null)}function ye(n,e){let{onDocumentCreated:t,onDocumentUpdated:r,onDocumentDeleted:s}=e.deps.firestoreTriggers,d=e.deps.pubsub,h=e?.topicPrefix??me,w={},p=new Map;function k(l){let c=p.get(l);return c||(c=d.topic(l),p.set(l,c),c)}async function y(l,c){await k(l).publishMessage({json:c});}for(let[l,c]of Object.entries(n)){let u=e?.repos?.[l],a;if(c._isGroup){if(!u?.triggerPath){console.warn(`[SyncTriggers] Skipping collection-group repo "${l}". Provide a triggerPath in the sync repos config for group collections.`);continue}a=u.triggerPath;}else a=u?.triggerPath??he(l,c);if(!a)continue;let o=c._systemKeys?.[0]??"docId",f=`${h}-${l}`;w[`${l}_onCreate`]=t(a,async b=>{let i=b.data;if(!i)return;let S=i.data();if(!S)return;let R=String(S[o]??i.id),v=j(S,{exclude:u?.exclude,columnMap:u?.columnMap}),A={operation:"INSERT",repoName:l,docId:R,data:v,timestamp:new Date().toISOString(),version:Date.now()};await y(f,A);}),w[`${l}_onUpdate`]=r(a,async b=>{let i=b.data?.after;if(!i)return;let S=i.data();if(!S)return;let R=String(S[o]??i.id),v=j(S,{exclude:u?.exclude,columnMap:u?.columnMap}),A={operation:"UPSERT",repoName:l,docId:R,data:v,timestamp:new Date().toISOString(),version:Date.now()};await y(f,A);}),w[`${l}_onDelete`]=s(a,async b=>{let i=b.data;if(!i)return;let S=i.data(),R=String(S?.[o]??i.id),v={operation:"DELETE",repoName:l,docId:R,data:null,timestamp:new Date().toISOString(),version:Date.now()};await y(f,v);});}return w}function ee(n){let e=n.toUpperCase();switch(e){case "INTEGER":return "INT64";case "FLOAT":return "FLOAT64";case "BOOLEAN":return "BOOL";default:return e}}function te(n,e){let t=ee(n),r=ee(e);return t===r?true:{INT64:["NUMERIC","BIGNUMERIC","FLOAT64"],NUMERIC:["BIGNUMERIC","FLOAT64"],DATE:["DATETIME","TIMESTAMP"],DATETIME:["TIMESTAMP"]}[t]?.includes(r)??false}var ne=new Set,G=class extends Error{constructor(t,r,s,d){super(`Schema drift detected on \`${t}\`: column \`${r}\` has type ${s} in BigQuery but the current Zod schema maps it to ${d}. BigQuery cannot safely convert between these types \u2014 to resolve, either (a) keep the BigQuery type and add a transform in your repo to coerce values, (b) rename the field in your Zod schema (creates a new column), or (c) drop & recreate the table.`);this.tableName=t;this.column=r;this.existingType=s;this.desiredType=d;this.name="SchemaTypeMismatchError";}};async function be(n,e,t,r,s,d,h){if(ne.has(n))return;let w=P(t,e.dialect,{primaryKey:s,exclude:d,columnMap:h});if(!await e.tableExists(r))await e.createTable({tableName:r,columns:w});else if(e.getTableColumnsWithTypes){let k=await e.getTableColumnsWithTypes(r),y=[];for(let l of w){let c=k.get(l.name);if(c===void 0){y.push(l);continue}if(e.dialect.name==="bigquery"&&!te(c,l.sqlType))throw new G(r,l.name,c,l.sqlType)}y.length>0&&(await e.addColumns(r,y),await e.onSchemaChange?.(r));}else {let k=new Set(await e.getTableColumns(r)),y=w.filter(l=>!k.has(l.name));y.length>0&&(await e.addColumns(r,y),await e.onSchemaChange?.(r));}ne.add(n);}function Se(n,e){let{deps:t,adapter:r,batchSize:s=100,flushIntervalMs:d=5e3,autoMigrate:h=false,topicPrefix:w="firestore-sync",workerOptions:p,repos:k={}}=e,y=new Map;function l(a,o){let f=y.get(a);if(f)return f;let i=k[a]?.tableName??a,S=async(R,v)=>{console.error(`[SyncWorker] Flush failed for "${a}" (${R.length} events):`,v);let A=`${w}-${a}-dlq`,E=t.pubsub.topic(A),[I]=await E.exists();I||(await E.create(),console.info(`[SyncWorker] Created DLQ topic "${A}"`));for(let g of R)await E.publishMessage({json:g});};return f=new M({adapter:r,tableName:i,primaryKey:o,batchSize:s,flushIntervalMs:d,onFlushError:S}),y.set(a,f),f}async function c(a){let{repoName:o}=a,f=n[o];if(!f){console.warn(`[SyncWorker] Unknown repo "${o}", skipping event`);return}let b=f._systemKeys?.[0]??f.documentKey??"docId",i=k[o],S=i?.columnMap,R=S?.[b]??b;if(h){let A=f.schema??void 0;if(A){let E=i?.tableName??o;await be(o,r,A,E,b,i?.exclude,S);}}let v=l(o,R);a.data&&(a.data[D]=a.version??Date.now()),v.enqueue(a);}function u(a){let o=async f=>{let b=f.data?.message?.json??f.data?.json;if(!b){console.warn("[SyncWorker] Received empty PubSub message");return}await c(b);let i=y.get(b.repoName);i&&await i.flush();};return p?t.pubsubHandler.onMessagePublished({topic:a,...p},o):t.pubsubHandler.onMessagePublished(a,o)}return {handleMessage:c,createHandler:u,queues:y,async shutdown(){let a=[];for(let o of y.values())a.push(o.shutdown());await Promise.all(a);}}}export{M as SyncQueue,pe as addColumnsDDL,ge as autoMigrate,ye as createSyncTriggers,Se as createSyncWorker,X as createTableDDL,le as createadminsyncServer,fe as generateDDL,j as serializeDocument,K as serializeValue,P as zodSchemaToColumns,ue as zodTypeToLogical};//# sourceMappingURL=index.js.map
91
91
  //# sourceMappingURL=index.js.map