@lpdjs/firestore-repo-service 2.3.1 → 2.3.3

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.
@@ -1,9 +1,9 @@
1
- 'use strict';var o="__sync_version";var d=class{constructor(){this.name="bigquery";}mapType(t){switch(t){case "string":return "STRING";case "number":return "FLOAT64";case "bigint":return "INT64";case "boolean":return "BOOL";case "timestamp":return "TIMESTAMP";case "json":return "JSON";case "text":return "STRING"}}quoteIdentifier(t){return `\`${t}\``}},f=new d,c=class c{constructor(t){this.bigquery=t.bigquery,this.datasetId=t.datasetId;}get dialect(){return f}async tableExists(t){let[e]=await this.dataset.table(t).exists();return e}async getTableColumns(t){let[e]=await this.dataset.table(t).getMetadata();return (e.schema?.fields??[]).map(s=>s.name)}async createTable(t){let e=a=>this.dialect.quoteIdentifier(a),r=t.columns.map(a=>{let n=a.isPrimaryKey?" NOT NULL":"";return ` ${e(a.name)} ${a.sqlType}${n}`}).join(`,
1
+ 'use strict';var l="__sync_version";function $(o){if(!o||typeof o!="object")return false;let t=o;return t.code!==400?false:(Array.isArray(t.errors)?t.errors:[]).some(n=>typeof n?.message=="string"&&n.message.toLowerCase().includes("serialize access"))}async function y(o,t=10,e=500){let n=0;for(;;)try{return await o()}catch(s){if(n++,!$(s)||n>t)throw s;let i=e*Math.pow(2,n),r=Math.random()*i;await new Promise(c=>setTimeout(c,r));}}var g=class{constructor(){this.name="bigquery";}mapType(t){switch(t){case "string":return "STRING";case "number":return "FLOAT64";case "bigint":return "INT64";case "boolean":return "BOOL";case "timestamp":return "TIMESTAMP";case "json":return "JSON";case "text":return "STRING"}}quoteIdentifier(t){return `\`${t}\``}},N=new g,u=class u{constructor(t){this.bigquery=t.bigquery,this.datasetId=t.datasetId;}get dialect(){return N}async tableExists(t){let[e]=await this.dataset.table(t).exists();return e}async getTableColumns(t){let[e]=await this.dataset.table(t).getMetadata();return (e.schema?.fields??[]).map(s=>s.name)}async createTable(t){let e=i=>this.dialect.quoteIdentifier(i),n=t.columns.map(i=>{let r=i.isPrimaryKey?" NOT NULL":"";return ` ${e(i.name)} ${i.sqlType}${r}`}).join(`,
2
2
  `),s=`CREATE TABLE IF NOT EXISTS ${this.fqn(t.tableName)} (
3
- ${r}
4
- );`;await this.bigquery.query({query:s});}async addColumns(t,e){let r=s=>this.dialect.quoteIdentifier(s);for(let s of e){let a=`ALTER TABLE ${this.fqn(t)} ADD COLUMN ${r(s.name)} ${s.sqlType};`;await this.bigquery.query({query:a});}}async insertRows(t,e){e.length!==0&&await this.dataset.table(t).insert(e);}async upsertRows(t,e,r){if(e.length===0)return;let s=Object.keys(e[0]),a=s.filter(i=>i!==r),n=i=>this.dialect.quoteIdentifier(i),q=e.map((i,E)=>`SELECT ${s.map(u=>E===0?`${this.escapeValue(i[u])} AS ${n(u)}`:this.escapeValue(i[u])).join(", ")}`).join(` UNION ALL
5
- `),y=a.map(i=>`T.${n(i)} = S.${n(i)}`).join(", "),S=s.map(i=>n(i)).join(", "),T=s.map(i=>`S.${n(i)}`).join(", "),m=s.includes(o)?` AND (T.${n(o)} IS NULL OR S.${n(o)} > T.${n(o)})`:"",p=[`MERGE ${this.fqn(t)} AS T`,`USING (
6
- ${q}
7
- ) AS S`,`ON T.${n(r)} = S.${n(r)}`,`WHEN MATCHED${m} THEN UPDATE SET ${y}`,`WHEN NOT MATCHED THEN INSERT (${S}) VALUES (${T});`].join(`
8
- `);await this.bigquery.query({query:p});}async deleteRows(t,e,r){if(r.length===0)return;let s=l=>this.dialect.quoteIdentifier(l),a=r.map(l=>this.escapeValue(l)).join(", "),n=`DELETE FROM ${this.fqn(t)} WHERE ${s(e)} IN (${a});`;await this.bigquery.query({query:n});}async executeRaw(t){await this.bigquery.query({query:t});}get dataset(){return this.bigquery.dataset(this.datasetId)}fqn(t){return `\`${this.datasetId}.${t}\``}escapeValue(t){return t==null?"NULL":typeof t=="boolean"?t?"TRUE":"FALSE":typeof t=="number"||typeof t=="bigint"?String(t):typeof t=="string"?c.ISO_TIMESTAMP_RE.test(t)?`TIMESTAMP('${t}')`:t.startsWith("[")&&t.endsWith("]")||t.startsWith("{")&&t.endsWith("}")?`PARSE_JSON('${t.replace(/'/g,"\\'")}')`:`'${t.replace(/'/g,"\\'")}'`:`PARSE_JSON('${JSON.stringify(t).replace(/'/g,"\\'")}')`}};c.ISO_TIMESTAMP_RE=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;var g=c;exports.BigQueryAdapter=g;exports.bigqueryDialect=f;//# sourceMappingURL=bigquery.cjs.map
3
+ ${n}
4
+ );`;await this.bigquery.query({query:s});}async addColumns(t,e){let n=s=>this.dialect.quoteIdentifier(s);for(let s of e){let i=`ALTER TABLE ${this.fqn(t)} ADD COLUMN ${n(s.name)} ${s.sqlType};`;await this.bigquery.query({query:i});}}async insertRows(t,e){e.length!==0&&await this.dataset.table(t).insert(e);}async upsertRows(t,e,n){if(e.length===0)return;let s=Object.keys(e[0]),i=s.filter(a=>a!==n),r=a=>this.dialect.quoteIdentifier(a),T=e.map((a,E)=>`SELECT ${s.map(d=>E===0?`${this.escapeValue(a[d])} AS ${r(d)}`:this.escapeValue(a[d])).join(", ")}`).join(` UNION ALL
5
+ `),q=i.map(a=>`T.${r(a)} = S.${r(a)}`).join(", "),p=s.map(a=>r(a)).join(", "),S=s.map(a=>`S.${r(a)}`).join(", "),f=s.includes(l)?` AND (T.${r(l)} IS NULL OR S.${r(l)} > T.${r(l)})`:"",h=[`MERGE ${this.fqn(t)} AS T`,`USING (
6
+ ${T}
7
+ ) AS S`,`ON T.${r(n)} = S.${r(n)}`,`WHEN MATCHED${f} THEN UPDATE SET ${q}`,`WHEN NOT MATCHED THEN INSERT (${p}) VALUES (${S});`].join(`
8
+ `);await y(()=>this.bigquery.query({query:h}));}async deleteRows(t,e,n){if(n.length===0)return;let s=c=>this.dialect.quoteIdentifier(c),i=n.map(c=>this.escapeValue(c)).join(", "),r=`DELETE FROM ${this.fqn(t)} WHERE ${s(e)} IN (${i});`;await y(()=>this.bigquery.query({query:r}));}async executeRaw(t){await this.bigquery.query({query:t});}get dataset(){return this.bigquery.dataset(this.datasetId)}fqn(t){return `\`${this.datasetId}.${t}\``}escapeValue(t){return t==null?"NULL":typeof t=="boolean"?t?"TRUE":"FALSE":typeof t=="number"||typeof t=="bigint"?String(t):typeof t=="string"?u.ISO_TIMESTAMP_RE.test(t)?`TIMESTAMP('${t}')`:t.startsWith("[")&&t.endsWith("]")||t.startsWith("{")&&t.endsWith("}")?`PARSE_JSON('${t.replace(/'/g,"\\'")}')`:`'${t.replace(/'/g,"\\'")}'`:`PARSE_JSON('${JSON.stringify(t).replace(/'/g,"\\'")}')`}};u.ISO_TIMESTAMP_RE=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;var m=u;exports.BigQueryAdapter=m;exports.bigqueryDialect=N;//# sourceMappingURL=bigquery.cjs.map
9
9
  //# sourceMappingURL=bigquery.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/sync/constants.ts","../../src/sync/adapters/bigquery.ts"],"names":["SYNC_VERSION_COLUMN","BigQueryDialect","logical","id","bigqueryDialect","_BigQueryAdapter","options","tableName","exists","metadata","f","table","qi","cols","c","notNull","ddl","columns","stmt","rows","primaryKey","allKeys","nonPkCols","k","source","row","i","updateSet","insertCols","insertVals","versionGuard","query","ids","escaped","v","sql","BigQueryAdapter"],"mappings":"aAaO,IAAMA,EAAsB,gBAAA,CCCnC,IAAMC,CAAAA,CAAN,KAA4C,CAA5C,WAAA,EAAA,CACE,IAAA,CAAS,IAAA,CAAO,WAAA,CAEhB,QAAQC,CAAAA,CAA8B,CACpC,OAAQA,CAAAA,EACN,KAAK,QAAA,CACH,OAAO,QAAA,CACT,KAAK,SACH,OAAO,SAAA,CACT,KAAK,QAAA,CACH,OAAO,OAAA,CACT,KAAK,SAAA,CACH,OAAO,OACT,KAAK,WAAA,CACH,OAAO,WAAA,CACT,KAAK,OACH,OAAO,MAAA,CACT,KAAK,MAAA,CACH,OAAO,QACX,CACF,CAEA,eAAA,CAAgBC,EAAoB,CAClC,OAAO,CAAA,EAAA,EAAKA,CAAE,IAChB,CACF,CAAA,CAGaC,EAA8B,IAAIH,CAAAA,CAuBlCI,EAAN,MAAMA,CAAsC,CAIjD,WAAA,CAAYC,EAA+C,CACzD,IAAA,CAAK,QAAA,CAAWA,CAAAA,CAAQ,SACxB,IAAA,CAAK,SAAA,CAAYA,CAAAA,CAAQ,UAC3B,CAGA,IAAI,OAAA,EAAsB,CACxB,OAAOF,CACT,CAGA,MAAM,WAAA,CAAYG,CAAAA,CAAqC,CACrD,GAAM,CAACC,CAAM,CAAA,CAAI,MAAM,KAAK,OAAA,CAAQ,KAAA,CAAMD,CAAS,CAAA,CAAE,QAAO,CAC5D,OAAOC,CACT,CAGA,MAAM,gBAAgBD,CAAAA,CAAsC,CAC1D,GAAM,CAACE,CAAQ,CAAA,CAAI,MAAM,IAAA,CAAK,OAAA,CAAQ,MAAMF,CAAS,CAAA,CAAE,WAAA,EAAY,CAEnE,QADwCE,CAAAA,CAAS,MAAA,EAAQ,QAAU,EAAC,EACtD,IAAKC,CAAAA,EAAMA,CAAAA,CAAE,IAAI,CACjC,CAGA,MAAM,WAAA,CAAYC,CAAAA,CAAmC,CACnD,IAAMC,CAAAA,CAAMT,CAAAA,EAAe,IAAA,CAAK,OAAA,CAAQ,gBAAgBA,CAAE,CAAA,CACpDU,EAAOF,CAAAA,CAAM,OAAA,CAChB,IAAKG,CAAAA,EAAM,CACV,IAAMC,CAAAA,CAAUD,EAAE,YAAA,CAAe,WAAA,CAAc,GAC/C,OAAO,CAAA,EAAA,EAAKF,EAAGE,CAAAA,CAAE,IAAI,CAAC,CAAA,CAAA,EAAIA,EAAE,OAAO,CAAA,EAAGC,CAAO,CAAA,CAC/C,CAAC,EACA,IAAA,CAAK,CAAA;AAAA,CAAK,EAEPC,CAAAA,CAAM,CAAA,2BAAA,EAA8B,KAAK,GAAA,CAAIL,CAAAA,CAAM,SAAS,CAAC,CAAA;AAAA,EAAOE,CAAI;AAAA,EAAA,CAAA,CAC9E,MAAM,IAAA,CAAK,QAAA,CAAS,KAAA,CAAM,CAAE,KAAA,CAAOG,CAAI,CAAC,EAC1C,CAGA,MAAM,UAAA,CAAWT,CAAAA,CAAmBU,EAAqC,CACvE,IAAML,CAAAA,CAAMT,CAAAA,EAAe,IAAA,CAAK,OAAA,CAAQ,eAAA,CAAgBA,CAAE,EAC1D,IAAA,IAAWW,CAAAA,IAAKG,CAAAA,CAAS,CACvB,IAAMC,CAAAA,CAAO,CAAA,YAAA,EAAe,IAAA,CAAK,IAAIX,CAAS,CAAC,CAAA,YAAA,EAAeK,CAAAA,CAAGE,CAAAA,CAAE,IAAI,CAAC,CAAA,CAAA,EAAIA,EAAE,OAAO,CAAA,CAAA,CAAA,CACrF,MAAM,IAAA,CAAK,QAAA,CAAS,KAAA,CAAM,CAAE,KAAA,CAAOI,CAAK,CAAC,EAC3C,CACF,CAGA,MAAM,UAAA,CACJX,CAAAA,CACAY,CAAAA,CACe,CACXA,CAAAA,CAAK,MAAA,GAAW,CAAA,EACpB,MAAM,IAAA,CAAK,OAAA,CAAQ,KAAA,CAAMZ,CAAS,EAAE,MAAA,CAAOY,CAAI,EACjD,CAQA,MAAM,UAAA,CACJZ,CAAAA,CACAY,CAAAA,CACAC,EACe,CACf,GAAID,CAAAA,CAAK,MAAA,GAAW,CAAA,CAAG,OAEvB,IAAME,CAAAA,CAAU,OAAO,IAAA,CAAKF,CAAAA,CAAK,CAAC,CAAE,CAAA,CAC9BG,CAAAA,CAAYD,CAAAA,CAAQ,MAAA,CAAQE,GAAMA,CAAAA,GAAMH,CAAU,CAAA,CAClDR,CAAAA,CAAMT,CAAAA,EAAe,IAAA,CAAK,OAAA,CAAQ,eAAA,CAAgBA,CAAE,CAAA,CAgBpDqB,CAAAA,CAbUL,CAAAA,CAAK,GAAA,CAAI,CAACM,CAAAA,CAAKC,CAAAA,GAUtB,CAAA,OAAA,EATQL,EACZ,GAAA,CAAKE,CAAAA,EAEFG,CAAAA,GAAM,CAAA,CACF,CAAA,EAAG,IAAA,CAAK,WAAA,CAAYD,CAAAA,CAAIF,CAAC,CAAC,CAAC,CAAA,IAAA,EAAOX,CAAAA,CAAGW,CAAC,CAAC,CAAA,CAAA,CACvC,IAAA,CAAK,YAAYE,CAAAA,CAAIF,CAAC,CAAC,CAE9B,EACA,IAAA,CAAK,IAAI,CACW,CAAA,CACxB,EAEsB,IAAA,CAAK,CAAA;AAAA,IAAA,CAAkB,CAAA,CAKxCI,CAAAA,CAAYL,CAAAA,CACf,GAAA,CAAKR,GAAM,CAAA,EAAA,EAAKF,CAAAA,CAAGE,CAAC,CAAC,CAAA,KAAA,EAAQF,CAAAA,CAAGE,CAAC,CAAC,EAAE,CAAA,CACpC,IAAA,CAAK,IAAI,CAAA,CAGNc,CAAAA,CAAaP,CAAAA,CAAQ,GAAA,CAAKP,CAAAA,EAAMF,EAAGE,CAAC,CAAC,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA,CAChDe,CAAAA,CAAaR,CAAAA,CAAQ,IAAKP,CAAAA,EAAM,CAAA,EAAA,EAAKF,CAAAA,CAAGE,CAAC,CAAC,CAAA,CAAE,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA,CAKvDgB,CAAAA,CAAeT,CAAAA,CAAQ,QAAA,CAASrB,CAAmB,CAAA,CACrD,CAAA,QAAA,EAAWY,CAAAA,CAAGZ,CAAmB,CAAC,CAAA,cAAA,EAAiBY,CAAAA,CAAGZ,CAAmB,CAAC,CAAA,KAAA,EAAQY,CAAAA,CAAGZ,CAAmB,CAAC,CAAA,CAAA,CAAA,CACzG,EAAA,CAEE+B,CAAAA,CAAQ,CACZ,CAAA,MAAA,EAAS,IAAA,CAAK,GAAA,CAAIxB,CAAS,CAAC,CAAA,KAAA,CAAA,CAC5B,CAAA;AAAA,IAAA,EAAgBiB,CAAM;AAAA,QAAA,CAAA,CACtB,CAAA,KAAA,EAAQZ,EAAGQ,CAAU,CAAC,QAAQR,CAAAA,CAAGQ,CAAU,CAAC,CAAA,CAAA,CAC5C,CAAA,YAAA,EAAeU,CAAY,CAAA,iBAAA,EAAoBH,CAAS,GACxD,CAAA,8BAAA,EAAiCC,CAAU,aAAaC,CAAU,CAAA,EAAA,CACpE,EAAE,IAAA,CAAK;AAAA,CAAI,EAEX,MAAM,IAAA,CAAK,SAAS,KAAA,CAAM,CAAE,MAAAE,CAAM,CAAC,EACrC,CAGA,MAAM,WACJxB,CAAAA,CACAa,CAAAA,CACAY,EACe,CACf,GAAIA,EAAI,MAAA,GAAW,CAAA,CAAG,OAEtB,IAAMpB,EAAMT,CAAAA,EAAe,IAAA,CAAK,QAAQ,eAAA,CAAgBA,CAAE,EACpD8B,CAAAA,CAAUD,CAAAA,CAAI,IAAKE,CAAAA,EAAM,IAAA,CAAK,YAAYA,CAAC,CAAC,EAAE,IAAA,CAAK,IAAI,EACvDH,CAAAA,CAAQ,CAAA,YAAA,EAAe,IAAA,CAAK,GAAA,CAAIxB,CAAS,CAAC,CAAA,OAAA,EAAUK,EAAGQ,CAAU,CAAC,QAAQa,CAAO,CAAA,EAAA,CAAA,CAEvF,MAAM,IAAA,CAAK,QAAA,CAAS,MAAM,CAAE,KAAA,CAAAF,CAAM,CAAC,EACrC,CAGA,MAAM,UAAA,CAAWI,CAAAA,CAA4B,CAC3C,MAAM,IAAA,CAAK,QAAA,CAAS,MAAM,CAAE,KAAA,CAAOA,CAAI,CAAC,EAC1C,CAOA,IAAY,OAAA,EAAU,CACpB,OAAO,IAAA,CAAK,SAAS,OAAA,CAAQ,IAAA,CAAK,SAAS,CAC7C,CAGQ,GAAA,CAAI5B,CAAAA,CAA2B,CACrC,OAAO,CAAA,EAAA,EAAK,KAAK,SAAS,CAAA,CAAA,EAAIA,CAAS,CAAA,EAAA,CACzC,CAOQ,YAAY2B,CAAAA,CAAoB,CACtC,OAAIA,CAAAA,EAAM,IAAA,CAAgC,OACtC,OAAOA,CAAAA,EAAM,UAAkBA,CAAAA,CAAI,MAAA,CAAS,OAAA,CAC5C,OAAOA,GAAM,QAAA,EAAY,OAAOA,GAAM,QAAA,CAAiB,MAAA,CAAOA,CAAC,CAAA,CAC/D,OAAOA,GAAM,QAAA,CAEX7B,CAAAA,CAAgB,iBAAiB,IAAA,CAAK6B,CAAC,EAClC,CAAA,WAAA,EAAcA,CAAC,KAIrBA,CAAAA,CAAE,UAAA,CAAW,GAAG,CAAA,EAAKA,EAAE,QAAA,CAAS,GAAG,GACnCA,CAAAA,CAAE,UAAA,CAAW,GAAG,CAAA,EAAKA,CAAAA,CAAE,SAAS,GAAG,CAAA,CAE7B,eAAeA,CAAAA,CAAE,OAAA,CAAQ,KAAM,KAAK,CAAC,KAEvC,CAAA,CAAA,EAAIA,CAAAA,CAAE,OAAA,CAAQ,IAAA,CAAM,KAAK,CAAC,CAAA,CAAA,CAAA,CAG5B,eAAe,IAAA,CAAK,SAAA,CAAUA,CAAC,CAAA,CAAE,OAAA,CAAQ,KAAM,KAAK,CAAC,IAC9D,CACF,EArLa7B,EA4Ja,gBAAA,CACtB,sCAAA,KA7JS+B,CAAAA,CAAN/B","file":"bigquery.cjs","sourcesContent":["/**\n * Internal constants shared between the worker, queue, schema mapper and\n * SQL adapters.\n */\n\n/**\n * Name of the SQL column that stores the publish-time `version` of each\n * sync event. Used by the worker to discard out-of-order PubSub deliveries\n * (the MERGE only updates rows when the incoming version is strictly\n * greater than the stored one).\n *\n * Two underscores prefix avoids collisions with user-defined fields.\n */\nexport const SYNC_VERSION_COLUMN = \"__sync_version\";\n","import { SYNC_VERSION_COLUMN } from \"../constants\";\nimport type {\n LogicalType,\n SqlAdapter,\n SqlColumn,\n SqlDialect,\n SqlTableDef,\n} from \"../types\";\n\n// ---------------------------------------------------------------------------\n// Dialect (internal — used only by BigQueryAdapter)\n// ---------------------------------------------------------------------------\n\n/** BigQuery SQL dialect mapping. */\nclass BigQueryDialect implements SqlDialect {\n readonly name = \"bigquery\";\n\n mapType(logical: LogicalType): string {\n switch (logical) {\n case \"string\":\n return \"STRING\";\n case \"number\":\n return \"FLOAT64\";\n case \"bigint\":\n return \"INT64\";\n case \"boolean\":\n return \"BOOL\";\n case \"timestamp\":\n return \"TIMESTAMP\";\n case \"json\":\n return \"JSON\";\n case \"text\":\n return \"STRING\";\n }\n }\n\n quoteIdentifier(id: string): string {\n return `\\`${id}\\``;\n }\n}\n\n/** Shared BigQuery dialect singleton. */\nexport const bigqueryDialect: SqlDialect = new BigQueryDialect();\n\n// ---------------------------------------------------------------------------\n// Adapter\n// ---------------------------------------------------------------------------\n\n/**\n * BigQuery implementation of {@link SqlAdapter}.\n *\n * Accepts an already-configured BigQuery client so the library does not pull\n * in `@google-cloud/bigquery` as a hard dependency.\n *\n * @example\n * ```ts\n * import { BigQuery } from \"@google-cloud/bigquery\";\n * import { BigQueryAdapter } from \"./adapters/bigquery\";\n *\n * const adapter = new BigQueryAdapter({\n * bigquery: new BigQuery({ projectId: \"my-project\" }),\n * datasetId: \"my_dataset\",\n * });\n * ```\n */\nexport class BigQueryAdapter implements SqlAdapter {\n private readonly bigquery: any;\n private readonly datasetId: string;\n\n constructor(options: { bigquery: any; datasetId: string }) {\n this.bigquery = options.bigquery;\n this.datasetId = options.datasetId;\n }\n\n /** The BigQuery SQL dialect. */\n get dialect(): SqlDialect {\n return bigqueryDialect;\n }\n\n /** Check whether a table exists in the dataset. */\n async tableExists(tableName: string): Promise<boolean> {\n const [exists] = await this.dataset.table(tableName).exists();\n return exists;\n }\n\n /** Return the column names currently present in the table. */\n async getTableColumns(tableName: string): Promise<string[]> {\n const [metadata] = await this.dataset.table(tableName).getMetadata();\n const fields: Array<{ name: string }> = metadata.schema?.fields ?? [];\n return fields.map((f) => f.name);\n }\n\n /** Create a table using a fully-qualified name. */\n async createTable(table: SqlTableDef): Promise<void> {\n const qi = (id: string) => this.dialect.quoteIdentifier(id);\n const cols = table.columns\n .map((c) => {\n const notNull = c.isPrimaryKey ? \" NOT NULL\" : \"\";\n return ` ${qi(c.name)} ${c.sqlType}${notNull}`;\n })\n .join(\",\\n\");\n\n const ddl = `CREATE TABLE IF NOT EXISTS ${this.fqn(table.tableName)} (\\n${cols}\\n);`;\n await this.bigquery.query({ query: ddl });\n }\n\n /** Add columns to an existing table using a fully-qualified name. */\n async addColumns(tableName: string, columns: SqlColumn[]): Promise<void> {\n const qi = (id: string) => this.dialect.quoteIdentifier(id);\n for (const c of columns) {\n const stmt = `ALTER TABLE ${this.fqn(tableName)} ADD COLUMN ${qi(c.name)} ${c.sqlType};`;\n await this.bigquery.query({ query: stmt });\n }\n }\n\n /** Append rows via BigQuery streaming insert. */\n async insertRows(\n tableName: string,\n rows: Record<string, unknown>[],\n ): Promise<void> {\n if (rows.length === 0) return;\n await this.dataset.table(tableName).insert(rows);\n }\n\n /**\n * Upsert rows using a MERGE DML statement.\n *\n * Builds a source table from inline SELECT … UNION ALL rows and merges\n * into the target on the given primary key.\n */\n async upsertRows(\n tableName: string,\n rows: Record<string, unknown>[],\n primaryKey: string,\n ): Promise<void> {\n if (rows.length === 0) return;\n\n const allKeys = Object.keys(rows[0]!);\n const nonPkCols = allKeys.filter((k) => k !== primaryKey);\n const qi = (id: string) => this.dialect.quoteIdentifier(id);\n\n // Build inline source: SELECT val AS col, … UNION ALL SELECT …\n const selects = rows.map((row, i) => {\n const values = allKeys\n .map((k) => {\n const aliased =\n i === 0\n ? `${this.escapeValue(row[k])} AS ${qi(k)}`\n : this.escapeValue(row[k]);\n return aliased;\n })\n .join(\", \");\n return `SELECT ${values}`;\n });\n\n const source = selects.join(\" UNION ALL\\n \");\n\n // UPDATE SET clause (non-PK columns).\n // Note: when __sync_version is present we still update it so the row\n // tracks the latest applied version.\n const updateSet = nonPkCols\n .map((c) => `T.${qi(c)} = S.${qi(c)}`)\n .join(\", \");\n\n // INSERT columns / values\n const insertCols = allKeys.map((c) => qi(c)).join(\", \");\n const insertVals = allKeys.map((c) => `S.${qi(c)}`).join(\", \");\n\n // Out-of-order protection: only UPDATE when the incoming version is\n // strictly greater than the stored one (NULL stored version means the\n // row pre-dates versioning → always update).\n const versionGuard = allKeys.includes(SYNC_VERSION_COLUMN)\n ? ` AND (T.${qi(SYNC_VERSION_COLUMN)} IS NULL OR S.${qi(SYNC_VERSION_COLUMN)} > T.${qi(SYNC_VERSION_COLUMN)})`\n : \"\";\n\n const query = [\n `MERGE ${this.fqn(tableName)} AS T`,\n `USING (\\n ${source}\\n ) AS S`,\n `ON T.${qi(primaryKey)} = S.${qi(primaryKey)}`,\n `WHEN MATCHED${versionGuard} THEN UPDATE SET ${updateSet}`,\n `WHEN NOT MATCHED THEN INSERT (${insertCols}) VALUES (${insertVals});`,\n ].join(\"\\n\");\n\n await this.bigquery.query({ query });\n }\n\n /** Delete rows by primary-key values. */\n async deleteRows(\n tableName: string,\n primaryKey: string,\n ids: string[],\n ): Promise<void> {\n if (ids.length === 0) return;\n\n const qi = (id: string) => this.dialect.quoteIdentifier(id);\n const escaped = ids.map((v) => this.escapeValue(v)).join(\", \");\n const query = `DELETE FROM ${this.fqn(tableName)} WHERE ${qi(primaryKey)} IN (${escaped});`;\n\n await this.bigquery.query({ query });\n }\n\n /** Execute a raw SQL statement (used by the migration manager). */\n async executeRaw(sql: string): Promise<void> {\n await this.bigquery.query({ query: sql });\n }\n\n // -------------------------------------------------------------------------\n // Private helpers\n // -------------------------------------------------------------------------\n\n /** The BigQuery Dataset handle. */\n private get dataset() {\n return this.bigquery.dataset(this.datasetId);\n }\n\n /** Return the fully-qualified table reference (`` `dataset.table` ``). */\n private fqn(tableName: string): string {\n return `\\`${this.datasetId}.${tableName}\\``;\n }\n\n /** ISO 8601 timestamp pattern (e.g. 2026-03-29T20:59:27.394Z) */\n private static readonly ISO_TIMESTAMP_RE =\n /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}/;\n\n /** Escape a value for use as a SQL literal. */\n private escapeValue(v: unknown): string {\n if (v === null || v === undefined) return \"NULL\";\n if (typeof v === \"boolean\") return v ? \"TRUE\" : \"FALSE\";\n if (typeof v === \"number\" || typeof v === \"bigint\") return String(v);\n if (typeof v === \"string\") {\n // ISO 8601 timestamps → TIMESTAMP literal (keeps type-safety with BQ TIMESTAMP columns)\n if (BigQueryAdapter.ISO_TIMESTAMP_RE.test(v)) {\n return `TIMESTAMP('${v}')`;\n }\n // Detect JSON strings (arrays/objects) → use PARSE_JSON for native JSON columns\n if (\n (v.startsWith(\"[\") && v.endsWith(\"]\")) ||\n (v.startsWith(\"{\") && v.endsWith(\"}\"))\n ) {\n return `PARSE_JSON('${v.replace(/'/g, \"\\\\'\")}')`;\n }\n return `'${v.replace(/'/g, \"\\\\'\")}'`;\n }\n // Objects / arrays → JSON\n return `PARSE_JSON('${JSON.stringify(v).replace(/'/g, \"\\\\'\")}')`;\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/sync/constants.ts","../../src/sync/adapters/bigquery.ts"],"names":["SYNC_VERSION_COLUMN","isConcurrentUpdateError","err","e","x","withRetry","fn","maxRetries","baseMs","attempt","cap","delay","res","BigQueryDialect","logical","id","bigqueryDialect","_BigQueryAdapter","options","tableName","exists","metadata","f","table","qi","cols","c","notNull","ddl","columns","stmt","rows","primaryKey","allKeys","nonPkCols","k","source","row","i","updateSet","insertCols","insertVals","versionGuard","query","ids","escaped","v","sql","BigQueryAdapter"],"mappings":"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","file":"bigquery.cjs","sourcesContent":["/**\n * Internal constants shared between the worker, queue, schema mapper and\n * SQL adapters.\n */\n\n/**\n * Name of the SQL column that stores the publish-time `version` of each\n * sync event. Used by the worker to discard out-of-order PubSub deliveries\n * (the MERGE only updates rows when the incoming version is strictly\n * greater than the stored one).\n *\n * Two underscores prefix avoids collisions with user-defined fields.\n */\nexport const SYNC_VERSION_COLUMN = \"__sync_version\";\n","import { SYNC_VERSION_COLUMN } from \"../constants\";\nimport type {\n LogicalType,\n SqlAdapter,\n SqlColumn,\n SqlDialect,\n SqlTableDef,\n} from \"../types\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Returns true when the BigQuery error is a \"concurrent update\" serialization\n * conflict (code 400, reason \"invalidQuery\" containing \"serialize access\").\n * These are safe to retry after a brief back-off.\n */\nfunction isConcurrentUpdateError(err: unknown): boolean {\n if (!err || typeof err !== \"object\") return false;\n const e = err as Record<string, unknown>;\n if (e[\"code\"] !== 400) return false;\n const errors = Array.isArray(e[\"errors\"]) ? e[\"errors\"] : [];\n return errors.some(\n (x: any) =>\n typeof x?.message === \"string\" &&\n x.message.toLowerCase().includes(\"serialize access\"),\n );\n}\n\n/**\n * Execute `fn`, retrying up to `maxRetries` times when BigQuery returns a\n * concurrent-update error. Uses full-jitter exponential back-off.\n */\nasync function withRetry<T>(\n fn: () => Promise<T>,\n maxRetries = 10,\n baseMs = 500,\n): Promise<T> {\n let attempt = 0;\n while (true) {\n try {\n return await fn();\n } catch (err) {\n attempt++;\n if (!isConcurrentUpdateError(err) || attempt > maxRetries) throw err;\n const cap = baseMs * Math.pow(2, attempt);\n const delay = Math.random() * cap; // full jitter\n await new Promise((res) => setTimeout(res, delay));\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Dialect (internal — used only by BigQueryAdapter)\n// ---------------------------------------------------------------------------\n\n/** BigQuery SQL dialect mapping. */\nclass BigQueryDialect implements SqlDialect {\n readonly name = \"bigquery\";\n\n mapType(logical: LogicalType): string {\n switch (logical) {\n case \"string\":\n return \"STRING\";\n case \"number\":\n return \"FLOAT64\";\n case \"bigint\":\n return \"INT64\";\n case \"boolean\":\n return \"BOOL\";\n case \"timestamp\":\n return \"TIMESTAMP\";\n case \"json\":\n return \"JSON\";\n case \"text\":\n return \"STRING\";\n }\n }\n\n quoteIdentifier(id: string): string {\n return `\\`${id}\\``;\n }\n}\n\n/** Shared BigQuery dialect singleton. */\nexport const bigqueryDialect: SqlDialect = new BigQueryDialect();\n\n// ---------------------------------------------------------------------------\n// Adapter\n// ---------------------------------------------------------------------------\n\n/**\n * BigQuery implementation of {@link SqlAdapter}.\n *\n * Accepts an already-configured BigQuery client so the library does not pull\n * in `@google-cloud/bigquery` as a hard dependency.\n *\n * @example\n * ```ts\n * import { BigQuery } from \"@google-cloud/bigquery\";\n * import { BigQueryAdapter } from \"./adapters/bigquery\";\n *\n * const adapter = new BigQueryAdapter({\n * bigquery: new BigQuery({ projectId: \"my-project\" }),\n * datasetId: \"my_dataset\",\n * });\n * ```\n */\nexport class BigQueryAdapter implements SqlAdapter {\n private readonly bigquery: any;\n private readonly datasetId: string;\n\n constructor(options: { bigquery: any; datasetId: string }) {\n this.bigquery = options.bigquery;\n this.datasetId = options.datasetId;\n }\n\n /** The BigQuery SQL dialect. */\n get dialect(): SqlDialect {\n return bigqueryDialect;\n }\n\n /** Check whether a table exists in the dataset. */\n async tableExists(tableName: string): Promise<boolean> {\n const [exists] = await this.dataset.table(tableName).exists();\n return exists;\n }\n\n /** Return the column names currently present in the table. */\n async getTableColumns(tableName: string): Promise<string[]> {\n const [metadata] = await this.dataset.table(tableName).getMetadata();\n const fields: Array<{ name: string }> = metadata.schema?.fields ?? [];\n return fields.map((f) => f.name);\n }\n\n /** Create a table using a fully-qualified name. */\n async createTable(table: SqlTableDef): Promise<void> {\n const qi = (id: string) => this.dialect.quoteIdentifier(id);\n const cols = table.columns\n .map((c) => {\n const notNull = c.isPrimaryKey ? \" NOT NULL\" : \"\";\n return ` ${qi(c.name)} ${c.sqlType}${notNull}`;\n })\n .join(\",\\n\");\n\n const ddl = `CREATE TABLE IF NOT EXISTS ${this.fqn(table.tableName)} (\\n${cols}\\n);`;\n await this.bigquery.query({ query: ddl });\n }\n\n /** Add columns to an existing table using a fully-qualified name. */\n async addColumns(tableName: string, columns: SqlColumn[]): Promise<void> {\n const qi = (id: string) => this.dialect.quoteIdentifier(id);\n for (const c of columns) {\n const stmt = `ALTER TABLE ${this.fqn(tableName)} ADD COLUMN ${qi(c.name)} ${c.sqlType};`;\n await this.bigquery.query({ query: stmt });\n }\n }\n\n /** Append rows via BigQuery streaming insert. */\n async insertRows(\n tableName: string,\n rows: Record<string, unknown>[],\n ): Promise<void> {\n if (rows.length === 0) return;\n await this.dataset.table(tableName).insert(rows);\n }\n\n /**\n * Upsert rows using a MERGE DML statement.\n *\n * Builds a source table from inline SELECT … UNION ALL rows and merges\n * into the target on the given primary key.\n */\n async upsertRows(\n tableName: string,\n rows: Record<string, unknown>[],\n primaryKey: string,\n ): Promise<void> {\n if (rows.length === 0) return;\n\n const allKeys = Object.keys(rows[0]!);\n const nonPkCols = allKeys.filter((k) => k !== primaryKey);\n const qi = (id: string) => this.dialect.quoteIdentifier(id);\n\n // Build inline source: SELECT val AS col, … UNION ALL SELECT …\n const selects = rows.map((row, i) => {\n const values = allKeys\n .map((k) => {\n const aliased =\n i === 0\n ? `${this.escapeValue(row[k])} AS ${qi(k)}`\n : this.escapeValue(row[k]);\n return aliased;\n })\n .join(\", \");\n return `SELECT ${values}`;\n });\n\n const source = selects.join(\" UNION ALL\\n \");\n\n // UPDATE SET clause (non-PK columns).\n // Note: when __sync_version is present we still update it so the row\n // tracks the latest applied version.\n const updateSet = nonPkCols\n .map((c) => `T.${qi(c)} = S.${qi(c)}`)\n .join(\", \");\n\n // INSERT columns / values\n const insertCols = allKeys.map((c) => qi(c)).join(\", \");\n const insertVals = allKeys.map((c) => `S.${qi(c)}`).join(\", \");\n\n // Out-of-order protection: only UPDATE when the incoming version is\n // strictly greater than the stored one (NULL stored version means the\n // row pre-dates versioning → always update).\n const versionGuard = allKeys.includes(SYNC_VERSION_COLUMN)\n ? ` AND (T.${qi(SYNC_VERSION_COLUMN)} IS NULL OR S.${qi(SYNC_VERSION_COLUMN)} > T.${qi(SYNC_VERSION_COLUMN)})`\n : \"\";\n\n const query = [\n `MERGE ${this.fqn(tableName)} AS T`,\n `USING (\\n ${source}\\n ) AS S`,\n `ON T.${qi(primaryKey)} = S.${qi(primaryKey)}`,\n `WHEN MATCHED${versionGuard} THEN UPDATE SET ${updateSet}`,\n `WHEN NOT MATCHED THEN INSERT (${insertCols}) VALUES (${insertVals});`,\n ].join(\"\\n\");\n\n await withRetry(() => this.bigquery.query({ query }));\n }\n\n /** Delete rows by primary-key values. */\n async deleteRows(\n tableName: string,\n primaryKey: string,\n ids: string[],\n ): Promise<void> {\n if (ids.length === 0) return;\n\n const qi = (id: string) => this.dialect.quoteIdentifier(id);\n const escaped = ids.map((v) => this.escapeValue(v)).join(\", \");\n const query = `DELETE FROM ${this.fqn(tableName)} WHERE ${qi(primaryKey)} IN (${escaped});`;\n\n await withRetry(() => this.bigquery.query({ query }));\n }\n\n /** Execute a raw SQL statement (used by the migration manager). */\n async executeRaw(sql: string): Promise<void> {\n await this.bigquery.query({ query: sql });\n }\n\n // -------------------------------------------------------------------------\n // Private helpers\n // -------------------------------------------------------------------------\n\n /** The BigQuery Dataset handle. */\n private get dataset() {\n return this.bigquery.dataset(this.datasetId);\n }\n\n /** Return the fully-qualified table reference (`` `dataset.table` ``). */\n private fqn(tableName: string): string {\n return `\\`${this.datasetId}.${tableName}\\``;\n }\n\n /** ISO 8601 timestamp pattern (e.g. 2026-03-29T20:59:27.394Z) */\n private static readonly ISO_TIMESTAMP_RE =\n /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}/;\n\n /** Escape a value for use as a SQL literal. */\n private escapeValue(v: unknown): string {\n if (v === null || v === undefined) return \"NULL\";\n if (typeof v === \"boolean\") return v ? \"TRUE\" : \"FALSE\";\n if (typeof v === \"number\" || typeof v === \"bigint\") return String(v);\n if (typeof v === \"string\") {\n // ISO 8601 timestamps → TIMESTAMP literal (keeps type-safety with BQ TIMESTAMP columns)\n if (BigQueryAdapter.ISO_TIMESTAMP_RE.test(v)) {\n return `TIMESTAMP('${v}')`;\n }\n // Detect JSON strings (arrays/objects) → use PARSE_JSON for native JSON columns\n if (\n (v.startsWith(\"[\") && v.endsWith(\"]\")) ||\n (v.startsWith(\"{\") && v.endsWith(\"}\"))\n ) {\n return `PARSE_JSON('${v.replace(/'/g, \"\\\\'\")}')`;\n }\n return `'${v.replace(/'/g, \"\\\\'\")}'`;\n }\n // Objects / arrays → JSON\n return `PARSE_JSON('${JSON.stringify(v).replace(/'/g, \"\\\\'\")}')`;\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { b as SqlDialect, a as SqlAdapter, c as SqlTableDef, d as SqlColumn } from '../types-BTt_FmeC.cjs';
