{"version":3,"file":"index.mjs","names":["syncKeyCounter","doc","doc","query"],"sources":["../src/registry/schema.ts","../src/utils/diff.ts","../src/core/collection.ts","../src/core/document.ts","../src/core/shared-subscription.ts","../src/react/hooks.ts","../src/registry/firestate.ts","../src/utils/shallow.ts","../src/utils/undo.ts","../src/core/store.ts","../src/react/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    FieldPath,\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 * Equality used solely to decide whether a write is a no-op (§3 of the\n * update-logic rewrite). Purpose-built to close the \"Maximum update depth\"\n * render loop, where a write whose result equals the current view must produce\n * NO new state identity. It differs from {@link isDeepEqual} in exactly the two\n * places that let the loop survive a naive guard:\n *\n * - `NaN` ≡ `NaN` (via `Object.is`). `computeDiff` compares primitives with\n *   `!==`, so a field that is `NaN` on both sides emits a spurious diff.\n * - An explicit-`undefined` key ≡ a missing key (`{x: undefined}` ≡ `{}`).\n *   `isDeepEqual` rejects these on a keys-length mismatch.\n *\n * Standalone ON PURPOSE — do NOT fold this into `isDeepEqual`. `computeDiff`\n * and `computeUndoDiff` depend on `isDeepEqual`'s current `NaN !== NaN` and\n * strict keys-length semantics; relaxing them there would change the wire\n * payload. Firestore opaque values (Timestamp, sentinels, refs) delegate to\n * their own `.isEqual`; arrays compare elementwise.\n */\nexport const valuesEqualForNoOp = (a: unknown, b: unknown): boolean => {\n    // Primitive / reference identity, with `Object.is` so `NaN` ≡ `NaN`.\n    if (Object.is(a, b)) return true\n\n    // Opaque Firestore types compare by their own identity semantics.\n    if (isFirestoreOpaque(a) || isFirestoreOpaque(b)) {\n        return (\n            isFirestoreOpaque(a) && isFirestoreOpaque(b) && a.isEqual(b)\n        )\n    }\n\n    if (Array.isArray(a) || Array.isArray(b)) {\n        if (!Array.isArray(a) || !Array.isArray(b)) return false\n        if (a.length !== b.length) return false\n        return a.every((item, i) => valuesEqualForNoOp(item, b[i]))\n    }\n\n    if (isPlainObject(a) && isPlainObject(b)) {\n        // Union of keys: an explicit-`undefined` value on one side equals a\n        // missing key on the other, because both recurse to\n        // `valuesEqualForNoOp(undefined, undefined) === true`.\n        const keys = new Set([...Object.keys(a), ...Object.keys(b)])\n        for (const key of keys) {\n            if (!valuesEqualForNoOp(a[key], b[key])) return false\n        }\n        return true\n    }\n\n    return false\n}\n\n/**\n * Snapshot-side no-op collapse comparison over the observable fields shared by\n * DocumentState and CollectionState (§3/§4): returns `true` when something a\n * consumer can observe changed. `data` uses {@link valuesEqualForNoOp} so an\n * explicit-`undefined` ≡ missing key does not count as a change. Collection\n * state additionally checks `isActive` at the call site.\n */\nexport const observableStateChanged = (\n    prev: {\n        isLoading: boolean\n        isSynced: boolean\n        error: Error | undefined\n        data: unknown\n    },\n    next: {\n        isLoading: boolean\n        isSynced: boolean\n        error: Error | undefined\n        data: unknown\n    }\n): boolean =>\n    prev.isLoading !== next.isLoading ||\n    prev.isSynced !== next.isSynced ||\n    prev.error !== next.error ||\n    !valuesEqualForNoOp(prev.data, next.data)\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 * Strip FieldValue sentinels from a freshly-computed `edits` object when they\n * match a write the server has already durably accepted (`committed`).\n *\n * A sentinel (`serverTimestamp`, `increment`, `arrayUnion`, `arrayRemove`) is a\n * fire-once write intent: the client ships the sentinel, the server resolves it\n * to a concrete value, and the confirming snapshot carries that value. The\n * snapshot rebase re-derives pending edits with `computeDiff` and re-applies\n * them over the new server truth — but a sentinel can never compare equal to\n * its own resolved value (`isDeepEqual` only delegates to `.isEqual` when both\n * sides are opaque), so a committed sentinel would be re-derived and re-written\n * on every snapshot forever (and a non-idempotent `increment` would re-apply\n * each loop — data corruption).\n *\n * The fix: once a write's promise resolves, the caller records its payload as\n * `committed`. On the next snapshot the rebase calls this to drop any sentinel\n * that sits at the same path AND is `.isEqual` to the committed one — it has\n * landed, so the server's value is now the truth. Everything else is preserved:\n * a not-yet-sent sentinel (no `committed` entry), a re-edited sentinel (a\n * different `.isEqual` result), and every plain value flow through untouched,\n * so genuine pending edits and collaborator merges are unaffected.\n *\n * `edits` is mutated in place (it is always a fresh `computeDiff` result).\n * Recurses into plain objects, so collection diffs keyed by docId converge too:\n * when a doc's nested diff empties out, the doc entry itself is removed.\n *\n * @internal\n */\nexport const dropCommittedSentinels = (\n    edits: Record<string, unknown>,\n    committed: Record<string, unknown> | null | undefined\n): Record<string, unknown> => {\n    if (!committed) return edits\n    for (const key of Object.keys(edits)) {\n        const value = edits[key]\n        const sent = committed[key]\n        if (isFirestoreOpaque(value)) {\n            if (isFirestoreOpaque(sent) && value.isEqual(sent)) {\n                delete edits[key]\n            }\n        } else if (isPlainObject(value) && isPlainObject(sent)) {\n            dropCommittedSentinels(value, sent)\n            if (Object.keys(value).length === 0) {\n                delete edits[key]\n            }\n        }\n    }\n    return edits\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 * ⚠️ The dot-joined keys this produces are AMBIGUOUS to Firestore if any map\n * key itself contains a \".\" (e.g. an email key `a@b.com`): `updateDoc` parses\n * the \".\" as a path separator and writes to the wrong nested field. The\n * write path uses {@link flattenDiffToFieldPaths} + `FieldPath` instead, which\n * keeps every segment literal. Reach for this only when dotted-string keys are\n * what you actually want.\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 * Flatten a nested diff into a list of `{ segments, value }` entries, where\n * `segments` is the path expressed as an array of *literal* key segments\n * (never joined into a dotted string).\n *\n * This is the segment-preserving sibling of {@link flattenDiff}. It exists\n * because dot-joined string keys are ambiguous to Firestore: `updateDoc(ref,\n * { \"users.a@b.com.role\": 4 })` parses the `.` inside the email key as a path\n * separator and writes to `users → \"a@b\" → \"com\" → role` instead of the\n * literal key `\"a@b.com\"`. Feeding these segments to `new FieldPath(...segments)`\n * (with the variadic `updateDoc(ref, fieldPath, value, …)` form) keeps every\n * segment literal, so a \".\" inside a map key is never re-interpreted.\n *\n * The opaque/plain-object rules mirror {@link flattenDiff} exactly: arrays,\n * FieldValue sentinels (deleteField, serverTimestamp, …), and Firestore value\n * types (Timestamp, DocumentReference, GeoPoint, Bytes, VectorValue) ride\n * through verbatim at their path; only plain objects are recursed into.\n *\n * @param diff - The nested diff object\n * @param prefix - Internal: accumulated path segments for recursion\n * @returns Flat list of `{ segments, value }` entries\n */\nexport const flattenDiffToFieldPaths = (\n    diff: Record<string, unknown>,\n    prefix: string[] = []\n): Array<{ segments: string[]; value: unknown }> => {\n    const result: Array<{ segments: string[]; value: unknown }> = []\n\n    for (const key of Object.keys(diff)) {\n        const value = diff[key]\n        const segments = [...prefix, 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.push({ segments, value })\n            continue\n        }\n\n        // Plain objects are recursively flattened\n        if (isPlainObject(value)) {\n            result.push(...flattenDiffToFieldPaths(value, segments))\n            continue\n        }\n\n        // Primitives (strings, numbers, booleans, null)\n        result.push({ segments, value })\n    }\n\n    return result\n}\n\n/**\n * Build the variadic `(fieldPath, value, …)` argument list for the\n * `updateDoc(ref, …)` / `batch.update(ref, …)` form from a nested diff. Each\n * segment array becomes a literal `FieldPath`, so a \".\" inside a map key (e.g.\n * the email key `a@b.com`) stays a single segment instead of being re-parsed\n * as a path separator. Returns `[]` for an empty diff — callers should skip\n * the update entirely in that case.\n */\nexport const diffToFieldPathArgs = (\n    diff: Record<string, unknown>\n): unknown[] =>\n    flattenDiffToFieldPaths(diff).flatMap((e) => [\n        new FieldPath(...e.segments),\n        e.value,\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    collection,\n    doc,\n    onSnapshot,\n    query,\n    writeBatch,\n    deleteField,\n    type FieldPath,\n    WithFieldValue,\n    QueryConstraint,\n    type CollectionReference,\n    type Query,\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    applyDiff,\n    applyDiffMutable,\n    applyOverridesAtPaths,\n    computeDiff,\n    deepClone,\n    diffToFieldPathArgs,\n    dropCommittedSentinels,\n    isDeepEqual,\n    observableStateChanged,\n    reconcileDisplayOverrides,\n    valuesEqualForNoOp,\n} from '../utils/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 * Build the Firestore query a collection subscription runs: `definition`-level\n * constraints first, then hook-level `extraConstraints`. With no constraints at\n * all the bare collection reference is itself a valid `Query`.\n *\n * Single source of truth for query assembly. `useCollection` decides whether a\n * fresh `queryConstraints` array is semantically the same query — and so\n * whether to keep the existing listener instead of tearing it down — by\n * building the prospective query with this exact function and comparing via\n * Firestore's `queryEqual` (see hooks.ts). That comparison is only correct if\n * it assembles the query the same way the subscription does, so both paths MUST\n * go through here. Don't re-inline the merge order at either call site.\n */\nexport const buildCollectionQuery = <TData>(\n    ref: CollectionReference<TData>,\n    definitionConstraints: QueryConstraint[] | undefined,\n    extraConstraints: QueryConstraint[] | undefined\n): Query<TData> => {\n    const all = [...(definitionConstraints ?? []), ...(extraConstraints ?? [])]\n    return all.length > 0 ? query(ref, ...all) : ref\n}\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    /**\n     * The payload of the last batch the server durably accepted (set the\n     * moment `batch.commit()` resolves, consumed by the next snapshot's\n     * rebase). Lets the rebase recognize a committed FieldValue sentinel and\n     * drop it instead of re-deriving and re-writing it forever — see\n     * `dropCommittedSentinels`.\n     */\n    committedWrite: 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    // 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        committedWrite: 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        // Active AND past the initial load: a lazy collection is not \"loaded\"\n        // until load() activates it and the first snapshot settles. This is the\n        // app-level \"ready to render the list\" signal.\n        isLoaded: state.isActive && !state.isLoading,\n        isSynced: state.localState === undefined,\n        isActive: state.isActive,\n        error: state.error,\n    })\n\n    // Last public state actually published — see document.ts for the full\n    // contract behind this snapshot-side no-op collapse (§3/§4).\n    let lastPublished: CollectionState<TData> | null = null\n    // Cached public state — see document.ts; lets the hook layer use getState()\n    // as a stable useSyncExternalStore snapshot.\n    let cachedState: CollectionState<TData> | null = null\n\n    const publicStateChanged = (\n        prev: CollectionState<TData>,\n        next: CollectionState<TData>\n    ): boolean =>\n        // isActive is collection-specific (lazy loading); the rest of the\n        // observable no-op collapse is shared with documents.\n        prev.isActive !== next.isActive ||\n        observableStateChanged(prev, next)\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        const publicState = getPublicState()\n        // Snapshot-side no-op collapse: nothing observable changed → publish\n        // nothing, keeping the cached handle identity stable.\n        if (lastPublished !== null && !publicStateChanged(lastPublished, publicState)) {\n            return\n        }\n        lastPublished = publicState\n        cachedHandle = null\n        // Reuse the published state as the cached snapshot (see document.ts).\n        cachedState = publicState\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        // No-op collapse (§3): a write whose merged result equals the current\n        // view produces NO new state identity, undo entry, notify, or\n        // autosave. rawBase and newLocalState are compared after id injection\n        // so both carry ids; valuesEqualForNoOp closes the NaN /\n        // explicit-undefined gaps that defeat a naive guard.\n        if (valuesEqualForNoOp(rawBase, newLocalState)) return\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        // No-op collapse (§3): adding an id that already holds identical data\n        // is a complete no-op. The returned id stays meaningful (the doc\n        // exists with that data) but no write/undo/notify is produced.\n        if (valuesEqualForNoOp(currentData, newLocalState)) return id\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            notify()\n            return\n        }\n\n        const diff = computeDiff(\n            syncState as FirestoreObject,\n            state.localState as FirestoreObject\n        )\n\n        // Snapshot exactly what we're committing so the next snapshot's rebase\n        // can recognize committed FieldValue sentinels and drop them. Captured\n        // before the await because localState may be re-edited mid-flight.\n        const committing = deepClone(state.localState)\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 the variadic\n                    // FieldPath form (not a flattened dotted-key object) so a\n                    // \".\" inside a map key (e.g. an email key `a@b.com`) stays a\n                    // literal segment instead of being re-parsed as a path\n                    // separator. update still fails if the doc doesn't exist, so\n                    // deleted docs are not accidentally recreated.\n                    const args = diffToFieldPathArgs(\n                        docDiff as Record<string, unknown>\n                    )\n                    if (args.length) {\n                        batch.update(\n                            docRef,\n                            ...(args as [\n                                string | FieldPath,\n                                unknown,\n                                ...unknown[]\n                            ])\n                        )\n                    }\n                }\n            }\n\n            await batch.commit()\n            // The server durably accepted this batch — record it for the\n            // next snapshot's rebase to drop committed sentinels.\n            state.committedWrite = committing\n        } catch (error) {\n            console.error('Collection sync failed:', error)\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        // `prevSync` is the BASELINE: the server snapshot our pending local\n        // edits are measured against. Advanced to `newSyncState` here, after\n        // capturing it for the rebase below.\n        const prevSync = state.syncState\n        state.syncState = newSyncState\n        // A successful snapshot supersedes any previous read or write error.\n        state.error = undefined\n\n        // Capture and consume the last committed batch before the rebase: a\n        // FieldValue sentinel present in this payload has landed on the server,\n        // so the rebase must drop it rather than re-derive it (see below).\n        const committed = state.committedWrite\n        state.committedWrite = undefined\n\n        // The rebase below runs on EVERY snapshot using `prevSync` as the\n        // baseline, not only the one confirming an inflight write. Previously a\n        // snapshot from another client left `localState` on a stale base, so\n        // the next sync re-wrote untouched fields (collaborator clobber) and\n        // recreated remotely-deleted docs (delete resurrection).\n        const currentLocal = state.localState\n\n        if (currentLocal !== undefined && prevSync !== undefined) {\n            // Field-level merge: re-derive the user's OWN edits relative to the\n            // baseline and re-apply only those over the new server truth.\n            const userEdits = computeDiff(\n                prevSync as FirestoreObject,\n                currentLocal as FirestoreObject\n            )\n            // Drop FieldValue sentinels we already committed: the server has\n            // resolved them, so newSyncState is the truth. Without this a\n            // sentinel never compares equal to its resolved value and would be\n            // re-derived and re-written on every snapshot forever.\n            dropCommittedSentinels(\n                userEdits as Record<string, unknown>,\n                committed as Record<string, unknown> | undefined\n            )\n            const rebasedLocalState = applyDiff(\n                newSyncState as FirestoreObject,\n                userEdits\n            ) as Record<string, TData>\n\n            // Deletes always win (Bug 2). A doc that existed in the baseline\n            // but is gone from the server was deleted remotely → drop it and\n            // any pending local edits to it, and never recreate it. This is\n            // unconditional: classified against the baseline, not the rebased\n            // result, so a stale local copy can't resurrect a deleted doc.\n            for (const docId of Object.keys(prevSync)) {\n                if (!(docId in newSyncState)) {\n                    delete rebasedLocalState[docId]\n                }\n            }\n\n            // Re-add ids (applyDiff may have introduced docs from the snapshot\n            // and merged edits onto them).\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\n            // If the rebase leaves nothing that differs from the server, the\n            // edits are fully absorbed → drop localState so isSynced flips back\n            // to true and no redundant write is queued.\n            state.localState = isDeepEqual(rebasedLocalState, newSyncState)\n                ? undefined\n                : rebasedLocalState\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 = buildCollectionQuery(\n            collectionRef,\n            definitionConstraints,\n            extraConstraints\n        )\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        isLoaded: state.isActive && !state.isLoading,\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    // Identity-stable like getHandle (see document.ts.getState).\n    const getState = (): CollectionState<TData> => {\n        if (cachedState === null) {\n            cachedState = getPublicState()\n        }\n        return cachedState\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,\n        getHandle,\n        sync,\n    }\n}\n","import {\n    doc,\n    collection,\n    onSnapshot,\n    setDoc,\n    updateDoc,\n    deleteDoc,\n    DocumentReference,\n    type FieldPath,\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    applyDiff,\n    applyDiffMutable,\n    applyOverridesAtPaths,\n    computeDiff,\n    deepClone,\n    diffToFieldPathArgs,\n    dropCommittedSentinels,\n    isDeepEqual,\n    observableStateChanged,\n    reconcileDisplayOverrides,\n    valuesEqualForNoOp,\n} from '../utils/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` and `committedWrite`.\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    /**\n     * The payload of the last write the server durably accepted (set the\n     * moment the write promise resolves, consumed by the next snapshot's\n     * rebase). Lets the rebase recognize a committed FieldValue sentinel and\n     * drop it instead of re-deriving and re-writing it forever — see\n     * `dropCommittedSentinels`.\n     */\n    committedWrite: 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        committedWrite: 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        // The completion of isLoading: the first snapshot has arrived (and any\n        // minLoadTime has elapsed). isLoading already folds in both, so the\n        // inverse is the \"ready to render\" signal.\n        isLoaded: !state.isLoading,\n        isSynced: state.localState === undefined,\n        error: state.error,\n    })\n\n    // Last public state actually published. Used to suppress redundant\n    // notifies (§3/§4 snapshot-side no-op collapse): a snapshot or write that\n    // leaves every observable field unchanged must not invalidate the handle\n    // or wake consumers, or the write-back render loop survives.\n    let lastPublished: DocumentState<TData> | null = null\n    // Cached public state — like cachedHandle, returns the same reference until\n    // notify() invalidates it, so the hook layer can use getState() as a stable\n    // useSyncExternalStore snapshot.\n    let cachedState: DocumentState<TData> | null = null\n\n    const publicStateChanged = observableStateChanged\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        const publicState = getPublicState()\n        // Snapshot-side no-op collapse: nothing a consumer can observe\n        // changed → publish nothing. Keeps the cached handle identity stable\n        // so useSyncExternalStore does not re-render.\n        if (lastPublished !== null && !publicStateChanged(lastPublished, publicState)) {\n            return\n        }\n        lastPublished = publicState\n        cachedHandle = null\n        // Reuse the just-built state as the cached snapshot so getState() and\n        // the published value share one identity-stable reference.\n        cachedState = publicState\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        // No-op collapse (§3): a write whose merged result equals the current\n        // view must produce NO new state identity, undo entry, notify, or\n        // autosave — otherwise the write-back render loop survives. Compare\n        // raw states (decides whether to STORE); valuesEqualForNoOp closes the\n        // NaN and explicit-undefined gaps that let the loop slip past a naive\n        // equality check.\n        if (valuesEqualForNoOp(rawBase, newLocalState)) {\n            return\n        }\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        // No-op collapse (§3): a set() whose payload equals the current stored\n        // value is a complete no-op. Skip only when there IS a current value —\n        // a set against undefined data is a creation and must proceed. Compare\n        // raw state so a held serverTimestamp() sentinel isn't masked by its\n        // display Timestamp.\n        if (\n            currentData !== undefined &&\n            valuesEqualForNoOp(state.localState ?? state.syncState, data)\n        ) {\n            return\n        }\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) {\n            return\n        }\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) {\n            return\n        }\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        // Snapshot exactly what we're committing so the next snapshot's rebase\n        // can recognize committed FieldValue sentinels and drop them. Captured\n        // in a local const before the await — NOT read back from shared state —\n        // because Firestore fires a local-cache echo snapshot (hasPendingWrites:\n        // true) before this write acks, and handleSnapshot clears\n        // state.inflightLocalState. Reading it back at line `committedWrite = …`\n        // would then capture `undefined`, losing the sentinel record and making\n        // serverTimestamp churn / increment double-apply. Mirrors collection.ts.\n        const committing = deepClone(state.localState)\n        state.inflightLocalState = committing\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 the variadic FieldPath\n                // form (not a flattened dotted-key object) so that a \".\" inside\n                // a map key (e.g. an email key `a@b.com`) is preserved as a\n                // literal segment instead of being re-parsed as a path\n                // separator. updateDoc still fails if the document doesn't\n                // exist, so deleted docs are not accidentally recreated.\n                const args = diffToFieldPathArgs(\n                    diff as Record<string, unknown>\n                )\n                if (args.length) {\n                    await updateDoc(\n                        docRef,\n                        ...(args as [\n                            string | FieldPath,\n                            unknown,\n                            ...unknown[]\n                        ])\n                    )\n                }\n            }\n            // The server durably accepted this payload. Record it so the next\n            // snapshot's rebase can recognize committed FieldValue sentinels\n            // (serverTimestamp, increment, …) and drop them instead of\n            // re-deriving and re-writing them forever.\n            state.committedWrite = committing\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        // `prevSync` is the BASELINE: the server snapshot our pending local\n        // edits are measured against. We advance it to `newSyncData` below,\n        // after using it to re-derive the user's own edits.\n        const prevSync = state.syncState\n        state.syncState = newSyncData\n        // A successful snapshot supersedes any previous read or write error.\n        state.error = undefined\n\n        // The rebase below runs on EVERY snapshot, using `prevSync` as the\n        // baseline, regardless of whether one of our own writes happened to be\n        // in flight. This is the fix for the collaborator-clobber class of bugs\n        // — previously the rebase only ran inside the `waitingForUpdate`\n        // window, so a snapshot from another client left `localState` sitting\n        // on a stale base.\n        state.waitingForUpdate = false\n        state.inflightLocalState = undefined\n        // Capture and consume the last committed write before the rebase: a\n        // FieldValue sentinel present in this payload has landed on the server,\n        // so the rebase must drop it rather than re-derive it (see below).\n        const committed = state.committedWrite\n        state.committedWrite = undefined\n        const currentLocal = state.localState\n\n        if (currentLocal === null) {\n            // Pending delete — our delete intent is the latest word. The doc\n            // is still present on the server here; preserve the tombstone so\n            // the next sync issues deleteDoc.\n        } else if (currentLocal !== undefined && prevSync !== undefined) {\n            // Field-level merge (Bug 1 fix). Re-derive the user's OWN edits\n            // relative to the baseline, then re-apply only those over the new\n            // server truth. Untouched fields follow the server; the client's\n            // actual edits survive. Same-field concurrent edits stay\n            // last-write-wins — the local edit is preserved and re-sent on the\n            // next sync.\n            const userEdits = computeDiff(prevSync, currentLocal)\n            // Drop FieldValue sentinels we already committed: they've been\n            // resolved by the server, so newSyncData is the truth. Without\n            // this a sentinel never compares equal to its resolved value and\n            // would be re-derived and re-written on every snapshot forever.\n            dropCommittedSentinels(\n                userEdits as Record<string, unknown>,\n                committed as Record<string, unknown> | null | undefined\n            )\n            const rebasedLocalState = applyDiff(newSyncData, userEdits)\n            // If the rebase leaves nothing that differs from the server, the\n            // edit has been fully absorbed → drop localState so isSynced flips\n            // back to true and no redundant write is queued.\n            const absorbed = isDeepEqual(rebasedLocalState, newSyncData)\n            state.localState = absorbed ? undefined : rebasedLocalState\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        // Drop any committed-write record: it described a now-deleted doc and\n        // must not be matched against a future recreation snapshot.\n        state.committedWrite = 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) {\n            return\n        }\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        isLoaded: !state.isLoading,\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    // Identity-stable like getHandle: rebuilt only after notify() invalidates\n    // the cache, so useSyncExternalStore can treat it as the snapshot.\n    const getState = (): DocumentState<TData> => {\n        if (cachedState === null) {\n            cachedState = getPublicState()\n        }\n        return cachedState\n    }\n\n    return {\n        load,\n        stop,\n        subscribe,\n        getState,\n        getHandle,\n        sync,\n    }\n}\n","import {\n    collection,\n    queryEqual,\n    type CollectionReference,\n    type Query,\n    type QueryConstraint,\n} from 'firebase/firestore'\nimport type {\n    CollectionDefinition,\n    CollectionHandle,\n    CollectionState,\n    DocumentDefinition,\n    DocumentHandle,\n    DocumentState,\n    FirestoreObject,\n    UpdateOptions,\n} from '../types'\nimport type { FirestateStore } from './store'\nimport { createDocumentSubscription } from './document'\nimport {\n    buildCollectionQuery,\n    createCollectionSubscription,\n} from './collection'\n\n/**\n * Shared, ref-counted subscription registry.\n *\n * Every `useDocument` / `useCollection` call for the *same* resource — same\n * `(definition, resolved path, doc id / query)` — transparently\n * shares ONE underlying `createDocumentSubscription` / `createCollectionSubscription`\n * instance, and therefore one `onSnapshot` listener and one\n * reconciled/optimistic state. A write through any handle is instantly visible\n * to every reader on that resource, and the per-hook `selector` slices that one\n * shared state instead of building a private subscription per call.\n *\n * `readOnly` is deliberately NOT part of the key. It is a *per-handle\n * capability* over the shared state, not a state fork: a writable hook (the\n * typical provider — the sole writer, which needs full data for its undo bridge\n * and global sync tracking) and any number of `readOnly: true` hooks (leaves\n * that only read-select) resolve the SAME entry, so a write through the writable\n * handle is instantly visible to every read-only reader. The shared\n * subscription is always built writable; a read-only facade neuters its own\n * handle's writers (and `sync`) and leaves the shared undo flag untouched, while\n * passing reads straight through (see {@link readOnlyDocumentHandle}).\n *\n * Lifecycle is ref-counted and lazy:\n * - A facade is built when a hook resolves the resource (its render-phase\n *   `useMemo`): it reuses the already-registered shared entry if one exists, or\n *   builds a DETACHED entry (subscription created, no listener attached) left\n *   out of the registry. `getHandle()`/`load()` work off it either way.\n * - `acquire()` (called from the hook's `subscribe` effect) registers the entry\n *   on first lease — adopting whatever a sibling registered first — bumps the\n *   ref count, and registers the hook's change callback. Deferring registration\n *   to commit means an aborted/suspended/discarded render (whose effect never\n *   runs) leaves nothing stranded in the registry. `load()` activates the\n *   shared Firestore listener (idempotent — only the first activation attaches).\n * - The last lease to `release()` tears the listener down (the underlying\n *   `stop()`) and evicts the entry, so a subsequent mount starts a fresh\n *   subscription — a lazy collection resets to `isActive: false` exactly as a\n *   single hook does today.\n *\n * The registry is scoped per {@link FirestateStore} (a WeakMap keyed by store)\n * and within that per *definition* (a WeakMap keyed by the definition object).\n * Keying by definition — not just the resolved path string — means two distinct\n * definitions that happen to resolve to the same path keep independent\n * subscriptions (their schema/autosave config may differ), while every hook\n * built from one registry entry shares correctly. This matches the headline use\n * case: a registry resource is one definition object referenced everywhere.\n */\n\n// `ReturnType<typeof fn>` resolves the generic to its `FirestoreObject`\n// constraint, giving a concrete subscription shape we can store heterogeneously\n// in the registry. Handles are cast back to the caller's `TData` at the facade\n// boundary — sound because the data shape only narrows the same stored object.\ntype AnyDocumentSubscription = ReturnType<typeof createDocumentSubscription>\ntype AnyCollectionSubscription = ReturnType<typeof createCollectionSubscription>\n\n/**\n * A registry entry: the shared subscription plus its lease bookkeeping.\n * `undoableEnabled` gates the entry's `onPushUndo` and is shared across all\n * leases (see {@link DocumentShared.setUndoable}).\n */\ninterface DocumentEntry {\n    sub: AnyDocumentSubscription\n    refCount: number\n    undoableEnabled: boolean\n    /**\n     * False once the entry has been torn down (`sub.stop()` on release-to-zero).\n     * A re-acquire of a non-live entry rebuilds `sub`, since `stop()` leaves\n     * stale loaded/loading state (see {@link getDocumentShared}).\n     */\n    live: boolean\n}\n\ninterface CollectionEntry {\n    sub: AnyCollectionSubscription\n    refCount: number\n    undoableEnabled: boolean\n    /** See {@link DocumentEntry.live}. */\n    live: boolean\n    /**\n     * The query this entry's listener runs, built with\n     * {@link buildCollectionQuery}. Used to match a prospective hook against\n     * existing entries via Firestore's `queryEqual` — semantic query identity,\n     * not array reference (see {@link getCollectionShared}).\n     */\n    query: Query<unknown>\n}\n\ninterface StoreRegistry {\n    docs: WeakMap<\n        DocumentDefinition<FirestoreObject>,\n        Map<string, DocumentEntry>\n    >\n    /**\n     * Keyed by definition → `collectionPath` → bucket of entries that differ\n     * only by query. A bucket is scanned with `queryEqual` to find the entry\n     * for a given query; distinct queries on the same path live as separate\n     * entries in the same bucket.\n     */\n    cols: WeakMap<\n        CollectionDefinition<FirestoreObject>,\n        Map<string, CollectionEntry[]>\n    >\n}\n\nconst registries = new WeakMap<FirestateStore, StoreRegistry>()\n\nconst getRegistry = (store: FirestateStore): StoreRegistry => {\n    let reg = registries.get(store)\n    if (!reg) {\n        reg = { docs: new WeakMap(), cols: new WeakMap() }\n        registries.set(store, reg)\n    }\n    return reg\n}\n\n// `readOnly` is intentionally absent from both keys: read-only and writable\n// hooks on the same resource must resolve the SAME entry (one shared state).\nconst docKey = (collectionPath: string, docId: string): string =>\n    `${collectionPath}\\0${docId}`\n\nconst colBucketKey = (collectionPath: string): string => collectionPath\n\n/**\n * Semantic query match, hardened for two cases the raw `queryEqual` does not\n * cover here: the same reference (the common case — a hook reuses its memoized\n * query) short-circuits true, and a `queryEqual` that throws (test harnesses\n * that mock the query builders pass non-`Query` placeholders) is treated as a\n * non-match rather than crashing the lookup.\n */\nconst sameQuery = (a: Query<unknown>, b: Query<unknown>): boolean => {\n    if (a === b) return true\n    try {\n        return queryEqual(a, b)\n    } catch {\n        return false\n    }\n}\n\n/** Push to the store-global undo manager, gated by the entry's shared flag. */\nconst makeOnPushUndo =\n    (store: FirestateStore, entry: { undoableEnabled: boolean }) =>\n    (undoAction: () => void, redoAction: () => void, opts?: UpdateOptions) => {\n        if (!entry.undoableEnabled) return\n        store.undoManager.push({\n            undo: undoAction,\n            redo: redoAction,\n            groupId: opts?.undoGroupId,\n            // Stamp the current router path so onNavigate can return the user\n            // to where this write happened before reverting it. Merged groups\n            // keep the newest action's path (see mergeGroupedActions).\n            path: store.getUndoPath(),\n        })\n    }\n\n// A read-only handle is the shared handle with its writers (and `sync`)\n// replaced by no-ops. Reads — `data`, status, `ref`, and a collection's `load`\n// (activating a lazy listener is a read, not a write) — pass straight through,\n// so a read-only facade observes every optimistic edit the writer makes while\n// being unable to author or flush a write itself.\nconst noop = (): void => {}\nconst asyncNoop = async (): Promise<void> => {}\nconst noopAdd = (): undefined => undefined\n\nconst readOnlyDocumentHandle = <T extends FirestoreObject>(\n    handle: DocumentHandle<T>\n): DocumentHandle<T> => ({\n    ...handle,\n    update: noop,\n    set: noop,\n    delete: noop,\n    sync: asyncNoop,\n})\n\nconst readOnlyCollectionHandle = <T extends FirestoreObject>(\n    handle: CollectionHandle<T>\n): CollectionHandle<T> => ({\n    ...handle,\n    update: noop,\n    add: noopAdd,\n    remove: noop,\n    sync: asyncNoop,\n})\n\n/**\n * A per-hook facade over a shared registry entry. Multiple hooks on the same\n * resource hold distinct facades bound to the *same* entry, so `getHandle()`\n * returns the same identity-stable handle to all of them and `acquire()` /\n * `release()` ref-count one shared listener.\n */\nexport interface DocumentShared<T extends FirestoreObject> {\n    /** The shared, identity-stable handle for this resource. */\n    getHandle: () => DocumentHandle<T>\n    /**\n     * The shared, identity-stable full observable state (data + every status\n     * flag, including `isSynced`). Passes through the read-only facade\n     * unchanged — state carries no writers to neuter — so a status hook reads\n     * the same state a writable hook does.\n     */\n    getState: () => DocumentState<T>\n    /** Activate the shared Firestore listener (idempotent across leases). */\n    load: () => void\n    /** Set whether this resource records undo entries (shared across leases). */\n    setUndoable: (enabled: boolean) => void\n    /**\n     * Register `onChange` and bump the ref count. Returns a release function\n     * that unregisters it and, when it was the last lease, stops the listener\n     * and evicts the entry. Release is idempotent.\n     */\n    acquire: (onChange: () => void) => () => void\n}\n\nexport interface CollectionShared<T extends FirestoreObject> {\n    getHandle: () => CollectionHandle<T>\n    /** See {@link DocumentShared.getState}. */\n    getState: () => CollectionState<T>\n    load: () => void\n    setUndoable: (enabled: boolean) => void\n    acquire: (onChange: () => void) => () => void\n}\n\nexport interface DocumentSharedParams<T extends FirestoreObject> {\n    store: FirestateStore\n    definition: DocumentDefinition<T>\n    collectionPath: string\n    docId: string\n    /**\n     * Per-handle read-only capability. Neuters only THIS facade's handle\n     * writers; it is not part of the share key, so a read-only and a writable\n     * hook on the same document share one listener and one optimistic state.\n     */\n    readOnly?: boolean\n}\n\n/**\n * Find or create the shared subscription for a document resource and return a\n * facade bound to it. Creating the entry attaches no listener. Intended to be\n * called from a hook's render-phase `useMemo`.\n */\nexport const getDocumentShared = <T extends FirestoreObject>({\n    store,\n    definition,\n    collectionPath,\n    docId,\n    readOnly,\n}: DocumentSharedParams<T>): DocumentShared<T> => {\n    const map = getDocMap(store, definition)\n    const key = docKey(collectionPath, docId)\n    // Per-handle capability, resolved exactly as the subscription used to:\n    // explicit hook override wins, then the definition default, then writable.\n    // It gates ONLY this facade's handle — never the shared state or its key.\n    const facadeReadOnly = readOnly ?? definition.readOnly ?? false\n\n    // Build a fresh subscription bound to `entry`. Used for a newly built entry\n    // and to revive an evicted one on re-acquire (its `sub` was stop()ed, which\n    // leaves stale loaded/loading state).\n    //\n    // The shared subscription is ALWAYS writable: the provider (sole writer)\n    // needs functional writers, and read-only facades neuter their own handle\n    // rather than fork a separate read-only subscription. Passing\n    // `readOnly: false` also overrides a read-only *definition*, so a hook that\n    // opts back in with `readOnly: false` gets a writable handle off the same\n    // shared state.\n    const buildSub = (entry: DocumentEntry): AnyDocumentSubscription =>\n        createDocumentSubscription({\n            store,\n            definition: definition as DocumentDefinition<FirestoreObject>,\n            docId,\n            collectionPath,\n            readOnly: false,\n            onPushUndo: makeOnPushUndo(store, entry),\n        })\n\n    const buildEntry = (): DocumentEntry => {\n        const entry: DocumentEntry = {\n            sub: null as unknown as AnyDocumentSubscription,\n            refCount: 0,\n            undoableEnabled: false,\n            live: true,\n        }\n        entry.sub = buildSub(entry)\n        return entry\n    }\n\n    // Reuse the already-registered shared entry if one exists; otherwise build a\n    // DETACHED entry and leave it OUT of the map. Registration is deferred to\n    // acquire() (commit time) so an aborted/suspended/discarded render — whose\n    // effect, and therefore acquire()/release(), never runs — leaves no\n    // refCount-0 entry stranded. getHandle()/load() operate on `ent` regardless.\n    let ent: DocumentEntry = map.get(key) ?? buildEntry()\n    let desiredUndoable = false\n    // Memoize the neutered read-only handle on the underlying handle's identity,\n    // so getHandle() stays referentially stable between notifies (a\n    // useSyncExternalStore requirement) and rebuilds the wrapper only when the\n    // shared subscription publishes a new handle.\n    let readOnlySource: DocumentHandle<T> | null = null\n    let readOnlyHandle: DocumentHandle<T> | null = null\n\n    return {\n        getHandle: () => {\n            const handle = ent.sub.getHandle() as DocumentHandle<T>\n            if (!facadeReadOnly) return handle\n            if (handle !== readOnlySource) {\n                readOnlySource = handle\n                readOnlyHandle = readOnlyDocumentHandle(handle)\n            }\n            return readOnlyHandle as DocumentHandle<T>\n        },\n        getState: () => ent.sub.getState() as DocumentState<T>,\n        load: () => ent.sub.load(),\n        setUndoable: (enabled) => {\n            // A read-only facade can't write, so it must not influence the\n            // shared undo flag the writer relies on (last-writer-wins).\n            if (facadeReadOnly) return\n            desiredUndoable = enabled\n            ent.undoableEnabled = enabled\n        },\n        acquire: (onChange) => {\n            // Commit time: adopt the already-registered shared entry if one\n            // exists (a sibling registered first, or this resource is still\n            // live), else register ours. The map thus holds only committed\n            // entries, each with refCount >= 1 — discarded renders leave nothing\n            // behind. Revive a torn-down entry with a fresh sub (stop() leaves\n            // stale state), honoring \"a subsequent mount starts fresh\".\n            const committed = map.get(key)\n            if (committed) {\n                ent = committed\n            } else {\n                map.set(key, ent)\n            }\n            if (!ent.live) {\n                ent.sub = buildSub(ent)\n                ent.live = true\n            }\n            // Only writers set the shared undo flag; a read-only lease leaves it\n            // as the writer (or default) left it (see setUndoable).\n            if (!facadeReadOnly) ent.undoableEnabled = desiredUndoable\n            ent.refCount++\n            const notifyUnsub = ent.sub.subscribe(onChange)\n            let released = false\n            return () => {\n                if (released) return\n                released = true\n                notifyUnsub()\n                ent.refCount--\n                if (ent.refCount <= 0) {\n                    ent.sub.stop()\n                    ent.live = false\n                    if (map.get(key) === ent) map.delete(key)\n                }\n            }\n        },\n    }\n}\n\nconst getDocMap = (\n    store: FirestateStore,\n    definition: DocumentDefinition<FirestoreObject>\n): Map<string, DocumentEntry> => {\n    const reg = getRegistry(store)\n    let map = reg.docs.get(definition)\n    if (!map) {\n        map = new Map()\n        reg.docs.set(definition, map)\n    }\n    return map\n}\n\nexport interface CollectionSharedParams<T extends FirestoreObject> {\n    store: FirestateStore\n    definition: CollectionDefinition<T>\n    collectionPath: string\n    /** Per-handle read-only capability — see {@link DocumentSharedParams.readOnly}. */\n    readOnly?: boolean\n    /** Hook-level extra constraints, passed through to the subscription verbatim. */\n    queryConstraints: QueryConstraint[] | undefined\n    /**\n     * The built query for this `(path, constraints)`, used to match existing\n     * entries by semantic identity. Built by the caller (it can throw for\n     * placeholder queries like an empty `in`); the caller only resolves a shared\n     * entry when it is a valid, non-null query.\n     */\n    query: Query<unknown>\n}\n\n/**\n * Find or create the shared subscription whose query is `queryEqual` to\n * `params.query` and return a facade bound to it. Entries on the same path but\n * with a different query coexist in the same bucket.\n */\nexport const getCollectionShared = <T extends FirestoreObject>({\n    store,\n    definition,\n    collectionPath,\n    readOnly,\n    queryConstraints,\n    query,\n}: CollectionSharedParams<T>): CollectionShared<T> => {\n    const bucket = getColBucket(store, definition, collectionPath)\n    // See getDocumentShared: per-handle capability, never part of the key.\n    const facadeReadOnly = readOnly ?? definition.readOnly ?? false\n\n    // Always writable — read-only facades neuter their own handle. See\n    // getDocumentShared.buildSub for the full rationale.\n    const buildSub = (entry: CollectionEntry): AnyCollectionSubscription =>\n        createCollectionSubscription({\n            store,\n            definition: definition as CollectionDefinition<FirestoreObject>,\n            collectionPath,\n            readOnly: false,\n            queryConstraints,\n            onPushUndo: makeOnPushUndo(store, entry),\n        })\n\n    const buildEntry = (): CollectionEntry => {\n        const entry: CollectionEntry = {\n            sub: null as unknown as AnyCollectionSubscription,\n            refCount: 0,\n            undoableEnabled: false,\n            live: true,\n            query,\n        }\n        entry.sub = buildSub(entry)\n        return entry\n    }\n\n    // Reuse the already-registered entry whose query matches; otherwise build a\n    // DETACHED one and leave it OUT of the bucket. Registration is deferred to\n    // acquire() so a discarded render leaves no refCount-0 entry behind —\n    // important for collections, whose buckets are linearly scanned by every\n    // lookup (see getDocumentShared for the full rationale).\n    let ent: CollectionEntry =\n        bucket.find((e) => sameQuery(e.query, query)) ?? buildEntry()\n    let desiredUndoable = false\n    // Memoize the neutered read-only handle on the underlying handle's identity.\n    // See getDocumentShared.\n    let readOnlySource: CollectionHandle<T> | null = null\n    let readOnlyHandle: CollectionHandle<T> | null = null\n\n    return {\n        getHandle: () => {\n            const handle = ent.sub.getHandle() as CollectionHandle<T>\n            if (!facadeReadOnly) return handle\n            if (handle !== readOnlySource) {\n                readOnlySource = handle\n                readOnlyHandle = readOnlyCollectionHandle(handle)\n            }\n            return readOnlyHandle as CollectionHandle<T>\n        },\n        getState: () => ent.sub.getState() as CollectionState<T>,\n        load: () => ent.sub.load(),\n        setUndoable: (enabled) => {\n            // See getDocumentShared.setUndoable.\n            if (facadeReadOnly) return\n            desiredUndoable = enabled\n            ent.undoableEnabled = enabled\n        },\n        acquire: (onChange) => {\n            // Commit time: adopt the matching registered entry if one exists,\n            // else register ours. The bucket thus holds only committed entries\n            // (refCount >= 1). Revive a torn-down entry with a fresh sub. See\n            // getDocumentShared.acquire for the full rationale.\n            const committed = bucket.find((e) => sameQuery(e.query, query))\n            if (committed) {\n                ent = committed\n            } else {\n                bucket.push(ent)\n            }\n            if (!ent.live) {\n                ent.sub = buildSub(ent)\n                ent.live = true\n            }\n            // Only writers set the shared undo flag (see setUndoable).\n            if (!facadeReadOnly) ent.undoableEnabled = desiredUndoable\n            ent.refCount++\n            const notifyUnsub = ent.sub.subscribe(onChange)\n            let released = false\n            return () => {\n                if (released) return\n                released = true\n                notifyUnsub()\n                ent.refCount--\n                if (ent.refCount <= 0) {\n                    ent.sub.stop()\n                    ent.live = false\n                    const idx = bucket.indexOf(ent)\n                    if (idx !== -1) bucket.splice(idx, 1)\n                }\n            }\n        },\n    }\n}\n\nconst getColBucket = (\n    store: FirestateStore,\n    definition: CollectionDefinition<FirestoreObject>,\n    collectionPath: string\n): CollectionEntry[] => {\n    const reg = getRegistry(store)\n    let byKey = reg.cols.get(definition)\n    if (!byKey) {\n        byKey = new Map()\n        reg.cols.set(definition, byKey)\n    }\n    const key = colBucketKey(collectionPath)\n    let bucket = byKey.get(key)\n    if (!bucket) {\n        bucket = []\n        byKey.set(key, bucket)\n    }\n    return bucket\n}\n\n/**\n * Build the query a collection hook would subscribe to, or `null` when the\n * constraints cannot form a valid query (e.g. a gated empty-`in` placeholder\n * Firestore refuses to construct). The hook only resolves a shared entry when\n * this is non-null — matching the lazy-before-load and `enabled`-gating\n * contracts where no listener should run yet.\n */\nexport const buildSharedCollectionQuery = (\n    store: FirestateStore,\n    collectionPath: string,\n    definitionConstraints: QueryConstraint[] | undefined,\n    extraConstraints: QueryConstraint[] | undefined\n): Query<unknown> | null => {\n    const ref = collection(\n        store.firestore,\n        collectionPath\n    ) as CollectionReference\n    try {\n        return buildCollectionQuery(\n            ref,\n            definitionConstraints,\n            extraConstraints\n        )\n    } catch {\n        return null\n    }\n}\n","import {\n    createContext,\n    useCallback,\n    useContext,\n    useEffect,\n    useMemo,\n    useRef,\n    useSyncExternalStore,\n} from 'react'\nimport { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector'\nimport { collection, queryEqual } from 'firebase/firestore'\nimport type {\n    CollectionReference,\n    Firestore,\n    Query,\n    QueryConstraint,\n} from 'firebase/firestore'\nimport type {\n    CollectionDefinition,\n    CollectionHandle,\n    CollectionState,\n    DocumentDefinition,\n    DocumentHandle,\n    DocumentState,\n    FirestoreObject,\n    LoadingStatus,\n    SelectedCollectionHandle,\n    SelectedDocumentHandle,\n    SyncStatus,\n    UndoManager,\n    UndoManagerState,\n} from '../types'\nimport { valuesEqualForNoOp } from '../utils/diff'\nimport type { FirestateStore } from '../core/store'\nimport { buildCollectionQuery } from '../core/collection'\nimport {\n    buildSharedCollectionQuery,\n    getCollectionShared,\n    getDocumentShared,\n} from '../core/shared-subscription'\n\n/**\n * Whether two hook-level `queryConstraints` arrays produce the same Firestore\n * query for a collection. `QueryConstraint` objects are opaque, so instead of\n * hand-rolling a deep compare we build both queries — with the same\n * `buildCollectionQuery` the subscription itself uses, so this check can never\n * drift from the query that actually runs — and defer to Firestore's own\n * `queryEqual`, which structurally compares filters, ordering, limits, and\n * cursors. This is what lets the subscription survive reference churn (e.g.\n * constraint inputs read from a deep-cloned document) while still rebuilding\n * when the query genuinely changes — no caller-supplied key.\n *\n * Building a query can throw: callers commonly gate with a deliberately invalid\n * placeholder like `where(documentId(), 'in', [])` while real IDs are pending,\n * and Firestore refuses to construct that. If building the prior snapshot\n * throws, no live listener could ever have run it, so there is nothing to\n * preserve — we treat the snapshots as unequal and let the caller adopt the\n * new constraints. This matters most for lazy collections, where a render can\n * carry such a placeholder before `load()` attaches any listener.\n */\nconst queryConstraintsEqual = (\n    firestore: Firestore,\n    collectionPath: string,\n    definitionConstraints: QueryConstraint[] | undefined,\n    a: QueryConstraint[] | undefined,\n    b: QueryConstraint[] | undefined\n): boolean => {\n    if (a === b) return true\n    const ref = collection(firestore, collectionPath) as CollectionReference\n    try {\n        return queryEqual(\n            buildCollectionQuery(ref, definitionConstraints, a),\n            buildCollectionQuery(ref, definitionConstraints, b)\n        )\n    } catch {\n        return false\n    }\n}\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    isLoaded: false,\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    isLoaded: false,\n    isActive: false,\n    load: NOOP,\n    sync: ASYNC_NOOP,\n    error: undefined,\n    ref: undefined,\n}\n\n// State snapshots for the disabled (`enabled: false`) path. A disabled resource\n// is *idle*: not loading, not loaded, synced (no pending writes), no data/error.\n// getStateSnapshot returns these so the selector — and the no-selector default\n// projection — run against a consistent shape (e.g. a disabled sync-status hook\n// yields `{ isSynced: true, isSaving: false }`, a disabled data handle\n// `isLoaded: false`). `isLoaded` is set explicitly false because `!isLoading`\n// would wrongly read as loaded here.\nconst DISABLED_DOCUMENT_STATE: DocumentState<FirestoreObject> = {\n    data: undefined,\n    isLoading: false,\n    isLoaded: false,\n    isSynced: true,\n    error: undefined,\n}\n\nconst DISABLED_COLLECTION_STATE: CollectionState<FirestoreObject> = {\n    data: EMPTY_RECORD,\n    isLoading: false,\n    isLoaded: false,\n    isSynced: true,\n    isActive: false,\n    error: undefined,\n}\n\n/**\n * Opts a {@link useDocument} call into a selected slice. The hook still returns\n * a full handle (writers, `ref`, status) — only `data` is narrowed to whatever\n * `selector` returns.\n */\nexport interface DocumentSelectorOptions<\n    TData extends FirestoreObject,\n    TSelected,\n> {\n    /**\n     * Project the document's observable state down to the slice this component\n     * reacts to. The selector receives the full {@link DocumentState} —\n     * `{ data, isLoading, isLoaded, isSynced, error }`, where `data` is\n     * `undefined` while the document is loading or the hook is disabled — and\n     * the component\n     * re-renders *only* when the returned slice changes (per `isEqual`). Status\n     * is not a freebie: read `s.isLoading`/`s.isSynced`/`s.error` here if you\n     * want to react to them (e.g. `s => ({ title: s.data?.title, saving:\n     * !s.isSynced })`). What you select is exactly what re-renders, and the\n     * returned handle exposes only the slice plus writers/`ref`.\n     */\n    selector: (state: DocumentState<TData>) => TSelected\n    /**\n     * Decide whether two consecutive slices are equal; the hook re-renders only\n     * when this returns `false`. Defaults to a deep value comparison, so a\n     * selector that returns a fresh object/array of the same shape does not\n     * over-render. Pass {@link shallow} for a one-level compare, or a custom\n     * comparator.\n     */\n    isEqual?: (a: TSelected, b: TSelected) => boolean\n}\n\n/**\n * Opts a {@link useCollection} call into a selected slice. See\n * {@link DocumentSelectorOptions}; the only difference is the selector receives\n * the collection's keyed record.\n */\nexport interface CollectionSelectorOptions<\n    TData extends FirestoreObject,\n    TSelected,\n> {\n    /**\n     * Project the collection's observable state down to the slice this component\n     * reacts to. The selector receives the full {@link CollectionState} —\n     * `{ data, isLoading, isLoaded, isSynced, isActive, error }`, where `data` is\n     * the keyed record (e.g. `s => s.data[id]` or `s => Object.keys(s.data)`) —\n     * and the\n     * component re-renders *only* when the returned slice changes. As with\n     * {@link DocumentSelectorOptions.selector}, status is reactive only if you\n     * select it, and the returned handle exposes just the slice plus\n     * writers/`ref`.\n     */\n    selector: (state: CollectionState<TData>) => TSelected\n    /** See {@link DocumentSelectorOptions.isEqual}. */\n    isEqual?: (a: TSelected, b: TSelected) => boolean\n}\n\n/**\n * Shape used by the non-selector hook overload to *exclude* selector options,\n * so passing a real `selector` falls through to the selector overload (which\n * infers `TSelected`) instead of silently resolving to the full-data return.\n */\ntype WithoutSelector = { selector?: undefined; isEqual?: undefined }\n\n/**\n * The projection used by the **no-selector (default) path** of each hook: the\n * *sync-agnostic* public view — `data` plus `isLoaded` and `error` (and a\n * collection's `isActive`; `undefined` for documents, so it no-ops in\n * {@link defaultSelectionEqual}). It deliberately OMITS `isSynced`, so a write\n * settling (the `isSynced` flip on every autosave) does NOT re-render a plain\n * data consumer — \"just render the record\" is the cheap default. Components that\n * render save state opt into the per-entry sync-status hook. The raw\n * `isLoading`/`isSynced` flags remain on the full state the selector path sees.\n *\n * It also omits the handle's methods and `ref`: those are read *live* from the\n * subscription in the final merge, never memoized here. If they rode along, a\n * subscription rebuild (id/path/query/enabled change) whose projection happened\n * to be value-equal would be collapsed by the equality check, and the hook would\n * keep returning the *previous* subscription's `load()`/`update()` — e.g. firing\n * against torn-down, empty-`in` constraints. This holds for the selector path\n * too, which is why it also reads methods/`ref` live.\n */\ninterface DefaultSelection<TSelected> {\n    data: TSelected\n    isLoaded: boolean\n    error: Error | undefined\n    isActive?: boolean\n}\n\n/**\n * Equality over a {@link DefaultSelection} (no-selector path): `isLoaded`,\n * `error`, and a collection's `isActive` compared by identity, the `data` slice\n * by `dataEqual` (the default value comparison). `isSynced` is absent by design,\n * so a sync flip cannot re-render the plain handle; a change to value-equal\n * `data` is still collapsed, while a load transition re-renders.\n */\nconst defaultSelectionEqual = <TSelected>(\n    a: DefaultSelection<TSelected>,\n    b: DefaultSelection<TSelected>,\n    dataEqual: (a: TSelected, b: TSelected) => boolean\n): boolean =>\n    a.isLoaded === b.isLoaded &&\n    a.error === b.error &&\n    a.isActive === b.isActive &&\n    dataEqual(a.data, b.data)\n\n// Default slice comparison: the same value-based no-op compare the subscription\n// itself uses (`valuesEqualForNoOp`), so an identity selector reproduces the\n// pre-selector re-render behavior exactly, and a selector returning a fresh\n// object does not over-render.\nconst defaultDataEqual = valuesEqualForNoOp as <TSelected>(\n    a: TSelected,\n    b: TSelected\n) => boolean\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: false) */\n    undoable?: boolean\n    /**\n     * If false, no subscription is created and a no-op handle is returned\n     * (`{ data: undefined, isLoaded: false, ref: undefined }`). Use this to gate\n     * subscriptions on route params that aren't ready yet. 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). When that key changes — typically because `params` produces a\n * different id — the hook tears down the old Firestore listener and attaches a\n * new one. Toggling `undoable` does not rebuild the subscription.\n *\n * `readOnly` is a *per-handle capability*, not part of the key: a `readOnly`\n * hook shares the same listener and optimistic state as a writable hook on the\n * same document (a write through the writable handle is instantly visible to\n * the read-only reader), and only this handle's own writers (`update`/`set`/\n * `delete`) and `sync` are disabled.\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, isLoaded: false }`). Mutations like\n * `update`/`set` will mutate orphaned local state with no effect — avoid\n * calling them server-side.\n *\n * The default handle is **sync-agnostic** — it carries `data`/`isLoaded`/`error`\n * but not `isSynced`, so it does not re-render when a write settles. Render save\n * state via the per-entry `use{Name}SyncStatus` hook, or fold `isSynced` into a\n * `selector`.\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, isLoaded } = useDocument({\n *     definition: projectDoc,\n *     params: { projectId },\n *   })\n *\n *   if (!isLoaded) 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 function useDocument<TData extends FirestoreObject>(\n    options: UseDocumentOptions<TData> & WithoutSelector\n): DocumentHandle<TData>\n/**\n * Selector overload: pass `selector` to narrow the returned `data` to a slice\n * and re-render only when that slice changes. Writers (`update`/`set`/`delete`)\n * and `ref` keep operating on the full document. See\n * {@link DocumentSelectorOptions}.\n *\n * @example\n * ```tsx\n * // Re-renders only when the title changes, not on any other field.\n * const { data: title, update } = useDocument({\n *   definition: projectDoc,\n *   params: { projectId },\n *   selector: (s) => s.data?.title,\n * })\n * ```\n */\nexport function useDocument<TData extends FirestoreObject, TSelected>(\n    options: UseDocumentOptions<TData> &\n        DocumentSelectorOptions<TData, TSelected>\n): SelectedDocumentHandle<TData, TSelected>\nexport function useDocument<TData extends FirestoreObject, TSelected>(\n    options: UseDocumentOptions<TData> & {\n        selector?: (state: DocumentState<TData>) => TSelected\n        isEqual?: (a: TSelected, b: TSelected) => boolean\n    }\n): DocumentHandle<TData> | SelectedDocumentHandle<TData, TSelected> {\n    const {\n        definition,\n        params = {},\n        readOnly,\n        undoable = false,\n        enabled = true,\n        selector,\n        isEqual,\n    } = options\n    const store = useStore()\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    // Resolve (or create) the shared subscription for this resource. Every hook\n    // targeting the same document shares one instance — and so one listener and\n    // one optimistic state — instead of building a private subscription. Created\n    // in render (no listener attached) so getSnapshot returns the real, live\n    // handle immediately, including its `ref`.\n    const shared = useMemo(\n        () =>\n            enabled && docId !== undefined && collectionPath !== undefined\n                ? getDocumentShared<TData>({\n                      store,\n                      definition,\n                      collectionPath,\n                      docId,\n                      readOnly,\n                  })\n                : null,\n        [enabled, store, definition, docId, collectionPath, readOnly]\n    )\n\n    // Keep the shared subscription's undo flag in sync without re-subscribing\n    // (toggling `undoable` must not tear the listener down). Last writer wins\n    // across co-mounted hooks; the common case is a single value per resource.\n    useEffect(() => {\n        shared?.setUndoable(undoable)\n    }, [shared, undoable])\n\n    const subscribe = useCallback(\n        (onChange: () => void) => {\n            if (!shared) return NOOP\n            shared.setUndoable(undoable)\n            // Bump the shared ref count, register this hook's change callback, and\n            // activate the listener. Release tears the listener down only when this\n            // is the last lease (see shared-subscription.ts).\n            const release = shared.acquire(onChange)\n            // load() attaches the listener and can throw synchronously (e.g. an\n            // invalid ref). acquire() has already taken the lease, so release it\n            // before propagating — otherwise refCount sticks >=1, the entry is never\n            // evicted, and the callback/listener leak (a later sibling on the same\n            // key inherits the zombie entry).\n            try {\n                shared.load()\n            } catch (e) {\n                release()\n                throw e\n            }\n            return release\n        },\n        // `undoable` intentionally omitted: the effect above syncs it without\n        // resubscribing.\n        // eslint-disable-next-line react-hooks/exhaustive-deps\n        [shared]\n    )\n\n    // The useSyncExternalStore snapshot is the full observable STATE (data +\n    // every status flag, including isSynced). The selector projects from it; the\n    // no-selector path projects the sync-agnostic default. getState() is\n    // identity-stable between notifies, as useSyncExternalStore requires.\n    const getStateSnapshot = useCallback(\n        () =>\n            shared\n                ? shared.getState()\n                : (DISABLED_DOCUMENT_STATE as DocumentState<TData>),\n        [shared]\n    )\n\n    // The live handle, read for writers and `ref` only. Kept separate from the\n    // state snapshot so re-renders gate on the projected slice while methods/ref\n    // always come from the CURRENT subscription — a rebuild whose slice is\n    // value-equal still hands back the new subscription's methods (this callback's\n    // identity changes with `shared`, re-running the merge memo).\n    const getHandle = useCallback(\n        () =>\n            shared\n                ? shared.getHandle()\n                : (DISABLED_DOCUMENT_HANDLE as DocumentHandle<TData>),\n        [shared]\n    )\n\n    // Project the state snapshot to the value that drives re-renders. Keyed on\n    // `selector` so a referentially-new selector re-projects; an inline selector\n    // still dedupes against the committed value via `equal`, so callers need not\n    // memoize it.\n    //\n    // With a `selector` (pure mode): the projection IS the selector's output over\n    // the full state, gated purely by `equal`, so a status field it ignores (e.g.\n    // isSynced) can neither re-render the component nor appear on its handle.\n    // Without one: the sync-agnostic default — data + isLoaded + error — so a\n    // write settling does not re-render.\n    const select = useCallback(\n        (\n            state: DocumentState<TData>\n        ): TSelected | DefaultSelection<TData | undefined> =>\n            selector\n                ? selector(state)\n                : {\n                      data: state.data,\n                      isLoaded: state.isLoaded,\n                      error: state.error,\n                  },\n        [selector]\n    )\n\n    const equal = useCallback(\n        (\n            a: TSelected | DefaultSelection<TData | undefined>,\n            b: TSelected | DefaultSelection<TData | undefined>\n        ): boolean =>\n            selector\n                ? (isEqual ?? defaultDataEqual)(a as TSelected, b as TSelected)\n                : defaultSelectionEqual(\n                      a as DefaultSelection<TData | undefined>,\n                      b as DefaultSelection<TData | undefined>,\n                      defaultDataEqual\n                  ),\n        [selector, isEqual]\n    )\n\n    const selection = useSyncExternalStoreWithSelector(\n        subscribe,\n        getStateSnapshot,\n        getStateSnapshot,\n        select,\n        equal\n    )\n\n    // Re-wrap into a handle. Methods and `ref` are read *live* via getHandle()\n    // (keyed on `shared`) so a rebuild always hands back the new subscription's\n    // methods even when the projection was value-equal (see DefaultSelection).\n    //\n    // Keyed on selector *presence*, never its identity: an inline selector is a\n    // fresh function each render yet yields a value-equal `selection`, so the\n    // handle must keep referential identity (callers need not memoize it). Only\n    // the handle's shape — selected vs full — depends on the selector.\n    const hasSelector = selector != null\n    return useMemo(() => {\n        const handle = getHandle()\n        if (hasSelector) {\n            // Pure selected handle: `data` is the slice; status is intentionally\n            // absent (select it to react to it). Writers act on the full doc.\n            return {\n                data: selection as TSelected,\n                update: handle.update,\n                set: handle.set,\n                delete: handle.delete,\n                sync: handle.sync,\n                ref: handle.ref,\n            }\n        }\n        // Default handle: sync-agnostic — data + isLoaded + error (no isSynced).\n        const s = selection as DefaultSelection<TData | undefined>\n        return {\n            data: s.data,\n            update: handle.update,\n            set: handle.set,\n            delete: handle.delete,\n            isLoaded: s.isLoaded,\n            sync: handle.sync,\n            error: s.error,\n            ref: handle.ref,\n        }\n    }, [selection, getHandle, hasSelector])\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: false) */\n    undoable?: boolean\n    /**\n     * If false, no subscription is created and a no-op handle is returned\n     * (`{ data: {}, isLoaded: 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 and the *semantic\n * identity* of `queryConstraints`. When either changes, the listener is torn\n * down and re-attached with the new query. Toggling `undoable` does not rebuild\n * the subscription. `readOnly` is a per-handle capability, not part of the key —\n * a `readOnly` hook shares one listener and optimistic state with a writable\n * hook on the same query (see {@link useDocument}).\n *\n * **You do not need to memoize `queryConstraints`.** `QueryConstraint` objects\n * are opaque, so Firestate compares the *built query* with Firestore's own\n * `queryEqual` instead of comparing array references. A fresh array that\n * produces the same query (e.g. constraint inputs read from a document that\n * Firestate deep-clones on optimistic updates) does not rebuild the listener;\n * only a genuine change to the query does:\n *\n * ```tsx\n * // stationIds may change reference on every edit to its parent document,\n * // even when its contents are unchanged — the listener survives anyway.\n * const stations = useCollection({\n *   definition: weatherStations,\n *   queryConstraints: [where(documentId(), 'in', stationIds)],\n * })\n * ```\n *\n * Memoizing is still a fine micro-optimization (it skips the per-render query\n * build + compare via the reference fast-path), but it is no longer required\n * for listener stability.\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: {}, isLoaded: false }`, `isActive: false` for\n * lazy). Avoid calling mutations server-side.\n *\n * Like {@link useDocument}, the default handle is **sync-agnostic** — `data`,\n * `isLoaded`, `isActive`, `error`, but not `isSynced`. `isActive` stays so a\n * lazy collection can gate a \"Load\" button; `isLoaded` is `isActive &&\n * !isLoading`. Render save state via `use{Name}SyncStatus`.\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, isLoaded } = 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 (!isLoaded) 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 function useCollection<TData extends FirestoreObject>(\n    options: UseCollectionOptions<TData> & WithoutSelector\n): CollectionHandle<TData>\n/**\n * Selector overload: pass `selector` to narrow the returned `data` to a slice\n * of the collection and re-render only when that slice changes. Writers\n * (`update`/`add`/`remove`) and `ref` keep operating on the full collection.\n * See {@link CollectionSelectorOptions}.\n *\n * @example\n * ```tsx\n * // Re-renders only when this one document's slice changes.\n * const { data: space } = useCollection({\n *   definition: spacesCollection,\n *   params: { projectId },\n *   selector: (s) => s.data[spaceId],\n * })\n * ```\n */\nexport function useCollection<TData extends FirestoreObject, TSelected>(\n    options: UseCollectionOptions<TData> &\n        CollectionSelectorOptions<TData, TSelected>\n): SelectedCollectionHandle<TData, TSelected>\nexport function useCollection<TData extends FirestoreObject, TSelected>(\n    options: UseCollectionOptions<TData> & {\n        selector?: (state: CollectionState<TData>) => TSelected\n        isEqual?: (a: TSelected, b: TSelected) => boolean\n    }\n): CollectionHandle<TData> | SelectedCollectionHandle<TData, TSelected> {\n    const {\n        definition,\n        params = {},\n        readOnly,\n        queryConstraints,\n        undoable = false,\n        enabled = true,\n        selector,\n        isEqual,\n    } = options\n    const store = useStore()\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    // Stabilize `queryConstraints` by *query identity*. QueryConstraint objects\n    // are opaque and can't be deep-compared directly, but the built query can —\n    // see queryConstraintsEqual(). When the incoming array produces the same\n    // query as the one we're already subscribed with (e.g. constraint inputs\n    // read from a deep-cloned document churned the array reference without\n    // changing the query), we keep the previous array reference so the memo\n    // below does not rebuild. A genuine change adopts the new reference and\n    // re-attaches the listener. When the path is unresolved we can't build a\n    // query, so we just pass the constraints through (the memo returns null).\n    //\n    // The comparison may only run against constraints captured during an *active*\n    // render (enabled with a resolved path). Constraints captured while disabled\n    // or unresolved can be ones the caller is gating precisely because they don't\n    // form a valid query yet — e.g. `where(documentId(), 'in', [])`, which\n    // Firestore refuses to build. Building such a stale snapshot just to compare\n    // would throw, so when the prior snapshot wasn't active we adopt the current\n    // constraints outright. There is no live listener to preserve in that case\n    // (the subscription was null), so adopting a fresh reference costs nothing.\n    const stableConstraintsRef = useRef(queryConstraints)\n    const stableActiveRef = useRef(false)\n    const active = enabled && collectionPath !== undefined\n    if (\n        collectionPath === undefined ||\n        !stableActiveRef.current ||\n        !queryConstraintsEqual(\n            store.firestore,\n            collectionPath,\n            definition.queryConstraints,\n            stableConstraintsRef.current,\n            queryConstraints\n        )\n    ) {\n        stableConstraintsRef.current = queryConstraints\n    }\n    stableActiveRef.current = active\n    const stableConstraints = stableConstraintsRef.current\n\n    const isLazy = definition.lazy ?? false\n\n    // Build the query this hook subscribes to, keyed by *query identity*\n    // (stableConstraints already absorbs reference churn). This is what the shared\n    // registry matches on via `queryEqual`, so two hooks with semantically equal\n    // queries share one listener regardless of array identity. `null` when the\n    // constraints can't form a valid query yet (e.g. a gated empty-`in`\n    // placeholder, or while disabled/unresolved): no listener can run, so the hook\n    // resolves no shared entry and returns the disabled handle.\n    const builtQuery = useMemo<Query<unknown> | null>(\n        () =>\n            active\n                ? buildSharedCollectionQuery(\n                      store,\n                      collectionPath!,\n                      definition.queryConstraints,\n                      stableConstraints\n                  )\n                : null,\n        [active, store, collectionPath, definition, stableConstraints]\n    )\n\n    // Resolve (or create) the shared subscription for this resource+query. As with\n    // useDocument, every hook on the same collection+query shares one instance.\n    const shared = useMemo(\n        () =>\n            active && builtQuery !== null\n                ? getCollectionShared<TData>({\n                      store,\n                      definition,\n                      collectionPath: collectionPath!,\n                      readOnly,\n                      queryConstraints: stableConstraints,\n                      query: builtQuery,\n                  })\n                : null,\n        [\n            active,\n            store,\n            definition,\n            collectionPath,\n            readOnly,\n            stableConstraints,\n            builtQuery,\n        ]\n    )\n\n    useEffect(() => {\n        shared?.setUndoable(undoable)\n    }, [shared, undoable])\n\n    const subscribe = useCallback(\n        (onChange: () => void) => {\n            if (!shared) return NOOP\n            shared.setUndoable(undoable)\n            const release = shared.acquire(onChange)\n            // Lazy collections activate the shared listener only via `load()` on the\n            // handle; non-lazy ones activate on mount. Either way the listener stays\n            // up until the last lease releases.\n            if (!isLazy) {\n                // See useDocument's subscribe: release the lease acquire() just took if\n                // load() throws synchronously, or the entry/listener/callback leak.\n                try {\n                    shared.load()\n                } catch (e) {\n                    release()\n                    throw e\n                }\n            }\n            return release\n        },\n        // `undoable` intentionally omitted: the effect above syncs it without\n        // resubscribing.\n        // eslint-disable-next-line react-hooks/exhaustive-deps\n        [shared, isLazy]\n    )\n\n    // Snapshot is the full collection STATE; the live handle is read separately\n    // for writers/`load`/`ref`. See useDocument for the full rationale.\n    const getStateSnapshot = useCallback(\n        () =>\n            shared\n                ? shared.getState()\n                : (DISABLED_COLLECTION_STATE as CollectionState<TData>),\n        [shared]\n    )\n\n    const getHandle = useCallback(\n        () =>\n            shared\n                ? shared.getHandle()\n                : (DISABLED_COLLECTION_HANDLE as CollectionHandle<TData>),\n        [shared]\n    )\n\n    // See useDocument. With a `selector`: project the full state. Without one:\n    // the sync-agnostic default — data + isLoaded + isActive + error (no\n    // isSynced). `isActive` stays so lazy collections can gate a \"Load\" button.\n    const select = useCallback(\n        (\n            state: CollectionState<TData>\n        ): TSelected | DefaultSelection<Record<string, TData>> =>\n            selector\n                ? selector(state)\n                : {\n                      data: state.data,\n                      isLoaded: state.isLoaded,\n                      isActive: state.isActive,\n                      error: state.error,\n                  },\n        [selector]\n    )\n\n    const equal = useCallback(\n        (\n            a: TSelected | DefaultSelection<Record<string, TData>>,\n            b: TSelected | DefaultSelection<Record<string, TData>>\n        ): boolean =>\n            selector\n                ? (isEqual ?? defaultDataEqual)(a as TSelected, b as TSelected)\n                : defaultSelectionEqual(\n                      a as DefaultSelection<Record<string, TData>>,\n                      b as DefaultSelection<Record<string, TData>>,\n                      defaultDataEqual\n                  ),\n        [selector, isEqual]\n    )\n\n    const selection = useSyncExternalStoreWithSelector(\n        subscribe,\n        getStateSnapshot,\n        getStateSnapshot,\n        select,\n        equal\n    )\n\n    // See useDocument: keyed on selector *presence*, not identity, so an inline\n    // selector still yields a stable handle. Methods/`ref` read live from\n    // getHandle; only the handle's shape depends on the selector.\n    const hasSelector = selector != null\n    return useMemo(() => {\n        const handle = getHandle()\n        if (hasSelector) {\n            // Pure selected handle: status is absent (select it to react to it);\n            // writers and `load` act on the full collection.\n            return {\n                data: selection as TSelected,\n                update: handle.update,\n                add: handle.add,\n                remove: handle.remove,\n                load: handle.load,\n                sync: handle.sync,\n                ref: handle.ref,\n            }\n        }\n        // Default handle: sync-agnostic — data + isLoaded + isActive + error.\n        const s = selection as DefaultSelection<Record<string, TData>>\n        return {\n            data: s.data,\n            update: handle.update,\n            add: handle.add,\n            remove: handle.remove,\n            isLoaded: s.isLoaded,\n            isActive: s.isActive ?? false,\n            load: handle.load,\n            sync: handle.sync,\n            error: s.error,\n            ref: handle.ref,\n        }\n    }, [selection, getHandle, hasSelector])\n}\n\n// ---------------------------------------------------------------------------\n// Per-resource status hooks (sync / loading)\n//\n// Thin readers over useDocument / useCollection with a fixed selector. Because\n// sharing is keyed by (definition, path, query) — not readOnly, not the selector\n// — these resolve the SAME shared entry as the data hook, so opting into status\n// adds NO listener and reads the same optimistic state. They return the projected\n// slice directly (no handle wrapper).\n// ---------------------------------------------------------------------------\n\n// Module-level selectors: stable identity keeps the underlying hook's `select`\n// callback stable. Each returns a fresh object, but the default value compare\n// (two booleans) collapses it, so a status hook re-renders only on a real flip.\n// Typed structurally on just the field read, so one selector serves both the\n// DocumentState and CollectionState selector slots.\nconst syncStatusSelector = (state: { isSynced: boolean }): SyncStatus => ({\n    isSynced: state.isSynced,\n    isSaving: !state.isSynced,\n})\n\nconst loadingStatusSelector = (state: {\n    isLoading: boolean\n    isLoaded: boolean\n}): LoadingStatus => ({\n    isLoading: state.isLoading,\n    isLoaded: state.isLoaded,\n})\n\n/** Options for the document status hooks (a subset of {@link UseDocumentOptions}). */\nexport interface UseDocumentStatusOptions<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    /**\n     * If false, no subscription is created and the idle status is returned\n     * (`{ isSynced: true, isSaving: false }` / `{ isLoading: false, isLoaded:\n     * false }`). Default: true.\n     */\n    enabled?: boolean\n}\n\n/** Options for the collection status hooks. Adds `queryConstraints`. */\nexport interface UseCollectionStatusOptions<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    /**\n     * Query constraints. Must produce the same query the data hook uses, or the\n     * status hook resolves a *different* shared entry (a second listener) —\n     * sharing is keyed by semantic query identity.\n     */\n    queryConstraints?: QueryConstraint[]\n    /** See {@link UseDocumentStatusOptions.enabled}. */\n    enabled?: boolean\n}\n\n/**\n * Subscribe to a document's **sync status only** — `{ isSynced, isSaving }`.\n *\n * The opt-in counterpart to the sync-agnostic default handle (see\n * {@link DocumentHandle}): it re-renders when sync state flips but never on data\n * changes, and shares the resource's one `onSnapshot` listener with\n * `useDocument` and any slice hooks, so opting in adds no listener. While\n * disabled it reports `{ isSynced: true, isSaving: false }`.\n */\nexport function useDocumentSyncStatus<TData extends FirestoreObject>(\n    options: UseDocumentStatusOptions<TData>\n): SyncStatus {\n    return useDocument({\n        definition: options.definition,\n        params: options.params,\n        enabled: options.enabled,\n        readOnly: true,\n        selector: syncStatusSelector,\n    }).data\n}\n\n/**\n * Subscribe to a document's **loading status only** — `{ isLoading, isLoaded }`.\n *\n * A spinner channel that shares the resource's listener and does NOT re-render\n * on data changes — for a progress indicator rendered apart from the data. The\n * data handle keeps `isLoaded` for the common render path; this is an extra\n * channel, not a replacement.\n */\nexport function useDocumentLoadingStatus<TData extends FirestoreObject>(\n    options: UseDocumentStatusOptions<TData>\n): LoadingStatus {\n    return useDocument({\n        definition: options.definition,\n        params: options.params,\n        enabled: options.enabled,\n        readOnly: true,\n        selector: loadingStatusSelector,\n    }).data\n}\n\n/**\n * Collection counterpart of {@link useDocumentSyncStatus} — `{ isSynced,\n * isSaving }` over a collection query, sharing its one listener.\n *\n * **Lazy caveat.** On a `lazy` collection this hook never calls `load()` itself:\n * activating a lazy listener is the data hook's job, and a passive status reader\n * must not silently start the listener (and bill the reads) the laziness exists\n * to defer. As the *lone* subscriber it therefore attaches no listener and stays\n * at the idle `{ isSynced: true, isSaving: false }`. Mount it alongside a\n * {@link useCollection} on the same query whose `load()` has run — the status\n * hook rides that one shared listener and reports real sync state. Non-lazy\n * collections activate on mount, so this hook works standalone there.\n */\nexport function useCollectionSyncStatus<TData extends FirestoreObject>(\n    options: UseCollectionStatusOptions<TData>\n): SyncStatus {\n    return useCollection({\n        definition: options.definition,\n        params: options.params,\n        queryConstraints: options.queryConstraints,\n        enabled: options.enabled,\n        readOnly: true,\n        selector: syncStatusSelector,\n    }).data\n}\n\n/**\n * Collection counterpart of {@link useDocumentLoadingStatus} — `{ isLoading,\n * isLoaded }` over a collection query, sharing its one listener.\n *\n * Same lazy caveat as {@link useCollectionSyncStatus}: on a `lazy` collection it\n * never calls `load()`, so as the lone subscriber it attaches no listener and\n * stays at the idle `{ isLoading: false, isLoaded: false }` until a co-mounted\n * {@link useCollection} (or any active hook on the same query) activates the\n * shared listener via `load()`. Non-lazy collections activate on mount.\n */\nexport function useCollectionLoadingStatus<TData extends FirestoreObject>(\n    options: UseCollectionStatusOptions<TData>\n): LoadingStatus {\n    return useCollection({\n        definition: options.definition,\n        params: options.params,\n        queryConstraints: options.queryConstraints,\n        enabled: options.enabled,\n        readOnly: true,\n        selector: loadingStatusSelector,\n    }).data\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  useDocumentSyncStatus,\n  useDocumentLoadingStatus,\n  useCollectionSyncStatus,\n  useCollectionLoadingStatus,\n  type UseDocumentOptions,\n  type UseCollectionOptions,\n  type DocumentSelectorOptions,\n  type CollectionSelectorOptions,\n} from \"../react/hooks\";\nimport type {\n  CollectionDefinition,\n  CollectionHandle,\n  CollectionState,\n  DocumentDefinition,\n  DocumentHandle,\n  DocumentState,\n  FirestoreObject,\n  LoadingStatus,\n  SelectedCollectionHandle,\n  SelectedDocumentHandle,\n  SyncStatus,\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  /**\n   * Path template, e.g. `'taskLists/{listId}'`, or a function returning the\n   * **full document path** at runtime (the collection/id split happens\n   * per-call via {@link splitDocPath}). Use the function form for paths that\n   * branch on a param. See {@link PathArg}.\n   */\n  path: PathArg<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   * Derive a **named slice-hook** off this document, sharing its schema and\n   * path — the schema is handed to firestate once, here, and never\n   * re-specified. The `selector` receives the full {@link DocumentState};\n   * return the slice the generated hook reacts to. For a *parameterized* slice,\n   * declare the extra params as the selector's second argument — the generated\n   * hook then requires the path params **and** those, merged into one bag.\n   *\n   * Pass the result to {@link createFirestate} under the key the hook is named\n   * for. Status is reactive only if the slice reads it, exactly as the inline\n   * `selector` option (see {@link DocumentHandle}); the comparator (`isEqual`)\n   * is baked in here, not passed per call.\n   *\n   * ```ts\n   * const project = doc({ path: 'projects/{projectId}', schema: ProjectSchema })\n   * const { useProject, useProjectTitle } = createFirestate({\n   *   project,                                           // → useProject (full)\n   *   projectTitle: project.select((s) => s.data?.name), // → useProjectTitle\n   * })\n   * ```\n   *\n   * A derived entry is a leaf, not a base: there is intentionally no\n   * `.select(...).select(...)` chaining.\n   *\n   * `PExtra` (the selector's own params) defaults to `{}`: a one-argument\n   * selector leaves it unbound, a two-argument one infers it from the annotated\n   * second parameter. One signature keeps the selector's `state` arg reliably\n   * typed in both cases. `PExtra` is intentionally unconstrained — leaving it\n   * `extends Record<string, string>` made TS resolve a param-less selector's\n   * `PExtra` to that constraint (not the `{}` default), wrongly forcing a\n   * `params` arg on no-placeholder paths; unconstrained also lets a slice take\n   * non-string params (e.g. `{ index: number }`).\n   */\n  select<TSelected, PExtra = {}>(\n    selector: (state: DocumentState<T>, params: PExtra) => TSelected,\n    options?: SelectOptions<TSelected>\n  ): SelectedDocEntry<T, P, PExtra, TSelected>;\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  /**\n   * Path template, e.g. `'taskLists/{listId}/tasks'`, or a function returning\n   * the **collection path** at runtime. Use the function form for paths that\n   * branch on a param. See {@link PathArg}.\n   */\n  path: PathArg<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  /**\n   * Derive a **named slice-hook** off this collection, sharing its schema,\n   * path, and query — see {@link DocEntry.select}. The `selector` receives the\n   * full {@link CollectionState} (`s.data` is the keyed record), and a\n   * parameterized slice declares its extra params as the selector's second\n   * argument.\n   *\n   * ```ts\n   * const tasks = col({ path: 'projects/{projectId}/tasks', schema: TaskSchema })\n   * const { useTasks, useTaskIds, useTaskById } = createFirestate({\n   *   tasks,                                                  // → useTasks (full)\n   *   taskIds:  tasks.select((s) => Object.keys(s.data)),     // → useTaskIds\n   *   taskById: tasks.select((s, p: { id: string }) => s.data[p.id]), // → useTaskById\n   * })\n   * // useTaskById requires the merged bag: useTaskById({ projectId, id })\n   * ```\n   *\n   * `PExtra` defaults to `{}` and is unconstrained — see {@link DocEntry.select}.\n   */\n  select<TSelected, PExtra = {}>(\n    selector: (state: CollectionState<T>, params: PExtra) => TSelected,\n    options?: SelectOptions<TSelected>\n  ): SelectedColEntry<T, P, PExtra, TSelected>;\n}\n\n/**\n * Options bundled into a `.select(...)` entry at definition time. Kept separate\n * from the runtime hook options (`enabled`/`readOnly`/`queryConstraints`)\n * because these are baked into the named hook, not passed per call.\n */\nexport interface SelectOptions<TSelected> {\n  /**\n   * Comparator for this named hook's slice; the hook re-renders only when it\n   * returns `false`. Defaults to a deep value compare (so a fresh object/array\n   * of equal shape does not over-render). Pass {@link shallow} or a custom fn.\n   */\n  isEqual?: (a: TSelected, b: TSelected) => boolean;\n}\n\n/**\n * A {@link DocEntry} narrowed by a `.select(...)` projection. Produced by\n * {@link DocEntry.select}, consumed by {@link createFirestate}, which turns it\n * into a hook whose `data` is the slice (`TSelected`) and whose params are the\n * path params (`P`) merged with the selector's own params (`PExtra`).\n *\n * The schema/path/options live on `base` — a derived entry never re-declares\n * them. `PExtra` is `{}` for an un-parameterized selector.\n */\nexport interface SelectedDocEntry<\n  T extends FirestoreObject,\n  P extends string,\n  PExtra,\n  TSelected\n> {\n  readonly __kind: \"document-selected\";\n  /** Base entry carrying schema/path/options — handed to firestate once. */\n  readonly base: DocEntry<T, P>;\n  /** Projection over the full state; receives the merged params bag at runtime. */\n  readonly selector: (state: DocumentState<T>, params: PExtra) => TSelected;\n  /** Comparator baked in at definition time (see {@link SelectOptions}). */\n  readonly isEqual?: (a: TSelected, b: TSelected) => boolean;\n}\n\n/**\n * A {@link ColEntry} narrowed by a `.select(...)` projection. See\n * {@link SelectedDocEntry}; the selector receives the collection's keyed state.\n */\nexport interface SelectedColEntry<\n  T extends FirestoreObject,\n  P extends string,\n  PExtra,\n  TSelected\n> {\n  readonly __kind: \"collection-selected\";\n  /** Base entry carrying schema/path/query/options — handed to firestate once. */\n  readonly base: ColEntry<T, P>;\n  /** Projection over the full state; receives the merged params bag at runtime. */\n  readonly selector: (state: CollectionState<T>, params: PExtra) => TSelected;\n  /** Comparator baked in at definition time (see {@link SelectOptions}). */\n  readonly isEqual?: (a: TSelected, b: TSelected) => boolean;\n}\n\nexport type FirestateEntry<\n  T extends FirestoreObject = FirestoreObject,\n  P extends string = string\n> = DocEntry<T, P> | ColEntry<T, P>;\n\n/** Any `.select(...)`-derived entry, regardless of its type parameters. */\nexport type AnySelectedEntry =\n  | SelectedDocEntry<any, any, any, any>\n  | SelectedColEntry<any, any, any, any>;\n\nexport type FirestateRegistry = Record<\n  string,\n  FirestateEntry<any, any> | AnySelectedEntry\n>;\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 * The `path` accepted by {@link doc} / {@link col}. Either a static template\n * (whose `{param}` placeholders are interpolated and whose param keys are\n * inferred via {@link ParamsOf}), or a function that returns the path at\n * runtime — for paths that branch on a param, e.g. live\n * `projects/{projectId}/spaces` vs. revision\n * `projects/{projectId}/revisions/{revisionId}/spaces`.\n *\n * With the function form, params can't be inferred from a template, so the\n * generated hook's params fall back to `Record<string, string>`.\n */\nexport type PathArg<P extends string> =\n  | P\n  | ((params: Record<string, string>) => string);\n\n// ---------------------------------------------------------------------------\n// Entry factories\n// ---------------------------------------------------------------------------\n\n// `select` is excluded too: it's a method the factory attaches, never an input.\ntype DocOpts<T extends FirestoreObject> = Omit<\n  DocEntry<T>,\n  \"__kind\" | \"__type\" | \"path\" | \"select\"\n>;\ntype ColOpts<T extends FirestoreObject> = Omit<\n  ColEntry<T>,\n  \"__kind\" | \"__type\" | \"path\" | \"select\"\n>;\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 * `path` may also be a function returning the full document path at runtime —\n * for paths that branch on a param. Param keys can't be inferred from a\n * function, so they fall back to `Record<string, string>`. See {@link PathArg}.\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: PathArg<P>;\n  }\n): DocEntry<z.infer<S>, P> {\n  const { path, ...rest } = opts;\n  // Static templates fail loud at registration: a malformed placeholder or a\n  // path that can't be split into a non-empty collection + id throws here.\n  // Function paths are checked per-call in buildDocumentDefinition, once they\n  // resolve to a concrete string.\n  if (typeof path === \"string\") {\n    validateTemplate(path);\n    splitDocPath(path);\n  }\n  const entry = { __kind: \"document\", path, ...rest } as Record<string, unknown>;\n  // Attach the `.select(...)` builder. It closes over `entry`, so a derived\n  // entry's `base` is this exact object — the schema/path/options are handed to\n  // firestate once, here, and reused by every slice-hook derived from it.\n  entry.select = (\n    selector: (\n      state: DocumentState<FirestoreObject>,\n      params: Record<string, string>\n    ) => unknown,\n    options?: SelectOptions<unknown>\n  ): SelectedDocEntry<FirestoreObject, string, Record<string, string>, unknown> => ({\n    __kind: \"document-selected\",\n    base: entry as unknown as DocEntry<FirestoreObject, string>,\n    selector,\n    isEqual: options?.isEqual,\n  });\n  return entry as unknown as DocEntry<z.infer<S>, P>;\n}\n\n/**\n * Declare a collection entry for a Firestate registry. See {@link doc}\n * for the schema/typing contract. `path` may also be a function returning\n * the collection path at runtime — see {@link PathArg}.\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: PathArg<P>;\n  }\n): ColEntry<z.infer<S>, P> {\n  const { path, ...rest } = opts;\n  // Static templates fail loud at registration; function paths resolve later.\n  if (typeof path === \"string\") {\n    validateTemplate(path);\n  }\n  const entry = { __kind: \"collection\", path, ...rest } as Record<string, unknown>;\n  // See doc(): the builder closes over `entry` so derived hooks reuse this\n  // collection's schema/path/query — never re-specified.\n  entry.select = (\n    selector: (\n      state: CollectionState<FirestoreObject>,\n      params: Record<string, string>\n    ) => unknown,\n    options?: SelectOptions<unknown>\n  ): SelectedColEntry<FirestoreObject, string, Record<string, string>, unknown> => ({\n    __kind: \"collection-selected\",\n    base: entry as unknown as ColEntry<FirestoreObject, string>,\n    selector,\n    isEqual: options?.isEqual,\n  });\n  return entry as unknown as ColEntry<z.infer<S>, P>;\n}\n\n// ---------------------------------------------------------------------------\n// createFirestate\n// ---------------------------------------------------------------------------\n\ntype HookName<K extends string> = `use${Capitalize<K>}`;\n\n// Excludes selector options from the non-selector overload, so passing a real\n// `selector` falls through to the selector overload (which infers `TSelected`)\n// instead of resolving to the full-data return.\ntype NoSelector = { selector?: undefined; isEqual?: undefined };\n\n// Each generated hook is overloaded: call it without a `selector` to get the\n// full handle, or with one to get a handle whose `data` is the selected slice.\n// The two `*OptionalParams` / `*RequiredParams` shapes capture whether the path\n// template has placeholders (optional vs. required `params`). In the selector\n// overload `options` is required (it must carry `selector`), so `params` cannot\n// be optional before it — the no-placeholder selector form therefore takes\n// `params` as `Record<string, string> | undefined` (pass `{}` or `undefined`).\n\ninterface DocHookOptionalParams<T extends FirestoreObject> {\n  (\n    params?: Record<string, string>,\n    options?: DocHookOptions<T> & NoSelector\n  ): DocumentHandle<T>;\n  <TSelected>(\n    params: Record<string, string> | undefined,\n    options: DocHookOptions<T> & DocumentSelectorOptions<T, TSelected>\n  ): SelectedDocumentHandle<T, TSelected>;\n}\n\ninterface DocHookRequiredParams<T extends FirestoreObject, P extends string> {\n  (\n    params: ParamsOf<P>,\n    options?: DocHookOptions<T> & NoSelector\n  ): DocumentHandle<T>;\n  <TSelected>(\n    params: ParamsOf<P>,\n    options: DocHookOptions<T> & DocumentSelectorOptions<T, TSelected>\n  ): SelectedDocumentHandle<T, TSelected>;\n}\n\ninterface ColHookOptionalParams<T extends FirestoreObject> {\n  (\n    params?: Record<string, string>,\n    options?: ColHookOptions<T> & NoSelector\n  ): CollectionHandle<T>;\n  <TSelected>(\n    params: Record<string, string> | undefined,\n    options: ColHookOptions<T> & CollectionSelectorOptions<T, TSelected>\n  ): SelectedCollectionHandle<T, TSelected>;\n}\n\ninterface ColHookRequiredParams<T extends FirestoreObject, P extends string> {\n  (\n    params: ParamsOf<P>,\n    options?: ColHookOptions<T> & NoSelector\n  ): CollectionHandle<T>;\n  <TSelected>(\n    params: ParamsOf<P>,\n    options: ColHookOptions<T> & CollectionSelectorOptions<T, TSelected>\n  ): SelectedCollectionHandle<T, TSelected>;\n}\n\n// ---------------------------------------------------------------------------\n// Selected (`.select`) hook shapes\n// ---------------------------------------------------------------------------\n\n// The merged params bag for a selected hook: the path-template params (`P`)\n// intersected with the selector's own params (`PExtra`), flattened so errors\n// read as one object instead of an intersection.\n//\n// `PExtra` arrives as `any` when the selector took no params (an empty `{}`\n// `PExtra` widens to `any` through the registry's `AnySelectedEntry` bound — the\n// `{}` is absorbed in the contravariant selector position). `any` here means \"no\n// declared params\", so the bag is just the path params; otherwise a no-arg slice\n// on a no-placeholder path would wrongly demand a `Record<string, any>`. A real\n// `PExtra` (e.g. `{ id: string }`) is never `any` and merges normally.\ntype IsAny<T> = 0 extends 1 & T ? true : false;\ntype SelectedParams<P extends string, PExtra> = IsAny<PExtra> extends true\n  ? ParamsOf<P>\n  : Prettify<ParamsOf<P> & PExtra>;\n\n// Generated hook for a selected document entry: `data` is the slice, `params`\n// is the merged bag, and `options` carries only the runtime knobs — the\n// selector and its comparator are baked in, so neither appears here. Params are\n// optional only when the merged bag has no keys (static path, no selector params).\ntype SelectedDocHookFor<\n  T extends FirestoreObject,\n  P extends string,\n  PExtra,\n  TSelected\n> = keyof SelectedParams<P, PExtra> extends never\n  ? (\n      params?: Record<string, string>,\n      options?: DocHookOptions<T>\n    ) => SelectedDocumentHandle<T, TSelected>\n  : (\n      params: SelectedParams<P, PExtra>,\n      options?: DocHookOptions<T>\n    ) => SelectedDocumentHandle<T, TSelected>;\n\n// As {@link SelectedDocHookFor}, for a selected collection entry.\ntype SelectedColHookFor<\n  T extends FirestoreObject,\n  P extends string,\n  PExtra,\n  TSelected\n> = keyof SelectedParams<P, PExtra> extends never\n  ? (\n      params?: Record<string, string>,\n      options?: ColHookOptions<T>\n    ) => SelectedCollectionHandle<T, TSelected>\n  : (\n      params: SelectedParams<P, PExtra>,\n      options?: ColHookOptions<T>\n    ) => SelectedCollectionHandle<T, TSelected>;\n\n// Selected entries are matched first: they carry no `path`/`schema`, so they\n// never collide with the base Doc/ColEntry arms below. For a base entry, a path\n// template with no placeholders takes optional `params`; one with placeholders\n// requires an object with exactly the extracted keys.\ntype HookFor<E> = E extends SelectedDocEntry<\n  infer T,\n  infer P,\n  infer PExtra,\n  infer TSelected\n>\n  ? SelectedDocHookFor<T, P, PExtra, TSelected>\n  : E extends SelectedColEntry<infer T, infer P, infer PExtra, infer TSelected>\n  ? SelectedColHookFor<T, P, PExtra, TSelected>\n  : E extends DocEntry<infer T, infer P>\n  ? keyof ParamsOf<P> extends never\n    ? DocHookOptionalParams<T>\n    : DocHookRequiredParams<T, P>\n  : E extends ColEntry<infer T, infer P>\n  ? keyof ParamsOf<P> extends never\n    ? ColHookOptionalParams<T>\n    : ColHookRequiredParams<T, P>\n  : never;\n\n// ---------------------------------------------------------------------------\n// Per-entry status hooks (sync / loading)\n// ---------------------------------------------------------------------------\n\n// Each *base* doc/col entry also gets `use{Name}SyncStatus` and\n// `use{Name}LoadingStatus`. `.select` (derived) entries do not: a slice's sync\n// and loading status are the resource's, read through its base hooks.\n//\n// Lazy caveat: for a `lazy` collection these status hooks never call `load()`\n// themselves (a passive reader must not activate the listener the laziness\n// defers), so a lone status hook reports idle until a co-mounted `use{Name}`\n// data hook activates the shared listener via `load()`. See\n// useCollectionSyncStatus in ../react/hooks for the full rationale.\ntype SyncStatusHookName<K extends string> = `${HookName<K>}SyncStatus`;\ntype LoadingStatusHookName<K extends string> = `${HookName<K>}LoadingStatus`;\n\n// Base (non-selected) entries, used to gate which keys produce status hooks.\ntype BaseEntry = DocEntry<any, any> | ColEntry<any, any>;\n\n// Runtime knobs forwarded to a generated status hook. Documents take only\n// `enabled`; collections add `queryConstraints` (which must match the data\n// hook's, or the status hook resolves a different shared entry — i.e. a second\n// listener). `readOnly`/`selector`/`isEqual` are owned by the status hook.\ntype DocStatusHookOptions = { enabled?: boolean };\ntype ColStatusHookOptions = {\n  enabled?: boolean;\n  queryConstraints?: QueryConstraint[];\n};\n\n// Params follow the same optional-vs-required rule as the data hooks: a path\n// with no `{placeholder}` takes optional `params`; one with placeholders\n// requires exactly those keys. `R` is the returned status shape.\ntype DocStatusHookFor<P extends string, Ret> = keyof ParamsOf<P> extends never\n  ? (params?: Record<string, string>, options?: DocStatusHookOptions) => Ret\n  : (params: ParamsOf<P>, options?: DocStatusHookOptions) => Ret;\n\ntype ColStatusHookFor<P extends string, Ret> = keyof ParamsOf<P> extends never\n  ? (params?: Record<string, string>, options?: ColStatusHookOptions) => Ret\n  : (params: ParamsOf<P>, options?: ColStatusHookOptions) => Ret;\n\ntype SyncStatusHookFor<E> = E extends DocEntry<any, infer P>\n  ? DocStatusHookFor<P, SyncStatus>\n  : E extends ColEntry<any, infer P>\n  ? ColStatusHookFor<P, SyncStatus>\n  : never;\n\ntype LoadingStatusHookFor<E> = E extends DocEntry<any, infer P>\n  ? DocStatusHookFor<P, LoadingStatus>\n  : E extends ColEntry<any, infer P>\n  ? ColStatusHookFor<P, LoadingStatus>\n  : never;\n\n// The generated API: the data/slice hooks, plus a sync-status and a\n// loading-status hook for every BASE entry (selected entries map their status\n// key to `never`, which drops it). Three mapped types intersected because key\n// remapping yields one key per source key — destructuring resolves the\n// intersection member-by-member.\nexport type FirestateApi<R extends FirestateRegistry> = {\n  [K in keyof R & string as HookName<K>]: HookFor<R[K]>;\n} & {\n  [K in keyof R & string as R[K] extends BaseEntry\n    ? SyncStatusHookName<K>\n    : never]: SyncStatusHookFor<R[K]>;\n} & {\n  [K in keyof R & string as R[K] extends BaseEntry\n    ? LoadingStatusHookName<K>\n    : never]: LoadingStatusHookFor<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  // Built definitions are memoized by their *base entry object*, so the base\n  // hook and every `.select` sibling derived from it resolve the SAME definition\n  // — and therefore the SAME shared subscription (one onSnapshot listener, one\n  // optimistic state). `.select` stores the base entry by reference, so `entry`\n  // (a base) and `entry.base` (a selected entry) hit the same key. Without this,\n  // each registry key built a fresh definition and the shared-subscription\n  // registry (keyed by definition identity) forked one listener per hook.\n  const docDefs = new Map<\n    DocEntry<FirestoreObject, string>,\n    DocumentDefinition<FirestoreObject>\n  >();\n  const colDefs = new Map<\n    ColEntry<FirestoreObject, string>,\n    CollectionDefinition<FirestoreObject>\n  >();\n  const docDefFor = (\n    base: DocEntry<FirestoreObject, string>\n  ): DocumentDefinition<FirestoreObject> => {\n    let def = docDefs.get(base);\n    if (!def) {\n      def = buildDocumentDefinition(base);\n      docDefs.set(base, def);\n    }\n    return def;\n  };\n  const colDefFor = (\n    base: ColEntry<FirestoreObject, string>\n  ): CollectionDefinition<FirestoreObject> => {\n    let def = colDefs.get(base);\n    if (!def) {\n      def = buildCollectionDefinition(base);\n      colDefs.set(base, def);\n    }\n    return def;\n  };\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 = docDefFor(entry);\n      api[hookName] = (\n        params: Record<string, string> = {},\n        options: DocHookOptions<FirestoreObject> = {}\n      ) => useDocument({ ...options, definition, params });\n      // Sync/loading status siblings share the same `definition`, so they\n      // resolve the SAME shared subscription as the data hook (no extra\n      // listener) — see the shared-subscription contract.\n      api[`${hookName}SyncStatus`] = (\n        params: Record<string, string> = {},\n        options: DocStatusHookOptions = {}\n      ) => useDocumentSyncStatus({ definition, params, enabled: options.enabled });\n      api[`${hookName}LoadingStatus`] = (\n        params: Record<string, string> = {},\n        options: DocStatusHookOptions = {}\n      ) =>\n        useDocumentLoadingStatus({ definition, params, enabled: options.enabled });\n    } else if (entry.__kind === \"collection\") {\n      const definition = colDefFor(entry);\n      api[hookName] = (\n        params: Record<string, string> = {},\n        options: ColHookOptions<FirestoreObject> = {}\n      ) => useCollection({ ...options, definition, params });\n      api[`${hookName}SyncStatus`] = (\n        params: Record<string, string> = {},\n        options: ColStatusHookOptions = {}\n      ) =>\n        useCollectionSyncStatus({\n          definition,\n          params,\n          enabled: options.enabled,\n          queryConstraints: options.queryConstraints,\n        });\n      api[`${hookName}LoadingStatus`] = (\n        params: Record<string, string> = {},\n        options: ColStatusHookOptions = {}\n      ) =>\n        useCollectionLoadingStatus({\n          definition,\n          params,\n          enabled: options.enabled,\n          queryConstraints: options.queryConstraints,\n        });\n    } else if (entry.__kind === \"document-selected\") {\n      const definition = docDefFor(entry.base);\n      const { selector, isEqual } = entry;\n      api[hookName] = (\n        params: Record<string, string> = {},\n        options: DocHookOptions<FirestoreObject> = {}\n      ) =>\n        useDocument({\n          ...options,\n          definition,\n          params,\n          // Adapt the (state, params) selector to Level 1's inline `selector`\n          // by closing over this call's params bag — so a parameterized slice\n          // (e.g. `(s, p) => s.data[p.id]`) reads its id from the same bag the\n          // path resolved from. A fresh closure each render is fine: useDocument\n          // dedupes on the selected *value*, not the selector's identity.\n          selector: (state) => selector(state, params),\n          isEqual,\n        });\n    } else {\n      // collection-selected\n      const definition = colDefFor(entry.base);\n      const { selector, isEqual } = entry;\n      api[hookName] = (\n        params: Record<string, string> = {},\n        options: ColHookOptions<FirestoreObject> = {}\n      ) =>\n        useCollection({\n          ...options,\n          definition,\n          params,\n          selector: (state) => selector(state, params),\n          isEqual,\n        });\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 { path } = entry;\n  const common = {\n    schema: entry.schema,\n    autosave: entry.autosave,\n    minLoadTime: entry.minLoadTime,\n    readOnly: entry.readOnly,\n    retryOnError: entry.retryOnError,\n    retryInterval: entry.retryInterval,\n  };\n\n  if (typeof path === \"function\") {\n    // The function returns the FULL document path; split it per-call. It has\n    // no `{param}` placeholders left to interpolate, but splitDocPath still\n    // throws loud on a missing '/' or an empty collection/id segment — the\n    // boundary check, just deferred to resolution time.\n    return defineDocument<T>({\n      ...common,\n      collection: (params) => splitDocPath(path(params)).collectionPath,\n      id: (params) => splitDocPath(path(params)).idTemplate,\n    } as DocumentDefinition<T>);\n  }\n\n  // Static template. Both halves are functions so any `{param}` placeholder in\n  // the collection portion (e.g. `projects/{projectId}/revisions`) is resolved\n  // per-call against the params passed to the hook.\n  const { collectionPath, idTemplate } = splitDocPath(path);\n  return defineDocument<T>({\n    ...common,\n    collection: (params) => interpolate(collectionPath, params),\n    id: (params) => interpolate(idTemplate, params),\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  const { path } = entry;\n  return defineCollection<T>({\n    schema: entry.schema,\n    // Function paths pass straight through to defineCollection; static\n    // templates are interpolated per-call.\n    path:\n      typeof path === \"function\"\n        ? path\n        : (params) => interpolate(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","/**\n * Shallow structural equality.\n *\n * Returns `true` when `a` and `b` are identical by `Object.is`, or are two\n * arrays / two plain objects whose entries are pairwise `Object.is`-equal one\n * level deep. Anything else (different shapes, nested objects that aren't\n * reference-equal) is `false`.\n *\n * Intended as the `isEqual` for a hook `selector` that builds a fresh array or\n * object every render — e.g. `data => Object.values(data).map(d => d.id)` or\n * `data => ({ name: data?.name, done: data?.done })`. The default selector\n * comparison is a *deep* value compare, which is correct but does more work\n * than needed for a flat projection; `shallow` re-renders on a genuine change\n * to any entry while collapsing the fresh-reference-same-entries case.\n *\n * Not recursive on purpose: if a selected entry is itself an object you mutate\n * in place rather than replace, prefer the default deep comparison or a\n * bespoke `isEqual`.\n */\nexport const shallow = <T>(a: T, b: T): boolean => {\n  if (Object.is(a, b)) return true;\n\n  if (\n    typeof a !== \"object\" ||\n    a === null ||\n    typeof b !== \"object\" ||\n    b === null\n  ) {\n    return false;\n  }\n\n  const aIsArray = Array.isArray(a);\n  if (aIsArray !== Array.isArray(b)) return false;\n\n  if (aIsArray) {\n    const arrA = a as unknown[];\n    const arrB = b as unknown[];\n    if (arrA.length !== arrB.length) return false;\n    for (let i = 0; i < arrA.length; i++) {\n      if (!Object.is(arrA[i], arrB[i])) return false;\n    }\n    return true;\n  }\n\n  const objA = a as Record<string, unknown>;\n  const objB = b as Record<string, unknown>;\n  const keysA = Object.keys(objA);\n  if (keysA.length !== Object.keys(objB).length) return false;\n  for (const key of keysA) {\n    if (\n      !Object.prototype.hasOwnProperty.call(objB, key) ||\n      !Object.is(objA[key], objB[key])\n    ) {\n      return false;\n    }\n  }\n  return true;\n};\n","import type {\n    Subscriber,\n    Unsubscribe,\n    UndoAction,\n    UndoManager,\n    UndoManagerState,\n} 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    /** Callback after an undo action has been successfully applied */\n    onUndo?: (action: UndoAction) => void\n    /** Callback after a redo action has been successfully applied */\n    onRedo?: (action: UndoAction) => void\n    /** Callback when an undo or redo action fails */\n    onError?: (\n        error: Error,\n        action: UndoAction,\n        operation: 'undo' | 'redo'\n    ) => void\n}\n\n/** A merged group keeps its original actions so it can execute atomically. */\ninterface GroupedUndoAction extends UndoAction {\n    actions?: readonly UndoAction[]\n}\n\nconst getGroupedActions = (action: GroupedUndoAction): readonly UndoAction[] =>\n    action.actions ?? [action]\n\n/**\n * Apply a group in the required direction. If a member fails, compensate the\n * members already applied so a failed undo/redo does not leave the group\n * partially applied (provided the compensating actions themselves succeed).\n */\nconst applyGroup = async (\n    actions: readonly UndoAction[],\n    operation: 'undo' | 'redo'\n): Promise<void> => {\n    const ordered = operation === 'undo' ? [...actions].reverse() : actions\n    const applied: UndoAction[] = []\n\n    try {\n        for (const action of ordered) {\n            await action[operation]()\n            applied.push(action)\n        }\n    } catch (error) {\n        const compensate = operation === 'undo' ? 'redo' : 'undo'\n        for (const action of applied.reverse()) {\n            await action[compensate]()\n        }\n        throw error\n    }\n}\n\nconst mergeGroupedActions = (\n    older: GroupedUndoAction,\n    newer: UndoAction\n): GroupedUndoAction => {\n    const actions = [...getGroupedActions(older), newer]\n\n    return {\n        undo: () => applyGroup(actions, 'undo'),\n        redo: () => applyGroup(actions, 'redo'),\n        groupId: newer.groupId,\n        path: newer.path ?? older.path,\n        description: newer.description ?? older.description,\n        actions,\n    }\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, onUndo, onRedo, onError } = 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] as\n                | GroupedUndoAction\n                | undefined\n            if (last?.groupId === action.groupId) {\n                // Keep the individual group members so the group can apply\n                // newest→oldest on undo and oldest→newest on redo.\n                undoStack.pop()\n                undoStack.push(mergeGroupedActions(last, action))\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        try {\n            // Navigate if path is set.\n            if (action.path && onNavigate) {\n                onNavigate(action.path)\n            }\n            await action.undo()\n        } catch (error) {\n            // Put it back on undo stack if it failed\n            undoStack.push(action)\n            if (onError) {\n                onError(error as Error, action, 'undo')\n            } else {\n                console.error('Undo failed:', error)\n            }\n            throw error\n        }\n\n        redoStack.push(action)\n        notify()\n        onUndo?.(action)\n    }\n\n    const redo = async () => {\n        const action = redoStack.pop()\n        if (!action) return\n\n        try {\n            // Navigate if path is set.\n            if (action.path && onNavigate) {\n                onNavigate(action.path)\n            }\n            await action.redo()\n        } catch (error) {\n            // Put it back on redo stack if it failed\n            redoStack.push(action)\n            if (onError) {\n                onError(error as Error, action, 'redo')\n            } else {\n                console.error('Redo failed:', error)\n            }\n            throw error\n        }\n\n        undoStack.push(action)\n\n        // Enforce max length\n        if (undoStack.length > maxLength) {\n            undoStack.shift()\n        }\n\n        notify()\n        onRedo?.(action)\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 {\n    ErrorContext,\n    FirestateConfig,\n    Subscriber,\n    UndoAction,\n    Unsubscribe,\n} from '../types'\nimport { createUndoManager, type UndoManagerWithSubscribe } from '../utils/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: (\n        handler?: (error: Error, context: ErrorContext) => void\n    ) => 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    /**\n     * Resolve the router path to stamp onto a handle-pushed undo action.\n     * Delegates to the config's `getUndoPath`; returns `undefined` when none\n     * is configured. Called by handle writers as they push undo actions.\n     */\n    getUndoPath: () => string | undefined\n    /**\n     * Replace the undo-path resolver at runtime. Used by FirestateProvider to\n     * keep the store identity stable when consumers pass an inline\n     * `getUndoPath` callback that changes reference on every render.\n     */\n    setGetUndoPath: (handler?: () => string | undefined) => void\n    /** Replace the successful-undo handler without recreating the store. */\n    setOnUndo: (handler?: (action: UndoAction) => void) => void\n    /** Replace the successful-redo handler without recreating the store. */\n    setOnRedo: (handler?: (action: UndoAction) => 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    let getUndoPath = config.getUndoPath\n    let onUndo = config.onUndo\n    let onRedo = config.onRedo\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        // Like onNavigate, these wrappers keep the undo manager and store\n        // stable while FirestateProvider replaces inline callback identities.\n        onUndo: (action) => onUndo?.(action),\n        onRedo: (action) => onRedo?.(action),\n        // UndoManager owns action execution; delegate failures through the\n        // store's established onError channel rather than adding undo-specific\n        // error callbacks to FirestateConfig.\n        onError: (error, action, operation) => {\n            const context: ErrorContext = {\n                type: 'undo',\n                path: action.path ?? 'undo',\n                operation,\n            }\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\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        getUndoPath: () => getUndoPath?.(),\n\n        setGetUndoPath: (handler) => {\n            getUndoPath = handler\n        },\n\n        setOnUndo: (handler) => {\n            onUndo = handler\n        },\n\n        setOnRedo: (handler) => {\n            onRedo = 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 '../core/store'\nimport { FirestateContext } from './hooks'\nimport type { ErrorContext, UndoAction } 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    /**\n     * Called when a handle write (`update`/`add`/`remove`) pushes an undo\n     * action, to stamp the current router path onto it. That path is what\n     * `onNavigate` later receives, so handle-driven undo can return users to\n     * where a change occurred. Read the path from your router here.\n     *\n     * @example\n     * ```tsx\n     * import { useLocation } from 'react-router-dom'\n     *\n     * function App() {\n     *   const location = useLocation()\n     *   return (\n     *     <FirestateProvider getUndoPath={() => location.pathname}>\n     *       {children}\n     *     </FirestateProvider>\n     *   )\n     * }\n     * ```\n     */\n    getUndoPath?: () => string | undefined\n    /** Custom error handler */\n    onError?: (error: Error, context: ErrorContext) => void\n    /** Called after an undo action has been successfully applied */\n    onUndo?: (action: UndoAction) => void\n    /** Called after a redo action has been successfully applied */\n    onRedo?: (action: UndoAction) => 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    getUndoPath,\n    onUndo,\n    onRedo,\n    children,\n}) => {\n    // Callback props are intentionally excluded from the deps so inline\n    // callbacks (new reference per render) do not re-create the store and drop\n    // every existing subscription. The store replaces the latest handlers below.\n    const store = useMemo(\n        () =>\n            createStore({\n                firestore,\n                autosave,\n                minLoadTime,\n                maxUndoLength,\n                onError,\n                onNavigate,\n                getUndoPath,\n                onUndo,\n                onRedo,\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    useEffect(() => {\n        store.setGetUndoPath(getUndoPath)\n    }, [store, getUndoPath])\n\n    useEffect(() => {\n        store.setOnUndo(onUndo)\n    }, [store, onUndo])\n\n    useEffect(() => {\n        store.setOnRedo(onRedo)\n    }, [store, onRedo])\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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"}