@lpdjs/firestore-repo-service 2.2.6 → 2.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{create-servers-DmggzSb3.d.cts → create-servers-BGtAS0oG.d.cts} +3 -3
- package/dist/{create-servers-D4-NGpKm.d.ts → create-servers-DMszgNtg.d.ts} +3 -3
- package/dist/history/index.cjs +2 -0
- package/dist/history/index.cjs.map +1 -0
- package/dist/history/index.d.cts +126 -0
- package/dist/history/index.d.ts +126 -0
- package/dist/history/index.js +2 -0
- package/dist/history/index.js.map +1 -0
- package/dist/{index-Cvip2Sgt.d.ts → index-BSZP0Gz9.d.ts} +3 -3
- package/dist/{index-DpD4DEqH.d.cts → index-D7oj3Pft.d.cts} +3 -3
- package/dist/index.cjs +120 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -12
- package/dist/index.d.ts +21 -12
- package/dist/index.js +120 -102
- package/dist/index.js.map +1 -1
- package/dist/{openapi-B3P2F8op.d.ts → openapi-BdqIkSjO.d.ts} +1 -1
- package/dist/{openapi-UJJ1aCFk.d.cts → openapi-lY43PZXy.d.cts} +1 -1
- package/dist/read-DfXXxhO4.d.cts +230 -0
- package/dist/read-DfXXxhO4.d.ts +230 -0
- package/dist/servers/admin/index.cjs +31 -13
- package/dist/servers/admin/index.cjs.map +1 -1
- package/dist/servers/admin/index.d.cts +4 -2
- package/dist/servers/admin/index.d.ts +4 -2
- package/dist/servers/admin/index.js +31 -13
- package/dist/servers/admin/index.js.map +1 -1
- package/dist/servers/crud/index.d.cts +6 -4
- package/dist/servers/crud/index.d.ts +6 -4
- package/dist/servers/index.cjs +33 -15
- package/dist/servers/index.cjs.map +1 -1
- package/dist/servers/index.d.cts +6 -5
- package/dist/servers/index.d.ts +6 -5
- package/dist/servers/index.js +33 -15
- package/dist/servers/index.js.map +1 -1
- package/dist/{types-Z9Qy8Xmx.d.ts → types-BESS89Fu.d.cts} +30 -12
- package/dist/{types-Z9Qy8Xmx.d.cts → types-BVUFVcpl.d.ts} +30 -12
- package/package.json +17 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { S as SyncQueue } from './queue-Dk1Ezhkf.cjs';
|
|
2
2
|
import { F as FirestoreSyncConfig, S as SyncEvent } from './types-CX5AbZWV.cjs';
|
|
3
|
-
import { O as OpenAPIDocument } from './openapi-
|
|
3
|
+
import { O as OpenAPIDocument } from './openapi-lY43PZXy.cjs';
|
|
4
4
|
import * as firebase_functions_v2_https from 'firebase-functions/v2/https';
|
|
5
5
|
import { onRequest, HttpsOptions } from 'firebase-functions/v2/https';
|
|
6
|
-
import { C as ConfiguredRepository, m as CrudServerOptions, l as CrudRepoConfig } from './types-
|
|
7
|
-
import { b as AdminServerOptions, A as AdminRepoConfig } from './index-
|
|
6
|
+
import { C as ConfiguredRepository, m as CrudServerOptions, l as CrudRepoConfig } from './types-BESS89Fu.cjs';
|
|
7
|
+
import { b as AdminServerOptions, A as AdminRepoConfig } from './index-D7oj3Pft.cjs';
|
|
8
8
|
|
|
9
9
|
/** Per-repo admin config with `repo` omitted (auto-bound from the registry key). */
|
|
10
10
|
type BoundAdminRepoConfig<TRepo extends ConfiguredRepository<any>> = Omit<AdminRepoConfig<TRepo>, "repo">;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { S as SyncQueue } from './queue-D_-aMf4H.js';
|
|
2
2
|
import { F as FirestoreSyncConfig, S as SyncEvent } from './types-CX5AbZWV.js';
|
|
3
|
-
import { O as OpenAPIDocument } from './openapi-
|
|
3
|
+
import { O as OpenAPIDocument } from './openapi-BdqIkSjO.js';
|
|
4
4
|
import * as firebase_functions_v2_https from 'firebase-functions/v2/https';
|
|
5
5
|
import { onRequest, HttpsOptions } from 'firebase-functions/v2/https';
|
|
6
|
-
import { C as ConfiguredRepository, m as CrudServerOptions, l as CrudRepoConfig } from './types-
|
|
7
|
-
import { b as AdminServerOptions, A as AdminRepoConfig } from './index-
|
|
6
|
+
import { C as ConfiguredRepository, m as CrudServerOptions, l as CrudRepoConfig } from './types-BVUFVcpl.js';
|
|
7
|
+
import { b as AdminServerOptions, A as AdminRepoConfig } from './index-BSZP0Gz9.js';
|
|
8
8
|
|
|
9
9
|
/** Per-repo admin config with `repo` omitted (auto-bound from the registry key). */
|
|
10
10
|
type BoundAdminRepoConfig<TRepo extends ConfiguredRepository<any>> = Omit<AdminRepoConfig<TRepo>, "repo">;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var firestore=require('firebase-admin/firestore'),crypto=require('crypto');function D(t){if(t===null)return "null";if(t===void 0)return "undefined";if(t instanceof firestore.Timestamp)return "timestamp";if(t instanceof Date)return "date";if(Array.isArray(t))return "array";let e=typeof t;return e==="string"||e==="number"||e==="boolean"?e:"object"}function C(t,e){if(t===e)return true;if(t===null||e===null||t===void 0||e===void 0)return t===e;if(t instanceof firestore.Timestamp&&e instanceof firestore.Timestamp)return t.isEqual(e);if(t instanceof Date&&e instanceof Date)return t.getTime()===e.getTime();if(Array.isArray(t)&&Array.isArray(e)){if(t.length!==e.length)return false;for(let r=0;r<t.length;r++)if(!C(t[r],e[r]))return false;return true}if(typeof t=="object"&&typeof e=="object")try{return JSON.stringify(t)===JSON.stringify(e)}catch{return false}return false}function O(t,e,r={}){let o=new Set([...r.exclude??[],...r.metaFields??[],...r.systemKeys??[]]),s=r.include?new Set(r.include):null,l=t??{},a=e??{},y=new Set([...Object.keys(l),...Object.keys(a)]),T={};for(let p of y){if(o.has(p)||s&&!s.has(p))continue;let i=l[p],n=a[p];C(i,n)||(T[p]={oldValue:i===void 0?null:i,newValue:n===void 0?null:n,type:{old:D(i),new:D(n)}});}return T}var U=7e5;function w(t){let e=t.ttlOverride??t.config.ttl,o={schemaVersion:2,historyDocId:crypto.randomUUID(),historyToObjectId:t.entityId,historySetAt:firestore.Timestamp.now(),operation:t.operation,meta:t.meta,changes:t.changes};e&&(o.expiresAt=firestore.Timestamp.fromMillis(Date.now()+e.days*24*60*60*1e3));let s=l=>{try{return Buffer.byteLength(JSON.stringify(l,(a,y)=>y instanceof firestore.Timestamp?y.toMillis():y),"utf8")}catch{return 0}};if(s(o.changes)>U){let l={};for(let[a,y]of Object.entries(o.changes)){let T=s(y.oldValue),p=s(y.newValue);l[a]={oldValue:T>5e4?"[truncated]":y.oldValue,newValue:p>5e4?"[truncated]":y.newValue,type:y.type};}o.changes=l,o._truncated=true;}return o}async function b(t,e,r,o){let s=e;return r.onBeforeWrite&&(s=await r.onBeforeWrite(e,o)),s?(await t.doc(s.historyDocId).set(s),{written:true,entry:s}):{written:false,reason:"dropped-by-onBeforeWrite"}}function M(t,e){let r=t??{},o={},s=e.meta;if(!s)return o;let l=i=>{if(!i)return;let n=r[i];return n===void 0||n===null?null:String(n)},a=l(s.userId);a!==void 0&&(o.userId=a);let y=l(s.userEmail);y!==void 0&&(o.userEmail=y);let T=l(s.reason);T!==void 0&&(o.reason=T);let p=l(s.comment);if(p!==void 0&&(o.comment=p),s.extras&&s.extras.length>0){let i={},n=false;for(let f of s.extras)f in r&&(i[f]=r[f],n=true);n&&(o.extras=i);}return o}function V(t){let e=t.meta;if(!e)return [];let r=[];return e.userId&&r.push(e.userId),e.userEmail&&r.push(e.userEmail),e.reason&&r.push(e.reason),e.comment&&r.push(e.comment),e.extras&&r.push(...e.extras),r}var N="history";function B(t,e){let r=e.ref?.path??void 0;return r?`${r}/{docId}`:(console.warn(`[HistoryTriggers] Cannot determine collection path for "${t}". Skipping.`),null)}function v(t,e){let{onDocumentWritten:r}=e.deps,o={};for(let[s,l]of Object.entries(t)){let a=l.history;if(!a?.enabled)continue;let y=a.subcollection??N,T=a.ttl??e.defaults?.ttl,p=e.repos?.[s],i;if(l._isGroup){if(!p?.triggerPath){console.warn(`[HistoryTriggers] Skipping collection-group repo "${s}". Provide a triggerPath in the history triggers repos override.`);continue}i=p.triggerPath;}else i=p?.triggerPath??B(s,l);if(!i)continue;let n=l._systemKeys??[],f=n[0]??"docId",c=V(a);o[`${s}_onHistory`]=r(i,async u=>{try{let g=u.data?.before?.data(),d=u.data?.after?.data(),H;if(!g&&d)H="create";else if(g&&!d)H="delete";else if(g&&d)H="update";else return;let h=String(d?.[f]??g?.[f]??u.params?.docId??u.data?.after?.id??u.data?.before?.id??"");if(!h)return;let m=O(g??{},d??{},{include:a.include,exclude:a.exclude,metaFields:c,systemKeys:n});if(H==="update"&&Object.keys(m).length===0)return;let E=M(d??g??null,a),L=w({entityId:h,operation:H,changes:m,meta:E,config:a,ttlOverride:T}),F=u.data?.after?.ref??u.data?.before?.ref;if(!F)return;let _=F.collection(y);await b(_,L,a,{repoName:s,docId:h,before:g??null,after:d??null});}catch(g){console.error(`[HistoryTriggers] Failed to record history for "${s}":`,g);}});}return o}var S=5;function K(t){return t.schemaVersion===2}function A(t){if(typeof t!="string")return "object";switch(t){case "string":case "number":case "boolean":case "object":case "array":case "timestamp":case "date":case "null":case "undefined":return t;default:return "object"}}function W(t){return {historyDocId:t.historyDocId,historyToObjectId:t.historyToObjectId,historySetAt:t.historySetAt,schemaVersion:2,operation:t.operation,meta:t.meta??{},changes:t.changes??{}}}function z(t){let e={};t.historyUserId!==void 0&&(e.userId=t.historyUserId??null),t.historyUserEmail!==void 0&&(e.userEmail=t.historyUserEmail??null);let r=t.extraHistoryDetails??null;r&&(r.reason!==void 0&&(e.reason=r.reason??null),r.comment!==void 0&&(e.comment=r.comment??null));let o={},s=false;if(t.historyDetails&&typeof t.historyDetails=="object")for(let[l,a]of Object.entries(t.historyDetails))o[l]=a,s=true;if(t.extraContentKeys&&typeof t.extraContentKeys=="object")for(let[l,a]of Object.entries(t.extraContentKeys))o[`content.${l}`]=a,s=true;return s&&(e.extras=o),e}function $(t){let e=t.changes?.oldValue??null,r=t.changes?.newValue??null,o=t.types?.oldValue,s=t.types?.newValue;return {oldValue:e,newValue:r,type:{old:o?A(o):D(e),new:s?A(s):D(r)}}}function q(t){let e=$(t),r=z(t);return {historyDocId:t.historyDocId,historyToObjectId:t.historyToObjectId,historySetAt:t.historySetAt,schemaVersion:1,operation:"update",meta:r,changes:{[t.field]:e}}}function G(t,e,r){return Math.abs(t.toMillis()-e.toMillis())<=r}function J(t,e){return (t.userId??null)===(e.userId??null)}function I(t,e={}){let r=e.groupToleranceMs??S,o=[];for(let s of t){if(K(s)){o.push(W(s));continue}let a=q(s),y=o[o.length-1];y&&y.schemaVersion===1&&G(y.historySetAt,a.historySetAt,r)&&J(y.meta,a.meta)?Object.assign(y.changes,a.changes):o.push(a);}return o}var X="history",Y=50;function j(t,e,r){return t(...r).collection(e)}function Q(t){return String(t[t.length-1]??"")}function Z(t,e,r,o){if(!o?.enabled)return null;let s=o.subcollection??X;async function l(...i){let n={},f=i,c=i[i.length-1];c!==null&&typeof c=="object"&&!(c instanceof firestore.Timestamp)&&("limit"in c||"cursor"in c||"direction"in c)&&(n=c,f=i.slice(0,-1));let u=j(t,s,f),g=n.direction??"desc",d=u.orderBy("historySetAt",g);return n.cursor&&(d=d.startAfter(n.cursor)),n.limit&&n.limit>0&&(d=d.limit(n.limit)),(await d.get()).docs.map(h=>({id:h.id,data:h.data()}))}async function a(...i){let n={},f=i,c=i[i.length-1];c!==null&&typeof c=="object"&&!(c instanceof firestore.Timestamp)&&("limit"in c||"cursor"in c||"direction"in c||"fields"in c||"operations"in c)&&(n=c,f=i.slice(0,-1));let u=n.limit??Y,g=Math.max(u,Math.min(u*8,500)),d=await l(...f,{limit:g,cursor:n.cursor,direction:n.direction??"desc"}),H=I(d.map(h=>h.data));if(n.fields&&n.fields.length>0){let h=new Set(n.fields);H=H.filter(m=>Object.keys(m.changes).some(E=>h.has(E)));}if(n.operations&&n.operations.length>0){let h=new Set(n.operations);H=H.filter(m=>h.has(m.operation));}return H.slice(0,u)}async function y(...i){let n=i[i.length-1],f={},c,u;return n!==null&&typeof n=="object"&&!(n instanceof firestore.Timestamp)?(f=n,c=i[i.length-2],u=i.slice(0,-2)):(c=n,u=i.slice(0,-1)),a(...u,{...f,fields:[c]})}async function T(...i){let n=i[i.length-1],f={},c,u;return n!==null&&typeof n=="object"&&!(n instanceof firestore.Timestamp)?(f=n,c=i[i.length-2],u=i.slice(0,-2)):(c=n,u=i.slice(0,-1)),a(...u,{...f,operations:[c]})}async function p(...i){let n=i[i.length-1],f=i.slice(0,-1),c=Q(f),u=O(n.before??{},n.after??{},{include:o.include,exclude:o.exclude,metaFields:V(o),systemKeys:e});if(n.operation==="update"&&Object.keys(u).length===0)return null;let d={...M(n.after??n.before??null,o),...n.meta??{}},H=w({entityId:c,operation:n.operation,changes:u,meta:d,config:o}),h=j(t,s,f),m=await b(h,H,o,{repoName:r,docId:c,before:n.before??null,after:n.after??null});return !m.written||!m.entry?null:{historyDocId:m.entry.historyDocId,historyToObjectId:m.entry.historyToObjectId,historySetAt:m.entry.historySetAt,schemaVersion:2,operation:m.entry.operation,meta:m.entry.meta,changes:m.entry.changes}}return {list:a,raw:l,byField:y,byOperation:T,recordManual:p}}exports.DEFAULT_GROUP_TOLERANCE_MS=S;exports.buildHistoryEntry=w;exports.computeDiff=O;exports.createHistoryMethods=Z;exports.createHistoryTriggers=v;exports.extractMeta=M;exports.metaFieldsOf=V;exports.normalizeHistoryDocs=I;exports.valueType=D;exports.valuesEqual=C;exports.writeHistoryEntry=b;//# sourceMappingURL=index.cjs.map
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/history/diff.ts","../../src/history/write.ts","../../src/history/triggers.ts","../../src/history/normalize.ts","../../src/history/read.ts"],"names":["valueType","v","Timestamp","t","valuesEqual","a","b","i","computeDiff","before","after","opts","excludeSet","includeSet","beforeObj","afterObj","keys","changes","key","oldValue","newValue","MAX_DOC_BYTES","buildHistoryEntry","params","ttl","entry","randomUUID","sizeOf","obj","_k","truncated","k","change","oldSize","newSize","writeHistoryEntry","historyRef","config","ctx","toWrite","extractMeta","source","src","meta","m","pickStr","userId","userEmail","reason","comment","extras","any","metaFieldsOf","list","DEFAULT_SUBCOLLECTION","buildDocumentPath","repoName","repo","collectionPath","createHistoryTriggers","repoMapping","onDocumentWritten","triggers","repoCfg","subcollection","override","documentPath","systemKeys","documentKey","metaFields","event","operation","docId","parentRef","err","DEFAULT_GROUP_TOLERANCE_MS","isV2","doc","legacyTypeToHistoryType","v2ToEntry","v1MetaOf","extra","anyExtra","v1FieldChange","declaredOld","declaredNew","v1ToEntry","tsClose","toleranceMs","sameAuthor","normalizeHistoryDocs","docs","tol","out","last","DEFAULT_LIMIT","getHistoryRef","documentRef","args","getDocId","createHistoryMethods","raw","pathArgs","ref","direction","q","d","limit","fetchLimit","rawDocs","entries","set","e","byField","field","byOperation","op","recordManual","payload","entityId","result"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Diff helper for the history module.\n *\n * Computes a top-level shallow diff between two snapshots of the same\n * document. Returns an empty changes object when nothing relevant changed.\n *\n * Meta fields, system keys (docId / pathKey / createdKey / updatedKey) and\n * any field listed in `exclude` are filtered out. When `include` is set, only\n * the listed fields are considered.\n */\n\nimport { Timestamp } from \"firebase-admin/firestore\";\nimport type {\n HistoryFieldChange,\n HistoryValueType,\n} from \"./types\";\n\nexport interface DiffOptions {\n include?: string[];\n exclude?: string[];\n /** Field names that hold meta info (auto-excluded from diff). */\n metaFields?: string[];\n /** System keys auto-excluded (docId, pathKey, createdKey, updatedKey). */\n systemKeys?: string[];\n}\n\nexport function valueType(v: unknown): HistoryValueType {\n if (v === null) return \"null\";\n if (v === undefined) return \"undefined\";\n if (v instanceof Timestamp) return \"timestamp\";\n if (v instanceof Date) return \"date\";\n if (Array.isArray(v)) return \"array\";\n const t = typeof v;\n if (t === \"string\" || t === \"number\" || t === \"boolean\") return t;\n return \"object\";\n}\n\n/** Cheap structural equality good enough for change detection. */\nexport function valuesEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null || a === undefined || b === undefined) {\n return a === b;\n }\n\n if (a instanceof Timestamp && b instanceof Timestamp) {\n return a.isEqual(b);\n }\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() === b.getTime();\n }\n\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!valuesEqual(a[i], b[i])) return false;\n }\n return true;\n }\n\n if (typeof a === \"object\" && typeof b === \"object\") {\n try {\n return JSON.stringify(a) === JSON.stringify(b);\n } catch {\n return false;\n }\n }\n\n return false;\n}\n\n/**\n * Compute a flat top-level diff. Nested objects are compared as a whole — they\n * appear in the diff if changed but are NOT decomposed key by key.\n */\nexport function computeDiff<T extends Record<string, unknown>>(\n before: T | null | undefined,\n after: T | null | undefined,\n opts: DiffOptions = {},\n): Record<string, HistoryFieldChange> {\n const excludeSet = new Set<string>([\n ...(opts.exclude ?? []),\n ...(opts.metaFields ?? []),\n ...(opts.systemKeys ?? []),\n ]);\n const includeSet = opts.include ? new Set<string>(opts.include) : null;\n\n const beforeObj = (before ?? {}) as Record<string, unknown>;\n const afterObj = (after ?? {}) as Record<string, unknown>;\n\n const keys = new Set<string>([\n ...Object.keys(beforeObj),\n ...Object.keys(afterObj),\n ]);\n\n const changes: Record<string, HistoryFieldChange> = {};\n for (const key of keys) {\n if (excludeSet.has(key)) continue;\n if (includeSet && !includeSet.has(key)) continue;\n\n const oldValue = beforeObj[key];\n const newValue = afterObj[key];\n if (valuesEqual(oldValue, newValue)) continue;\n\n changes[key] = {\n oldValue: oldValue === undefined ? null : oldValue,\n newValue: newValue === undefined ? null : newValue,\n type: { old: valueType(oldValue), new: valueType(newValue) },\n };\n }\n\n return changes;\n}\n","/**\n * Write helpers for history entries.\n *\n * Builds a {@link V2HistoryDoc} from a diff + meta payload and writes it to\n * the entity's history subcollection. Used by {@link createHistoryTriggers}\n * and by the optional `repo.history.recordManual(...)` API.\n */\n\nimport {\n Timestamp,\n type CollectionReference,\n} from \"firebase-admin/firestore\";\nimport { randomUUID } from \"node:crypto\";\nimport type {\n HistoryConfigForModel,\n HistoryFieldChange,\n HistoryMeta,\n HistoryOperation,\n V2HistoryDoc,\n} from \"./types\";\n\nexport interface BuildEntryParams<T> {\n entityId: string;\n operation: HistoryOperation;\n changes: Record<string, HistoryFieldChange>;\n meta: HistoryMeta;\n config: HistoryConfigForModel<T>;\n ttlOverride?: { days: number };\n}\n\n/** Soft size guard — leave ~300 KiB headroom under Firestore's 1 MiB limit. */\nconst MAX_DOC_BYTES = 700_000;\n\nexport function buildHistoryEntry<T>(\n params: BuildEntryParams<T>,\n): V2HistoryDoc {\n const ttl = params.ttlOverride ?? params.config.ttl;\n const id = randomUUID();\n const entry: V2HistoryDoc = {\n schemaVersion: 2,\n historyDocId: id,\n historyToObjectId: params.entityId,\n historySetAt: Timestamp.now(),\n operation: params.operation,\n meta: params.meta,\n changes: params.changes,\n };\n if (ttl) {\n entry.expiresAt = Timestamp.fromMillis(\n Date.now() + ttl.days * 24 * 60 * 60 * 1000,\n );\n }\n\n // Soft size guard — if the JSON projection of `changes` is too large,\n // truncate large fields and mark the doc.\n const sizeOf = (obj: unknown): number => {\n try {\n return Buffer.byteLength(\n JSON.stringify(obj, (_k, v) => {\n if (v instanceof Timestamp) return v.toMillis();\n return v;\n }),\n \"utf8\",\n );\n } catch {\n return 0;\n }\n };\n\n if (sizeOf(entry.changes) > MAX_DOC_BYTES) {\n const truncated: Record<string, HistoryFieldChange> = {};\n for (const [k, change] of Object.entries(entry.changes)) {\n const oldSize = sizeOf(change.oldValue);\n const newSize = sizeOf(change.newValue);\n truncated[k] = {\n oldValue:\n oldSize > 50_000 ? \"[truncated]\" : change.oldValue,\n newValue:\n newSize > 50_000 ? \"[truncated]\" : change.newValue,\n type: change.type,\n };\n }\n entry.changes = truncated;\n entry._truncated = true;\n }\n\n return entry;\n}\n\nexport interface WriteEntryResult {\n written: boolean;\n entry?: V2HistoryDoc;\n reason?: string;\n}\n\n/**\n * Write a single history entry to the given subcollection reference.\n * Returns `{ written: false }` when the entry was dropped by `onBeforeWrite`.\n */\nexport async function writeHistoryEntry<T>(\n historyRef: CollectionReference,\n entry: V2HistoryDoc,\n config: HistoryConfigForModel<T>,\n ctx: { repoName: string; docId: string; before: T | null; after: T | null },\n): Promise<WriteEntryResult> {\n let toWrite: V2HistoryDoc | null = entry;\n if (config.onBeforeWrite) {\n toWrite = await config.onBeforeWrite(entry, ctx);\n }\n if (!toWrite) return { written: false, reason: \"dropped-by-onBeforeWrite\" };\n\n await historyRef.doc(toWrite.historyDocId).set(toWrite);\n return { written: true, entry: toWrite };\n}\n\n/**\n * Extract meta values from a Firestore document snapshot using the\n * declared meta config. Missing fields become `null`.\n */\nexport function extractMeta<T>(\n source: Record<string, unknown> | null | undefined,\n config: HistoryConfigForModel<T>,\n): HistoryMeta {\n const src = source ?? {};\n const meta: HistoryMeta = {};\n const m = config.meta;\n if (!m) return meta;\n\n const pickStr = (key?: string): string | null | undefined => {\n if (!key) return undefined;\n const v = src[key];\n if (v === undefined) return null;\n return v === null ? null : String(v);\n };\n\n const userId = pickStr(m.userId);\n if (userId !== undefined) meta.userId = userId;\n const userEmail = pickStr(m.userEmail);\n if (userEmail !== undefined) meta.userEmail = userEmail;\n const reason = pickStr(m.reason);\n if (reason !== undefined) meta.reason = reason;\n const comment = pickStr(m.comment);\n if (comment !== undefined) meta.comment = comment;\n\n if (m.extras && m.extras.length > 0) {\n const extras: Record<string, unknown> = {};\n let any = false;\n for (const k of m.extras) {\n if (k in src) {\n extras[k] = src[k];\n any = true;\n }\n }\n if (any) meta.extras = extras;\n }\n\n return meta;\n}\n\n/**\n * Returns all field names that should be excluded from the diff because\n * they're declared as meta fields (so we don't log them as their own change).\n */\nexport function metaFieldsOf<T>(config: HistoryConfigForModel<T>): string[] {\n const m = config.meta;\n if (!m) return [];\n const list: string[] = [];\n if (m.userId) list.push(m.userId);\n if (m.userEmail) list.push(m.userEmail);\n if (m.reason) list.push(m.reason);\n if (m.comment) list.push(m.comment);\n if (m.extras) list.push(...m.extras);\n return list;\n}\n","/**\n * `createHistoryTriggers` — generates Firestore Cloud Functions (v2) that\n * capture every write to a configured repository and write a v2 history\n * entry into the entity's history subcollection.\n *\n * Mirrors the DI / per-repo override pattern of {@link createSyncTriggers}.\n *\n * @example\n * ```ts\n * import { createHistoryTriggers } from \"@lpdjs/firestore-repo-service/history\";\n * import * as firestoreTriggers from \"firebase-functions/v2/firestore\";\n *\n * const triggers = createHistoryTriggers(repos, {\n * deps: { onDocumentWritten: firestoreTriggers.onDocumentWritten },\n * defaults: { ttl: { days: 365 } },\n * });\n *\n * export const { residences_onHistory, prevention_workshops_onHistory } = triggers;\n * ```\n */\n\nimport { computeDiff } from \"./diff\";\nimport {\n buildHistoryEntry,\n extractMeta,\n metaFieldsOf,\n writeHistoryEntry,\n} from \"./write\";\nimport type {\n HistoryConfigForModel,\n HistoryOperation,\n HistoryTriggersConfig,\n} from \"./types\";\n\nconst DEFAULT_SUBCOLLECTION = \"history\";\n\n/** Determine the trigger document path pattern for a non-group repo. */\nfunction buildDocumentPath(repoName: string, repo: any): string | null {\n const collectionPath: string | undefined =\n (repo as any).ref?.path ?? undefined;\n if (!collectionPath) {\n console.warn(\n `[HistoryTriggers] Cannot determine collection path for \"${repoName}\". Skipping.`,\n );\n return null;\n }\n return `${collectionPath}/{docId}`;\n}\n\nexport function createHistoryTriggers<M extends Record<string, any>>(\n repoMapping: M,\n config: HistoryTriggersConfig<NoInfer<M>>,\n): Record<string, any> {\n const { onDocumentWritten } = config.deps;\n const triggers: Record<string, any> = {};\n\n for (const [repoName, repo] of Object.entries(repoMapping) as [\n string,\n any,\n ][]) {\n const repoCfg = (repo as any).history as\n | (HistoryConfigForModel<unknown> & { enabled: boolean })\n | undefined;\n if (!repoCfg?.enabled) continue;\n\n const subcollection = repoCfg.subcollection ?? DEFAULT_SUBCOLLECTION;\n const ttl = repoCfg.ttl ?? config.defaults?.ttl;\n\n const override = config.repos?.[repoName as keyof M & string];\n let documentPath: string | null;\n if ((repo as any)._isGroup) {\n if (!override?.triggerPath) {\n console.warn(\n `[HistoryTriggers] Skipping collection-group repo \"${repoName}\". ` +\n \"Provide a triggerPath in the history triggers repos override.\",\n );\n continue;\n }\n documentPath = override.triggerPath;\n } else {\n documentPath = override?.triggerPath ?? buildDocumentPath(repoName, repo);\n }\n if (!documentPath) continue;\n\n const systemKeys: string[] = (repo as any)._systemKeys ?? [];\n const documentKey: string = systemKeys[0] ?? \"docId\";\n const metaFields = metaFieldsOf(repoCfg as HistoryConfigForModel<unknown>);\n\n triggers[`${repoName}_onHistory`] = onDocumentWritten(\n documentPath,\n async (event: any) => {\n try {\n const before = event.data?.before?.data() as\n | Record<string, unknown>\n | undefined;\n const after = event.data?.after?.data() as\n | Record<string, unknown>\n | undefined;\n\n let operation: HistoryOperation;\n if (!before && after) operation = \"create\";\n else if (before && !after) operation = \"delete\";\n else if (before && after) operation = \"update\";\n else return;\n\n const docId = String(\n after?.[documentKey] ??\n before?.[documentKey] ??\n event.params?.docId ??\n event.data?.after?.id ??\n event.data?.before?.id ??\n \"\",\n );\n if (!docId) return;\n\n const changes = computeDiff(before ?? {}, after ?? {}, {\n include: repoCfg.include as string[] | undefined,\n exclude: repoCfg.exclude as string[] | undefined,\n metaFields,\n systemKeys,\n });\n\n if (operation === \"update\" && Object.keys(changes).length === 0) {\n return; // no relevant change\n }\n\n const meta = extractMeta(\n after ?? before ?? null,\n repoCfg as HistoryConfigForModel<unknown>,\n );\n\n const entry = buildHistoryEntry({\n entityId: docId,\n operation,\n changes,\n meta,\n config: repoCfg as HistoryConfigForModel<unknown>,\n ttlOverride: ttl,\n });\n\n // Build the subcollection ref from the parent doc ref of the trigger.\n const parentRef =\n event.data?.after?.ref ?? event.data?.before?.ref;\n if (!parentRef) return;\n const historyRef = parentRef.collection(subcollection);\n\n await writeHistoryEntry(\n historyRef,\n entry,\n repoCfg as HistoryConfigForModel<unknown>,\n {\n repoName,\n docId,\n before: (before ?? null) as any,\n after: (after ?? null) as any,\n },\n );\n } catch (err) {\n console.error(\n `[HistoryTriggers] Failed to record history for \"${repoName}\":`,\n err,\n );\n }\n },\n );\n }\n\n return triggers;\n}\n","/**\n * v1 ↔ v2 normalisation. The reader returns a unified {@link HistoryEntry}\n * shape regardless of which schema version a Firestore document was written\n * with.\n *\n * v2 doc → pass-through.\n * v1 doc (single field per doc) → wrapped into a 1-field unified entry.\n * Consecutive v1 docs that share the same `historySetAt` timestamp\n * (within a small tolerance) are merged into a single entry, mimicking\n * the original \"one update -> N field docs\" intent.\n */\n\nimport { Timestamp } from \"firebase-admin/firestore\";\nimport type {\n HistoryEntry,\n HistoryFieldChange,\n HistoryMeta,\n HistoryValueType,\n V1HistoryDoc,\n V2HistoryDoc,\n} from \"./types\";\nimport { valueType } from \"./diff\";\n\n/** Default tolerance (ms) used to group v1 docs from the same update. */\nexport const DEFAULT_GROUP_TOLERANCE_MS = 5;\n\ntype AnyHistoryDoc = (V1HistoryDoc | V2HistoryDoc) & Record<string, unknown>;\n\nfunction isV2(doc: AnyHistoryDoc): doc is V2HistoryDoc & Record<string, unknown> {\n return doc.schemaVersion === 2;\n}\n\nfunction legacyTypeToHistoryType(t: unknown): HistoryValueType {\n if (typeof t !== \"string\") return \"object\";\n switch (t) {\n case \"string\":\n case \"number\":\n case \"boolean\":\n case \"object\":\n case \"array\":\n case \"timestamp\":\n case \"date\":\n case \"null\":\n case \"undefined\":\n return t;\n default:\n return \"object\";\n }\n}\n\nfunction v2ToEntry(doc: V2HistoryDoc): HistoryEntry {\n return {\n historyDocId: doc.historyDocId,\n historyToObjectId: doc.historyToObjectId,\n historySetAt: doc.historySetAt,\n schemaVersion: 2,\n operation: doc.operation,\n meta: doc.meta ?? {},\n changes: doc.changes ?? {},\n };\n}\n\nfunction v1MetaOf(doc: V1HistoryDoc & Record<string, unknown>): HistoryMeta {\n const meta: HistoryMeta = {};\n if (doc.historyUserId !== undefined) meta.userId = doc.historyUserId ?? null;\n if (doc.historyUserEmail !== undefined)\n meta.userEmail = doc.historyUserEmail ?? null;\n\n const extra = doc.extraHistoryDetails ?? null;\n if (extra) {\n if (extra.reason !== undefined) meta.reason = extra.reason ?? null;\n if (extra.comment !== undefined) meta.comment = extra.comment ?? null;\n }\n\n const extras: Record<string, unknown> = {};\n let anyExtra = false;\n if (doc.historyDetails && typeof doc.historyDetails === \"object\") {\n for (const [k, v] of Object.entries(doc.historyDetails)) {\n extras[k] = v;\n anyExtra = true;\n }\n }\n if (doc.extraContentKeys && typeof doc.extraContentKeys === \"object\") {\n for (const [k, v] of Object.entries(doc.extraContentKeys)) {\n extras[`content.${k}`] = v;\n anyExtra = true;\n }\n }\n if (anyExtra) meta.extras = extras;\n return meta;\n}\n\nfunction v1FieldChange(doc: V1HistoryDoc): HistoryFieldChange {\n const oldValue = doc.changes?.oldValue ?? null;\n const newValue = doc.changes?.newValue ?? null;\n const declaredOld = doc.types?.oldValue;\n const declaredNew = doc.types?.newValue;\n return {\n oldValue,\n newValue,\n type: {\n old: declaredOld\n ? legacyTypeToHistoryType(declaredOld)\n : valueType(oldValue),\n new: declaredNew\n ? legacyTypeToHistoryType(declaredNew)\n : valueType(newValue),\n },\n };\n}\n\nfunction v1ToEntry(doc: V1HistoryDoc): HistoryEntry {\n const change = v1FieldChange(doc);\n const meta = v1MetaOf(doc as V1HistoryDoc & Record<string, unknown>);\n return {\n historyDocId: doc.historyDocId,\n historyToObjectId: doc.historyToObjectId,\n historySetAt: doc.historySetAt,\n schemaVersion: 1,\n operation: \"update\",\n meta,\n changes: { [doc.field]: change },\n };\n}\n\nfunction tsClose(a: Timestamp, b: Timestamp, toleranceMs: number): boolean {\n return Math.abs(a.toMillis() - b.toMillis()) <= toleranceMs;\n}\n\nfunction sameAuthor(a: HistoryMeta, b: HistoryMeta): boolean {\n return (a.userId ?? null) === (b.userId ?? null);\n}\n\n/**\n * Normalise a list of raw Firestore docs into unified {@link HistoryEntry}.\n * The input list MUST be sorted by `historySetAt` (asc or desc — the function\n * preserves order). v1 docs sharing the same timestamp + author are merged.\n */\nexport function normalizeHistoryDocs(\n docs: AnyHistoryDoc[],\n opts: { groupToleranceMs?: number } = {},\n): HistoryEntry[] {\n const tol = opts.groupToleranceMs ?? DEFAULT_GROUP_TOLERANCE_MS;\n const out: HistoryEntry[] = [];\n\n for (const doc of docs) {\n if (isV2(doc)) {\n out.push(v2ToEntry(doc));\n continue;\n }\n\n const v1 = doc as V1HistoryDoc;\n const entry = v1ToEntry(v1);\n\n const last = out[out.length - 1];\n if (\n last &&\n last.schemaVersion === 1 &&\n tsClose(last.historySetAt, entry.historySetAt, tol) &&\n sameAuthor(last.meta, entry.meta)\n ) {\n // Merge into the previous v1 entry (same logical update).\n Object.assign(last.changes, entry.changes);\n // Keep earliest historyDocId (stable id of the group).\n } else {\n out.push(entry);\n }\n }\n\n return out;\n}\n","/**\n * `repo.history.*` read API.\n *\n * - `list(docId, opts)` → unified, paginated, normalised entries (v1 + v2).\n * - `raw(docId, opts)` → raw Firestore docs (no normalisation).\n * - `byField(docId, field, opts)` / `byOperation(docId, op, opts)` →\n * convenience wrappers.\n * - `recordManual(docId, payload)` → bypass the trigger and write a v2 entry\n * synchronously (use sparingly; prefer trigger-based capture).\n */\n\nimport {\n Timestamp,\n type CollectionReference,\n type DocumentReference,\n} from \"firebase-admin/firestore\";\nimport { computeDiff } from \"./diff\";\nimport { normalizeHistoryDocs } from \"./normalize\";\nimport {\n buildHistoryEntry,\n extractMeta,\n metaFieldsOf,\n writeHistoryEntry,\n} from \"./write\";\nimport type {\n HistoryConfigForModel,\n HistoryEntry,\n HistoryListOptions,\n HistoryMeta,\n HistoryOperation,\n HistoryRawListOptions,\n V1HistoryDoc,\n V2HistoryDoc,\n} from \"./types\";\n\nconst DEFAULT_SUBCOLLECTION = \"history\";\nconst DEFAULT_LIMIT = 50;\n\nfunction getHistoryRef(\n documentRef: (...args: any[]) => DocumentReference,\n subcollection: string,\n args: any[],\n): CollectionReference {\n const docRef = documentRef(...args);\n return docRef.collection(subcollection);\n}\n\nfunction getDocId(args: any[]): string {\n return String(args[args.length - 1] ?? \"\");\n}\n\n/**\n * Build the `repo.history` API for a given configured repository.\n * Returns `null` when history is not enabled — the factory then skips\n * exposing the namespace.\n */\nexport function createHistoryMethods<T>(\n documentRef: (...args: any[]) => DocumentReference,\n systemKeys: string[],\n repoName: string,\n config: HistoryConfigForModel<T> & { enabled: boolean },\n) {\n if (!config?.enabled) return null;\n\n const subcollection = config.subcollection ?? DEFAULT_SUBCOLLECTION;\n\n async function raw(\n ...args: any[]\n ): Promise<Array<{ id: string; data: V1HistoryDoc | V2HistoryDoc }>> {\n let opts: HistoryRawListOptions = {};\n let pathArgs = args;\n const last = args[args.length - 1];\n if (\n last !== null &&\n typeof last === \"object\" &&\n !(last instanceof Timestamp) &&\n (\"limit\" in last || \"cursor\" in last || \"direction\" in last)\n ) {\n opts = last as HistoryRawListOptions;\n pathArgs = args.slice(0, -1);\n }\n\n const ref = getHistoryRef(documentRef, subcollection, pathArgs);\n const direction = opts.direction ?? \"desc\";\n let q = ref.orderBy(\"historySetAt\", direction);\n if (opts.cursor) q = q.startAfter(opts.cursor);\n if (opts.limit && opts.limit > 0) q = q.limit(opts.limit);\n\n const snap = await q.get();\n return snap.docs.map((d) => ({\n id: d.id,\n data: d.data() as V1HistoryDoc | V2HistoryDoc,\n }));\n }\n\n async function list(\n ...args: any[]\n ): Promise<HistoryEntry<T>[]> {\n let opts: HistoryListOptions<T> = {};\n let pathArgs = args;\n const last = args[args.length - 1];\n if (\n last !== null &&\n typeof last === \"object\" &&\n !(last instanceof Timestamp) &&\n (\"limit\" in last ||\n \"cursor\" in last ||\n \"direction\" in last ||\n \"fields\" in last ||\n \"operations\" in last)\n ) {\n opts = last as HistoryListOptions<T>;\n pathArgs = args.slice(0, -1);\n }\n\n const limit = opts.limit ?? DEFAULT_LIMIT;\n // Read a bit more from Firestore because v1 entries get merged afterwards.\n // Cap at limit * 8 to avoid pathological reads.\n const fetchLimit = Math.max(limit, Math.min(limit * 8, 500));\n\n const rawDocs = await raw(...pathArgs, {\n limit: fetchLimit,\n cursor: opts.cursor,\n direction: opts.direction ?? \"desc\",\n });\n\n let entries = normalizeHistoryDocs(\n rawDocs.map((d) => d.data) as any[],\n ) as HistoryEntry<T>[];\n\n if (opts.fields && opts.fields.length > 0) {\n const set = new Set<string>(opts.fields as string[]);\n entries = entries.filter((e) =>\n Object.keys(e.changes).some((k) => set.has(k)),\n );\n }\n if (opts.operations && opts.operations.length > 0) {\n const set = new Set<HistoryOperation>(opts.operations);\n entries = entries.filter((e) => set.has(e.operation));\n }\n\n return entries.slice(0, limit);\n }\n\n async function byField(\n ...args: any[]\n ): Promise<HistoryEntry<T>[]> {\n const last = args[args.length - 1];\n let opts: HistoryListOptions<T> = {};\n let field: keyof T & string;\n let pathArgs: any[];\n if (last !== null && typeof last === \"object\" && !(last instanceof Timestamp)) {\n opts = last as HistoryListOptions<T>;\n field = args[args.length - 2] as keyof T & string;\n pathArgs = args.slice(0, -2);\n } else {\n field = last as keyof T & string;\n pathArgs = args.slice(0, -1);\n }\n return list(...pathArgs, { ...opts, fields: [field] });\n }\n\n async function byOperation(\n ...args: any[]\n ): Promise<HistoryEntry<T>[]> {\n const last = args[args.length - 1];\n let opts: HistoryListOptions<T> = {};\n let op: HistoryOperation;\n let pathArgs: any[];\n if (last !== null && typeof last === \"object\" && !(last instanceof Timestamp)) {\n opts = last as HistoryListOptions<T>;\n op = args[args.length - 2] as HistoryOperation;\n pathArgs = args.slice(0, -2);\n } else {\n op = last as HistoryOperation;\n pathArgs = args.slice(0, -1);\n }\n return list(...pathArgs, { ...opts, operations: [op] });\n }\n\n /**\n * Manually record a history entry. Bypasses the trigger and lets the caller\n * pass an explicit before/after pair plus meta (useful for synchronous flows\n * that need richer context than what the trigger can extract).\n */\n async function recordManual(\n ...args: any[]\n ): Promise<HistoryEntry<T> | null> {\n const payload = args[args.length - 1] as {\n operation: HistoryOperation;\n before?: T | null;\n after?: T | null;\n meta?: HistoryMeta;\n };\n const pathArgs = args.slice(0, -1);\n const entityId = getDocId(pathArgs);\n\n const changes = computeDiff(\n (payload.before ?? {}) as Record<string, unknown>,\n (payload.after ?? {}) as Record<string, unknown>,\n {\n include: config.include as string[] | undefined,\n exclude: config.exclude as string[] | undefined,\n metaFields: metaFieldsOf(config),\n systemKeys,\n },\n );\n if (\n payload.operation === \"update\" &&\n Object.keys(changes).length === 0\n ) {\n return null;\n }\n\n const fallbackMeta = extractMeta(\n (payload.after ?? payload.before ?? null) as Record<string, unknown> | null,\n config,\n );\n const meta: HistoryMeta = { ...fallbackMeta, ...(payload.meta ?? {}) };\n\n const entry = buildHistoryEntry({\n entityId,\n operation: payload.operation,\n changes,\n meta,\n config,\n });\n\n const ref = getHistoryRef(documentRef, subcollection, pathArgs);\n const result = await writeHistoryEntry(ref, entry, config, {\n repoName,\n docId: entityId,\n before: payload.before ?? null,\n after: payload.after ?? null,\n });\n if (!result.written || !result.entry) return null;\n\n return {\n historyDocId: result.entry.historyDocId,\n historyToObjectId: result.entry.historyToObjectId,\n historySetAt: result.entry.historySetAt,\n schemaVersion: 2,\n operation: result.entry.operation,\n meta: result.entry.meta,\n changes: result.entry.changes,\n } as HistoryEntry<T>;\n }\n\n return { list, raw, byField, byOperation, recordManual };\n}\n\nexport type HistoryMethods<T> = NonNullable<\n ReturnType<typeof createHistoryMethods<T>>\n>;\n"]}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { b as HistoryTriggersConfig, c as HistoryOperation, d as HistoryFieldChange, e as HistoryMeta, H as HistoryConfigForModel, V as V2HistoryDoc, f as HistoryValueType, g as V1HistoryDoc, h as HistoryEntry } from '../read-DfXXxhO4.cjs';
|
|
2
|
+
export { j as HistoryConfigBase, k as HistoryFirestoreTriggersDep, l as HistoryListOptions, a as HistoryMethods, m as HistoryRawListOptions, n as HistoryTriggerRepoOverride, i as createHistoryMethods } from '../read-DfXXxhO4.cjs';
|
|
3
|
+
import { CollectionReference } from 'firebase-admin/firestore';
|
|
4
|
+
import 'firebase-functions/v2/firestore';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `createHistoryTriggers` — generates Firestore Cloud Functions (v2) that
|
|
8
|
+
* capture every write to a configured repository and write a v2 history
|
|
9
|
+
* entry into the entity's history subcollection.
|
|
10
|
+
*
|
|
11
|
+
* Mirrors the DI / per-repo override pattern of {@link createSyncTriggers}.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { createHistoryTriggers } from "@lpdjs/firestore-repo-service/history";
|
|
16
|
+
* import * as firestoreTriggers from "firebase-functions/v2/firestore";
|
|
17
|
+
*
|
|
18
|
+
* const triggers = createHistoryTriggers(repos, {
|
|
19
|
+
* deps: { onDocumentWritten: firestoreTriggers.onDocumentWritten },
|
|
20
|
+
* defaults: { ttl: { days: 365 } },
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* export const { residences_onHistory, prevention_workshops_onHistory } = triggers;
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
declare function createHistoryTriggers<M extends Record<string, any>>(repoMapping: M, config: HistoryTriggersConfig<NoInfer<M>>): Record<string, any>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Write helpers for history entries.
|
|
31
|
+
*
|
|
32
|
+
* Builds a {@link V2HistoryDoc} from a diff + meta payload and writes it to
|
|
33
|
+
* the entity's history subcollection. Used by {@link createHistoryTriggers}
|
|
34
|
+
* and by the optional `repo.history.recordManual(...)` API.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
interface BuildEntryParams<T> {
|
|
38
|
+
entityId: string;
|
|
39
|
+
operation: HistoryOperation;
|
|
40
|
+
changes: Record<string, HistoryFieldChange>;
|
|
41
|
+
meta: HistoryMeta;
|
|
42
|
+
config: HistoryConfigForModel<T>;
|
|
43
|
+
ttlOverride?: {
|
|
44
|
+
days: number;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
declare function buildHistoryEntry<T>(params: BuildEntryParams<T>): V2HistoryDoc;
|
|
48
|
+
interface WriteEntryResult {
|
|
49
|
+
written: boolean;
|
|
50
|
+
entry?: V2HistoryDoc;
|
|
51
|
+
reason?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Write a single history entry to the given subcollection reference.
|
|
55
|
+
* Returns `{ written: false }` when the entry was dropped by `onBeforeWrite`.
|
|
56
|
+
*/
|
|
57
|
+
declare function writeHistoryEntry<T>(historyRef: CollectionReference, entry: V2HistoryDoc, config: HistoryConfigForModel<T>, ctx: {
|
|
58
|
+
repoName: string;
|
|
59
|
+
docId: string;
|
|
60
|
+
before: T | null;
|
|
61
|
+
after: T | null;
|
|
62
|
+
}): Promise<WriteEntryResult>;
|
|
63
|
+
/**
|
|
64
|
+
* Extract meta values from a Firestore document snapshot using the
|
|
65
|
+
* declared meta config. Missing fields become `null`.
|
|
66
|
+
*/
|
|
67
|
+
declare function extractMeta<T>(source: Record<string, unknown> | null | undefined, config: HistoryConfigForModel<T>): HistoryMeta;
|
|
68
|
+
/**
|
|
69
|
+
* Returns all field names that should be excluded from the diff because
|
|
70
|
+
* they're declared as meta fields (so we don't log them as their own change).
|
|
71
|
+
*/
|
|
72
|
+
declare function metaFieldsOf<T>(config: HistoryConfigForModel<T>): string[];
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Diff helper for the history module.
|
|
76
|
+
*
|
|
77
|
+
* Computes a top-level shallow diff between two snapshots of the same
|
|
78
|
+
* document. Returns an empty changes object when nothing relevant changed.
|
|
79
|
+
*
|
|
80
|
+
* Meta fields, system keys (docId / pathKey / createdKey / updatedKey) and
|
|
81
|
+
* any field listed in `exclude` are filtered out. When `include` is set, only
|
|
82
|
+
* the listed fields are considered.
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
interface DiffOptions {
|
|
86
|
+
include?: string[];
|
|
87
|
+
exclude?: string[];
|
|
88
|
+
/** Field names that hold meta info (auto-excluded from diff). */
|
|
89
|
+
metaFields?: string[];
|
|
90
|
+
/** System keys auto-excluded (docId, pathKey, createdKey, updatedKey). */
|
|
91
|
+
systemKeys?: string[];
|
|
92
|
+
}
|
|
93
|
+
declare function valueType(v: unknown): HistoryValueType;
|
|
94
|
+
/** Cheap structural equality good enough for change detection. */
|
|
95
|
+
declare function valuesEqual(a: unknown, b: unknown): boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Compute a flat top-level diff. Nested objects are compared as a whole — they
|
|
98
|
+
* appear in the diff if changed but are NOT decomposed key by key.
|
|
99
|
+
*/
|
|
100
|
+
declare function computeDiff<T extends Record<string, unknown>>(before: T | null | undefined, after: T | null | undefined, opts?: DiffOptions): Record<string, HistoryFieldChange>;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* v1 ↔ v2 normalisation. The reader returns a unified {@link HistoryEntry}
|
|
104
|
+
* shape regardless of which schema version a Firestore document was written
|
|
105
|
+
* with.
|
|
106
|
+
*
|
|
107
|
+
* v2 doc → pass-through.
|
|
108
|
+
* v1 doc (single field per doc) → wrapped into a 1-field unified entry.
|
|
109
|
+
* Consecutive v1 docs that share the same `historySetAt` timestamp
|
|
110
|
+
* (within a small tolerance) are merged into a single entry, mimicking
|
|
111
|
+
* the original "one update -> N field docs" intent.
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
/** Default tolerance (ms) used to group v1 docs from the same update. */
|
|
115
|
+
declare const DEFAULT_GROUP_TOLERANCE_MS = 5;
|
|
116
|
+
type AnyHistoryDoc = (V1HistoryDoc | V2HistoryDoc) & Record<string, unknown>;
|
|
117
|
+
/**
|
|
118
|
+
* Normalise a list of raw Firestore docs into unified {@link HistoryEntry}.
|
|
119
|
+
* The input list MUST be sorted by `historySetAt` (asc or desc — the function
|
|
120
|
+
* preserves order). v1 docs sharing the same timestamp + author are merged.
|
|
121
|
+
*/
|
|
122
|
+
declare function normalizeHistoryDocs(docs: AnyHistoryDoc[], opts?: {
|
|
123
|
+
groupToleranceMs?: number;
|
|
124
|
+
}): HistoryEntry[];
|
|
125
|
+
|
|
126
|
+
export { DEFAULT_GROUP_TOLERANCE_MS, HistoryConfigForModel, HistoryEntry, HistoryFieldChange, HistoryMeta, HistoryOperation, HistoryTriggersConfig, HistoryValueType, V1HistoryDoc, V2HistoryDoc, buildHistoryEntry, computeDiff, createHistoryTriggers, extractMeta, metaFieldsOf, normalizeHistoryDocs, valueType, valuesEqual, writeHistoryEntry };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { b as HistoryTriggersConfig, c as HistoryOperation, d as HistoryFieldChange, e as HistoryMeta, H as HistoryConfigForModel, V as V2HistoryDoc, f as HistoryValueType, g as V1HistoryDoc, h as HistoryEntry } from '../read-DfXXxhO4.js';
|
|
2
|
+
export { j as HistoryConfigBase, k as HistoryFirestoreTriggersDep, l as HistoryListOptions, a as HistoryMethods, m as HistoryRawListOptions, n as HistoryTriggerRepoOverride, i as createHistoryMethods } from '../read-DfXXxhO4.js';
|
|
3
|
+
import { CollectionReference } from 'firebase-admin/firestore';
|
|
4
|
+
import 'firebase-functions/v2/firestore';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `createHistoryTriggers` — generates Firestore Cloud Functions (v2) that
|
|
8
|
+
* capture every write to a configured repository and write a v2 history
|
|
9
|
+
* entry into the entity's history subcollection.
|
|
10
|
+
*
|
|
11
|
+
* Mirrors the DI / per-repo override pattern of {@link createSyncTriggers}.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { createHistoryTriggers } from "@lpdjs/firestore-repo-service/history";
|
|
16
|
+
* import * as firestoreTriggers from "firebase-functions/v2/firestore";
|
|
17
|
+
*
|
|
18
|
+
* const triggers = createHistoryTriggers(repos, {
|
|
19
|
+
* deps: { onDocumentWritten: firestoreTriggers.onDocumentWritten },
|
|
20
|
+
* defaults: { ttl: { days: 365 } },
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* export const { residences_onHistory, prevention_workshops_onHistory } = triggers;
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
declare function createHistoryTriggers<M extends Record<string, any>>(repoMapping: M, config: HistoryTriggersConfig<NoInfer<M>>): Record<string, any>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Write helpers for history entries.
|
|
31
|
+
*
|
|
32
|
+
* Builds a {@link V2HistoryDoc} from a diff + meta payload and writes it to
|
|
33
|
+
* the entity's history subcollection. Used by {@link createHistoryTriggers}
|
|
34
|
+
* and by the optional `repo.history.recordManual(...)` API.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
interface BuildEntryParams<T> {
|
|
38
|
+
entityId: string;
|
|
39
|
+
operation: HistoryOperation;
|
|
40
|
+
changes: Record<string, HistoryFieldChange>;
|
|
41
|
+
meta: HistoryMeta;
|
|
42
|
+
config: HistoryConfigForModel<T>;
|
|
43
|
+
ttlOverride?: {
|
|
44
|
+
days: number;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
declare function buildHistoryEntry<T>(params: BuildEntryParams<T>): V2HistoryDoc;
|
|
48
|
+
interface WriteEntryResult {
|
|
49
|
+
written: boolean;
|
|
50
|
+
entry?: V2HistoryDoc;
|
|
51
|
+
reason?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Write a single history entry to the given subcollection reference.
|
|
55
|
+
* Returns `{ written: false }` when the entry was dropped by `onBeforeWrite`.
|
|
56
|
+
*/
|
|
57
|
+
declare function writeHistoryEntry<T>(historyRef: CollectionReference, entry: V2HistoryDoc, config: HistoryConfigForModel<T>, ctx: {
|
|
58
|
+
repoName: string;
|
|
59
|
+
docId: string;
|
|
60
|
+
before: T | null;
|
|
61
|
+
after: T | null;
|
|
62
|
+
}): Promise<WriteEntryResult>;
|
|
63
|
+
/**
|
|
64
|
+
* Extract meta values from a Firestore document snapshot using the
|
|
65
|
+
* declared meta config. Missing fields become `null`.
|
|
66
|
+
*/
|
|
67
|
+
declare function extractMeta<T>(source: Record<string, unknown> | null | undefined, config: HistoryConfigForModel<T>): HistoryMeta;
|
|
68
|
+
/**
|
|
69
|
+
* Returns all field names that should be excluded from the diff because
|
|
70
|
+
* they're declared as meta fields (so we don't log them as their own change).
|
|
71
|
+
*/
|
|
72
|
+
declare function metaFieldsOf<T>(config: HistoryConfigForModel<T>): string[];
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Diff helper for the history module.
|
|
76
|
+
*
|
|
77
|
+
* Computes a top-level shallow diff between two snapshots of the same
|
|
78
|
+
* document. Returns an empty changes object when nothing relevant changed.
|
|
79
|
+
*
|
|
80
|
+
* Meta fields, system keys (docId / pathKey / createdKey / updatedKey) and
|
|
81
|
+
* any field listed in `exclude` are filtered out. When `include` is set, only
|
|
82
|
+
* the listed fields are considered.
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
interface DiffOptions {
|
|
86
|
+
include?: string[];
|
|
87
|
+
exclude?: string[];
|
|
88
|
+
/** Field names that hold meta info (auto-excluded from diff). */
|
|
89
|
+
metaFields?: string[];
|
|
90
|
+
/** System keys auto-excluded (docId, pathKey, createdKey, updatedKey). */
|
|
91
|
+
systemKeys?: string[];
|
|
92
|
+
}
|
|
93
|
+
declare function valueType(v: unknown): HistoryValueType;
|
|
94
|
+
/** Cheap structural equality good enough for change detection. */
|
|
95
|
+
declare function valuesEqual(a: unknown, b: unknown): boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Compute a flat top-level diff. Nested objects are compared as a whole — they
|
|
98
|
+
* appear in the diff if changed but are NOT decomposed key by key.
|
|
99
|
+
*/
|
|
100
|
+
declare function computeDiff<T extends Record<string, unknown>>(before: T | null | undefined, after: T | null | undefined, opts?: DiffOptions): Record<string, HistoryFieldChange>;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* v1 ↔ v2 normalisation. The reader returns a unified {@link HistoryEntry}
|
|
104
|
+
* shape regardless of which schema version a Firestore document was written
|
|
105
|
+
* with.
|
|
106
|
+
*
|
|
107
|
+
* v2 doc → pass-through.
|
|
108
|
+
* v1 doc (single field per doc) → wrapped into a 1-field unified entry.
|
|
109
|
+
* Consecutive v1 docs that share the same `historySetAt` timestamp
|
|
110
|
+
* (within a small tolerance) are merged into a single entry, mimicking
|
|
111
|
+
* the original "one update -> N field docs" intent.
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
/** Default tolerance (ms) used to group v1 docs from the same update. */
|
|
115
|
+
declare const DEFAULT_GROUP_TOLERANCE_MS = 5;
|
|
116
|
+
type AnyHistoryDoc = (V1HistoryDoc | V2HistoryDoc) & Record<string, unknown>;
|
|
117
|
+
/**
|
|
118
|
+
* Normalise a list of raw Firestore docs into unified {@link HistoryEntry}.
|
|
119
|
+
* The input list MUST be sorted by `historySetAt` (asc or desc — the function
|
|
120
|
+
* preserves order). v1 docs sharing the same timestamp + author are merged.
|
|
121
|
+
*/
|
|
122
|
+
declare function normalizeHistoryDocs(docs: AnyHistoryDoc[], opts?: {
|
|
123
|
+
groupToleranceMs?: number;
|
|
124
|
+
}): HistoryEntry[];
|
|
125
|
+
|
|
126
|
+
export { DEFAULT_GROUP_TOLERANCE_MS, HistoryConfigForModel, HistoryEntry, HistoryFieldChange, HistoryMeta, HistoryOperation, HistoryTriggersConfig, HistoryValueType, V1HistoryDoc, V2HistoryDoc, buildHistoryEntry, computeDiff, createHistoryTriggers, extractMeta, metaFieldsOf, normalizeHistoryDocs, valueType, valuesEqual, writeHistoryEntry };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import {Timestamp}from'firebase-admin/firestore';import {randomUUID}from'crypto';function D(t){if(t===null)return "null";if(t===void 0)return "undefined";if(t instanceof Timestamp)return "timestamp";if(t instanceof Date)return "date";if(Array.isArray(t))return "array";let e=typeof t;return e==="string"||e==="number"||e==="boolean"?e:"object"}function C(t,e){if(t===e)return true;if(t===null||e===null||t===void 0||e===void 0)return t===e;if(t instanceof Timestamp&&e instanceof Timestamp)return t.isEqual(e);if(t instanceof Date&&e instanceof Date)return t.getTime()===e.getTime();if(Array.isArray(t)&&Array.isArray(e)){if(t.length!==e.length)return false;for(let r=0;r<t.length;r++)if(!C(t[r],e[r]))return false;return true}if(typeof t=="object"&&typeof e=="object")try{return JSON.stringify(t)===JSON.stringify(e)}catch{return false}return false}function O(t,e,r={}){let o=new Set([...r.exclude??[],...r.metaFields??[],...r.systemKeys??[]]),s=r.include?new Set(r.include):null,l=t??{},a=e??{},y=new Set([...Object.keys(l),...Object.keys(a)]),T={};for(let p of y){if(o.has(p)||s&&!s.has(p))continue;let i=l[p],n=a[p];C(i,n)||(T[p]={oldValue:i===void 0?null:i,newValue:n===void 0?null:n,type:{old:D(i),new:D(n)}});}return T}var U=7e5;function w(t){let e=t.ttlOverride??t.config.ttl,o={schemaVersion:2,historyDocId:randomUUID(),historyToObjectId:t.entityId,historySetAt:Timestamp.now(),operation:t.operation,meta:t.meta,changes:t.changes};e&&(o.expiresAt=Timestamp.fromMillis(Date.now()+e.days*24*60*60*1e3));let s=l=>{try{return Buffer.byteLength(JSON.stringify(l,(a,y)=>y instanceof Timestamp?y.toMillis():y),"utf8")}catch{return 0}};if(s(o.changes)>U){let l={};for(let[a,y]of Object.entries(o.changes)){let T=s(y.oldValue),p=s(y.newValue);l[a]={oldValue:T>5e4?"[truncated]":y.oldValue,newValue:p>5e4?"[truncated]":y.newValue,type:y.type};}o.changes=l,o._truncated=true;}return o}async function b(t,e,r,o){let s=e;return r.onBeforeWrite&&(s=await r.onBeforeWrite(e,o)),s?(await t.doc(s.historyDocId).set(s),{written:true,entry:s}):{written:false,reason:"dropped-by-onBeforeWrite"}}function M(t,e){let r=t??{},o={},s=e.meta;if(!s)return o;let l=i=>{if(!i)return;let n=r[i];return n===void 0||n===null?null:String(n)},a=l(s.userId);a!==void 0&&(o.userId=a);let y=l(s.userEmail);y!==void 0&&(o.userEmail=y);let T=l(s.reason);T!==void 0&&(o.reason=T);let p=l(s.comment);if(p!==void 0&&(o.comment=p),s.extras&&s.extras.length>0){let i={},n=false;for(let f of s.extras)f in r&&(i[f]=r[f],n=true);n&&(o.extras=i);}return o}function V(t){let e=t.meta;if(!e)return [];let r=[];return e.userId&&r.push(e.userId),e.userEmail&&r.push(e.userEmail),e.reason&&r.push(e.reason),e.comment&&r.push(e.comment),e.extras&&r.push(...e.extras),r}var N="history";function B(t,e){let r=e.ref?.path??void 0;return r?`${r}/{docId}`:(console.warn(`[HistoryTriggers] Cannot determine collection path for "${t}". Skipping.`),null)}function v(t,e){let{onDocumentWritten:r}=e.deps,o={};for(let[s,l]of Object.entries(t)){let a=l.history;if(!a?.enabled)continue;let y=a.subcollection??N,T=a.ttl??e.defaults?.ttl,p=e.repos?.[s],i;if(l._isGroup){if(!p?.triggerPath){console.warn(`[HistoryTriggers] Skipping collection-group repo "${s}". Provide a triggerPath in the history triggers repos override.`);continue}i=p.triggerPath;}else i=p?.triggerPath??B(s,l);if(!i)continue;let n=l._systemKeys??[],f=n[0]??"docId",c=V(a);o[`${s}_onHistory`]=r(i,async u=>{try{let g=u.data?.before?.data(),d=u.data?.after?.data(),H;if(!g&&d)H="create";else if(g&&!d)H="delete";else if(g&&d)H="update";else return;let h=String(d?.[f]??g?.[f]??u.params?.docId??u.data?.after?.id??u.data?.before?.id??"");if(!h)return;let m=O(g??{},d??{},{include:a.include,exclude:a.exclude,metaFields:c,systemKeys:n});if(H==="update"&&Object.keys(m).length===0)return;let E=M(d??g??null,a),L=w({entityId:h,operation:H,changes:m,meta:E,config:a,ttlOverride:T}),F=u.data?.after?.ref??u.data?.before?.ref;if(!F)return;let _=F.collection(y);await b(_,L,a,{repoName:s,docId:h,before:g??null,after:d??null});}catch(g){console.error(`[HistoryTriggers] Failed to record history for "${s}":`,g);}});}return o}var S=5;function K(t){return t.schemaVersion===2}function A(t){if(typeof t!="string")return "object";switch(t){case "string":case "number":case "boolean":case "object":case "array":case "timestamp":case "date":case "null":case "undefined":return t;default:return "object"}}function W(t){return {historyDocId:t.historyDocId,historyToObjectId:t.historyToObjectId,historySetAt:t.historySetAt,schemaVersion:2,operation:t.operation,meta:t.meta??{},changes:t.changes??{}}}function z(t){let e={};t.historyUserId!==void 0&&(e.userId=t.historyUserId??null),t.historyUserEmail!==void 0&&(e.userEmail=t.historyUserEmail??null);let r=t.extraHistoryDetails??null;r&&(r.reason!==void 0&&(e.reason=r.reason??null),r.comment!==void 0&&(e.comment=r.comment??null));let o={},s=false;if(t.historyDetails&&typeof t.historyDetails=="object")for(let[l,a]of Object.entries(t.historyDetails))o[l]=a,s=true;if(t.extraContentKeys&&typeof t.extraContentKeys=="object")for(let[l,a]of Object.entries(t.extraContentKeys))o[`content.${l}`]=a,s=true;return s&&(e.extras=o),e}function $(t){let e=t.changes?.oldValue??null,r=t.changes?.newValue??null,o=t.types?.oldValue,s=t.types?.newValue;return {oldValue:e,newValue:r,type:{old:o?A(o):D(e),new:s?A(s):D(r)}}}function q(t){let e=$(t),r=z(t);return {historyDocId:t.historyDocId,historyToObjectId:t.historyToObjectId,historySetAt:t.historySetAt,schemaVersion:1,operation:"update",meta:r,changes:{[t.field]:e}}}function G(t,e,r){return Math.abs(t.toMillis()-e.toMillis())<=r}function J(t,e){return (t.userId??null)===(e.userId??null)}function I(t,e={}){let r=e.groupToleranceMs??S,o=[];for(let s of t){if(K(s)){o.push(W(s));continue}let a=q(s),y=o[o.length-1];y&&y.schemaVersion===1&&G(y.historySetAt,a.historySetAt,r)&&J(y.meta,a.meta)?Object.assign(y.changes,a.changes):o.push(a);}return o}var X="history",Y=50;function j(t,e,r){return t(...r).collection(e)}function Q(t){return String(t[t.length-1]??"")}function Z(t,e,r,o){if(!o?.enabled)return null;let s=o.subcollection??X;async function l(...i){let n={},f=i,c=i[i.length-1];c!==null&&typeof c=="object"&&!(c instanceof Timestamp)&&("limit"in c||"cursor"in c||"direction"in c)&&(n=c,f=i.slice(0,-1));let u=j(t,s,f),g=n.direction??"desc",d=u.orderBy("historySetAt",g);return n.cursor&&(d=d.startAfter(n.cursor)),n.limit&&n.limit>0&&(d=d.limit(n.limit)),(await d.get()).docs.map(h=>({id:h.id,data:h.data()}))}async function a(...i){let n={},f=i,c=i[i.length-1];c!==null&&typeof c=="object"&&!(c instanceof Timestamp)&&("limit"in c||"cursor"in c||"direction"in c||"fields"in c||"operations"in c)&&(n=c,f=i.slice(0,-1));let u=n.limit??Y,g=Math.max(u,Math.min(u*8,500)),d=await l(...f,{limit:g,cursor:n.cursor,direction:n.direction??"desc"}),H=I(d.map(h=>h.data));if(n.fields&&n.fields.length>0){let h=new Set(n.fields);H=H.filter(m=>Object.keys(m.changes).some(E=>h.has(E)));}if(n.operations&&n.operations.length>0){let h=new Set(n.operations);H=H.filter(m=>h.has(m.operation));}return H.slice(0,u)}async function y(...i){let n=i[i.length-1],f={},c,u;return n!==null&&typeof n=="object"&&!(n instanceof Timestamp)?(f=n,c=i[i.length-2],u=i.slice(0,-2)):(c=n,u=i.slice(0,-1)),a(...u,{...f,fields:[c]})}async function T(...i){let n=i[i.length-1],f={},c,u;return n!==null&&typeof n=="object"&&!(n instanceof Timestamp)?(f=n,c=i[i.length-2],u=i.slice(0,-2)):(c=n,u=i.slice(0,-1)),a(...u,{...f,operations:[c]})}async function p(...i){let n=i[i.length-1],f=i.slice(0,-1),c=Q(f),u=O(n.before??{},n.after??{},{include:o.include,exclude:o.exclude,metaFields:V(o),systemKeys:e});if(n.operation==="update"&&Object.keys(u).length===0)return null;let d={...M(n.after??n.before??null,o),...n.meta??{}},H=w({entityId:c,operation:n.operation,changes:u,meta:d,config:o}),h=j(t,s,f),m=await b(h,H,o,{repoName:r,docId:c,before:n.before??null,after:n.after??null});return !m.written||!m.entry?null:{historyDocId:m.entry.historyDocId,historyToObjectId:m.entry.historyToObjectId,historySetAt:m.entry.historySetAt,schemaVersion:2,operation:m.entry.operation,meta:m.entry.meta,changes:m.entry.changes}}return {list:a,raw:l,byField:y,byOperation:T,recordManual:p}}export{S as DEFAULT_GROUP_TOLERANCE_MS,w as buildHistoryEntry,O as computeDiff,Z as createHistoryMethods,v as createHistoryTriggers,M as extractMeta,V as metaFieldsOf,I as normalizeHistoryDocs,D as valueType,C as valuesEqual,b as writeHistoryEntry};//# sourceMappingURL=index.js.map
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/history/diff.ts","../../src/history/write.ts","../../src/history/triggers.ts","../../src/history/normalize.ts","../../src/history/read.ts"],"names":["valueType","v","Timestamp","t","valuesEqual","a","b","i","computeDiff","before","after","opts","excludeSet","includeSet","beforeObj","afterObj","keys","changes","key","oldValue","newValue","MAX_DOC_BYTES","buildHistoryEntry","params","ttl","entry","randomUUID","sizeOf","obj","_k","truncated","k","change","oldSize","newSize","writeHistoryEntry","historyRef","config","ctx","toWrite","extractMeta","source","src","meta","m","pickStr","userId","userEmail","reason","comment","extras","any","metaFieldsOf","list","DEFAULT_SUBCOLLECTION","buildDocumentPath","repoName","repo","collectionPath","createHistoryTriggers","repoMapping","onDocumentWritten","triggers","repoCfg","subcollection","override","documentPath","systemKeys","documentKey","metaFields","event","operation","docId","parentRef","err","DEFAULT_GROUP_TOLERANCE_MS","isV2","doc","legacyTypeToHistoryType","v2ToEntry","v1MetaOf","extra","anyExtra","v1FieldChange","declaredOld","declaredNew","v1ToEntry","tsClose","toleranceMs","sameAuthor","normalizeHistoryDocs","docs","tol","out","last","DEFAULT_LIMIT","getHistoryRef","documentRef","args","getDocId","createHistoryMethods","raw","pathArgs","ref","direction","q","d","limit","fetchLimit","rawDocs","entries","set","e","byField","field","byOperation","op","recordManual","payload","entityId","result"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Diff helper for the history module.\n *\n * Computes a top-level shallow diff between two snapshots of the same\n * document. Returns an empty changes object when nothing relevant changed.\n *\n * Meta fields, system keys (docId / pathKey / createdKey / updatedKey) and\n * any field listed in `exclude` are filtered out. When `include` is set, only\n * the listed fields are considered.\n */\n\nimport { Timestamp } from \"firebase-admin/firestore\";\nimport type {\n HistoryFieldChange,\n HistoryValueType,\n} from \"./types\";\n\nexport interface DiffOptions {\n include?: string[];\n exclude?: string[];\n /** Field names that hold meta info (auto-excluded from diff). */\n metaFields?: string[];\n /** System keys auto-excluded (docId, pathKey, createdKey, updatedKey). */\n systemKeys?: string[];\n}\n\nexport function valueType(v: unknown): HistoryValueType {\n if (v === null) return \"null\";\n if (v === undefined) return \"undefined\";\n if (v instanceof Timestamp) return \"timestamp\";\n if (v instanceof Date) return \"date\";\n if (Array.isArray(v)) return \"array\";\n const t = typeof v;\n if (t === \"string\" || t === \"number\" || t === \"boolean\") return t;\n return \"object\";\n}\n\n/** Cheap structural equality good enough for change detection. */\nexport function valuesEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null || a === undefined || b === undefined) {\n return a === b;\n }\n\n if (a instanceof Timestamp && b instanceof Timestamp) {\n return a.isEqual(b);\n }\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() === b.getTime();\n }\n\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!valuesEqual(a[i], b[i])) return false;\n }\n return true;\n }\n\n if (typeof a === \"object\" && typeof b === \"object\") {\n try {\n return JSON.stringify(a) === JSON.stringify(b);\n } catch {\n return false;\n }\n }\n\n return false;\n}\n\n/**\n * Compute a flat top-level diff. Nested objects are compared as a whole — they\n * appear in the diff if changed but are NOT decomposed key by key.\n */\nexport function computeDiff<T extends Record<string, unknown>>(\n before: T | null | undefined,\n after: T | null | undefined,\n opts: DiffOptions = {},\n): Record<string, HistoryFieldChange> {\n const excludeSet = new Set<string>([\n ...(opts.exclude ?? []),\n ...(opts.metaFields ?? []),\n ...(opts.systemKeys ?? []),\n ]);\n const includeSet = opts.include ? new Set<string>(opts.include) : null;\n\n const beforeObj = (before ?? {}) as Record<string, unknown>;\n const afterObj = (after ?? {}) as Record<string, unknown>;\n\n const keys = new Set<string>([\n ...Object.keys(beforeObj),\n ...Object.keys(afterObj),\n ]);\n\n const changes: Record<string, HistoryFieldChange> = {};\n for (const key of keys) {\n if (excludeSet.has(key)) continue;\n if (includeSet && !includeSet.has(key)) continue;\n\n const oldValue = beforeObj[key];\n const newValue = afterObj[key];\n if (valuesEqual(oldValue, newValue)) continue;\n\n changes[key] = {\n oldValue: oldValue === undefined ? null : oldValue,\n newValue: newValue === undefined ? null : newValue,\n type: { old: valueType(oldValue), new: valueType(newValue) },\n };\n }\n\n return changes;\n}\n","/**\n * Write helpers for history entries.\n *\n * Builds a {@link V2HistoryDoc} from a diff + meta payload and writes it to\n * the entity's history subcollection. Used by {@link createHistoryTriggers}\n * and by the optional `repo.history.recordManual(...)` API.\n */\n\nimport {\n Timestamp,\n type CollectionReference,\n} from \"firebase-admin/firestore\";\nimport { randomUUID } from \"node:crypto\";\nimport type {\n HistoryConfigForModel,\n HistoryFieldChange,\n HistoryMeta,\n HistoryOperation,\n V2HistoryDoc,\n} from \"./types\";\n\nexport interface BuildEntryParams<T> {\n entityId: string;\n operation: HistoryOperation;\n changes: Record<string, HistoryFieldChange>;\n meta: HistoryMeta;\n config: HistoryConfigForModel<T>;\n ttlOverride?: { days: number };\n}\n\n/** Soft size guard — leave ~300 KiB headroom under Firestore's 1 MiB limit. */\nconst MAX_DOC_BYTES = 700_000;\n\nexport function buildHistoryEntry<T>(\n params: BuildEntryParams<T>,\n): V2HistoryDoc {\n const ttl = params.ttlOverride ?? params.config.ttl;\n const id = randomUUID();\n const entry: V2HistoryDoc = {\n schemaVersion: 2,\n historyDocId: id,\n historyToObjectId: params.entityId,\n historySetAt: Timestamp.now(),\n operation: params.operation,\n meta: params.meta,\n changes: params.changes,\n };\n if (ttl) {\n entry.expiresAt = Timestamp.fromMillis(\n Date.now() + ttl.days * 24 * 60 * 60 * 1000,\n );\n }\n\n // Soft size guard — if the JSON projection of `changes` is too large,\n // truncate large fields and mark the doc.\n const sizeOf = (obj: unknown): number => {\n try {\n return Buffer.byteLength(\n JSON.stringify(obj, (_k, v) => {\n if (v instanceof Timestamp) return v.toMillis();\n return v;\n }),\n \"utf8\",\n );\n } catch {\n return 0;\n }\n };\n\n if (sizeOf(entry.changes) > MAX_DOC_BYTES) {\n const truncated: Record<string, HistoryFieldChange> = {};\n for (const [k, change] of Object.entries(entry.changes)) {\n const oldSize = sizeOf(change.oldValue);\n const newSize = sizeOf(change.newValue);\n truncated[k] = {\n oldValue:\n oldSize > 50_000 ? \"[truncated]\" : change.oldValue,\n newValue:\n newSize > 50_000 ? \"[truncated]\" : change.newValue,\n type: change.type,\n };\n }\n entry.changes = truncated;\n entry._truncated = true;\n }\n\n return entry;\n}\n\nexport interface WriteEntryResult {\n written: boolean;\n entry?: V2HistoryDoc;\n reason?: string;\n}\n\n/**\n * Write a single history entry to the given subcollection reference.\n * Returns `{ written: false }` when the entry was dropped by `onBeforeWrite`.\n */\nexport async function writeHistoryEntry<T>(\n historyRef: CollectionReference,\n entry: V2HistoryDoc,\n config: HistoryConfigForModel<T>,\n ctx: { repoName: string; docId: string; before: T | null; after: T | null },\n): Promise<WriteEntryResult> {\n let toWrite: V2HistoryDoc | null = entry;\n if (config.onBeforeWrite) {\n toWrite = await config.onBeforeWrite(entry, ctx);\n }\n if (!toWrite) return { written: false, reason: \"dropped-by-onBeforeWrite\" };\n\n await historyRef.doc(toWrite.historyDocId).set(toWrite);\n return { written: true, entry: toWrite };\n}\n\n/**\n * Extract meta values from a Firestore document snapshot using the\n * declared meta config. Missing fields become `null`.\n */\nexport function extractMeta<T>(\n source: Record<string, unknown> | null | undefined,\n config: HistoryConfigForModel<T>,\n): HistoryMeta {\n const src = source ?? {};\n const meta: HistoryMeta = {};\n const m = config.meta;\n if (!m) return meta;\n\n const pickStr = (key?: string): string | null | undefined => {\n if (!key) return undefined;\n const v = src[key];\n if (v === undefined) return null;\n return v === null ? null : String(v);\n };\n\n const userId = pickStr(m.userId);\n if (userId !== undefined) meta.userId = userId;\n const userEmail = pickStr(m.userEmail);\n if (userEmail !== undefined) meta.userEmail = userEmail;\n const reason = pickStr(m.reason);\n if (reason !== undefined) meta.reason = reason;\n const comment = pickStr(m.comment);\n if (comment !== undefined) meta.comment = comment;\n\n if (m.extras && m.extras.length > 0) {\n const extras: Record<string, unknown> = {};\n let any = false;\n for (const k of m.extras) {\n if (k in src) {\n extras[k] = src[k];\n any = true;\n }\n }\n if (any) meta.extras = extras;\n }\n\n return meta;\n}\n\n/**\n * Returns all field names that should be excluded from the diff because\n * they're declared as meta fields (so we don't log them as their own change).\n */\nexport function metaFieldsOf<T>(config: HistoryConfigForModel<T>): string[] {\n const m = config.meta;\n if (!m) return [];\n const list: string[] = [];\n if (m.userId) list.push(m.userId);\n if (m.userEmail) list.push(m.userEmail);\n if (m.reason) list.push(m.reason);\n if (m.comment) list.push(m.comment);\n if (m.extras) list.push(...m.extras);\n return list;\n}\n","/**\n * `createHistoryTriggers` — generates Firestore Cloud Functions (v2) that\n * capture every write to a configured repository and write a v2 history\n * entry into the entity's history subcollection.\n *\n * Mirrors the DI / per-repo override pattern of {@link createSyncTriggers}.\n *\n * @example\n * ```ts\n * import { createHistoryTriggers } from \"@lpdjs/firestore-repo-service/history\";\n * import * as firestoreTriggers from \"firebase-functions/v2/firestore\";\n *\n * const triggers = createHistoryTriggers(repos, {\n * deps: { onDocumentWritten: firestoreTriggers.onDocumentWritten },\n * defaults: { ttl: { days: 365 } },\n * });\n *\n * export const { residences_onHistory, prevention_workshops_onHistory } = triggers;\n * ```\n */\n\nimport { computeDiff } from \"./diff\";\nimport {\n buildHistoryEntry,\n extractMeta,\n metaFieldsOf,\n writeHistoryEntry,\n} from \"./write\";\nimport type {\n HistoryConfigForModel,\n HistoryOperation,\n HistoryTriggersConfig,\n} from \"./types\";\n\nconst DEFAULT_SUBCOLLECTION = \"history\";\n\n/** Determine the trigger document path pattern for a non-group repo. */\nfunction buildDocumentPath(repoName: string, repo: any): string | null {\n const collectionPath: string | undefined =\n (repo as any).ref?.path ?? undefined;\n if (!collectionPath) {\n console.warn(\n `[HistoryTriggers] Cannot determine collection path for \"${repoName}\". Skipping.`,\n );\n return null;\n }\n return `${collectionPath}/{docId}`;\n}\n\nexport function createHistoryTriggers<M extends Record<string, any>>(\n repoMapping: M,\n config: HistoryTriggersConfig<NoInfer<M>>,\n): Record<string, any> {\n const { onDocumentWritten } = config.deps;\n const triggers: Record<string, any> = {};\n\n for (const [repoName, repo] of Object.entries(repoMapping) as [\n string,\n any,\n ][]) {\n const repoCfg = (repo as any).history as\n | (HistoryConfigForModel<unknown> & { enabled: boolean })\n | undefined;\n if (!repoCfg?.enabled) continue;\n\n const subcollection = repoCfg.subcollection ?? DEFAULT_SUBCOLLECTION;\n const ttl = repoCfg.ttl ?? config.defaults?.ttl;\n\n const override = config.repos?.[repoName as keyof M & string];\n let documentPath: string | null;\n if ((repo as any)._isGroup) {\n if (!override?.triggerPath) {\n console.warn(\n `[HistoryTriggers] Skipping collection-group repo \"${repoName}\". ` +\n \"Provide a triggerPath in the history triggers repos override.\",\n );\n continue;\n }\n documentPath = override.triggerPath;\n } else {\n documentPath = override?.triggerPath ?? buildDocumentPath(repoName, repo);\n }\n if (!documentPath) continue;\n\n const systemKeys: string[] = (repo as any)._systemKeys ?? [];\n const documentKey: string = systemKeys[0] ?? \"docId\";\n const metaFields = metaFieldsOf(repoCfg as HistoryConfigForModel<unknown>);\n\n triggers[`${repoName}_onHistory`] = onDocumentWritten(\n documentPath,\n async (event: any) => {\n try {\n const before = event.data?.before?.data() as\n | Record<string, unknown>\n | undefined;\n const after = event.data?.after?.data() as\n | Record<string, unknown>\n | undefined;\n\n let operation: HistoryOperation;\n if (!before && after) operation = \"create\";\n else if (before && !after) operation = \"delete\";\n else if (before && after) operation = \"update\";\n else return;\n\n const docId = String(\n after?.[documentKey] ??\n before?.[documentKey] ??\n event.params?.docId ??\n event.data?.after?.id ??\n event.data?.before?.id ??\n \"\",\n );\n if (!docId) return;\n\n const changes = computeDiff(before ?? {}, after ?? {}, {\n include: repoCfg.include as string[] | undefined,\n exclude: repoCfg.exclude as string[] | undefined,\n metaFields,\n systemKeys,\n });\n\n if (operation === \"update\" && Object.keys(changes).length === 0) {\n return; // no relevant change\n }\n\n const meta = extractMeta(\n after ?? before ?? null,\n repoCfg as HistoryConfigForModel<unknown>,\n );\n\n const entry = buildHistoryEntry({\n entityId: docId,\n operation,\n changes,\n meta,\n config: repoCfg as HistoryConfigForModel<unknown>,\n ttlOverride: ttl,\n });\n\n // Build the subcollection ref from the parent doc ref of the trigger.\n const parentRef =\n event.data?.after?.ref ?? event.data?.before?.ref;\n if (!parentRef) return;\n const historyRef = parentRef.collection(subcollection);\n\n await writeHistoryEntry(\n historyRef,\n entry,\n repoCfg as HistoryConfigForModel<unknown>,\n {\n repoName,\n docId,\n before: (before ?? null) as any,\n after: (after ?? null) as any,\n },\n );\n } catch (err) {\n console.error(\n `[HistoryTriggers] Failed to record history for \"${repoName}\":`,\n err,\n );\n }\n },\n );\n }\n\n return triggers;\n}\n","/**\n * v1 ↔ v2 normalisation. The reader returns a unified {@link HistoryEntry}\n * shape regardless of which schema version a Firestore document was written\n * with.\n *\n * v2 doc → pass-through.\n * v1 doc (single field per doc) → wrapped into a 1-field unified entry.\n * Consecutive v1 docs that share the same `historySetAt` timestamp\n * (within a small tolerance) are merged into a single entry, mimicking\n * the original \"one update -> N field docs\" intent.\n */\n\nimport { Timestamp } from \"firebase-admin/firestore\";\nimport type {\n HistoryEntry,\n HistoryFieldChange,\n HistoryMeta,\n HistoryValueType,\n V1HistoryDoc,\n V2HistoryDoc,\n} from \"./types\";\nimport { valueType } from \"./diff\";\n\n/** Default tolerance (ms) used to group v1 docs from the same update. */\nexport const DEFAULT_GROUP_TOLERANCE_MS = 5;\n\ntype AnyHistoryDoc = (V1HistoryDoc | V2HistoryDoc) & Record<string, unknown>;\n\nfunction isV2(doc: AnyHistoryDoc): doc is V2HistoryDoc & Record<string, unknown> {\n return doc.schemaVersion === 2;\n}\n\nfunction legacyTypeToHistoryType(t: unknown): HistoryValueType {\n if (typeof t !== \"string\") return \"object\";\n switch (t) {\n case \"string\":\n case \"number\":\n case \"boolean\":\n case \"object\":\n case \"array\":\n case \"timestamp\":\n case \"date\":\n case \"null\":\n case \"undefined\":\n return t;\n default:\n return \"object\";\n }\n}\n\nfunction v2ToEntry(doc: V2HistoryDoc): HistoryEntry {\n return {\n historyDocId: doc.historyDocId,\n historyToObjectId: doc.historyToObjectId,\n historySetAt: doc.historySetAt,\n schemaVersion: 2,\n operation: doc.operation,\n meta: doc.meta ?? {},\n changes: doc.changes ?? {},\n };\n}\n\nfunction v1MetaOf(doc: V1HistoryDoc & Record<string, unknown>): HistoryMeta {\n const meta: HistoryMeta = {};\n if (doc.historyUserId !== undefined) meta.userId = doc.historyUserId ?? null;\n if (doc.historyUserEmail !== undefined)\n meta.userEmail = doc.historyUserEmail ?? null;\n\n const extra = doc.extraHistoryDetails ?? null;\n if (extra) {\n if (extra.reason !== undefined) meta.reason = extra.reason ?? null;\n if (extra.comment !== undefined) meta.comment = extra.comment ?? null;\n }\n\n const extras: Record<string, unknown> = {};\n let anyExtra = false;\n if (doc.historyDetails && typeof doc.historyDetails === \"object\") {\n for (const [k, v] of Object.entries(doc.historyDetails)) {\n extras[k] = v;\n anyExtra = true;\n }\n }\n if (doc.extraContentKeys && typeof doc.extraContentKeys === \"object\") {\n for (const [k, v] of Object.entries(doc.extraContentKeys)) {\n extras[`content.${k}`] = v;\n anyExtra = true;\n }\n }\n if (anyExtra) meta.extras = extras;\n return meta;\n}\n\nfunction v1FieldChange(doc: V1HistoryDoc): HistoryFieldChange {\n const oldValue = doc.changes?.oldValue ?? null;\n const newValue = doc.changes?.newValue ?? null;\n const declaredOld = doc.types?.oldValue;\n const declaredNew = doc.types?.newValue;\n return {\n oldValue,\n newValue,\n type: {\n old: declaredOld\n ? legacyTypeToHistoryType(declaredOld)\n : valueType(oldValue),\n new: declaredNew\n ? legacyTypeToHistoryType(declaredNew)\n : valueType(newValue),\n },\n };\n}\n\nfunction v1ToEntry(doc: V1HistoryDoc): HistoryEntry {\n const change = v1FieldChange(doc);\n const meta = v1MetaOf(doc as V1HistoryDoc & Record<string, unknown>);\n return {\n historyDocId: doc.historyDocId,\n historyToObjectId: doc.historyToObjectId,\n historySetAt: doc.historySetAt,\n schemaVersion: 1,\n operation: \"update\",\n meta,\n changes: { [doc.field]: change },\n };\n}\n\nfunction tsClose(a: Timestamp, b: Timestamp, toleranceMs: number): boolean {\n return Math.abs(a.toMillis() - b.toMillis()) <= toleranceMs;\n}\n\nfunction sameAuthor(a: HistoryMeta, b: HistoryMeta): boolean {\n return (a.userId ?? null) === (b.userId ?? null);\n}\n\n/**\n * Normalise a list of raw Firestore docs into unified {@link HistoryEntry}.\n * The input list MUST be sorted by `historySetAt` (asc or desc — the function\n * preserves order). v1 docs sharing the same timestamp + author are merged.\n */\nexport function normalizeHistoryDocs(\n docs: AnyHistoryDoc[],\n opts: { groupToleranceMs?: number } = {},\n): HistoryEntry[] {\n const tol = opts.groupToleranceMs ?? DEFAULT_GROUP_TOLERANCE_MS;\n const out: HistoryEntry[] = [];\n\n for (const doc of docs) {\n if (isV2(doc)) {\n out.push(v2ToEntry(doc));\n continue;\n }\n\n const v1 = doc as V1HistoryDoc;\n const entry = v1ToEntry(v1);\n\n const last = out[out.length - 1];\n if (\n last &&\n last.schemaVersion === 1 &&\n tsClose(last.historySetAt, entry.historySetAt, tol) &&\n sameAuthor(last.meta, entry.meta)\n ) {\n // Merge into the previous v1 entry (same logical update).\n Object.assign(last.changes, entry.changes);\n // Keep earliest historyDocId (stable id of the group).\n } else {\n out.push(entry);\n }\n }\n\n return out;\n}\n","/**\n * `repo.history.*` read API.\n *\n * - `list(docId, opts)` → unified, paginated, normalised entries (v1 + v2).\n * - `raw(docId, opts)` → raw Firestore docs (no normalisation).\n * - `byField(docId, field, opts)` / `byOperation(docId, op, opts)` →\n * convenience wrappers.\n * - `recordManual(docId, payload)` → bypass the trigger and write a v2 entry\n * synchronously (use sparingly; prefer trigger-based capture).\n */\n\nimport {\n Timestamp,\n type CollectionReference,\n type DocumentReference,\n} from \"firebase-admin/firestore\";\nimport { computeDiff } from \"./diff\";\nimport { normalizeHistoryDocs } from \"./normalize\";\nimport {\n buildHistoryEntry,\n extractMeta,\n metaFieldsOf,\n writeHistoryEntry,\n} from \"./write\";\nimport type {\n HistoryConfigForModel,\n HistoryEntry,\n HistoryListOptions,\n HistoryMeta,\n HistoryOperation,\n HistoryRawListOptions,\n V1HistoryDoc,\n V2HistoryDoc,\n} from \"./types\";\n\nconst DEFAULT_SUBCOLLECTION = \"history\";\nconst DEFAULT_LIMIT = 50;\n\nfunction getHistoryRef(\n documentRef: (...args: any[]) => DocumentReference,\n subcollection: string,\n args: any[],\n): CollectionReference {\n const docRef = documentRef(...args);\n return docRef.collection(subcollection);\n}\n\nfunction getDocId(args: any[]): string {\n return String(args[args.length - 1] ?? \"\");\n}\n\n/**\n * Build the `repo.history` API for a given configured repository.\n * Returns `null` when history is not enabled — the factory then skips\n * exposing the namespace.\n */\nexport function createHistoryMethods<T>(\n documentRef: (...args: any[]) => DocumentReference,\n systemKeys: string[],\n repoName: string,\n config: HistoryConfigForModel<T> & { enabled: boolean },\n) {\n if (!config?.enabled) return null;\n\n const subcollection = config.subcollection ?? DEFAULT_SUBCOLLECTION;\n\n async function raw(\n ...args: any[]\n ): Promise<Array<{ id: string; data: V1HistoryDoc | V2HistoryDoc }>> {\n let opts: HistoryRawListOptions = {};\n let pathArgs = args;\n const last = args[args.length - 1];\n if (\n last !== null &&\n typeof last === \"object\" &&\n !(last instanceof Timestamp) &&\n (\"limit\" in last || \"cursor\" in last || \"direction\" in last)\n ) {\n opts = last as HistoryRawListOptions;\n pathArgs = args.slice(0, -1);\n }\n\n const ref = getHistoryRef(documentRef, subcollection, pathArgs);\n const direction = opts.direction ?? \"desc\";\n let q = ref.orderBy(\"historySetAt\", direction);\n if (opts.cursor) q = q.startAfter(opts.cursor);\n if (opts.limit && opts.limit > 0) q = q.limit(opts.limit);\n\n const snap = await q.get();\n return snap.docs.map((d) => ({\n id: d.id,\n data: d.data() as V1HistoryDoc | V2HistoryDoc,\n }));\n }\n\n async function list(\n ...args: any[]\n ): Promise<HistoryEntry<T>[]> {\n let opts: HistoryListOptions<T> = {};\n let pathArgs = args;\n const last = args[args.length - 1];\n if (\n last !== null &&\n typeof last === \"object\" &&\n !(last instanceof Timestamp) &&\n (\"limit\" in last ||\n \"cursor\" in last ||\n \"direction\" in last ||\n \"fields\" in last ||\n \"operations\" in last)\n ) {\n opts = last as HistoryListOptions<T>;\n pathArgs = args.slice(0, -1);\n }\n\n const limit = opts.limit ?? DEFAULT_LIMIT;\n // Read a bit more from Firestore because v1 entries get merged afterwards.\n // Cap at limit * 8 to avoid pathological reads.\n const fetchLimit = Math.max(limit, Math.min(limit * 8, 500));\n\n const rawDocs = await raw(...pathArgs, {\n limit: fetchLimit,\n cursor: opts.cursor,\n direction: opts.direction ?? \"desc\",\n });\n\n let entries = normalizeHistoryDocs(\n rawDocs.map((d) => d.data) as any[],\n ) as HistoryEntry<T>[];\n\n if (opts.fields && opts.fields.length > 0) {\n const set = new Set<string>(opts.fields as string[]);\n entries = entries.filter((e) =>\n Object.keys(e.changes).some((k) => set.has(k)),\n );\n }\n if (opts.operations && opts.operations.length > 0) {\n const set = new Set<HistoryOperation>(opts.operations);\n entries = entries.filter((e) => set.has(e.operation));\n }\n\n return entries.slice(0, limit);\n }\n\n async function byField(\n ...args: any[]\n ): Promise<HistoryEntry<T>[]> {\n const last = args[args.length - 1];\n let opts: HistoryListOptions<T> = {};\n let field: keyof T & string;\n let pathArgs: any[];\n if (last !== null && typeof last === \"object\" && !(last instanceof Timestamp)) {\n opts = last as HistoryListOptions<T>;\n field = args[args.length - 2] as keyof T & string;\n pathArgs = args.slice(0, -2);\n } else {\n field = last as keyof T & string;\n pathArgs = args.slice(0, -1);\n }\n return list(...pathArgs, { ...opts, fields: [field] });\n }\n\n async function byOperation(\n ...args: any[]\n ): Promise<HistoryEntry<T>[]> {\n const last = args[args.length - 1];\n let opts: HistoryListOptions<T> = {};\n let op: HistoryOperation;\n let pathArgs: any[];\n if (last !== null && typeof last === \"object\" && !(last instanceof Timestamp)) {\n opts = last as HistoryListOptions<T>;\n op = args[args.length - 2] as HistoryOperation;\n pathArgs = args.slice(0, -2);\n } else {\n op = last as HistoryOperation;\n pathArgs = args.slice(0, -1);\n }\n return list(...pathArgs, { ...opts, operations: [op] });\n }\n\n /**\n * Manually record a history entry. Bypasses the trigger and lets the caller\n * pass an explicit before/after pair plus meta (useful for synchronous flows\n * that need richer context than what the trigger can extract).\n */\n async function recordManual(\n ...args: any[]\n ): Promise<HistoryEntry<T> | null> {\n const payload = args[args.length - 1] as {\n operation: HistoryOperation;\n before?: T | null;\n after?: T | null;\n meta?: HistoryMeta;\n };\n const pathArgs = args.slice(0, -1);\n const entityId = getDocId(pathArgs);\n\n const changes = computeDiff(\n (payload.before ?? {}) as Record<string, unknown>,\n (payload.after ?? {}) as Record<string, unknown>,\n {\n include: config.include as string[] | undefined,\n exclude: config.exclude as string[] | undefined,\n metaFields: metaFieldsOf(config),\n systemKeys,\n },\n );\n if (\n payload.operation === \"update\" &&\n Object.keys(changes).length === 0\n ) {\n return null;\n }\n\n const fallbackMeta = extractMeta(\n (payload.after ?? payload.before ?? null) as Record<string, unknown> | null,\n config,\n );\n const meta: HistoryMeta = { ...fallbackMeta, ...(payload.meta ?? {}) };\n\n const entry = buildHistoryEntry({\n entityId,\n operation: payload.operation,\n changes,\n meta,\n config,\n });\n\n const ref = getHistoryRef(documentRef, subcollection, pathArgs);\n const result = await writeHistoryEntry(ref, entry, config, {\n repoName,\n docId: entityId,\n before: payload.before ?? null,\n after: payload.after ?? null,\n });\n if (!result.written || !result.entry) return null;\n\n return {\n historyDocId: result.entry.historyDocId,\n historyToObjectId: result.entry.historyToObjectId,\n historySetAt: result.entry.historySetAt,\n schemaVersion: 2,\n operation: result.entry.operation,\n meta: result.entry.meta,\n changes: result.entry.changes,\n } as HistoryEntry<T>;\n }\n\n return { list, raw, byField, byOperation, recordManual };\n}\n\nexport type HistoryMethods<T> = NonNullable<\n ReturnType<typeof createHistoryMethods<T>>\n>;\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { HttpsOptions } from 'firebase-functions/v2/https';
|
|
3
|
-
import { C as ConfiguredRepository, R as RepositoryConfig, F as FieldPath, n as FieldRole } from './types-
|
|
3
|
+
import { C as ConfiguredRepository, R as RepositoryConfig, F as FieldPath, n as FieldRole } from './types-BVUFVcpl.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Minimal zero-dependency HTTP router for Firebase Functions.
|
|
@@ -202,7 +202,7 @@ interface RelationalFieldMeta {
|
|
|
202
202
|
interface AdminRepoEntry {
|
|
203
203
|
name: string;
|
|
204
204
|
path: string;
|
|
205
|
-
repo: ConfiguredRepository<RepositoryConfig<any, any, any, any, any, any, any, any, any, any>>;
|
|
205
|
+
repo: ConfiguredRepository<RepositoryConfig<any, any, any, any, any, any, any, any, any, any, any>>;
|
|
206
206
|
schema: z.ZodObject<any>;
|
|
207
207
|
/** document key field name (default: "docId") */
|
|
208
208
|
documentKey?: string;
|
|
@@ -279,7 +279,7 @@ type RepoRegistry = Record<string, AdminRepoEntry>;
|
|
|
279
279
|
* `createRepositoryConfig(schema)`).
|
|
280
280
|
* @internal
|
|
281
281
|
*/
|
|
282
|
-
type RepoModelType<TRepo> = TRepo extends ConfiguredRepository<infer C> ? C extends RepositoryConfig<infer T, any, any, any, any, any, any, any, any, any> ? T : never : never;
|
|
282
|
+
type RepoModelType<TRepo> = TRepo extends ConfiguredRepository<infer C> ? C extends RepositoryConfig<infer T, any, any, any, any, any, any, any, any, any, any> ? T : never : never;
|
|
283
283
|
/**
|
|
284
284
|
* Configuration for a single repository in the admin server.
|
|
285
285
|
*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { HttpsOptions } from 'firebase-functions/v2/https';
|
|
3
|
-
import { C as ConfiguredRepository, R as RepositoryConfig, F as FieldPath, n as FieldRole } from './types-
|
|
3
|
+
import { C as ConfiguredRepository, R as RepositoryConfig, F as FieldPath, n as FieldRole } from './types-BESS89Fu.cjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Minimal zero-dependency HTTP router for Firebase Functions.
|
|
@@ -202,7 +202,7 @@ interface RelationalFieldMeta {
|
|
|
202
202
|
interface AdminRepoEntry {
|
|
203
203
|
name: string;
|
|
204
204
|
path: string;
|
|
205
|
-
repo: ConfiguredRepository<RepositoryConfig<any, any, any, any, any, any, any, any, any, any>>;
|
|
205
|
+
repo: ConfiguredRepository<RepositoryConfig<any, any, any, any, any, any, any, any, any, any, any>>;
|
|
206
206
|
schema: z.ZodObject<any>;
|
|
207
207
|
/** document key field name (default: "docId") */
|
|
208
208
|
documentKey?: string;
|
|
@@ -279,7 +279,7 @@ type RepoRegistry = Record<string, AdminRepoEntry>;
|
|
|
279
279
|
* `createRepositoryConfig(schema)`).
|
|
280
280
|
* @internal
|
|
281
281
|
*/
|
|
282
|
-
type RepoModelType<TRepo> = TRepo extends ConfiguredRepository<infer C> ? C extends RepositoryConfig<infer T, any, any, any, any, any, any, any, any, any> ? T : never : never;
|
|
282
|
+
type RepoModelType<TRepo> = TRepo extends ConfiguredRepository<infer C> ? C extends RepositoryConfig<infer T, any, any, any, any, any, any, any, any, any, any> ? T : never : never;
|
|
283
283
|
/**
|
|
284
284
|
* Configuration for a single repository in the admin server.
|
|
285
285
|
*
|