@lpdjs/firestore-repo-service 2.1.18 → 2.2.1
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/sync/bigquery.d.cts +4 -1
- package/dist/sync/bigquery.d.ts +4 -1
- package/dist/sync/index.cjs +5 -5
- package/dist/sync/index.d.cts +8 -12
- package/dist/sync/index.d.ts +8 -12
- package/dist/sync/index.js +5 -5
- package/dist/{types-CbeFN-A9.d.cts → types-BbCdscqh.d.cts} +27 -10
- package/dist/{types-CbeFN-A9.d.ts → types-BbCdscqh.d.ts} +27 -10
- package/package.json +9 -9
package/dist/sync/bigquery.d.cts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { c as SqlDialect, S as SqlAdapter, e as SqlTableDef, d as SqlColumn } from '../types-
|
|
1
|
+
import { c as SqlDialect, S as SqlAdapter, e as SqlTableDef, d as SqlColumn } from '../types-BbCdscqh.cjs';
|
|
2
|
+
import 'firebase-functions/v2/firestore';
|
|
3
|
+
import 'firebase-functions/v2/https';
|
|
4
|
+
import 'firebase-functions/v2/pubsub';
|
|
2
5
|
|
|
3
6
|
/** Shared BigQuery dialect singleton. */
|
|
4
7
|
declare const bigqueryDialect: SqlDialect;
|
package/dist/sync/bigquery.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { c as SqlDialect, S as SqlAdapter, e as SqlTableDef, d as SqlColumn } from '../types-
|
|
1
|
+
import { c as SqlDialect, S as SqlAdapter, e as SqlTableDef, d as SqlColumn } from '../types-BbCdscqh.js';
|
|
2
|
+
import 'firebase-functions/v2/firestore';
|
|
3
|
+
import 'firebase-functions/v2/https';
|
|
4
|
+
import 'firebase-functions/v2/pubsub';
|
|
2
5
|
|
|
3
6
|
/** Shared BigQuery dialect singleton. */
|
|
4
7
|
declare const bigqueryDialect: SqlDialect;
|
package/dist/sync/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
'use strict';function re(o){let e=[],t=o.replace(/[.*+?^${}()|[\]\\]/g,a=>a===":"?a:`\\${a}`).replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g,(a,s)=>(e.push(s),"([^/]+)"));return {pattern:new RegExp(`^${t}$`),paramNames:e}}function se(o){let e=o.path??o.url??"/",t=e.indexOf("?");return t===-1?e:e.slice(0,t)}var L=class{constructor(){this.routes=[];this.middlewares=[];this.notFoundHandler=(e,t)=>{t.status(404).send("Not Found");};this.errorHandler=(e,t,a)=>{console.error("[MiniRouter]",e),a.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,a){let{pattern:s,paramNames:u}=re(t);return this.routes.push({method:e.toUpperCase(),pattern:s,paramNames:u,handler:a}),this}async handle(e,t){let a=(e.method??"GET").toUpperCase(),s=se(e),u=null,g={};for(let R of this.routes){if(R.method!==a)continue;let b=s.match(R.pattern);if(b){u=R,g={},R.paramNames.forEach((f,d)=>{g[f]=decodeURIComponent(b[d+1]??"");});break}}let w=Object.assign(e,{params:g}),c=u?u.handler:this.notFoundHandler;try{await this.runMiddlewareChain(w,t,c);}catch(R){this.errorHandler(R,e,t);}}async runMiddlewareChain(e,t,a){let s=0,u=async()=>{if(s<this.middlewares.length){let g=this.middlewares[s++];await g(e,t,u);}else await a(e,t);};await u();}};var ae={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 F(o){let e=o,t=e._zod?.def?.type;if(t)return ae[t]??`Zod${t.charAt(0).toUpperCase()}${t.slice(1)}`;let a=e._def?.typeName;return a||""}function
|
|
1
|
+
'use strict';function re(o){let e=[],t=o.replace(/[.*+?^${}()|[\]\\]/g,a=>a===":"?a:`\\${a}`).replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g,(a,s)=>(e.push(s),"([^/]+)"));return {pattern:new RegExp(`^${t}$`),paramNames:e}}function se(o){let e=o.path??o.url??"/",t=e.indexOf("?");return t===-1?e:e.slice(0,t)}var L=class{constructor(){this.routes=[];this.middlewares=[];this.notFoundHandler=(e,t)=>{t.status(404).send("Not Found");};this.errorHandler=(e,t,a)=>{console.error("[MiniRouter]",e),a.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,a){let{pattern:s,paramNames:u}=re(t);return this.routes.push({method:e.toUpperCase(),pattern:s,paramNames:u,handler:a}),this}async handle(e,t){let a=(e.method??"GET").toUpperCase(),s=se(e),u=null,g={};for(let R of this.routes){if(R.method!==a)continue;let b=s.match(R.pattern);if(b){u=R,g={},R.paramNames.forEach((f,d)=>{g[f]=decodeURIComponent(b[d+1]??"");});break}}let w=Object.assign(e,{params:g}),c=u?u.handler:this.notFoundHandler;try{await this.runMiddlewareChain(w,t,c);}catch(R){this.errorHandler(R,e,t);}}async runMiddlewareChain(e,t,a){let s=0,u=async()=>{if(s<this.middlewares.length){let g=this.middlewares[s++];await g(e,t,u);}else await a(e,t);};await u();}};var ae={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 F(o){let e=o,t=e._zod?.def?.type;if(t)return ae[t]??`Zod${t.charAt(0).toUpperCase()}${t.slice(1)}`;let a=e._def?.typeName;return a||""}function W(o){let e=o;if(e._zod?.def?.innerType)return e._zod.def.innerType;if(e._def?.innerType)return e._def.innerType}function K(o){let e=o;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 _="__sync_version";var ie=new Set(["ZodOptional","ZodNullable","ZodDefault"]);function V(o){let e=o,t=false;for(;;){let a=F(e);if(!ie.has(a))break;(a==="ZodOptional"||a==="ZodNullable")&&(t=true);let s=W(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 ce(o){let{inner:e}=V(o);return J[F(e)]??"json"}function Y(o,e,t,a,s,u,g,w){for(let[c,R]of Object.entries(o)){let b=t?`${t}__${c}`:c;if(s.has(c)||s.has(b))continue;let{inner:f,nullable:d}=V(R),l=F(f),i=a||d;if(l==="ZodObject"){let r=K(f);Y(r,e,b,i,s,u,g,w);continue}let n=J[l]??"json",p=b===g||c===g,S=u[b]??u[c]??b;w.push({name:S,sqlType:e.mapType(n),nullable:p?false:i,isPrimaryKey:p});}}function O(o,e,t={}){let{primaryKey:a,exclude:s=[],columnMap:u={}}=t,g=new Set(s),w=K(o),c=[];return Y(w,e,"",false,g,u,a,c),c.some(R=>R.name===_)||c.push({name:_,sqlType:e.mapType("bigint"),nullable:true,isPrimaryKey:false,description:"Monotonic publish version (Date.now() ms). Internal."}),c}function Q(o){if(o==null)return null;if(typeof o=="object"&&typeof o.toDate=="function")return o.toDate().toISOString();if(o instanceof Date)return o.toISOString();if(Buffer.isBuffer(o))return o.toString("base64");if(o instanceof Uint8Array)return Buffer.from(o).toString("base64");if(typeof o=="object"&&"latitude"in o&&"longitude"in o){let e=o;return JSON.stringify({lat:e.latitude,lng:e.longitude})}return Array.isArray(o)?JSON.stringify(o.map(Q)):o}function X(o,e,t){for(let[a,s]of Object.entries(o)){let u=e?`${e}__${a}`:a;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)?X(s,u,t):t[u]=Q(s);}}function Z(o,e){let t=new Set(e?.exclude),a=e?.columnMap??{},s={};X(o,"",s);let u={};for(let[g,w]of Object.entries(s)){if(t.has(g))continue;let c=g.split("__")[0];if(c!==g&&t.has(c))continue;let R=a[g]??(g.includes("__")?a[g.split("__").pop()]:void 0)??g;u[R]=w;}return u}function q(o,e){if(process.env.FUNCTIONS_EMULATOR==="true"){let s=process.env.GCLOUD_PROJECT??process.env.GOOGLE_CLOUD_PROJECT??"demo-project",u=process.env.FUNCTION_REGION??"us-central1",g=(process.env.FUNCTION_TARGET??"").replace(/\./g,"-");return `/${s}/${u}/${g}${e}`}let t=process.env.K_SERVICE,a=o.hostname??o.headers?.host??"";return t&&a.includes("cloudfunctions.net")?`/${t.toLowerCase()}${e}`:e}function D(o,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>${o} \u2014 Sync Admin</title>
|
|
@@ -42,7 +42,7 @@ ${t}
|
|
|
42
42
|
</table>
|
|
43
43
|
${p}
|
|
44
44
|
${S}
|
|
45
|
-
</div>`);E(l,r);}),f.get(`${c}`,(d,l)=>{let i=q(d,c);l.status(302).set("Location",`${i}/`).send("");}),R.healthCheck&&f.get(`${c}/:repoName/health`,async(d,l)=>{let i=q(d,c),n=b.find(y=>y.name===d.params.repoName);if(!n){E(l,D("Not Found",i,`<p>Unknown repo: ${d.params.repoName}</p>`),404);return}if(!n.schema){E(l,D("Health Check",i,`<p class="badge badge-warn">No Zod schema attached to "${n.name}"</p>`));return}let p=
|
|
45
|
+
</div>`);E(l,r);}),f.get(`${c}`,(d,l)=>{let i=q(d,c);l.status(302).set("Location",`${i}/`).send("");}),R.healthCheck&&f.get(`${c}/:repoName/health`,async(d,l)=>{let i=q(d,c),n=b.find(y=>y.name===d.params.repoName);if(!n){E(l,D("Not Found",i,`<p>Unknown repo: ${d.params.repoName}</p>`),404);return}if(!n.schema){E(l,D("Health Check",i,`<p class="badge badge-warn">No Zod schema attached to "${n.name}"</p>`));return}let p=O(n.schema,e.dialect,{primaryKey:n.documentKey,exclude:n.repoCfg?.exclude,columnMap:n.repoCfg?.columnMap}),S=[],r=false,h=null;try{r=await e.tableExists(n.tableName),r&&(S=await e.getTableColumns(n.tableName));}catch(y){h=y?.message??String(y);}let C=new Set(S),k=new Set(p.map(y=>y.name)),x=p.filter(y=>!C.has(y.name)),P=S.filter(y=>!k.has(y)),A=p.filter(y=>C.has(y.name)),m=r&&x.length===0&&!h;if(I(d)){z(l,{repo:n.name,table:n.tableName,tableExists:r,healthy:m,error:h,columns:{expected:p.map(y=>({name:y.name,type:y.sqlType,nullable:y.nullable,isPrimaryKey:y.isPrimaryKey})),actual:S,matched:A.map(y=>y.name),missing:x.map(y=>({name:y.name,type:y.sqlType})),extra:P}});return}let $=m?'<span class="badge badge-ok">Healthy</span>':'<span class="badge badge-err">Unhealthy</span>',T=p.map(y=>{let j=C.has(y.name)?'<span class="badge badge-ok">OK</span>':'<span class="badge badge-err">MISSING</span>';return `<tr><td>${y.name}</td><td>${y.sqlType}</td><td>${y.nullable?"Yes":"No"}</td><td>${y.isPrimaryKey?"\u2713":""}</td><td>${j}</td></tr>`}).join(`
|
|
46
46
|
`),v=P.map(y=>`<tr><td>${y}</td><td colspan="3" class="muted">not in schema</td><td><span class="badge badge-warn">EXTRA</span></td></tr>`).join(`
|
|
47
47
|
`),N=D(`Health: ${n.name}`,i,`<div class="card">
|
|
48
48
|
<p>Table: <code>${n.tableName}</code> ${r?$:'<span class="badge badge-err">NOT FOUND</span>'}</p>
|
|
@@ -88,11 +88,11 @@ ${t}
|
|
|
88
88
|
${k("BigQuery",C.bigquery)}
|
|
89
89
|
${k("Pub/Sub",C.pubsub)}
|
|
90
90
|
${k("Firestore",C.firestore)}
|
|
91
|
-
</div>`);E(l,A);}),async(d,l)=>{await f.handle(d,l);}}var de="firestore-sync";function ue(o,e){let t=e.ref?.path??void 0;return t?`${t}/{docId}`:(console.warn(`[SyncTriggers] Cannot determine collection path for "${o}". Skipping.`),null)}function H(o,e){let{onDocumentCreated:t,onDocumentUpdated:a,onDocumentDeleted:s}=e.deps.firestoreTriggers,u=e.deps.pubsub,g=e?.topicPrefix??de,w={},c=new Map;function R(f){let d=c.get(f);return d||(d=u.topic(f),c.set(f,d),d)}async function b(f,d){await R(f).publishMessage({json:d});}for(let[f,d]of Object.entries(o)){let l=e?.repos?.[f],i;if(d._isGroup){if(!l?.triggerPath){console.warn(`[SyncTriggers] Skipping collection-group repo "${f}". Provide a triggerPath in the sync repos config for group collections.`);continue}i=l.triggerPath;}else i=l?.triggerPath??ue(f,d);if(!i)continue;let n=d._systemKeys?.[0]??"docId",p=`${g}-${f}`;w[`${f}_onCreate`]=t(i,async S=>{let r=S.data;if(!r)return;let h=r.data();if(!h)return;let C=String(h[n]??r.id),k=Z(h,{exclude:l?.exclude,columnMap:l?.columnMap}),x={operation:"INSERT",repoName:f,docId:C,data:k,timestamp:new Date().toISOString(),version:Date.now()};await b(p,x);}),w[`${f}_onUpdate`]=a(i,async S=>{let r=S.data?.after;if(!r)return;let h=r.data();if(!h)return;let C=String(h[n]??r.id),k=Z(h,{exclude:l?.exclude,columnMap:l?.columnMap}),x={operation:"UPSERT",repoName:f,docId:C,data:k,timestamp:new Date().toISOString(),version:Date.now()};await b(p,x);}),w[`${f}_onDelete`]=s(i,async S=>{let r=S.data;if(!r)return;let h=r.data(),C=String(h?.[n]??r.id),k={operation:"DELETE",repoName:f,docId:C,data:null,timestamp:new Date().toISOString(),version:Date.now()};await b(p,k);});}return w}var M=class{constructor(e){this.buffer=[];this.flushing=false;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(){if(this.flushing||this.buffer.length===0)return;this.flushing=true;let e=this.buffer.splice(0,this.batchSize);try{let t=new Map,a=[];for(let u of e)if(u.operation==="DELETE")a.push(u.docId),t.delete(u.docId);else if(u.data){let g=t.get(u.docId);if(!g)t.set(u.docId,u.data);else {let w=Number(g[
|
|
91
|
+
</div>`);E(l,A);}),async(d,l)=>{await f.handle(d,l);}}var de="firestore-sync";function ue(o,e){let t=e.ref?.path??void 0;return t?`${t}/{docId}`:(console.warn(`[SyncTriggers] Cannot determine collection path for "${o}". Skipping.`),null)}function H(o,e){let{onDocumentCreated:t,onDocumentUpdated:a,onDocumentDeleted:s}=e.deps.firestoreTriggers,u=e.deps.pubsub,g=e?.topicPrefix??de,w={},c=new Map;function R(f){let d=c.get(f);return d||(d=u.topic(f),c.set(f,d),d)}async function b(f,d){await R(f).publishMessage({json:d});}for(let[f,d]of Object.entries(o)){let l=e?.repos?.[f],i;if(d._isGroup){if(!l?.triggerPath){console.warn(`[SyncTriggers] Skipping collection-group repo "${f}". Provide a triggerPath in the sync repos config for group collections.`);continue}i=l.triggerPath;}else i=l?.triggerPath??ue(f,d);if(!i)continue;let n=d._systemKeys?.[0]??"docId",p=`${g}-${f}`;w[`${f}_onCreate`]=t(i,async S=>{let r=S.data;if(!r)return;let h=r.data();if(!h)return;let C=String(h[n]??r.id),k=Z(h,{exclude:l?.exclude,columnMap:l?.columnMap}),x={operation:"INSERT",repoName:f,docId:C,data:k,timestamp:new Date().toISOString(),version:Date.now()};await b(p,x);}),w[`${f}_onUpdate`]=a(i,async S=>{let r=S.data?.after;if(!r)return;let h=r.data();if(!h)return;let C=String(h[n]??r.id),k=Z(h,{exclude:l?.exclude,columnMap:l?.columnMap}),x={operation:"UPSERT",repoName:f,docId:C,data:k,timestamp:new Date().toISOString(),version:Date.now()};await b(p,x);}),w[`${f}_onDelete`]=s(i,async S=>{let r=S.data;if(!r)return;let h=r.data(),C=String(h?.[n]??r.id),k={operation:"DELETE",repoName:f,docId:C,data:null,timestamp:new Date().toISOString(),version:Date.now()};await b(p,k);});}return w}var M=class{constructor(e){this.buffer=[];this.flushing=false;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(){if(this.flushing||this.buffer.length===0)return;this.flushing=true;let e=this.buffer.splice(0,this.batchSize);try{let t=new Map,a=[];for(let u of e)if(u.operation==="DELETE")a.push(u.docId),t.delete(u.docId);else if(u.data){let g=t.get(u.docId);if(!g)t.set(u.docId,u.data);else {let w=Number(g[_]??0);Number(u.data[_]??0)>=w&&t.set(u.docId,u.data);}}let s=Array.from(t.values());s.length>0&&await this.adapter.upsertRows(this.tableName,s,this.primaryKey),a.length>0&&await this.adapter.deleteRows(this.tableName,this.primaryKey,a);}catch(t){this.onFlushError?await this.onFlushError(e,t).catch(a=>{console.error(`[SyncQueue] Flush error for ${this.tableName}:`,t),console.error("[SyncQueue] Error handler also failed:",a);}):(this.buffer.unshift(...e),console.error(`[SyncQueue] Flush failed for ${this.tableName}:`,t));}finally{this.flushing=false;}}async shutdown(){this.timer&&(clearInterval(this.timer),this.timer=null),await this.flush();}};var ee=new Set;async function le(o,e,t,a,s,u,g){if(ee.has(o))return;let w=O(t,e.dialect,{primaryKey:s,exclude:u,columnMap:g});if(!await e.tableExists(a))await e.createTable({tableName:a,columns:w});else {let R=new Set(await e.getTableColumns(a)),b=w.filter(f=>!R.has(f.name));b.length>0&&await e.addColumns(a,b);}ee.add(o);}function G(o,e){let{deps:t,adapter:a,batchSize:s=100,flushIntervalMs:u=5e3,autoMigrate:g=false,topicPrefix:w="firestore-sync",workerOptions:c,repos:R={}}=e,b=new Map;function f(i,n){let p=b.get(i);if(p)return p;let r=R[i]?.tableName??i,h=async(C,k)=>{console.error(`[SyncWorker] Flush failed for "${i}" (${C.length} events):`,k);try{let x=`${w}-${i}-dlq`,P=t.pubsub.topic(x),[A]=await P.exists();A||(await P.create(),console.info(`[SyncWorker] Created DLQ topic "${x}"`));for(let m of C)await P.publishMessage({json:m});}catch(x){console.error(`[SyncWorker] Dead-letter publish also failed for ${i}:`,x);}};return p=new M({adapter:a,tableName:r,primaryKey:n,batchSize:s,flushIntervalMs:u,onFlushError:h}),b.set(i,p),p}async function d(i){let{repoName:n}=i,p=o[n];if(!p){console.warn(`[SyncWorker] Unknown repo "${n}", skipping event`);return}let S=p._systemKeys?.[0]??p.documentKey??"docId",r=R[n],h=r?.columnMap,C=h?.[S]??S;if(g){let x=p.schema??void 0;if(x){let P=r?.tableName??n;await le(n,a,x,P,S,r?.exclude,h);}}let k=f(n,C);i.data&&(i.data[_]=i.version??Date.now()),k.enqueue(i);}function l(i){let n=async p=>{let S=p.data?.message?.json??p.data?.json;if(!S){console.warn("[SyncWorker] Received empty PubSub message");return}await d(S);let r=b.get(S.repoName);r&&await r.flush();};return c?t.pubsubHandler.onMessagePublished({topic:i,...c},n):t.pubsubHandler.onMessagePublished(i,n)}return {handleMessage:d,createHandler:l,queues:b,async shutdown(){let i=[];for(let n of b.values())i.push(n.shutdown());await Promise.all(i);}}}var pe="firestore-sync";function te(o){if(typeof o!="function")return o;let e=o,t;return new Proxy({},{get(a,s){return t||(t=e()),t[s]},has(a,s){return t||(t=e()),s in t}})}function fe(o,e){let{deps:t,adapter:a,topicPrefix:s=pe,batchSize:u,flushIntervalMs:g,autoMigrate:w,admin:c,workerOptions:R,repos:b}=e,f=te(t.pubsub),d=te(a),l=H(o,{deps:{firestoreTriggers:t.firestoreTriggers,pubsub:f},topicPrefix:s,repos:b}),i=G(o,{deps:{pubsubHandler:t.pubsubHandler,pubsub:f},adapter:d,batchSize:u,flushIntervalMs:g,autoMigrate:w,topicPrefix:s,workerOptions:R,repos:b}),n={};for(let r of Object.keys(o))n[`sync_${r}`]=i.createHandler(`${s}-${r}`);let p=null;c&&(p=B(o,d,i.queues,i.handleMessage,c,b??{},f,s),n.adminsync=c.onRequest?c.httpsOptions?c.onRequest(c.httpsOptions,p):c.onRequest(p):p);let S={functions:{...l,...n},adminHandler:p,handleMessage:i.handleMessage,queues:i.queues,shutdown:i.shutdown};for(let r of ["adminHandler","handleMessage","queues","shutdown"])Object.defineProperty(S,r,{enumerable:false});return S}function ne(o,e){let t=e.columns.map(a=>{let s=a.isPrimaryKey?" NOT NULL":"";return ` ${o.quoteIdentifier(a.name)} ${a.sqlType}${s}`}).join(`,
|
|
92
92
|
`);return `CREATE TABLE IF NOT EXISTS ${o.quoteIdentifier(e.tableName)} (
|
|
93
93
|
${t}
|
|
94
94
|
);`}function me(o,e,t){return t.map(a=>`ALTER TABLE ${o.quoteIdentifier(e)} ADD COLUMN ${o.quoteIdentifier(a.name)} ${a.sqlType};`).join(`
|
|
95
|
-
`)}function ge(o,e,t){let a=[];for(let[s,u]of Object.entries(o)){let g=u.schema??u._schema??void 0;if(!g)continue;let w=t?.repos?.[s],c=w?.tableName??s,R=u._systemKeys?.[0]??u.documentKey??"docId",b=
|
|
95
|
+
`)}function ge(o,e,t){let a=[];for(let[s,u]of Object.entries(o)){let g=u.schema??u._schema??void 0;if(!g)continue;let w=t?.repos?.[s],c=w?.tableName??s,R=u._systemKeys?.[0]??u.documentKey??"docId",b=O(g,e,{primaryKey:R,exclude:w?.exclude,columnMap:w?.columnMap}),f={tableName:c,columns:b};a.push(ne(e,f));}return a.join(`
|
|
96
96
|
|
|
97
|
-
`)}async function ye(o,e,t){let a={created:[],altered:[],upToDate:[],skipped:[]};for(let[s,u]of Object.entries(o)){let g=u.schema??void 0;if(!g){a.skipped.push(s);continue}let w=t?.repos?.[s],c=w?.tableName??s,R=u._systemKeys?.[0]??u.documentKey??"docId",b=
|
|
97
|
+
`)}async function ye(o,e,t){let a={created:[],altered:[],upToDate:[],skipped:[]};for(let[s,u]of Object.entries(o)){let g=u.schema??void 0;if(!g){a.skipped.push(s);continue}let w=t?.repos?.[s],c=w?.tableName??s,R=u._systemKeys?.[0]??u.documentKey??"docId",b=O(g,e.dialect,{primaryKey:R,exclude:w?.exclude,columnMap:w?.columnMap}),f={tableName:c,columns:b};if(!await e.tableExists(c))await e.createTable(f),a.created.push(c);else {let l=new Set(await e.getTableColumns(c)),i=b.filter(n=>!l.has(n.name));i.length>0?(await e.addColumns(c,i),a.altered.push(c)):a.upToDate.push(c);}}return a}exports.SyncQueue=M;exports.addColumnsDDL=me;exports.autoMigrate=ye;exports.createFirestoreSync=fe;exports.createSyncTriggers=H;exports.createSyncWorker=G;exports.createTableDDL=ne;exports.createadminsyncServer=B;exports.generateDDL=ge;exports.serializeDocument=Z;exports.serializeValue=Q;exports.zodSchemaToColumns=O;exports.zodTypeToLogical=ce;//# sourceMappingURL=index.cjs.map
|
|
98
98
|
//# sourceMappingURL=index.cjs.map
|
package/dist/sync/index.d.cts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { S as SqlAdapter, a as SyncEvent, b as adminsyncConfig, R as RepoSyncConfig, P as PubSubClientDep, F as FirestoreSyncConfig, c as SqlDialect, d as SqlColumn, e as SqlTableDef, G as GenerateDDLConfig, L as LogicalType, f as SyncTriggersConfig, g as SyncWorkerConfig } from '../types-
|
|
2
|
-
export { h as FirestoreTriggersDep, O as OrFactory, i as PubSubHandlerDep, j as SyncDeps, k as SyncOperation, l as
|
|
1
|
+
import { S as SqlAdapter, a as SyncEvent, b as adminsyncConfig, R as RepoSyncConfig, P as PubSubClientDep, F as FirestoreSyncConfig, c as SqlDialect, d as SqlColumn, e as SqlTableDef, G as GenerateDDLConfig, L as LogicalType, f as SyncTriggersConfig, g as SyncWorkerConfig } from '../types-BbCdscqh.cjs';
|
|
2
|
+
export { A as AdminHttpsOptions, 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-BbCdscqh.cjs';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
+
import * as firebase_functions_core from 'firebase-functions/core';
|
|
5
|
+
import * as firebase_functions_pubsub from 'firebase-functions/pubsub';
|
|
6
|
+
import 'firebase-functions/v2/firestore';
|
|
7
|
+
import 'firebase-functions/v2/https';
|
|
8
|
+
import 'firebase-functions/v2/pubsub';
|
|
4
9
|
|
|
5
10
|
/**
|
|
6
11
|
* Per-repo in-memory batch buffer.
|
|
@@ -282,15 +287,6 @@ declare function serializeDocument(doc: Record<string, unknown>, options?: Pick<
|
|
|
282
287
|
*/
|
|
283
288
|
declare function createSyncTriggers<M extends Record<string, any>>(repoMapping: M, config: SyncTriggersConfig<NoInfer<M>>): Record<string, any>;
|
|
284
289
|
|
|
285
|
-
/**
|
|
286
|
-
* PubSub worker — creates a Cloud Function that receives {@link SyncEvent}
|
|
287
|
-
* messages from PubSub, routes them to per-repo {@link SyncQueue}s, and
|
|
288
|
-
* flushes batches to the configured {@link SqlAdapter}.
|
|
289
|
-
*
|
|
290
|
-
* Dependencies (`firebase-functions`, `@google-cloud/pubsub`) are injected
|
|
291
|
-
* via the `deps` config property.
|
|
292
|
-
*/
|
|
293
|
-
|
|
294
290
|
/**
|
|
295
291
|
* Create a PubSub-triggered Cloud Function that syncs Firestore changes
|
|
296
292
|
* to a SQL database.
|
|
@@ -305,7 +301,7 @@ declare function createSyncWorker<M extends Record<string, any>>(repoMapping: M,
|
|
|
305
301
|
/** Process a SyncEvent directly (for testing or custom PubSub integration). */
|
|
306
302
|
handleMessage: (syncEvent: SyncEvent) => Promise<void>;
|
|
307
303
|
/** Create a Cloud Function handler for a specific PubSub topic. */
|
|
308
|
-
createHandler: (topicName: string) => any
|
|
304
|
+
createHandler: (topicName: string) => firebase_functions_core.CloudFunction<firebase_functions_core.CloudEvent<firebase_functions_pubsub.MessagePublishedData<any>>>;
|
|
309
305
|
/** Internal queue map (for testing). */
|
|
310
306
|
queues: Map<string, SyncQueue>;
|
|
311
307
|
/** Flush all queues and stop timers. */
|
package/dist/sync/index.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { S as SqlAdapter, a as SyncEvent, b as adminsyncConfig, R as RepoSyncConfig, P as PubSubClientDep, F as FirestoreSyncConfig, c as SqlDialect, d as SqlColumn, e as SqlTableDef, G as GenerateDDLConfig, L as LogicalType, f as SyncTriggersConfig, g as SyncWorkerConfig } from '../types-
|
|
2
|
-
export { h as FirestoreTriggersDep, O as OrFactory, i as PubSubHandlerDep, j as SyncDeps, k as SyncOperation, l as
|
|
1
|
+
import { S as SqlAdapter, a as SyncEvent, b as adminsyncConfig, R as RepoSyncConfig, P as PubSubClientDep, F as FirestoreSyncConfig, c as SqlDialect, d as SqlColumn, e as SqlTableDef, G as GenerateDDLConfig, L as LogicalType, f as SyncTriggersConfig, g as SyncWorkerConfig } from '../types-BbCdscqh.js';
|
|
2
|
+
export { A as AdminHttpsOptions, 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-BbCdscqh.js';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
+
import * as firebase_functions_core from 'firebase-functions/core';
|
|
5
|
+
import * as firebase_functions_pubsub from 'firebase-functions/pubsub';
|
|
6
|
+
import 'firebase-functions/v2/firestore';
|
|
7
|
+
import 'firebase-functions/v2/https';
|
|
8
|
+
import 'firebase-functions/v2/pubsub';
|
|
4
9
|
|
|
5
10
|
/**
|
|
6
11
|
* Per-repo in-memory batch buffer.
|
|
@@ -282,15 +287,6 @@ declare function serializeDocument(doc: Record<string, unknown>, options?: Pick<
|
|
|
282
287
|
*/
|
|
283
288
|
declare function createSyncTriggers<M extends Record<string, any>>(repoMapping: M, config: SyncTriggersConfig<NoInfer<M>>): Record<string, any>;
|
|
284
289
|
|
|
285
|
-
/**
|
|
286
|
-
* PubSub worker — creates a Cloud Function that receives {@link SyncEvent}
|
|
287
|
-
* messages from PubSub, routes them to per-repo {@link SyncQueue}s, and
|
|
288
|
-
* flushes batches to the configured {@link SqlAdapter}.
|
|
289
|
-
*
|
|
290
|
-
* Dependencies (`firebase-functions`, `@google-cloud/pubsub`) are injected
|
|
291
|
-
* via the `deps` config property.
|
|
292
|
-
*/
|
|
293
|
-
|
|
294
290
|
/**
|
|
295
291
|
* Create a PubSub-triggered Cloud Function that syncs Firestore changes
|
|
296
292
|
* to a SQL database.
|
|
@@ -305,7 +301,7 @@ declare function createSyncWorker<M extends Record<string, any>>(repoMapping: M,
|
|
|
305
301
|
/** Process a SyncEvent directly (for testing or custom PubSub integration). */
|
|
306
302
|
handleMessage: (syncEvent: SyncEvent) => Promise<void>;
|
|
307
303
|
/** Create a Cloud Function handler for a specific PubSub topic. */
|
|
308
|
-
createHandler: (topicName: string) => any
|
|
304
|
+
createHandler: (topicName: string) => firebase_functions_core.CloudFunction<firebase_functions_core.CloudEvent<firebase_functions_pubsub.MessagePublishedData<any>>>;
|
|
309
305
|
/** Internal queue map (for testing). */
|
|
310
306
|
queues: Map<string, SyncQueue>;
|
|
311
307
|
/** Flush all queues and stop timers. */
|
package/dist/sync/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
function re(o){let e=[],t=o.replace(/[.*+?^${}()|[\]\\]/g,a=>a===":"?a:`\\${a}`).replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g,(a,s)=>(e.push(s),"([^/]+)"));return {pattern:new RegExp(`^${t}$`),paramNames:e}}function se(o){let e=o.path??o.url??"/",t=e.indexOf("?");return t===-1?e:e.slice(0,t)}var L=class{constructor(){this.routes=[];this.middlewares=[];this.notFoundHandler=(e,t)=>{t.status(404).send("Not Found");};this.errorHandler=(e,t,a)=>{console.error("[MiniRouter]",e),a.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,a){let{pattern:s,paramNames:u}=re(t);return this.routes.push({method:e.toUpperCase(),pattern:s,paramNames:u,handler:a}),this}async handle(e,t){let a=(e.method??"GET").toUpperCase(),s=se(e),u=null,g={};for(let R of this.routes){if(R.method!==a)continue;let b=s.match(R.pattern);if(b){u=R,g={},R.paramNames.forEach((f,d)=>{g[f]=decodeURIComponent(b[d+1]??"");});break}}let w=Object.assign(e,{params:g}),c=u?u.handler:this.notFoundHandler;try{await this.runMiddlewareChain(w,t,c);}catch(R){this.errorHandler(R,e,t);}}async runMiddlewareChain(e,t,a){let s=0,u=async()=>{if(s<this.middlewares.length){let g=this.middlewares[s++];await g(e,t,u);}else await a(e,t);};await u();}};var ae={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 F(o){let e=o,t=e._zod?.def?.type;if(t)return ae[t]??`Zod${t.charAt(0).toUpperCase()}${t.slice(1)}`;let a=e._def?.typeName;return a||""}function
|
|
1
|
+
function re(o){let e=[],t=o.replace(/[.*+?^${}()|[\]\\]/g,a=>a===":"?a:`\\${a}`).replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g,(a,s)=>(e.push(s),"([^/]+)"));return {pattern:new RegExp(`^${t}$`),paramNames:e}}function se(o){let e=o.path??o.url??"/",t=e.indexOf("?");return t===-1?e:e.slice(0,t)}var L=class{constructor(){this.routes=[];this.middlewares=[];this.notFoundHandler=(e,t)=>{t.status(404).send("Not Found");};this.errorHandler=(e,t,a)=>{console.error("[MiniRouter]",e),a.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,a){let{pattern:s,paramNames:u}=re(t);return this.routes.push({method:e.toUpperCase(),pattern:s,paramNames:u,handler:a}),this}async handle(e,t){let a=(e.method??"GET").toUpperCase(),s=se(e),u=null,g={};for(let R of this.routes){if(R.method!==a)continue;let b=s.match(R.pattern);if(b){u=R,g={},R.paramNames.forEach((f,d)=>{g[f]=decodeURIComponent(b[d+1]??"");});break}}let w=Object.assign(e,{params:g}),c=u?u.handler:this.notFoundHandler;try{await this.runMiddlewareChain(w,t,c);}catch(R){this.errorHandler(R,e,t);}}async runMiddlewareChain(e,t,a){let s=0,u=async()=>{if(s<this.middlewares.length){let g=this.middlewares[s++];await g(e,t,u);}else await a(e,t);};await u();}};var ae={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 F(o){let e=o,t=e._zod?.def?.type;if(t)return ae[t]??`Zod${t.charAt(0).toUpperCase()}${t.slice(1)}`;let a=e._def?.typeName;return a||""}function W(o){let e=o;if(e._zod?.def?.innerType)return e._zod.def.innerType;if(e._def?.innerType)return e._def.innerType}function K(o){let e=o;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 _="__sync_version";var ie=new Set(["ZodOptional","ZodNullable","ZodDefault"]);function V(o){let e=o,t=false;for(;;){let a=F(e);if(!ie.has(a))break;(a==="ZodOptional"||a==="ZodNullable")&&(t=true);let s=W(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 ce(o){let{inner:e}=V(o);return J[F(e)]??"json"}function Y(o,e,t,a,s,u,g,w){for(let[c,R]of Object.entries(o)){let b=t?`${t}__${c}`:c;if(s.has(c)||s.has(b))continue;let{inner:f,nullable:d}=V(R),l=F(f),i=a||d;if(l==="ZodObject"){let r=K(f);Y(r,e,b,i,s,u,g,w);continue}let n=J[l]??"json",p=b===g||c===g,S=u[b]??u[c]??b;w.push({name:S,sqlType:e.mapType(n),nullable:p?false:i,isPrimaryKey:p});}}function O(o,e,t={}){let{primaryKey:a,exclude:s=[],columnMap:u={}}=t,g=new Set(s),w=K(o),c=[];return Y(w,e,"",false,g,u,a,c),c.some(R=>R.name===_)||c.push({name:_,sqlType:e.mapType("bigint"),nullable:true,isPrimaryKey:false,description:"Monotonic publish version (Date.now() ms). Internal."}),c}function Q(o){if(o==null)return null;if(typeof o=="object"&&typeof o.toDate=="function")return o.toDate().toISOString();if(o instanceof Date)return o.toISOString();if(Buffer.isBuffer(o))return o.toString("base64");if(o instanceof Uint8Array)return Buffer.from(o).toString("base64");if(typeof o=="object"&&"latitude"in o&&"longitude"in o){let e=o;return JSON.stringify({lat:e.latitude,lng:e.longitude})}return Array.isArray(o)?JSON.stringify(o.map(Q)):o}function X(o,e,t){for(let[a,s]of Object.entries(o)){let u=e?`${e}__${a}`:a;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)?X(s,u,t):t[u]=Q(s);}}function Z(o,e){let t=new Set(e?.exclude),a=e?.columnMap??{},s={};X(o,"",s);let u={};for(let[g,w]of Object.entries(s)){if(t.has(g))continue;let c=g.split("__")[0];if(c!==g&&t.has(c))continue;let R=a[g]??(g.includes("__")?a[g.split("__").pop()]:void 0)??g;u[R]=w;}return u}function q(o,e){if(process.env.FUNCTIONS_EMULATOR==="true"){let s=process.env.GCLOUD_PROJECT??process.env.GOOGLE_CLOUD_PROJECT??"demo-project",u=process.env.FUNCTION_REGION??"us-central1",g=(process.env.FUNCTION_TARGET??"").replace(/\./g,"-");return `/${s}/${u}/${g}${e}`}let t=process.env.K_SERVICE,a=o.hostname??o.headers?.host??"";return t&&a.includes("cloudfunctions.net")?`/${t.toLowerCase()}${e}`:e}function D(o,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>${o} \u2014 Sync Admin</title>
|
|
@@ -42,7 +42,7 @@ ${t}
|
|
|
42
42
|
</table>
|
|
43
43
|
${p}
|
|
44
44
|
${S}
|
|
45
|
-
</div>`);E(l,r);}),f.get(`${c}`,(d,l)=>{let i=q(d,c);l.status(302).set("Location",`${i}/`).send("");}),R.healthCheck&&f.get(`${c}/:repoName/health`,async(d,l)=>{let i=q(d,c),n=b.find(y=>y.name===d.params.repoName);if(!n){E(l,D("Not Found",i,`<p>Unknown repo: ${d.params.repoName}</p>`),404);return}if(!n.schema){E(l,D("Health Check",i,`<p class="badge badge-warn">No Zod schema attached to "${n.name}"</p>`));return}let p=
|
|
45
|
+
</div>`);E(l,r);}),f.get(`${c}`,(d,l)=>{let i=q(d,c);l.status(302).set("Location",`${i}/`).send("");}),R.healthCheck&&f.get(`${c}/:repoName/health`,async(d,l)=>{let i=q(d,c),n=b.find(y=>y.name===d.params.repoName);if(!n){E(l,D("Not Found",i,`<p>Unknown repo: ${d.params.repoName}</p>`),404);return}if(!n.schema){E(l,D("Health Check",i,`<p class="badge badge-warn">No Zod schema attached to "${n.name}"</p>`));return}let p=O(n.schema,e.dialect,{primaryKey:n.documentKey,exclude:n.repoCfg?.exclude,columnMap:n.repoCfg?.columnMap}),S=[],r=false,h=null;try{r=await e.tableExists(n.tableName),r&&(S=await e.getTableColumns(n.tableName));}catch(y){h=y?.message??String(y);}let C=new Set(S),k=new Set(p.map(y=>y.name)),x=p.filter(y=>!C.has(y.name)),P=S.filter(y=>!k.has(y)),A=p.filter(y=>C.has(y.name)),m=r&&x.length===0&&!h;if(I(d)){z(l,{repo:n.name,table:n.tableName,tableExists:r,healthy:m,error:h,columns:{expected:p.map(y=>({name:y.name,type:y.sqlType,nullable:y.nullable,isPrimaryKey:y.isPrimaryKey})),actual:S,matched:A.map(y=>y.name),missing:x.map(y=>({name:y.name,type:y.sqlType})),extra:P}});return}let $=m?'<span class="badge badge-ok">Healthy</span>':'<span class="badge badge-err">Unhealthy</span>',T=p.map(y=>{let j=C.has(y.name)?'<span class="badge badge-ok">OK</span>':'<span class="badge badge-err">MISSING</span>';return `<tr><td>${y.name}</td><td>${y.sqlType}</td><td>${y.nullable?"Yes":"No"}</td><td>${y.isPrimaryKey?"\u2713":""}</td><td>${j}</td></tr>`}).join(`
|
|
46
46
|
`),v=P.map(y=>`<tr><td>${y}</td><td colspan="3" class="muted">not in schema</td><td><span class="badge badge-warn">EXTRA</span></td></tr>`).join(`
|
|
47
47
|
`),N=D(`Health: ${n.name}`,i,`<div class="card">
|
|
48
48
|
<p>Table: <code>${n.tableName}</code> ${r?$:'<span class="badge badge-err">NOT FOUND</span>'}</p>
|
|
@@ -88,11 +88,11 @@ ${t}
|
|
|
88
88
|
${k("BigQuery",C.bigquery)}
|
|
89
89
|
${k("Pub/Sub",C.pubsub)}
|
|
90
90
|
${k("Firestore",C.firestore)}
|
|
91
|
-
</div>`);E(l,A);}),async(d,l)=>{await f.handle(d,l);}}var de="firestore-sync";function ue(o,e){let t=e.ref?.path??void 0;return t?`${t}/{docId}`:(console.warn(`[SyncTriggers] Cannot determine collection path for "${o}". Skipping.`),null)}function H(o,e){let{onDocumentCreated:t,onDocumentUpdated:a,onDocumentDeleted:s}=e.deps.firestoreTriggers,u=e.deps.pubsub,g=e?.topicPrefix??de,w={},c=new Map;function R(f){let d=c.get(f);return d||(d=u.topic(f),c.set(f,d),d)}async function b(f,d){await R(f).publishMessage({json:d});}for(let[f,d]of Object.entries(o)){let l=e?.repos?.[f],i;if(d._isGroup){if(!l?.triggerPath){console.warn(`[SyncTriggers] Skipping collection-group repo "${f}". Provide a triggerPath in the sync repos config for group collections.`);continue}i=l.triggerPath;}else i=l?.triggerPath??ue(f,d);if(!i)continue;let n=d._systemKeys?.[0]??"docId",p=`${g}-${f}`;w[`${f}_onCreate`]=t(i,async S=>{let r=S.data;if(!r)return;let h=r.data();if(!h)return;let C=String(h[n]??r.id),k=Z(h,{exclude:l?.exclude,columnMap:l?.columnMap}),x={operation:"INSERT",repoName:f,docId:C,data:k,timestamp:new Date().toISOString(),version:Date.now()};await b(p,x);}),w[`${f}_onUpdate`]=a(i,async S=>{let r=S.data?.after;if(!r)return;let h=r.data();if(!h)return;let C=String(h[n]??r.id),k=Z(h,{exclude:l?.exclude,columnMap:l?.columnMap}),x={operation:"UPSERT",repoName:f,docId:C,data:k,timestamp:new Date().toISOString(),version:Date.now()};await b(p,x);}),w[`${f}_onDelete`]=s(i,async S=>{let r=S.data;if(!r)return;let h=r.data(),C=String(h?.[n]??r.id),k={operation:"DELETE",repoName:f,docId:C,data:null,timestamp:new Date().toISOString(),version:Date.now()};await b(p,k);});}return w}var M=class{constructor(e){this.buffer=[];this.flushing=false;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(){if(this.flushing||this.buffer.length===0)return;this.flushing=true;let e=this.buffer.splice(0,this.batchSize);try{let t=new Map,a=[];for(let u of e)if(u.operation==="DELETE")a.push(u.docId),t.delete(u.docId);else if(u.data){let g=t.get(u.docId);if(!g)t.set(u.docId,u.data);else {let w=Number(g[
|
|
91
|
+
</div>`);E(l,A);}),async(d,l)=>{await f.handle(d,l);}}var de="firestore-sync";function ue(o,e){let t=e.ref?.path??void 0;return t?`${t}/{docId}`:(console.warn(`[SyncTriggers] Cannot determine collection path for "${o}". Skipping.`),null)}function H(o,e){let{onDocumentCreated:t,onDocumentUpdated:a,onDocumentDeleted:s}=e.deps.firestoreTriggers,u=e.deps.pubsub,g=e?.topicPrefix??de,w={},c=new Map;function R(f){let d=c.get(f);return d||(d=u.topic(f),c.set(f,d),d)}async function b(f,d){await R(f).publishMessage({json:d});}for(let[f,d]of Object.entries(o)){let l=e?.repos?.[f],i;if(d._isGroup){if(!l?.triggerPath){console.warn(`[SyncTriggers] Skipping collection-group repo "${f}". Provide a triggerPath in the sync repos config for group collections.`);continue}i=l.triggerPath;}else i=l?.triggerPath??ue(f,d);if(!i)continue;let n=d._systemKeys?.[0]??"docId",p=`${g}-${f}`;w[`${f}_onCreate`]=t(i,async S=>{let r=S.data;if(!r)return;let h=r.data();if(!h)return;let C=String(h[n]??r.id),k=Z(h,{exclude:l?.exclude,columnMap:l?.columnMap}),x={operation:"INSERT",repoName:f,docId:C,data:k,timestamp:new Date().toISOString(),version:Date.now()};await b(p,x);}),w[`${f}_onUpdate`]=a(i,async S=>{let r=S.data?.after;if(!r)return;let h=r.data();if(!h)return;let C=String(h[n]??r.id),k=Z(h,{exclude:l?.exclude,columnMap:l?.columnMap}),x={operation:"UPSERT",repoName:f,docId:C,data:k,timestamp:new Date().toISOString(),version:Date.now()};await b(p,x);}),w[`${f}_onDelete`]=s(i,async S=>{let r=S.data;if(!r)return;let h=r.data(),C=String(h?.[n]??r.id),k={operation:"DELETE",repoName:f,docId:C,data:null,timestamp:new Date().toISOString(),version:Date.now()};await b(p,k);});}return w}var M=class{constructor(e){this.buffer=[];this.flushing=false;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(){if(this.flushing||this.buffer.length===0)return;this.flushing=true;let e=this.buffer.splice(0,this.batchSize);try{let t=new Map,a=[];for(let u of e)if(u.operation==="DELETE")a.push(u.docId),t.delete(u.docId);else if(u.data){let g=t.get(u.docId);if(!g)t.set(u.docId,u.data);else {let w=Number(g[_]??0);Number(u.data[_]??0)>=w&&t.set(u.docId,u.data);}}let s=Array.from(t.values());s.length>0&&await this.adapter.upsertRows(this.tableName,s,this.primaryKey),a.length>0&&await this.adapter.deleteRows(this.tableName,this.primaryKey,a);}catch(t){this.onFlushError?await this.onFlushError(e,t).catch(a=>{console.error(`[SyncQueue] Flush error for ${this.tableName}:`,t),console.error("[SyncQueue] Error handler also failed:",a);}):(this.buffer.unshift(...e),console.error(`[SyncQueue] Flush failed for ${this.tableName}:`,t));}finally{this.flushing=false;}}async shutdown(){this.timer&&(clearInterval(this.timer),this.timer=null),await this.flush();}};var ee=new Set;async function le(o,e,t,a,s,u,g){if(ee.has(o))return;let w=O(t,e.dialect,{primaryKey:s,exclude:u,columnMap:g});if(!await e.tableExists(a))await e.createTable({tableName:a,columns:w});else {let R=new Set(await e.getTableColumns(a)),b=w.filter(f=>!R.has(f.name));b.length>0&&await e.addColumns(a,b);}ee.add(o);}function G(o,e){let{deps:t,adapter:a,batchSize:s=100,flushIntervalMs:u=5e3,autoMigrate:g=false,topicPrefix:w="firestore-sync",workerOptions:c,repos:R={}}=e,b=new Map;function f(i,n){let p=b.get(i);if(p)return p;let r=R[i]?.tableName??i,h=async(C,k)=>{console.error(`[SyncWorker] Flush failed for "${i}" (${C.length} events):`,k);try{let x=`${w}-${i}-dlq`,P=t.pubsub.topic(x),[A]=await P.exists();A||(await P.create(),console.info(`[SyncWorker] Created DLQ topic "${x}"`));for(let m of C)await P.publishMessage({json:m});}catch(x){console.error(`[SyncWorker] Dead-letter publish also failed for ${i}:`,x);}};return p=new M({adapter:a,tableName:r,primaryKey:n,batchSize:s,flushIntervalMs:u,onFlushError:h}),b.set(i,p),p}async function d(i){let{repoName:n}=i,p=o[n];if(!p){console.warn(`[SyncWorker] Unknown repo "${n}", skipping event`);return}let S=p._systemKeys?.[0]??p.documentKey??"docId",r=R[n],h=r?.columnMap,C=h?.[S]??S;if(g){let x=p.schema??void 0;if(x){let P=r?.tableName??n;await le(n,a,x,P,S,r?.exclude,h);}}let k=f(n,C);i.data&&(i.data[_]=i.version??Date.now()),k.enqueue(i);}function l(i){let n=async p=>{let S=p.data?.message?.json??p.data?.json;if(!S){console.warn("[SyncWorker] Received empty PubSub message");return}await d(S);let r=b.get(S.repoName);r&&await r.flush();};return c?t.pubsubHandler.onMessagePublished({topic:i,...c},n):t.pubsubHandler.onMessagePublished(i,n)}return {handleMessage:d,createHandler:l,queues:b,async shutdown(){let i=[];for(let n of b.values())i.push(n.shutdown());await Promise.all(i);}}}var pe="firestore-sync";function te(o){if(typeof o!="function")return o;let e=o,t;return new Proxy({},{get(a,s){return t||(t=e()),t[s]},has(a,s){return t||(t=e()),s in t}})}function fe(o,e){let{deps:t,adapter:a,topicPrefix:s=pe,batchSize:u,flushIntervalMs:g,autoMigrate:w,admin:c,workerOptions:R,repos:b}=e,f=te(t.pubsub),d=te(a),l=H(o,{deps:{firestoreTriggers:t.firestoreTriggers,pubsub:f},topicPrefix:s,repos:b}),i=G(o,{deps:{pubsubHandler:t.pubsubHandler,pubsub:f},adapter:d,batchSize:u,flushIntervalMs:g,autoMigrate:w,topicPrefix:s,workerOptions:R,repos:b}),n={};for(let r of Object.keys(o))n[`sync_${r}`]=i.createHandler(`${s}-${r}`);let p=null;c&&(p=B(o,d,i.queues,i.handleMessage,c,b??{},f,s),n.adminsync=c.onRequest?c.httpsOptions?c.onRequest(c.httpsOptions,p):c.onRequest(p):p);let S={functions:{...l,...n},adminHandler:p,handleMessage:i.handleMessage,queues:i.queues,shutdown:i.shutdown};for(let r of ["adminHandler","handleMessage","queues","shutdown"])Object.defineProperty(S,r,{enumerable:false});return S}function ne(o,e){let t=e.columns.map(a=>{let s=a.isPrimaryKey?" NOT NULL":"";return ` ${o.quoteIdentifier(a.name)} ${a.sqlType}${s}`}).join(`,
|
|
92
92
|
`);return `CREATE TABLE IF NOT EXISTS ${o.quoteIdentifier(e.tableName)} (
|
|
93
93
|
${t}
|
|
94
94
|
);`}function me(o,e,t){return t.map(a=>`ALTER TABLE ${o.quoteIdentifier(e)} ADD COLUMN ${o.quoteIdentifier(a.name)} ${a.sqlType};`).join(`
|
|
95
|
-
`)}function ge(o,e,t){let a=[];for(let[s,u]of Object.entries(o)){let g=u.schema??u._schema??void 0;if(!g)continue;let w=t?.repos?.[s],c=w?.tableName??s,R=u._systemKeys?.[0]??u.documentKey??"docId",b=
|
|
95
|
+
`)}function ge(o,e,t){let a=[];for(let[s,u]of Object.entries(o)){let g=u.schema??u._schema??void 0;if(!g)continue;let w=t?.repos?.[s],c=w?.tableName??s,R=u._systemKeys?.[0]??u.documentKey??"docId",b=O(g,e,{primaryKey:R,exclude:w?.exclude,columnMap:w?.columnMap}),f={tableName:c,columns:b};a.push(ne(e,f));}return a.join(`
|
|
96
96
|
|
|
97
|
-
`)}async function ye(o,e,t){let a={created:[],altered:[],upToDate:[],skipped:[]};for(let[s,u]of Object.entries(o)){let g=u.schema??void 0;if(!g){a.skipped.push(s);continue}let w=t?.repos?.[s],c=w?.tableName??s,R=u._systemKeys?.[0]??u.documentKey??"docId",b=
|
|
97
|
+
`)}async function ye(o,e,t){let a={created:[],altered:[],upToDate:[],skipped:[]};for(let[s,u]of Object.entries(o)){let g=u.schema??void 0;if(!g){a.skipped.push(s);continue}let w=t?.repos?.[s],c=w?.tableName??s,R=u._systemKeys?.[0]??u.documentKey??"docId",b=O(g,e.dialect,{primaryKey:R,exclude:w?.exclude,columnMap:w?.columnMap}),f={tableName:c,columns:b};if(!await e.tableExists(c))await e.createTable(f),a.created.push(c);else {let l=new Set(await e.getTableColumns(c)),i=b.filter(n=>!l.has(n.name));i.length>0?(await e.addColumns(c,i),a.altered.push(c)):a.upToDate.push(c);}}return a}export{M as SyncQueue,me as addColumnsDDL,ye as autoMigrate,fe as createFirestoreSync,H as createSyncTriggers,G as createSyncWorker,ne as createTableDDL,B as createadminsyncServer,ge as generateDDL,Z as serializeDocument,Q as serializeValue,O as zodSchemaToColumns,ce as zodTypeToLogical};//# sourceMappingURL=index.js.map
|
|
98
98
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { onDocumentCreated, onDocumentUpdated, onDocumentDeleted } from 'firebase-functions/v2/firestore';
|
|
2
|
+
import { onRequest, HttpsOptions } from 'firebase-functions/v2/https';
|
|
3
|
+
import { onMessagePublished, PubSubOptions } from 'firebase-functions/v2/pubsub';
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* Types and interfaces for the Firestore → SQL sync module.
|
|
3
7
|
*
|
|
@@ -5,6 +9,18 @@
|
|
|
5
9
|
* worker) and any SQL backend (BigQuery, PostgreSQL, …). Only the adapter
|
|
6
10
|
* touches the database SDK; everything else works with these abstractions.
|
|
7
11
|
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Cloud Functions v2 options forwarded to `onMessagePublished()` for every
|
|
15
|
+
* sync worker handler. The `topic` field is omitted because the library sets
|
|
16
|
+
* it itself from `topicPrefix` + repo name.
|
|
17
|
+
*/
|
|
18
|
+
type SyncWorkerOptions = Omit<PubSubOptions, "topic">;
|
|
19
|
+
/**
|
|
20
|
+
* Cloud Functions v2 options forwarded to `onRequest()` for the admin handler.
|
|
21
|
+
* Re-export of `HttpsOptions` from `firebase-functions/v2/https`.
|
|
22
|
+
*/
|
|
23
|
+
type AdminHttpsOptions = HttpsOptions;
|
|
8
24
|
/** A value that can be provided directly or as a lazy factory function. */
|
|
9
25
|
type OrFactory<T> = T | (() => T);
|
|
10
26
|
/** A single column in a SQL table. */
|
|
@@ -165,13 +181,13 @@ type TypedRepoSyncConfigs<M> = {
|
|
|
165
181
|
};
|
|
166
182
|
/** Firestore trigger constructors from `firebase-functions/v2/firestore`. */
|
|
167
183
|
interface FirestoreTriggersDep {
|
|
168
|
-
onDocumentCreated:
|
|
169
|
-
onDocumentUpdated:
|
|
170
|
-
onDocumentDeleted:
|
|
184
|
+
onDocumentCreated: typeof onDocumentCreated;
|
|
185
|
+
onDocumentUpdated: typeof onDocumentUpdated;
|
|
186
|
+
onDocumentDeleted: typeof onDocumentDeleted;
|
|
171
187
|
}
|
|
172
188
|
/** PubSub handler from `firebase-functions/v2/pubsub`. */
|
|
173
189
|
interface PubSubHandlerDep {
|
|
174
|
-
onMessagePublished:
|
|
190
|
+
onMessagePublished: typeof onMessagePublished;
|
|
175
191
|
}
|
|
176
192
|
/** PubSub client instance (e.g. `new PubSub()`). */
|
|
177
193
|
interface PubSubClientDep {
|
|
@@ -223,7 +239,7 @@ interface SyncWorkerConfig<M = Record<string, any>> {
|
|
|
223
239
|
* workerOptions: { concurrency: 10, maxInstances: 10, memory: "512MiB" }
|
|
224
240
|
* ```
|
|
225
241
|
*/
|
|
226
|
-
workerOptions?:
|
|
242
|
+
workerOptions?: SyncWorkerOptions;
|
|
227
243
|
/** Per-repo overrides */
|
|
228
244
|
repos?: TypedRepoSyncConfigs<M>;
|
|
229
245
|
}
|
|
@@ -268,16 +284,17 @@ interface adminsyncConfig {
|
|
|
268
284
|
/** Feature flags controlling which endpoints are enabled */
|
|
269
285
|
featuresFlag?: adminsyncFeaturesFlag;
|
|
270
286
|
/**
|
|
271
|
-
* `onRequest` from `firebase-functions/https` (or `firebase-functions/
|
|
287
|
+
* `onRequest` from `firebase-functions/v2/https` (or `firebase-functions/https`,
|
|
288
|
+
* which re-exports the v2 version in `firebase-functions` ≥ 5).
|
|
272
289
|
* When provided, the admin handler is automatically wrapped as a Cloud Function.
|
|
273
290
|
* If omitted, the raw `(req, res) => void` handler is exposed instead.
|
|
274
291
|
*/
|
|
275
|
-
onRequest?:
|
|
292
|
+
onRequest?: typeof onRequest;
|
|
276
293
|
/**
|
|
277
294
|
* Options forwarded to `onRequest()` as the first argument (e.g. `{ invoker: "public" }`).
|
|
278
295
|
* Only used when `onRequest` is also provided.
|
|
279
296
|
*/
|
|
280
|
-
httpsOptions?:
|
|
297
|
+
httpsOptions?: AdminHttpsOptions;
|
|
281
298
|
}
|
|
282
299
|
/** Options for `createFirestoreSync()` — the unified wrapper. */
|
|
283
300
|
interface FirestoreSyncConfig<M = Record<string, any>> {
|
|
@@ -309,7 +326,7 @@ interface FirestoreSyncConfig<M = Record<string, any>> {
|
|
|
309
326
|
* worker handler. Use to tune `concurrency`, `maxInstances`, `minInstances`,
|
|
310
327
|
* `memory`, `timeoutSeconds`, `region`, `cpu`, etc.
|
|
311
328
|
*/
|
|
312
|
-
workerOptions?:
|
|
329
|
+
workerOptions?: SyncWorkerOptions;
|
|
313
330
|
/**
|
|
314
331
|
* Optional sync admin endpoint. When provided, a `adminsync` handler is
|
|
315
332
|
* added to `sync.functions` exposing health-check, force-sync, and queue
|
|
@@ -320,4 +337,4 @@ interface FirestoreSyncConfig<M = Record<string, any>> {
|
|
|
320
337
|
repos?: TypedRepoSyncConfigs<M>;
|
|
321
338
|
}
|
|
322
339
|
|
|
323
|
-
export type { FirestoreSyncConfig as F, GenerateDDLConfig as G, LogicalType as L, OrFactory as O, PubSubClientDep as P, RepoSyncConfig as R, SqlAdapter as S, SyncEvent as a, adminsyncConfig as b, SqlDialect as c, SqlColumn as d, SqlTableDef as e, SyncTriggersConfig as f, SyncWorkerConfig as g, FirestoreTriggersDep as h, PubSubHandlerDep as i, SyncDeps as j, SyncOperation as k,
|
|
340
|
+
export type { AdminHttpsOptions as A, FirestoreSyncConfig as F, GenerateDDLConfig as G, LogicalType as L, OrFactory as O, PubSubClientDep as P, RepoSyncConfig as R, SqlAdapter as S, SyncEvent as a, adminsyncConfig as b, SqlDialect as c, SqlColumn as d, SqlTableDef as e, SyncTriggersConfig as f, SyncWorkerConfig as g, FirestoreTriggersDep as h, PubSubHandlerDep as i, SyncDeps as j, SyncOperation as k, SyncWorkerOptions as l, adminsyncBasicAuth as m, adminsyncFeaturesFlag as n };
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { onDocumentCreated, onDocumentUpdated, onDocumentDeleted } from 'firebase-functions/v2/firestore';
|
|
2
|
+
import { onRequest, HttpsOptions } from 'firebase-functions/v2/https';
|
|
3
|
+
import { onMessagePublished, PubSubOptions } from 'firebase-functions/v2/pubsub';
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* Types and interfaces for the Firestore → SQL sync module.
|
|
3
7
|
*
|
|
@@ -5,6 +9,18 @@
|
|
|
5
9
|
* worker) and any SQL backend (BigQuery, PostgreSQL, …). Only the adapter
|
|
6
10
|
* touches the database SDK; everything else works with these abstractions.
|
|
7
11
|
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Cloud Functions v2 options forwarded to `onMessagePublished()` for every
|
|
15
|
+
* sync worker handler. The `topic` field is omitted because the library sets
|
|
16
|
+
* it itself from `topicPrefix` + repo name.
|
|
17
|
+
*/
|
|
18
|
+
type SyncWorkerOptions = Omit<PubSubOptions, "topic">;
|
|
19
|
+
/**
|
|
20
|
+
* Cloud Functions v2 options forwarded to `onRequest()` for the admin handler.
|
|
21
|
+
* Re-export of `HttpsOptions` from `firebase-functions/v2/https`.
|
|
22
|
+
*/
|
|
23
|
+
type AdminHttpsOptions = HttpsOptions;
|
|
8
24
|
/** A value that can be provided directly or as a lazy factory function. */
|
|
9
25
|
type OrFactory<T> = T | (() => T);
|
|
10
26
|
/** A single column in a SQL table. */
|
|
@@ -165,13 +181,13 @@ type TypedRepoSyncConfigs<M> = {
|
|
|
165
181
|
};
|
|
166
182
|
/** Firestore trigger constructors from `firebase-functions/v2/firestore`. */
|
|
167
183
|
interface FirestoreTriggersDep {
|
|
168
|
-
onDocumentCreated:
|
|
169
|
-
onDocumentUpdated:
|
|
170
|
-
onDocumentDeleted:
|
|
184
|
+
onDocumentCreated: typeof onDocumentCreated;
|
|
185
|
+
onDocumentUpdated: typeof onDocumentUpdated;
|
|
186
|
+
onDocumentDeleted: typeof onDocumentDeleted;
|
|
171
187
|
}
|
|
172
188
|
/** PubSub handler from `firebase-functions/v2/pubsub`. */
|
|
173
189
|
interface PubSubHandlerDep {
|
|
174
|
-
onMessagePublished:
|
|
190
|
+
onMessagePublished: typeof onMessagePublished;
|
|
175
191
|
}
|
|
176
192
|
/** PubSub client instance (e.g. `new PubSub()`). */
|
|
177
193
|
interface PubSubClientDep {
|
|
@@ -223,7 +239,7 @@ interface SyncWorkerConfig<M = Record<string, any>> {
|
|
|
223
239
|
* workerOptions: { concurrency: 10, maxInstances: 10, memory: "512MiB" }
|
|
224
240
|
* ```
|
|
225
241
|
*/
|
|
226
|
-
workerOptions?:
|
|
242
|
+
workerOptions?: SyncWorkerOptions;
|
|
227
243
|
/** Per-repo overrides */
|
|
228
244
|
repos?: TypedRepoSyncConfigs<M>;
|
|
229
245
|
}
|
|
@@ -268,16 +284,17 @@ interface adminsyncConfig {
|
|
|
268
284
|
/** Feature flags controlling which endpoints are enabled */
|
|
269
285
|
featuresFlag?: adminsyncFeaturesFlag;
|
|
270
286
|
/**
|
|
271
|
-
* `onRequest` from `firebase-functions/https` (or `firebase-functions/
|
|
287
|
+
* `onRequest` from `firebase-functions/v2/https` (or `firebase-functions/https`,
|
|
288
|
+
* which re-exports the v2 version in `firebase-functions` ≥ 5).
|
|
272
289
|
* When provided, the admin handler is automatically wrapped as a Cloud Function.
|
|
273
290
|
* If omitted, the raw `(req, res) => void` handler is exposed instead.
|
|
274
291
|
*/
|
|
275
|
-
onRequest?:
|
|
292
|
+
onRequest?: typeof onRequest;
|
|
276
293
|
/**
|
|
277
294
|
* Options forwarded to `onRequest()` as the first argument (e.g. `{ invoker: "public" }`).
|
|
278
295
|
* Only used when `onRequest` is also provided.
|
|
279
296
|
*/
|
|
280
|
-
httpsOptions?:
|
|
297
|
+
httpsOptions?: AdminHttpsOptions;
|
|
281
298
|
}
|
|
282
299
|
/** Options for `createFirestoreSync()` — the unified wrapper. */
|
|
283
300
|
interface FirestoreSyncConfig<M = Record<string, any>> {
|
|
@@ -309,7 +326,7 @@ interface FirestoreSyncConfig<M = Record<string, any>> {
|
|
|
309
326
|
* worker handler. Use to tune `concurrency`, `maxInstances`, `minInstances`,
|
|
310
327
|
* `memory`, `timeoutSeconds`, `region`, `cpu`, etc.
|
|
311
328
|
*/
|
|
312
|
-
workerOptions?:
|
|
329
|
+
workerOptions?: SyncWorkerOptions;
|
|
313
330
|
/**
|
|
314
331
|
* Optional sync admin endpoint. When provided, a `adminsync` handler is
|
|
315
332
|
* added to `sync.functions` exposing health-check, force-sync, and queue
|
|
@@ -320,4 +337,4 @@ interface FirestoreSyncConfig<M = Record<string, any>> {
|
|
|
320
337
|
repos?: TypedRepoSyncConfigs<M>;
|
|
321
338
|
}
|
|
322
339
|
|
|
323
|
-
export type { FirestoreSyncConfig as F, GenerateDDLConfig as G, LogicalType as L, OrFactory as O, PubSubClientDep as P, RepoSyncConfig as R, SqlAdapter as S, SyncEvent as a, adminsyncConfig as b, SqlDialect as c, SqlColumn as d, SqlTableDef as e, SyncTriggersConfig as f, SyncWorkerConfig as g, FirestoreTriggersDep as h, PubSubHandlerDep as i, SyncDeps as j, SyncOperation as k,
|
|
340
|
+
export type { AdminHttpsOptions as A, FirestoreSyncConfig as F, GenerateDDLConfig as G, LogicalType as L, OrFactory as O, PubSubClientDep as P, RepoSyncConfig as R, SqlAdapter as S, SyncEvent as a, adminsyncConfig as b, SqlDialect as c, SqlColumn as d, SqlTableDef as e, SyncTriggersConfig as f, SyncWorkerConfig as g, FirestoreTriggersDep as h, PubSubHandlerDep as i, SyncDeps as j, SyncOperation as k, SyncWorkerOptions as l, adminsyncBasicAuth as m, adminsyncFeaturesFlag as n };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lpdjs/firestore-repo-service",
|
|
3
|
-
"version": "2.1
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"description": "⚡ Type-safe Firestore ORM with auto-generated repositories, advanced queries, relations with typed select, pagination with include, batch/bulk operations, aggregations and transactions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
"@google-cloud/bigquery": "^7.0.0 || ^8.0.0",
|
|
153
153
|
"@google-cloud/pubsub": "^4.0.0 || ^5.0.0",
|
|
154
154
|
"firebase-admin": "^11.0.0 || ^12.0.0 || ^13.0.0",
|
|
155
|
-
"firebase-functions": "^
|
|
155
|
+
"firebase-functions": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
|
156
156
|
"typescript": "^5.0.0",
|
|
157
157
|
"zod": "^4"
|
|
158
158
|
},
|
|
@@ -168,20 +168,20 @@
|
|
|
168
168
|
}
|
|
169
169
|
},
|
|
170
170
|
"devDependencies": {
|
|
171
|
-
"@google-cloud/bigquery": "^8.
|
|
171
|
+
"@google-cloud/bigquery": "^8.3.0",
|
|
172
172
|
"@google-cloud/pubsub": "^5.3.0",
|
|
173
173
|
"@types/bun": "latest",
|
|
174
174
|
"@types/express": "latest",
|
|
175
175
|
"concurrently": "^9.2.1",
|
|
176
|
-
"firebase-admin": "^13.
|
|
177
|
-
"firebase-functions": "^7.2.
|
|
176
|
+
"firebase-admin": "^13.8.0",
|
|
177
|
+
"firebase-functions": "^7.2.5",
|
|
178
178
|
"tsup": "^8.5.1",
|
|
179
179
|
"typescript": "^5.9.3",
|
|
180
|
-
"wait-on": "^9.0.
|
|
181
|
-
"zod": "^4.3
|
|
180
|
+
"wait-on": "^9.0.5",
|
|
181
|
+
"zod": "^4.4.3"
|
|
182
182
|
},
|
|
183
183
|
"dependencies": {
|
|
184
|
-
"@hono/node-server": "^1.19.
|
|
185
|
-
"hono": "^4.12.
|
|
184
|
+
"@hono/node-server": "^1.19.14",
|
|
185
|
+
"hono": "^4.12.17"
|
|
186
186
|
}
|
|
187
187
|
}
|