@hvakr/firestate 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -306,6 +306,48 @@ To skip undo tracking:
306
306
  project.update({ lastViewed: Date.now() }, { undoable: false })
307
307
  ```
308
308
 
309
+ #### Navigation-aware undo/redo
310
+
311
+ When an undo action is tagged with a `path`, undo/redo can return the user to
312
+ the route where the change occurred before reverting it. Wire your router's
313
+ `navigate` via `onNavigate` on `FirestateProvider`:
314
+
315
+ ```tsx
316
+ import { useNavigate } from 'react-router-dom'
317
+
318
+ function App() {
319
+ const navigate = useNavigate()
320
+
321
+ return (
322
+ <FirestateProvider
323
+ firestore={db}
324
+ onNavigate={(path) => navigate(path)}
325
+ >
326
+ {children}
327
+ </FirestateProvider>
328
+ )
329
+ }
330
+ ```
331
+
332
+ When creating the store manually, pass `onNavigate` to `createStore`:
333
+
334
+ ```ts
335
+ const store = createStore({
336
+ firestore: db,
337
+ onNavigate: (path) => router.push(path),
338
+ })
339
+ ```
340
+
341
+ Actions record a path via the `path` field on `UndoAction`:
342
+
343
+ ```tsx
344
+ undoManager.push({
345
+ undo: () => restoreValue(),
346
+ redo: () => applyValue(),
347
+ path: '/projects/123', // navigate here on undo/redo
348
+ })
349
+ ```
350
+
309
351
  ### Lazy Collections
310
352
 
311
353
  For large applications, you may not want to subscribe to every collection immediately:
@@ -592,6 +634,7 @@ Main provider component.
592
634
  autosave={1000} // Optional: default debounce (ms)
593
635
  minLoadTime={0} // Optional: minimum loading time (ms)
594
636
  maxUndoLength={20} // Optional: max undo stack size
637
+ onNavigate={(path) => navigate(path)} // Optional: router navigate for path-aware undo/redo
595
638
  onError={(error, context) => {
596
639
  // Optional: custom error handler
597
640
  console.error(context.path, error)
package/dist/index.d.mts CHANGED
@@ -243,8 +243,12 @@ interface FirestateConfig {
243
243
  minLoadTime?: number;
244
244
  /** Maximum undo stack length, default 20 */
245
245
  maxUndoLength?: number;
246
- /** Enable navigation-aware undo/redo */
247
- enableNavigation?: boolean;
246
+ /**
247
+ * Callback invoked before undo/redo when the action carries a `path`.
248
+ * Wire your router's `navigate` here so undo/redo returns the user to
249
+ * where a change occurred before reverting it.
250
+ */
251
+ onNavigate?: (path: string) => void;
248
252
  /** Custom error handler */
249
253
  onError?: (error: Error, context: ErrorContext) => void;
250
254
  }
@@ -409,6 +413,12 @@ interface FirestateStore {
409
413
  * callback that changes reference on every render.
410
414
  */
411
415
  setOnError: (handler?: (error: Error, context: ErrorContext) => void) => void;
416
+ /**
417
+ * Replace the navigation handler at runtime. Used by FirestateProvider to
418
+ * keep the store identity stable when consumers pass an inline `onNavigate`
419
+ * callback that changes reference on every render.
420
+ */
421
+ setOnNavigate: (handler?: (path: string) => void) => void;
412
422
  /** Subscribe to sync state changes */
413
423
  subscribeToSyncState: (fn: Subscriber<boolean>) => Unsubscribe;
414
424
  /** Report a document/collection sync state change */
@@ -1018,6 +1028,26 @@ interface FirestateProviderProps {
1018
1028
  minLoadTime?: number;
1019
1029
  /** Maximum undo stack length, default 20 */
1020
1030
  maxUndoLength?: number;
1031
+ /**
1032
+ * Called before undo/redo when the action carries a `path`. Wire your
1033
+ * router's `navigate` here to return users to where a change occurred
1034
+ * before reverting it.
1035
+ *
1036
+ * @example
1037
+ * ```tsx
1038
+ * import { useNavigate } from 'react-router-dom'
1039
+ *
1040
+ * function App() {
1041
+ * const navigate = useNavigate()
1042
+ * return (
1043
+ * <FirestateProvider onNavigate={(path) => navigate(path)}>
1044
+ * {children}
1045
+ * </FirestateProvider>
1046
+ * )
1047
+ * }
1048
+ * ```
1049
+ */
1050
+ onNavigate?: (path: string) => void;
1021
1051
  /** Custom error handler */
1022
1052
  onError?: (error: Error, context: ErrorContext) => void;
1023
1053
  /** React children */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/schema.ts","../src/undo.ts","../src/store.ts","../src/hooks.ts","../src/firestate.ts","../src/diff.ts","../src/document.ts","../src/collection.ts","../src/provider.tsx"],"mappings":";;;;;;;AAYA;;KAAY,WAAA,MAAiB,CAAA,gCACX,CAAA,IAAK,WAAA,CAAY,CAAA,CAAE,CAAA,OACjC,CAAA;AAAA;;AAUJ;AAKA;AAUA;AAcA;;;AAvCI,KAUQ,eAAA,GAAkB,MAAA;AAAA;AAK9B;AAUA;AAf8B,UAKb,aAAA;EAAA;EAAA,QAAA;EAAA;EAAA,WAAA;AAAA;AAAA;AAUjB;AAcA;AAxBiB,UAUA,aAAA;EAAA;EAAA,IAAA,EAET,CAAA;EAAA;EAAA,SAAA;EAAA;EAAA,QAAA;EAAA;EAAA,KAAA,EAMC,KAAA;AAAA;AAAA;AAMT;;AANS,UAMQ,eAAA;EAAA;EAAA,IAAA,EAET,MAAA,SAAe,CAAA;EAAA;EAAA,SAAA;EAAA;EAAA,QAAA;EAAA;EAAA,QAAA;EAAA;EAAA,KAAA,EAQd,KAAA;AAAA;AAAA;;AAMT;AANS,UAMQ,cAAA,WAAyB,eAAA;EAAA;EAAA,IAAA,EAElC,CAAA;EAAA;EAAA,MAAA,GAAA,IAAA,EAGE,cAAA,CAAe,WAAA,CAAY,CAAA,IAAA,OAAA,GACvB,aAAA;EAAA;EAAA,GAAA,GAAA,IAAA,EAGA,CAAA,EAAA,OAAA,GAAa,aAAA;EAAA;EAAA,MAAA,GAAA,OAAA,GAEN,aAAA;EAAA;EAAA,SAAA;EAAA;EAAA,QAAA;EAAA;EAAA,IAAA,QAMP,OAAA;EAAA;EAAA,KAAA,EAEL,KAAA;EAAA;;;;EAAA,GAAA,EAKF,iBAAA,CAAkB,CAAA;AAAA;AAAA;;;AAAA,UAMR,gBAAA,WAA2B,eAAA;EAAA;EAAA,IAAA,EAEpC,MAAA,SAAe,CAAA;EAAA;EAAA,MAAA,GAAA,IAAA,EAGb,cAAA,CAAe,WAAA,CAAY,MAAA,SAAe,CAAA,KAAA,OAAA,GACtC,aAAA;EAAA;;;;;;;;EAAA,GAAA;IAAA,CAAA,EAAA,UAAA,IAAA,EAWS,IAAA,CAAK,CAAA,SAAA,OAAA,GAAoB,aAAA;IAAA,CAAA,IAAA,EACrC,IAAA,CAAK,CAAA,SAAA,OAAA,GAAoB,aAAA;EAAA;EAAA;EAAA,MAAA,GAAA,EAAA,UAAA,OAAA,GAGH,aAAA;EAAA;EAAA,SAAA;EAAA;EAAA,QAAA;EAAA;EAAA,QAAA;EAAA;EAAA,IAAA;EAAA;EAAA,IAAA,QAUnB,OAAA;EAAA;EAAA,KAAA,EAEL,KAAA;EAAA;;;;EAAA,GAAA,EAKF,mBAAA,CAAoB,CAAA;AAAA;AAAA;;;AAAA,UAMV,UAAA;EAAA;EAAA,IAAA,QAEH,OAAA;EAAA;EAAA,IAAA,QAEA,OAAA;EAAA;EAAA,OAAA;EAAA;EAAA,IAAA;EAAA;EAAA,WAAA;AAAA;AAAA;AAYd;AAcA;AA1Bc,UAYG,gBAAA;EAAA;EAAA,SAAA,WAEK,UAAA;EAAA;EAAA,SAAA,WAEA,UAAA;EAAA;EAAA,OAAA;EAAA;EAAA,OAAA;AAAA;AAAA;AAUtB;;AAVsB,UAUL,WAAA,SAAoB,gBAAA;EAAA;EAAA,IAAA,QAEvB,OAAA;EAAA;EAAA,IAAA,QAEA,OAAA;EAAA;EAAA,IAAA,GAAA,MAAA,EAEG,UAAA;EAAA;EAAA,KAAA;AAAA;AAAA;;;AAWjB;;;AAXiB,UAWA,kBAAA,eAAiC,eAAA;EAAA;;;;;;AAuClD;;;;;EAvCkD,MAAA,GAYvC,OAAA,CAAQ,KAAA;EAAA;;;;;AA2BnB;EA3BmB,UAAA,aAAA,MAAA,EAOc,MAAA;EAAA;EAAA,EAAA,aAAA,MAAA,EAER,MAAA;EAAA;EAAA,QAAA;EAAA;EAAA,WAAA;EAAA;EAAA,QAAA;EAAA;EAAA,YAAA;EAAA;EAAA,aAAA;AAAA;AAAA;;AAkBzB;;;AAlByB,UAkBR,oBAAA,eAAmC,eAAA;EAAA;;;;;;AA8BpD;EA9BoD,MAAA,GAQzC,OAAA,CAAQ,KAAA;EAAA;EAAA,IAAA,aAAA,MAAA,EAEQ,MAAA;EAAA;EAAA,QAAA;EAAA;EAAA,WAAA;EAAA;EAAA,QAAA;EAAA;EAAA,IAAA;EAAA;EAAA,gBAAA,GAUN,eAAA;EAAA;EAAA,YAAA;EAAA;EAAA,aAAA;AAAA;AAAA;;AAUrB;AAVqB,UAUJ,eAAA;EAAA;EAAA,SAAA,EAEJ,SAAA;EAAA;EAAA,QAAA;EAAA;EAAA,WAAA;EAAA;EAAA,aAAA;EAAA;EAAA,gBAAA;EAAA;EAAA,OAAA,IAAA,KAAA,EAUO,KAAA,EAAA,OAAA,EAAgB,YAAA;AAAA;AAAA;;AAMpC;AANoC,UAMnB,YAAA;EAAA,IAAA;EAAA,IAAA;EAAA,SAAA;AAAA;AAAA;AASjB;AAKA;AAdiB,KASL,UAAA,OAAA,KAAA,EAAwB,CAAA;AAAA;AAKpC;;AALoC,KAKxB,WAAA;;;;ACtPZ;;;;;;;;;;;;AAKA;;;;;;;AA2BA;;;;;;;;;;;;AAKA;;;;;;iBArCgB,cAAA,WAAyB,OAAA,CAAQ,eAAA,EAAA,CAAA,UAAA,EACnC,IAAA,CAAK,kBAAA,CAAmB,CAAA,CAAE,KAAA,CAAM,CAAA;EAAA,MAAA,EAClC,CAAA;AAAA,IAET,kBAAA,CAAmB,CAAA,CAAE,KAAA,CAAM,CAAA;AAAA,iBACd,cAAA,eAA6B,eAAA,CAAA,CAAA,UAAA,EAC/B,kBAAA,CAAmB,KAAA,IAC9B,kBAAA,CAAmB,KAAA;AAAA;;;AAyBtB;;;;;;;;;;;;AAKA;;;AA9BsB,iBAyBN,gBAAA,WAA2B,OAAA,CAAQ,eAAA,EAAA,CAAA,UAAA,EACrC,IAAA,CAAK,oBAAA,CAAqB,CAAA,CAAE,KAAA,CAAM,CAAA;EAAA,MAAA,EACpC,CAAA;AAAA,IAET,oBAAA,CAAqB,CAAA,CAAE,KAAA,CAAM,CAAA;AAAA,iBAChB,gBAAA,eAA+B,eAAA,CAAA,CAAA,UAAA,EACjC,oBAAA,CAAqB,KAAA,IAChC,oBAAA,CAAqB,KAAA;AAAA;;;AAAA,KAUZ,iBAAA,WAA4B,kBAAA,CAAmB,eAAA,KACzD,CAAA,SAAU,kBAAA,YAAA,CAAA;AAAA;;AAKZ;AALY,KAKA,aAAA,WAAwB,kBAAA,CAAmB,eAAA,KACrD,iBAAA,CAAkB,CAAA;EAAA,EAAA;AAAA;AAAA;;;AAAA,KAKR,mBAAA,WACA,oBAAA,CAAqB,eAAA,KAC7B,CAAA,SAAU,oBAAA,YAAA,CAAA;AAAA;;AAKd;AALc,KAKF,uBAAA,WACA,oBAAA,CAAqB,eAAA,KAC7B,mBAAA,CAAoB,CAAA;EAAA,EAAA;AAAA;;;;AC9GxB;AAyBA;UAzBiB,iBAAA;EAAA;EAAA,SAAA;EAAA;EAAA,UAAA,IAAA,IAAA;AAAA;AAAA;AAyBjB;;;;;;;;AAoKA;;;;AC3LA;;;;;ADFiB,cAyBJ,iBAAA,GAAA,MAAA,GACD,iBAAA,KACT,WAAA;EAAA,SAAA,GAAA,EAAA,EACiB,UAAA,CAAW,gBAAA,MAAsB,WAAA;EAAA,QAAA,QACjC,gBAAA;AAAA;AAAA;;AAgKpB;AAhKoB,KAgKR,wBAAA,GAA2B,UAAA,QAAkB,iBAAA;;;;AC3LzD;;UAAiB,cAAA;EAAA;EAAA,SAAA,SAAA,EAEO,SAAA;EAAA;EAAA,SAAA,WAAA,EAEE,wBAAA;EAAA;EAAA,SAAA,QAAA;EAAA;EAAA,SAAA,WAAA;EAAA;EAAA,WAAA,GAAA,KAAA,EAMD,KAAA,EAAA,OAAA,EAAgB,YAAA;EAAA;;;;;EAAA,UAAA,GAAA,OAAA,IAAA,KAAA,EAMN,KAAA,EAAA,OAAA,EAAgB,YAAA;EAAA;EAAA,oBAAA,GAAA,EAAA,EAEpB,UAAA,cAAwB,WAAA;EAAA;EAAA,eAAA,GAAA,GAAA,UAAA,QAAA;EAAA;;AA+BvD;AAoFA;EAnHuD,mBAAA,GAAA,GAAA;EAAA;EAAA,SAAA,QAAA;AAAA;AAAA;;AA+BvD;AAoFA;;;;ACvEA;AAKA;AAWA;AAmCA;AAgBA;;;;;;AA2DA;;AD1KuD,cA+B1C,WAAA,GAAA,MAAA,EAAuB,eAAA,KAAkB,cAAA;AAAA;AAoFtD;;AApFsD,KAoF1C,KAAA,GAAQ,UAAA,QAAkB,WAAA;;;;ACvEtC;AAKA;cALa,gBAAA,EAAgB,MAAA,CAAA,OAAA,CAAA,cAAA;AAAA;AAK7B;AAWA;AAhB6B,cAKhB,QAAA,QAAe,cAAA;AAAA;AAW5B;AAmCA;AA9C4B,cAWf,cAAA,QAAqB,WAAA;AAAA;AAmClC;AAgBA;AAnDkC,cAmCrB,WAAA;AAAA;AAgBb;;AAhBa,UAgBI,kBAAA,eAAiC,eAAA;EAAA;EAAA,UAAA,EAEpC,kBAAA,CAAmB,KAAA;EAAA;EAAA,MAAA,GAEtB,MAAA;EAAA;EAAA,QAAA;EAAA;EAAA,QAAA;EAAA;;AAuDX;;;;EAvDW,OAAA;AAAA;AAAA;;AAuDX;;;;;;;AAwFA;;;;;;;AAmEA;;;;;;;AAoGA;;;;ACvZA;;;;;;AAQA;;;;;;AAGE;AA2BF;AD2DW,cAuDE,WAAA,iBAA6B,eAAA,EAAA,OAAA,EAC/B,kBAAA,CAAmB,KAAA,MAC3B,cAAA,CAAe,KAAA;AAAA;;;AAAA,UAsFD,oBAAA,eAAmC,eAAA;EAAA;EAAA,UAAA,EAEtC,oBAAA,CAAqB,KAAA;EAAA;EAAA,MAAA,GAExB,MAAA;EAAA;EAAA,QAAA;EAAA;EAAA,gBAAA,GAIU,eAAA;EAAA;EAAA,QAAA;EAAA;;AA2DrB;;;EA3DqB,OAAA;AAAA;AAAA;;AA2DrB;;;;;;;AAoGA;;;;ACvZA;;;;;;AAQA;;;;;;AAGE;AA2BF;;;;;;;;AAwBA;;;;;;;;;AAgBA;;;;;AD0KqB,cA2DR,aAAA,iBAA+B,eAAA,EAAA,OAAA,EACjC,oBAAA,CAAqB,KAAA,MAC7B,gBAAA,CAAiB,KAAA;AAAA;;;AAkGpB;;;;ACvZA;;;;ADqToB,cAkGP,wBAAA;;;;ACvZb;;;;KAAY,cAAA,WAAyB,eAAA,IAAmB,IAAA,CACtD,kBAAA,CAAmB,CAAA;AAAA;;;AAAA,KAOT,cAAA,WAAyB,eAAA,IAAmB,IAAA,CACtD,oBAAA,CAAqB,CAAA;AAAA,UAQb,kBAAA;EAAA;EAAA,QAAA;EAAA;EAAA,WAAA;EAAA;EAAA,QAAA;EAAA;EAAA,YAAA;EAAA;EAAA,aAAA;AAAA;AAAA;AAqBV;;;;;;;AArBU,UAqBO,QAAA,WACL,eAAA,qCAEF,kBAAA;EAAA,SAAA,MAAA;EAAA,SAAA,MAAA,GAEU,CAAA;EAAA;EAAA,IAAA,EAEZ,CAAA;EAAA;;;;;AAiBR;;;;;;;EAjBQ,MAAA,EAaE,OAAA,CAAQ,CAAA;AAAA;AAAA;AAAA,UAID,QAAA,WACL,eAAA,qCAEF,kBAAA;EAAA,SAAA,MAAA;EAAA,SAAA,MAAA,GAEU,CAAA;EAAA;EAAA,IAAA,EAEZ,CAAA;EAAA;EAAA,MAAA,EAEE,OAAA,CAAQ,CAAA;EAAA;EAAA,IAAA;EAAA;EAAA,gBAAA,GAIG,eAAA;AAAA;AAAA,KAGT,cAAA,WACA,eAAA,GAAkB,eAAA,+BAE1B,QAAA,CAAS,CAAA,EAAG,CAAA,IAAK,QAAA,CAAS,CAAA,EAAG,CAAA;AAAA,KAErB,iBAAA,GAAoB,MAAA,SAAe,cAAA;AAAA;AAgB/C;;;;;;;AAE6B;;AAlBkB,KAgBnC,QAAA,oCAA4C,CAAA,GACpD,MAAA,mBACA,QAAA,CAAS,WAAA,CAAY,CAAA;AAAA,KAEpB,WAAA,qBACH,CAAA,0DACc,CAAA,cAAe,WAAA,CAAY,IAAA;AAAA,KAKtC,QAAA,oBAA4B,CAAA,GAAI,CAAA,CAAE,CAAA;AAAA,KAMlC,OAAA,WAAkB,eAAA,IAAmB,IAAA,CAAK,QAAA,CAAS,CAAA;AAAA,KACnD,OAAA,WAAkB,eAAA,IAAmB,IAAA,CAAK,QAAA,CAAS,CAAA;AAAA;;;;AAuBxD;;;;;;;;;;;;;;AAwBA;;;AA/CwD,iBAuBxC,GAAA,WACJ,OAAA,CAAQ,eAAA,mCAAA,CAAA,IAAA,EAGZ,IAAA,CAAK,OAAA,CAAQ,CAAA,CAAE,KAAA,CAAM,CAAA;EAAA,MAAA,EACjB,CAAA;EAAA,IAAA,EACF,CAAA;AAAA,IAEP,QAAA,CAAS,CAAA,CAAE,KAAA,CAAM,CAAA,GAAI,CAAA;AAAA;;;AAgBxB;AAhBwB,iBAgBR,GAAA,WACJ,OAAA,CAAQ,eAAA,mCAAA,CAAA,IAAA,EAGZ,IAAA,CAAK,OAAA,CAAQ,CAAA,CAAE,KAAA,CAAM,CAAA;EAAA,MAAA,EACjB,CAAA;EAAA,IAAA,EACF,CAAA;AAAA,IAEP,QAAA,CAAS,CAAA,CAAE,KAAA,CAAM,CAAA,GAAI,CAAA;AAAA,KAanB,QAAA,2BAAmC,UAAA,CAAW,CAAA;AAAA,KAK9C,OAAA,MAAa,CAAA,SAAU,QAAA,2BAClB,QAAA,CAAS,CAAA,mBAAA,MAAA,GAEF,MAAA,kBAAA,OAAA,GACC,cAAA,CAAe,CAAA,MACtB,cAAA,CAAe,CAAA,KAAA,MAAA,EAEV,QAAA,CAAS,CAAA,GAAA,OAAA,GACP,cAAA,CAAe,CAAA,MACtB,cAAA,CAAe,CAAA,IACtB,CAAA,SAAU,QAAA,2BACJ,QAAA,CAAS,CAAA,mBAAA,MAAA,GAEF,MAAA,kBAAA,OAAA,GACC,cAAA,CAAe,CAAA,MACtB,gBAAA,CAAiB,CAAA,KAAA,MAAA,EAEZ,QAAA,CAAS,CAAA,GAAA,OAAA,GACP,cAAA,CAAe,CAAA,MACtB,gBAAA,CAAiB,CAAA;AAAA,KAGhB,YAAA,WAAuB,iBAAA,kBACrB,CAAA,aAAc,QAAA,CAAS,CAAA,IAAK,OAAA,CAAQ,CAAA,CAAE,CAAA;AAAA;;;AAcpD;;;;;;;;AAdoD,iBAcpC,eAAA,WAA0B,iBAAA,CAAA,CAAA,QAAA,EAC9B,CAAA,GACT,YAAA,CAAa,CAAA;;;;ACpNhB;AAmCA;cAnCa,WAAA,GAAA,CAAA,WAAA,CAAA;AAAA;AAmCb;;;;;;;AAnCa,cAmCA,WAAA,aAAyB,eAAA,EAAA,IAAA,EAC5B,CAAA,EAAA,EAAA,EACF,CAAA,iBACL,cAAA,CAAe,WAAA,CAAY,CAAA;AAAA;;;;AA+E9B;AA2DA;AAuBA;AAwBA;AAkCA;;;AA3N8B,cA+EjB,gBAAA,GAAA,MAAA,EACD,eAAA,EAAA,IAAA,EACF,MAAA;AAAA;AAyDV;AAuBA;AAwBA;AAkCA;;;;;;AA1IU,cAyDG,SAAA,MAAA,KAAA,EAAuB,CAAA,KAAI,CAAA;AAAA;AAuBxC;AAwBA;AA/CwC,cAuB3B,WAAA,GAAA,IAAA,EAAqB,MAAA;AAAA;AAwBlC;AAkCA;;;;;;;;;;;;AAoCA;;;;;;;AA9FkC,cAwBrB,WAAA,GAAA,IAAA,EACH,MAAA,mBAAA,MAAA,cAEP,MAAA;AAAA;AA+BH;;AA/BG,cA+BU,UAAA,aAAwB,eAAA,EAAA,KAAA,EAC1B,cAAA,CAAe,WAAA,CAAY,CAAA,IAAA,MAAA,EAC1B,cAAA,CAAe,WAAA,CAAY,CAAA,OACpC,cAAA,CAAe,WAAA,CAAY,CAAA;AAAA;;;;AAiC9B;;;;;;;;AAiCA;AAlE8B,cAiCjB,SAAA,aAAuB,eAAA,EAAA,KAAA,EACzB,CAAA,EAAA,IAAA,EACD,cAAA,CAAe,WAAA,CAAY,CAAA,OAClC,CAAA;AAAA;;AA8BH;;;;;;;;;;AAsBA;AAkCA;AAmCA;AAkCA;;;;ACzdA;;;;;AD8TG,cA8BU,eAAA,aAA6B,eAAA,EAAA,UAAA,EAC1B,CAAA,EAAA,IAAA,EACN,cAAA,CAAe,WAAA,CAAY,CAAA,OAClC,cAAA,CAAe,WAAA,CAAY,CAAA;AAAA;;;;AAmB9B;AAkCA;AAmCA;AAkCA;;;;ACzdA;;;AD+V8B,cAmBjB,gBAAA,GAAA,IAAA,EACH,MAAA,mBAAA,IAAA;AAAA;AAiCV;AAmCA;AAkCA;;;;ACzdA;;;;;;;ADmXU,cAiCG,gBAAA,GAAA,IAAA,EACH,MAAA,mBAAA,IAAA;AAAA;AAkCV;AAkCA;;;;ACzdA;;;;;;;AA2EA;;AD0UU,cAkCG,gBAAA,GAAA,IAAA,UAAA,KAAA,cAGV,MAAA;AAAA;AA+BH;;;;ACzdA;;;;;;AD0bG,cA+BU,aAAA,GAAA,QAAA,EACC,MAAA,sBACX,MAAA;;;;AC3dH;;UAAiB,eAAA,eAA8B,eAAA;EAAA;EAAA,KAAA,EAEpC,cAAA;EAAA;EAAA,UAAA,EAEK,kBAAA,CAAmB,KAAA;EAAA;;;;EAAA,KAAA;EAAA;;;;AAuEnC;EAvEmC,cAAA;EAAA;EAAA,QAAA;EAAA;EAAA,UAAA,IAAA,UAAA,cAAA,UAAA,cAAA,OAAA,GAkBjB,aAAA;AAAA;AAAA;;AAqDlB;;;;;;;;;;;;;;;;;AArDkB,cAqDL,0BAAA,iBAA4C,eAAA,EAAA,OAAA,EAC5C,eAAA,CAAgB,KAAA;EAAA,oCAAA,IAAA;EAAA,IAAA;EAAA,SAAA,GAAA,EAAA,EAOT,UAAA,CAAW,aAAA,CAAc,KAAA,OAAY,WAAA;EAAA,QAAA,QAErC,aAAA,CAAc,KAAA;EAAA,SAAA,QAEb,cAAA,CAAe,KAAA;EAAA,IAAA,QAEpB,OAAA;AAAA;;;;ACxFhB;;UAAiB,iBAAA,eAAgC,eAAA;EAAA;EAAA,KAAA,EAEtC,cAAA;EAAA;EAAA,UAAA,EAEK,oBAAA,CAAqB,KAAA;EAAA;;;;;EAAA,cAAA;EAAA;EAAA,QAAA;EAAA;EAAA,gBAAA,GAUd,eAAA;EAAA;EAAA,UAAA,IAAA,UAAA,cAAA,UAAA,cAAA,OAAA,GAKL,aAAA;AAAA;AAAA;;AA0ClB;;;;;;;;;;;;;;;;;AA1CkB,cA0CL,4BAAA,iBAA8C,eAAA,EAAA,OAAA,EAC9C,iBAAA,CAAkB,KAAA;EAAA,mDAAA,IAAA;EAAA,IAAA;EAAA,SAAA,GAAA,EAAA,EAOX,UAAA,CAAW,eAAA,CAAgB,KAAA,OAAY,WAAA;EAAA,QAAA,QAEvC,eAAA,CAAgB,KAAA;EAAA,SAAA,QAEf,gBAAA,CAAiB,KAAA;EAAA,IAAA,QAEtB,OAAA;AAAA;;;;ACpGhB;;UAAiB,sBAAA;EAAA;EAAA,SAAA,EAEJ,SAAA;EAAA;EAAA,QAAA;EAAA;EAAA,WAAA;EAAA;EAAA,aAAA;EAAA;EAAA,OAAA,IAAA,KAAA,EAQO,KAAA,EAAA,OAAA,EAAgB,YAAA;EAAA;EAAA,QAAA,EAExB,KAAA,CAAM,SAAA;AAAA;AAAA;;AAyBlB;AAsCA;AAwBA;AAmCA;;;;;;;;;;;;;;;;;AA1HkB,cAyBL,iBAAA,EAAmB,KAAA,CAAM,EAAA,CAAG,sBAAA;AAAA;AAsCzC;AAwBA;AA9DyC,UAsCxB,2BAAA;EAAA;EAAA,KAAA,EAER,cAAA;EAAA;EAAA,QAAA,EAEG,KAAA,CAAM,SAAA;AAAA;AAAA;AAoBlB;AAmCA;;;;;;;;;;;;;;;AAvDkB,cAoBL,sBAAA,EAAwB,KAAA,CAAM,EAAA,CAAG,2BAAA;AAAA;AAmC9C;;;;;;;;;;;;;;;;;;;;;;;;;AAnC8C,cAmCjC,wBAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/schema.ts","../src/undo.ts","../src/store.ts","../src/hooks.ts","../src/firestate.ts","../src/diff.ts","../src/document.ts","../src/collection.ts","../src/provider.tsx"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -484,16 +484,16 @@ const createDocumentSubscription = (options) => {
484
484
  };
485
485
  const updateState = (diff, undoOptions = {}) => {
486
486
  if (isReadOnly) return;
487
- const currentData = getMergedData();
488
- if (!currentData) {
487
+ if (!getMergedData()) {
489
488
  if (process.env.NODE_ENV !== "production") console.warn(`[firestate] update() on ${collectionPath}/${documentId} was ignored: there is no current data to diff against. This happens when the document is still loading, has been deleted, or doesn't exist yet. Use set() to create the document, or gate update calls on a non-undefined data value.`);
490
489
  return;
491
490
  }
492
- const newLocalState = deepClone(currentData);
491
+ const rawBase = state.localState ?? state.syncState;
492
+ const newLocalState = deepClone(rawBase);
493
493
  applyDiffMutable(newLocalState, diff);
494
494
  if (undoOptions?.undoable !== false && onPushUndo) {
495
- const undoDiff = computeDiff(newLocalState, currentData);
496
- const redoDiff = computeDiff(currentData, newLocalState);
495
+ const undoDiff = computeDiff(newLocalState, rawBase);
496
+ const redoDiff = computeDiff(rawBase, newLocalState);
497
497
  onPushUndo(() => updateState(undoDiff, { undoable: false }), () => updateState(redoDiff, { undoable: false }), undoOptions);
498
498
  }