1
+ import { b as SqlDialect, a as SqlAdapter, c as SqlTableDef, d as SqlColumn } from '../types-BW592RAZ.cjs';
2
2
  import '../firebase-auth-D1APf9PA.cjs';
3
3
  import 'firebase-functions/v2/firestore';
4
4
  import 'firebase-functions/v2/https';
@@ -1,4 +1,4 @@
1
- import { b as SqlDialect, a as SqlAdapter, c as SqlTableDef, d as SqlColumn } from '../types-DXoT10nB.js';
1
+ import { b as SqlDialect, a as SqlAdapter, c as SqlTableDef, d as SqlColumn } from '../types-CBwcflkG.js';
2
2
  import '../firebase-auth-D1APf9PA.js';
3
3
  import 'firebase-functions/v2/firestore';
4
4
  import 'firebase-functions/v2/https';
@@ -1,9 +1,9 @@
1
- var o="__sync_version";var d=class{constructor(){this.name="bigquery";}mapType(t){switch(t){case "string":return "STRING";case "number":return "FLOAT64";case "bigint":return "INT64";case "boolean":return "BOOL";case "timestamp":return "TIMESTAMP";case "json":return "JSON";case "text":return "STRING"}}quoteIdentifier(t){return `\`${t}\``}},f=new d,c=class c{constructor(t){this.bigquery=t.bigquery,this.datasetId=t.datasetId;}get dialect(){return f}async tableExists(t){let[e]=await this.dataset.table(t).exists();return e}async getTableColumns(t){let[e]=await this.dataset.table(t).getMetadata();return (e.schema?.fields??[]).map(s=>s.name)}async createTable(t){let e=a=>this.dialect.quoteIdentifier(a),r=t.columns.map(a=>{let n=a.isPrimaryKey?" NOT NULL":"";return ` ${e(a.name)} ${a.sqlType}${n}`}).join(`,
1
+ var l="__sync_version";function $(o){if(!o||typeof o!="object")return false;let t=o;return t.code!==400?false:(Array.isArray(t.errors)?t.errors:[]).some(n=>typeof n?.message=="string"&&n.message.toLowerCase().includes("serialize access"))}async function y(o,t=10,e=500){let n=0;for(;;)try{return await o()}catch(s){if(n++,!$(s)||n>t)throw s;let i=e*Math.pow(2,n),r=Math.random()*i;await new Promise(c=>setTimeout(c,r));}}var g=class{constructor(){this.name="bigquery";}mapType(t){switch(t){case "string":return "STRING";case "number":return "FLOAT64";case "bigint":return "INT64";case "boolean":return "BOOL";case "timestamp":return "TIMESTAMP";case "json":return "JSON";case "text":return "STRING"}}quoteIdentifier(t){return `\`${t}\``}},N=new g,u=class u{constructor(t){this.bigquery=t.bigquery,this.datasetId=t.datasetId;}get dialect(){return N}async tableExists(t){let[e]=await this.dataset.table(t).exists();return e}async getTableColumns(t){let[e]=await this.dataset.table(t).getMetadata();return (e.schema?.fields??[]).map(s=>s.name)}async createTable(t){let e=i=>this.dialect.quoteIdentifier(i),n=t.columns.map(i=>{let r=i.isPrimaryKey?" NOT NULL":"";return ` ${e(i.name)} ${i.sqlType}${r}`}).join(`,
2
2
  `),s=`CREATE TABLE IF NOT EXISTS ${this.fqn(t.tableName)} (
3
- ${r}
4
- );`;await this.bigquery.query({query:s});}async addColumns(t,e){let r=s=>this.dialect.quoteIdentifier(s);for(let s of e){let a=`ALTER TABLE ${this.fqn(t)} ADD COLUMN ${r(s.name)} ${s.sqlType};`;await this.bigquery.query({query:a});}}async insertRows(t,e){e.length!==0&&await this.dataset.table(t).insert(e);}async upsertRows(t,e,r){if(e.length===0)return;let s=Object.keys(e[0]),a=s.filter(i=>i!==r),n=i=>this.dialect.quoteIdentifier(i),q=e.map((i,E)=>`SELECT ${s.map(u=>E===0?`${this.escapeValue(i[u])} AS ${n(u)}`:this.escapeValue(i[u])).join(", ")}`).join(` UNION ALL
5
- `),y=a.map(i=>`T.${n(i)} = S.${n(i)}`).join(", "),S=s.map(i=>n(i)).join(", "),T=s.map(i=>`S.${n(i)}`).join(", "),m=s.includes(o)?` AND (T.${n(o)} IS NULL OR S.${n(o)} > T.${n(o)})`:"",p=[`MERGE ${this.fqn(t)} AS T`,`USING (
6
- ${q}
7
- ) AS S`,`ON T.${n(r)} = S.${n(r)}`,`WHEN MATCHED${m} THEN UPDATE SET ${y}`,`WHEN NOT MATCHED THEN INSERT (${S}) VALUES (${T});`].join(`
8
- `);await this.bigquery.query({query:p});}async deleteRows(t,e,r){if(r.length===0)return;let s=l=>this.dialect.quoteIdentifier(l),a=r.map(l=>this.escapeValue(l)).join(", "),n=`DELETE FROM ${this.fqn(t)} WHERE ${s(e)} IN (${a});`;await this.bigquery.query({query:n});}async executeRaw(t){await this.bigquery.query({query:t});}get dataset(){return this.bigquery.dataset(this.datasetId)}fqn(t){return `\`${this.datasetId}.${t}\``}escapeValue(t){return t==null?"NULL":typeof t=="boolean"?t?"TRUE":"FALSE":typeof t=="number"||typeof t=="bigint"?String(t):typeof t=="string"?c.ISO_TIMESTAMP_RE.test(t)?`TIMESTAMP('${t}')`:t.startsWith("[")&&t.endsWith("]")||t.startsWith("{")&&t.endsWith("}")?`PARSE_JSON('${t.replace(/'/g,"\\'")}')`:`'${t.replace(/'/g,"\\'")}'`:`PARSE_JSON('${JSON.stringify(t).replace(/'/g,"\\'")}')`}};c.ISO_TIMESTAMP_RE=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;var g=c;export{g as BigQueryAdapter,f as bigqueryDialect};//# sourceMappingURL=bigquery.js.map
3
+ ${n}
4
+ );`;await this.bigquery.query({query:s});}async addColumns(t,e){let n=s=>this.dialect.quoteIdentifier(s);for(let s of e){let i=`ALTER TABLE ${this.fqn(t)} ADD COLUMN ${n(s.name)} ${s.sqlType};`;await this.bigquery.query({query:i});}}async insertRows(t,e){e.length!==0&&await this.dataset.table(t).insert(e);}async upsertRows(t,e,n){if(e.length===0)return;let s=Object.keys(e[0]),i=s.filter(a=>a!==n),r=a=>this.dialect.quoteIdentifier(a),T=e.map((a,E)=>`SELECT ${s.map(d=>E===0?`${this.escapeValue(a[d])} AS ${r(d)}`:this.escapeValue(a[d])).join(", ")}`).join(` UNION ALL
5
+ `),q=i.map(a=>`T.${r(a)} = S.${r(a)}`).join(", "),p=s.map(a=>r(a)).join(", "),S=s.map(a=>`S.${r(a)}`).join(", "),f=s.includes(l)?` AND (T.${r(l)} IS NULL OR S.${r(l)} > T.${r(l)})`:"",h=[`MERGE ${this.fqn(t)} AS T`,`USING (
6
+ ${T}
7
+ ) AS S`,`ON T.${r(n)} = S.${r(n)}`,`WHEN MATCHED${f} THEN UPDATE SET ${q}`,`WHEN NOT MATCHED THEN INSERT (${p}) VALUES (${S});`].join(`
8
+ `);await y(()=>this.bigquery.query({query:h}));}async deleteRows(t,e,n){if(n.length===0)return;let s=c=>this.dialect.quoteIdentifier(c),i=n.map(c=>this.escapeValue(c)).join(", "),r=`DELETE FROM ${this.fqn(t)} WHERE ${s(e)} IN (${i});`;await y(()=>this.bigquery.query({query:r}));}async executeRaw(t){await this.bigquery.query({query:t});}get dataset(){return this.bigquery.dataset(this.datasetId)}fqn(t){return `\`${this.datasetId}.${t}\``}escapeValue(t){return t==null?"NULL":typeof t=="boolean"?t?"TRUE":"FALSE":typeof t=="number"||typeof t=="bigint"?String(t):typeof t=="string"?u.ISO_TIMESTAMP_RE.test(t)?`TIMESTAMP('${t}')`:t.startsWith("[")&&t.endsWith("]")||t.startsWith("{")&&t.endsWith("}")?`PARSE_JSON('${t.replace(/'/g,"\\'")}')`:`'${t.replace(/'/g,"\\'")}'`:`PARSE_JSON('${JSON.stringify(t).replace(/'/g,"\\'")}')`}};u.ISO_TIMESTAMP_RE=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;var m=u;export{m as BigQueryAdapter,N as bigqueryDialect};//# sourceMappingURL=bigquery.js.map
9
9
  //# sourceMappingURL=bigquery.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/sync/constants.ts","../../src/sync/adapters/bigquery.ts"],"names":["SYNC_VERSION_COLUMN","BigQueryDialect","logical","id","bigqueryDialect","_BigQueryAdapter","options","tableName","exists","metadata","f","table","qi","cols","c","notNull","ddl","columns","stmt","rows","primaryKey","allKeys","nonPkCols","k","source","row","i","updateSet","insertCols","insertVals","versionGuard","query","ids","escaped","v","sql","BigQueryAdapter"],"mappings":"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","file":"bigquery.js","sourcesContent":["/**\n * Internal constants shared between the worker, queue, schema mapper and\n * SQL adapters.\n */\n\n/**\n * Name of the SQL column that stores the publish-time `version` of each\n * sync event. Used by the worker to discard out-of-order PubSub deliveries\n * (the MERGE only updates rows when the incoming version is strictly\n * greater than the stored one).\n *\n * Two underscores prefix avoids collisions with user-defined fields.\n */\nexport const SYNC_VERSION_COLUMN = \"__sync_version\";\n","import { SYNC_VERSION_COLUMN } from \"../constants\";\nimport type {\n LogicalType,\n SqlAdapter,\n SqlColumn,\n SqlDialect,\n SqlTableDef,\n} from \"../types\";\n\n// ---------------------------------------------------------------------------\n// Dialect (internal — used only by BigQueryAdapter)\n// ---------------------------------------------------------------------------\n\n/** BigQuery SQL dialect mapping. */\nclass BigQueryDialect implements SqlDialect {\n readonly name = \"bigquery\";\n\n mapType(logical: LogicalType): string {\n switch (logical) {\n case \"string\":\n return \"STRING\";\n case \"number\":\n return \"FLOAT64\";\n case \"bigint\":\n return \"INT64\";\n case \"boolean\":\n return \"BOOL\";\n case \"timestamp\":\n return \"TIMESTAMP\";\n case \"json\":\n return \"JSON\";\n case \"text\":\n return \"STRING\";\n }\n }\n\n quoteIdentifier(id: string): string {\n return `\\`${id}\\``;\n }\n}\n\n/** Shared BigQuery dialect singleton. */\nexport const bigqueryDialect: SqlDialect = new BigQueryDialect();\n\n// ---------------------------------------------------------------------------\n// Adapter\n// ---------------------------------------------------------------------------\n\n/**\n * BigQuery implementation of {@link SqlAdapter}.\n *\n * Accepts an already-configured BigQuery client so the library does not pull\n * in `@google-cloud/bigquery` as a hard dependency.\n *\n * @example\n * ```ts\n * import { BigQuery } from \"@google-cloud/bigquery\";\n * import { BigQueryAdapter } from \"./adapters/bigquery\";\n *\n * const adapter = new BigQueryAdapter({\n * bigquery: new BigQuery({ projectId: \"my-project\" }),\n * datasetId: \"my_dataset\",\n * });\n * ```\n */\nexport class BigQueryAdapter implements SqlAdapter {\n private readonly bigquery: any;\n private readonly datasetId: string;\n\n constructor(options: { bigquery: any; datasetId: string }) {\n this.bigquery = options.bigquery;\n this.datasetId = options.datasetId;\n }\n\n /** The BigQuery SQL dialect. */\n get dialect(): SqlDialect {\n return bigqueryDialect;\n }\n\n /** Check whether a table exists in the dataset. */\n async tableExists(tableName: string): Promise<boolean> {\n const [exists] = await this.dataset.table(tableName).exists();\n return exists;\n }\n\n /** Return the column names currently present in the table. */\n async getTableColumns(tableName: string): Promise<string[]> {\n const [metadata] = await this.dataset.table(tableName).getMetadata();\n const fields: Array<{ name: string }> = metadata.schema?.fields ?? [];\n return fields.map((f) => f.name);\n }\n\n /** Create a table using a fully-qualified name. */\n async createTable(table: SqlTableDef): Promise<void> {\n const qi = (id: string) => this.dialect.quoteIdentifier(id);\n const cols = table.columns\n .map((c) => {\n const notNull = c.isPrimaryKey ? \" NOT NULL\" : \"\";\n return ` ${qi(c.name)} ${c.sqlType}${notNull}`;\n })\n .join(\",\\n\");\n\n const ddl = `CREATE TABLE IF NOT EXISTS ${this.fqn(table.tableName)} (\\n${cols}\\n);`;\n await this.bigquery.query({ query: ddl });\n }\n\n /** Add columns to an existing table using a fully-qualified name. */\n async addColumns(tableName: string, columns: SqlColumn[]): Promise<void> {\n const qi = (id: string) => this.dialect.quoteIdentifier(id);\n for (const c of columns) {\n const stmt = `ALTER TABLE ${this.fqn(tableName)} ADD COLUMN ${qi(c.name)} ${c.sqlType};`;\n await this.bigquery.query({ query: stmt });\n }\n }\n\n /** Append rows via BigQuery streaming insert. */\n async insertRows(\n tableName: string,\n rows: Record<string, unknown>[],\n ): Promise<void> {\n if (rows.length === 0) return;\n await this.dataset.table(tableName).insert(rows);\n }\n\n /**\n * Upsert rows using a MERGE DML statement.\n *\n * Builds a source table from inline SELECT … UNION ALL rows and merges\n * into the target on the given primary key.\n */\n async upsertRows(\n tableName: string,\n rows: Record<string, unknown>[],\n primaryKey: string,\n ): Promise<void> {\n if (rows.length === 0) return;\n\n const allKeys = Object.keys(rows[0]!);\n const nonPkCols = allKeys.filter((k) => k !== primaryKey);\n const qi = (id: string) => this.dialect.quoteIdentifier(id);\n\n // Build inline source: SELECT val AS col, … UNION ALL SELECT …\n const selects = rows.map((row, i) => {\n const values = allKeys\n .map((k) => {\n const aliased =\n i === 0\n ? `${this.escapeValue(row[k])} AS ${qi(k)}`\n : this.escapeValue(row[k]);\n return aliased;\n })\n .join(\", \");\n return `SELECT ${values}`;\n });\n\n const source = selects.join(\" UNION ALL\\n \");\n\n // UPDATE SET clause (non-PK columns).\n // Note: when __sync_version is present we still update it so the row\n // tracks the latest applied version.\n const updateSet = nonPkCols\n .map((c) => `T.${qi(c)} = S.${qi(c)}`)\n .join(\", \");\n\n // INSERT columns / values\n const insertCols = allKeys.map((c) => qi(c)).join(\", \");\n const insertVals = allKeys.map((c) => `S.${qi(c)}`).join(\", \");\n\n // Out-of-order protection: only UPDATE when the incoming version is\n // strictly greater than the stored one (NULL stored version means the\n // row pre-dates versioning → always update).\n const versionGuard = allKeys.includes(SYNC_VERSION_COLUMN)\n ? ` AND (T.${qi(SYNC_VERSION_COLUMN)} IS NULL OR S.${qi(SYNC_VERSION_COLUMN)} > T.${qi(SYNC_VERSION_COLUMN)})`\n : \"\";\n\n const query = [\n `MERGE ${this.fqn(tableName)} AS T`,\n `USING (\\n ${source}\\n ) AS S`,\n `ON T.${qi(primaryKey)} = S.${qi(primaryKey)}`,\n `WHEN MATCHED${versionGuard} THEN UPDATE SET ${updateSet}`,\n `WHEN NOT MATCHED THEN INSERT (${insertCols}) VALUES (${insertVals});`,\n ].join(\"\\n\");\n\n await this.bigquery.query({ query });\n }\n\n /** Delete rows by primary-key values. */\n async deleteRows(\n tableName: string,\n primaryKey: string,\n ids: string[],\n ): Promise<void> {\n if (ids.length === 0) return;\n\n const qi = (id: string) => this.dialect.quoteIdentifier(id);\n const escaped = ids.map((v) => this.escapeValue(v)).join(\", \");\n const query = `DELETE FROM ${this.fqn(tableName)} WHERE ${qi(primaryKey)} IN (${escaped});`;\n\n await this.bigquery.query({ query });\n }\n\n /** Execute a raw SQL statement (used by the migration manager). */\n async executeRaw(sql: string): Promise<void> {\n await this.bigquery.query({ query: sql });\n }\n\n // -------------------------------------------------------------------------\n // Private helpers\n // -------------------------------------------------------------------------\n\n /** The BigQuery Dataset handle. */\n private get dataset() {\n return this.bigquery.dataset(this.datasetId);\n }\n\n /** Return the fully-qualified table reference (`` `dataset.table` ``). */\n private fqn(tableName: string): string {\n return `\\`${this.datasetId}.${tableName}\\``;\n }\n\n /** ISO 8601 timestamp pattern (e.g. 2026-03-29T20:59:27.394Z) */\n private static readonly ISO_TIMESTAMP_RE =\n /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}/;\n\n /** Escape a value for use as a SQL literal. */\n private escapeValue(v: unknown): string {\n if (v === null || v === undefined) return \"NULL\";\n if (typeof v === \"boolean\") return v ? \"TRUE\" : \"FALSE\";\n if (typeof v === \"number\" || typeof v === \"bigint\") return String(v);\n if (typeof v === \"string\") {\n // ISO 8601 timestamps → TIMESTAMP literal (keeps type-safety with BQ TIMESTAMP columns)\n if (BigQueryAdapter.ISO_TIMESTAMP_RE.test(v)) {\n return `TIMESTAMP('${v}')`;\n }\n // Detect JSON strings (arrays/objects) → use PARSE_JSON for native JSON columns\n if (\n (v.startsWith(\"[\") && v.endsWith(\"]\")) ||\n (v.startsWith(\"{\") && v.endsWith(\"}\"))\n ) {\n return `PARSE_JSON('${v.replace(/'/g, \"\\\\'\")}')`;\n }\n return `'${v.replace(/'/g, \"\\\\'\")}'`;\n }\n // Objects / arrays → JSON\n return `PARSE_JSON('${JSON.stringify(v).replace(/'/g, \"\\\\'\")}')`;\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/sync/constants.ts","../../src/sync/adapters/bigquery.ts"],"names":["SYNC_VERSION_COLUMN","isConcurrentUpdateError","err","e","x","withRetry","fn","maxRetries","baseMs","attempt","cap","delay","res","BigQueryDialect","logical","id","bigqueryDialect","_BigQueryAdapter","options","tableName","exists","metadata","f","table","qi","cols","c","notNull","ddl","columns","stmt","rows","primaryKey","allKeys","nonPkCols","k","source","row","i","updateSet","insertCols","insertVals","versionGuard","query","ids","escaped","v","sql","BigQueryAdapter"],"mappings":"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","file":"bigquery.js","sourcesContent":["/**\n * Internal constants shared between the worker, queue, schema mapper and\n * SQL adapters.\n */\n\n/**\n * Name of the SQL column that stores the publish-time `version` of each\n * sync event. Used by the worker to discard out-of-order PubSub deliveries\n * (the MERGE only updates rows when the incoming version is strictly\n * greater than the stored one).\n *\n * Two underscores prefix avoids collisions with user-defined fields.\n */\nexport const SYNC_VERSION_COLUMN = \"__sync_version\";\n","import { SYNC_VERSION_COLUMN } from \"../constants\";\nimport type {\n LogicalType,\n SqlAdapter,\n SqlColumn,\n SqlDialect,\n SqlTableDef,\n} from \"../types\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Returns true when the BigQuery error is a \"concurrent update\" serialization\n * conflict (code 400, reason \"invalidQuery\" containing \"serialize access\").\n * These are safe to retry after a brief back-off.\n */\nfunction isConcurrentUpdateError(err: unknown): boolean {\n if (!err || typeof err !== \"object\") return false;\n const e = err as Record<string, unknown>;\n if (e[\"code\"] !== 400) return false;\n const errors = Array.isArray(e[\"errors\"]) ? e[\"errors\"] : [];\n return errors.some(\n (x: any) =>\n typeof x?.message === \"string\" &&\n x.message.toLowerCase().includes(\"serialize access\"),\n );\n}\n\n/**\n * Execute `fn`, retrying up to `maxRetries` times when BigQuery returns a\n * concurrent-update error. Uses full-jitter exponential back-off.\n */\nasync function withRetry<T>(\n fn: () => Promise<T>,\n maxRetries = 10,\n baseMs = 500,\n): Promise<T> {\n let attempt = 0;\n while (true) {\n try {\n return await fn();\n } catch (err) {\n attempt++;\n if (!isConcurrentUpdateError(err) || attempt > maxRetries) throw err;\n const cap = baseMs * Math.pow(2, attempt);\n const delay = Math.random() * cap; // full jitter\n await new Promise((res) => setTimeout(res, delay));\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Dialect (internal — used only by BigQueryAdapter)\n// ---------------------------------------------------------------------------\n\n/** BigQuery SQL dialect mapping. */\nclass BigQueryDialect implements SqlDialect {\n readonly name = \"bigquery\";\n\n mapType(logical: LogicalType): string {\n switch (logical) {\n case \"string\":\n return \"STRING\";\n case \"number\":\n return \"FLOAT64\";\n case \"bigint\":\n return \"INT64\";\n case \"boolean\":\n return \"BOOL\";\n case \"timestamp\":\n return \"TIMESTAMP\";\n case \"json\":\n return \"JSON\";\n case \"text\":\n return \"STRING\";\n }\n }\n\n quoteIdentifier(id: string): string {\n return `\\`${id}\\``;\n }\n}\n\n/** Shared BigQuery dialect singleton. */\nexport const bigqueryDialect: SqlDialect = new BigQueryDialect();\n\n// ---------------------------------------------------------------------------\n// Adapter\n// ---------------------------------------------------------------------------\n\n/**\n * BigQuery implementation of {@link SqlAdapter}.\n *\n * Accepts an already-configured BigQuery client so the library does not pull\n * in `@google-cloud/bigquery` as a hard dependency.\n *\n * @example\n * ```ts\n * import { BigQuery } from \"@google-cloud/bigquery\";\n * import { BigQueryAdapter } from \"./adapters/bigquery\";\n *\n * const adapter = new BigQueryAdapter({\n * bigquery: new BigQuery({ projectId: \"my-project\" }),\n * datasetId: \"my_dataset\",\n * });\n * ```\n */\nexport class BigQueryAdapter implements SqlAdapter {\n private readonly bigquery: any;\n private readonly datasetId: string;\n\n constructor(options: { bigquery: any; datasetId: string }) {\n this.bigquery = options.bigquery;\n this.datasetId = options.datasetId;\n }\n\n /** The BigQuery SQL dialect. */\n get dialect(): SqlDialect {\n return bigqueryDialect;\n }\n\n /** Check whether a table exists in the dataset. */\n async tableExists(tableName: string): Promise<boolean> {\n const [exists] = await this.dataset.table(tableName).exists();\n return exists;\n }\n\n /** Return the column names currently present in the table. */\n async getTableColumns(tableName: string): Promise<string[]> {\n const [metadata] = await this.dataset.table(tableName).getMetadata();\n const fields: Array<{ name: string }> = metadata.schema?.fields ?? [];\n return fields.map((f) => f.name);\n }\n\n /** Create a table using a fully-qualified name. */\n async createTable(table: SqlTableDef): Promise<void> {\n const qi = (id: string) => this.dialect.quoteIdentifier(id);\n const cols = table.columns\n .map((c) => {\n const notNull = c.isPrimaryKey ? \" NOT NULL\" : \"\";\n return ` ${qi(c.name)} ${c.sqlType}${notNull}`;\n })\n .join(\",\\n\");\n\n const ddl = `CREATE TABLE IF NOT EXISTS ${this.fqn(table.tableName)} (\\n${cols}\\n);`;\n await this.bigquery.query({ query: ddl });\n }\n\n /** Add columns to an existing table using a fully-qualified name. */\n async addColumns(tableName: string, columns: SqlColumn[]): Promise<void> {\n const qi = (id: string) => this.dialect.quoteIdentifier(id);\n for (const c of columns) {\n const stmt = `ALTER TABLE ${this.fqn(tableName)} ADD COLUMN ${qi(c.name)} ${c.sqlType};`;\n await this.bigquery.query({ query: stmt });\n }\n }\n\n /** Append rows via BigQuery streaming insert. */\n async insertRows(\n tableName: string,\n rows: Record<string, unknown>[],\n ): Promise<void> {\n if (rows.length === 0) return;\n await this.dataset.table(tableName).insert(rows);\n }\n\n /**\n * Upsert rows using a MERGE DML statement.\n *\n * Builds a source table from inline SELECT … UNION ALL rows and merges\n * into the target on the given primary key.\n */\n async upsertRows(\n tableName: string,\n rows: Record<string, unknown>[],\n primaryKey: string,\n ): Promise<void> {\n if (rows.length === 0) return;\n\n const allKeys = Object.keys(rows[0]!);\n const nonPkCols = allKeys.filter((k) => k !== primaryKey);\n const qi = (id: string) => this.dialect.quoteIdentifier(id);\n\n // Build inline source: SELECT val AS col, … UNION ALL SELECT …\n const selects = rows.map((row, i) => {\n const values = allKeys\n .map((k) => {\n const aliased =\n i === 0\n ? `${this.escapeValue(row[k])} AS ${qi(k)}`\n : this.escapeValue(row[k]);\n return aliased;\n })\n .join(\", \");\n return `SELECT ${values}`;\n });\n\n const source = selects.join(\" UNION ALL\\n \");\n\n // UPDATE SET clause (non-PK columns).\n // Note: when __sync_version is present we still update it so the row\n // tracks the latest applied version.\n const updateSet = nonPkCols\n .map((c) => `T.${qi(c)} = S.${qi(c)}`)\n .join(\", \");\n\n // INSERT columns / values\n const insertCols = allKeys.map((c) => qi(c)).join(\", \");\n const insertVals = allKeys.map((c) => `S.${qi(c)}`).join(\", \");\n\n // Out-of-order protection: only UPDATE when the incoming version is\n // strictly greater than the stored one (NULL stored version means the\n // row pre-dates versioning → always update).\n const versionGuard = allKeys.includes(SYNC_VERSION_COLUMN)\n ? ` AND (T.${qi(SYNC_VERSION_COLUMN)} IS NULL OR S.${qi(SYNC_VERSION_COLUMN)} > T.${qi(SYNC_VERSION_COLUMN)})`\n : \"\";\n\n const query = [\n `MERGE ${this.fqn(tableName)} AS T`,\n `USING (\\n ${source}\\n ) AS S`,\n `ON T.${qi(primaryKey)} = S.${qi(primaryKey)}`,\n `WHEN MATCHED${versionGuard} THEN UPDATE SET ${updateSet}`,\n `WHEN NOT MATCHED THEN INSERT (${insertCols}) VALUES (${insertVals});`,\n ].join(\"\\n\");\n\n await withRetry(() => this.bigquery.query({ query }));\n }\n\n /** Delete rows by primary-key values. */\n async deleteRows(\n tableName: string,\n primaryKey: string,\n ids: string[],\n ): Promise<void> {\n if (ids.length === 0) return;\n\n const qi = (id: string) => this.dialect.quoteIdentifier(id);\n const escaped = ids.map((v) => this.escapeValue(v)).join(\", \");\n const query = `DELETE FROM ${this.fqn(tableName)} WHERE ${qi(primaryKey)} IN (${escaped});`;\n\n await withRetry(() => this.bigquery.query({ query }));\n }\n\n /** Execute a raw SQL statement (used by the migration manager). */\n async executeRaw(sql: string): Promise<void> {\n await this.bigquery.query({ query: sql });\n }\n\n // -------------------------------------------------------------------------\n // Private helpers\n // -------------------------------------------------------------------------\n\n /** The BigQuery Dataset handle. */\n private get dataset() {\n return this.bigquery.dataset(this.datasetId);\n }\n\n /** Return the fully-qualified table reference (`` `dataset.table` ``). */\n private fqn(tableName: string): string {\n return `\\`${this.datasetId}.${tableName}\\``;\n }\n\n /** ISO 8601 timestamp pattern (e.g. 2026-03-29T20:59:27.394Z) */\n private static readonly ISO_TIMESTAMP_RE =\n /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}/;\n\n /** Escape a value for use as a SQL literal. */\n private escapeValue(v: unknown): string {\n if (v === null || v === undefined) return \"NULL\";\n if (typeof v === \"boolean\") return v ? \"TRUE\" : \"FALSE\";\n if (typeof v === \"number\" || typeof v === \"bigint\") return String(v);\n if (typeof v === \"string\") {\n // ISO 8601 timestamps → TIMESTAMP literal (keeps type-safety with BQ TIMESTAMP columns)\n if (BigQueryAdapter.ISO_TIMESTAMP_RE.test(v)) {\n return `TIMESTAMP('${v}')`;\n }\n // Detect JSON strings (arrays/objects) → use PARSE_JSON for native JSON columns\n if (\n (v.startsWith(\"[\") && v.endsWith(\"]\")) ||\n (v.startsWith(\"{\") && v.endsWith(\"}\"))\n ) {\n return `PARSE_JSON('${v.replace(/'/g, \"\\\\'\")}')`;\n }\n return `'${v.replace(/'/g, \"\\\\'\")}'`;\n }\n // Objects / arrays → JSON\n return `PARSE_JSON('${JSON.stringify(v).replace(/'/g, \"\\\\'\")}')`;\n }\n}\n"]}
