@neetru/sdk 3.1.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/auth.cjs +159 -15
  2. package/dist/auth.cjs.map +1 -1
  3. package/dist/auth.d.cts +1 -1
  4. package/dist/auth.d.ts +1 -1
  5. package/dist/auth.mjs +159 -15
  6. package/dist/auth.mjs.map +1 -1
  7. package/dist/catalog.cjs +50 -2
  8. package/dist/catalog.cjs.map +1 -1
  9. package/dist/catalog.d.cts +1 -1
  10. package/dist/catalog.d.ts +1 -1
  11. package/dist/catalog.mjs +50 -2
  12. package/dist/catalog.mjs.map +1 -1
  13. package/dist/checkout.cjs +52 -3
  14. package/dist/checkout.cjs.map +1 -1
  15. package/dist/checkout.d.cts +1 -1
  16. package/dist/checkout.d.ts +1 -1
  17. package/dist/checkout.mjs +52 -3
  18. package/dist/checkout.mjs.map +1 -1
  19. package/dist/db-react.cjs +3 -1
  20. package/dist/db-react.cjs.map +1 -1
  21. package/dist/db-react.d.cts +1 -1
  22. package/dist/db-react.d.ts +1 -1
  23. package/dist/db-react.mjs +3 -1
  24. package/dist/db-react.mjs.map +1 -1
  25. package/dist/db.cjs +93 -6
  26. package/dist/db.cjs.map +1 -1
  27. package/dist/db.d.cts +1 -1
  28. package/dist/db.d.ts +1 -1
  29. package/dist/db.mjs +93 -6
  30. package/dist/db.mjs.map +1 -1
  31. package/dist/entitlements.cjs +50 -2
  32. package/dist/entitlements.cjs.map +1 -1
  33. package/dist/entitlements.d.cts +1 -1
  34. package/dist/entitlements.d.ts +1 -1
  35. package/dist/entitlements.mjs +50 -2
  36. package/dist/entitlements.mjs.map +1 -1
  37. package/dist/errors.cjs.map +1 -1
  38. package/dist/errors.d.cts +3 -1
  39. package/dist/errors.d.ts +3 -1
  40. package/dist/errors.mjs.map +1 -1
  41. package/dist/firestore-compat.cjs +14 -2
  42. package/dist/firestore-compat.cjs.map +1 -1
  43. package/dist/firestore-compat.d.cts +28 -3
  44. package/dist/firestore-compat.d.ts +28 -3
  45. package/dist/firestore-compat.mjs +14 -2
  46. package/dist/firestore-compat.mjs.map +1 -1
  47. package/dist/index.cjs +195 -16
  48. package/dist/index.cjs.map +1 -1
  49. package/dist/index.d.cts +9 -3
  50. package/dist/index.d.ts +9 -3
  51. package/dist/index.mjs +194 -17
  52. package/dist/index.mjs.map +1 -1
  53. package/dist/mocks.cjs.map +1 -1
  54. package/dist/mocks.d.cts +1 -1
  55. package/dist/mocks.d.ts +1 -1
  56. package/dist/mocks.mjs.map +1 -1
  57. package/dist/notifications.cjs +50 -2
  58. package/dist/notifications.cjs.map +1 -1
  59. package/dist/notifications.d.cts +1 -1
  60. package/dist/notifications.d.ts +1 -1
  61. package/dist/notifications.mjs +50 -2
  62. package/dist/notifications.mjs.map +1 -1
  63. package/dist/react.cjs +2 -3
  64. package/dist/react.cjs.map +1 -1
  65. package/dist/react.d.cts +22 -4
  66. package/dist/react.d.ts +22 -4
  67. package/dist/react.mjs +2 -3
  68. package/dist/react.mjs.map +1 -1
  69. package/dist/support.cjs +50 -2
  70. package/dist/support.cjs.map +1 -1
  71. package/dist/support.d.cts +1 -1
  72. package/dist/support.d.ts +1 -1
  73. package/dist/support.mjs +50 -2
  74. package/dist/support.mjs.map +1 -1
  75. package/dist/telemetry.cjs +65 -4
  76. package/dist/telemetry.cjs.map +1 -1
  77. package/dist/telemetry.d.cts +1 -1
  78. package/dist/telemetry.d.ts +1 -1
  79. package/dist/telemetry.mjs +65 -4
  80. package/dist/telemetry.mjs.map +1 -1
  81. package/dist/{types-BRv8wBxX.d.ts → types-B3XFHD4A.d.ts} +60 -1
  82. package/dist/{types-nwErcRX8.d.cts → types-DePdSU3P.d.cts} +60 -1
  83. package/dist/usage.cjs +50 -2
  84. package/dist/usage.cjs.map +1 -1
  85. package/dist/usage.d.cts +1 -1
  86. package/dist/usage.d.ts +1 -1
  87. package/dist/usage.mjs +50 -2
  88. package/dist/usage.mjs.map +1 -1
  89. package/dist/webhooks.cjs +83 -2
  90. package/dist/webhooks.cjs.map +1 -1
  91. package/dist/webhooks.d.cts +1 -1
  92. package/dist/webhooks.d.ts +1 -1
  93. package/dist/webhooks.mjs +83 -3
  94. package/dist/webhooks.mjs.map +1 -1
  95. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/db/react.ts"],"names":["React"],"mappings":"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","file":"db-react.cjs","sourcesContent":["/**\r\n * Bindings React do módulo db (M2) — `@neetru/sdk/react`.\r\n *\r\n * Exporta três hooks de tempo real que se integram com a camada offline do\r\n * `@neetru/sdk/db`:\r\n *\r\n * - `useDocument(ref, id)` — subscreve ao onSnapshot de um documento.\r\n * - `useCollection(ref, query?)` — subscreve ao onSnapshot de uma coleção.\r\n * - `useSyncState(ns)` — observa o SyncState do namespace offline.\r\n *\r\n * ### Design\r\n * Segue o padrão do subpath `/react` existente (cliente/ref passados\r\n * explicitamente, sem Context Provider obrigatório). Alinhado com 02-sdk.md §3.6.\r\n *\r\n * ### React 18/19 StrictMode\r\n * Cada hook usa um ref interno para rastrear se o componente foi desmontado,\r\n * e a função `useEffect` retorna sempre a função de unsubscribe. Em StrictMode,\r\n * o duplo-mount/unmount do React 18 chama cleanup corretamente — não há leak.\r\n *\r\n * ### Dependências\r\n * `react` é peer dependency — não importada como dep direta.\r\n */\r\nimport * as React from 'react';\r\nimport type { DbCollectionRef, DbDoc, DbQuery, DbListResult, DbGetResult } from './collection-ref';\r\nimport type { SyncState, Unsubscribe } from './offline/types';\r\nimport type { NeetruClient } from '../types';\r\n\r\n// ─── Tipos públicos dos hooks ─────────────────────────────────────────────────\r\n\r\n/**\r\n * Resultado de `useDocument`.\r\n *\r\n * - `data` — dados do documento, ou `null` se não existe / ainda carregando.\r\n * - `loading` — `true` enquanto o primeiro snapshot não foi entregue.\r\n * - `fromCache` — o snapshot veio do cache local, não do servidor.\r\n * - `stale` — conexão caída ou sincronizando; pode haver dados mais novos.\r\n */\r\nexport interface UseDocumentResult<T> {\r\n data: T | null;\r\n loading: boolean;\r\n fromCache: boolean;\r\n stale: boolean;\r\n}\r\n\r\n/**\r\n * Resultado de `useCollection`.\r\n *\r\n * - `docs` — documentos da coleção (ou subconjunto via query).\r\n * - `loading` — `true` enquanto o primeiro snapshot não foi entregue.\r\n * - `fromCache` — o snapshot veio do cache local.\r\n * - `stale` — conexão caída ou sincronizando.\r\n * - `hasPendingWrites` — ao menos um doc tem escrita local não confirmada.\r\n */\r\nexport interface UseCollectionResult<T> {\r\n docs: DbDoc<T>[];\r\n loading: boolean;\r\n fromCache: boolean;\r\n stale: boolean;\r\n hasPendingWrites: boolean;\r\n}\r\n\r\n/**\r\n * Subconjunto mínimo do namespace offline que `useSyncState` precisa.\r\n * Compatível com `NeetruDbDocuments` de `collection-ref.ts`.\r\n *\r\n * Passado explicitamente (sem Context) para alinhar com o padrão do SDK.\r\n * Tipicamente: o namespace retornado por `createOfflineDocumentsNamespace`.\r\n *\r\n * `client.db` (o `NeetruDb` retornado por `createNeetruClient`) também satisfaz\r\n * este contrato — expõe `syncState` + `onSyncStateChanged` reais. Por isso\r\n * `useSyncState` aceita tanto a fonte direta quanto o `NeetruClient` inteiro\r\n * (ver `UseSyncStateInput`).\r\n */\r\nexport interface SyncStateSource {\r\n readonly syncState: SyncState;\r\n onSyncStateChanged(cb: (s: SyncState) => void): Unsubscribe;\r\n}\r\n\r\n/**\r\n * Entrada aceita por `useSyncState`:\r\n * - `NeetruClient` → resolve `client.db` automaticamente (forma oficial,\r\n * alinhada com 02-sdk.md §3.6 `useSyncState(client)`).\r\n * - `SyncStateSource` → fonte direta (`client.db` ou o namespace offline cru).\r\n */\r\nexport type UseSyncStateInput = NeetruClient | SyncStateSource;\r\n\r\n/**\r\n * Type guard — detecta se a entrada é um `NeetruClient` (tem `.db` que é, ele\r\n * próprio, um `SyncStateSource`) e não uma fonte direta. Distinção em runtime\r\n * porque os shapes não se sobrepõem: a fonte direta expõe `syncState` no nível\r\n * de topo; o client expõe via `.db`.\r\n */\r\nfunction isNeetruClient(input: UseSyncStateInput): input is NeetruClient {\r\n const maybe = input as { db?: unknown; syncState?: unknown };\r\n // Fonte direta já tem `syncState` no topo → não é o client wrapper.\r\n if (typeof maybe.syncState !== 'undefined') return false;\r\n const db = maybe.db as { syncState?: unknown; onSyncStateChanged?: unknown } | undefined;\r\n return (\r\n !!db &&\r\n typeof db.syncState !== 'undefined' &&\r\n typeof db.onSyncStateChanged === 'function'\r\n );\r\n}\r\n\r\n/** Normaliza qualquer entrada aceita para a `SyncStateSource` subjacente. */\r\nfunction resolveSyncStateSource(input: UseSyncStateInput): SyncStateSource {\r\n return isNeetruClient(input) ? (input.db as unknown as SyncStateSource) : input;\r\n}\r\n\r\n// ─── Serialização estável de query ────────────────────────────────────────────\r\n\r\n/**\r\n * Serializa uma DbQuery para string estável, usada como chave de efeito.\r\n *\r\n * `useCollection` re-subscreve somente quando a serialização muda, NÃO por\r\n * identidade de objeto. Isso evita a armadilha clássica de inline-object em\r\n * JSX que causaria re-subscribe em todo render.\r\n */\r\nfunction serializeQuery(query: DbQuery | undefined): string {\r\n if (!query) return '';\r\n try {\r\n return JSON.stringify(query, Object.keys(query).sort());\r\n } catch {\r\n return String(query);\r\n }\r\n}\r\n\r\n// ─── useDocument ─────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Hook de tempo real para um documento específico.\r\n *\r\n * Subscreve a `ref.doc(id).onSnapshot` no mount. Re-subscreve quando `ref`\r\n * ou `id` mudam. Cancela a subscrição no unmount.\r\n *\r\n * ```tsx\r\n * const { data, loading, fromCache, stale } = useDocument(ordersRef, orderId);\r\n * if (loading) return <Spinner />;\r\n * if (!data) return <NotFound />;\r\n * return <OrderCard order={data} fromCache={fromCache} />;\r\n * ```\r\n *\r\n * Seguro em React 18/19 StrictMode: a cleanup function do useEffect cancela\r\n * o listener no unmount, e o double-mount do StrictMode não vaza estado.\r\n */\r\nexport function useDocument<T>(\r\n ref: DbCollectionRef<T>,\r\n id: string,\r\n): UseDocumentResult<T> {\r\n const [state, setState] = React.useState<UseDocumentResult<T>>({\r\n data: null,\r\n loading: true,\r\n fromCache: true,\r\n stale: false,\r\n });\r\n\r\n // Ref para detectar unmount e evitar setState após cleanup.\r\n const mountedRef = React.useRef(true);\r\n\r\n React.useEffect(() => {\r\n mountedRef.current = true;\r\n // Reset para loading quando ref/id muda\r\n setState({ data: null, loading: true, fromCache: true, stale: false });\r\n\r\n const docRef = ref.doc(id);\r\n\r\n const unsubscribe: Unsubscribe = docRef.onSnapshot(\r\n (snap: DbGetResult<T> | null) => {\r\n if (!mountedRef.current) return;\r\n if (snap === null || snap.docs.length === 0) {\r\n setState({\r\n data: null,\r\n loading: false,\r\n fromCache: snap?.fromCache ?? true,\r\n stale: snap?.stale ?? false,\r\n });\r\n } else {\r\n setState({\r\n data: snap.docs[0]?.data ?? null,\r\n loading: false,\r\n fromCache: snap.fromCache,\r\n stale: snap.stale,\r\n });\r\n }\r\n },\r\n );\r\n\r\n return () => {\r\n mountedRef.current = false;\r\n unsubscribe();\r\n };\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [ref, id]);\r\n\r\n return state;\r\n}\r\n\r\n// ─── useCollection ────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Hook de tempo real para uma coleção (ou subconjunto filtrado).\r\n *\r\n * Subscreve a `ref.onSnapshot(query, cb)` no mount. Re-subscreve quando `ref`\r\n * ou a serialização de `query` muda — NÃO por identidade de objeto, o que\r\n * evita re-subscribe desnecessário em inline objects.\r\n *\r\n * ```tsx\r\n * const { docs, loading, hasPendingWrites } = useCollection(ordersRef, {\r\n * where: [{ field: 'status', op: '==', value: 'open' }],\r\n * });\r\n * ```\r\n *\r\n * Seguro em React 18/19 StrictMode.\r\n */\r\nexport function useCollection<T>(\r\n ref: DbCollectionRef<T>,\r\n query?: DbQuery,\r\n): UseCollectionResult<T> {\r\n const [state, setState] = React.useState<UseCollectionResult<T>>({\r\n docs: [],\r\n loading: true,\r\n fromCache: true,\r\n stale: false,\r\n hasPendingWrites: false,\r\n });\r\n\r\n const mountedRef = React.useRef(true);\r\n\r\n // Serializamos a query para garantir que mudanças de valor (não de referência)\r\n // triggam o re-subscribe, mas que objetos iguais não causem loop.\r\n const queryKey = serializeQuery(query);\r\n\r\n React.useEffect(() => {\r\n mountedRef.current = true;\r\n // Reset loading ao trocar de query/ref\r\n setState({\r\n docs: [],\r\n loading: true,\r\n fromCache: true,\r\n stale: false,\r\n hasPendingWrites: false,\r\n });\r\n\r\n const unsubscribe: Unsubscribe = ref.onSnapshot(\r\n query,\r\n (snap: DbListResult<T>) => {\r\n if (!mountedRef.current) return;\r\n setState({\r\n docs: snap.docs,\r\n loading: false,\r\n fromCache: snap.fromCache,\r\n stale: snap.stale,\r\n hasPendingWrites: snap.hasPendingWrites,\r\n });\r\n },\r\n );\r\n\r\n return () => {\r\n mountedRef.current = false;\r\n unsubscribe();\r\n };\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [ref, queryKey]);\r\n\r\n return state;\r\n}\r\n\r\n// ─── useSyncState ─────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Hook para observar o `SyncState` da camada offline em tempo real.\r\n *\r\n * Retorna o `SyncState` atual e re-renderiza quando ele muda. Usado para\r\n * renderizar indicadores \"sincronizando…\" / \"offline\" / contadores de escritas\r\n * pendentes na UI.\r\n *\r\n * Aceita duas formas de entrada (SDK-QW-4 — wiring ao client real):\r\n *\r\n * ```tsx\r\n * // Forma oficial — passe o client; o hook resolve client.db internamente.\r\n * const client = createNeetruClient({ apiKey });\r\n * const state = useSyncState(client);\r\n *\r\n * // Forma direta — passe a fonte (client.db ou o namespace offline cru).\r\n * const state = useSyncState(client.db);\r\n *\r\n * if (state.status === 'offline') return <OfflineBanner pending={state.pendingWrites} />;\r\n * ```\r\n *\r\n * Antes desta versão o hook só aceitava a `SyncStateSource` crua, o que levava\r\n * consumers a stub-arem o estado pra sempre `'idle'` em vez de ligar no client.\r\n * Agora `useSyncState(client)` (a forma documentada em 02-sdk.md §3.6) funciona\r\n * de ponta a ponta — `client.db.syncState` / `client.db.onSyncStateChanged` são\r\n * reais.\r\n *\r\n * Seguro em React 18/19 StrictMode.\r\n */\r\nexport function useSyncState(input: UseSyncStateInput): SyncState {\r\n // Resolve uma única vez por render; se a identidade da fonte resolvida mudar,\r\n // o efeito abaixo re-subscreve (dependência `source`).\r\n const source = resolveSyncStateSource(input);\r\n\r\n const [state, setState] = React.useState<SyncState>(() => source.syncState);\r\n\r\n const mountedRef = React.useRef(true);\r\n\r\n React.useEffect(() => {\r\n mountedRef.current = true;\r\n\r\n // Snapshot inicial imediato ao montar (pode ter mudado entre render e effect)\r\n if (mountedRef.current) {\r\n setState(source.syncState);\r\n }\r\n\r\n const unsubscribe: Unsubscribe = source.onSyncStateChanged((s: SyncState) => {\r\n if (!mountedRef.current) return;\r\n setState(s);\r\n });\r\n\r\n return () => {\r\n mountedRef.current = false;\r\n unsubscribe();\r\n };\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [source]);\r\n\r\n return state;\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/db/react.ts"],"names":["React"],"mappings":"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","file":"db-react.cjs","sourcesContent":["/**\r\n * Bindings React do módulo db (M2) — `@neetru/sdk/react`.\r\n *\r\n * Exporta três hooks de tempo real que se integram com a camada offline do\r\n * `@neetru/sdk/db`:\r\n *\r\n * - `useDocument(ref, id)` — subscreve ao onSnapshot de um documento.\r\n * - `useCollection(ref, query?)` — subscreve ao onSnapshot de uma coleção.\r\n * - `useSyncState(ns)` — observa o SyncState do namespace offline.\r\n *\r\n * ### Design\r\n * Segue o padrão do subpath `/react` existente (cliente/ref passados\r\n * explicitamente, sem Context Provider obrigatório). Alinhado com 02-sdk.md §3.6.\r\n *\r\n * ### React 18/19 StrictMode\r\n * Cada hook usa um ref interno para rastrear se o componente foi desmontado,\r\n * e a função `useEffect` retorna sempre a função de unsubscribe. Em StrictMode,\r\n * o duplo-mount/unmount do React 18 chama cleanup corretamente — não há leak.\r\n *\r\n * ### Dependências\r\n * `react` é peer dependency — não importada como dep direta.\r\n */\r\nimport * as React from 'react';\r\nimport type { DbCollectionRef, DbDoc, DbQuery, DbListResult, DbGetResult } from './collection-ref';\r\nimport type { SyncState, Unsubscribe } from './offline/types';\r\nimport type { NeetruClient } from '../types';\r\n\r\n// ─── Tipos públicos dos hooks ─────────────────────────────────────────────────\r\n\r\n/**\r\n * Resultado de `useDocument`.\r\n *\r\n * - `data` — dados do documento, ou `null` se não existe / ainda carregando.\r\n * - `loading` — `true` enquanto o primeiro snapshot não foi entregue.\r\n * - `fromCache` — o snapshot veio do cache local, não do servidor.\r\n * - `stale` — conexão caída ou sincronizando; pode haver dados mais novos.\r\n */\r\nexport interface UseDocumentResult<T> {\r\n data: T | null;\r\n loading: boolean;\r\n fromCache: boolean;\r\n stale: boolean;\r\n}\r\n\r\n/**\r\n * Resultado de `useCollection`.\r\n *\r\n * - `docs` — documentos da coleção (ou subconjunto via query).\r\n * - `loading` — `true` enquanto o primeiro snapshot não foi entregue.\r\n * - `fromCache` — o snapshot veio do cache local.\r\n * - `stale` — conexão caída ou sincronizando.\r\n * - `hasPendingWrites` — ao menos um doc tem escrita local não confirmada.\r\n */\r\nexport interface UseCollectionResult<T> {\r\n docs: DbDoc<T>[];\r\n loading: boolean;\r\n fromCache: boolean;\r\n stale: boolean;\r\n hasPendingWrites: boolean;\r\n}\r\n\r\n/**\r\n * Subconjunto mínimo do namespace offline que `useSyncState` precisa.\r\n * Compatível com `NeetruDbDocuments` de `collection-ref.ts`.\r\n *\r\n * Passado explicitamente (sem Context) para alinhar com o padrão do SDK.\r\n * Tipicamente: o namespace retornado por `createOfflineDocumentsNamespace`.\r\n *\r\n * `client.db` (o `NeetruDb` retornado por `createNeetruClient`) também satisfaz\r\n * este contrato — expõe `syncState` + `onSyncStateChanged` reais. Por isso\r\n * `useSyncState` aceita tanto a fonte direta quanto o `NeetruClient` inteiro\r\n * (ver `UseSyncStateInput`).\r\n */\r\nexport interface SyncStateSource {\r\n readonly syncState: SyncState;\r\n onSyncStateChanged(cb: (s: SyncState) => void): Unsubscribe;\r\n}\r\n\r\n/**\r\n * Entrada aceita por `useSyncState`:\r\n * - `NeetruClient` → resolve `client.db` automaticamente (forma oficial,\r\n * alinhada com 02-sdk.md §3.6 `useSyncState(client)`).\r\n * - `SyncStateSource` → fonte direta (`client.db` ou o namespace offline cru).\r\n */\r\nexport type UseSyncStateInput = NeetruClient | SyncStateSource;\r\n\r\n/**\r\n * Type guard — detecta se a entrada é um `NeetruClient` (tem `.db` que é, ele\r\n * próprio, um `SyncStateSource`) e não uma fonte direta. Distinção em runtime\r\n * porque os shapes não se sobrepõem: a fonte direta expõe `syncState` no nível\r\n * de topo; o client expõe via `.db`.\r\n */\r\nfunction isNeetruClient(input: UseSyncStateInput): input is NeetruClient {\r\n const maybe = input as { db?: unknown; syncState?: unknown };\r\n // Fonte direta já tem `syncState` no topo → não é o client wrapper.\r\n if (typeof maybe.syncState !== 'undefined') return false;\r\n const db = maybe.db as { syncState?: unknown; onSyncStateChanged?: unknown } | undefined;\r\n return (\r\n !!db &&\r\n typeof db.syncState !== 'undefined' &&\r\n typeof db.onSyncStateChanged === 'function'\r\n );\r\n}\r\n\r\n/** Normaliza qualquer entrada aceita para a `SyncStateSource` subjacente. */\r\nfunction resolveSyncStateSource(input: UseSyncStateInput): SyncStateSource {\r\n return isNeetruClient(input) ? (input.db as unknown as SyncStateSource) : input;\r\n}\r\n\r\n// ─── Serialização estável de query ────────────────────────────────────────────\r\n\r\n/**\r\n * Serializa uma DbQuery para string estável, usada como chave de efeito.\r\n *\r\n * `useCollection` re-subscreve somente quando a serialização muda, NÃO por\r\n * identidade de objeto. Isso evita a armadilha clássica de inline-object em\r\n * JSX que causaria re-subscribe em todo render.\r\n */\r\nfunction serializeQuery(query: DbQuery | undefined): string {\r\n if (!query) return '';\r\n try {\r\n return JSON.stringify(query, Object.keys(query).sort());\r\n } catch {\r\n return String(query);\r\n }\r\n}\r\n\r\n// ─── useDocument ─────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Hook de tempo real para um documento específico.\r\n *\r\n * Subscreve a `ref.doc(id).onSnapshot` no mount. Re-subscreve quando `ref`\r\n * ou `id` mudam. Cancela a subscrição no unmount.\r\n *\r\n * ```tsx\r\n * const { data, loading, fromCache, stale } = useDocument(ordersRef, orderId);\r\n * if (loading) return <Spinner />;\r\n * if (!data) return <NotFound />;\r\n * return <OrderCard order={data} fromCache={fromCache} />;\r\n * ```\r\n *\r\n * Seguro em React 18/19 StrictMode: a cleanup function do useEffect cancela\r\n * o listener no unmount, e o double-mount do StrictMode não vaza estado.\r\n */\r\nexport function useDocument<T>(\r\n ref: DbCollectionRef<T>,\r\n id: string,\r\n): UseDocumentResult<T> {\r\n const [state, setState] = React.useState<UseDocumentResult<T>>({\r\n data: null,\r\n loading: true,\r\n fromCache: true,\r\n stale: false,\r\n });\r\n\r\n // Ref para detectar unmount e evitar setState após cleanup.\r\n const mountedRef = React.useRef(true);\r\n\r\n React.useEffect(() => {\r\n mountedRef.current = true;\r\n // Reset para loading quando ref/id muda\r\n setState({ data: null, loading: true, fromCache: true, stale: false });\r\n\r\n const docRef = ref.doc(id);\r\n\r\n const unsubscribe: Unsubscribe = docRef.onSnapshot(\r\n (snap: DbGetResult<T> | null) => {\r\n if (!mountedRef.current) return;\r\n if (snap === null || snap.docs.length === 0) {\r\n setState({\r\n data: null,\r\n loading: false,\r\n fromCache: snap?.fromCache ?? true,\r\n stale: snap?.stale ?? false,\r\n });\r\n } else {\r\n setState({\r\n data: snap.docs[0]?.data ?? null,\r\n loading: false,\r\n fromCache: snap.fromCache,\r\n stale: snap.stale,\r\n });\r\n }\r\n },\r\n );\r\n\r\n return () => {\r\n mountedRef.current = false;\r\n unsubscribe();\r\n };\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [ref, id]);\r\n\r\n return state;\r\n}\r\n\r\n// ─── useCollection ────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Hook de tempo real para uma coleção (ou subconjunto filtrado).\r\n *\r\n * Subscreve a `ref.onSnapshot(query, cb)` no mount. Re-subscreve quando `ref`\r\n * ou a serialização de `query` muda — NÃO por identidade de objeto, o que\r\n * evita re-subscribe desnecessário em inline objects.\r\n *\r\n * ```tsx\r\n * const { docs, loading, hasPendingWrites } = useCollection(ordersRef, {\r\n * where: [{ field: 'status', op: '==', value: 'open' }],\r\n * });\r\n * ```\r\n *\r\n * Seguro em React 18/19 StrictMode.\r\n */\r\nexport function useCollection<T>(\r\n ref: DbCollectionRef<T>,\r\n query?: DbQuery,\r\n): UseCollectionResult<T> {\r\n const [state, setState] = React.useState<UseCollectionResult<T>>({\r\n docs: [],\r\n loading: true,\r\n fromCache: true,\r\n stale: false,\r\n hasPendingWrites: false,\r\n });\r\n\r\n const mountedRef = React.useRef(true);\r\n\r\n // Serializamos a query para garantir que mudanças de valor (não de referência)\r\n // triggam o re-subscribe, mas que objetos iguais não causem loop.\r\n const queryKey = serializeQuery(query);\r\n\r\n // Bug corrigido: o efeito usava `query` da closure externa, que poderia estar\r\n // desatualizado quando ref mudava sem o queryKey mudar. O ref mantém sempre o\r\n // valor mais recente sem re-disparar o efeito.\r\n const queryRef = React.useRef(query);\r\n queryRef.current = query;\r\n\r\n React.useEffect(() => {\r\n mountedRef.current = true;\r\n // Reset loading ao trocar de query/ref\r\n setState({\r\n docs: [],\r\n loading: true,\r\n fromCache: true,\r\n stale: false,\r\n hasPendingWrites: false,\r\n });\r\n\r\n const unsubscribe: Unsubscribe = ref.onSnapshot(\r\n queryRef.current,\r\n (snap: DbListResult<T>) => {\r\n if (!mountedRef.current) return;\r\n setState({\r\n docs: snap.docs,\r\n loading: false,\r\n fromCache: snap.fromCache,\r\n stale: snap.stale,\r\n hasPendingWrites: snap.hasPendingWrites,\r\n });\r\n },\r\n );\r\n\r\n return () => {\r\n mountedRef.current = false;\r\n unsubscribe();\r\n };\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [ref, queryKey]);\r\n\r\n return state;\r\n}\r\n\r\n// ─── useSyncState ─────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Hook para observar o `SyncState` da camada offline em tempo real.\r\n *\r\n * Retorna o `SyncState` atual e re-renderiza quando ele muda. Usado para\r\n * renderizar indicadores \"sincronizando…\" / \"offline\" / contadores de escritas\r\n * pendentes na UI.\r\n *\r\n * Aceita duas formas de entrada (SDK-QW-4 — wiring ao client real):\r\n *\r\n * ```tsx\r\n * // Forma oficial — passe o client; o hook resolve client.db internamente.\r\n * const client = createNeetruClient({ apiKey });\r\n * const state = useSyncState(client);\r\n *\r\n * // Forma direta — passe a fonte (client.db ou o namespace offline cru).\r\n * const state = useSyncState(client.db);\r\n *\r\n * if (state.status === 'offline') return <OfflineBanner pending={state.pendingWrites} />;\r\n * ```\r\n *\r\n * Antes desta versão o hook só aceitava a `SyncStateSource` crua, o que levava\r\n * consumers a stub-arem o estado pra sempre `'idle'` em vez de ligar no client.\r\n * Agora `useSyncState(client)` (a forma documentada em 02-sdk.md §3.6) funciona\r\n * de ponta a ponta — `client.db.syncState` / `client.db.onSyncStateChanged` são\r\n * reais.\r\n *\r\n * Seguro em React 18/19 StrictMode.\r\n */\r\nexport function useSyncState(input: UseSyncStateInput): SyncState {\r\n // Resolve uma única vez por render; se a identidade da fonte resolvida mudar,\r\n // o efeito abaixo re-subscreve (dependência `source`).\r\n const source = resolveSyncStateSource(input);\r\n\r\n const [state, setState] = React.useState<SyncState>(() => source.syncState);\r\n\r\n const mountedRef = React.useRef(true);\r\n\r\n React.useEffect(() => {\r\n mountedRef.current = true;\r\n\r\n // Snapshot inicial imediato ao montar (pode ter mudado entre render e effect)\r\n if (mountedRef.current) {\r\n setState(source.syncState);\r\n }\r\n\r\n const unsubscribe: Unsubscribe = source.onSyncStateChanged((s: SyncState) => {\r\n if (!mountedRef.current) return;\r\n setState(s);\r\n });\r\n\r\n return () => {\r\n mountedRef.current = false;\r\n unsubscribe();\r\n };\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [source]);\r\n\r\n return state;\r\n}\r\n"]}