499
499
  state.localState = newLocalState;
@@ -509,7 +509,7 @@ const createDocumentSubscription = (options) => {
509
509
  const dataForRedo = deepClone(data);
510
510
  if (currentData === void 0) onPushUndo(() => deleteDocument({ undoable: false }), () => setData(dataForRedo, { undoable: false }), undoOptions);
511
511
  else {
512
- const dataToRestore = deepClone(currentData);
512
+ const dataToRestore = deepClone(state.localState ?? state.syncState);
513
513
  onPushUndo(() => setData(dataToRestore, { undoable: false }), () => setData(dataForRedo, { undoable: false }), undoOptions);
514
514
  }
515
515
  }
@@ -520,10 +520,9 @@ const createDocumentSubscription = (options) => {
520
520
  };
521
521
  const deleteDocument = (undoOptions = {}) => {
522
522
  if (isReadOnly) return;
523
- const currentData = getMergedData();
524
- if (currentData === void 0) return;
523
+ if (getMergedData() === void 0) return;
525
524
  if (undoOptions?.undoable !== false && onPushUndo) {
526
- const dataToRestore = deepClone(currentData);
525
+ const dataToRestore = deepClone(state.localState ?? state.syncState);
527
526
  onPushUndo(() => setData(dataToRestore, { undoable: false }), () => deleteDocument({ undoable: false }), undoOptions);
528
527
  }
529
528
  state.localState = null;
@@ -786,13 +785,13 @@ const createCollectionSubscription = (options) => {
786
785
  warnNoSnapshot("update");
787
786
  return;
788
787
  }
789
- const currentData = getMergedData();
790
- const newLocalState = deepClone(currentData);
788
+ const rawBase = state.localState ?? state.syncState ?? {};
789
+ const newLocalState = deepClone(rawBase);
791
790
  applyDiffMutable(newLocalState, diff);
792
791
  for (const [docId, docData] of Object.entries(newLocalState)) if (docData && typeof docData === "object") docData.id = docId;
793
792
  if (undoOptions?.undoable !== false && onPushUndo) {
794
- const undoDiff = computeDiff(newLocalState, currentData);
795
- const redoDiff = computeDiff(currentData, newLocalState);
793
+ const undoDiff = computeDiff(newLocalState, rawBase);
794
+ const redoDiff = computeDiff(rawBase, newLocalState);
796
795
  onPushUndo(() => updateState(undoDiff, { undoable: false }), () => updateState(redoDiff, { undoable: false }), undoOptions);
797
796
  }
798
797
  state.localState = newLocalState;
@@ -1630,7 +1629,11 @@ const createUndoManager = (config = {}) => {
1630
1629
  const createStore = (config) => {
1631
1630
  const { firestore, autosave = 1e3, minLoadTime = 0, maxUndoLength = 20 } = config;
1632
1631
  let onError = config.onError;
1633
- const undoManager = createUndoManager({ maxLength: maxUndoLength });
1632
+ let onNavigate = config.onNavigate;
1633
+ const undoManager = createUndoManager({
1634
+ maxLength: maxUndoLength,
1635
+ onNavigate: (path) => onNavigate?.(path)
1636
+ });
1634
1637
  const syncStates = /* @__PURE__ */ new Map();
1635
1638
  const syncSubscribers = /* @__PURE__ */ new Set();
1636
1639
  const computeIsSynced = () => {
@@ -1653,6 +1656,9 @@ const createStore = (config) => {
1653
1656
  setOnError: (handler) => {
1654
1657
  onError = handler;
1655
1658
  },
1659
+ setOnNavigate: (handler) => {
1660
+ onNavigate = handler;
1661
+ },
1656
1662
  subscribeToSyncState: (fn) => {
1657
1663
  syncSubscribers.add(fn);
1658
1664
  return () => syncSubscribers.delete(fn);
@@ -1699,13 +1705,14 @@ const createStore = (config) => {
1699
1705
  * }
1700
1706
  * ```
1701
1707
  */
1702
- const FirestateProvider = ({ firestore, autosave = 1e3, minLoadTime = 0, maxUndoLength = 20, onError, children }) => {
1708
+ const FirestateProvider = ({ firestore, autosave = 1e3, minLoadTime = 0, maxUndoLength = 20, onError, onNavigate, children }) => {
1703
1709
  const store = useMemo(() => createStore({
1704
1710
  firestore,
1705
1711
  autosave,
1706
1712
  minLoadTime,
1707
1713
  maxUndoLength,
1708
- onError
1714
+ onError,
1715
+ onNavigate
1709
1716
  }), [
1710
1717
  firestore,
1711
1718
  autosave,
@@ -1715,6 +1722,9 @@ const FirestateProvider = ({ firestore, autosave = 1e3, minLoadTime = 0, maxUndo
1715
1722
  useEffect(() => {
1716
1723
  store.setOnError(onError);
1717
1724
  }, [store, onError]);
1725
+ useEffect(() => {
1726
+ store.setOnNavigate(onNavigate);
1727
+ }, [store, onNavigate]);
1718
1728
  return /* @__PURE__ */ jsx(FirestateContext.Provider, {
1719
1729
  value: store,
1720
1730
  children
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["syncKeyCounter","doc","doc"],"sources":["../src/schema.ts","../src/diff.ts","../src/document.ts","../src/collection.ts","../src/hooks.ts","../src/firestate.ts","../src/undo.ts","../src/store.ts","../src/provider.tsx"],"sourcesContent":["import type { ZodType, z } from \"zod\";\nimport type {\n CollectionDefinition,\n DocumentDefinition,\n FirestoreObject,\n} from \"./types\";\n\n/**\n * Define a typed document. `TData` is the document's TypeScript shape.\n *\n * **Most apps should reach for {@link createFirestate} + {@link doc} instead**\n * — that builds a registry of every Firestore thing in one object and\n * generates typed hooks for you. `defineDocument` is the lower-level\n * escape hatch: use it when you need fully custom `collection` / `id`\n * derivation, when you're calling firestate outside React, or when a\n * registry doesn't fit your control flow.\n *\n * Two ways to use:\n *\n * 1. Plain TypeScript type (no schema, no runtime validation):\n * ```ts\n * interface Project { name: string; createdAt: number }\n *\n * const projectDoc = defineDocument<Project>({\n * collection: 'projects',\n * id: (params) => params.projectId,\n * })\n * ```\n *\n * 2. With a Zod schema — `TData` is inferred from `z.infer<S>`. Firestate\n * runs `schema.parse(...)` on full-payload writes (`set`/`add`) so bad\n * data throws at the call site. Partial `update(diff)` calls are not\n * validated (diffs frequently contain Firestore sentinels).\n * ```ts\n * import { z } from 'zod'\n *\n * const ProjectSchema = z.object({ name: z.string(), createdAt: z.number() })\n *\n * const projectDoc = defineDocument({\n * schema: ProjectSchema,\n * collection: 'projects',\n * id: (params) => params.projectId,\n * })\n * ```\n */\nexport function defineDocument<S extends ZodType<FirestoreObject>>(\n definition: Omit<DocumentDefinition<z.infer<S>>, \"schema\"> & {\n schema: S;\n }\n): DocumentDefinition<z.infer<S>>;\nexport function defineDocument<TData extends FirestoreObject>(\n definition: DocumentDefinition<TData>\n): DocumentDefinition<TData>;\nexport function defineDocument(\n definition: DocumentDefinition<FirestoreObject>\n): DocumentDefinition<FirestoreObject> {\n return definition;\n}\n\n/**\n * Define a typed collection. `TData` is the shape of each document in the\n * collection. See {@link defineDocument} for the schema/plain-type tradeoff.\n *\n * **Most apps should reach for {@link createFirestate} + {@link col} instead.**\n * `defineCollection` is the escape hatch for fully custom path derivation\n * or non-React usage.\n *\n * @example\n * ```ts\n * interface Space { name: string; area: number }\n *\n * const spacesCollection = defineCollection<Space>({\n * path: (params) => `projects/${params.projectId}/spaces`,\n * lazy: true,\n * })\n * ```\n */\nexport function defineCollection<S extends ZodType<FirestoreObject>>(\n definition: Omit<CollectionDefinition<z.infer<S>>, \"schema\"> & {\n schema: S;\n }\n): CollectionDefinition<z.infer<S>>;\nexport function defineCollection<TData extends FirestoreObject>(\n definition: CollectionDefinition<TData>\n): CollectionDefinition<TData>;\nexport function defineCollection(\n definition: CollectionDefinition<FirestoreObject>\n): CollectionDefinition<FirestoreObject> {\n return definition;\n}\n\n/**\n * Infer the document data type from a {@link DocumentDefinition}.\n */\nexport type InferDocumentData<T extends DocumentDefinition<FirestoreObject>> =\n T extends DocumentDefinition<infer D> ? D : never;\n\n/**\n * Infer the document data type (with `id` field) from a {@link DocumentDefinition}.\n */\nexport type InferDocument<T extends DocumentDefinition<FirestoreObject>> =\n InferDocumentData<T> & { id: string };\n\n/**\n * Infer the document data type from a {@link CollectionDefinition}.\n */\nexport type InferCollectionData<\n T extends CollectionDefinition<FirestoreObject>\n> = T extends CollectionDefinition<infer D> ? D : never;\n\n/**\n * Infer the document data type (with `id` field) from a {@link CollectionDefinition}.\n */\nexport type InferCollectionDocument<\n T extends CollectionDefinition<FirestoreObject>\n> = InferCollectionData<T> & { id: string };\n","import {\n deleteField,\n serverTimestamp,\n Timestamp,\n WithFieldValue,\n} from 'firebase/firestore'\nimport type { DeepPartial, FirestoreObject } from './types'\n\n/**\n * Check if a value is a plain object (not array, null, or special Firestore type)\n */\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n !(value instanceof Timestamp) &&\n Object.getPrototypeOf(value) === Object.prototype\n\n/**\n * Check if a value is a Firestore opaque type: a FieldValue sentinel\n * (`serverTimestamp`, `deleteField`, `increment`, `arrayUnion`,\n * `arrayRemove`, …) or a value type the SDK ships with its own identity\n * semantics (`Timestamp`, `DocumentReference`, `GeoPoint`, `Bytes`,\n * `VectorValue`). They all expose `.isEqual()` and have a non-plain\n * prototype.\n *\n * The diff / clone pipeline must treat these as **opaque**: never iterate\n * their keys, never substitute their values, never compare them by `===`.\n * Doing any of those silently breaks the write path — see the C1\n * regression where `serverTimestamp()` was replaced with `Timestamp.now()`\n * before reaching Firestore.\n */\nconst isFirestoreOpaque = (\n value: unknown\n): value is { isEqual: (other: unknown) => boolean } => {\n if (value === null || typeof value !== 'object') return false\n if (Object.getPrototypeOf(value) === Object.prototype) return false\n return (\n 'isEqual' in value &&\n typeof (value as { isEqual: unknown }).isEqual === 'function'\n )\n}\n\n// Reference sentinels used to identify specific FieldValue kinds. The\n// Firebase SDK does not export the sentinel subclasses; the only stable\n// way to ask \"is this a serverTimestamp / deleteField?\" is to construct a\n// reference instance once and delegate to its `.isEqual()`. Hoisting them\n// to module scope avoids reconstructing on every call.\nconst SERVER_TIMESTAMP_REF = serverTimestamp()\nconst DELETE_FIELD_REF = deleteField()\n\nconst isDeleteField = (value: unknown): boolean =>\n isFirestoreOpaque(value) && value.isEqual(DELETE_FIELD_REF)\n\nconst isServerTimestamp = (value: unknown): boolean =>\n isFirestoreOpaque(value) && value.isEqual(SERVER_TIMESTAMP_REF)\n\n/**\n * Check if two values are deeply equal\n */\nexport const isDeepEqual = (a: unknown, b: unknown): boolean => {\n if (a === b) return true\n if (a === null || b === null) return false\n if (typeof a !== typeof b) return false\n\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false\n return a.every((item, i) => isDeepEqual(item, b[i]))\n }\n\n // Opaque Firestore types delegate to their own `.isEqual`. Catches\n // Timestamp, DocumentReference, GeoPoint, Bytes, VectorValue and\n // every FieldValue sentinel kind.\n if (isFirestoreOpaque(a) && isFirestoreOpaque(b)) {\n return a.isEqual(b)\n }\n\n if (isPlainObject(a) && isPlainObject(b)) {\n const keysA = Object.keys(a)\n const keysB = Object.keys(b)\n if (keysA.length !== keysB.length) return false\n return keysA.every((key) => isDeepEqual(a[key], b[key]))\n }\n\n return false\n}\n\n/**\n * Compute the minimal diff between two objects for Firestore updates.\n * Returns only the fields that changed, using deleteField() for removed fields.\n *\n * @param from - The original object (sync state)\n * @param to - The target object (local state)\n * @returns A partial object containing only changed fields\n */\nexport const computeDiff = <T extends FirestoreObject>(\n from: T,\n to: T | undefined\n): WithFieldValue<DeepPartial<T>> => {\n if (to === undefined) {\n return deleteField() as WithFieldValue<DeepPartial<T>>\n }\n\n const diff: Record<string, unknown> = {}\n\n // Check for changed or added fields\n for (const key of Object.keys(to)) {\n const fromValue = from[key]\n const toValue = to[key]\n\n // Arrays are compared by value and replaced entirely\n if (Array.isArray(toValue)) {\n if (!isDeepEqual(fromValue, toValue)) {\n diff[key] = toValue\n }\n continue\n }\n\n // Nested objects get recursive diff\n if (isPlainObject(toValue)) {\n if (!isDeepEqual(fromValue, toValue)) {\n const nestedDiff = computeDiff(\n (fromValue as Record<string, unknown>) ?? {},\n toValue\n )\n if (Object.keys(nestedDiff).length > 0) {\n diff[key] = nestedDiff\n }\n }\n continue\n }\n\n // Firestore opaque values — sentinels (serverTimestamp,\n // arrayUnion, …) and value types (Timestamp, DocumentReference,\n // …). Compare via `.isEqual` so identical-by-value Timestamps\n // don't show up as spurious diffs every sync; pass the value\n // through unchanged so sentinels survive into the write payload\n // for the server to expand.\n if (isFirestoreOpaque(toValue)) {\n if (\n !isFirestoreOpaque(fromValue) ||\n !toValue.isEqual(fromValue)\n ) {\n diff[key] = toValue\n }\n continue\n }\n\n // Primitives are compared directly\n if (toValue !== undefined && fromValue !== toValue) {\n diff[key] = toValue\n }\n }\n\n // Check for removed fields. Only `undefined` triggers a delete — `null`\n // is a valid Firestore value and is preserved via the primitive-comparison\n // branch above.\n for (const key of Object.keys(from)) {\n if (to[key] === undefined) {\n diff[key] = deleteField()\n }\n }\n\n return diff as WithFieldValue<DeepPartial<T>>\n}\n\n/**\n * Apply a Firestore diff to a target object in place (mutating).\n * Handles deleteField(), serverTimestamp(), and nested objects.\n *\n * Most code should use `applyDiff` (immutable) instead.\n * This mutable version is useful for performance-critical paths\n * where you're already working with a cloned object.\n *\n * @param target - The object to mutate\n * @param diff - The diff to apply\n */\nexport const applyDiffMutable = (\n target: FirestoreObject,\n diff: Record<string, unknown>\n): void => {\n for (const key of Object.keys(diff)) {\n const value = (diff as Record<string, unknown>)[key]\n\n // Firestore opaque values: FieldValue sentinels and value types.\n if (isFirestoreOpaque(value)) {\n // `deleteField()` is structural — actually drop the key from\n // the local view. Matches what Firestore does on commit, and\n // what `computeDiff`'s removed-field branch round-trips back\n // out to a sentinel on the next diff.\n if (isDeleteField(value)) {\n delete (target as Record<string, unknown>)[key]\n continue\n }\n // Every other opaque value (serverTimestamp, increment,\n // arrayUnion/Remove, Timestamp, DocumentReference, GeoPoint,\n // Bytes, VectorValue, …) is preserved by reference. Sentinels\n // must reach Firestore in their original form so the server\n // can expand them; value types must keep their prototype.\n //\n // `serverTimestamp()` used to be substituted with\n // `Timestamp.now()` here, which silently shipped client clock\n // time to Firestore. The optimistic-display companion lives\n // in document.ts / collection.ts as `displayOverrides`.\n ;(target as Record<string, unknown>)[key] = value\n continue\n }\n\n // Handle nested objects\n if (isPlainObject(value)) {\n const existingValue = (target as Record<string, unknown>)[key]\n if (!isPlainObject(existingValue)) {\n ;(target as Record<string, unknown>)[key] = {}\n }\n applyDiffMutable(\n (target as Record<string, unknown>)[key] as FirestoreObject,\n value as Record<string, unknown>\n )\n continue\n }\n\n // Handle primitives and arrays\n ;(target as Record<string, unknown>)[key] = value\n }\n}\n\n/**\n * Create a deep clone of an object that's safe for Firestore operations.\n *\n * Firestore opaque values (FieldValue sentinels, Timestamp,\n * DocumentReference, GeoPoint, Bytes, VectorValue) are returned **by\n * reference**. They are immutable from the user's perspective; cloning\n * them by walking keys would either lose their prototype — turning a\n * `DocumentReference` into a plain object Firestore can't recognize —\n * or destroy a sentinel that needed to reach the server intact.\n */\nexport const deepClone = <T>(value: T): T => {\n if (value === null || typeof value !== 'object') {\n return value\n }\n\n if (isFirestoreOpaque(value)) {\n return value\n }\n\n if (Array.isArray(value)) {\n return value.map(deepClone) as T\n }\n\n const result: Record<string, unknown> = {}\n for (const key of Object.keys(value)) {\n result[key] = deepClone((value as Record<string, unknown>)[key])\n }\n return result as T\n}\n\n/**\n * Check if a diff is empty (no changes)\n */\nexport const isDiffEmpty = (diff: Record<string, unknown>): boolean =>\n Object.keys(diff).length === 0\n\n/**\n * Flatten a nested diff object to dot notation for use with Firestore's updateDoc.\n *\n * This converts:\n * ```\n * { building: { floors: 5, height: 100 }, name: 'Test' }\n * ```\n * To:\n * ```\n * { 'building.floors': 5, 'building.height': 100, 'name': 'Test' }\n * ```\n *\n * Arrays, FieldValue sentinels (deleteField, serverTimestamp, …) and\n * Firestore value types (Timestamp, DocumentReference, GeoPoint, Bytes,\n * VectorValue) are NOT flattened — they're preserved at their path so\n * Firestore receives them in their original form.\n *\n * @param diff - The nested diff object\n * @param prefix - Internal: current path prefix for recursion\n * @returns Flattened object with dotted keys\n */\nexport const flattenDiff = (\n diff: Record<string, unknown>,\n prefix = ''\n): Record<string, unknown> => {\n const result: Record<string, unknown> = {}\n\n for (const key of Object.keys(diff)) {\n const value = diff[key]\n const path = prefix ? `${prefix}.${key}` : key\n\n // Arrays, FieldValue sentinels, and Firestore value types are\n // opaque from flatten's perspective — kept at the path verbatim.\n if (Array.isArray(value) || isFirestoreOpaque(value)) {\n result[path] = value\n continue\n }\n\n // Plain objects are recursively flattened\n if (isPlainObject(value)) {\n const nested = flattenDiff(value, path)\n Object.assign(result, nested)\n continue\n }\n\n // Primitives (strings, numbers, booleans, null)\n result[path] = value\n }\n\n return result\n}\n\n/**\n * Merge two diffs together, with the second taking precedence\n */\nexport const mergeDiffs = <T extends FirestoreObject>(\n first: WithFieldValue<DeepPartial<T>>,\n second: WithFieldValue<DeepPartial<T>>\n): WithFieldValue<DeepPartial<T>> => {\n const result = deepClone(first) as Record<string, unknown>\n\n for (const key of Object.keys(second)) {\n const firstValue = result[key]\n const secondValue = (second as Record<string, unknown>)[key]\n\n if (isPlainObject(firstValue) && isPlainObject(secondValue)) {\n result[key] = mergeDiffs(\n firstValue as WithFieldValue<DeepPartial<FirestoreObject>>,\n secondValue as WithFieldValue<DeepPartial<FirestoreObject>>\n )\n } else {\n result[key] = secondValue\n }\n }\n\n return result as WithFieldValue<DeepPartial<T>>\n}\n\n/**\n * Apply a diff to an object, returning a new object.\n * The original object is not modified.\n *\n * @example\n * ```ts\n * const original = { name: 'Project', count: 5 }\n * const diff = { name: 'Updated', count: deleteField() }\n * const result = applyDiff(original, diff)\n * // result = { name: 'Updated' }\n * // original is unchanged\n * ```\n */\nexport const applyDiff = <T extends FirestoreObject>(\n state: T,\n diff: WithFieldValue<DeepPartial<T>>\n): T => {\n const result = deepClone(state)\n applyDiffMutable(result, diff as Record<string, unknown>)\n return result\n}\n\n/**\n * Compute the undo diff that would reverse the effect of applying a diff to a state.\n *\n * Given a starting state and a diff that was (or will be) applied to it,\n * returns a new diff that when applied to the result would restore the original state.\n *\n * @example\n * ```ts\n * const startState = { name: 'Foo', count: 5 }\n * const diff = { name: 'Bar', count: deleteField() }\n *\n * // Apply the diff\n * const endState = applyDiff(startState, diff)\n * // endState = { name: 'Bar' }\n *\n * // Compute the undo\n * const undoDiff = computeUndoDiff(startState, diff)\n * // undoDiff = { name: 'Foo', count: 5 }\n *\n * // Applying undoDiff to endState restores startState\n * const restored = applyDiff(endState, undoDiff)\n * // restored = { name: 'Foo', count: 5 }\n * ```\n */\nexport const computeUndoDiff = <T extends FirestoreObject>(\n startState: T,\n diff: WithFieldValue<DeepPartial<T>>\n): WithFieldValue<DeepPartial<T>> => {\n const endState = applyDiff(startState, diff)\n return computeDiff(endState, startState)\n}\n\n/**\n * Check if a diff affects a specific path (supports dot notation).\n *\n * @example\n * ```ts\n * const diff = { building: { floors: 5 }, name: 'Test' }\n *\n * diffContainsPath(diff, 'name') // true\n * diffContainsPath(diff, 'building') // true\n * diffContainsPath(diff, 'building.floors') // true\n * diffContainsPath(diff, 'building.height') // false\n * diffContainsPath(diff, 'other') // false\n * ```\n */\nexport const diffContainsPath = (\n diff: Record<string, unknown>,\n path: string\n): boolean => {\n const parts = path.split('.')\n let current: unknown = diff\n\n for (const part of parts) {\n if (current === null || typeof current !== 'object') {\n return false\n }\n if (!(part in (current as Record<string, unknown>))) {\n return false\n }\n current = (current as Record<string, unknown>)[part]\n }\n\n return true\n}\n\n/**\n * Extract the value at a specific path from a diff (supports dot notation).\n * Returns undefined if the path doesn't exist in the diff.\n *\n * @example\n * ```ts\n * const diff = { building: { floors: 5, height: 100 }, name: 'Test' }\n *\n * extractDiffValue(diff, 'name') // 'Test'\n * extractDiffValue(diff, 'building') // { floors: 5, height: 100 }\n * extractDiffValue(diff, 'building.floors') // 5\n * extractDiffValue(diff, 'building.missing') // undefined\n * ```\n */\nexport const extractDiffValue = (\n diff: Record<string, unknown>,\n path: string\n): unknown => {\n const parts = path.split('.')\n let current: unknown = diff\n\n for (const part of parts) {\n if (current === null || typeof current !== 'object') {\n return undefined\n }\n if (!(part in (current as Record<string, unknown>))) {\n return undefined\n }\n current = (current as Record<string, unknown>)[part]\n }\n\n return current\n}\n\n/**\n * Create a diff that sets a value at a specific path (supports dot notation).\n *\n * @example\n * ```ts\n * createDiffAtPath('name', 'New Name')\n * // { name: 'New Name' }\n *\n * createDiffAtPath('building.floors', 5)\n * // { building: { floors: 5 } }\n *\n * createDiffAtPath('building.config.enabled', true)\n * // { building: { config: { enabled: true } } }\n * ```\n */\nexport const createDiffAtPath = (\n path: string,\n value: unknown\n): Record<string, unknown> => {\n const parts = path.split('.')\n const result: Record<string, unknown> = {}\n\n let current = result\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]\n if (part === undefined) continue\n current[part] = {}\n current = current[part] as Record<string, unknown>\n }\n\n const lastPart = parts[parts.length - 1]\n if (lastPart !== undefined) {\n current[lastPart] = value\n }\n\n return result\n}\n\n/**\n * Invert a flattened diff back to nested object structure.\n * Opposite of flattenDiff.\n *\n * @example\n * ```ts\n * const flat = { 'building.floors': 5, 'building.height': 100, 'name': 'Test' }\n * const nested = unflattenDiff(flat)\n * // { building: { floors: 5, height: 100 }, name: 'Test' }\n * ```\n */\nexport const unflattenDiff = (\n flatDiff: Record<string, unknown>\n): Record<string, unknown> => {\n const result: Record<string, unknown> = {}\n\n for (const [path, value] of Object.entries(flatDiff)) {\n const parts = path.split('.')\n\n let current = result\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]\n if (part === undefined) continue\n if (!(part in current) || typeof current[part] !== 'object') {\n current[part] = {}\n }\n current = current[part] as Record<string, unknown>\n }\n\n const lastPart = parts[parts.length - 1]\n if (lastPart !== undefined) {\n current[lastPart] = value\n }\n }\n\n return result\n}\n\n// ---------------------------------------------------------------------------\n// Display overrides\n//\n// `serverTimestamp()` sentinels need to survive `localState` so the write\n// path can ship them to Firestore for server-side expansion (C1). That\n// leaves the optimistic UI with a `FieldValue` object sitting at the\n// field — components can't render it. The display-override layer\n// captures `Timestamp.now()` at the moment a sentinel first enters\n// `localState`, stores it keyed by dotted path, and substitutes it into\n// the merged view at read time. The captured Timestamp is frozen for the\n// lifetime of the sentinel (it doesn't drift forward on re-renders), and\n// the entry is dropped automatically once the sentinel leaves\n// `localState` — either because the server ack cleared `localState`, or\n// because the user overwrote that path with an explicit value.\n//\n// Scope: only `serverTimestamp()` gets a display override. `increment`,\n// `arrayUnion`, and `arrayRemove` would need access to the SDK's\n// non-public internal fields (`_operand`, `_elements`) to compute a\n// display value; consumers using those should gate their render code on\n// the field not being a sentinel.\n// ---------------------------------------------------------------------------\n\n/**\n * Walk a state object collecting every dotted path that currently holds\n * a `serverTimestamp()` sentinel. Arrays are not traversed — Firestore\n * doesn't allow sentinels inside arrays. Non-plain objects (Timestamps,\n * DocumentReferences, …) are leaves.\n *\n * @internal\n */\nexport const collectServerTimestampPaths = (\n state: Record<string, unknown> | null | undefined,\n prefix = '',\n out: Set<string> = new Set()\n): Set<string> => {\n if (!state) return out\n for (const key of Object.keys(state)) {\n const value = state[key]\n const path = prefix ? `${prefix}.${key}` : key\n if (isServerTimestamp(value)) {\n out.add(path)\n continue\n }\n if (isPlainObject(value)) {\n collectServerTimestampPaths(value, path, out)\n }\n }\n return out\n}\n\n/**\n * Reconcile a `displayOverrides` map against the current `localState`:\n *\n * - For each path that holds a `serverTimestamp()` sentinel but has no\n * override yet, capture `Timestamp.now()` and store it (frozen-at-\n * first-sighting).\n * - For each existing override whose path no longer holds a sentinel\n * (sentinel was overwritten, or `localState` cleared on snapshot ack),\n * drop it.\n *\n * The map is mutated in place. Pass a custom `now` for deterministic\n * tests; defaults to `Timestamp.now()`.\n *\n * @internal\n */\nexport const reconcileDisplayOverrides = (\n localState: Record<string, unknown> | null | undefined,\n overrides: Map<string, unknown>,\n now: () => unknown = () => Timestamp.now()\n): void => {\n const currentPaths = collectServerTimestampPaths(localState)\n for (const path of currentPaths) {\n if (!overrides.has(path)) {\n overrides.set(path, now())\n }\n }\n for (const path of [...overrides.keys()]) {\n if (!currentPaths.has(path)) {\n overrides.delete(path)\n }\n }\n}\n\nconst setAtPath = (\n obj: Record<string, unknown>,\n path: string,\n value: unknown\n): void => {\n const parts = path.split('.')\n let cur = obj\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]!\n if (!isPlainObject(cur[part])) cur[part] = {}\n cur = cur[part] as Record<string, unknown>\n }\n cur[parts[parts.length - 1]!] = value\n}\n\n/**\n * Apply a path → value override map to a merged view, returning a new\n * object. Used by document.ts / collection.ts to substitute display\n * values for sentinels still present in `localState`.\n *\n * @internal\n */\nexport const applyOverridesAtPaths = <T extends FirestoreObject>(\n merged: T,\n overrides: ReadonlyMap<string, unknown>\n): T => {\n if (overrides.size === 0) return merged\n const result = deepClone(merged) as Record<string, unknown>\n for (const [path, value] of overrides) {\n setAtPath(result, path, value)\n }\n return result as T\n}\n","import {\n doc,\n collection,\n onSnapshot,\n setDoc,\n updateDoc,\n deleteDoc,\n DocumentReference,\n WithFieldValue,\n} from 'firebase/firestore'\nimport type {\n DeepPartial,\n DocumentDefinition,\n DocumentHandle,\n DocumentState,\n FirestoreObject,\n Subscriber,\n Unsubscribe,\n UpdateOptions,\n} from './types'\nimport type { FirestateStore } from './store'\nimport {\n applyDiffMutable,\n applyOverridesAtPaths,\n computeDiff,\n deepClone,\n flattenDiff,\n isDeepEqual,\n reconcileDisplayOverrides,\n} from './diff'\n\n// Module-level counter so each subscription instance gets a unique sync key,\n// even when multiple instances target the same document path.\nlet syncKeyCounter = 0\n\n/**\n * Options for creating a document subscription\n */\nexport interface DocumentOptions<TData extends FirestoreObject> {\n /** The store instance */\n store: FirestateStore\n /** Document definition from defineDocument() */\n definition: DocumentDefinition<TData>\n /**\n * Resolved document id. If omitted and `definition.id` is a string, that\n * value is used. If `definition.id` is a function, this option is required.\n */\n docId?: string\n /**\n * Resolved collection path. If omitted and `definition.collection` is a\n * string, that value is used. If `definition.collection` is a function,\n * this option is required.\n */\n collectionPath?: string\n /** Override read-only setting */\n readOnly?: boolean\n /** Callback for pushing undo actions */\n onPushUndo?: (\n undoAction: () => void,\n redoAction: () => void,\n options?: UpdateOptions\n ) => void\n}\n\n/**\n * Internal state for a document subscription.\n *\n * `localState` uses three distinct values:\n * - `undefined`: no pending local changes\n * - `null`: pending delete (the autosave-driven sync will issue deleteDoc)\n * - object: pending update/set (synced via updateDoc/setDoc)\n *\n * The same convention applies to `inflightLocalState`.\n */\ninterface DocumentInternalState<T extends FirestoreObject> {\n syncState: T | undefined\n localState: T | null | undefined\n isLoading: boolean\n error: Error | undefined\n waitingForUpdate: boolean\n inflightLocalState: T | null | undefined\n /** Whether the pending operation is a full set (create/replace) vs a partial update */\n isSetOperation: boolean\n /**\n * Frozen display values for `serverTimestamp()` sentinels currently\n * sitting in `localState`. Keyed by dotted path. Captured at the\n * moment a sentinel first appears, dropped when the sentinel leaves\n * `localState` (sync ack or overwrite). Substituted into the merged\n * view by `getMergedData` so consumers always see a renderable\n * `Timestamp`, never a raw FieldValue, while the write is in flight.\n */\n displayOverrides: Map<string, unknown>\n}\n\n/**\n * Create a document subscription.\n * This is a low-level API - prefer using useDocument hook in React.\n *\n * @example\n * ```ts\n * const subscription = createDocumentSubscription({\n * store,\n * definition: projectDoc,\n * docId: '123',\n * })\n *\n * const unsubscribe = subscription.subscribe((state) => {\n * console.log('Document state:', state)\n * })\n *\n * subscription.load()\n * ```\n */\nexport const createDocumentSubscription = <TData extends FirestoreObject>(\n options: DocumentOptions<TData>\n): {\n /** Attach the Firestore listener */\n load: () => void\n /** Stop the Firestore listener */\n stop: () => void\n /** Subscribe to state changes */\n subscribe: (fn: Subscriber<DocumentState<TData>>) => Unsubscribe\n /** Get current state */\n getState: () => DocumentState<TData>\n /** Get document handle for updates */\n getHandle: () => DocumentHandle<TData>\n /** Force sync now */\n sync: () => Promise<void>\n} => {\n const { store, definition, docId, collectionPath: resolvedCollectionPath, readOnly, onPushUndo } = options\n const { firestore, autosave: defaultAutosave, minLoadTime: defaultMinLoadTime } = store\n\n const {\n collection: collectionConfig,\n id,\n autosave = defaultAutosave,\n minLoadTime = defaultMinLoadTime,\n readOnly: definitionReadOnly,\n retryOnError = false,\n retryInterval = 5000,\n schema,\n } = definition\n\n const isReadOnly = readOnly ?? definitionReadOnly ?? false\n // Prefer the caller-resolved docId. Fall back to a string `definition.id`\n // for ergonomic direct use; if both are missing, the caller forgot to\n // resolve a function id and we surface that immediately.\n const documentId = docId ?? (typeof id === 'string' ? id : undefined)\n if (documentId === undefined) {\n throw new Error(\n `createDocumentSubscription: definition.id is a function; pass a resolved docId in options.`\n )\n }\n // Same shape as docId: prefer a caller-resolved path, fall back to a\n // string `definition.collection` for direct use. Function definitions\n // must come pre-resolved from the hook layer.\n const collectionPath = resolvedCollectionPath ?? (typeof collectionConfig === 'string' ? collectionConfig : undefined)\n if (collectionPath === undefined) {\n throw new Error(\n `createDocumentSubscription: definition.collection is a function; pass a resolved collectionPath in options.`\n )\n }\n\n // Create document reference\n const docRef = doc(\n collection(firestore, collectionPath),\n documentId\n ) as DocumentReference<TData>\n\n // Internal state\n const state: DocumentInternalState<TData> = {\n syncState: undefined,\n localState: undefined,\n isLoading: true,\n error: undefined,\n waitingForUpdate: false,\n inflightLocalState: undefined,\n isSetOperation: false,\n displayOverrides: new Map(),\n }\n\n const subscribers = new Set<Subscriber<DocumentState<TData>>>()\n let unsubscribeListener: Unsubscribe | null = null\n let autosaveTimeout: ReturnType<typeof setTimeout> | null = null\n let retryTimeout: ReturnType<typeof setTimeout> | null = null\n let minLoadTimeout: ReturnType<typeof setTimeout> | null = null\n let minLoadTimeElapsed = false\n let loaded = false\n // Cached handle — returns the same reference until notify() invalidates\n // it. Lets useSyncExternalStore consumers rely on handle identity.\n let cachedHandle: DocumentHandle<TData> | null = null\n\n // Unique key for sync tracking, scoped per-instance so multiple\n // subscriptions to the same path don't share (or clobber) one entry.\n const syncKey = `doc:${collectionPath}/${documentId}#${++syncKeyCounter}`\n\n const getMergedData = (): TData | undefined => {\n // null localState marks a pending delete — surface as no data.\n if (state.localState === null) return undefined\n const base = state.localState ?? state.syncState\n if (base === undefined) return undefined\n return applyOverridesAtPaths(base, state.displayOverrides)\n }\n\n const getPublicState = (): DocumentState<TData> => ({\n data: getMergedData(),\n isLoading: state.isLoading,\n isSynced: state.localState === undefined,\n error: state.error,\n })\n\n const notify = () => {\n // Reconcile display overrides against the current localState\n // before publishing — captures Timestamp.now() for any newly\n // arrived serverTimestamp() sentinel and drops entries whose\n // sentinels have been overwritten or acked away.\n reconcileDisplayOverrides(\n state.localState && typeof state.localState === 'object'\n ? (state.localState as Record<string, unknown>)\n : undefined,\n state.displayOverrides\n )\n cachedHandle = null\n const publicState = getPublicState()\n subscribers.forEach((fn) => fn(publicState))\n store.reportSyncState(syncKey, publicState.isSynced)\n }\n\n const updateState = (\n diff: WithFieldValue<DeepPartial<TData>>,\n undoOptions: UpdateOptions = {}\n ) => {\n if (isReadOnly) return\n\n const currentData = getMergedData()\n if (!currentData) {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n `[firestate] update() on ${collectionPath}/${documentId} was ignored: there is no current data to diff against. ` +\n `This happens when the document is still loading, has been deleted, or doesn't exist yet. ` +\n `Use set() to create the document, or gate update calls on a non-undefined data value.`\n )\n }\n return\n }\n\n const newLocalState = deepClone(currentData)\n applyDiffMutable(newLocalState, diff as Record<string, unknown>)\n\n // Push undo eagerly against the pre-mutation state. Cmd+Z within the\n // autosave window pops this entry, applies the inverse via updateState,\n // and the sync() no-op shortcut absorbs the resulting same-as-syncState\n // case without a Firestore write.\n if (undoOptions?.undoable !== false && onPushUndo) {\n const undoDiff = computeDiff(\n newLocalState as FirestoreObject,\n currentData as FirestoreObject\n )\n const redoDiff = computeDiff(\n currentData as FirestoreObject,\n newLocalState as FirestoreObject\n )\n onPushUndo(\n () => updateState(undoDiff as WithFieldValue<DeepPartial<TData>>, { undoable: false }),\n () => updateState(redoDiff as WithFieldValue<DeepPartial<TData>>, { undoable: false }),\n undoOptions\n )\n }\n\n state.localState = newLocalState\n state.isSetOperation = false\n\n notify()\n scheduleAutosave()\n }\n\n const setData = (data: TData, undoOptions: UpdateOptions = {}) => {\n if (isReadOnly) return\n\n // Use schema.parse as a validation guard — throw on bad input — but\n // discard the parsed result and store the caller's original object.\n // Storing parsed output would (a) silently drop unknown keys via\n // Zod's default `.strip()` behavior, and (b) cause undo/redo replay\n // through this same path to re-apply any schema transforms a second\n // time. Partial update() diffs are intentionally NOT validated:\n // diffs commonly carry Firestore sentinels (serverTimestamp,\n // arrayUnion, deleteField) that aren't representable in a strict\n // schema.\n if (schema) schema.parse(data)\n\n const currentData = getMergedData()\n\n // Push undo eagerly. A set against undefined data is a creation;\n // its undo is a delete. Otherwise we restore the prior snapshot via\n // setData, which is symmetric and handles full-replace semantics\n // (including field removals) correctly.\n if (undoOptions?.undoable !== false && onPushUndo) {\n const dataForRedo = deepClone(data)\n if (currentData === undefined) {\n onPushUndo(\n () => deleteDocument({ undoable: false }),\n () => setData(dataForRedo, { undoable: false }),\n undoOptions\n )\n } else {\n const dataToRestore = deepClone(currentData)\n onPushUndo(\n () => setData(dataToRestore, { undoable: false }),\n () => setData(dataForRedo, { undoable: false }),\n undoOptions\n )\n }\n }\n\n state.localState = deepClone(data)\n state.isSetOperation = true\n\n notify()\n scheduleAutosave()\n }\n\n const deleteDocument = (undoOptions: UpdateOptions = {}) => {\n if (isReadOnly) return\n\n const currentData = getMergedData()\n // Nothing to delete — bail rather than queueing a no-op deleteDoc.\n if (currentData === undefined) return\n\n // Push undo against the pre-delete data (which includes any pending\n // local edits at this moment).\n if (undoOptions?.undoable !== false && onPushUndo) {\n const dataToRestore = deepClone(currentData)\n onPushUndo(\n () => setData(dataToRestore, { undoable: false }),\n () => deleteDocument({ undoable: false }),\n undoOptions\n )\n }\n\n // Mark localState as a pending delete and let scheduleAutosave drive\n // the actual deleteDoc call — same flow as set/update.\n state.localState = null\n state.isSetOperation = false\n\n notify()\n scheduleAutosave()\n }\n\n const scheduleAutosave = () => {\n if (autosaveTimeout) {\n clearTimeout(autosaveTimeout)\n }\n if (autosave > 0) {\n autosaveTimeout = setTimeout(() => {\n sync()\n }, autosave)\n }\n }\n\n const sync = async () => {\n if (state.localState === undefined) return\n\n // Pending delete — issue deleteDoc and let the listener confirm via\n // handleMissingDocument. Undo was already pushed at mutation time.\n if (state.localState === null) {\n state.inflightLocalState = null\n state.waitingForUpdate = true\n\n try {\n await deleteDoc(docRef)\n } catch (error) {\n console.error('Sync failed:', error)\n state.waitingForUpdate = false\n state.inflightLocalState = undefined\n state.error = error as Error\n store.reportError(error as Error, {\n type: 'document',\n path: `${collectionPath}/${documentId}`,\n operation: 'write',\n })\n notify()\n }\n return\n }\n\n // No-op if local state already matches what the server holds. This is\n // the path that an undo-of-pending-local takes: the inverse update\n // brings localState back to syncState, and we exit without a write.\n if (state.syncState && isDeepEqual(state.localState, state.syncState)) {\n state.localState = undefined\n state.inflightLocalState = undefined\n notify()\n return\n }\n\n const wasSetOperation = state.isSetOperation\n state.isSetOperation = false\n\n // A creation occurs when there's no server state to diff against —\n // either the user explicitly called set() to create the document, or\n // the listener has reported the doc as missing. In both cases we use\n // setDoc.\n const isCreation = !state.syncState\n const useSetDoc = wasSetOperation || isCreation\n\n const diff = state.syncState\n ? computeDiff(state.syncState, state.localState)\n : undefined\n\n state.inflightLocalState = deepClone(state.localState)\n\n state.waitingForUpdate = true\n\n try {\n if (useSetDoc) {\n // Full set / creation — use setDoc to create or completely\n // replace the document.\n await setDoc(docRef, state.localState as TData)\n } else {\n // Partial update - use updateDoc with flattened diff to prevent\n // accidentally recreating deleted documents. updateDoc will fail\n // if the document doesn't exist.\n const flatDiff = flattenDiff(diff as Record<string, unknown>)\n await updateDoc(docRef, flatDiff)\n }\n } catch (error) {\n console.error('Sync failed:', error)\n state.waitingForUpdate = false\n state.inflightLocalState = undefined\n // Surface to React: handle.error reflects the failure and the\n // listener will keep state.localState so consumers can retry by\n // calling sync() or by issuing another update. Autosave is not\n // automatically rescheduled to avoid retry loops on permission\n // errors — that policy is left to the consumer.\n state.error = error as Error\n store.reportError(error as Error, {\n type: 'document',\n path: `${collectionPath}/${documentId}`,\n operation: 'write',\n })\n notify()\n }\n }\n\n const handleSnapshot = (newSyncData: TData) => {\n state.syncState = newSyncData\n // A successful snapshot supersedes any previous read or write error.\n state.error = undefined\n\n if (state.waitingForUpdate) {\n state.waitingForUpdate = false\n const inflightState = state.inflightLocalState\n state.inflightLocalState = undefined\n const currentLocal = state.localState\n\n if (inflightState === null) {\n // Inflight was a delete but the listener fired with the doc\n // still present. The deleteDoc result is still in flight (or\n // failed and reverted). Leave localState alone — it's either\n // still null (we still want the delete) or non-null (user\n // changed their mind), and either way the next sync handles it.\n } else if (currentLocal === null) {\n // User issued a delete while a set/update was inflight. The\n // pending delete is the latest intent; preserve it for the\n // next sync.\n } else if (\n inflightState &&\n currentLocal &&\n !isDeepEqual(currentLocal, inflightState)\n ) {\n // Rebase local changes onto the new sync state.\n const changesSinceInflight = computeDiff(inflightState, currentLocal)\n const rebasedLocalState = deepClone(newSyncData)\n applyDiffMutable(rebasedLocalState, changesSinceInflight as Record<string, unknown>)\n state.localState = rebasedLocalState\n } else {\n state.localState = undefined\n }\n }\n\n if (minLoadTimeElapsed) {\n state.isLoading = false\n }\n loaded = true\n\n // If local edits exist and aren't currently being synced, schedule an\n // autosave. Covers the case where set() ran before the first snapshot\n // arrived and the initial sync attempt bailed early.\n if (state.localState !== undefined) {\n scheduleAutosave()\n }\n\n notify()\n }\n\n // A document that does not exist is not an error condition — consumers\n // commonly use that state to render a \"create\" UI. Clear loading and\n // leave error undefined; data will be undefined via getMergedData().\n const handleMissingDocument = () => {\n state.syncState = undefined\n state.error = undefined\n\n // The only localState that should clear when the doc goes missing is\n // our own pending-delete marker. Any other pending edits (object\n // value) represent the user's intent to recreate the doc — the next\n // sync() will issue a setDoc against the now-missing doc and create\n // it from scratch.\n if (state.localState === null) {\n state.localState = undefined\n state.isSetOperation = false\n if (autosaveTimeout) {\n clearTimeout(autosaveTimeout)\n autosaveTimeout = null\n }\n }\n\n if (state.waitingForUpdate) {\n state.waitingForUpdate = false\n state.inflightLocalState = undefined\n }\n if (minLoadTimeElapsed) {\n state.isLoading = false\n }\n loaded = true\n notify()\n }\n\n const handleError = (error: Error) => {\n if (retryOnError) {\n console.warn('Document listener error, retrying:', error)\n retryTimeout = setTimeout(() => {\n stop()\n load()\n }, retryInterval)\n } else {\n state.error = error\n // Don't leave consumers stuck on a loading spinner — the listener\n // has reported a terminal error, so loading is done.\n state.isLoading = false\n loaded = true\n store.reportError(error, {\n type: 'document',\n path: `${collectionPath}/${documentId}`,\n operation: 'read',\n })\n notify()\n }\n }\n\n const load = () => {\n if (unsubscribeListener) return\n\n loaded = false\n minLoadTimeElapsed = false\n\n unsubscribeListener = onSnapshot(\n docRef,\n (snapshot) => {\n if (snapshot.exists()) {\n handleSnapshot(snapshot.data())\n } else if (!snapshot.metadata.fromCache) {\n handleMissingDocument()\n }\n },\n handleError\n )\n\n // Min load time handler — tracked so stop() can cancel it.\n minLoadTimeout = setTimeout(() => {\n minLoadTimeout = null\n if (loaded) {\n state.isLoading = false\n notify()\n }\n minLoadTimeElapsed = true\n }, minLoadTime)\n }\n\n const stop = () => {\n if (unsubscribeListener) {\n unsubscribeListener()\n unsubscribeListener = null\n }\n if (autosaveTimeout) {\n clearTimeout(autosaveTimeout)\n autosaveTimeout = null\n }\n if (retryTimeout) {\n clearTimeout(retryTimeout)\n retryTimeout = null\n }\n if (minLoadTimeout) {\n clearTimeout(minLoadTimeout)\n minLoadTimeout = null\n }\n // Drop this subscription's entry from the global sync-state map so\n // an unmounted hook does not leave useIsSynced stuck at false.\n store.unregisterSyncState(syncKey)\n }\n\n const subscribe = (fn: Subscriber<DocumentState<TData>>): Unsubscribe => {\n subscribers.add(fn)\n return () => subscribers.delete(fn)\n }\n\n const buildHandle = (): DocumentHandle<TData> => ({\n data: getMergedData(),\n update: updateState,\n set: setData,\n delete: deleteDocument,\n isLoading: state.isLoading,\n isSynced: state.localState === undefined,\n sync,\n error: state.error,\n ref: docRef,\n })\n\n const getHandle = (): DocumentHandle<TData> => {\n if (cachedHandle === null) {\n cachedHandle = buildHandle()\n }\n return cachedHandle\n }\n\n return {\n load,\n stop,\n subscribe,\n getState: getPublicState,\n getHandle,\n sync,\n }\n}\n","import {\n collection,\n doc,\n onSnapshot,\n query,\n writeBatch,\n deleteField,\n WithFieldValue,\n QueryConstraint,\n type CollectionReference,\n} from 'firebase/firestore'\nimport type {\n CollectionDefinition,\n CollectionHandle,\n CollectionState,\n DeepPartial,\n FirestoreObject,\n Subscriber,\n Unsubscribe,\n UpdateOptions,\n} from './types'\nimport type { FirestateStore } from './store'\nimport {\n applyDiffMutable,\n applyOverridesAtPaths,\n computeDiff,\n deepClone,\n flattenDiff,\n isDeepEqual,\n reconcileDisplayOverrides,\n} from './diff'\n\n// Module-level counter so each subscription instance gets a unique sync key,\n// even when multiple instances target the same collection path.\nlet syncKeyCounter = 0\n\n/**\n * Options for creating a collection subscription\n */\nexport interface CollectionOptions<TData extends FirestoreObject> {\n /** The store instance */\n store: FirestateStore\n /** Collection definition from defineCollection() */\n definition: CollectionDefinition<TData>\n /**\n * Resolved collection path. If omitted and `definition.path` is a string,\n * that value is used. If `definition.path` is a function, this option is\n * required.\n */\n collectionPath?: string\n /** Override read-only setting */\n readOnly?: boolean\n /** Additional query constraints */\n queryConstraints?: QueryConstraint[]\n /** Callback for pushing undo actions */\n onPushUndo?: (\n undoAction: () => void,\n redoAction: () => void,\n options?: UpdateOptions\n ) => void\n}\n\n/**\n * Internal state for a collection subscription\n */\ninterface CollectionInternalState<T extends FirestoreObject> {\n syncState: Record<string, T> | undefined\n localState: Record<string, T> | undefined\n isLoading: boolean\n isActive: boolean\n error: Error | undefined\n waitingForUpdate: boolean\n inflightLocalState: Record<string, T> | undefined\n /**\n * Frozen display values for `serverTimestamp()` sentinels currently\n * sitting in `localState`. Keyed by dotted path (e.g.\n * `\"<docId>.updatedAt\"`). See document.ts for the full contract.\n */\n displayOverrides: Map<string, unknown>\n}\n\n/**\n * Create a collection subscription.\n * This is a low-level API - prefer using useCollection hook in React.\n *\n * @example\n * ```ts\n * const subscription = createCollectionSubscription({\n * store,\n * definition: spacesCollection,\n * collectionPath: 'projects/123/spaces',\n * })\n *\n * const unsubscribe = subscription.subscribe((state) => {\n * console.log('Collection state:', state)\n * })\n *\n * subscription.load() // For lazy collections\n * ```\n */\nexport const createCollectionSubscription = <TData extends FirestoreObject>(\n options: CollectionOptions<TData>\n): {\n /** Activate the subscription (for lazy loading) */\n load: () => void\n /** Stop the Firestore listener */\n stop: () => void\n /** Subscribe to state changes */\n subscribe: (fn: Subscriber<CollectionState<TData>>) => Unsubscribe\n /** Get current state */\n getState: () => CollectionState<TData>\n /** Get collection handle for updates */\n getHandle: () => CollectionHandle<TData>\n /** Force sync now */\n sync: () => Promise<void>\n} => {\n const { store, definition, collectionPath: resolvedPath, readOnly, queryConstraints: extraConstraints, onPushUndo } = options\n const { firestore, autosave: defaultAutosave, minLoadTime: defaultMinLoadTime } = store\n\n const {\n path,\n autosave = defaultAutosave,\n minLoadTime = defaultMinLoadTime,\n readOnly: definitionReadOnly,\n lazy = false,\n queryConstraints: definitionConstraints,\n retryOnError = false,\n retryInterval = 5000,\n schema,\n } = definition\n\n const isReadOnly = readOnly ?? definitionReadOnly ?? false\n // Prefer the caller-resolved path. Fall back to a string `definition.path`\n // for ergonomic direct use; if both are missing, the caller forgot to\n // resolve a function path.\n const collectionPath = resolvedPath ?? (typeof path === 'string' ? path : undefined)\n if (collectionPath === undefined) {\n throw new Error(\n `createCollectionSubscription: definition.path is a function; pass a resolved collectionPath in options.`\n )\n }\n const allConstraints = [...(definitionConstraints ?? []), ...(extraConstraints ?? [])]\n\n // Create collection reference\n const collectionRef = collection(firestore, collectionPath) as CollectionReference<TData>\n\n // Internal state\n const state: CollectionInternalState<TData> = {\n syncState: undefined,\n localState: undefined,\n isLoading: !lazy,\n isActive: !lazy,\n error: undefined,\n waitingForUpdate: false,\n inflightLocalState: undefined,\n displayOverrides: new Map(),\n }\n\n const subscribers = new Set<Subscriber<CollectionState<TData>>>()\n let unsubscribeListener: Unsubscribe | null = null\n let autosaveTimeout: ReturnType<typeof setTimeout> | null = null\n let minLoadTimeout: ReturnType<typeof setTimeout> | null = null\n let retryTimeout: ReturnType<typeof setTimeout> | null = null\n let minLoadTimeElapsed = false\n let loaded = false\n // Cached handle — returns the same reference until notify() invalidates\n // it. Lets useSyncExternalStore consumers rely on handle identity.\n let cachedHandle: CollectionHandle<TData> | null = null\n\n // Unique key for sync tracking, scoped per-instance so multiple\n // subscriptions to the same path don't share (or clobber) one entry.\n const syncKey = `col:${collectionPath}#${++syncKeyCounter}`\n\n const getMergedData = (): Record<string, TData> => {\n const base = state.localState ?? state.syncState ?? {}\n return applyOverridesAtPaths(base, state.displayOverrides)\n }\n\n const getPublicState = (): CollectionState<TData> => ({\n data: getMergedData(),\n isLoading: state.isLoading,\n isSynced: state.localState === undefined,\n isActive: state.isActive,\n error: state.error,\n })\n\n const notify = () => {\n // Reconcile display overrides against the current localState\n // before publishing — see document.ts for the full contract.\n reconcileDisplayOverrides(\n state.localState as Record<string, unknown> | undefined,\n state.displayOverrides\n )\n cachedHandle = null\n const publicState = getPublicState()\n subscribers.forEach((fn) => fn(publicState))\n store.reportSyncState(syncKey, publicState.isSynced)\n }\n\n // Pre-snapshot mutations are unsafe because computing a partial-update\n // local state without knowing the existing server fields would cause the\n // subsequent diff to mark unrelated fields as deleted. Document mutations\n // bail the same way when there's no current data.\n const warnNoSnapshot = (method: string) => {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n `[firestate] ${method}() on ${collectionPath} was ignored: the first snapshot has not arrived yet. ` +\n `Gate calls on the collection's isLoading/isActive state, or await the first data before mutating.`\n )\n }\n }\n\n const updateState = (\n diff: WithFieldValue<DeepPartial<Record<string, TData>>>,\n undoOptions: UpdateOptions = {}\n ) => {\n if (isReadOnly) return\n if (state.syncState === undefined) {\n warnNoSnapshot('update')\n return\n }\n\n const currentData = getMergedData()\n const newLocalState = deepClone(currentData)\n applyDiffMutable(newLocalState, diff as Record<string, unknown>)\n\n // Ensure each document has its id\n for (const [docId, docData] of Object.entries(newLocalState)) {\n if (docData && typeof docData === 'object') {\n ;(docData as Record<string, unknown>).id = docId\n }\n }\n\n // Push undo eagerly against the pre-mutation snapshot. Cmd+Z within\n // the autosave window pops this entry, applies the inverse via\n // updateState, and the sync() no-op shortcut absorbs the resulting\n // same-as-syncState case without a Firestore write.\n if (undoOptions?.undoable !== false && onPushUndo) {\n const undoDiff = computeDiff(\n newLocalState as FirestoreObject,\n currentData as FirestoreObject\n )\n const redoDiff = computeDiff(\n currentData as FirestoreObject,\n newLocalState as FirestoreObject\n )\n onPushUndo(\n () => updateState(undoDiff as WithFieldValue<DeepPartial<Record<string, TData>>>, { undoable: false }),\n () => updateState(redoDiff as WithFieldValue<DeepPartial<Record<string, TData>>>, { undoable: false }),\n undoOptions\n )\n }\n\n state.localState = newLocalState\n\n notify()\n scheduleAutosave()\n }\n\n // Overloaded: callers can pass (id, data, opts) or (data, opts). The\n // no-id form generates a Firestore auto-id via doc(collectionRef).id and\n // returns it so the caller can reference the new doc immediately.\n // Returns undefined when the mutation is dropped so callers can't\n // accidentally route on or persist an id that was never queued.\n function addDocument(\n id: string,\n data: Omit<TData, 'id'>,\n undoOptions?: UpdateOptions\n ): string | undefined\n function addDocument(\n data: Omit<TData, 'id'>,\n undoOptions?: UpdateOptions\n ): string | undefined\n function addDocument(\n idOrData: string | Omit<TData, 'id'>,\n dataOrOptions?: Omit<TData, 'id'> | UpdateOptions,\n maybeUndoOptions?: UpdateOptions\n ): string | undefined {\n const hasExplicitId = typeof idOrData === 'string'\n const data = (hasExplicitId ? dataOrOptions : idOrData) as Omit<TData, 'id'>\n const undoOptions = (hasExplicitId\n ? maybeUndoOptions\n : (dataOrOptions as UpdateOptions | undefined)) ?? {}\n\n if (isReadOnly) return undefined\n if (state.syncState === undefined) {\n // Bail rather than queueing: an explicit id that happens to exist\n // on the server would round-trip through computeDiff and clobber\n // any remote-only fields, and we have no way to know without a\n // first snapshot.\n warnNoSnapshot('add')\n return undefined\n }\n\n // Only allocate an auto-id once we know we're going to queue the\n // write — otherwise the caller might persist an id that was dropped.\n const id = hasExplicitId ? (idOrData as string) : doc(collectionRef).id\n\n // Use schema.parse as a validation guard — throw on bad input — but\n // discard the parsed result and store the caller's original object\n // with id attached. Storing parsed output would silently drop\n // unknown keys via Zod's default `.strip()` and re-transform on\n // undo/redo replay. We feed `{ ...data, id }` to parse so the same\n // validation works whether the user's schema declares `id` or not.\n const newDoc = { ...data, id } as unknown as TData\n if (schema) schema.parse(newDoc)\n\n const currentData = getMergedData()\n const newLocalState = deepClone(currentData)\n newLocalState[id] = newDoc\n\n // Push undo eagerly. Inverse diff deletes the just-added doc.\n if (undoOptions?.undoable !== false && onPushUndo) {\n const undoDiff = computeDiff(\n newLocalState as FirestoreObject,\n currentData as FirestoreObject\n )\n const redoDiff = computeDiff(\n currentData as FirestoreObject,\n newLocalState as FirestoreObject\n )\n onPushUndo(\n () => updateState(undoDiff as WithFieldValue<DeepPartial<Record<string, TData>>>, { undoable: false }),\n () => updateState(redoDiff as WithFieldValue<DeepPartial<Record<string, TData>>>, { undoable: false }),\n undoOptions\n )\n }\n\n state.localState = newLocalState\n\n notify()\n scheduleAutosave()\n\n return id\n }\n\n const removeDocument = (id: string, undoOptions: UpdateOptions = {}) => {\n if (isReadOnly) return\n if (state.syncState === undefined) {\n warnNoSnapshot('remove')\n return\n }\n\n const currentData = getMergedData()\n if (!(id in currentData)) return\n\n const newLocalState = deepClone(currentData)\n delete newLocalState[id]\n\n // Push undo eagerly. Inverse diff re-adds the removed doc.\n if (undoOptions?.undoable !== false && onPushUndo) {\n const undoDiff = computeDiff(\n newLocalState as FirestoreObject,\n currentData as FirestoreObject\n )\n const redoDiff = computeDiff(\n currentData as FirestoreObject,\n newLocalState as FirestoreObject\n )\n onPushUndo(\n () => updateState(undoDiff as WithFieldValue<DeepPartial<Record<string, TData>>>, { undoable: false }),\n () => updateState(redoDiff as WithFieldValue<DeepPartial<Record<string, TData>>>, { undoable: false }),\n undoOptions\n )\n }\n\n state.localState = newLocalState\n\n notify()\n scheduleAutosave()\n }\n\n const scheduleAutosave = () => {\n if (autosaveTimeout) {\n clearTimeout(autosaveTimeout)\n }\n if (autosave > 0) {\n autosaveTimeout = setTimeout(() => {\n sync()\n }, autosave)\n }\n }\n\n const sync = async () => {\n if (!state.localState) return\n // syncState is guaranteed defined here: every mutation that can set\n // localState bails when syncState is undefined. This guard is purely\n // defensive against a direct sync() call after stop().\n if (state.syncState === undefined) return\n\n const syncState = state.syncState\n\n if (isDeepEqual(state.localState, syncState)) {\n state.localState = undefined\n state.inflightLocalState = undefined\n notify()\n return\n }\n\n const diff = computeDiff(\n syncState as FirestoreObject,\n state.localState as FirestoreObject\n )\n state.inflightLocalState = deepClone(state.localState)\n\n state.waitingForUpdate = true\n\n try {\n const batch = writeBatch(firestore)\n const deleteFieldSentinel = deleteField()\n\n for (const [docId, docDiff] of Object.entries(diff)) {\n const docRef = doc(collectionRef, docId)\n\n // Check if this is a delete operation\n if (\n docDiff !== null &&\n typeof docDiff === 'object' &&\n 'isEqual' in docDiff &&\n typeof docDiff.isEqual === 'function' &&\n (docDiff as { isEqual: (v: unknown) => boolean }).isEqual(deleteFieldSentinel)\n ) {\n batch.delete(docRef)\n } else if (!(docId in syncState)) {\n // New document - use set to create it\n batch.set(docRef, docDiff as Record<string, unknown>)\n } else {\n // Existing document - use update with flattened diff to prevent\n // accidentally recreating deleted documents\n const flatDiff = flattenDiff(docDiff as Record<string, unknown>)\n batch.update(docRef, flatDiff)\n }\n }\n\n await batch.commit()\n } catch (error) {\n console.error('Collection sync failed:', error)\n state.waitingForUpdate = false\n state.inflightLocalState = undefined\n // Surface to React: handle.error reflects the failure and the\n // listener will keep state.localState so consumers can retry by\n // calling sync(). Autosave is not automatically rescheduled to\n // avoid retry loops on permission errors.\n state.error = error as Error\n store.reportError(error as Error, {\n type: 'collection',\n path: collectionPath,\n operation: 'write',\n })\n notify()\n }\n }\n\n const handleSnapshot = (docs: Array<{ id: string; data: TData }>) => {\n const newSyncState: Record<string, TData> = {}\n for (const { id, data } of docs) {\n newSyncState[id] = { ...data, id } as TData\n }\n\n state.syncState = newSyncState\n // A successful snapshot supersedes any previous read or write error.\n state.error = undefined\n\n if (state.waitingForUpdate) {\n state.waitingForUpdate = false\n const inflightState = state.inflightLocalState\n state.inflightLocalState = undefined\n const currentLocal = state.localState\n\n // Rebase local changes if they changed since we started the sync\n if (\n inflightState &&\n currentLocal &&\n !isDeepEqual(currentLocal, inflightState)\n ) {\n const changesSinceInflight = computeDiff(\n inflightState as FirestoreObject,\n currentLocal as FirestoreObject\n )\n const rebasedLocalState = deepClone(newSyncState)\n applyDiffMutable(rebasedLocalState, changesSinceInflight as Record<string, unknown>)\n // Re-add ids\n for (const [docId, docData] of Object.entries(rebasedLocalState)) {\n if (docData && typeof docData === 'object') {\n ;(docData as Record<string, unknown>).id = docId\n }\n }\n state.localState = rebasedLocalState\n } else {\n state.localState = undefined\n }\n }\n\n if (minLoadTimeElapsed) {\n state.isLoading = false\n }\n loaded = true\n\n // If a rebase produced fresh local edits, ensure they flush. The\n // user's update() during the inflight write already scheduled an\n // autosave, so this is mostly defensive.\n if (state.localState !== undefined) {\n scheduleAutosave()\n }\n\n notify()\n }\n\n const handleError = (error: Error) => {\n if (retryOnError) {\n console.warn('Collection listener error, retrying:', error)\n retryTimeout = setTimeout(() => {\n stop()\n startListener()\n }, retryInterval)\n } else {\n state.error = error\n // Don't leave consumers stuck on a loading spinner — the listener\n // has reported a terminal error, so loading is done.\n state.isLoading = false\n loaded = true\n store.reportError(error, {\n type: 'collection',\n path: collectionPath,\n operation: 'read',\n })\n notify()\n }\n }\n\n const startListener = () => {\n if (unsubscribeListener) return\n\n loaded = false\n minLoadTimeElapsed = false\n\n const q = allConstraints.length > 0\n ? query(collectionRef, ...allConstraints)\n : collectionRef\n\n unsubscribeListener = onSnapshot(\n q,\n (snapshot) => {\n const docs = snapshot.docs.map((docSnap) => ({\n id: docSnap.id,\n data: docSnap.data() as TData,\n }))\n handleSnapshot(docs)\n },\n handleError\n )\n\n // Min load time handler — tracked so stop() can cancel it.\n minLoadTimeout = setTimeout(() => {\n minLoadTimeout = null\n if (loaded) {\n state.isLoading = false\n notify()\n }\n minLoadTimeElapsed = true\n }, minLoadTime)\n }\n\n const load = () => {\n // Listener-level idempotency so the hook can safely call load() on\n // every mount (including Strict Mode's mount-cleanup-remount cycle).\n if (unsubscribeListener) return\n if (!state.isActive) {\n state.isActive = true\n state.isLoading = true\n notify()\n }\n startListener()\n }\n\n const stop = () => {\n if (retryTimeout) {\n clearTimeout(retryTimeout)\n retryTimeout = null\n }\n if (unsubscribeListener) {\n unsubscribeListener()\n unsubscribeListener = null\n }\n if (autosaveTimeout) {\n clearTimeout(autosaveTimeout)\n autosaveTimeout = null\n }\n if (minLoadTimeout) {\n clearTimeout(minLoadTimeout)\n minLoadTimeout = null\n }\n // Drop this subscription's entry from the global sync-state map so\n // an unmounted hook does not leave useIsSynced stuck at false.\n store.unregisterSyncState(syncKey)\n }\n\n const subscribe = (fn: Subscriber<CollectionState<TData>>): Unsubscribe => {\n subscribers.add(fn)\n return () => subscribers.delete(fn)\n }\n\n const buildHandle = (): CollectionHandle<TData> => ({\n data: getMergedData(),\n update: updateState,\n add: addDocument,\n remove: removeDocument,\n isLoading: state.isLoading,\n isSynced: state.localState === undefined,\n isActive: state.isActive,\n load,\n sync,\n error: state.error,\n ref: collectionRef,\n })\n\n const getHandle = (): CollectionHandle<TData> => {\n if (cachedHandle === null) {\n cachedHandle = buildHandle()\n }\n return cachedHandle\n }\n\n // No constructor-side auto-start: callers (the hook for non-lazy, or\n // users directly for lazy) invoke load() to attach the listener. This\n // keeps subscription creation side-effect-free, matching document.ts.\n\n return {\n load,\n stop,\n subscribe,\n getState: getPublicState,\n getHandle,\n sync,\n }\n}\n","import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from \"react\";\nimport type { QueryConstraint } from \"firebase/firestore\";\nimport type {\n CollectionDefinition,\n CollectionHandle,\n DocumentDefinition,\n DocumentHandle,\n FirestoreObject,\n UndoManager,\n UndoManagerState,\n UpdateOptions,\n} from \"./types\";\nimport type { FirestateStore } from \"./store\";\nimport { createDocumentSubscription } from \"./document\";\nimport { createCollectionSubscription } from \"./collection\";\n\n/**\n * Returned when a hook is called with `enabled: false`. Module-level constants\n * so getSnapshot returns a stable reference and useSyncExternalStore doesn't\n * re-render. Cast at the call site to the generic handle type — every method\n * is a no-op so the cast is sound.\n */\nconst NOOP = () => {};\nconst ASYNC_NOOP = async () => {};\nconst EMPTY_RECORD: Record<string, never> = {};\n\nconst DISABLED_DOCUMENT_HANDLE: DocumentHandle<FirestoreObject> = {\n data: undefined,\n update: NOOP,\n set: NOOP,\n delete: NOOP,\n isLoading: false,\n isSynced: true,\n sync: ASYNC_NOOP,\n error: undefined,\n ref: undefined,\n};\n\n// The disabled add() satisfies both overloads but performs no work and\n// returns undefined to match the bail-path contract from collection.ts.\n// Consumers using `enabled: false` should not be calling mutation methods\n// on the disabled handle.\nconst DISABLED_ADD = () => undefined;\n\nconst DISABLED_COLLECTION_HANDLE: CollectionHandle<FirestoreObject> = {\n data: EMPTY_RECORD,\n update: NOOP,\n add: DISABLED_ADD,\n remove: NOOP,\n isLoading: false,\n isSynced: true,\n isActive: false,\n load: NOOP,\n sync: ASYNC_NOOP,\n error: undefined,\n ref: undefined,\n};\n\n/**\n * Context for providing the Firestate store\n */\nexport const FirestateContext = createContext<FirestateStore | null>(null);\n\n/**\n * Hook to access the Firestate store\n */\nexport const useStore = (): FirestateStore => {\n const store = useContext(FirestateContext);\n if (!store) {\n throw new Error(\"useStore must be used within a FirestateProvider\");\n }\n return store;\n};\n\n/**\n * Hook to access the undo manager\n */\nexport const useUndoManager = (): UndoManager => {\n const store = useStore();\n const { undoManager } = store;\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => undoManager.subscribe(onStoreChange),\n [undoManager]\n );\n\n // Delegate to the manager's cached snapshot so getSnapshot returns a stable\n // reference across React's multiple per-commit calls. Building the snapshot\n // inline here would create a new object every call and trip the\n // \"getSnapshot should be cached\" warning + an infinite re-render loop.\n const getSnapshot = useCallback(\n (): UndoManagerState => undoManager.getState(),\n [undoManager]\n );\n\n const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n return useMemo(\n () => ({\n ...state,\n push: undoManager.push,\n undo: undoManager.undo,\n redo: undoManager.redo,\n clear: undoManager.clear,\n }),\n [state, undoManager]\n );\n};\n\n/**\n * Hook to check if all tracked resources are synced\n */\nexport const useIsSynced = (): boolean => {\n const store = useStore();\n\n const subscribe = useCallback(\n (onChange: () => void) => store.subscribeToSyncState(() => onChange()),\n [store]\n );\n\n const getSnapshot = useCallback(() => store.isSynced, [store]);\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n};\n\n/**\n * Options for useDocument hook\n */\nexport interface UseDocumentOptions<TData extends FirestoreObject> {\n /** Document definition from defineDocument() */\n definition: DocumentDefinition<TData>;\n /** Route/path parameters for dynamic paths */\n params?: Record<string, string>;\n /** Override read-only setting */\n readOnly?: boolean;\n /** Enable undo/redo for this document (default: true) */\n undoable?: boolean;\n /**\n * If false, no subscription is created and a no-op handle is returned\n * (`{ data: undefined, isLoading: false, isSynced: true, ref: undefined }`).\n * Use this to gate subscriptions on route params that aren't ready yet.\n * Default: true.\n */\n enabled?: boolean;\n}\n\n/**\n * Hook to subscribe to a Firestore document with real-time updates.\n *\n * The subscription is keyed on the resolved document path (`definition` +\n * computed id) and `readOnly`. When that key changes — typically because\n * `params` produces a different id — the hook tears down the old Firestore\n * listener and attaches a new one. Toggling `undoable` does not rebuild the\n * subscription.\n *\n * Use `enabled: false` to suppress the subscription entirely (e.g., when\n * route params aren't ready yet).\n *\n * **SSR.** On the server there is no Firestore listener, so this hook returns\n * the initial handle (`{ data: undefined, isLoading: true }`). Mutations like\n * `update`/`set` will mutate orphaned local state with no effect — avoid\n * calling them server-side.\n *\n * @example\n * ```tsx\n * const projectDoc = defineDocument<Project>({\n * collection: 'projects',\n * id: (params) => params.projectId,\n * })\n *\n * function ProjectEditor({ projectId }: { projectId: string }) {\n * const { data, update, isLoading, isSynced } = useDocument({\n * definition: projectDoc,\n * params: { projectId },\n * })\n *\n * if (isLoading) return <Spinner />\n *\n * return (\n * <input\n * value={data?.name ?? ''}\n * onChange={(e) => update({ name: e.target.value })}\n * />\n * )\n * }\n * ```\n */\nexport const useDocument = <TData extends FirestoreObject>(\n options: UseDocumentOptions<TData>\n): DocumentHandle<TData> => {\n const {\n definition,\n params = {},\n readOnly,\n undoable = true,\n enabled = true,\n } = options;\n const store = useStore();\n const undoManager = store.undoManager;\n\n // Hold the latest `undoable` in a ref so the onPushUndo callback can stay\n // referentially stable. Without this, every undoable toggle would tear\n // down the Firestore listener and re-attach it for no good reason.\n const undoableRef = useRef(undoable);\n undoableRef.current = undoable;\n\n const onPushUndo = useCallback(\n (undoAction: () => void, redoAction: () => void, opts?: UpdateOptions) => {\n if (!undoableRef.current) return;\n undoManager.push({\n undo: undoAction,\n redo: redoAction,\n groupId: opts?.undoGroupId,\n });\n },\n [undoManager]\n );\n\n // Resolve the doc id and collection path at render time. When disabled we\n // skip resolution — consumers commonly pass `enabled: false` precisely\n // because params aren't ready and definition.id(params) would fail.\n const docId = enabled\n ? typeof definition.id === \"function\"\n ? definition.id(params)\n : definition.id\n : undefined;\n\n const collectionPath = enabled\n ? typeof definition.collection === \"function\"\n ? definition.collection(params)\n : definition.collection\n : undefined;\n\n const subscription = useMemo(\n () =>\n enabled && docId !== undefined && collectionPath !== undefined\n ? createDocumentSubscription({\n store,\n definition,\n docId,\n collectionPath,\n readOnly,\n onPushUndo,\n })\n : null,\n [enabled, store, definition, docId, collectionPath, readOnly, onPushUndo]\n );\n\n const subscribe = useCallback(\n (onChange: () => void) => {\n if (!subscription) return NOOP;\n const unsub = subscription.subscribe(() => onChange());\n subscription.load();\n return () => {\n unsub();\n subscription.stop();\n };\n },\n [subscription]\n );\n\n const getSnapshot = useCallback(\n () =>\n subscription\n ? subscription.getHandle()\n : (DISABLED_DOCUMENT_HANDLE as DocumentHandle<TData>),\n [subscription]\n );\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n};\n\n/**\n * Options for useCollection hook\n */\nexport interface UseCollectionOptions<TData extends FirestoreObject> {\n /** Collection definition from defineCollection() */\n definition: CollectionDefinition<TData>;\n /** Route/path parameters for dynamic paths */\n params?: Record<string, string>;\n /** Override read-only setting */\n readOnly?: boolean;\n /** Additional query constraints */\n queryConstraints?: QueryConstraint[];\n /** Enable undo/redo for this collection (default: true) */\n undoable?: boolean;\n /**\n * If false, no subscription is created and a no-op handle is returned\n * (`{ data: {}, isLoading: false, isActive: false }`). Use this to gate on\n * route params that aren't ready yet. Default: true.\n */\n enabled?: boolean;\n}\n\n/**\n * Hook to subscribe to a Firestore collection with real-time updates.\n *\n * The subscription is keyed on the resolved collection path, `readOnly`, and\n * the `queryConstraints` reference. When any of these change, the listener\n * is torn down and re-attached with the new query. Toggling `undoable` does\n * not rebuild the subscription.\n *\n * **Memoize `queryConstraints`.** An inline array (`queryConstraints={[where(...)]}`)\n * creates a new reference every render, which will thrash the listener.\n * Wrap in `useMemo` with the underlying filter values as deps.\n *\n * Use `enabled: false` to suppress the subscription entirely (e.g., when\n * route params aren't ready yet).\n *\n * **SSR.** On the server there is no Firestore listener, so this hook returns\n * the initial handle (`{ data: {}, isLoading: true }` for non-lazy, or\n * `isActive: false` for lazy). Avoid calling mutations server-side.\n *\n * @example\n * ```tsx\n * const spacesCollection = defineCollection<Space>({\n * path: (params) => `projects/${params.projectId}/spaces`,\n * lazy: true,\n * })\n *\n * function SpacesList({ projectId }: { projectId: string }) {\n * const { data, update, load, isActive, isLoading } = useCollection({\n * definition: spacesCollection,\n * params: { projectId },\n * })\n *\n * // Lazy load on mount\n * useEffect(() => { load() }, [load])\n *\n * if (!isActive) return <Button onClick={load}>Load Spaces</Button>\n * if (isLoading) return <Spinner />\n *\n * return (\n * <ul>\n * {Object.values(data).map((space) => (\n * <li key={space.id}>{space.name}</li>\n * ))}\n * </ul>\n * )\n * }\n * ```\n */\nexport const useCollection = <TData extends FirestoreObject>(\n options: UseCollectionOptions<TData>\n): CollectionHandle<TData> => {\n const {\n definition,\n params = {},\n readOnly,\n queryConstraints,\n undoable = true,\n enabled = true,\n } = options;\n const store = useStore();\n const undoManager = store.undoManager;\n\n const undoableRef = useRef(undoable);\n undoableRef.current = undoable;\n\n const onPushUndo = useCallback(\n (undoAction: () => void, redoAction: () => void, opts?: UpdateOptions) => {\n if (!undoableRef.current) return;\n undoManager.push({\n undo: undoAction,\n redo: redoAction,\n groupId: opts?.undoGroupId,\n });\n },\n [undoManager]\n );\n\n // Resolve the collection path at render time. When disabled we skip\n // resolution — consumers commonly pass `enabled: false` precisely because\n // params aren't ready.\n const collectionPath = enabled\n ? typeof definition.path === \"function\"\n ? definition.path(params)\n : definition.path\n : undefined;\n\n const subscription = useMemo(\n () =>\n enabled && collectionPath !== undefined\n ? createCollectionSubscription({\n store,\n definition,\n collectionPath,\n readOnly,\n queryConstraints,\n onPushUndo,\n })\n : null,\n [\n enabled,\n store,\n definition,\n collectionPath,\n readOnly,\n queryConstraints,\n onPushUndo,\n ]\n );\n\n const isLazy = definition.lazy ?? false;\n\n const subscribe = useCallback(\n (onChange: () => void) => {\n if (!subscription) return NOOP;\n const unsub = subscription.subscribe(() => onChange());\n if (!isLazy) {\n subscription.load();\n }\n return () => {\n unsub();\n subscription.stop();\n };\n },\n [subscription, isLazy]\n );\n\n const getSnapshot = useCallback(\n () =>\n subscription\n ? subscription.getHandle()\n : (DISABLED_COLLECTION_HANDLE as CollectionHandle<TData>),\n [subscription]\n );\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n};\n\n/**\n * Keyboard shortcut hook for undo/redo\n *\n * @example\n * ```tsx\n * function App() {\n * useUndoKeyboardShortcuts()\n * return <YourApp />\n * }\n * ```\n */\nexport const useUndoKeyboardShortcuts = (): void => {\n // Read the manager ref directly — we only need .undo() / .redo() (stable\n // refs), not its state. Subscribing via useUndoManager would re-render\n // the host component on every undo-stack change.\n const undoManager = useStore().undoManager;\n\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n const platform =\n (\n navigator as Navigator & {\n userAgentData?: { platform: string };\n }\n ).userAgentData?.platform ?? navigator.platform;\n const isMac = platform.toUpperCase().includes(\"MAC\");\n const modifier = isMac ? e.metaKey : e.ctrlKey;\n\n if (!modifier) return;\n\n if (e.key === \"z\" && !e.shiftKey) {\n e.preventDefault();\n undoManager.undo();\n } else if ((e.key === \"z\" && e.shiftKey) || e.key === \"y\") {\n e.preventDefault();\n undoManager.redo();\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [undoManager]);\n};\n","/**\n * Registry-driven Firestate API.\n *\n * Declare every document and collection in a single object and let the\n * library generate the typed read/write hooks. Replaces hand-writing a\n * `useSpaces`, `useWallTypes`, ... hook per Firestore collection.\n *\n * ```ts\n * interface TaskList { name: string; createdAt: number }\n * interface Task { title: string; completed: boolean }\n *\n * export const { useTaskList, useTasks } = createFirestate({\n * taskList: doc<TaskList>('taskLists/{listId}'),\n * tasks: col<Task>('taskLists/{listId}/tasks'),\n * })\n *\n * // At the call site:\n * const taskList = useTaskList({ listId })\n * const tasks = useTasks({ listId })\n * ```\n */\nimport { defineDocument, defineCollection } from \"./schema\";\nimport {\n useDocument,\n useCollection,\n type UseDocumentOptions,\n type UseCollectionOptions,\n} from \"./hooks\";\nimport type {\n CollectionDefinition,\n CollectionHandle,\n DocumentDefinition,\n DocumentHandle,\n FirestoreObject,\n} from \"./types\";\nimport type { QueryConstraint } from \"firebase/firestore\";\nimport type { ZodType, z } from \"zod\";\n\n/**\n * Knobs forwarded from a generated document hook to {@link useDocument}.\n * Same shape as `UseDocumentOptions` minus the fields the registry already\n * owns (`definition`, `params`).\n */\nexport type DocHookOptions<T extends FirestoreObject> = Omit<\n UseDocumentOptions<T>,\n \"definition\" | \"params\"\n>;\n\n/**\n * Knobs forwarded from a generated collection hook to {@link useCollection}.\n */\nexport type ColHookOptions<T extends FirestoreObject> = Omit<\n UseCollectionOptions<T>,\n \"definition\" | \"params\"\n>;\n\n// ---------------------------------------------------------------------------\n// Registry entry shapes\n// ---------------------------------------------------------------------------\n\ninterface CommonEntryOptions {\n /** Debounce interval for autosave (ms). */\n autosave?: number;\n /** Minimum loading indicator time (ms). */\n minLoadTime?: number;\n /** Whether this entry is read-only. */\n readOnly?: boolean;\n /** Retry the snapshot listener on transient errors. */\n retryOnError?: boolean;\n /** Retry interval (ms). */\n retryInterval?: number;\n}\n\n/**\n * Document entry in a Firestate registry. Produced by {@link doc}.\n *\n * The `P` generic carries the path template's string-literal type so the\n * generated hook can type-check param keys. `__kind` is a runtime\n * discriminator; `__type` is a phantom field used purely for inference at\n * the call site and is never read.\n */\nexport interface DocEntry<\n T extends FirestoreObject,\n P extends string = string\n> extends CommonEntryOptions {\n readonly __kind: \"document\";\n readonly __type?: T;\n /** Path template, e.g. `'taskLists/{listId}'`. */\n path: P;\n /**\n * Zod schema. **Required** — firestate's registry API is opinionated\n * about Zod. The schema is the source of `T` for the generated hooks\n * via `z.infer`, and firestate runs `schema.parse(...)` on full-payload\n * writes (`set`/`add`) so bad data throws at the call site rather than\n * after a Firestore round trip. Partial `update(diff)` is NOT validated\n * (diffs frequently contain Firestore sentinels like `serverTimestamp()`).\n *\n * If you don't want a schema at all, use {@link defineDocument} directly —\n * the escape hatch keeps the plain-TypeScript form at the cost of looser\n * param typing and no runtime validation.\n */\n schema: ZodType<T>;\n}\n\n/** Collection entry in a Firestate registry. Produced by {@link col}. */\nexport interface ColEntry<\n T extends FirestoreObject,\n P extends string = string\n> extends CommonEntryOptions {\n readonly __kind: \"collection\";\n readonly __type?: T;\n /** Path template, e.g. `'taskLists/{listId}/tasks'`. */\n path: P;\n /** Zod schema. Required. See {@link DocEntry.schema}. */\n schema: ZodType<T>;\n /** Only subscribe when `load()` is called. */\n lazy?: boolean;\n /** Additional Firestore query constraints. */\n queryConstraints?: QueryConstraint[];\n}\n\nexport type FirestateEntry<\n T extends FirestoreObject = FirestoreObject,\n P extends string = string\n> = DocEntry<T, P> | ColEntry<T, P>;\n\nexport type FirestateRegistry = Record<string, FirestateEntry<any, any>>;\n\n// ---------------------------------------------------------------------------\n// Path → params extraction\n// ---------------------------------------------------------------------------\n\n/**\n * Extract `{name}` placeholders from a path template into a params shape.\n *\n * - `'users'` → `{}`\n * - `'users/{userId}'` → `{ userId: string }`\n * - `'projects/{projectId}/revisions/{revisionId}'` → `{ projectId: string; revisionId: string }`\n *\n * When the path is widened to `string` (no literal preserved), we fall\n * back to `Record<string, string>` so existing call sites keep compiling.\n */\nexport type ParamsOf<P extends string> = string extends P\n ? Record<string, string>\n : Prettify<RawParamsOf<P>>;\n\ntype RawParamsOf<P extends string> =\n P extends `${string}{${infer K}}${infer Rest}`\n ? { [Key in K]: string } & RawParamsOf<Rest>\n : {};\n\n// Force TS to evaluate intersections so error messages show\n// `{ projectId: string; revisionId: string }` instead of an intersection.\ntype Prettify<T> = { [K in keyof T]: T[K] } & {};\n\n// ---------------------------------------------------------------------------\n// Entry factories\n// ---------------------------------------------------------------------------\n\ntype DocOpts<T extends FirestoreObject> = Omit<DocEntry<T>, \"__kind\" | \"__type\" | \"path\">;\ntype ColOpts<T extends FirestoreObject> = Omit<ColEntry<T>, \"__kind\" | \"__type\" | \"path\">;\n\n/**\n * Declare a single-document entry for a Firestate registry.\n *\n * **A Zod `schema` field is required.** Both the data type (`T`) and the\n * path's literal type (`P`) are inferred from the call — `T` via\n * `z.infer<S>`, `P` from `path` — so the generated hook can statically\n * type-check the params object the caller passes. The schema also runs\n * at runtime on full-payload writes (`set`/`add`).\n *\n * If you'd rather not provide a schema at all, use {@link defineDocument}\n * directly — that escape hatch keeps the plain-TypeScript form, at the\n * cost of looser param typing on the hook and no runtime validation.\n *\n * ```ts\n * import { z } from 'zod'\n *\n * const TaskListSchema = z.object({ name: z.string(), createdAt: z.number() })\n * doc({ path: 'taskLists/{listId}', schema: TaskListSchema })\n * // → DocEntry<{ name: string; createdAt: number }, 'taskLists/{listId}'>\n * ```\n */\nexport function doc<\n S extends ZodType<FirestoreObject>,\n const P extends string = string\n>(\n opts: Omit<DocOpts<z.infer<S>>, \"schema\"> & {\n schema: S;\n path: P;\n }\n): DocEntry<z.infer<S>, P> {\n const { path, ...rest } = opts;\n validateTemplate(path);\n // Bail at registration time if the path can't be split into a non-empty\n // collection + id — same loud-at-the-boundary spirit as interpolate.\n splitDocPath(path);\n return { __kind: \"document\", path, ...rest } as unknown as DocEntry<\n z.infer<S>,\n P\n >;\n}\n\n/**\n * Declare a collection entry for a Firestate registry. See {@link doc}\n * for the schema/typing contract.\n */\nexport function col<\n S extends ZodType<FirestoreObject>,\n const P extends string = string\n>(\n opts: Omit<ColOpts<z.infer<S>>, \"schema\"> & {\n schema: S;\n path: P;\n }\n): ColEntry<z.infer<S>, P> {\n const { path, ...rest } = opts;\n validateTemplate(path);\n return { __kind: \"collection\", path, ...rest } as unknown as ColEntry<\n z.infer<S>,\n P\n >;\n}\n\n// ---------------------------------------------------------------------------\n// createFirestate\n// ---------------------------------------------------------------------------\n\ntype HookName<K extends string> = `use${Capitalize<K>}`;\n\n// If the path template has no placeholders, `params` is optional (any\n// caller-supplied object is fine). When the template has placeholders,\n// the caller must pass an object with exactly the extracted keys.\ntype HookFor<E> = E extends DocEntry<infer T, infer P>\n ? keyof ParamsOf<P> extends never\n ? (\n params?: Record<string, string>,\n options?: DocHookOptions<T>\n ) => DocumentHandle<T>\n : (\n params: ParamsOf<P>,\n options?: DocHookOptions<T>\n ) => DocumentHandle<T>\n : E extends ColEntry<infer T, infer P>\n ? keyof ParamsOf<P> extends never\n ? (\n params?: Record<string, string>,\n options?: ColHookOptions<T>\n ) => CollectionHandle<T>\n : (\n params: ParamsOf<P>,\n options?: ColHookOptions<T>\n ) => CollectionHandle<T>\n : never;\n\nexport type FirestateApi<R extends FirestateRegistry> = {\n [K in keyof R & string as HookName<K>]: HookFor<R[K]>;\n};\n\n/**\n * Turn a Firestate registry into a map of typed React hooks. Each entry\n * `K` produces a hook named `use{Capitalize<K>}`.\n *\n * ```ts\n * export const { useTaskList, useTasks } = createFirestate({\n * taskList: doc<TaskList>('taskLists/{listId}'),\n * tasks: col<Task>('taskLists/{listId}/tasks'),\n * })\n * ```\n */\nexport function createFirestate<R extends FirestateRegistry>(\n registry: R\n): FirestateApi<R> {\n const api: Record<string, unknown> = {};\n\n for (const key of Object.keys(registry)) {\n if (!isValidKey(key)) {\n throw new Error(\n `[firestate] registry key \"${key}\" must start with a letter and contain only letters, digits, _ or $`\n );\n }\n const entry = registry[key]!;\n const hookName = toHookName(key);\n\n if (entry.__kind === \"document\") {\n const definition = buildDocumentDefinition(entry);\n api[hookName] = (\n params: Record<string, string> = {},\n options: DocHookOptions<FirestoreObject> = {}\n ) => useDocument({ ...options, definition, params });\n } else {\n const definition = buildCollectionDefinition(entry);\n api[hookName] = (\n params: Record<string, string> = {},\n options: ColHookOptions<FirestoreObject> = {}\n ) => useCollection({ ...options, definition, params });\n }\n }\n\n return api as FirestateApi<R>;\n}\n\n/**\n * Build the underlying {@link DocumentDefinition} for a registry doc entry.\n * Exported for unit testing — registry consumers should call\n * {@link createFirestate} instead.\n *\n * @internal\n */\nexport function buildDocumentDefinition<T extends FirestoreObject>(\n entry: DocEntry<T>\n): DocumentDefinition<T> {\n const { collectionPath, idTemplate } = splitDocPath(entry.path);\n // Both halves are functions so any `{param}` placeholder in the\n // collection portion (e.g. `projects/{projectId}/revisions`) is\n // resolved per-call against the params passed to the hook.\n return defineDocument<T>({\n schema: entry.schema,\n collection: (params) => interpolate(collectionPath, params),\n id: (params) => interpolate(idTemplate, params),\n autosave: entry.autosave,\n minLoadTime: entry.minLoadTime,\n readOnly: entry.readOnly,\n retryOnError: entry.retryOnError,\n retryInterval: entry.retryInterval,\n } as DocumentDefinition<T>);\n}\n\n/**\n * Build the underlying {@link CollectionDefinition} for a registry col entry.\n *\n * @internal\n */\nexport function buildCollectionDefinition<T extends FirestoreObject>(\n entry: ColEntry<T>\n): CollectionDefinition<T> {\n return defineCollection<T>({\n schema: entry.schema,\n path: (params) => interpolate(entry.path, params),\n autosave: entry.autosave,\n minLoadTime: entry.minLoadTime,\n readOnly: entry.readOnly,\n lazy: entry.lazy,\n queryConstraints: entry.queryConstraints,\n retryOnError: entry.retryOnError,\n retryInterval: entry.retryInterval,\n } as CollectionDefinition<T>);\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers (also exported for testing)\n// ---------------------------------------------------------------------------\n\nconst VALID_KEY = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n\nfunction isValidKey(key: string): boolean {\n return VALID_KEY.test(key);\n}\n\nfunction toHookName(key: string): string {\n return `use${key[0]!.toUpperCase()}${key.slice(1)}`;\n}\n\n/**\n * Replace `{name}` placeholders in a path template with values from `params`.\n * Throws if a placeholder is missing from `params` — failing loud at the\n * boundary is better than silently building a `taskLists/undefined/tasks`\n * URL and getting a useless Firestore error later.\n *\n * @internal\n */\nexport function interpolatePath(\n template: string,\n params: Record<string, string>\n): string {\n return interpolate(template, params);\n}\n\n// Matches a single `{name}` placeholder where the name is a valid JS-ish\n// identifier (letter or underscore start, then letters/digits/underscores).\n// Used both to interpolate and to validate templates up front.\nconst PLACEHOLDER = /\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g;\n\n/**\n * Validate that a path template uses only well-formed `{name}` placeholders\n * — no unclosed braces, no hyphens/dots inside placeholders, no `{1}` style\n * digit-leading names. Throws at definition time so a typo in the template\n * fails loud at `doc()` / `col()`, not three layers deep when a component\n * mounts.\n */\nfunction validateTemplate(template: string): void {\n // Strip the well-formed placeholders, then look for any stray `{` or `}` —\n // those signal a malformed (unclosed or weirdly-spelled) placeholder.\n const stripped = template.replace(PLACEHOLDER, \"\");\n if (stripped.includes(\"{\") || stripped.includes(\"}\")) {\n throw new Error(\n `[firestate] path \"${template}\" contains a malformed placeholder. ` +\n `Placeholders must look like \"{name}\" where name starts with a letter or underscore.`\n );\n }\n}\n\nfunction interpolate(template: string, params: Record<string, string>): string {\n return template.replace(PLACEHOLDER, (_, key) => {\n const v = params[key];\n if (v === undefined) {\n throw new Error(\n `[firestate] missing param \"${key}\" for path \"${template}\"`\n );\n }\n if (v === \"\") {\n // An empty value would silently produce `taskLists//tasks`, which\n // Firestore later rejects with an opaque \"Document path must not be\n // empty\" — keep the friendly error at the boundary.\n throw new Error(\n `[firestate] param \"${key}\" for path \"${template}\" must not be an empty string`\n );\n }\n return v;\n });\n}\n\n/**\n * Split a document path template into a collection path and an id template.\n * `'taskLists/{listId}'` → `{ collectionPath: 'taskLists', idTemplate: '{listId}' }`.\n *\n * @internal\n */\nexport function splitDocPath(path: string): {\n collectionPath: string;\n idTemplate: string;\n} {\n const lastSlash = path.lastIndexOf(\"/\");\n if (lastSlash === -1) {\n throw new Error(\n `[firestate] document path \"${path}\" must contain at least one '/' separating the collection from the document id`\n );\n }\n const collectionPath = path.slice(0, lastSlash);\n const idTemplate = path.slice(lastSlash + 1);\n if (collectionPath === \"\" || idTemplate === \"\") {\n throw new Error(\n `[firestate] document path \"${path}\" must have non-empty collection and id segments`\n );\n }\n return { collectionPath, idTemplate };\n}\n","import type { Subscriber, Unsubscribe, UndoAction, UndoManager, UndoManagerState } from './types'\n\n/**\n * Configuration for creating an undo manager\n */\nexport interface UndoManagerConfig {\n /** Maximum number of undo actions to keep, default 20 */\n maxLength?: number\n /** Callback when navigation is requested (for path-aware undo) */\n onNavigate?: (path: string) => void\n}\n\n/**\n * Create an undo manager instance.\n * This is a standalone, framework-agnostic implementation.\n *\n * @example\n * ```ts\n * const undoManager = createUndoManager({ maxLength: 10 })\n *\n * undoManager.push({\n * undo: () => restoreOldValue(),\n * redo: () => applyNewValue(),\n * description: 'Update project name',\n * })\n *\n * await undoManager.undo() // Calls restoreOldValue()\n * await undoManager.redo() // Calls applyNewValue()\n * ```\n */\nexport const createUndoManager = (\n config: UndoManagerConfig = {}\n): UndoManager & {\n subscribe: (fn: Subscriber<UndoManagerState>) => Unsubscribe\n getState: () => UndoManagerState\n} => {\n const { maxLength = 20, onNavigate } = config\n\n let undoStack: UndoAction[] = []\n let redoStack: UndoAction[] = []\n const subscribers = new Set<Subscriber<UndoManagerState>>()\n // Cached snapshot — returns the same reference until notify() invalidates\n // it. Required so React's useSyncExternalStore consumers (useUndoManager)\n // see a stable snapshot across the multiple getSnapshot() calls React\n // makes per commit; otherwise the inequality on Object.is triggers an\n // infinite re-render and the \"getSnapshot should be cached\" warning.\n let cachedState: UndoManagerState | null = null\n\n const getState = (): UndoManagerState => {\n if (cachedState === null) {\n cachedState = {\n undoStack,\n redoStack,\n canUndo: undoStack.length > 0,\n canRedo: redoStack.length > 0,\n }\n }\n return cachedState\n }\n\n const notify = () => {\n cachedState = null\n const state = getState()\n subscribers.forEach((fn) => fn(state))\n }\n\n const push = (action: UndoAction) => {\n // Check if we should merge with previous action (same groupId)\n if (action.groupId && undoStack.length > 0) {\n const last = undoStack[undoStack.length - 1]\n if (last?.groupId === action.groupId) {\n // Pop and merge. Undo walks the group newest→oldest so each\n // step reverses its specific change in reverse order; redo\n // walks oldest→newest to re-apply in original order. The\n // previous (older) order produced incorrect cumulative state\n // whenever grouped actions touched the same field.\n undoStack.pop()\n undoStack.push({\n undo: async () => {\n await action.undo()\n await last.undo()\n },\n redo: async () => {\n await last.redo()\n await action.redo()\n },\n groupId: action.groupId,\n path: action.path ?? last.path,\n description: action.description ?? last.description,\n })\n // Clear redo stack on any new action\n redoStack = []\n notify()\n return\n }\n }\n\n undoStack.push(action)\n\n // Enforce max length\n if (undoStack.length > maxLength) {\n undoStack.shift()\n }\n\n // Clear redo stack on any new action\n redoStack = []\n notify()\n }\n\n const undo = async () => {\n const action = undoStack.pop()\n if (!action) return\n\n // Navigate if path is set\n if (action.path && onNavigate) {\n onNavigate(action.path)\n }\n\n try {\n await action.undo()\n redoStack.push(action)\n } catch (error) {\n // Put it back on undo stack if it failed\n undoStack.push(action)\n console.error('Undo failed:', error)\n throw error\n }\n\n notify()\n }\n\n const redo = async () => {\n const action = redoStack.pop()\n if (!action) return\n\n // Navigate if path is set\n if (action.path && onNavigate) {\n onNavigate(action.path)\n }\n\n try {\n await action.redo()\n undoStack.push(action)\n\n // Enforce max length\n if (undoStack.length > maxLength) {\n undoStack.shift()\n }\n } catch (error) {\n // Put it back on redo stack if it failed\n redoStack.push(action)\n console.error('Redo failed:', error)\n throw error\n }\n\n notify()\n }\n\n const clear = () => {\n undoStack = []\n redoStack = []\n notify()\n }\n\n const subscribe = (fn: Subscriber<UndoManagerState>): Unsubscribe => {\n subscribers.add(fn)\n return () => subscribers.delete(fn)\n }\n\n return {\n get undoStack() {\n return undoStack\n },\n get redoStack() {\n return redoStack\n },\n get canUndo() {\n return undoStack.length > 0\n },\n get canRedo() {\n return redoStack.length > 0\n },\n push,\n undo,\n redo,\n clear,\n subscribe,\n getState,\n }\n}\n\n/**\n * Type for the undo manager with subscription capability\n */\nexport type UndoManagerWithSubscribe = ReturnType<typeof createUndoManager>\n","import type { Firestore } from 'firebase/firestore'\nimport type { ErrorContext, FirestateConfig, Subscriber, Unsubscribe } from './types'\nimport { createUndoManager, type UndoManagerWithSubscribe } from './undo'\n\n/**\n * Firestate store that holds configuration and shared state\n */\nexport interface FirestateStore {\n /** Firestore instance */\n readonly firestore: Firestore\n /** Undo manager instance */\n readonly undoManager: UndoManagerWithSubscribe\n /** Default autosave interval (ms) */\n readonly autosave: number\n /** Default minimum load time (ms) */\n readonly minLoadTime: number\n /** Report an error */\n reportError: (error: Error, context: ErrorContext) => void\n /**\n * Replace the error handler at runtime. Used by FirestateProvider to keep\n * the store identity stable when consumers pass an inline `onError`\n * callback that changes reference on every render.\n */\n setOnError: (handler?: (error: Error, context: ErrorContext) => void) => void\n /** Subscribe to sync state changes */\n subscribeToSyncState: (fn: Subscriber<boolean>) => Unsubscribe\n /** Report a document/collection sync state change */\n reportSyncState: (key: string, isSynced: boolean) => void\n /**\n * Remove a sync-state key. Subscriptions call this on stop() so an\n * unmounted hook does not leave the global isSynced stuck at false.\n */\n unregisterSyncState: (key: string) => void\n /** Get whether all tracked resources are synced */\n readonly isSynced: boolean\n}\n\n/**\n * Create a Firestate store.\n * This is the central configuration point for your Firestore state management.\n *\n * @example\n * ```ts\n * import { createStore } from 'firestate'\n * import { db } from './firebase'\n *\n * export const store = createStore({\n * firestore: db,\n * autosave: 1000,\n * maxUndoLength: 20,\n * onError: (error, context) => {\n * console.error(`Error in ${context.type} ${context.path}:`, error)\n * },\n * })\n * ```\n */\nexport const createStore = (config: FirestateConfig): FirestateStore => {\n const {\n firestore,\n autosave = 1000,\n minLoadTime = 0,\n maxUndoLength = 20,\n } = config\n\n // Mutable so the provider can update it without re-creating the store.\n let onError = config.onError\n\n const undoManager = createUndoManager({\n maxLength: maxUndoLength,\n })\n\n // Track sync state of all documents/collections\n const syncStates = new Map<string, boolean>()\n const syncSubscribers = new Set<Subscriber<boolean>>()\n\n const computeIsSynced = (): boolean => {\n for (const synced of syncStates.values()) {\n if (!synced) return false\n }\n return true\n }\n\n const notifySyncSubscribers = () => {\n const isSynced = computeIsSynced()\n syncSubscribers.forEach((fn) => fn(isSynced))\n }\n\n return {\n firestore,\n undoManager,\n autosave,\n minLoadTime,\n\n reportError: (error, context) => {\n if (onError) {\n onError(error, context)\n } else {\n console.error(\n `Firestate error in ${context.type} ${context.path} during ${context.operation}:`,\n error\n )\n }\n },\n\n setOnError: (handler) => {\n onError = handler\n },\n\n subscribeToSyncState: (fn) => {\n syncSubscribers.add(fn)\n return () => syncSubscribers.delete(fn)\n },\n\n reportSyncState: (key, isSynced) => {\n const prev = syncStates.get(key)\n if (prev !== isSynced) {\n syncStates.set(key, isSynced)\n notifySyncSubscribers()\n }\n },\n\n unregisterSyncState: (key) => {\n const prev = syncStates.get(key)\n if (prev === undefined) return\n syncStates.delete(key)\n // Removing a `false` entry can flip global isSynced to true.\n if (prev === false) {\n notifySyncSubscribers()\n }\n },\n\n get isSynced() {\n return computeIsSynced()\n },\n }\n}\n\n/**\n * Type alias for the store type\n */\nexport type Store = ReturnType<typeof createStore>\n","import React, {\n useCallback,\n useEffect,\n useMemo,\n useSyncExternalStore,\n} from \"react\";\nimport type { Firestore } from \"firebase/firestore\";\nimport { createStore, type FirestateStore } from \"./store\";\nimport { FirestateContext } from \"./hooks\";\nimport type { ErrorContext } from \"./types\";\n\n/**\n * Props for FirestateProvider\n */\nexport interface FirestateProviderProps {\n /** Firestore instance */\n firestore: Firestore;\n /** Default autosave interval (ms), default 1000 */\n autosave?: number;\n /** Default minimum load time (ms), default 0 */\n minLoadTime?: number;\n /** Maximum undo stack length, default 20 */\n maxUndoLength?: number;\n /** Custom error handler */\n onError?: (error: Error, context: ErrorContext) => void;\n /** React children */\n children: React.ReactNode;\n}\n\n/**\n * Provider component that sets up Firestate for your application.\n *\n * @example\n * ```tsx\n * import { FirestateProvider } from 'firestate'\n * import { db } from './firebase'\n *\n * function App() {\n * return (\n * <FirestateProvider\n * firestore={db}\n * autosave={1000}\n * maxUndoLength={20}\n * onError={(error, ctx) => console.error(ctx.path, error)}\n * >\n * <YourApp />\n * </FirestateProvider>\n * )\n * }\n * ```\n */\nexport const FirestateProvider: React.FC<FirestateProviderProps> = ({\n firestore,\n autosave = 1000,\n minLoadTime = 0,\n maxUndoLength = 20,\n onError,\n children,\n}) => {\n // onError is intentionally excluded from the deps so that an inline\n // callback (new reference per render) does not re-create the store and\n // drop every existing subscription. The store exposes setOnError so the\n // latest handler can be applied without store re-creation.\n const store = useMemo(\n () =>\n createStore({\n firestore,\n autosave,\n minLoadTime,\n maxUndoLength,\n onError,\n }),\n [firestore, autosave, minLoadTime, maxUndoLength]\n );\n\n useEffect(() => {\n store.setOnError(onError);\n }, [store, onError]);\n\n return (\n <FirestateContext.Provider value={store}>\n {children}\n </FirestateContext.Provider>\n );\n};\n\n/**\n * Props for using an existing store\n */\nexport interface FirestateStoreProviderProps {\n /** Pre-created store instance */\n store: FirestateStore;\n /** React children */\n children: React.ReactNode;\n}\n\n/**\n * Provider that uses an existing store instance.\n * Useful when you need to create the store outside of React.\n *\n * @example\n * ```tsx\n * const store = createStore({ firestore: db })\n *\n * function App() {\n * return (\n * <FirestateStoreProvider store={store}>\n * <YourApp />\n * </FirestateStoreProvider>\n * )\n * }\n * ```\n */\nexport const FirestateStoreProvider: React.FC<FirestateStoreProviderProps> = ({\n store,\n children,\n}) => (\n <FirestateContext.Provider value={store}>\n {children}\n </FirestateContext.Provider>\n);\n\n/**\n * Hook to use navigation blocker when there are unsaved changes.\n * Works with react-router or similar routers.\n *\n * @example\n * ```tsx\n * function ProjectPage() {\n * const shouldBlock = useUnsavedChangesBlocker()\n *\n * // Use with react-router's useBlocker\n * const blocker = useBlocker(\n * ({ currentLocation, nextLocation }) =>\n * currentLocation.pathname !== nextLocation.pathname && shouldBlock\n * )\n *\n * return (\n * <>\n * <ProjectEditor />\n * {blocker.state === 'blocked' && (\n * <Dialog>Your changes may not be saved!</Dialog>\n * )}\n * </>\n * )\n * }\n * ```\n */\nexport const useUnsavedChangesBlocker = (): boolean => {\n const store = React.useContext(FirestateContext);\n\n const subscribe = useCallback(\n (onChange: () => void) =>\n store ? store.subscribeToSyncState(() => onChange()) : () => {},\n [store]\n );\n\n const getSnapshot = useCallback(\n () => (store ? !store.isSynced : false),\n [store]\n );\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["syncKeyCounter","doc","doc"],"sources":["../src/schema.ts","../src/diff.ts","../src/document.ts","../src/collection.ts","../src/hooks.ts","../src/firestate.ts","../src/undo.ts","../src/store.ts","../src/provider.tsx"],"sourcesContent":["import type { ZodType, z } from \"zod\";\nimport type {\n CollectionDefinition,\n DocumentDefinition,\n FirestoreObject,\n} from \"./types\";\n\n/**\n * Define a typed document. `TData` is the document's TypeScript shape.\n *\n * **Most apps should reach for {@link createFirestate} + {@link doc} instead**\n * — that builds a registry of every Firestore thing in one object and\n * generates typed hooks for you. `defineDocument` is the lower-level\n * escape hatch: use it when you need fully custom `collection` / `id`\n * derivation, when you're calling firestate outside React, or when a\n * registry doesn't fit your control flow.\n *\n * Two ways to use:\n *\n * 1. Plain TypeScript type (no schema, no runtime validation):\n * ```ts\n * interface Project { name: string; createdAt: number }\n *\n * const projectDoc = defineDocument<Project>({\n * collection: 'projects',\n * id: (params) => params.projectId,\n * })\n * ```\n *\n * 2. With a Zod schema — `TData` is inferred from `z.infer<S>`. Firestate\n * runs `schema.parse(...)` on full-payload writes (`set`/`add`) so bad\n * data throws at the call site. Partial `update(diff)` calls are not\n * validated (diffs frequently contain Firestore sentinels).\n * ```ts\n * import { z } from 'zod'\n *\n * const ProjectSchema = z.object({ name: z.string(), createdAt: z.number() })\n *\n * const projectDoc = defineDocument({\n * schema: ProjectSchema,\n * collection: 'projects',\n * id: (params) => params.projectId,\n * })\n * ```\n */\nexport function defineDocument<S extends ZodType<FirestoreObject>>(\n definition: Omit<DocumentDefinition<z.infer<S>>, \"schema\"> & {\n schema: S;\n }\n): DocumentDefinition<z.infer<S>>;\nexport function defineDocument<TData extends FirestoreObject>(\n definition: DocumentDefinition<TData>\n): DocumentDefinition<TData>;\nexport function defineDocument(\n definition: DocumentDefinition<FirestoreObject>\n): DocumentDefinition<FirestoreObject> {\n return definition;\n}\n\n/**\n * Define a typed collection. `TData` is the shape of each document in the\n * collection. See {@link defineDocument} for the schema/plain-type tradeoff.\n *\n * **Most apps should reach for {@link createFirestate} + {@link col} instead.**\n * `defineCollection` is the escape hatch for fully custom path derivation\n * or non-React usage.\n *\n * @example\n * ```ts\n * interface Space { name: string; area: number }\n *\n * const spacesCollection = defineCollection<Space>({\n * path: (params) => `projects/${params.projectId}/spaces`,\n * lazy: true,\n * })\n * ```\n */\nexport function defineCollection<S extends ZodType<FirestoreObject>>(\n definition: Omit<CollectionDefinition<z.infer<S>>, \"schema\"> & {\n schema: S;\n }\n): CollectionDefinition<z.infer<S>>;\nexport function defineCollection<TData extends FirestoreObject>(\n definition: CollectionDefinition<TData>\n): CollectionDefinition<TData>;\nexport function defineCollection(\n definition: CollectionDefinition<FirestoreObject>\n): CollectionDefinition<FirestoreObject> {\n return definition;\n}\n\n/**\n * Infer the document data type from a {@link DocumentDefinition}.\n */\nexport type InferDocumentData<T extends DocumentDefinition<FirestoreObject>> =\n T extends DocumentDefinition<infer D> ? D : never;\n\n/**\n * Infer the document data type (with `id` field) from a {@link DocumentDefinition}.\n */\nexport type InferDocument<T extends DocumentDefinition<FirestoreObject>> =\n InferDocumentData<T> & { id: string };\n\n/**\n * Infer the document data type from a {@link CollectionDefinition}.\n */\nexport type InferCollectionData<\n T extends CollectionDefinition<FirestoreObject>\n> = T extends CollectionDefinition<infer D> ? D : never;\n\n/**\n * Infer the document data type (with `id` field) from a {@link CollectionDefinition}.\n */\nexport type InferCollectionDocument<\n T extends CollectionDefinition<FirestoreObject>\n> = InferCollectionData<T> & { id: string };\n","import {\n deleteField,\n serverTimestamp,\n Timestamp,\n WithFieldValue,\n} from 'firebase/firestore'\nimport type { DeepPartial, FirestoreObject } from './types'\n\n/**\n * Check if a value is a plain object (not array, null, or special Firestore type)\n */\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n !(value instanceof Timestamp) &&\n Object.getPrototypeOf(value) === Object.prototype\n\n/**\n * Check if a value is a Firestore opaque type: a FieldValue sentinel\n * (`serverTimestamp`, `deleteField`, `increment`, `arrayUnion`,\n * `arrayRemove`, …) or a value type the SDK ships with its own identity\n * semantics (`Timestamp`, `DocumentReference`, `GeoPoint`, `Bytes`,\n * `VectorValue`). They all expose `.isEqual()` and have a non-plain\n * prototype.\n *\n * The diff / clone pipeline must treat these as **opaque**: never iterate\n * their keys, never substitute their values, never compare them by `===`.\n * Doing any of those silently breaks the write path — see the C1\n * regression where `serverTimestamp()` was replaced with `Timestamp.now()`\n * before reaching Firestore.\n */\nconst isFirestoreOpaque = (\n value: unknown\n): value is { isEqual: (other: unknown) => boolean } => {\n if (value === null || typeof value !== 'object') return false\n if (Object.getPrototypeOf(value) === Object.prototype) return false\n return (\n 'isEqual' in value &&\n typeof (value as { isEqual: unknown }).isEqual === 'function'\n )\n}\n\n// Reference sentinels used to identify specific FieldValue kinds. The\n// Firebase SDK does not export the sentinel subclasses; the only stable\n// way to ask \"is this a serverTimestamp / deleteField?\" is to construct a\n// reference instance once and delegate to its `.isEqual()`. Hoisting them\n// to module scope avoids reconstructing on every call.\nconst SERVER_TIMESTAMP_REF = serverTimestamp()\nconst DELETE_FIELD_REF = deleteField()\n\nconst isDeleteField = (value: unknown): boolean =>\n isFirestoreOpaque(value) && value.isEqual(DELETE_FIELD_REF)\n\nconst isServerTimestamp = (value: unknown): boolean =>\n isFirestoreOpaque(value) && value.isEqual(SERVER_TIMESTAMP_REF)\n\n/**\n * Check if two values are deeply equal\n */\nexport const isDeepEqual = (a: unknown, b: unknown): boolean => {\n if (a === b) return true\n if (a === null || b === null) return false\n if (typeof a !== typeof b) return false\n\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false\n return a.every((item, i) => isDeepEqual(item, b[i]))\n }\n\n // Opaque Firestore types delegate to their own `.isEqual`. Catches\n // Timestamp, DocumentReference, GeoPoint, Bytes, VectorValue and\n // every FieldValue sentinel kind.\n if (isFirestoreOpaque(a) && isFirestoreOpaque(b)) {\n return a.isEqual(b)\n }\n\n if (isPlainObject(a) && isPlainObject(b)) {\n const keysA = Object.keys(a)\n const keysB = Object.keys(b)\n if (keysA.length !== keysB.length) return false\n return keysA.every((key) => isDeepEqual(a[key], b[key]))\n }\n\n return false\n}\n\n/**\n * Compute the minimal diff between two objects for Firestore updates.\n * Returns only the fields that changed, using deleteField() for removed fields.\n *\n * @param from - The original object (sync state)\n * @param to - The target object (local state)\n * @returns A partial object containing only changed fields\n */\nexport const computeDiff = <T extends FirestoreObject>(\n from: T,\n to: T | undefined\n): WithFieldValue<DeepPartial<T>> => {\n if (to === undefined) {\n return deleteField() as WithFieldValue<DeepPartial<T>>\n }\n\n const diff: Record<string, unknown> = {}\n\n // Check for changed or added fields\n for (const key of Object.keys(to)) {\n const fromValue = from[key]\n const toValue = to[key]\n\n // Arrays are compared by value and replaced entirely\n if (Array.isArray(toValue)) {\n if (!isDeepEqual(fromValue, toValue)) {\n diff[key] = toValue\n }\n continue\n }\n\n // Nested objects get recursive diff\n if (isPlainObject(toValue)) {\n if (!isDeepEqual(fromValue, toValue)) {\n const nestedDiff = computeDiff(\n (fromValue as Record<string, unknown>) ?? {},\n toValue\n )\n if (Object.keys(nestedDiff).length > 0) {\n diff[key] = nestedDiff\n }\n }\n continue\n }\n\n // Firestore opaque values — sentinels (serverTimestamp,\n // arrayUnion, …) and value types (Timestamp, DocumentReference,\n // …). Compare via `.isEqual` so identical-by-value Timestamps\n // don't show up as spurious diffs every sync; pass the value\n // through unchanged so sentinels survive into the write payload\n // for the server to expand.\n if (isFirestoreOpaque(toValue)) {\n if (\n !isFirestoreOpaque(fromValue) ||\n !toValue.isEqual(fromValue)\n ) {\n diff[key] = toValue\n }\n continue\n }\n\n // Primitives are compared directly\n if (toValue !== undefined && fromValue !== toValue) {\n diff[key] = toValue\n }\n }\n\n // Check for removed fields. Only `undefined` triggers a delete — `null`\n // is a valid Firestore value and is preserved via the primitive-comparison\n // branch above.\n for (const key of Object.keys(from)) {\n if (to[key] === undefined) {\n diff[key] = deleteField()\n }\n }\n\n return diff as WithFieldValue<DeepPartial<T>>\n}\n\n/**\n * Apply a Firestore diff to a target object in place (mutating).\n * Handles deleteField(), serverTimestamp(), and nested objects.\n *\n * Most code should use `applyDiff` (immutable) instead.\n * This mutable version is useful for performance-critical paths\n * where you're already working with a cloned object.\n *\n * @param target - The object to mutate\n * @param diff - The diff to apply\n */\nexport const applyDiffMutable = (\n target: FirestoreObject,\n diff: Record<string, unknown>\n): void => {\n for (const key of Object.keys(diff)) {\n const value = (diff as Record<string, unknown>)[key]\n\n // Firestore opaque values: FieldValue sentinels and value types.\n if (isFirestoreOpaque(value)) {\n // `deleteField()` is structural — actually drop the key from\n // the local view. Matches what Firestore does on commit, and\n // what `computeDiff`'s removed-field branch round-trips back\n // out to a sentinel on the next diff.\n if (isDeleteField(value)) {\n delete (target as Record<string, unknown>)[key]\n continue\n }\n // Every other opaque value (serverTimestamp, increment,\n // arrayUnion/Remove, Timestamp, DocumentReference, GeoPoint,\n // Bytes, VectorValue, …) is preserved by reference. Sentinels\n // must reach Firestore in their original form so the server\n // can expand them; value types must keep their prototype.\n //\n // `serverTimestamp()` used to be substituted with\n // `Timestamp.now()` here, which silently shipped client clock\n // time to Firestore. The optimistic-display companion lives\n // in document.ts / collection.ts as `displayOverrides`.\n ;(target as Record<string, unknown>)[key] = value\n continue\n }\n\n // Handle nested objects\n if (isPlainObject(value)) {\n const existingValue = (target as Record<string, unknown>)[key]\n if (!isPlainObject(existingValue)) {\n ;(target as Record<string, unknown>)[key] = {}\n }\n applyDiffMutable(\n (target as Record<string, unknown>)[key] as FirestoreObject,\n value as Record<string, unknown>\n )\n continue\n }\n\n // Handle primitives and arrays\n ;(target as Record<string, unknown>)[key] = value\n }\n}\n\n/**\n * Create a deep clone of an object that's safe for Firestore operations.\n *\n * Firestore opaque values (FieldValue sentinels, Timestamp,\n * DocumentReference, GeoPoint, Bytes, VectorValue) are returned **by\n * reference**. They are immutable from the user's perspective; cloning\n * them by walking keys would either lose their prototype — turning a\n * `DocumentReference` into a plain object Firestore can't recognize —\n * or destroy a sentinel that needed to reach the server intact.\n */\nexport const deepClone = <T>(value: T): T => {\n if (value === null || typeof value !== 'object') {\n return value\n }\n\n if (isFirestoreOpaque(value)) {\n return value\n }\n\n if (Array.isArray(value)) {\n return value.map(deepClone) as T\n }\n\n const result: Record<string, unknown> = {}\n for (const key of Object.keys(value)) {\n result[key] = deepClone((value as Record<string, unknown>)[key])\n }\n return result as T\n}\n\n/**\n * Check if a diff is empty (no changes)\n */\nexport const isDiffEmpty = (diff: Record<string, unknown>): boolean =>\n Object.keys(diff).length === 0\n\n/**\n * Flatten a nested diff object to dot notation for use with Firestore's updateDoc.\n *\n * This converts:\n * ```\n * { building: { floors: 5, height: 100 }, name: 'Test' }\n * ```\n * To:\n * ```\n * { 'building.floors': 5, 'building.height': 100, 'name': 'Test' }\n * ```\n *\n * Arrays, FieldValue sentinels (deleteField, serverTimestamp, …) and\n * Firestore value types (Timestamp, DocumentReference, GeoPoint, Bytes,\n * VectorValue) are NOT flattened — they're preserved at their path so\n * Firestore receives them in their original form.\n *\n * @param diff - The nested diff object\n * @param prefix - Internal: current path prefix for recursion\n * @returns Flattened object with dotted keys\n */\nexport const flattenDiff = (\n diff: Record<string, unknown>,\n prefix = ''\n): Record<string, unknown> => {\n const result: Record<string, unknown> = {}\n\n for (const key of Object.keys(diff)) {\n const value = diff[key]\n const path = prefix ? `${prefix}.${key}` : key\n\n // Arrays, FieldValue sentinels, and Firestore value types are\n // opaque from flatten's perspective — kept at the path verbatim.\n if (Array.isArray(value) || isFirestoreOpaque(value)) {\n result[path] = value\n continue\n }\n\n // Plain objects are recursively flattened\n if (isPlainObject(value)) {\n const nested = flattenDiff(value, path)\n Object.assign(result, nested)\n continue\n }\n\n // Primitives (strings, numbers, booleans, null)\n result[path] = value\n }\n\n return result\n}\n\n/**\n * Merge two diffs together, with the second taking precedence\n */\nexport const mergeDiffs = <T extends FirestoreObject>(\n first: WithFieldValue<DeepPartial<T>>,\n second: WithFieldValue<DeepPartial<T>>\n): WithFieldValue<DeepPartial<T>> => {\n const result = deepClone(first) as Record<string, unknown>\n\n for (const key of Object.keys(second)) {\n const firstValue = result[key]\n const secondValue = (second as Record<string, unknown>)[key]\n\n if (isPlainObject(firstValue) && isPlainObject(secondValue)) {\n result[key] = mergeDiffs(\n firstValue as WithFieldValue<DeepPartial<FirestoreObject>>,\n secondValue as WithFieldValue<DeepPartial<FirestoreObject>>\n )\n } else {\n result[key] = secondValue\n }\n }\n\n return result as WithFieldValue<DeepPartial<T>>\n}\n\n/**\n * Apply a diff to an object, returning a new object.\n * The original object is not modified.\n *\n * @example\n * ```ts\n * const original = { name: 'Project', count: 5 }\n * const diff = { name: 'Updated', count: deleteField() }\n * const result = applyDiff(original, diff)\n * // result = { name: 'Updated' }\n * // original is unchanged\n * ```\n */\nexport const applyDiff = <T extends FirestoreObject>(\n state: T,\n diff: WithFieldValue<DeepPartial<T>>\n): T => {\n const result = deepClone(state)\n applyDiffMutable(result, diff as Record<string, unknown>)\n return result\n}\n\n/**\n * Compute the undo diff that would reverse the effect of applying a diff to a state.\n *\n * Given a starting state and a diff that was (or will be) applied to it,\n * returns a new diff that when applied to the result would restore the original state.\n *\n * @example\n * ```ts\n * const startState = { name: 'Foo', count: 5 }\n * const diff = { name: 'Bar', count: deleteField() }\n *\n * // Apply the diff\n * const endState = applyDiff(startState, diff)\n * // endState = { name: 'Bar' }\n *\n * // Compute the undo\n * const undoDiff = computeUndoDiff(startState, diff)\n * // undoDiff = { name: 'Foo', count: 5 }\n *\n * // Applying undoDiff to endState restores startState\n * const restored = applyDiff(endState, undoDiff)\n * // restored = { name: 'Foo', count: 5 }\n * ```\n */\nexport const computeUndoDiff = <T extends FirestoreObject>(\n startState: T,\n diff: WithFieldValue<DeepPartial<T>>\n): WithFieldValue<DeepPartial<T>> => {\n const endState = applyDiff(startState, diff)\n return computeDiff(endState, startState)\n}\n\n/**\n * Check if a diff affects a specific path (supports dot notation).\n *\n * @example\n * ```ts\n * const diff = { building: { floors: 5 }, name: 'Test' }\n *\n * diffContainsPath(diff, 'name') // true\n * diffContainsPath(diff, 'building') // true\n * diffContainsPath(diff, 'building.floors') // true\n * diffContainsPath(diff, 'building.height') // false\n * diffContainsPath(diff, 'other') // false\n * ```\n */\nexport const diffContainsPath = (\n diff: Record<string, unknown>,\n path: string\n): boolean => {\n const parts = path.split('.')\n let current: unknown = diff\n\n for (const part of parts) {\n if (current === null || typeof current !== 'object') {\n return false\n }\n if (!(part in (current as Record<string, unknown>))) {\n return false\n }\n current = (current as Record<string, unknown>)[part]\n }\n\n return true\n}\n\n/**\n * Extract the value at a specific path from a diff (supports dot notation).\n * Returns undefined if the path doesn't exist in the diff.\n *\n * @example\n * ```ts\n * const diff = { building: { floors: 5, height: 100 }, name: 'Test' }\n *\n * extractDiffValue(diff, 'name') // 'Test'\n * extractDiffValue(diff, 'building') // { floors: 5, height: 100 }\n * extractDiffValue(diff, 'building.floors') // 5\n * extractDiffValue(diff, 'building.missing') // undefined\n * ```\n */\nexport const extractDiffValue = (\n diff: Record<string, unknown>,\n path: string\n): unknown => {\n const parts = path.split('.')\n let current: unknown = diff\n\n for (const part of parts) {\n if (current === null || typeof current !== 'object') {\n return undefined\n }\n if (!(part in (current as Record<string, unknown>))) {\n return undefined\n }\n current = (current as Record<string, unknown>)[part]\n }\n\n return current\n}\n\n/**\n * Create a diff that sets a value at a specific path (supports dot notation).\n *\n * @example\n * ```ts\n * createDiffAtPath('name', 'New Name')\n * // { name: 'New Name' }\n *\n * createDiffAtPath('building.floors', 5)\n * // { building: { floors: 5 } }\n *\n * createDiffAtPath('building.config.enabled', true)\n * // { building: { config: { enabled: true } } }\n * ```\n */\nexport const createDiffAtPath = (\n path: string,\n value: unknown\n): Record<string, unknown> => {\n const parts = path.split('.')\n const result: Record<string, unknown> = {}\n\n let current = result\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]\n if (part === undefined) continue\n current[part] = {}\n current = current[part] as Record<string, unknown>\n }\n\n const lastPart = parts[parts.length - 1]\n if (lastPart !== undefined) {\n current[lastPart] = value\n }\n\n return result\n}\n\n/**\n * Invert a flattened diff back to nested object structure.\n * Opposite of flattenDiff.\n *\n * @example\n * ```ts\n * const flat = { 'building.floors': 5, 'building.height': 100, 'name': 'Test' }\n * const nested = unflattenDiff(flat)\n * // { building: { floors: 5, height: 100 }, name: 'Test' }\n * ```\n */\nexport const unflattenDiff = (\n flatDiff: Record<string, unknown>\n): Record<string, unknown> => {\n const result: Record<string, unknown> = {}\n\n for (const [path, value] of Object.entries(flatDiff)) {\n const parts = path.split('.')\n\n let current = result\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]\n if (part === undefined) continue\n if (!(part in current) || typeof current[part] !== 'object') {\n current[part] = {}\n }\n current = current[part] as Record<string, unknown>\n }\n\n const lastPart = parts[parts.length - 1]\n if (lastPart !== undefined) {\n current[lastPart] = value\n }\n }\n\n return result\n}\n\n// ---------------------------------------------------------------------------\n// Display overrides\n//\n// `serverTimestamp()` sentinels need to survive `localState` so the write\n// path can ship them to Firestore for server-side expansion (C1). That\n// leaves the optimistic UI with a `FieldValue` object sitting at the\n// field — components can't render it. The display-override layer\n// captures `Timestamp.now()` at the moment a sentinel first enters\n// `localState`, stores it keyed by dotted path, and substitutes it into\n// the merged view at read time. The captured Timestamp is frozen for the\n// lifetime of the sentinel (it doesn't drift forward on re-renders), and\n// the entry is dropped automatically once the sentinel leaves\n// `localState` — either because the server ack cleared `localState`, or\n// because the user overwrote that path with an explicit value.\n//\n// Scope: only `serverTimestamp()` gets a display override. `increment`,\n// `arrayUnion`, and `arrayRemove` would need access to the SDK's\n// non-public internal fields (`_operand`, `_elements`) to compute a\n// display value; consumers using those should gate their render code on\n// the field not being a sentinel.\n// ---------------------------------------------------------------------------\n\n/**\n * Walk a state object collecting every dotted path that currently holds\n * a `serverTimestamp()` sentinel. Arrays are not traversed — Firestore\n * doesn't allow sentinels inside arrays. Non-plain objects (Timestamps,\n * DocumentReferences, …) are leaves.\n *\n * @internal\n */\nexport const collectServerTimestampPaths = (\n state: Record<string, unknown> | null | undefined,\n prefix = '',\n out: Set<string> = new Set()\n): Set<string> => {\n if (!state) return out\n for (const key of Object.keys(state)) {\n const value = state[key]\n const path = prefix ? `${prefix}.${key}` : key\n if (isServerTimestamp(value)) {\n out.add(path)\n continue\n }\n if (isPlainObject(value)) {\n collectServerTimestampPaths(value, path, out)\n }\n }\n return out\n}\n\n/**\n * Reconcile a `displayOverrides` map against the current `localState`:\n *\n * - For each path that holds a `serverTimestamp()` sentinel but has no\n * override yet, capture `Timestamp.now()` and store it (frozen-at-\n * first-sighting).\n * - For each existing override whose path no longer holds a sentinel\n * (sentinel was overwritten, or `localState` cleared on snapshot ack),\n * drop it.\n *\n * The map is mutated in place. Pass a custom `now` for deterministic\n * tests; defaults to `Timestamp.now()`.\n *\n * @internal\n */\nexport const reconcileDisplayOverrides = (\n localState: Record<string, unknown> | null | undefined,\n overrides: Map<string, unknown>,\n now: () => unknown = () => Timestamp.now()\n): void => {\n const currentPaths = collectServerTimestampPaths(localState)\n for (const path of currentPaths) {\n if (!overrides.has(path)) {\n overrides.set(path, now())\n }\n }\n for (const path of [...overrides.keys()]) {\n if (!currentPaths.has(path)) {\n overrides.delete(path)\n }\n }\n}\n\nconst setAtPath = (\n obj: Record<string, unknown>,\n path: string,\n value: unknown\n): void => {\n const parts = path.split('.')\n let cur = obj\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]!\n if (!isPlainObject(cur[part])) cur[part] = {}\n cur = cur[part] as Record<string, unknown>\n }\n cur[parts[parts.length - 1]!] = value\n}\n\n/**\n * Apply a path → value override map to a merged view, returning a new\n * object. Used by document.ts / collection.ts to substitute display\n * values for sentinels still present in `localState`.\n *\n * @internal\n */\nexport const applyOverridesAtPaths = <T extends FirestoreObject>(\n merged: T,\n overrides: ReadonlyMap<string, unknown>\n): T => {\n if (overrides.size === 0) return merged\n const result = deepClone(merged) as Record<string, unknown>\n for (const [path, value] of overrides) {\n setAtPath(result, path, value)\n }\n return result as T\n}\n","import {\n doc,\n collection,\n onSnapshot,\n setDoc,\n updateDoc,\n deleteDoc,\n DocumentReference,\n WithFieldValue,\n} from 'firebase/firestore'\nimport type {\n DeepPartial,\n DocumentDefinition,\n DocumentHandle,\n DocumentState,\n FirestoreObject,\n Subscriber,\n Unsubscribe,\n UpdateOptions,\n} from './types'\nimport type { FirestateStore } from './store'\nimport {\n applyDiffMutable,\n applyOverridesAtPaths,\n computeDiff,\n deepClone,\n flattenDiff,\n isDeepEqual,\n reconcileDisplayOverrides,\n} from './diff'\n\n// Module-level counter so each subscription instance gets a unique sync key,\n// even when multiple instances target the same document path.\nlet syncKeyCounter = 0\n\n/**\n * Options for creating a document subscription\n */\nexport interface DocumentOptions<TData extends FirestoreObject> {\n /** The store instance */\n store: FirestateStore\n /** Document definition from defineDocument() */\n definition: DocumentDefinition<TData>\n /**\n * Resolved document id. If omitted and `definition.id` is a string, that\n * value is used. If `definition.id` is a function, this option is required.\n */\n docId?: string\n /**\n * Resolved collection path. If omitted and `definition.collection` is a\n * string, that value is used. If `definition.collection` is a function,\n * this option is required.\n */\n collectionPath?: string\n /** Override read-only setting */\n readOnly?: boolean\n /** Callback for pushing undo actions */\n onPushUndo?: (\n undoAction: () => void,\n redoAction: () => void,\n options?: UpdateOptions\n ) => void\n}\n\n/**\n * Internal state for a document subscription.\n *\n * `localState` uses three distinct values:\n * - `undefined`: no pending local changes\n * - `null`: pending delete (the autosave-driven sync will issue deleteDoc)\n * - object: pending update/set (synced via updateDoc/setDoc)\n *\n * The same convention applies to `inflightLocalState`.\n */\ninterface DocumentInternalState<T extends FirestoreObject> {\n syncState: T | undefined\n localState: T | null | undefined\n isLoading: boolean\n error: Error | undefined\n waitingForUpdate: boolean\n inflightLocalState: T | null | undefined\n /** Whether the pending operation is a full set (create/replace) vs a partial update */\n isSetOperation: boolean\n /**\n * Frozen display values for `serverTimestamp()` sentinels currently\n * sitting in `localState`. Keyed by dotted path. Captured at the\n * moment a sentinel first appears, dropped when the sentinel leaves\n * `localState` (sync ack or overwrite). Substituted into the merged\n * view by `getMergedData` so consumers always see a renderable\n * `Timestamp`, never a raw FieldValue, while the write is in flight.\n */\n displayOverrides: Map<string, unknown>\n}\n\n/**\n * Create a document subscription.\n * This is a low-level API - prefer using useDocument hook in React.\n *\n * @example\n * ```ts\n * const subscription = createDocumentSubscription({\n * store,\n * definition: projectDoc,\n * docId: '123',\n * })\n *\n * const unsubscribe = subscription.subscribe((state) => {\n * console.log('Document state:', state)\n * })\n *\n * subscription.load()\n * ```\n */\nexport const createDocumentSubscription = <TData extends FirestoreObject>(\n options: DocumentOptions<TData>\n): {\n /** Attach the Firestore listener */\n load: () => void\n /** Stop the Firestore listener */\n stop: () => void\n /** Subscribe to state changes */\n subscribe: (fn: Subscriber<DocumentState<TData>>) => Unsubscribe\n /** Get current state */\n getState: () => DocumentState<TData>\n /** Get document handle for updates */\n getHandle: () => DocumentHandle<TData>\n /** Force sync now */\n sync: () => Promise<void>\n} => {\n const { store, definition, docId, collectionPath: resolvedCollectionPath, readOnly, onPushUndo } = options\n const { firestore, autosave: defaultAutosave, minLoadTime: defaultMinLoadTime } = store\n\n const {\n collection: collectionConfig,\n id,\n autosave = defaultAutosave,\n minLoadTime = defaultMinLoadTime,\n readOnly: definitionReadOnly,\n retryOnError = false,\n retryInterval = 5000,\n schema,\n } = definition\n\n const isReadOnly = readOnly ?? definitionReadOnly ?? false\n // Prefer the caller-resolved docId. Fall back to a string `definition.id`\n // for ergonomic direct use; if both are missing, the caller forgot to\n // resolve a function id and we surface that immediately.\n const documentId = docId ?? (typeof id === 'string' ? id : undefined)\n if (documentId === undefined) {\n throw new Error(\n `createDocumentSubscription: definition.id is a function; pass a resolved docId in options.`\n )\n }\n // Same shape as docId: prefer a caller-resolved path, fall back to a\n // string `definition.collection` for direct use. Function definitions\n // must come pre-resolved from the hook layer.\n const collectionPath = resolvedCollectionPath ?? (typeof collectionConfig === 'string' ? collectionConfig : undefined)\n if (collectionPath === undefined) {\n throw new Error(\n `createDocumentSubscription: definition.collection is a function; pass a resolved collectionPath in options.`\n )\n }\n\n // Create document reference\n const docRef = doc(\n collection(firestore, collectionPath),\n documentId\n ) as DocumentReference<TData>\n\n // Internal state\n const state: DocumentInternalState<TData> = {\n syncState: undefined,\n localState: undefined,\n isLoading: true,\n error: undefined,\n waitingForUpdate: false,\n inflightLocalState: undefined,\n isSetOperation: false,\n displayOverrides: new Map(),\n }\n\n const subscribers = new Set<Subscriber<DocumentState<TData>>>()\n let unsubscribeListener: Unsubscribe | null = null\n let autosaveTimeout: ReturnType<typeof setTimeout> | null = null\n let retryTimeout: ReturnType<typeof setTimeout> | null = null\n let minLoadTimeout: ReturnType<typeof setTimeout> | null = null\n let minLoadTimeElapsed = false\n let loaded = false\n // Cached handle — returns the same reference until notify() invalidates\n // it. Lets useSyncExternalStore consumers rely on handle identity.\n let cachedHandle: DocumentHandle<TData> | null = null\n\n // Unique key for sync tracking, scoped per-instance so multiple\n // subscriptions to the same path don't share (or clobber) one entry.\n const syncKey = `doc:${collectionPath}/${documentId}#${++syncKeyCounter}`\n\n const getMergedData = (): TData | undefined => {\n // null localState marks a pending delete — surface as no data.\n if (state.localState === null) return undefined\n const base = state.localState ?? state.syncState\n if (base === undefined) return undefined\n return applyOverridesAtPaths(base, state.displayOverrides)\n }\n\n const getPublicState = (): DocumentState<TData> => ({\n data: getMergedData(),\n isLoading: state.isLoading,\n isSynced: state.localState === undefined,\n error: state.error,\n })\n\n const notify = () => {\n // Reconcile display overrides against the current localState\n // before publishing — captures Timestamp.now() for any newly\n // arrived serverTimestamp() sentinel and drops entries whose\n // sentinels have been overwritten or acked away.\n reconcileDisplayOverrides(\n state.localState && typeof state.localState === 'object'\n ? (state.localState as Record<string, unknown>)\n : undefined,\n state.displayOverrides\n )\n cachedHandle = null\n const publicState = getPublicState()\n subscribers.forEach((fn) => fn(publicState))\n store.reportSyncState(syncKey, publicState.isSynced)\n }\n\n const updateState = (\n diff: WithFieldValue<DeepPartial<TData>>,\n undoOptions: UpdateOptions = {}\n ) => {\n if (isReadOnly) return\n\n const currentData = getMergedData()\n if (!currentData) {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n `[firestate] update() on ${collectionPath}/${documentId} was ignored: there is no current data to diff against. ` +\n `This happens when the document is still loading, has been deleted, or doesn't exist yet. ` +\n `Use set() to create the document, or gate update calls on a non-undefined data value.`\n )\n }\n return\n }\n\n // Use raw localState as the mutation base so serverTimestamp() sentinels\n // in localState survive into newLocalState. getMergedData() substitutes\n // display-override Timestamps at sentinel paths, which would erase the\n // sentinel from state.localState on the next update() call.\n const rawBase = (state.localState ?? state.syncState) as TData\n const newLocalState = deepClone(rawBase)\n applyDiffMutable(newLocalState, diff as Record<string, unknown>)\n\n // Push undo eagerly against the pre-mutation state. Cmd+Z within the\n // autosave window pops this entry, applies the inverse via updateState,\n // and the sync() no-op shortcut absorbs the resulting same-as-syncState\n // case without a Firestore write.\n if (undoOptions?.undoable !== false && onPushUndo) {\n const undoDiff = computeDiff(\n newLocalState as FirestoreObject,\n rawBase as FirestoreObject\n )\n const redoDiff = computeDiff(\n rawBase as FirestoreObject,\n newLocalState as FirestoreObject\n )\n onPushUndo(\n () => updateState(undoDiff as WithFieldValue<DeepPartial<TData>>, { undoable: false }),\n () => updateState(redoDiff as WithFieldValue<DeepPartial<TData>>, { undoable: false }),\n undoOptions\n )\n }\n\n state.localState = newLocalState\n state.isSetOperation = false\n\n notify()\n scheduleAutosave()\n }\n\n const setData = (data: TData, undoOptions: UpdateOptions = {}) => {\n if (isReadOnly) return\n\n // Use schema.parse as a validation guard — throw on bad input — but\n // discard the parsed result and store the caller's original object.\n // Storing parsed output would (a) silently drop unknown keys via\n // Zod's default `.strip()` behavior, and (b) cause undo/redo replay\n // through this same path to re-apply any schema transforms a second\n // time. Partial update() diffs are intentionally NOT validated:\n // diffs commonly carry Firestore sentinels (serverTimestamp,\n // arrayUnion, deleteField) that aren't representable in a strict\n // schema.\n if (schema) schema.parse(data)\n\n const currentData = getMergedData()\n\n // Push undo eagerly. A set against undefined data is a creation;\n // its undo is a delete. Otherwise we restore the prior snapshot via\n // setData, which is symmetric and handles full-replace semantics\n // (including field removals) correctly.\n if (undoOptions?.undoable !== false && onPushUndo) {\n const dataForRedo = deepClone(data)\n if (currentData === undefined) {\n onPushUndo(\n () => deleteDocument({ undoable: false }),\n () => setData(dataForRedo, { undoable: false }),\n undoOptions\n )\n } else {\n // Snapshot raw localState so the restore payload contains\n // serverTimestamp() sentinels, not the frozen Timestamps that\n // getMergedData() substitutes for display purposes.\n const dataToRestore = deepClone((state.localState ?? state.syncState) as TData)\n onPushUndo(\n () => setData(dataToRestore, { undoable: false }),\n () => setData(dataForRedo, { undoable: false }),\n undoOptions\n )\n }\n }\n\n state.localState = deepClone(data)\n state.isSetOperation = true\n\n notify()\n scheduleAutosave()\n }\n\n const deleteDocument = (undoOptions: UpdateOptions = {}) => {\n if (isReadOnly) return\n\n const currentData = getMergedData()\n // Nothing to delete — bail rather than queueing a no-op deleteDoc.\n if (currentData === undefined) return\n\n // Push undo against the pre-delete data (which includes any pending\n // local edits at this moment).\n if (undoOptions?.undoable !== false && onPushUndo) {\n // Snapshot raw localState — same reason as in setData above.\n const dataToRestore = deepClone((state.localState ?? state.syncState) as TData)\n onPushUndo(\n () => setData(dataToRestore, { undoable: false }),\n () => deleteDocument({ undoable: false }),\n undoOptions\n )\n }\n\n // Mark localState as a pending delete and let scheduleAutosave drive\n // the actual deleteDoc call — same flow as set/update.\n state.localState = null\n state.isSetOperation = false\n\n notify()\n scheduleAutosave()\n }\n\n const scheduleAutosave = () => {\n if (autosaveTimeout) {\n clearTimeout(autosaveTimeout)\n }\n if (autosave > 0) {\n autosaveTimeout = setTimeout(() => {\n sync()\n }, autosave)\n }\n }\n\n const sync = async () => {\n if (state.localState === undefined) return\n\n // Pending delete — issue deleteDoc and let the listener confirm via\n // handleMissingDocument. Undo was already pushed at mutation time.\n if (state.localState === null) {\n state.inflightLocalState = null\n state.waitingForUpdate = true\n\n try {\n await deleteDoc(docRef)\n } catch (error) {\n console.error('Sync failed:', error)\n state.waitingForUpdate = false\n state.inflightLocalState = undefined\n state.error = error as Error\n store.reportError(error as Error, {\n type: 'document',\n path: `${collectionPath}/${documentId}`,\n operation: 'write',\n })\n notify()\n }\n return\n }\n\n // No-op if local state already matches what the server holds. This is\n // the path that an undo-of-pending-local takes: the inverse update\n // brings localState back to syncState, and we exit without a write.\n if (state.syncState && isDeepEqual(state.localState, state.syncState)) {\n state.localState = undefined\n state.inflightLocalState = undefined\n notify()\n return\n }\n\n const wasSetOperation = state.isSetOperation\n state.isSetOperation = false\n\n // A creation occurs when there's no server state to diff against —\n // either the user explicitly called set() to create the document, or\n // the listener has reported the doc as missing. In both cases we use\n // setDoc.\n const isCreation = !state.syncState\n const useSetDoc = wasSetOperation || isCreation\n\n const diff = state.syncState\n ? computeDiff(state.syncState, state.localState)\n : undefined\n\n state.inflightLocalState = deepClone(state.localState)\n\n state.waitingForUpdate = true\n\n try {\n if (useSetDoc) {\n // Full set / creation — use setDoc to create or completely\n // replace the document.\n await setDoc(docRef, state.localState as TData)\n } else {\n // Partial update - use updateDoc with flattened diff to prevent\n // accidentally recreating deleted documents. updateDoc will fail\n // if the document doesn't exist.\n const flatDiff = flattenDiff(diff as Record<string, unknown>)\n await updateDoc(docRef, flatDiff)\n }\n } catch (error) {\n console.error('Sync failed:', error)\n state.waitingForUpdate = false\n state.inflightLocalState = undefined\n // Surface to React: handle.error reflects the failure and the\n // listener will keep state.localState so consumers can retry by\n // calling sync() or by issuing another update. Autosave is not\n // automatically rescheduled to avoid retry loops on permission\n // errors — that policy is left to the consumer.\n state.error = error as Error\n store.reportError(error as Error, {\n type: 'document',\n path: `${collectionPath}/${documentId}`,\n operation: 'write',\n })\n notify()\n }\n }\n\n const handleSnapshot = (newSyncData: TData) => {\n state.syncState = newSyncData\n // A successful snapshot supersedes any previous read or write error.\n state.error = undefined\n\n if (state.waitingForUpdate) {\n state.waitingForUpdate = false\n const inflightState = state.inflightLocalState\n state.inflightLocalState = undefined\n const currentLocal = state.localState\n\n if (inflightState === null) {\n // Inflight was a delete but the listener fired with the doc\n // still present. The deleteDoc result is still in flight (or\n // failed and reverted). Leave localState alone — it's either\n // still null (we still want the delete) or non-null (user\n // changed their mind), and either way the next sync handles it.\n } else if (currentLocal === null) {\n // User issued a delete while a set/update was inflight. The\n // pending delete is the latest intent; preserve it for the\n // next sync.\n } else if (\n inflightState &&\n currentLocal &&\n !isDeepEqual(currentLocal, inflightState)\n ) {\n // Rebase local changes onto the new sync state.\n const changesSinceInflight = computeDiff(inflightState, currentLocal)\n const rebasedLocalState = deepClone(newSyncData)\n applyDiffMutable(rebasedLocalState, changesSinceInflight as Record<string, unknown>)\n state.localState = rebasedLocalState\n } else {\n state.localState = undefined\n }\n }\n\n if (minLoadTimeElapsed) {\n state.isLoading = false\n }\n loaded = true\n\n // If local edits exist and aren't currently being synced, schedule an\n // autosave. Covers the case where set() ran before the first snapshot\n // arrived and the initial sync attempt bailed early.\n if (state.localState !== undefined) {\n scheduleAutosave()\n }\n\n notify()\n }\n\n // A document that does not exist is not an error condition — consumers\n // commonly use that state to render a \"create\" UI. Clear loading and\n // leave error undefined; data will be undefined via getMergedData().\n const handleMissingDocument = () => {\n state.syncState = undefined\n state.error = undefined\n\n // The only localState that should clear when the doc goes missing is\n // our own pending-delete marker. Any other pending edits (object\n // value) represent the user's intent to recreate the doc — the next\n // sync() will issue a setDoc against the now-missing doc and create\n // it from scratch.\n if (state.localState === null) {\n state.localState = undefined\n state.isSetOperation = false\n if (autosaveTimeout) {\n clearTimeout(autosaveTimeout)\n autosaveTimeout = null\n }\n }\n\n if (state.waitingForUpdate) {\n state.waitingForUpdate = false\n state.inflightLocalState = undefined\n }\n if (minLoadTimeElapsed) {\n state.isLoading = false\n }\n loaded = true\n notify()\n }\n\n const handleError = (error: Error) => {\n if (retryOnError) {\n console.warn('Document listener error, retrying:', error)\n retryTimeout = setTimeout(() => {\n stop()\n load()\n }, retryInterval)\n } else {\n state.error = error\n // Don't leave consumers stuck on a loading spinner — the listener\n // has reported a terminal error, so loading is done.\n state.isLoading = false\n loaded = true\n store.reportError(error, {\n type: 'document',\n path: `${collectionPath}/${documentId}`,\n operation: 'read',\n })\n notify()\n }\n }\n\n const load = () => {\n if (unsubscribeListener) return\n\n loaded = false\n minLoadTimeElapsed = false\n\n unsubscribeListener = onSnapshot(\n docRef,\n (snapshot) => {\n if (snapshot.exists()) {\n handleSnapshot(snapshot.data())\n } else if (!snapshot.metadata.fromCache) {\n handleMissingDocument()\n }\n },\n handleError\n )\n\n // Min load time handler — tracked so stop() can cancel it.\n minLoadTimeout = setTimeout(() => {\n minLoadTimeout = null\n if (loaded) {\n state.isLoading = false\n notify()\n }\n minLoadTimeElapsed = true\n }, minLoadTime)\n }\n\n const stop = () => {\n if (unsubscribeListener) {\n unsubscribeListener()\n unsubscribeListener = null\n }\n if (autosaveTimeout) {\n clearTimeout(autosaveTimeout)\n autosaveTimeout = null\n }\n if (retryTimeout) {\n clearTimeout(retryTimeout)\n retryTimeout = null\n }\n if (minLoadTimeout) {\n clearTimeout(minLoadTimeout)\n minLoadTimeout = null\n }\n // Drop this subscription's entry from the global sync-state map so\n // an unmounted hook does not leave useIsSynced stuck at false.\n store.unregisterSyncState(syncKey)\n }\n\n const subscribe = (fn: Subscriber<DocumentState<TData>>): Unsubscribe => {\n subscribers.add(fn)\n return () => subscribers.delete(fn)\n }\n\n const buildHandle = (): DocumentHandle<TData> => ({\n data: getMergedData(),\n update: updateState,\n set: setData,\n delete: deleteDocument,\n isLoading: state.isLoading,\n isSynced: state.localState === undefined,\n sync,\n error: state.error,\n ref: docRef,\n })\n\n const getHandle = (): DocumentHandle<TData> => {\n if (cachedHandle === null) {\n cachedHandle = buildHandle()\n }\n return cachedHandle\n }\n\n return {\n load,\n stop,\n subscribe,\n getState: getPublicState,\n getHandle,\n sync,\n }\n}\n","import {\n collection,\n doc,\n onSnapshot,\n query,\n writeBatch,\n deleteField,\n WithFieldValue,\n QueryConstraint,\n type CollectionReference,\n} from 'firebase/firestore'\nimport type {\n CollectionDefinition,\n CollectionHandle,\n CollectionState,\n DeepPartial,\n FirestoreObject,\n Subscriber,\n Unsubscribe,\n UpdateOptions,\n} from './types'\nimport type { FirestateStore } from './store'\nimport {\n applyDiffMutable,\n applyOverridesAtPaths,\n computeDiff,\n deepClone,\n flattenDiff,\n isDeepEqual,\n reconcileDisplayOverrides,\n} from './diff'\n\n// Module-level counter so each subscription instance gets a unique sync key,\n// even when multiple instances target the same collection path.\nlet syncKeyCounter = 0\n\n/**\n * Options for creating a collection subscription\n */\nexport interface CollectionOptions<TData extends FirestoreObject> {\n /** The store instance */\n store: FirestateStore\n /** Collection definition from defineCollection() */\n definition: CollectionDefinition<TData>\n /**\n * Resolved collection path. If omitted and `definition.path` is a string,\n * that value is used. If `definition.path` is a function, this option is\n * required.\n */\n collectionPath?: string\n /** Override read-only setting */\n readOnly?: boolean\n /** Additional query constraints */\n queryConstraints?: QueryConstraint[]\n /** Callback for pushing undo actions */\n onPushUndo?: (\n undoAction: () => void,\n redoAction: () => void,\n options?: UpdateOptions\n ) => void\n}\n\n/**\n * Internal state for a collection subscription\n */\ninterface CollectionInternalState<T extends FirestoreObject> {\n syncState: Record<string, T> | undefined\n localState: Record<string, T> | undefined\n isLoading: boolean\n isActive: boolean\n error: Error | undefined\n waitingForUpdate: boolean\n inflightLocalState: Record<string, T> | undefined\n /**\n * Frozen display values for `serverTimestamp()` sentinels currently\n * sitting in `localState`. Keyed by dotted path (e.g.\n * `\"<docId>.updatedAt\"`). See document.ts for the full contract.\n */\n displayOverrides: Map<string, unknown>\n}\n\n/**\n * Create a collection subscription.\n * This is a low-level API - prefer using useCollection hook in React.\n *\n * @example\n * ```ts\n * const subscription = createCollectionSubscription({\n * store,\n * definition: spacesCollection,\n * collectionPath: 'projects/123/spaces',\n * })\n *\n * const unsubscribe = subscription.subscribe((state) => {\n * console.log('Collection state:', state)\n * })\n *\n * subscription.load() // For lazy collections\n * ```\n */\nexport const createCollectionSubscription = <TData extends FirestoreObject>(\n options: CollectionOptions<TData>\n): {\n /** Activate the subscription (for lazy loading) */\n load: () => void\n /** Stop the Firestore listener */\n stop: () => void\n /** Subscribe to state changes */\n subscribe: (fn: Subscriber<CollectionState<TData>>) => Unsubscribe\n /** Get current state */\n getState: () => CollectionState<TData>\n /** Get collection handle for updates */\n getHandle: () => CollectionHandle<TData>\n /** Force sync now */\n sync: () => Promise<void>\n} => {\n const { store, definition, collectionPath: resolvedPath, readOnly, queryConstraints: extraConstraints, onPushUndo } = options\n const { firestore, autosave: defaultAutosave, minLoadTime: defaultMinLoadTime } = store\n\n const {\n path,\n autosave = defaultAutosave,\n minLoadTime = defaultMinLoadTime,\n readOnly: definitionReadOnly,\n lazy = false,\n queryConstraints: definitionConstraints,\n retryOnError = false,\n retryInterval = 5000,\n schema,\n } = definition\n\n const isReadOnly = readOnly ?? definitionReadOnly ?? false\n // Prefer the caller-resolved path. Fall back to a string `definition.path`\n // for ergonomic direct use; if both are missing, the caller forgot to\n // resolve a function path.\n const collectionPath = resolvedPath ?? (typeof path === 'string' ? path : undefined)\n if (collectionPath === undefined) {\n throw new Error(\n `createCollectionSubscription: definition.path is a function; pass a resolved collectionPath in options.`\n )\n }\n const allConstraints = [...(definitionConstraints ?? []), ...(extraConstraints ?? [])]\n\n // Create collection reference\n const collectionRef = collection(firestore, collectionPath) as CollectionReference<TData>\n\n // Internal state\n const state: CollectionInternalState<TData> = {\n syncState: undefined,\n localState: undefined,\n isLoading: !lazy,\n isActive: !lazy,\n error: undefined,\n waitingForUpdate: false,\n inflightLocalState: undefined,\n displayOverrides: new Map(),\n }\n\n const subscribers = new Set<Subscriber<CollectionState<TData>>>()\n let unsubscribeListener: Unsubscribe | null = null\n let autosaveTimeout: ReturnType<typeof setTimeout> | null = null\n let minLoadTimeout: ReturnType<typeof setTimeout> | null = null\n let retryTimeout: ReturnType<typeof setTimeout> | null = null\n let minLoadTimeElapsed = false\n let loaded = false\n // Cached handle — returns the same reference until notify() invalidates\n // it. Lets useSyncExternalStore consumers rely on handle identity.\n let cachedHandle: CollectionHandle<TData> | null = null\n\n // Unique key for sync tracking, scoped per-instance so multiple\n // subscriptions to the same path don't share (or clobber) one entry.\n const syncKey = `col:${collectionPath}#${++syncKeyCounter}`\n\n const getMergedData = (): Record<string, TData> => {\n const base = state.localState ?? state.syncState ?? {}\n return applyOverridesAtPaths(base, state.displayOverrides)\n }\n\n const getPublicState = (): CollectionState<TData> => ({\n data: getMergedData(),\n isLoading: state.isLoading,\n isSynced: state.localState === undefined,\n isActive: state.isActive,\n error: state.error,\n })\n\n const notify = () => {\n // Reconcile display overrides against the current localState\n // before publishing — see document.ts for the full contract.\n reconcileDisplayOverrides(\n state.localState as Record<string, unknown> | undefined,\n state.displayOverrides\n )\n cachedHandle = null\n const publicState = getPublicState()\n subscribers.forEach((fn) => fn(publicState))\n store.reportSyncState(syncKey, publicState.isSynced)\n }\n\n // Pre-snapshot mutations are unsafe because computing a partial-update\n // local state without knowing the existing server fields would cause the\n // subsequent diff to mark unrelated fields as deleted. Document mutations\n // bail the same way when there's no current data.\n const warnNoSnapshot = (method: string) => {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n `[firestate] ${method}() on ${collectionPath} was ignored: the first snapshot has not arrived yet. ` +\n `Gate calls on the collection's isLoading/isActive state, or await the first data before mutating.`\n )\n }\n }\n\n const updateState = (\n diff: WithFieldValue<DeepPartial<Record<string, TData>>>,\n undoOptions: UpdateOptions = {}\n ) => {\n if (isReadOnly) return\n if (state.syncState === undefined) {\n warnNoSnapshot('update')\n return\n }\n\n // Use raw localState as the mutation base so serverTimestamp() sentinels\n // in localState survive into newLocalState. getMergedData() substitutes\n // display-override Timestamps at sentinel paths, which would erase the\n // sentinel from state.localState on the next update() call.\n const rawBase = state.localState ?? state.syncState ?? {}\n const newLocalState = deepClone(rawBase)\n applyDiffMutable(newLocalState, diff as Record<string, unknown>)\n\n // Ensure each document has its id\n for (const [docId, docData] of Object.entries(newLocalState)) {\n if (docData && typeof docData === 'object') {\n ;(docData as Record<string, unknown>).id = docId\n }\n }\n\n // Push undo eagerly against the pre-mutation snapshot. Cmd+Z within\n // the autosave window pops this entry, applies the inverse via\n // updateState, and the sync() no-op shortcut absorbs the resulting\n // same-as-syncState case without a Firestore write.\n if (undoOptions?.undoable !== false && onPushUndo) {\n const undoDiff = computeDiff(\n newLocalState as FirestoreObject,\n rawBase as FirestoreObject\n )\n const redoDiff = computeDiff(\n rawBase as FirestoreObject,\n newLocalState as FirestoreObject\n )\n onPushUndo(\n () => updateState(undoDiff as WithFieldValue<DeepPartial<Record<string, TData>>>, { undoable: false }),\n () => updateState(redoDiff as WithFieldValue<DeepPartial<Record<string, TData>>>, { undoable: false }),\n undoOptions\n )\n }\n\n state.localState = newLocalState\n\n notify()\n scheduleAutosave()\n }\n\n // Overloaded: callers can pass (id, data, opts) or (data, opts). The\n // no-id form generates a Firestore auto-id via doc(collectionRef).id and\n // returns it so the caller can reference the new doc immediately.\n // Returns undefined when the mutation is dropped so callers can't\n // accidentally route on or persist an id that was never queued.\n function addDocument(\n id: string,\n data: Omit<TData, 'id'>,\n undoOptions?: UpdateOptions\n ): string | undefined\n function addDocument(\n data: Omit<TData, 'id'>,\n undoOptions?: UpdateOptions\n ): string | undefined\n function addDocument(\n idOrData: string | Omit<TData, 'id'>,\n dataOrOptions?: Omit<TData, 'id'> | UpdateOptions,\n maybeUndoOptions?: UpdateOptions\n ): string | undefined {\n const hasExplicitId = typeof idOrData === 'string'\n const data = (hasExplicitId ? dataOrOptions : idOrData) as Omit<TData, 'id'>\n const undoOptions = (hasExplicitId\n ? maybeUndoOptions\n : (dataOrOptions as UpdateOptions | undefined)) ?? {}\n\n if (isReadOnly) return undefined\n if (state.syncState === undefined) {\n // Bail rather than queueing: an explicit id that happens to exist\n // on the server would round-trip through computeDiff and clobber\n // any remote-only fields, and we have no way to know without a\n // first snapshot.\n warnNoSnapshot('add')\n return undefined\n }\n\n // Only allocate an auto-id once we know we're going to queue the\n // write — otherwise the caller might persist an id that was dropped.\n const id = hasExplicitId ? (idOrData as string) : doc(collectionRef).id\n\n // Use schema.parse as a validation guard — throw on bad input — but\n // discard the parsed result and store the caller's original object\n // with id attached. Storing parsed output would silently drop\n // unknown keys via Zod's default `.strip()` and re-transform on\n // undo/redo replay. We feed `{ ...data, id }` to parse so the same\n // validation works whether the user's schema declares `id` or not.\n const newDoc = { ...data, id } as unknown as TData\n if (schema) schema.parse(newDoc)\n\n const currentData = getMergedData()\n const newLocalState = deepClone(currentData)\n newLocalState[id] = newDoc\n\n // Push undo eagerly. Inverse diff deletes the just-added doc.\n if (undoOptions?.undoable !== false && onPushUndo) {\n const undoDiff = computeDiff(\n newLocalState as FirestoreObject,\n currentData as FirestoreObject\n )\n const redoDiff = computeDiff(\n currentData as FirestoreObject,\n newLocalState as FirestoreObject\n )\n onPushUndo(\n () => updateState(undoDiff as WithFieldValue<DeepPartial<Record<string, TData>>>, { undoable: false }),\n () => updateState(redoDiff as WithFieldValue<DeepPartial<Record<string, TData>>>, { undoable: false }),\n undoOptions\n )\n }\n\n state.localState = newLocalState\n\n notify()\n scheduleAutosave()\n\n return id\n }\n\n const removeDocument = (id: string, undoOptions: UpdateOptions = {}) => {\n if (isReadOnly) return\n if (state.syncState === undefined) {\n warnNoSnapshot('remove')\n return\n }\n\n const currentData = getMergedData()\n if (!(id in currentData)) return\n\n const newLocalState = deepClone(currentData)\n delete newLocalState[id]\n\n // Push undo eagerly. Inverse diff re-adds the removed doc.\n if (undoOptions?.undoable !== false && onPushUndo) {\n const undoDiff = computeDiff(\n newLocalState as FirestoreObject,\n currentData as FirestoreObject\n )\n const redoDiff = computeDiff(\n currentData as FirestoreObject,\n newLocalState as FirestoreObject\n )\n onPushUndo(\n () => updateState(undoDiff as WithFieldValue<DeepPartial<Record<string, TData>>>, { undoable: false }),\n () => updateState(redoDiff as WithFieldValue<DeepPartial<Record<string, TData>>>, { undoable: false }),\n undoOptions\n )\n }\n\n state.localState = newLocalState\n\n notify()\n scheduleAutosave()\n }\n\n const scheduleAutosave = () => {\n if (autosaveTimeout) {\n clearTimeout(autosaveTimeout)\n }\n if (autosave > 0) {\n autosaveTimeout = setTimeout(() => {\n sync()\n }, autosave)\n }\n }\n\n const sync = async () => {\n if (!state.localState) return\n // syncState is guaranteed defined here: every mutation that can set\n // localState bails when syncState is undefined. This guard is purely\n // defensive against a direct sync() call after stop().\n if (state.syncState === undefined) return\n\n const syncState = state.syncState\n\n if (isDeepEqual(state.localState, syncState)) {\n state.localState = undefined\n state.inflightLocalState = undefined\n notify()\n return\n }\n\n const diff = computeDiff(\n syncState as FirestoreObject,\n state.localState as FirestoreObject\n )\n state.inflightLocalState = deepClone(state.localState)\n\n state.waitingForUpdate = true\n\n try {\n const batch = writeBatch(firestore)\n const deleteFieldSentinel = deleteField()\n\n for (const [docId, docDiff] of Object.entries(diff)) {\n const docRef = doc(collectionRef, docId)\n\n // Check if this is a delete operation\n if (\n docDiff !== null &&\n typeof docDiff === 'object' &&\n 'isEqual' in docDiff &&\n typeof docDiff.isEqual === 'function' &&\n (docDiff as { isEqual: (v: unknown) => boolean }).isEqual(deleteFieldSentinel)\n ) {\n batch.delete(docRef)\n } else if (!(docId in syncState)) {\n // New document - use set to create it\n batch.set(docRef, docDiff as Record<string, unknown>)\n } else {\n // Existing document - use update with flattened diff to prevent\n // accidentally recreating deleted documents\n const flatDiff = flattenDiff(docDiff as Record<string, unknown>)\n batch.update(docRef, flatDiff)\n }\n }\n\n await batch.commit()\n } catch (error) {\n console.error('Collection sync failed:', error)\n state.waitingForUpdate = false\n state.inflightLocalState = undefined\n // Surface to React: handle.error reflects the failure and the\n // listener will keep state.localState so consumers can retry by\n // calling sync(). Autosave is not automatically rescheduled to\n // avoid retry loops on permission errors.\n state.error = error as Error\n store.reportError(error as Error, {\n type: 'collection',\n path: collectionPath,\n operation: 'write',\n })\n notify()\n }\n }\n\n const handleSnapshot = (docs: Array<{ id: string; data: TData }>) => {\n const newSyncState: Record<string, TData> = {}\n for (const { id, data } of docs) {\n newSyncState[id] = { ...data, id } as TData\n }\n\n state.syncState = newSyncState\n // A successful snapshot supersedes any previous read or write error.\n state.error = undefined\n\n if (state.waitingForUpdate) {\n state.waitingForUpdate = false\n const inflightState = state.inflightLocalState\n state.inflightLocalState = undefined\n const currentLocal = state.localState\n\n // Rebase local changes if they changed since we started the sync\n if (\n inflightState &&\n currentLocal &&\n !isDeepEqual(currentLocal, inflightState)\n ) {\n const changesSinceInflight = computeDiff(\n inflightState as FirestoreObject,\n currentLocal as FirestoreObject\n )\n const rebasedLocalState = deepClone(newSyncState)\n applyDiffMutable(rebasedLocalState, changesSinceInflight as Record<string, unknown>)\n // Re-add ids\n for (const [docId, docData] of Object.entries(rebasedLocalState)) {\n if (docData && typeof docData === 'object') {\n ;(docData as Record<string, unknown>).id = docId\n }\n }\n state.localState = rebasedLocalState\n } else {\n state.localState = undefined\n }\n }\n\n if (minLoadTimeElapsed) {\n state.isLoading = false\n }\n loaded = true\n\n // If a rebase produced fresh local edits, ensure they flush. The\n // user's update() during the inflight write already scheduled an\n // autosave, so this is mostly defensive.\n if (state.localState !== undefined) {\n scheduleAutosave()\n }\n\n notify()\n }\n\n const handleError = (error: Error) => {\n if (retryOnError) {\n console.warn('Collection listener error, retrying:', error)\n retryTimeout = setTimeout(() => {\n stop()\n startListener()\n }, retryInterval)\n } else {\n state.error = error\n // Don't leave consumers stuck on a loading spinner — the listener\n // has reported a terminal error, so loading is done.\n state.isLoading = false\n loaded = true\n store.reportError(error, {\n type: 'collection',\n path: collectionPath,\n operation: 'read',\n })\n notify()\n }\n }\n\n const startListener = () => {\n if (unsubscribeListener) return\n\n loaded = false\n minLoadTimeElapsed = false\n\n const q = allConstraints.length > 0\n ? query(collectionRef, ...allConstraints)\n : collectionRef\n\n unsubscribeListener = onSnapshot(\n q,\n (snapshot) => {\n const docs = snapshot.docs.map((docSnap) => ({\n id: docSnap.id,\n data: docSnap.data() as TData,\n }))\n handleSnapshot(docs)\n },\n handleError\n )\n\n // Min load time handler — tracked so stop() can cancel it.\n minLoadTimeout = setTimeout(() => {\n minLoadTimeout = null\n if (loaded) {\n state.isLoading = false\n notify()\n }\n minLoadTimeElapsed = true\n }, minLoadTime)\n }\n\n const load = () => {\n // Listener-level idempotency so the hook can safely call load() on\n // every mount (including Strict Mode's mount-cleanup-remount cycle).\n if (unsubscribeListener) return\n if (!state.isActive) {\n state.isActive = true\n state.isLoading = true\n notify()\n }\n startListener()\n }\n\n const stop = () => {\n if (retryTimeout) {\n clearTimeout(retryTimeout)\n retryTimeout = null\n }\n if (unsubscribeListener) {\n unsubscribeListener()\n unsubscribeListener = null\n }\n if (autosaveTimeout) {\n clearTimeout(autosaveTimeout)\n autosaveTimeout = null\n }\n if (minLoadTimeout) {\n clearTimeout(minLoadTimeout)\n minLoadTimeout = null\n }\n // Drop this subscription's entry from the global sync-state map so\n // an unmounted hook does not leave useIsSynced stuck at false.\n store.unregisterSyncState(syncKey)\n }\n\n const subscribe = (fn: Subscriber<CollectionState<TData>>): Unsubscribe => {\n subscribers.add(fn)\n return () => subscribers.delete(fn)\n }\n\n const buildHandle = (): CollectionHandle<TData> => ({\n data: getMergedData(),\n update: updateState,\n add: addDocument,\n remove: removeDocument,\n isLoading: state.isLoading,\n isSynced: state.localState === undefined,\n isActive: state.isActive,\n load,\n sync,\n error: state.error,\n ref: collectionRef,\n })\n\n const getHandle = (): CollectionHandle<TData> => {\n if (cachedHandle === null) {\n cachedHandle = buildHandle()\n }\n return cachedHandle\n }\n\n // No constructor-side auto-start: callers (the hook for non-lazy, or\n // users directly for lazy) invoke load() to attach the listener. This\n // keeps subscription creation side-effect-free, matching document.ts.\n\n return {\n load,\n stop,\n subscribe,\n getState: getPublicState,\n getHandle,\n sync,\n }\n}\n","import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from \"react\";\nimport type { QueryConstraint } from \"firebase/firestore\";\nimport type {\n CollectionDefinition,\n CollectionHandle,\n DocumentDefinition,\n DocumentHandle,\n FirestoreObject,\n UndoManager,\n UndoManagerState,\n UpdateOptions,\n} from \"./types\";\nimport type { FirestateStore } from \"./store\";\nimport { createDocumentSubscription } from \"./document\";\nimport { createCollectionSubscription } from \"./collection\";\n\n/**\n * Returned when a hook is called with `enabled: false`. Module-level constants\n * so getSnapshot returns a stable reference and useSyncExternalStore doesn't\n * re-render. Cast at the call site to the generic handle type — every method\n * is a no-op so the cast is sound.\n */\nconst NOOP = () => {};\nconst ASYNC_NOOP = async () => {};\nconst EMPTY_RECORD: Record<string, never> = {};\n\nconst DISABLED_DOCUMENT_HANDLE: DocumentHandle<FirestoreObject> = {\n data: undefined,\n update: NOOP,\n set: NOOP,\n delete: NOOP,\n isLoading: false,\n isSynced: true,\n sync: ASYNC_NOOP,\n error: undefined,\n ref: undefined,\n};\n\n// The disabled add() satisfies both overloads but performs no work and\n// returns undefined to match the bail-path contract from collection.ts.\n// Consumers using `enabled: false` should not be calling mutation methods\n// on the disabled handle.\nconst DISABLED_ADD = () => undefined;\n\nconst DISABLED_COLLECTION_HANDLE: CollectionHandle<FirestoreObject> = {\n data: EMPTY_RECORD,\n update: NOOP,\n add: DISABLED_ADD,\n remove: NOOP,\n isLoading: false,\n isSynced: true,\n isActive: false,\n load: NOOP,\n sync: ASYNC_NOOP,\n error: undefined,\n ref: undefined,\n};\n\n/**\n * Context for providing the Firestate store\n */\nexport const FirestateContext = createContext<FirestateStore | null>(null);\n\n/**\n * Hook to access the Firestate store\n */\nexport const useStore = (): FirestateStore => {\n const store = useContext(FirestateContext);\n if (!store) {\n throw new Error(\"useStore must be used within a FirestateProvider\");\n }\n return store;\n};\n\n/**\n * Hook to access the undo manager\n */\nexport const useUndoManager = (): UndoManager => {\n const store = useStore();\n const { undoManager } = store;\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => undoManager.subscribe(onStoreChange),\n [undoManager]\n );\n\n // Delegate to the manager's cached snapshot so getSnapshot returns a stable\n // reference across React's multiple per-commit calls. Building the snapshot\n // inline here would create a new object every call and trip the\n // \"getSnapshot should be cached\" warning + an infinite re-render loop.\n const getSnapshot = useCallback(\n (): UndoManagerState => undoManager.getState(),\n [undoManager]\n );\n\n const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n return useMemo(\n () => ({\n ...state,\n push: undoManager.push,\n undo: undoManager.undo,\n redo: undoManager.redo,\n clear: undoManager.clear,\n }),\n [state, undoManager]\n );\n};\n\n/**\n * Hook to check if all tracked resources are synced\n */\nexport const useIsSynced = (): boolean => {\n const store = useStore();\n\n const subscribe = useCallback(\n (onChange: () => void) => store.subscribeToSyncState(() => onChange()),\n [store]\n );\n\n const getSnapshot = useCallback(() => store.isSynced, [store]);\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n};\n\n/**\n * Options for useDocument hook\n */\nexport interface UseDocumentOptions<TData extends FirestoreObject> {\n /** Document definition from defineDocument() */\n definition: DocumentDefinition<TData>;\n /** Route/path parameters for dynamic paths */\n params?: Record<string, string>;\n /** Override read-only setting */\n readOnly?: boolean;\n /** Enable undo/redo for this document (default: true) */\n undoable?: boolean;\n /**\n * If false, no subscription is created and a no-op handle is returned\n * (`{ data: undefined, isLoading: false, isSynced: true, ref: undefined }`).\n * Use this to gate subscriptions on route params that aren't ready yet.\n * Default: true.\n */\n enabled?: boolean;\n}\n\n/**\n * Hook to subscribe to a Firestore document with real-time updates.\n *\n * The subscription is keyed on the resolved document path (`definition` +\n * computed id) and `readOnly`. When that key changes — typically because\n * `params` produces a different id — the hook tears down the old Firestore\n * listener and attaches a new one. Toggling `undoable` does not rebuild the\n * subscription.\n *\n * Use `enabled: false` to suppress the subscription entirely (e.g., when\n * route params aren't ready yet).\n *\n * **SSR.** On the server there is no Firestore listener, so this hook returns\n * the initial handle (`{ data: undefined, isLoading: true }`). Mutations like\n * `update`/`set` will mutate orphaned local state with no effect — avoid\n * calling them server-side.\n *\n * @example\n * ```tsx\n * const projectDoc = defineDocument<Project>({\n * collection: 'projects',\n * id: (params) => params.projectId,\n * })\n *\n * function ProjectEditor({ projectId }: { projectId: string }) {\n * const { data, update, isLoading, isSynced } = useDocument({\n * definition: projectDoc,\n * params: { projectId },\n * })\n *\n * if (isLoading) return <Spinner />\n *\n * return (\n * <input\n * value={data?.name ?? ''}\n * onChange={(e) => update({ name: e.target.value })}\n * />\n * )\n * }\n * ```\n */\nexport const useDocument = <TData extends FirestoreObject>(\n options: UseDocumentOptions<TData>\n): DocumentHandle<TData> => {\n const {\n definition,\n params = {},\n readOnly,\n undoable = true,\n enabled = true,\n } = options;\n const store = useStore();\n const undoManager = store.undoManager;\n\n // Hold the latest `undoable` in a ref so the onPushUndo callback can stay\n // referentially stable. Without this, every undoable toggle would tear\n // down the Firestore listener and re-attach it for no good reason.\n const undoableRef = useRef(undoable);\n undoableRef.current = undoable;\n\n const onPushUndo = useCallback(\n (undoAction: () => void, redoAction: () => void, opts?: UpdateOptions) => {\n if (!undoableRef.current) return;\n undoManager.push({\n undo: undoAction,\n redo: redoAction,\n groupId: opts?.undoGroupId,\n });\n },\n [undoManager]\n );\n\n // Resolve the doc id and collection path at render time. When disabled we\n // skip resolution — consumers commonly pass `enabled: false` precisely\n // because params aren't ready and definition.id(params) would fail.\n const docId = enabled\n ? typeof definition.id === \"function\"\n ? definition.id(params)\n : definition.id\n : undefined;\n\n const collectionPath = enabled\n ? typeof definition.collection === \"function\"\n ? definition.collection(params)\n : definition.collection\n : undefined;\n\n const subscription = useMemo(\n () =>\n enabled && docId !== undefined && collectionPath !== undefined\n ? createDocumentSubscription({\n store,\n definition,\n docId,\n collectionPath,\n readOnly,\n onPushUndo,\n })\n : null,\n [enabled, store, definition, docId, collectionPath, readOnly, onPushUndo]\n );\n\n const subscribe = useCallback(\n (onChange: () => void) => {\n if (!subscription) return NOOP;\n const unsub = subscription.subscribe(() => onChange());\n subscription.load();\n return () => {\n unsub();\n subscription.stop();\n };\n },\n [subscription]\n );\n\n const getSnapshot = useCallback(\n () =>\n subscription\n ? subscription.getHandle()\n : (DISABLED_DOCUMENT_HANDLE as DocumentHandle<TData>),\n [subscription]\n );\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n};\n\n/**\n * Options for useCollection hook\n */\nexport interface UseCollectionOptions<TData extends FirestoreObject> {\n /** Collection definition from defineCollection() */\n definition: CollectionDefinition<TData>;\n /** Route/path parameters for dynamic paths */\n params?: Record<string, string>;\n /** Override read-only setting */\n readOnly?: boolean;\n /** Additional query constraints */\n queryConstraints?: QueryConstraint[];\n /** Enable undo/redo for this collection (default: true) */\n undoable?: boolean;\n /**\n * If false, no subscription is created and a no-op handle is returned\n * (`{ data: {}, isLoading: false, isActive: false }`). Use this to gate on\n * route params that aren't ready yet. Default: true.\n */\n enabled?: boolean;\n}\n\n/**\n * Hook to subscribe to a Firestore collection with real-time updates.\n *\n * The subscription is keyed on the resolved collection path, `readOnly`, and\n * the `queryConstraints` reference. When any of these change, the listener\n * is torn down and re-attached with the new query. Toggling `undoable` does\n * not rebuild the subscription.\n *\n * **Memoize `queryConstraints`.** An inline array (`queryConstraints={[where(...)]}`)\n * creates a new reference every render, which will thrash the listener.\n * Wrap in `useMemo` with the underlying filter values as deps.\n *\n * Use `enabled: false` to suppress the subscription entirely (e.g., when\n * route params aren't ready yet).\n *\n * **SSR.** On the server there is no Firestore listener, so this hook returns\n * the initial handle (`{ data: {}, isLoading: true }` for non-lazy, or\n * `isActive: false` for lazy). Avoid calling mutations server-side.\n *\n * @example\n * ```tsx\n * const spacesCollection = defineCollection<Space>({\n * path: (params) => `projects/${params.projectId}/spaces`,\n * lazy: true,\n * })\n *\n * function SpacesList({ projectId }: { projectId: string }) {\n * const { data, update, load, isActive, isLoading } = useCollection({\n * definition: spacesCollection,\n * params: { projectId },\n * })\n *\n * // Lazy load on mount\n * useEffect(() => { load() }, [load])\n *\n * if (!isActive) return <Button onClick={load}>Load Spaces</Button>\n * if (isLoading) return <Spinner />\n *\n * return (\n * <ul>\n * {Object.values(data).map((space) => (\n * <li key={space.id}>{space.name}</li>\n * ))}\n * </ul>\n * )\n * }\n * ```\n */\nexport const useCollection = <TData extends FirestoreObject>(\n options: UseCollectionOptions<TData>\n): CollectionHandle<TData> => {\n const {\n definition,\n params = {},\n readOnly,\n queryConstraints,\n undoable = true,\n enabled = true,\n } = options;\n const store = useStore();\n const undoManager = store.undoManager;\n\n const undoableRef = useRef(undoable);\n undoableRef.current = undoable;\n\n const onPushUndo = useCallback(\n (undoAction: () => void, redoAction: () => void, opts?: UpdateOptions) => {\n if (!undoableRef.current) return;\n undoManager.push({\n undo: undoAction,\n redo: redoAction,\n groupId: opts?.undoGroupId,\n });\n },\n [undoManager]\n );\n\n // Resolve the collection path at render time. When disabled we skip\n // resolution — consumers commonly pass `enabled: false` precisely because\n // params aren't ready.\n const collectionPath = enabled\n ? typeof definition.path === \"function\"\n ? definition.path(params)\n : definition.path\n : undefined;\n\n const subscription = useMemo(\n () =>\n enabled && collectionPath !== undefined\n ? createCollectionSubscription({\n store,\n definition,\n collectionPath,\n readOnly,\n queryConstraints,\n onPushUndo,\n })\n : null,\n [\n enabled,\n store,\n definition,\n collectionPath,\n readOnly,\n queryConstraints,\n onPushUndo,\n ]\n );\n\n const isLazy = definition.lazy ?? false;\n\n const subscribe = useCallback(\n (onChange: () => void) => {\n if (!subscription) return NOOP;\n const unsub = subscription.subscribe(() => onChange());\n if (!isLazy) {\n subscription.load();\n }\n return () => {\n unsub();\n subscription.stop();\n };\n },\n [subscription, isLazy]\n );\n\n const getSnapshot = useCallback(\n () =>\n subscription\n ? subscription.getHandle()\n : (DISABLED_COLLECTION_HANDLE as CollectionHandle<TData>),\n [subscription]\n );\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n};\n\n/**\n * Keyboard shortcut hook for undo/redo\n *\n * @example\n * ```tsx\n * function App() {\n * useUndoKeyboardShortcuts()\n * return <YourApp />\n * }\n * ```\n */\nexport const useUndoKeyboardShortcuts = (): void => {\n // Read the manager ref directly — we only need .undo() / .redo() (stable\n // refs), not its state. Subscribing via useUndoManager would re-render\n // the host component on every undo-stack change.\n const undoManager = useStore().undoManager;\n\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n const platform =\n (\n navigator as Navigator & {\n userAgentData?: { platform: string };\n }\n ).userAgentData?.platform ?? navigator.platform;\n const isMac = platform.toUpperCase().includes(\"MAC\");\n const modifier = isMac ? e.metaKey : e.ctrlKey;\n\n if (!modifier) return;\n\n if (e.key === \"z\" && !e.shiftKey) {\n e.preventDefault();\n undoManager.undo();\n } else if ((e.key === \"z\" && e.shiftKey) || e.key === \"y\") {\n e.preventDefault();\n undoManager.redo();\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [undoManager]);\n};\n","/**\n * Registry-driven Firestate API.\n *\n * Declare every document and collection in a single object and let the\n * library generate the typed read/write hooks. Replaces hand-writing a\n * `useSpaces`, `useWallTypes`, ... hook per Firestore collection.\n *\n * ```ts\n * interface TaskList { name: string; createdAt: number }\n * interface Task { title: string; completed: boolean }\n *\n * export const { useTaskList, useTasks } = createFirestate({\n * taskList: doc<TaskList>('taskLists/{listId}'),\n * tasks: col<Task>('taskLists/{listId}/tasks'),\n * })\n *\n * // At the call site:\n * const taskList = useTaskList({ listId })\n * const tasks = useTasks({ listId })\n * ```\n */\nimport { defineDocument, defineCollection } from \"./schema\";\nimport {\n useDocument,\n useCollection,\n type UseDocumentOptions,\n type UseCollectionOptions,\n} from \"./hooks\";\nimport type {\n CollectionDefinition,\n CollectionHandle,\n DocumentDefinition,\n DocumentHandle,\n FirestoreObject,\n} from \"./types\";\nimport type { QueryConstraint } from \"firebase/firestore\";\nimport type { ZodType, z } from \"zod\";\n\n/**\n * Knobs forwarded from a generated document hook to {@link useDocument}.\n * Same shape as `UseDocumentOptions` minus the fields the registry already\n * owns (`definition`, `params`).\n */\nexport type DocHookOptions<T extends FirestoreObject> = Omit<\n UseDocumentOptions<T>,\n \"definition\" | \"params\"\n>;\n\n/**\n * Knobs forwarded from a generated collection hook to {@link useCollection}.\n */\nexport type ColHookOptions<T extends FirestoreObject> = Omit<\n UseCollectionOptions<T>,\n \"definition\" | \"params\"\n>;\n\n// ---------------------------------------------------------------------------\n// Registry entry shapes\n// ---------------------------------------------------------------------------\n\ninterface CommonEntryOptions {\n /** Debounce interval for autosave (ms). */\n autosave?: number;\n /** Minimum loading indicator time (ms). */\n minLoadTime?: number;\n /** Whether this entry is read-only. */\n readOnly?: boolean;\n /** Retry the snapshot listener on transient errors. */\n retryOnError?: boolean;\n /** Retry interval (ms). */\n retryInterval?: number;\n}\n\n/**\n * Document entry in a Firestate registry. Produced by {@link doc}.\n *\n * The `P` generic carries the path template's string-literal type so the\n * generated hook can type-check param keys. `__kind` is a runtime\n * discriminator; `__type` is a phantom field used purely for inference at\n * the call site and is never read.\n */\nexport interface DocEntry<\n T extends FirestoreObject,\n P extends string = string\n> extends CommonEntryOptions {\n readonly __kind: \"document\";\n readonly __type?: T;\n /** Path template, e.g. `'taskLists/{listId}'`. */\n path: P;\n /**\n * Zod schema. **Required** — firestate's registry API is opinionated\n * about Zod. The schema is the source of `T` for the generated hooks\n * via `z.infer`, and firestate runs `schema.parse(...)` on full-payload\n * writes (`set`/`add`) so bad data throws at the call site rather than\n * after a Firestore round trip. Partial `update(diff)` is NOT validated\n * (diffs frequently contain Firestore sentinels like `serverTimestamp()`).\n *\n * If you don't want a schema at all, use {@link defineDocument} directly —\n * the escape hatch keeps the plain-TypeScript form at the cost of looser\n * param typing and no runtime validation.\n */\n schema: ZodType<T>;\n}\n\n/** Collection entry in a Firestate registry. Produced by {@link col}. */\nexport interface ColEntry<\n T extends FirestoreObject,\n P extends string = string\n> extends CommonEntryOptions {\n readonly __kind: \"collection\";\n readonly __type?: T;\n /** Path template, e.g. `'taskLists/{listId}/tasks'`. */\n path: P;\n /** Zod schema. Required. See {@link DocEntry.schema}. */\n schema: ZodType<T>;\n /** Only subscribe when `load()` is called. */\n lazy?: boolean;\n /** Additional Firestore query constraints. */\n queryConstraints?: QueryConstraint[];\n}\n\nexport type FirestateEntry<\n T extends FirestoreObject = FirestoreObject,\n P extends string = string\n> = DocEntry<T, P> | ColEntry<T, P>;\n\nexport type FirestateRegistry = Record<string, FirestateEntry<any, any>>;\n\n// ---------------------------------------------------------------------------\n// Path → params extraction\n// ---------------------------------------------------------------------------\n\n/**\n * Extract `{name}` placeholders from a path template into a params shape.\n *\n * - `'users'` → `{}`\n * - `'users/{userId}'` → `{ userId: string }`\n * - `'projects/{projectId}/revisions/{revisionId}'` → `{ projectId: string; revisionId: string }`\n *\n * When the path is widened to `string` (no literal preserved), we fall\n * back to `Record<string, string>` so existing call sites keep compiling.\n */\nexport type ParamsOf<P extends string> = string extends P\n ? Record<string, string>\n : Prettify<RawParamsOf<P>>;\n\ntype RawParamsOf<P extends string> =\n P extends `${string}{${infer K}}${infer Rest}`\n ? { [Key in K]: string } & RawParamsOf<Rest>\n : {};\n\n// Force TS to evaluate intersections so error messages show\n// `{ projectId: string; revisionId: string }` instead of an intersection.\ntype Prettify<T> = { [K in keyof T]: T[K] } & {};\n\n// ---------------------------------------------------------------------------\n// Entry factories\n// ---------------------------------------------------------------------------\n\ntype DocOpts<T extends FirestoreObject> = Omit<DocEntry<T>, \"__kind\" | \"__type\" | \"path\">;\ntype ColOpts<T extends FirestoreObject> = Omit<ColEntry<T>, \"__kind\" | \"__type\" | \"path\">;\n\n/**\n * Declare a single-document entry for a Firestate registry.\n *\n * **A Zod `schema` field is required.** Both the data type (`T`) and the\n * path's literal type (`P`) are inferred from the call — `T` via\n * `z.infer<S>`, `P` from `path` — so the generated hook can statically\n * type-check the params object the caller passes. The schema also runs\n * at runtime on full-payload writes (`set`/`add`).\n *\n * If you'd rather not provide a schema at all, use {@link defineDocument}\n * directly — that escape hatch keeps the plain-TypeScript form, at the\n * cost of looser param typing on the hook and no runtime validation.\n *\n * ```ts\n * import { z } from 'zod'\n *\n * const TaskListSchema = z.object({ name: z.string(), createdAt: z.number() })\n * doc({ path: 'taskLists/{listId}', schema: TaskListSchema })\n * // → DocEntry<{ name: string; createdAt: number }, 'taskLists/{listId}'>\n * ```\n */\nexport function doc<\n S extends ZodType<FirestoreObject>,\n const P extends string = string\n>(\n opts: Omit<DocOpts<z.infer<S>>, \"schema\"> & {\n schema: S;\n path: P;\n }\n): DocEntry<z.infer<S>, P> {\n const { path, ...rest } = opts;\n validateTemplate(path);\n // Bail at registration time if the path can't be split into a non-empty\n // collection + id — same loud-at-the-boundary spirit as interpolate.\n splitDocPath(path);\n return { __kind: \"document\", path, ...rest } as unknown as DocEntry<\n z.infer<S>,\n P\n >;\n}\n\n/**\n * Declare a collection entry for a Firestate registry. See {@link doc}\n * for the schema/typing contract.\n */\nexport function col<\n S extends ZodType<FirestoreObject>,\n const P extends string = string\n>(\n opts: Omit<ColOpts<z.infer<S>>, \"schema\"> & {\n schema: S;\n path: P;\n }\n): ColEntry<z.infer<S>, P> {\n const { path, ...rest } = opts;\n validateTemplate(path);\n return { __kind: \"collection\", path, ...rest } as unknown as ColEntry<\n z.infer<S>,\n P\n >;\n}\n\n// ---------------------------------------------------------------------------\n// createFirestate\n// ---------------------------------------------------------------------------\n\ntype HookName<K extends string> = `use${Capitalize<K>}`;\n\n// If the path template has no placeholders, `params` is optional (any\n// caller-supplied object is fine). When the template has placeholders,\n// the caller must pass an object with exactly the extracted keys.\ntype HookFor<E> = E extends DocEntry<infer T, infer P>\n ? keyof ParamsOf<P> extends never\n ? (\n params?: Record<string, string>,\n options?: DocHookOptions<T>\n ) => DocumentHandle<T>\n : (\n params: ParamsOf<P>,\n options?: DocHookOptions<T>\n ) => DocumentHandle<T>\n : E extends ColEntry<infer T, infer P>\n ? keyof ParamsOf<P> extends never\n ? (\n params?: Record<string, string>,\n options?: ColHookOptions<T>\n ) => CollectionHandle<T>\n : (\n params: ParamsOf<P>,\n options?: ColHookOptions<T>\n ) => CollectionHandle<T>\n : never;\n\nexport type FirestateApi<R extends FirestateRegistry> = {\n [K in keyof R & string as HookName<K>]: HookFor<R[K]>;\n};\n\n/**\n * Turn a Firestate registry into a map of typed React hooks. Each entry\n * `K` produces a hook named `use{Capitalize<K>}`.\n *\n * ```ts\n * export const { useTaskList, useTasks } = createFirestate({\n * taskList: doc<TaskList>('taskLists/{listId}'),\n * tasks: col<Task>('taskLists/{listId}/tasks'),\n * })\n * ```\n */\nexport function createFirestate<R extends FirestateRegistry>(\n registry: R\n): FirestateApi<R> {\n const api: Record<string, unknown> = {};\n\n for (const key of Object.keys(registry)) {\n if (!isValidKey(key)) {\n throw new Error(\n `[firestate] registry key \"${key}\" must start with a letter and contain only letters, digits, _ or $`\n );\n }\n const entry = registry[key]!;\n const hookName = toHookName(key);\n\n if (entry.__kind === \"document\") {\n const definition = buildDocumentDefinition(entry);\n api[hookName] = (\n params: Record<string, string> = {},\n options: DocHookOptions<FirestoreObject> = {}\n ) => useDocument({ ...options, definition, params });\n } else {\n const definition = buildCollectionDefinition(entry);\n api[hookName] = (\n params: Record<string, string> = {},\n options: ColHookOptions<FirestoreObject> = {}\n ) => useCollection({ ...options, definition, params });\n }\n }\n\n return api as FirestateApi<R>;\n}\n\n/**\n * Build the underlying {@link DocumentDefinition} for a registry doc entry.\n * Exported for unit testing — registry consumers should call\n * {@link createFirestate} instead.\n *\n * @internal\n */\nexport function buildDocumentDefinition<T extends FirestoreObject>(\n entry: DocEntry<T>\n): DocumentDefinition<T> {\n const { collectionPath, idTemplate } = splitDocPath(entry.path);\n // Both halves are functions so any `{param}` placeholder in the\n // collection portion (e.g. `projects/{projectId}/revisions`) is\n // resolved per-call against the params passed to the hook.\n return defineDocument<T>({\n schema: entry.schema,\n collection: (params) => interpolate(collectionPath, params),\n id: (params) => interpolate(idTemplate, params),\n autosave: entry.autosave,\n minLoadTime: entry.minLoadTime,\n readOnly: entry.readOnly,\n retryOnError: entry.retryOnError,\n retryInterval: entry.retryInterval,\n } as DocumentDefinition<T>);\n}\n\n/**\n * Build the underlying {@link CollectionDefinition} for a registry col entry.\n *\n * @internal\n */\nexport function buildCollectionDefinition<T extends FirestoreObject>(\n entry: ColEntry<T>\n): CollectionDefinition<T> {\n return defineCollection<T>({\n schema: entry.schema,\n path: (params) => interpolate(entry.path, params),\n autosave: entry.autosave,\n minLoadTime: entry.minLoadTime,\n readOnly: entry.readOnly,\n lazy: entry.lazy,\n queryConstraints: entry.queryConstraints,\n retryOnError: entry.retryOnError,\n retryInterval: entry.retryInterval,\n } as CollectionDefinition<T>);\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers (also exported for testing)\n// ---------------------------------------------------------------------------\n\nconst VALID_KEY = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n\nfunction isValidKey(key: string): boolean {\n return VALID_KEY.test(key);\n}\n\nfunction toHookName(key: string): string {\n return `use${key[0]!.toUpperCase()}${key.slice(1)}`;\n}\n\n/**\n * Replace `{name}` placeholders in a path template with values from `params`.\n * Throws if a placeholder is missing from `params` — failing loud at the\n * boundary is better than silently building a `taskLists/undefined/tasks`\n * URL and getting a useless Firestore error later.\n *\n * @internal\n */\nexport function interpolatePath(\n template: string,\n params: Record<string, string>\n): string {\n return interpolate(template, params);\n}\n\n// Matches a single `{name}` placeholder where the name is a valid JS-ish\n// identifier (letter or underscore start, then letters/digits/underscores).\n// Used both to interpolate and to validate templates up front.\nconst PLACEHOLDER = /\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g;\n\n/**\n * Validate that a path template uses only well-formed `{name}` placeholders\n * — no unclosed braces, no hyphens/dots inside placeholders, no `{1}` style\n * digit-leading names. Throws at definition time so a typo in the template\n * fails loud at `doc()` / `col()`, not three layers deep when a component\n * mounts.\n */\nfunction validateTemplate(template: string): void {\n // Strip the well-formed placeholders, then look for any stray `{` or `}` —\n // those signal a malformed (unclosed or weirdly-spelled) placeholder.\n const stripped = template.replace(PLACEHOLDER, \"\");\n if (stripped.includes(\"{\") || stripped.includes(\"}\")) {\n throw new Error(\n `[firestate] path \"${template}\" contains a malformed placeholder. ` +\n `Placeholders must look like \"{name}\" where name starts with a letter or underscore.`\n );\n }\n}\n\nfunction interpolate(template: string, params: Record<string, string>): string {\n return template.replace(PLACEHOLDER, (_, key) => {\n const v = params[key];\n if (v === undefined) {\n throw new Error(\n `[firestate] missing param \"${key}\" for path \"${template}\"`\n );\n }\n if (v === \"\") {\n // An empty value would silently produce `taskLists//tasks`, which\n // Firestore later rejects with an opaque \"Document path must not be\n // empty\" — keep the friendly error at the boundary.\n throw new Error(\n `[firestate] param \"${key}\" for path \"${template}\" must not be an empty string`\n );\n }\n return v;\n });\n}\n\n/**\n * Split a document path template into a collection path and an id template.\n * `'taskLists/{listId}'` → `{ collectionPath: 'taskLists', idTemplate: '{listId}' }`.\n *\n * @internal\n */\nexport function splitDocPath(path: string): {\n collectionPath: string;\n idTemplate: string;\n} {\n const lastSlash = path.lastIndexOf(\"/\");\n if (lastSlash === -1) {\n throw new Error(\n `[firestate] document path \"${path}\" must contain at least one '/' separating the collection from the document id`\n );\n }\n const collectionPath = path.slice(0, lastSlash);\n const idTemplate = path.slice(lastSlash + 1);\n if (collectionPath === \"\" || idTemplate === \"\") {\n throw new Error(\n `[firestate] document path \"${path}\" must have non-empty collection and id segments`\n );\n }\n return { collectionPath, idTemplate };\n}\n","import type { Subscriber, Unsubscribe, UndoAction, UndoManager, UndoManagerState } from './types'\n\n/**\n * Configuration for creating an undo manager\n */\nexport interface UndoManagerConfig {\n /** Maximum number of undo actions to keep, default 20 */\n maxLength?: number\n /** Callback when navigation is requested (for path-aware undo) */\n onNavigate?: (path: string) => void\n}\n\n/**\n * Create an undo manager instance.\n * This is a standalone, framework-agnostic implementation.\n *\n * @example\n * ```ts\n * const undoManager = createUndoManager({ maxLength: 10 })\n *\n * undoManager.push({\n * undo: () => restoreOldValue(),\n * redo: () => applyNewValue(),\n * description: 'Update project name',\n * })\n *\n * await undoManager.undo() // Calls restoreOldValue()\n * await undoManager.redo() // Calls applyNewValue()\n * ```\n */\nexport const createUndoManager = (\n config: UndoManagerConfig = {}\n): UndoManager & {\n subscribe: (fn: Subscriber<UndoManagerState>) => Unsubscribe\n getState: () => UndoManagerState\n} => {\n const { maxLength = 20, onNavigate } = config\n\n let undoStack: UndoAction[] = []\n let redoStack: UndoAction[] = []\n const subscribers = new Set<Subscriber<UndoManagerState>>()\n // Cached snapshot — returns the same reference until notify() invalidates\n // it. Required so React's useSyncExternalStore consumers (useUndoManager)\n // see a stable snapshot across the multiple getSnapshot() calls React\n // makes per commit; otherwise the inequality on Object.is triggers an\n // infinite re-render and the \"getSnapshot should be cached\" warning.\n let cachedState: UndoManagerState | null = null\n\n const getState = (): UndoManagerState => {\n if (cachedState === null) {\n cachedState = {\n undoStack,\n redoStack,\n canUndo: undoStack.length > 0,\n canRedo: redoStack.length > 0,\n }\n }\n return cachedState\n }\n\n const notify = () => {\n cachedState = null\n const state = getState()\n subscribers.forEach((fn) => fn(state))\n }\n\n const push = (action: UndoAction) => {\n // Check if we should merge with previous action (same groupId)\n if (action.groupId && undoStack.length > 0) {\n const last = undoStack[undoStack.length - 1]\n if (last?.groupId === action.groupId) {\n // Pop and merge. Undo walks the group newest→oldest so each\n // step reverses its specific change in reverse order; redo\n // walks oldest→newest to re-apply in original order. The\n // previous (older) order produced incorrect cumulative state\n // whenever grouped actions touched the same field.\n undoStack.pop()\n undoStack.push({\n undo: async () => {\n await action.undo()\n await last.undo()\n },\n redo: async () => {\n await last.redo()\n await action.redo()\n },\n groupId: action.groupId,\n path: action.path ?? last.path,\n description: action.description ?? last.description,\n })\n // Clear redo stack on any new action\n redoStack = []\n notify()\n return\n }\n }\n\n undoStack.push(action)\n\n // Enforce max length\n if (undoStack.length > maxLength) {\n undoStack.shift()\n }\n\n // Clear redo stack on any new action\n redoStack = []\n notify()\n }\n\n const undo = async () => {\n const action = undoStack.pop()\n if (!action) return\n\n // Navigate if path is set\n if (action.path && onNavigate) {\n onNavigate(action.path)\n }\n\n try {\n await action.undo()\n redoStack.push(action)\n } catch (error) {\n // Put it back on undo stack if it failed\n undoStack.push(action)\n console.error('Undo failed:', error)\n throw error\n }\n\n notify()\n }\n\n const redo = async () => {\n const action = redoStack.pop()\n if (!action) return\n\n // Navigate if path is set\n if (action.path && onNavigate) {\n onNavigate(action.path)\n }\n\n try {\n await action.redo()\n undoStack.push(action)\n\n // Enforce max length\n if (undoStack.length > maxLength) {\n undoStack.shift()\n }\n } catch (error) {\n // Put it back on redo stack if it failed\n redoStack.push(action)\n console.error('Redo failed:', error)\n throw error\n }\n\n notify()\n }\n\n const clear = () => {\n undoStack = []\n redoStack = []\n notify()\n }\n\n const subscribe = (fn: Subscriber<UndoManagerState>): Unsubscribe => {\n subscribers.add(fn)\n return () => subscribers.delete(fn)\n }\n\n return {\n get undoStack() {\n return undoStack\n },\n get redoStack() {\n return redoStack\n },\n get canUndo() {\n return undoStack.length > 0\n },\n get canRedo() {\n return redoStack.length > 0\n },\n push,\n undo,\n redo,\n clear,\n subscribe,\n getState,\n }\n}\n\n/**\n * Type for the undo manager with subscription capability\n */\nexport type UndoManagerWithSubscribe = ReturnType<typeof createUndoManager>\n","import type { Firestore } from 'firebase/firestore'\nimport type { ErrorContext, FirestateConfig, Subscriber, Unsubscribe } from './types'\nimport { createUndoManager, type UndoManagerWithSubscribe } from './undo'\n\n/**\n * Firestate store that holds configuration and shared state\n */\nexport interface FirestateStore {\n /** Firestore instance */\n readonly firestore: Firestore\n /** Undo manager instance */\n readonly undoManager: UndoManagerWithSubscribe\n /** Default autosave interval (ms) */\n readonly autosave: number\n /** Default minimum load time (ms) */\n readonly minLoadTime: number\n /** Report an error */\n reportError: (error: Error, context: ErrorContext) => void\n /**\n * Replace the error handler at runtime. Used by FirestateProvider to keep\n * the store identity stable when consumers pass an inline `onError`\n * callback that changes reference on every render.\n */\n setOnError: (handler?: (error: Error, context: ErrorContext) => void) => void\n /**\n * Replace the navigation handler at runtime. Used by FirestateProvider to\n * keep the store identity stable when consumers pass an inline `onNavigate`\n * callback that changes reference on every render.\n */\n setOnNavigate: (handler?: (path: string) => void) => void\n /** Subscribe to sync state changes */\n subscribeToSyncState: (fn: Subscriber<boolean>) => Unsubscribe\n /** Report a document/collection sync state change */\n reportSyncState: (key: string, isSynced: boolean) => void\n /**\n * Remove a sync-state key. Subscriptions call this on stop() so an\n * unmounted hook does not leave the global isSynced stuck at false.\n */\n unregisterSyncState: (key: string) => void\n /** Get whether all tracked resources are synced */\n readonly isSynced: boolean\n}\n\n/**\n * Create a Firestate store.\n * This is the central configuration point for your Firestore state management.\n *\n * @example\n * ```ts\n * import { createStore } from 'firestate'\n * import { db } from './firebase'\n *\n * export const store = createStore({\n * firestore: db,\n * autosave: 1000,\n * maxUndoLength: 20,\n * onError: (error, context) => {\n * console.error(`Error in ${context.type} ${context.path}:`, error)\n * },\n * })\n * ```\n */\nexport const createStore = (config: FirestateConfig): FirestateStore => {\n const {\n firestore,\n autosave = 1000,\n minLoadTime = 0,\n maxUndoLength = 20,\n } = config\n\n // Mutable so the provider can update them without re-creating the store.\n let onError = config.onError\n let onNavigate = config.onNavigate\n\n const undoManager = createUndoManager({\n maxLength: maxUndoLength,\n // Stable wrapper — delegates to the mutable onNavigate ref so the\n // undo manager doesn't need to be recreated when the callback changes.\n onNavigate: (path) => onNavigate?.(path),\n })\n\n // Track sync state of all documents/collections\n const syncStates = new Map<string, boolean>()\n const syncSubscribers = new Set<Subscriber<boolean>>()\n\n const computeIsSynced = (): boolean => {\n for (const synced of syncStates.values()) {\n if (!synced) return false\n }\n return true\n }\n\n const notifySyncSubscribers = () => {\n const isSynced = computeIsSynced()\n syncSubscribers.forEach((fn) => fn(isSynced))\n }\n\n return {\n firestore,\n undoManager,\n autosave,\n minLoadTime,\n\n reportError: (error, context) => {\n if (onError) {\n onError(error, context)\n } else {\n console.error(\n `Firestate error in ${context.type} ${context.path} during ${context.operation}:`,\n error\n )\n }\n },\n\n setOnError: (handler) => {\n onError = handler\n },\n\n setOnNavigate: (handler) => {\n onNavigate = handler\n },\n\n subscribeToSyncState: (fn) => {\n syncSubscribers.add(fn)\n return () => syncSubscribers.delete(fn)\n },\n\n reportSyncState: (key, isSynced) => {\n const prev = syncStates.get(key)\n if (prev !== isSynced) {\n syncStates.set(key, isSynced)\n notifySyncSubscribers()\n }\n },\n\n unregisterSyncState: (key) => {\n const prev = syncStates.get(key)\n if (prev === undefined) return\n syncStates.delete(key)\n // Removing a `false` entry can flip global isSynced to true.\n if (prev === false) {\n notifySyncSubscribers()\n }\n },\n\n get isSynced() {\n return computeIsSynced()\n },\n }\n}\n\n/**\n * Type alias for the store type\n */\nexport type Store = ReturnType<typeof createStore>\n","import React, {\n useCallback,\n useEffect,\n useMemo,\n useSyncExternalStore,\n} from \"react\";\nimport type { Firestore } from \"firebase/firestore\";\nimport { createStore, type FirestateStore } from \"./store\";\nimport { FirestateContext } from \"./hooks\";\nimport type { ErrorContext } from \"./types\";\n\n/**\n * Props for FirestateProvider\n */\nexport interface FirestateProviderProps {\n /** Firestore instance */\n firestore: Firestore;\n /** Default autosave interval (ms), default 1000 */\n autosave?: number;\n /** Default minimum load time (ms), default 0 */\n minLoadTime?: number;\n /** Maximum undo stack length, default 20 */\n maxUndoLength?: number;\n /**\n * Called before undo/redo when the action carries a `path`. Wire your\n * router's `navigate` here to return users to where a change occurred\n * before reverting it.\n *\n * @example\n * ```tsx\n * import { useNavigate } from 'react-router-dom'\n *\n * function App() {\n * const navigate = useNavigate()\n * return (\n * <FirestateProvider onNavigate={(path) => navigate(path)}>\n * {children}\n * </FirestateProvider>\n * )\n * }\n * ```\n */\n onNavigate?: (path: string) => void;\n /** Custom error handler */\n onError?: (error: Error, context: ErrorContext) => void;\n /** React children */\n children: React.ReactNode;\n}\n\n/**\n * Provider component that sets up Firestate for your application.\n *\n * @example\n * ```tsx\n * import { FirestateProvider } from 'firestate'\n * import { db } from './firebase'\n *\n * function App() {\n * return (\n * <FirestateProvider\n * firestore={db}\n * autosave={1000}\n * maxUndoLength={20}\n * onError={(error, ctx) => console.error(ctx.path, error)}\n * >\n * <YourApp />\n * </FirestateProvider>\n * )\n * }\n * ```\n */\nexport const FirestateProvider: React.FC<FirestateProviderProps> = ({\n firestore,\n autosave = 1000,\n minLoadTime = 0,\n maxUndoLength = 20,\n onError,\n onNavigate,\n children,\n}) => {\n // onError and onNavigate are intentionally excluded from the deps so that\n // inline callbacks (new reference per render) do not re-create the store and\n // drop every existing subscription. The store exposes setOnError /\n // setOnNavigate so the latest handlers can be applied without store\n // re-creation.\n const store = useMemo(\n () =>\n createStore({\n firestore,\n autosave,\n minLoadTime,\n maxUndoLength,\n onError,\n onNavigate,\n }),\n [firestore, autosave, minLoadTime, maxUndoLength]\n );\n\n useEffect(() => {\n store.setOnError(onError);\n }, [store, onError]);\n\n useEffect(() => {\n store.setOnNavigate(onNavigate);\n }, [store, onNavigate]);\n\n return (\n <FirestateContext.Provider value={store}>\n {children}\n </FirestateContext.Provider>\n );\n};\n\n/**\n * Props for using an existing store\n */\nexport interface FirestateStoreProviderProps {\n /** Pre-created store instance */\n store: FirestateStore;\n /** React children */\n children: React.ReactNode;\n}\n\n/**\n * Provider that uses an existing store instance.\n * Useful when you need to create the store outside of React.\n *\n * @example\n * ```tsx\n * const store = createStore({ firestore: db })\n *\n * function App() {\n * return (\n * <FirestateStoreProvider store={store}>\n * <YourApp />\n * </FirestateStoreProvider>\n * )\n * }\n * ```\n */\nexport const FirestateStoreProvider: React.FC<FirestateStoreProviderProps> = ({\n store,\n children,\n}) => (\n <FirestateContext.Provider value={store}>\n {children}\n </FirestateContext.Provider>\n);\n\n/**\n * Hook to use navigation blocker when there are unsaved changes.\n * Works with react-router or similar routers.\n *\n * @example\n * ```tsx\n * function ProjectPage() {\n * const shouldBlock = useUnsavedChangesBlocker()\n *\n * // Use with react-router's useBlocker\n * const blocker = useBlocker(\n * ({ currentLocation, nextLocation }) =>\n * currentLocation.pathname !== nextLocation.pathname && shouldBlock\n * )\n *\n * return (\n * <>\n * <ProjectEditor />\n * {blocker.state === 'blocked' && (\n * <Dialog>Your changes may not be saved!</Dialog>\n * )}\n * </>\n * )\n * }\n * ```\n */\nexport const useUnsavedChangesBlocker = (): boolean => {\n const store = React.useContext(FirestateContext);\n\n const subscribe = useCallback(\n (onChange: () => void) =>\n store ? store.subscribeToSyncState(() => onChange()) : () => {},\n [store]\n );\n\n const getSnapshot = useCallback(\n () => (store ? !store.isSynced : false),\n [store]\n );\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hvakr/firestate",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Firestore state management for React with real-time sync, undo/redo, optimistic updates, and Zod schema validation",
5
5
  "author": "HVAKR",
6
6
  "license": "MIT",
@@ -31,13 +31,13 @@
31
31
  "prepublishOnly": "npm run build"
32
32
  },
33
33
  "peerDependencies": {
34
- "firebase": "^10.0.0 || ^11.0.0",
34
+ "firebase": "^10.0.0 || ^11.0.0 || ^12.0.0",
35
35
  "react": "^18.0.0 || ^19.0.0",
36
36
  "zod": "^4.0.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/react": "^18.3.12",
40
- "firebase": "^11.1.0",
40
+ "firebase": "^12.0.0",
41
41
  "prettier": "^3.8.3",
42
42
  "react": "^18.3.1",
43
43
  "tsdown": "^0.20.0-beta.3",