@@ -1,7 +1,7 @@
1
- 'use strict';function te(o){let e=[],t=o.replace(/[.*+?^${}()|[\]\\]/g,s=>s===":"?s:`\\${s}`).replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g,(s,i)=>(e.push(i),"([^/]+)"));return {pattern:new RegExp(`^${t}$`),paramNames:e}}function ne(o){let e=o.path??o.url??"/",t=e.indexOf("?");return t===-1?e:e.slice(0,t)}var M=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:i,paramNames:u}=te(t);return this.routes.push({method:e.toUpperCase(),pattern:i,paramNames:u,handler:s}),this}async handle(e,t){let s=(e.method??"GET").toUpperCase(),i=ne(e),u=null,y={};for(let k of this.routes){if(k.method!==s)continue;let S=i.match(k.pattern);if(S){u=k,y={},k.paramNames.forEach((f,c)=>{y[f]=decodeURIComponent(S[c+1]??"");});break}}let x=Object.assign(e,{params:y}),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 i=0,u=async()=>{if(i<this.middlewares.length){let y=this.middlewares[i++];await y(e,t,u);}else await s(e,t);};await u();}};function O(o,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",y=process.env.FUNCTION_REGION??"us-central1",x=(process.env.FUNCTION_TARGET??"").replace(/\./g,"-");return `/${u}/${y}/${x}${t}`}let s=process.env.K_SERVICE,i=o?.hostname??o?.headers?.host??"";return s&&typeof i=="string"&&i.includes("cloudfunctions.net")?`/${s.toLowerCase()}${t}`:t}function B(o){return !!o&&typeof o=="object"&&o.__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 F(o){let e=o,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(o){let e=o;if(e._zod?.def?.innerType)return e._zod.def.innerType;if(e._def?.innerType)return e._def.innerType}function H(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 D="__sync_version";var ie=new Set(["ZodOptional","ZodNullable","ZodDefault"]);function Q(o){let e=o,t=false;for(;;){let s=F(e);if(!ie.has(s))break;(s==="ZodOptional"||s==="ZodNullable")&&(t=true);let i=G(e);if(!i)break;e=i;}return {inner:e,nullable:t}}var W={ZodString:"string",ZodNumber:"number",ZodBigInt:"bigint",ZodBoolean:"boolean",ZodDate:"timestamp",ZodEnum:"string",ZodNativeEnum:"string",ZodLiteral:"string"};function ae(o){let{inner:e}=Q(o);return W[F(e)]??"json"}function J(o,e,t,s,i,u,y,x){for(let[l,k]of Object.entries(o)){let S=t?`${t}__${l}`:l;if(i.has(l)||i.has(S))continue;let{inner:f,nullable:c}=Q(k),d=F(f),a=s||c;if(d==="ZodObject"){let r=H(f);J(r,e,S,a,i,u,y,x);continue}let n=W[d]??"json",p=S===y||l===y,b=u[S]??u[l]??S;x.push({name:b,sqlType:e.mapType(n),nullable:p?false:a,isPrimaryKey:p});}}function _(o,e,t={}){let{primaryKey:s,exclude:i=[],columnMap:u={}}=t,y=new Set(i),x=H(o),l=[];return J(x,e,"",false,y,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(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(K)):o}function V(o,e,t){for(let[s,i]of Object.entries(o)){let u=e?`${e}__${s}`:s;i!=null&&typeof i=="object"&&!Array.isArray(i)&&!(i instanceof Date)&&!Buffer.isBuffer(i)&&!(i instanceof Uint8Array)&&typeof i.toDate!="function"&&!("latitude"in i&&"longitude"in i)?V(i,u,t):t[u]=K(i);}}function z(o,e){let t=new Set(e?.exclude),s=e?.columnMap??{},i={};V(o,"",i);let u={};for(let[y,x]of Object.entries(i)){if(t.has(y))continue;let l=y.split("__")[0];if(l!==y&&t.has(l))continue;let k=s[y]??(y.includes("__")?s[y.split("__").pop()]:void 0)??y;u[k]=x;}return u}function P(o,e,t){return `<!DOCTYPE html>
1
+ 'use strict';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>
2
2
  <html lang="en"><head>
3
3
  <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
4
- <title>${o} \u2014 Sync Admin</title>
4
+ <title>${n} \u2014 Sync Admin</title>
5
5
  <style>
6
6
  *{box-sizing:border-box;margin:0;padding:0}
7
7
  body{font-family:system-ui,-apple-system,sans-serif;background:#f5f5f5;color:#1a1a1a;padding:2rem}
@@ -26,73 +26,66 @@
26
26
  </style>
27
27
  </head><body>
28
28
  <nav><a href="${e}/">\u2190 Dashboard</a></nav>
29
- <h1>${o}</h1>
29
+ <h1>${n}</h1>
30
30
  ${t}
31
- </body></html>`}function N(o,e,t=200){o.status(t).set("Content-Type","text/html; charset=utf-8").send(e);}function j(o,e,t=200){o.status(t).set("Content-Type","application/json").send(JSON.stringify(e,null,2));}function q(o){return (o.headers?.accept??"").includes("application/json")}function ce(o,e,t,s,i,u,y,x){let l=(i.basePath??"/").replace(/\/$/,"")||"",k=i.featuresFlag??{},S=[];for(let[c,d]of Object.entries(o)){let a=u[c];S.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 M;if(i.auth)if(B(i.auth)){let c=i.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 i.auth=="function")f.use(i.auth);else {let c=i.auth.realm??"Sync Admin",d="Basic "+Buffer.from(`${i.auth.username}:${i.auth.password}`).toString("base64");f.use((a,n,p)=>{if((a.headers?.authorization??"")!==d){n.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),n=S.map(h=>{let C=[];return k.healthCheck&&C.push(`<a class="btn" href="${a}/${h.name}/health">Health</a>`),k.manualSync&&C.push(`<a class="btn btn-primary" href="${a}/${h.name}/force-sync">Force Sync</a>`),`<tr>
32
- <td><strong>${h.name}</strong></td>
33
- <td>${h.tableName}</td>
34
- <td>${h.isGroup?'<span class="badge badge-warn">group</span>':'<span class="badge badge-ok">collection</span>'}</td>
35
- <td>${h.schema?"\u2713":"\u2717"}</td>
36
- <td>${C.join(" ")}</td>
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>
32
+ <td><strong>${i.name}</strong></td>
33
+ <td>${i.tableName}</td>
34
+ <td>${i.isGroup?'<span class="badge badge-warn">group</span>':'<span class="badge badge-ok">collection</span>'}</td>
35
+ <td>${i.schema?"\u2713":"\u2717"}</td>
36
+ <td>${S.join(" ")}</td>
37
37
  </tr>`}).join(`
38
- `),p=k.viewQueue?`<p><a class="btn" href="${a}/queues">View Queues</a></p>`:"",b=k.configCheck?`<p style="margin-top:.5rem"><a class="btn" href="${a}/config-check">\u2699 Config Check</a></p>`:"",r=P("Sync Dashboard",a,`<div class="card">
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">
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
- <tbody>${n}</tbody>
41
+ <tbody>${o}</tbody>
42
42
  </table>
43
43
  ${p}
44
- ${b}
45
- </div>`);N(d,r);}),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),n=S.find(m=>m.name===c.params.repoName);if(!n){N(d,P("Not Found",a,`<p>Unknown repo: ${c.params.repoName}</p>`),404);return}if(!n.schema){N(d,P("Health Check",a,`<p class="badge badge-warn">No Zod schema attached to "${n.name}"</p>`));return}let p=_(n.schema,e.dialect,{primaryKey:n.documentKey,exclude:n.repoCfg?.exclude,columnMap:n.repoCfg?.columnMap}),b=[],r=false,h=null;try{r=await e.tableExists(n.tableName),r&&(b=await e.getTableColumns(n.tableName));}catch(m){h=m?.message??String(m);}let C=new Set(b),$=new Set(p.map(m=>m.name)),T=p.filter(m=>!C.has(m.name)),E=b.filter(m=>!$.has(m)),I=p.filter(m=>C.has(m.name)),g=r&&T.length===0&&!h;if(q(c)){j(d,{repo:n.name,table:n.tableName,tableExists:r,healthy:g,error:h,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:T.map(m=>({name:m.name,type:m.sqlType})),extra:E}});return}let w=g?'<span class="badge badge-ok">Healthy</span>':'<span class="badge badge-err">Unhealthy</span>',v=p.map(m=>{let L=C.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(`
46
- `),R=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(`
47
- `),A=P(`Health: ${n.name}`,a,`<div class="card">
48
- <p>Table: <code>${n.tableName}</code> ${r?w:'<span class="badge badge-err">NOT FOUND</span>'}</p>
49
- ${h?`<p class="badge badge-err">Error: ${h}</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(`
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>
48
+ ${S?`<p class="badge badge-err">Error: ${S}</p>`:""}
50
49
  <h2>Columns</h2>
51
50
  <table>
52
51
  <thead><tr><th>Column</th><th>SQL Type</th><th>Nullable</th><th>PK</th><th>Status</th></tr></thead>
53
- <tbody>${v}${R}</tbody>
52
+ <tbody>${R}${C}</tbody>
54
53
  </table>
55
- </div>`);N(d,A);}),k.manualSync&&(f.get(`${l}/:repoName/force-sync`,(c,d)=>{let a=O(c,l),n=S.find(b=>b.name===c.params.repoName);if(!n){N(d,P("Not Found",a,`<p>Unknown repo: ${c.params.repoName}</p>`),404);return}let p=P(`Force Sync: ${n.name}`,a,`<div class="card">
56
- <p>This will read <strong>all</strong> documents from the <code>${n.name}</code> Firestore collection
57
- and upsert them into the <code>${n.tableName}</code> SQL table.</p>
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">
55
+ <p>This will read <strong>all</strong> documents from the <code>${o.name}</code> Firestore collection
56
+ and upsert them into the <code>${o.tableName}</code> SQL table.</p>
58
57
  <p class="muted" style="margin:.75rem 0">This may take a while for large collections.</p>
59
- <form method="POST" action="${a}/${n.name}/force-sync">
58
+ <form method="POST" action="${a}/${o.name}/force-sync">
60
59
  <button type="submit" class="btn btn-primary">Start Force Sync</button>
61
60
  </form>
62
- </div>`);N(d,p);}),f.post(`${l}/:repoName/force-sync`,async(c,d)=>{let a=O(c,l),n=S.find(g=>g.name===c.params.repoName);if(!n){j(d,{error:`Unknown repo: ${c.params.repoName}`},404);return}let p=n.repo.ref;if(!p){j(d,{error:`No collection reference for "${n.name}"`},400);return}let b=0,r=0,h=[],C=500,$=p.limit(C),T=null;try{for(;;){let v=await(T?$.startAfter(T):$).get();if(v.empty)break;for(let R of v.docs){let A=R.data(),m=String(A[n.documentKey]??R.id),L=z(A,{exclude:n.repoCfg?.exclude,columnMap:n.repoCfg?.columnMap});try{await s({operation:"UPSERT",repoName:n.name,docId:m,data:L,timestamp:new Date().toISOString()}),b++;}catch(U){r++;let ee=U?.message??String(U);console.error(`[ForceSync:${n.name}] doc=${m} failed:`,U),h.length<5&&h.push(`${m}: ${ee}`);}}if(T=v.docs[v.docs.length-1],v.docs.length<C)break}let g=t.get(n.name);g&&await g.flush();}catch(g){if(q(c)){j(d,{error:g?.message??String(g),synced:b,errors:r},500);return}N(d,P(`Force Sync: ${n.name}`,a,`<div class="card">
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">
63
62
  <p class="badge badge-err">Error: ${g?.message??String(g)}</p>
64
- <p>Synced ${b} docs before failure (${r} errors).</p>
65
- </div>`),500);return}if(q(c)){j(d,{repo:n.name,table:n.tableName,synced:b,errors:r,...h.length>0&&{errorSamples:h}});return}let E=h.length>0?`<details style="margin-top:1rem"><summary>First ${h.length} error(s)</summary>
66
- <pre style="white-space:pre-wrap">${h.map(g=>g.replace(/[<>&]/g,w=>`&#${w.charCodeAt(0)};`)).join(`
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(`
67
66
 
68
- `)}</pre></details>`:"",I=P(`Force Sync: ${n.name}`,a,`<div class="card">
69
- <p class="badge ${r>0?"badge-warn":"badge-ok"}">${r>0?"Completed with errors":"Complete"}</p>
70
- <p>Synced <strong>${b}</strong> documents to <code>${n.tableName}</code>.</p>
71
- ${r>0?`<p class="badge badge-warn">${r} error(s)</p>`:""}
72
- ${E}
73
- </div>`);N(d,I);})),k.viewQueue&&f.get(`${l}/queues`,(c,d)=>{let a=O(c,l),n=[];for(let r of S){let h=t.get(r.name);n.push({repo:r.name,table:r.tableName,pending:h?h.size:0});}if(q(c)){j(d,{queues:n});return}let p=n.map(r=>`<tr><td>${r.repo}</td><td>${r.table}</td><td>${r.pending===0?'<span class="badge badge-ok">0</span>':`<span class="badge badge-warn">${r.pending}</span>`}</td></tr>`).join(`
74
- `),b=P("Sync Queues",a,`<div class="card">
75
- <table>
76
- <thead><tr><th>Repository</th><th>Table</th><th>Pending</th></tr></thead>
77
- <tbody>${p}</tbody>
78
- </table>
79
- </div>`);N(d,b);}),k.configCheck&&f.get(`${l}/config-check`,async(c,d)=>{let a=O(c,l),n=process.env.GCLOUD_PROJECT??process.env.GOOGLE_CLOUD_PROJECT??process.env.GCP_PROJECT??"unknown",p="https://console.cloud.google.com",b=x??"firestore-sync",r=[];try{await e.tableExists("__nonexistent_health_check__"),r.push({name:"BigQuery API",category:"bigquery",status:"ok",message:"BigQuery API is reachable"});}catch(g){let w=g?.message??String(g),v=w.toLowerCase(),R=v.includes("disabled")||v.includes("has not been used")||v.includes("accessnotconfigured"),A=v.includes("permission")||w.includes("403")||v.includes("access denied"),m=v.includes("project")&&v.includes("not found"),L=v.includes("not found")||w.includes("404");R?r.push({name:"BigQuery API",category:"bigquery",status:"error",message:"BigQuery API is not enabled",fix:{gcloud:`gcloud services enable bigquery.googleapis.com --project=${n}`,console:`${p}/apis/library/bigquery.googleapis.com?project=${n}`}}):m?r.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`}}):A?r.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=${n})`,`gcloud projects add-iam-policy-binding ${n} --member="serviceAccount:$SA" --role="roles/bigquery.dataEditor"`,`gcloud projects add-iam-policy-binding ${n} --member="serviceAccount:$SA" --role="roles/bigquery.jobUser"`].join(`
80
- `),console:`${p}/iam-admin/iam?project=${n}`}}):L?r.push({name:"BigQuery Dataset",category:"bigquery",status:"error",message:`Dataset not found: ${w}`,fix:{hint:"Create the dataset first",gcloud:`bq mk --dataset ${n}:YOUR_DATASET_ID`,console:`${p}/bigquery?project=${n}`}}):r.push({name:"BigQuery API",category:"bigquery",status:"ok",message:"BigQuery API is reachable (table lookup returned expected error)"});}for(let g of S)try{let w=await e.tableExists(g.tableName);r.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){r.push({name:`Table: ${g.tableName}`,category:"bigquery",status:"error",message:w?.message??String(w)});}if(y)for(let g of S){let w=`${b}-${g.name}`;try{let v=y.topic(w);if(typeof v.exists=="function"){let[R]=await v.exists();r.push({name:`Topic: ${w}`,category:"pubsub",status:R?"ok":"error",message:R?`Topic \`${w}\` exists`:`Topic \`${w}\` does not exist`,...!R&&{fix:{gcloud:`gcloud pubsub topics create ${w} --project=${n}`,console:`${p}/cloudpubsub/topic/list?project=${n}`}}});}else r.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=${n}`,console:`${p}/cloudpubsub/topic/list?project=${n}`,hint:"Ensure the topic exists. It is auto-created by the Firebase emulator but must exist in production."}});}catch(v){let R=v?.message??String(v),A=R.includes("disabled")||R.includes("has not been used");if(r.push({name:A?"Pub/Sub API":`Topic: ${w}`,category:"pubsub",status:"error",message:A?"Pub/Sub API is not enabled":R,fix:A?{gcloud:`gcloud services enable pubsub.googleapis.com --project=${n}`,console:`${p}/apis/library/pubsub.googleapis.com?project=${n}`}:{gcloud:`gcloud pubsub topics create ${w} --project=${n}`,console:`${p}/cloudpubsub/topic/list?project=${n}`}}),A)break}}else r.push({name:"Pub/Sub Client",category:"pubsub",status:"warn",message:"PubSub client not available for config check"});if(q(c)){let g=r.every(w=>w.status==="ok");j(d,{project:n,healthy:g,checks:r});return}let h=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>',C={bigquery:r.filter(g=>g.category==="bigquery"),pubsub:r.filter(g=>g.category==="pubsub"),firestore:r.filter(g=>g.category==="firestore")},$=(g,w)=>{if(w.length===0)return "";let v=w.map(R=>{let A="";if(R.fix){let m=[];R.fix.hint&&m.push(`<p class="muted">${R.fix.hint}</p>`),R.fix.gcloud&&m.push(`<pre>$ ${R.fix.gcloud}</pre>`),R.fix.console&&m.push(`<p><a href="${R.fix.console}" target="_blank">Open GCP Console \u2192</a></p>`),A=`<div style="margin-top:.5rem">${m.join("")}</div>`;}return `<tr>
81
- <td>${h(R.status)}</td>
82
- <td><strong>${R.name}</strong><br><span class="muted">${R.message}</span>${A}</td>
67
+ `)}</pre></details>`:"",I=N(`Force Sync: ${o.name}`,a,`<div class="card">
68
+ <p class="badge ${i>0?"badge-warn":"badge-ok"}">${i>0?"Completed with errors":"Complete"}</p>
69
+ <p>Synced <strong>${b}</strong> documents to <code>${o.tableName}</code>.</p>
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>
74
+ <td>${S(C.status)}</td>
75
+ <td><strong>${C.name}</strong><br><span class="muted">${C.message}</span>${$}</td>
83
76
  </tr>`}).join(`
84
77
  `);return `<h2>${g}</h2>
85
78
  <table><thead><tr><th style="width:80px">Status</th><th>Check</th></tr></thead>
86
- <tbody>${v}</tbody></table>`},E=r.every(g=>g.status==="ok")?'<span class="badge badge-ok">All checks passed</span>':'<span class="badge badge-warn">Some issues found</span>',I=P("Config Check",a,`<div class="card">
87
- <p>Project: <code>${n}</code> ${E}</p>
88
- ${$("BigQuery",C.bigquery)}
89
- ${$("Pub/Sub",C.pubsub)}
90
- ${$("Firestore",C.firestore)}
91
- </div>`);N(d,I);}),async(c,d)=>{await f.handle(c,d);}}function Y(o,e){let t=e.columns.map(s=>{let i=s.isPrimaryKey?" NOT NULL":"";return ` ${o.quoteIdentifier(s.name)} ${s.sqlType}${i}`}).join(`,
92
- `);return `CREATE TABLE IF NOT EXISTS ${o.quoteIdentifier(e.tableName)} (
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(`,
85
+ `);return `CREATE TABLE IF NOT EXISTS ${n.quoteIdentifier(e.tableName)} (
93
86
  ${t}
94
- );`}function de(o,e,t){return t.map(s=>`ALTER TABLE ${o.quoteIdentifier(e)} ADD COLUMN ${o.quoteIdentifier(s.name)} ${s.sqlType};`).join(`
95
- `)}function ue(o,e,t){let s=[];for(let[i,u]of Object.entries(o)){let y=u.schema??u._schema??void 0;if(!y)continue;let x=t?.repos?.[i],l=x?.tableName??i,k=u._systemKeys?.[0]??u.documentKey??"docId",S=_(y,e,{primaryKey:k,exclude:x?.exclude,columnMap:x?.columnMap}),f={tableName:l,columns:S};s.push(Y(e,f));}return s.join(`
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(`
96
89
 
97
- `)}async function le(o,e,t){let s={created:[],altered:[],upToDate:[],skipped:[]};for(let[i,u]of Object.entries(o)){let y=u.schema??void 0;if(!y){s.skipped.push(i);continue}let x=t?.repos?.[i],l=x?.tableName??i,k=u._systemKeys?.[0]??u.documentKey??"docId",S=_(y,e.dialect,{primaryKey:k,exclude:x?.exclude,columnMap:x?.columnMap}),f={tableName:l,columns:S};if(!await e.tableExists(l))await e.createTable(f),s.created.push(l);else {let d=new Set(await e.getTableColumns(l)),a=S.filter(n=>!d.has(n.name));a.length>0?(await e.addColumns(l,a),s.altered.push(l)):s.upToDate.push(l);}}return s}var Z=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,s=[];for(let u of e)if(u.operation==="DELETE")s.push(u.docId),t.delete(u.docId);else if(u.data){let y=t.get(u.docId);if(!y)t.set(u.docId,u.data);else {let x=Number(y[D]??0);Number(u.data[D]??0)>=x&&t.set(u.docId,u.data);}}let i=Array.from(t.values());i.length>0&&await this.adapter.upsertRows(this.tableName,i,this.primaryKey),s.length>0&&await this.adapter.deleteRows(this.tableName,this.primaryKey,s);}catch(t){this.onFlushError?await this.onFlushError(e,t).catch(s=>{console.error(`[SyncQueue] Flush error for ${this.tableName}:`,t),console.error("[SyncQueue] Error handler also failed:",s);}):(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 pe="firestore-sync";function fe(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 ge(o,e){let{onDocumentCreated:t,onDocumentUpdated:s,onDocumentDeleted:i}=e.deps.firestoreTriggers,u=e.deps.pubsub,y=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 S(f,c){await k(f).publishMessage({json:c});}for(let[f,c]of Object.entries(o)){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 n=c._systemKeys?.[0]??"docId",p=`${y}-${f}`;x[`${f}_onCreate`]=t(a,async b=>{let r=b.data;if(!r)return;let h=r.data();if(!h)return;let C=String(h[n]??r.id),$=z(h,{exclude:d?.exclude,columnMap:d?.columnMap}),T={operation:"INSERT",repoName:f,docId:C,data:$,timestamp:new Date().toISOString(),version:Date.now()};await S(p,T);}),x[`${f}_onUpdate`]=s(a,async b=>{let r=b.data?.after;if(!r)return;let h=r.data();if(!h)return;let C=String(h[n]??r.id),$=z(h,{exclude:d?.exclude,columnMap:d?.columnMap}),T={operation:"UPSERT",repoName:f,docId:C,data:$,timestamp:new Date().toISOString(),version:Date.now()};await S(p,T);}),x[`${f}_onDelete`]=i(a,async b=>{let r=b.data;if(!r)return;let h=r.data(),C=String(h?.[n]??r.id),$={operation:"DELETE",repoName:f,docId:C,data:null,timestamp:new Date().toISOString(),version:Date.now()};await S(p,$);});}return x}var X=new Set;async function me(o,e,t,s,i,u,y){if(X.has(o))return;let x=_(t,e.dialect,{primaryKey:i,exclude:u,columnMap:y});if(!await e.tableExists(s))await e.createTable({tableName:s,columns:x});else {let k=new Set(await e.getTableColumns(s)),S=x.filter(f=>!k.has(f.name));S.length>0&&await e.addColumns(s,S);}X.add(o);}function he(o,e){let{deps:t,adapter:s,batchSize:i=100,flushIntervalMs:u=5e3,autoMigrate:y=false,topicPrefix:x="firestore-sync",workerOptions:l,repos:k={}}=e,S=new Map;function f(a,n){let p=S.get(a);if(p)return p;let r=k[a]?.tableName??a,h=async(C,$)=>{console.error(`[SyncWorker] Flush failed for "${a}" (${C.length} events):`,$);try{let T=`${x}-${a}-dlq`,E=t.pubsub.topic(T),[I]=await E.exists();I||(await E.create(),console.info(`[SyncWorker] Created DLQ topic "${T}"`));for(let g of C)await E.publishMessage({json:g});}catch(T){console.error(`[SyncWorker] Dead-letter publish also failed for ${a}:`,T);}};return p=new Z({adapter:s,tableName:r,primaryKey:n,batchSize:i,flushIntervalMs:u,onFlushError:h}),S.set(a,p),p}async function c(a){let{repoName:n}=a,p=o[n];if(!p){console.warn(`[SyncWorker] Unknown repo "${n}", skipping event`);return}let b=p._systemKeys?.[0]??p.documentKey??"docId",r=k[n],h=r?.columnMap,C=h?.[b]??b;if(y){let T=p.schema??void 0;if(T){let E=r?.tableName??n;await me(n,s,T,E,b,r?.exclude,h);}}let $=f(n,C);a.data&&(a.data[D]=a.version??Date.now()),$.enqueue(a);}function d(a){let n=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 r=S.get(b.repoName);r&&await r.flush();};return l?t.pubsubHandler.onMessagePublished({topic:a,...l},n):t.pubsubHandler.onMessagePublished(a,n)}return {handleMessage:c,createHandler:d,queues:S,async shutdown(){let a=[];for(let n of S.values())a.push(n.shutdown());await Promise.all(a);}}}exports.SyncQueue=Z;exports.addColumnsDDL=de;exports.autoMigrate=le;exports.createSyncTriggers=ge;exports.createSyncWorker=he;exports.createTableDDL=Y;exports.createadminsyncServer=ce;exports.generateDDL=ue;exports.serializeDocument=z;exports.serializeValue=K;exports.zodSchemaToColumns=_;exports.zodTypeToLogical=ae;//# sourceMappingURL=index.cjs.map
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);}}}exports.SyncQueue=q;exports.addColumnsDDL=de;exports.autoMigrate=le;exports.createSyncTriggers=ge;exports.createSyncWorker=he;exports.createTableDDL=Y;exports.createadminsyncServer=ce;exports.generateDDL=ue;exports.serializeDocument=j;exports.serializeValue=K;exports.zodSchemaToColumns=E;exports.zodTypeToLogical=ae;//# sourceMappingURL=index.cjs.map
98
91
  //# sourceMappingURL=index.cjs.map