@@ -1,4 +1,4 @@
1
- import { aa as SyncState, af as Unsubscribe, o as DbDoc, z as NeetruClient, n as DbCollectionRef, t as DbQuery } from './types-nwErcRX8.cjs';
1
+ import { aa as SyncState, af as Unsubscribe, o as DbDoc, z as NeetruClient, n as DbCollectionRef, t as DbQuery } from './types-DePdSU3P.cjs';
2
2
  import 'drizzle-orm/node-postgres';
3
3
  import './errors.cjs';
4
4
 
@@ -1,4 +1,4 @@
1
- import { aa as SyncState, af as Unsubscribe, o as DbDoc, z as NeetruClient, n as DbCollectionRef, t as DbQuery } from './types-BRv8wBxX.js';
1
+ import { aa as SyncState, af as Unsubscribe, o as DbDoc, z as NeetruClient, n as DbCollectionRef, t as DbQuery } from './types-B3XFHD4A.js';
2
2
  import 'drizzle-orm/node-postgres';
3
3
  import './errors.js';
4
4
 
package/dist/db-react.mjs CHANGED
@@ -67,6 +67,8 @@ function useCollection(ref, query) {
67
67
  });
68
68
  const mountedRef = React.useRef(true);
69
69
  const queryKey = serializeQuery(query);
