@neetru/sdk 3.1.0 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth.cjs +159 -15
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +1 -1
- package/dist/auth.d.ts +1 -1
- package/dist/auth.mjs +159 -15
- package/dist/auth.mjs.map +1 -1
- package/dist/catalog.cjs +50 -2
- package/dist/catalog.cjs.map +1 -1
- package/dist/catalog.d.cts +1 -1
- package/dist/catalog.d.ts +1 -1
- package/dist/catalog.mjs +50 -2
- package/dist/catalog.mjs.map +1 -1
- package/dist/checkout.cjs +52 -3
- package/dist/checkout.cjs.map +1 -1
- package/dist/checkout.d.cts +1 -1
- package/dist/checkout.d.ts +1 -1
- package/dist/checkout.mjs +52 -3
- package/dist/checkout.mjs.map +1 -1
- package/dist/db-react.cjs +3 -1
- package/dist/db-react.cjs.map +1 -1
- package/dist/db-react.d.cts +1 -1
- package/dist/db-react.d.ts +1 -1
- package/dist/db-react.mjs +3 -1
- package/dist/db-react.mjs.map +1 -1
- package/dist/db.cjs +93 -6
- package/dist/db.cjs.map +1 -1
- package/dist/db.d.cts +1 -1
- package/dist/db.d.ts +1 -1
- package/dist/db.mjs +93 -6
- package/dist/db.mjs.map +1 -1
- package/dist/entitlements.cjs +50 -2
- package/dist/entitlements.cjs.map +1 -1
- package/dist/entitlements.d.cts +1 -1
- package/dist/entitlements.d.ts +1 -1
- package/dist/entitlements.mjs +50 -2
- package/dist/entitlements.mjs.map +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.cts +3 -1
- package/dist/errors.d.ts +3 -1
- package/dist/errors.mjs.map +1 -1
- package/dist/firestore-compat.cjs +29 -4
- package/dist/firestore-compat.cjs.map +1 -1
- package/dist/firestore-compat.d.cts +28 -3
- package/dist/firestore-compat.d.ts +28 -3
- package/dist/firestore-compat.mjs +29 -4
- package/dist/firestore-compat.mjs.map +1 -1
- package/dist/index.cjs +195 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.mjs +194 -17
- package/dist/index.mjs.map +1 -1
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.cts +1 -1
- package/dist/mocks.d.ts +1 -1
- package/dist/mocks.mjs.map +1 -1
- package/dist/notifications.cjs +50 -2
- package/dist/notifications.cjs.map +1 -1
- package/dist/notifications.d.cts +1 -1
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.mjs +50 -2
- package/dist/notifications.mjs.map +1 -1
- package/dist/react.cjs +2 -3
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +22 -4
- package/dist/react.d.ts +22 -4
- package/dist/react.mjs +2 -3
- package/dist/react.mjs.map +1 -1
- package/dist/support.cjs +50 -2
- package/dist/support.cjs.map +1 -1
- package/dist/support.d.cts +1 -1
- package/dist/support.d.ts +1 -1
- package/dist/support.mjs +50 -2
- package/dist/support.mjs.map +1 -1
- package/dist/telemetry.cjs +65 -4
- package/dist/telemetry.cjs.map +1 -1
- package/dist/telemetry.d.cts +1 -1
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.mjs +65 -4
- package/dist/telemetry.mjs.map +1 -1
- package/dist/{types-BRv8wBxX.d.ts → types-B3XFHD4A.d.ts} +60 -1
- package/dist/{types-nwErcRX8.d.cts → types-DePdSU3P.d.cts} +60 -1
- package/dist/usage.cjs +50 -2
- package/dist/usage.cjs.map +1 -1
- package/dist/usage.d.cts +1 -1
- package/dist/usage.d.ts +1 -1
- package/dist/usage.mjs +50 -2
- package/dist/usage.mjs.map +1 -1
- package/dist/webhooks.cjs +83 -2
- package/dist/webhooks.cjs.map +1 -1
- package/dist/webhooks.d.cts +1 -1
- package/dist/webhooks.d.ts +1 -1
- package/dist/webhooks.mjs +83 -3
- package/dist/webhooks.mjs.map +1 -1
- package/package.json +1 -1
package/dist/db-react.cjs.map
CHANGED
|
@@ -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"]}
|
package/dist/db-react.d.cts
CHANGED
|
@@ -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-
|
|
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
|
|
package/dist/db-react.d.ts
CHANGED
|
@@ -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-
|
|
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
|
-
|
|
82
|
+
queryRef.current,
|
|
81
83
|
(snap) => {
|
|
82
84
|
if (!mountedRef.current) return;
|
|
83
85
|
setState({
|
package/dist/db-react.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/db/react.ts"],"names":[],"mappings":";;;AA4FA,SAAS,eAAe,KAAA,EAAiD;AACvE,EAAA,MAAM,KAAA,GAAQ,KAAA;AAEd,EAAA,IAAI,OAAO,KAAA,CAAM,SAAA,KAAc,WAAA,EAAa,OAAO,KAAA;AACnD,EAAA,MAAM,KAAK,KAAA,CAAM,EAAA;AACjB,EAAA,OACE,CAAC,CAAC,EAAA,IACF,OAAO,GAAG,SAAA,KAAc,WAAA,IACxB,OAAO,EAAA,CAAG,kBAAA,KAAuB,UAAA;AAErC;AAGA,SAAS,uBAAuB,KAAA,EAA2C;AACzE,EAAA,OAAO,cAAA,CAAe,KAAK,CAAA,GAAK,KAAA,CAAM,EAAA,GAAoC,KAAA;AAC5E;AAWA,SAAS,eAAe,KAAA,EAAoC;AAC1D,EAAA,IAAI,CAAC,OAAO,OAAO,EAAA;AACnB,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,CAAK,UAAU,KAAA,EAAO,MAAA,CAAO,KAAK,KAAK,CAAA,CAAE,MAAM,CAAA;AAAA,EACxD,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,OAAO,KAAK,CAAA;AAAA,EACrB;AACF;AAoBO,SAAS,WAAA,CACd,KACA,EAAA,EACsB;AACtB,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAU,KAAA,CAAA,QAAA,CAA+B;AAAA,IAC7D,IAAA,EAAM,IAAA;AAAA,IACN,OAAA,EAAS,IAAA;AAAA,IACT,SAAA,EAAW,IAAA;AAAA,IACX,KAAA,EAAO;AAAA,GACR,CAAA;AAGD,EAAA,MAAM,UAAA,GAAmB,aAAO,IAAI,CAAA;AAEpC,EAAM,gBAAU,MAAM;AACpB,IAAA,UAAA,CAAW,OAAA,GAAU,IAAA;AAErB,IAAA,QAAA,CAAS,EAAE,MAAM,IAAA,EAAM,OAAA,EAAS,MAAM,SAAA,EAAW,IAAA,EAAM,KAAA,EAAO,KAAA,EAAO,CAAA;AAErE,IAAA,MAAM,MAAA,GAAS,GAAA,CAAI,GAAA,CAAI,EAAE,CAAA;AAEzB,IAAA,MAAM,cAA2B,MAAA,CAAO,UAAA;AAAA,MACtC,CAAC,IAAA,KAAgC;AAC/B,QAAA,IAAI,CAAC,WAAW,OAAA,EAAS;AACzB,QAAA,IAAI,IAAA,KAAS,IAAA,IAAQ,IAAA,CAAK,IAAA,CAAK,WAAW,CAAA,EAAG;AAC3C,UAAA,QAAA,CAAS;AAAA,YACP,IAAA,EAAM,IAAA;AAAA,YACN,OAAA,EAAS,KAAA;AAAA,YACT,SAAA,EAAW,MAAM,SAAA,IAAa,IAAA;AAAA,YAC9B,KAAA,EAAO,MAAM,KAAA,IAAS;AAAA,WACvB,CAAA;AAAA,QACH,CAAA,MAAO;AACL,UAAA,QAAA,CAAS;AAAA,YACP,IAAA,EAAM,IAAA,CAAK,IAAA,CAAK,CAAC,GAAG,IAAA,IAAQ,IAAA;AAAA,YAC5B,OAAA,EAAS,KAAA;AAAA,YACT,WAAW,IAAA,CAAK,SAAA;AAAA,YAChB,OAAO,IAAA,CAAK;AAAA,WACb,CAAA;AAAA,QACH;AAAA,MACF;AAAA,KACF;AAEA,IAAA,OAAO,MAAM;AACX,MAAA,UAAA,CAAW,OAAA,GAAU,KAAA;AACrB,MAAA,WAAA,EAAY;AAAA,IACd,CAAA;AAAA,EAEF,CAAA,EAAG,CAAC,GAAA,EAAK,EAAE,CAAC,CAAA;AAEZ,EAAA,OAAO,KAAA;AACT;AAmBO,SAAS,aAAA,CACd,KACA,KAAA,EACwB;AACxB,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAU,KAAA,CAAA,QAAA,CAAiC;AAAA,IAC/D,MAAM,EAAC;AAAA,IACP,OAAA,EAAS,IAAA;AAAA,IACT,SAAA,EAAW,IAAA;AAAA,IACX,KAAA,EAAO,KAAA;AAAA,IACP,gBAAA,EAAkB;AAAA,GACnB,CAAA;AAED,EAAA,MAAM,UAAA,GAAmB,aAAO,IAAI,CAAA;AAIpC,EAAA,MAAM,QAAA,GAAW,eAAe,KAAK,CAAA;AAErC,EAAM,gBAAU,MAAM;AACpB,IAAA,UAAA,CAAW,OAAA,GAAU,IAAA;AAErB,IAAA,QAAA,CAAS;AAAA,MACP,MAAM,EAAC;AAAA,MACP,OAAA,EAAS,IAAA;AAAA,MACT,SAAA,EAAW,IAAA;AAAA,MACX,KAAA,EAAO,KAAA;AAAA,MACP,gBAAA,EAAkB;AAAA,KACnB,CAAA;AAED,IAAA,MAAM,cAA2B,GAAA,CAAI,UAAA;AAAA,MACnC,KAAA;AAAA,MACA,CAAC,IAAA,KAA0B;AACzB,QAAA,IAAI,CAAC,WAAW,OAAA,EAAS;AACzB,QAAA,QAAA,CAAS;AAAA,UACP,MAAM,IAAA,CAAK,IAAA;AAAA,UACX,OAAA,EAAS,KAAA;AAAA,UACT,WAAW,IAAA,CAAK,SAAA;AAAA,UAChB,OAAO,IAAA,CAAK,KAAA;AAAA,UACZ,kBAAkB,IAAA,CAAK;AAAA,SACxB,CAAA;AAAA,MACH;AAAA,KACF;AAEA,IAAA,OAAO,MAAM;AACX,MAAA,UAAA,CAAW,OAAA,GAAU,KAAA;AACrB,MAAA,WAAA,EAAY;AAAA,IACd,CAAA;AAAA,EAEF,CAAA,EAAG,CAAC,GAAA,EAAK,QAAQ,CAAC,CAAA;AAElB,EAAA,OAAO,KAAA;AACT;AAgCO,SAAS,aAAa,KAAA,EAAqC;AAGhE,EAAA,MAAM,MAAA,GAAS,uBAAuB,KAAK,CAAA;AAE3C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,IAAU,KAAA,CAAA,QAAA,CAAoB,MAAM,OAAO,SAAS,CAAA;AAE1E,EAAA,MAAM,UAAA,GAAmB,aAAO,IAAI,CAAA;AAEpC,EAAM,gBAAU,MAAM;AACpB,IAAA,UAAA,CAAW,OAAA,GAAU,IAAA;AAGrB,IAAA,IAAI,WAAW,OAAA,EAAS;AACtB,MAAA,QAAA,CAAS,OAAO,SAAS,CAAA;AAAA,IAC3B;AAEA,IAAA,MAAM,WAAA,GAA2B,MAAA,CAAO,kBAAA,CAAmB,CAAC,CAAA,KAAiB;AAC3E,MAAA,IAAI,CAAC,WAAW,OAAA,EAAS;AACzB,MAAA,QAAA,CAAS,CAAC,CAAA;AAAA,IACZ,CAAC,CAAA;AAED,IAAA,OAAO,MAAM;AACX,MAAA,UAAA,CAAW,OAAA,GAAU,KAAA;AACrB,MAAA,WAAA,EAAY;AAAA,IACd,CAAA;AAAA,EAEF,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAEX,EAAA,OAAO,KAAA;AACT","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.
|
|
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
|
-
|
|
3266
|
-
|
|
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();
|