70
+ const queryRef = React.useRef(query);
71
+ queryRef.current = query;
70
72
  React.useEffect(() => {
71
73
  mountedRef.current = true;
72
74
  setState({
@@ -77,7 +79,7 @@ function useCollection(ref, query) {
77
79
  hasPendingWrites: false
78
80
  });
79
81
  const unsubscribe = ref.onSnapshot(
80
- query,
82
+ queryRef.current,
81
83
  (snap) => {
82
84
  if (!mountedRef.current) return;
83
85
  setState({
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/db/react.ts"],"names":[],"mappings":"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","file":"db-react.mjs","sourcesContent":["/**\r\n * Bindings React do módulo db (M2) — `@neetru/sdk/react`.\r\n *\r\n * Exporta três hooks de tempo real que se integram com a camada offline do\r\n * `@neetru/sdk/db`:\r\n *\r\n * - `useDocument(ref, id)` — subscreve ao onSnapshot de um documento.\r\n * - `useCollection(ref, query?)` — subscreve ao onSnapshot de uma coleção.\r\n * - `useSyncState(ns)` — observa o SyncState do namespace offline.\r\n *\r\n * ### Design\r\n * Segue o padrão do subpath `/react` existente (cliente/ref passados\r\n * explicitamente, sem Context Provider obrigatório). Alinhado com 02-sdk.md §3.6.\r\n *\r\n * ### React 18/19 StrictMode\r\n * Cada hook usa um ref interno para rastrear se o componente foi desmontado,\r\n * e a função `useEffect` retorna sempre a função de unsubscribe. Em StrictMode,\r\n * o duplo-mount/unmount do React 18 chama cleanup corretamente — não há leak.\r\n *\r\n * ### Dependências\r\n * `react` é peer dependency — não importada como dep direta.\r\n */\r\nimport * as React from 'react';\r\nimport type { DbCollectionRef, DbDoc, DbQuery, DbListResult, DbGetResult } from './collection-ref';\r\nimport type { SyncState, Unsubscribe } from './offline/types';\r\nimport type { NeetruClient } from '../types';\r\n\r\n// ─── Tipos públicos dos hooks ─────────────────────────────────────────────────\r\n\r\n/**\r\n * Resultado de `useDocument`.\r\n *\r\n * - `data` — dados do documento, ou `null` se não existe / ainda carregando.\r\n * - `loading` — `true` enquanto o primeiro snapshot não foi entregue.\r\n * - `fromCache` — o snapshot veio do cache local, não do servidor.\r\n * - `stale` — conexão caída ou sincronizando; pode haver dados mais novos.\r\n */\r\nexport interface UseDocumentResult<T> {\r\n data: T | null;\r\n loading: boolean;\r\n fromCache: boolean;\r\n stale: boolean;\r\n}\r\n\r\n/**\r\n * Resultado de `useCollection`.\r\n *\r\n * - `docs` — documentos da coleção (ou subconjunto via query).\r\n * - `loading` — `true` enquanto o primeiro snapshot não foi entregue.\r\n * - `fromCache` — o snapshot veio do cache local.\r\n * - `stale` — conexão caída ou sincronizando.\r\n * - `hasPendingWrites` — ao menos um doc tem escrita local não confirmada.\r\n */\r\nexport interface UseCollectionResult<T> {\r\n docs: DbDoc<T>[];\r\n loading: boolean;\r\n fromCache: boolean;\r\n stale: boolean;\r\n hasPendingWrites: boolean;\r\n}\r\n\r\n/**\r\n * Subconjunto mínimo do namespace offline que `useSyncState` precisa.\r\n * Compatível com `NeetruDbDocuments` de `collection-ref.ts`.\r\n *\r\n * Passado explicitamente (sem Context) para alinhar com o padrão do SDK.\r\n * Tipicamente: o namespace retornado por `createOfflineDocumentsNamespace`.\r\n *\r\n * `client.db` (o `NeetruDb` retornado por `createNeetruClient`) também satisfaz\r\n * este contrato — expõe `syncState` + `onSyncStateChanged` reais. Por isso\r\n * `useSyncState` aceita tanto a fonte direta quanto o `NeetruClient` inteiro\r\n * (ver `UseSyncStateInput`).\r\n */\r\nexport interface SyncStateSource {\r\n readonly syncState: SyncState;\r\n onSyncStateChanged(cb: (s: SyncState) => void): Unsubscribe;\r\n}\r\n\r\n/**\r\n * Entrada aceita por `useSyncState`:\r\n * - `NeetruClient` → resolve `client.db` automaticamente (forma oficial,\r\n * alinhada com 02-sdk.md §3.6 `useSyncState(client)`).\r\n * - `SyncStateSource` → fonte direta (`client.db` ou o namespace offline cru).\r\n */\r\nexport type UseSyncStateInput = NeetruClient | SyncStateSource;\r\n\r\n/**\r\n * Type guard — detecta se a entrada é um `NeetruClient` (tem `.db` que é, ele\r\n * próprio, um `SyncStateSource`) e não uma fonte direta. Distinção em runtime\r\n * porque os shapes não se sobrepõem: a fonte direta expõe `syncState` no nível\r\n * de topo; o client expõe via `.db`.\r\n */\r\nfunction isNeetruClient(input: UseSyncStateInput): input is NeetruClient {\r\n const maybe = input as { db?: unknown; syncState?: unknown };\r\n // Fonte direta já tem `syncState` no topo → não é o client wrapper.\r\n if (typeof maybe.syncState !== 'undefined') return false;\r\n const db = maybe.db as { syncState?: unknown; onSyncStateChanged?: unknown } | undefined;\r\n return (\r\n !!db &&\r\n typeof db.syncState !== 'undefined' &&\r\n typeof db.onSyncStateChanged === 'function'\r\n );\r\n}\r\n\r\n/** Normaliza qualquer entrada aceita para a `SyncStateSource` subjacente. */\r\nfunction resolveSyncStateSource(input: UseSyncStateInput): SyncStateSource {\r\n return isNeetruClient(input) ? (input.db as unknown as SyncStateSource) : input;\r\n}\r\n\r\n// ─── Serialização estável de query ────────────────────────────────────────────\r\n\r\n/**\r\n * Serializa uma DbQuery para string estável, usada como chave de efeito.\r\n *\r\n * `useCollection` re-subscreve somente quando a serialização muda, NÃO por\r\n * identidade de objeto. Isso evita a armadilha clássica de inline-object em\r\n * JSX que causaria re-subscribe em todo render.\r\n */\r\nfunction serializeQuery(query: DbQuery | undefined): string {\r\n if (!query) return '';\r\n try {\r\n return JSON.stringify(query, Object.keys(query).sort());\r\n } catch {\r\n return String(query);\r\n }\r\n}\r\n\r\n// ─── useDocument ─────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Hook de tempo real para um documento específico.\r\n *\r\n * Subscreve a `ref.doc(id).onSnapshot` no mount. Re-subscreve quando `ref`\r\n * ou `id` mudam. Cancela a subscrição no unmount.\r\n *\r\n * ```tsx\r\n * const { data, loading, fromCache, stale } = useDocument(ordersRef, orderId);\r\n * if (loading) return <Spinner />;\r\n * if (!data) return <NotFound />;\r\n * return <OrderCard order={data} fromCache={fromCache} />;\r\n * ```\r\n *\r\n * Seguro em React 18/19 StrictMode: a cleanup function do useEffect cancela\r\n * o listener no unmount, e o double-mount do StrictMode não vaza estado.\r\n */\r\nexport function useDocument<T>(\r\n ref: DbCollectionRef<T>,\r\n id: string,\r\n): UseDocumentResult<T> {\r\n const [state, setState] = React.useState<UseDocumentResult<T>>({\r\n data: null,\r\n loading: true,\r\n fromCache: true,\r\n stale: false,\r\n });\r\n\r\n // Ref para detectar unmount e evitar setState após cleanup.\r\n const mountedRef = React.useRef(true);\r\n\r\n React.useEffect(() => {\r\n mountedRef.current = true;\r\n // Reset para loading quando ref/id muda\r\n setState({ data: null, loading: true, fromCache: true, stale: false });\r\n\r\n const docRef = ref.doc(id);\r\n\r\n const unsubscribe: Unsubscribe = docRef.onSnapshot(\r\n (snap: DbGetResult<T> | null) => {\r\n if (!mountedRef.current) return;\r\n if (snap === null || snap.docs.length === 0) {\r\n setState({\r\n data: null,\r\n loading: false,\r\n fromCache: snap?.fromCache ?? true,\r\n stale: snap?.stale ?? false,\r\n });\r\n } else {\r\n setState({\r\n data: snap.docs[0]?.data ?? null,\r\n loading: false,\r\n fromCache: snap.fromCache,\r\n stale: snap.stale,\r\n });\r\n }\r\n },\r\n );\r\n\r\n return () => {\r\n mountedRef.current = false;\r\n unsubscribe();\r\n };\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [ref, id]);\r\n\r\n return state;\r\n}\r\n\r\n// ─── useCollection ────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Hook de tempo real para uma coleção (ou subconjunto filtrado).\r\n *\r\n * Subscreve a `ref.onSnapshot(query, cb)` no mount. Re-subscreve quando `ref`\r\n * ou a serialização de `query` muda — NÃO por identidade de objeto, o que\r\n * evita re-subscribe desnecessário em inline objects.\r\n *\r\n * ```tsx\r\n * const { docs, loading, hasPendingWrites } = useCollection(ordersRef, {\r\n * where: [{ field: 'status', op: '==', value: 'open' }],\r\n * });\r\n * ```\r\n *\r\n * Seguro em React 18/19 StrictMode.\r\n */\r\nexport function useCollection<T>(\r\n ref: DbCollectionRef<T>,\r\n query?: DbQuery,\r\n): UseCollectionResult<T> {\r\n const [state, setState] = React.useState<UseCollectionResult<T>>({\r\n docs: [],\r\n loading: true,\r\n fromCache: true,\r\n stale: false,\r\n hasPendingWrites: false,\r\n });\r\n\r\n const mountedRef = React.useRef(true);\r\n\r\n // Serializamos a query para garantir que mudanças de valor (não de referência)\r\n // triggam o re-subscribe, mas que objetos iguais não causem loop.\r\n const queryKey = serializeQuery(query);\r\n\r\n React.useEffect(() => {\r\n mountedRef.current = true;\r\n // Reset loading ao trocar de query/ref\r\n setState({\r\n docs: [],\r\n loading: true,\r\n fromCache: true,\r\n stale: false,\r\n hasPendingWrites: false,\r\n });\r\n\r\n const unsubscribe: Unsubscribe = ref.onSnapshot(\r\n query,\r\n (snap: DbListResult<T>) => {\r\n if (!mountedRef.current) return;\r\n setState({\r\n docs: snap.docs,\r\n loading: false,\r\n fromCache: snap.fromCache,\r\n stale: snap.stale,\r\n hasPendingWrites: snap.hasPendingWrites,\r\n });\r\n },\r\n );\r\n\r\n return () => {\r\n mountedRef.current = false;\r\n unsubscribe();\r\n };\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [ref, queryKey]);\r\n\r\n return state;\r\n}\r\n\r\n// ─── useSyncState ─────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Hook para observar o `SyncState` da camada offline em tempo real.\r\n *\r\n * Retorna o `SyncState` atual e re-renderiza quando ele muda. Usado para\r\n * renderizar indicadores \"sincronizando…\" / \"offline\" / contadores de escritas\r\n * pendentes na UI.\r\n *\r\n * Aceita duas formas de entrada (SDK-QW-4 — wiring ao client real):\r\n *\r\n * ```tsx\r\n * // Forma oficial — passe o client; o hook resolve client.db internamente.\r\n * const client = createNeetruClient({ apiKey });\r\n * const state = useSyncState(client);\r\n *\r\n * // Forma direta — passe a fonte (client.db ou o namespace offline cru).\r\n * const state = useSyncState(client.db);\r\n *\r\n * if (state.status === 'offline') return <OfflineBanner pending={state.pendingWrites} />;\r\n * ```\r\n *\r\n * Antes desta versão o hook só aceitava a `SyncStateSource` crua, o que levava\r\n * consumers a stub-arem o estado pra sempre `'idle'` em vez de ligar no client.\r\n * Agora `useSyncState(client)` (a forma documentada em 02-sdk.md §3.6) funciona\r\n * de ponta a ponta — `client.db.syncState` / `client.db.onSyncStateChanged` são\r\n * reais.\r\n *\r\n * Seguro em React 18/19 StrictMode.\r\n */\r\nexport function useSyncState(input: UseSyncStateInput): SyncState {\r\n // Resolve uma única vez por render; se a identidade da fonte resolvida mudar,\r\n // o efeito abaixo re-subscreve (dependência `source`).\r\n const source = resolveSyncStateSource(input);\r\n\r\n const [state, setState] = React.useState<SyncState>(() => source.syncState);\r\n\r\n const mountedRef = React.useRef(true);\r\n\r\n React.useEffect(() => {\r\n mountedRef.current = true;\r\n\r\n // Snapshot inicial imediato ao montar (pode ter mudado entre render e effect)\r\n if (mountedRef.current) {\r\n setState(source.syncState);\r\n }\r\n\r\n const unsubscribe: Unsubscribe = source.onSyncStateChanged((s: SyncState) => {\r\n if (!mountedRef.current) return;\r\n setState(s);\r\n });\r\n\r\n return () => {\r\n mountedRef.current = false;\r\n unsubscribe();\r\n };\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [source]);\r\n\r\n return state;\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/db/react.ts"],"names":[],"mappings":"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","file":"db-react.mjs","sourcesContent":["/**\r\n * Bindings React do módulo db (M2) — `@neetru/sdk/react`.\r\n *\r\n * Exporta três hooks de tempo real que se integram com a camada offline do\r\n * `@neetru/sdk/db`:\r\n *\r\n * - `useDocument(ref, id)` — subscreve ao onSnapshot de um documento.\r\n * - `useCollection(ref, query?)` — subscreve ao onSnapshot de uma coleção.\r\n * - `useSyncState(ns)` — observa o SyncState do namespace offline.\r\n *\r\n * ### Design\r\n * Segue o padrão do subpath `/react` existente (cliente/ref passados\r\n * explicitamente, sem Context Provider obrigatório). Alinhado com 02-sdk.md §3.6.\r\n *\r\n * ### React 18/19 StrictMode\r\n * Cada hook usa um ref interno para rastrear se o componente foi desmontado,\r\n * e a função `useEffect` retorna sempre a função de unsubscribe. Em StrictMode,\r\n * o duplo-mount/unmount do React 18 chama cleanup corretamente — não há leak.\r\n *\r\n * ### Dependências\r\n * `react` é peer dependency — não importada como dep direta.\r\n */\r\nimport * as React from 'react';\r\nimport type { DbCollectionRef, DbDoc, DbQuery, DbListResult, DbGetResult } from './collection-ref';\r\nimport type { SyncState, Unsubscribe } from './offline/types';\r\nimport type { NeetruClient } from '../types';\r\n\r\n// ─── Tipos públicos dos hooks ─────────────────────────────────────────────────\r\n\r\n/**\r\n * Resultado de `useDocument`.\r\n *\r\n * - `data` — dados do documento, ou `null` se não existe / ainda carregando.\r\n * - `loading` — `true` enquanto o primeiro snapshot não foi entregue.\r\n * - `fromCache` — o snapshot veio do cache local, não do servidor.\r\n * - `stale` — conexão caída ou sincronizando; pode haver dados mais novos.\r\n */\r\nexport interface UseDocumentResult<T> {\r\n data: T | null;\r\n loading: boolean;\r\n fromCache: boolean;\r\n stale: boolean;\r\n}\r\n\r\n/**\r\n * Resultado de `useCollection`.\r\n *\r\n * - `docs` — documentos da coleção (ou subconjunto via query).\r\n * - `loading` — `true` enquanto o primeiro snapshot não foi entregue.\r\n * - `fromCache` — o snapshot veio do cache local.\r\n * - `stale` — conexão caída ou sincronizando.\r\n * - `hasPendingWrites` — ao menos um doc tem escrita local não confirmada.\r\n */\r\nexport interface UseCollectionResult<T> {\r\n docs: DbDoc<T>[];\r\n loading: boolean;\r\n fromCache: boolean;\r\n stale: boolean;\r\n hasPendingWrites: boolean;\r\n}\r\n\r\n/**\r\n * Subconjunto mínimo do namespace offline que `useSyncState` precisa.\r\n * Compatível com `NeetruDbDocuments` de `collection-ref.ts`.\r\n *\r\n * Passado explicitamente (sem Context) para alinhar com o padrão do SDK.\r\n * Tipicamente: o namespace retornado por `createOfflineDocumentsNamespace`.\r\n *\r\n * `client.db` (o `NeetruDb` retornado por `createNeetruClient`) também satisfaz\r\n * este contrato — expõe `syncState` + `onSyncStateChanged` reais. Por isso\r\n * `useSyncState` aceita tanto a fonte direta quanto o `NeetruClient` inteiro\r\n * (ver `UseSyncStateInput`).\r\n */\r\nexport interface SyncStateSource {\r\n readonly syncState: SyncState;\r\n onSyncStateChanged(cb: (s: SyncState) => void): Unsubscribe;\r\n}\r\n\r\n/**\r\n * Entrada aceita por `useSyncState`:\r\n * - `NeetruClient` → resolve `client.db` automaticamente (forma oficial,\r\n * alinhada com 02-sdk.md §3.6 `useSyncState(client)`).\r\n * - `SyncStateSource` → fonte direta (`client.db` ou o namespace offline cru).\r\n */\r\nexport type UseSyncStateInput = NeetruClient | SyncStateSource;\r\n\r\n/**\r\n * Type guard — detecta se a entrada é um `NeetruClient` (tem `.db` que é, ele\r\n * próprio, um `SyncStateSource`) e não uma fonte direta. Distinção em runtime\r\n * porque os shapes não se sobrepõem: a fonte direta expõe `syncState` no nível\r\n * de topo; o client expõe via `.db`.\r\n */\r\nfunction isNeetruClient(input: UseSyncStateInput): input is NeetruClient {\r\n const maybe = input as { db?: unknown; syncState?: unknown };\r\n // Fonte direta já tem `syncState` no topo → não é o client wrapper.\r\n if (typeof maybe.syncState !== 'undefined') return false;\r\n const db = maybe.db as { syncState?: unknown; onSyncStateChanged?: unknown } | undefined;\r\n return (\r\n !!db &&\r\n typeof db.syncState !== 'undefined' &&\r\n typeof db.onSyncStateChanged === 'function'\r\n );\r\n}\r\n\r\n/** Normaliza qualquer entrada aceita para a `SyncStateSource` subjacente. */\r\nfunction resolveSyncStateSource(input: UseSyncStateInput): SyncStateSource {\r\n return isNeetruClient(input) ? (input.db as unknown as SyncStateSource) : input;\r\n}\r\n\r\n// ─── Serialização estável de query ────────────────────────────────────────────\r\n\r\n/**\r\n * Serializa uma DbQuery para string estável, usada como chave de efeito.\r\n *\r\n * `useCollection` re-subscreve somente quando a serialização muda, NÃO por\r\n * identidade de objeto. Isso evita a armadilha clássica de inline-object em\r\n * JSX que causaria re-subscribe em todo render.\r\n */\r\nfunction serializeQuery(query: DbQuery | undefined): string {\r\n if (!query) return '';\r\n try {\r\n return JSON.stringify(query, Object.keys(query).sort());\r\n } catch {\r\n return String(query);\r\n }\r\n}\r\n\r\n// ─── useDocument ─────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Hook de tempo real para um documento específico.\r\n *\r\n * Subscreve a `ref.doc(id).onSnapshot` no mount. Re-subscreve quando `ref`\r\n * ou `id` mudam. Cancela a subscrição no unmount.\r\n *\r\n * ```tsx\r\n * const { data, loading, fromCache, stale } = useDocument(ordersRef, orderId);\r\n * if (loading) return <Spinner />;\r\n * if (!data) return <NotFound />;\r\n * return <OrderCard order={data} fromCache={fromCache} />;\r\n * ```\r\n *\r\n * Seguro em React 18/19 StrictMode: a cleanup function do useEffect cancela\r\n * o listener no unmount, e o double-mount do StrictMode não vaza estado.\r\n */\r\nexport function useDocument<T>(\r\n ref: DbCollectionRef<T>,\r\n id: string,\r\n): UseDocumentResult<T> {\r\n const [state, setState] = React.useState<UseDocumentResult<T>>({\r\n data: null,\r\n loading: true,\r\n fromCache: true,\r\n stale: false,\r\n });\r\n\r\n // Ref para detectar unmount e evitar setState após cleanup.\r\n const mountedRef = React.useRef(true);\r\n\r\n React.useEffect(() => {\r\n mountedRef.current = true;\r\n // Reset para loading quando ref/id muda\r\n setState({ data: null, loading: true, fromCache: true, stale: false });\r\n\r\n const docRef = ref.doc(id);\r\n\r\n const unsubscribe: Unsubscribe = docRef.onSnapshot(\r\n (snap: DbGetResult<T> | null) => {\r\n if (!mountedRef.current) return;\r\n if (snap === null || snap.docs.length === 0) {\r\n setState({\r\n data: null,\r\n loading: false,\r\n fromCache: snap?.fromCache ?? true,\r\n stale: snap?.stale ?? false,\r\n });\r\n } else {\r\n setState({\r\n data: snap.docs[0]?.data ?? null,\r\n loading: false,\r\n fromCache: snap.fromCache,\r\n stale: snap.stale,\r\n });\r\n }\r\n },\r\n );\r\n\r\n return () => {\r\n mountedRef.current = false;\r\n unsubscribe();\r\n };\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [ref, id]);\r\n\r\n return state;\r\n}\r\n\r\n// ─── useCollection ────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Hook de tempo real para uma coleção (ou subconjunto filtrado).\r\n *\r\n * Subscreve a `ref.onSnapshot(query, cb)` no mount. Re-subscreve quando `ref`\r\n * ou a serialização de `query` muda — NÃO por identidade de objeto, o que\r\n * evita re-subscribe desnecessário em inline objects.\r\n *\r\n * ```tsx\r\n * const { docs, loading, hasPendingWrites } = useCollection(ordersRef, {\r\n * where: [{ field: 'status', op: '==', value: 'open' }],\r\n * });\r\n * ```\r\n *\r\n * Seguro em React 18/19 StrictMode.\r\n */\r\nexport function useCollection<T>(\r\n ref: DbCollectionRef<T>,\r\n query?: DbQuery,\r\n): UseCollectionResult<T> {\r\n const [state, setState] = React.useState<UseCollectionResult<T>>({\r\n docs: [],\r\n loading: true,\r\n fromCache: true,\r\n stale: false,\r\n hasPendingWrites: false,\r\n });\r\n\r\n const mountedRef = React.useRef(true);\r\n\r\n // Serializamos a query para garantir que mudanças de valor (não de referência)\r\n // triggam o re-subscribe, mas que objetos iguais não causem loop.\r\n const queryKey = serializeQuery(query);\r\n\r\n // Bug corrigido: o efeito usava `query` da closure externa, que poderia estar\r\n // desatualizado quando ref mudava sem o queryKey mudar. O ref mantém sempre o\r\n // valor mais recente sem re-disparar o efeito.\r\n const queryRef = React.useRef(query);\r\n queryRef.current = query;\r\n\r\n React.useEffect(() => {\r\n mountedRef.current = true;\r\n // Reset loading ao trocar de query/ref\r\n setState({\r\n docs: [],\r\n loading: true,\r\n fromCache: true,\r\n stale: false,\r\n hasPendingWrites: false,\r\n });\r\n\r\n const unsubscribe: Unsubscribe = ref.onSnapshot(\r\n queryRef.current,\r\n (snap: DbListResult<T>) => {\r\n if (!mountedRef.current) return;\r\n setState({\r\n docs: snap.docs,\r\n loading: false,\r\n fromCache: snap.fromCache,\r\n stale: snap.stale,\r\n hasPendingWrites: snap.hasPendingWrites,\r\n });\r\n },\r\n );\r\n\r\n return () => {\r\n mountedRef.current = false;\r\n unsubscribe();\r\n };\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [ref, queryKey]);\r\n\r\n return state;\r\n}\r\n\r\n// ─── useSyncState ─────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Hook para observar o `SyncState` da camada offline em tempo real.\r\n *\r\n * Retorna o `SyncState` atual e re-renderiza quando ele muda. Usado para\r\n * renderizar indicadores \"sincronizando…\" / \"offline\" / contadores de escritas\r\n * pendentes na UI.\r\n *\r\n * Aceita duas formas de entrada (SDK-QW-4 — wiring ao client real):\r\n *\r\n * ```tsx\r\n * // Forma oficial — passe o client; o hook resolve client.db internamente.\r\n * const client = createNeetruClient({ apiKey });\r\n * const state = useSyncState(client);\r\n *\r\n * // Forma direta — passe a fonte (client.db ou o namespace offline cru).\r\n * const state = useSyncState(client.db);\r\n *\r\n * if (state.status === 'offline') return <OfflineBanner pending={state.pendingWrites} />;\r\n * ```\r\n *\r\n * Antes desta versão o hook só aceitava a `SyncStateSource` crua, o que levava\r\n * consumers a stub-arem o estado pra sempre `'idle'` em vez de ligar no client.\r\n * Agora `useSyncState(client)` (a forma documentada em 02-sdk.md §3.6) funciona\r\n * de ponta a ponta — `client.db.syncState` / `client.db.onSyncStateChanged` são\r\n * reais.\r\n *\r\n * Seguro em React 18/19 StrictMode.\r\n */\r\nexport function useSyncState(input: UseSyncStateInput): SyncState {\r\n // Resolve uma única vez por render; se a identidade da fonte resolvida mudar,\r\n // o efeito abaixo re-subscreve (dependência `source`).\r\n const source = resolveSyncStateSource(input);\r\n\r\n const [state, setState] = React.useState<SyncState>(() => source.syncState);\r\n\r\n const mountedRef = React.useRef(true);\r\n\r\n React.useEffect(() => {\r\n mountedRef.current = true;\r\n\r\n // Snapshot inicial imediato ao montar (pode ter mudado entre render e effect)\r\n if (mountedRef.current) {\r\n setState(source.syncState);\r\n }\r\n\r\n const unsubscribe: Unsubscribe = source.onSyncStateChanged((s: SyncState) => {\r\n if (!mountedRef.current) return;\r\n setState(s);\r\n });\r\n\r\n return () => {\r\n mountedRef.current = false;\r\n unsubscribe();\r\n };\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [source]);\r\n\r\n return state;\r\n}\r\n"]}
package/dist/db.cjs CHANGED
@@ -87,8 +87,47 @@ var init_idempotency = __esm({
87
87
  // src/http.ts
88
88
  var http_exports = {};
89
89
  __export(http_exports, {
90
+ checkCoreVersionCompat: () => checkCoreVersionCompat,
90
91
  httpRequest: () => httpRequest
91
92
  });
93
+ function stripRevision(v) {
94
+ return v.split("+")[0] ?? v;
95
+ }
96
+ function compareCalVer(a, b) {
97
+ const partsA = stripRevision(a).split(".").map(Number);
98
+ const partsB = stripRevision(b).split(".").map(Number);
99
+ const len = Math.max(partsA.length, partsB.length);
100
+ for (let i = 0; i < len; i++) {
101
+ const pa = partsA[i] ?? 0;
102
+ const pb = partsB[i] ?? 0;
103
+ if (pa !== pb) return pa - pb;
104
+ }
105
+ return 0;
106
+ }
107
+ function getSdkMajor() {
108
+ try {
109
+ const ver = globalThis.__NEETRU_SDK_VERSION__;
110
+ if (ver) return parseInt(ver.split(".")[0] ?? "3", 10);
111
+ } catch {
112
+ }
113
+ return 3;
114
+ }
115
+ function checkCoreVersionCompat(coreVersion) {
116
+ if (!coreVersion) return;
117
+ const sdkMajor = getSdkMajor();
118
+ const minCore = COMPAT_MATRIX[sdkMajor];
119
+ if (!minCore) return;
120
+ const stripped = stripRevision(coreVersion);
121
+ if (compareCalVer(stripped, minCore) < 0) {
122
+ const key = `${sdkMajor}:${stripped}`;
123
+ if (!_warnedCoreTooOld.has(key)) {
124
+ _warnedCoreTooOld.add(key);
125
+ console.warn(
126
+ `[Neetru SDK] Aten\xE7\xE3o: Core vers\xE3o "${coreVersion}" \xE9 anterior ao m\xEDnimo recomendado "${minCore}" para SDK major ${sdkMajor}. Alguns recursos podem n\xE3o funcionar corretamente. Atualize o Core para a vers\xE3o mais recente.`
127
+ );
128
+ }
129
+ }
130
+ }
92
131
  function backoffMs(attempt) {
93
132
  const base = 200 * Math.pow(4, attempt);
94
133
  const jitter = base * 0.2 * (Math.random() * 2 - 1);
@@ -97,11 +136,11 @@ function backoffMs(attempt) {
97
136
  function parseRetryAfter(value) {
98
137
  if (!value) return null;
99
138
  const secs = Number(value);
100
- if (Number.isFinite(secs) && secs >= 0) return Math.round(secs * 1e3);
139
+ if (Number.isFinite(secs) && secs >= 0) return Math.min(Math.round(secs * 1e3), MAX_RETRY_AFTER_MS);
101
140
  const dateMs = Date.parse(value);
102
141
  if (Number.isFinite(dateMs)) {
103
142
  const delta = dateMs - Date.now();
104
- if (delta > 0) return delta;
143
+ if (delta > 0) return Math.min(delta, MAX_RETRY_AFTER_MS);
105
144
  }
106
145
  return null;
107
146
  }
@@ -112,6 +151,7 @@ function statusToCode(status) {
112
151
  if (status === 401) return "unauthorized";
113
152
  if (status === 403) return "forbidden";
114
153
  if (status === 404) return "not_found";
154
+ if (status === 409) return "conflict";
115
155
  if (status === 422 || status === 400) return "validation_failed";
116
156
  if (status === 429) return "rate_limited";
117
157
  if (status >= 500) return "server_error";
@@ -183,6 +223,8 @@ async function httpRequest(config, opts) {
183
223
  }
184
224
  const requestId = res.headers.get("x-request-id") ?? res.headers.get("x-correlation-id") ?? void 0;
185
225
  if (res.ok) {
226
+ const coreVersion = res.headers.get("x-neetru-version");
227
+ if (coreVersion) checkCoreVersionCompat(coreVersion);
186
228
  const parsed = await safeJson(res);
187
229
  return parsed;
188
230
  }
@@ -211,11 +253,17 @@ async function httpRequest(config, opts) {
211
253
  }
212
254
  throw lastError ?? new NeetruError("unknown", "unexpected httpRequest exit");
213
255
  }
214
- var DEFAULT_RETRIES_READ, DEFAULT_RETRIES_WRITE, READ_METHODS, RETRYABLE_CODES2;
256
+ var COMPAT_MATRIX, _warnedCoreTooOld, DEFAULT_RETRIES_READ, DEFAULT_RETRIES_WRITE, READ_METHODS, RETRYABLE_CODES2, MAX_RETRY_AFTER_MS;
215
257
  var init_http = __esm({
216
258
  "src/http.ts"() {
217
259
  init_errors();
218
260
  init_idempotency();
261
+ COMPAT_MATRIX = {
262
+ // SDK major 3.x requer Core ≥ 2026.05.1 (suporte a X-Neetru-Version header,
263
+ // runTransaction write-after-read, verifyWebhookRequest, webhooks 3.0+)
264
+ 3: "2026.05.1"
265
+ };
266
+ _warnedCoreTooOld = /* @__PURE__ */ new Set();
219
267
  DEFAULT_RETRIES_READ = 2;
220
268
  DEFAULT_RETRIES_WRITE = 0;
221
269
  READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
@@ -224,6 +272,7 @@ var init_http = __esm({
224
272
  "server_error",
225
273
  "network_error"
226
274
  ]);
275
+ MAX_RETRY_AFTER_MS = 6e4;
227
276
  }
228
277
  });
229
278
 
@@ -790,6 +839,31 @@ var LocalStore = class {
790
839
  this.db?.close();
791
840
  this.db = null;
792
841
  }
842
+ /**
843
+ * Apaga completamente o banco IndexedDB e o recria do zero.
844
+ *
845
+ * Ao contrário de `close()` + `open()` (que reabre o banco existente sem
846
+ * apagar dados), `clearAll()` chama `indexedDB.deleteDatabase(dbName)` e
847
+ * só então chama `open()` — garantindo que nenhum documento, mutação ou
848
+ * metadado sobrevive.
849
+ *
850
+ * BUG fix: `clearCache()` em `NeetruDbDocumentsImpl` chamava close+open,
851
+ * que reabre o mesmo banco. `clearAll()` é o método correto.
852
+ */
853
+ async clearAll() {
854
+ if (this.db !== null) {
855
+ this.db.close();
856
+ this.db = null;
857
+ }
858
+ await new Promise((resolve, reject) => {
859
+ const req = indexedDB.deleteDatabase(this.dbName);
860
+ req.onsuccess = () => resolve();
861
+ req.onerror = () => reject(req.error);
862
+ req.onblocked = () => {
863
+ };
864
+ });
865
+ await this.open();
866
+ }
793
867
  assertOpen() {
794
868
  if (this.db === null) {
795
869
  throw new Error("LocalStore: banco n\xE3o est\xE1 aberto. Chame open() primeiro.");
@@ -997,6 +1071,12 @@ var MemoryStore = class {
997
1071
  async open() {
998
1072
  }
999
1073
  async close() {
1074
+ }
1075
+ /**
1076
+ * Apaga todos os dados em memória — equivalente ao `indexedDB.deleteDatabase`
1077
+ * que `LocalStore.clearAll()` executa em ambientes com IndexedDB.
1078
+ */
1079
+ async clearAll() {
1000
1080
  this._docs.clear();
1001
1081
  this._mutations.clear();
1002
1082
  this._meta.clear();
@@ -1522,6 +1602,8 @@ var SyncEngine = class {
1522
1602
  const phase1Ok = await this._phase1Push();
1523
1603
  if (!phase1Ok) {
1524
1604
  this._updateState({ status: "idle" });
1605
+ this._refreshPendingWrites().catch(() => {
1606
+ });
1525
1607
  this._syncing = false;
1526
1608
  return;
1527
1609
  }
@@ -1908,7 +1990,7 @@ var SyncEngine = class {
1908
1990
  ownerId: localDoc.meta.ownerId
1909
1991
  }
1910
1992
  };
1911
- const pendingMuts = await this._queue.drain();
1993
+ const pendingMuts = await this._queue.listAll();
1912
1994
  const docMut = pendingMuts.find(
1913
1995
  (m) => m.collection === serverDoc.collection && m.docId === serverDoc.id
1914
1996
  );
@@ -3262,8 +3344,13 @@ var NeetruDbDocumentsImpl = class {
3262
3344
  return this._engine.flush();
3263
3345
  }
3264
3346
  async clearCache() {
3265
- await this._store.close();
3266
- await this._store.open();
3347
+ const storeWithClear = this._store;
3348
+ if (typeof storeWithClear.clearAll === "function") {
3349
+ await storeWithClear.clearAll();
3350
+ } else {
3351
+ await this._store.close();
3352
+ await this._store.open();
3353
+ }
3267
3354
  }
3268
3355
  async getConflicts() {
3269
3356
  return this._store.listConflicts();