@neetru/sdk 3.0.1 → 3.1.0
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/CHANGELOG.md +554 -492
- package/dist/auth.cjs +84 -15
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +1 -2
- package/dist/auth.d.ts +1 -2
- package/dist/auth.mjs +84 -15
- package/dist/auth.mjs.map +1 -1
- package/dist/catalog.d.cts +1 -2
- package/dist/catalog.d.ts +1 -2
- package/dist/checkout.d.cts +1 -2
- package/dist/checkout.d.ts +1 -2
- package/dist/db-react.cjs +15 -5
- package/dist/db-react.cjs.map +1 -1
- package/dist/db-react.d.cts +35 -13
- package/dist/db-react.d.ts +35 -13
- package/dist/db-react.mjs +15 -5
- package/dist/db-react.mjs.map +1 -1
- package/dist/db.cjs +104 -18
- package/dist/db.cjs.map +1 -1
- package/dist/db.d.cts +1 -2
- package/dist/db.d.ts +1 -2
- package/dist/db.mjs +99 -19
- package/dist/db.mjs.map +1 -1
- package/dist/entitlements.d.cts +1 -2
- package/dist/entitlements.d.ts +1 -2
- package/dist/firestore-compat.cjs +399 -0
- package/dist/firestore-compat.cjs.map +1 -0
- package/dist/firestore-compat.d.cts +294 -0
- package/dist/firestore-compat.d.ts +294 -0
- package/dist/firestore-compat.mjs +371 -0
- package/dist/firestore-compat.mjs.map +1 -0
- package/dist/index.cjs +105 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -3
- package/dist/index.d.ts +2 -3
- package/dist/index.mjs +100 -20
- package/dist/index.mjs.map +1 -1
- package/dist/mocks.d.cts +1 -2
- package/dist/mocks.d.ts +1 -2
- package/dist/notifications.d.cts +1 -2
- package/dist/notifications.d.ts +1 -2
- package/dist/react.cjs +267 -0
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +117 -3
- package/dist/react.d.ts +117 -3
- package/dist/react.mjs +265 -1
- package/dist/react.mjs.map +1 -1
- package/dist/support.d.cts +1 -2
- package/dist/support.d.ts +1 -2
- package/dist/telemetry.d.cts +1 -2
- package/dist/telemetry.d.ts +1 -2
- package/dist/{types-DALIhcbq.d.ts → types-BRv8wBxX.d.ts} +581 -2
- package/dist/{types-Lfd3LiAF.d.cts → types-nwErcRX8.d.cts} +581 -2
- package/dist/usage.d.cts +1 -2
- package/dist/usage.d.ts +1 -2
- package/dist/webhooks.d.cts +1 -2
- package/dist/webhooks.d.ts +1 -2
- package/package.json +156 -151
- package/dist/collection-ref-DqAAhuhX.d.cts +0 -472
- package/dist/collection-ref-DqAAhuhX.d.ts +0 -472
package/dist/react.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import './
|
|
2
|
+
import { z as NeetruClient, g as CheckoutStartResult, A as AuthNamespace, T as NeetruUser } from './types-nwErcRX8.cjs';
|
|
3
|
+
import { CollectionReference, Query, DocumentReference } from './firestore-compat.cjs';
|
|
4
4
|
import 'drizzle-orm/node-postgres';
|
|
5
5
|
import './errors.cjs';
|
|
6
6
|
|
|
@@ -111,5 +111,119 @@ interface EntitlementGateProps {
|
|
|
111
111
|
* deve preferir `mode='readonly'` pra preservar engajamento (não hard-block).
|
|
112
112
|
*/
|
|
113
113
|
declare function EntitlementGate({ client, feature, productSlug, mode, fallback, loading, children, }: EntitlementGateProps): React.ReactElement;
|
|
114
|
+
/** Resultado de `useCollection`. */
|
|
115
|
+
interface UseCollectionResult<T> {
|
|
116
|
+
/** Documentos (cada um com `id` embutido) ou `null` enquanto carrega/sem ref. */
|
|
117
|
+
data: Array<T & {
|
|
118
|
+
id: string;
|
|
119
|
+
}> | null;
|
|
120
|
+
/** `true` enquanto o primeiro snapshot não chegou. */
|
|
121
|
+
isLoading: boolean;
|
|
122
|
+
/** Erro do último refresh (dados exibidos podem estar defasados). */
|
|
123
|
+
error: Error | null;
|
|
124
|
+
/** `true` quando um refresh falhou ou o snapshot veio do cache/offline. */
|
|
125
|
+
isStale: boolean;
|
|
126
|
+
/** epoch ms do último snapshot bem-sucedido, ou `null`. */
|
|
127
|
+
lastUpdated: number | null;
|
|
128
|
+
}
|
|
129
|
+
/** Resultado de `useDoc`. */
|
|
130
|
+
interface UseDocResult<T> {
|
|
131
|
+
data: (T & {
|
|
132
|
+
id: string;
|
|
133
|
+
}) | null;
|
|
134
|
+
isLoading: boolean;
|
|
135
|
+
error: Error | null;
|
|
136
|
+
isStale: boolean;
|
|
137
|
+
lastUpdated: number | null;
|
|
138
|
+
}
|
|
139
|
+
/** Opções comuns dos hooks de dados. */
|
|
140
|
+
interface UseDataOptions {
|
|
141
|
+
/**
|
|
142
|
+
* Intervalo de polling de fallback em ms (quando o transporte não oferece
|
|
143
|
+
* realtime nativo). Default 5000. O polling pausa quando a aba está oculta.
|
|
144
|
+
*/
|
|
145
|
+
intervalMs?: number;
|
|
146
|
+
/**
|
|
147
|
+
* Força polling mesmo quando o `onSnapshot` nativo está disponível. Útil para
|
|
148
|
+
* transportes REST onde `onSnapshot` só entrega o snapshot inicial.
|
|
149
|
+
* Default: `false` (usa realtime quando o transporte oferece).
|
|
150
|
+
*/
|
|
151
|
+
poll?: boolean;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Hook de tempo real para uma coleção (ou subconjunto filtrado via `query`).
|
|
155
|
+
*
|
|
156
|
+
* Aceita uma `CollectionReference` ou `Query` do `@neetru/sdk/firestore-compat`.
|
|
157
|
+
* Subscreve ao `onSnapshot` nativo do `client.db`; quando o transporte é REST
|
|
158
|
+
* (sem realtime nativo), faz polling visibility-aware no `intervalMs`.
|
|
159
|
+
*
|
|
160
|
+
* ```tsx
|
|
161
|
+
* import { collection, query, where } from '@neetru/sdk/firestore-compat';
|
|
162
|
+
* import { useCollection } from '@neetru/sdk/react';
|
|
163
|
+
*
|
|
164
|
+
* const ref = useMemo(
|
|
165
|
+
* () => query(collection(client.db, 'orders'), where('status', '==', 'open')),
|
|
166
|
+
* [],
|
|
167
|
+
* );
|
|
168
|
+
* const { data, isLoading, error, isStale } = useCollection<Order>(ref);
|
|
169
|
+
* ```
|
|
170
|
+
*
|
|
171
|
+
* Seguro em React 18/19 StrictMode.
|
|
172
|
+
*/
|
|
173
|
+
declare function useCollection<T = Record<string, unknown>>(source: CollectionReference<T> | Query<T> | null | undefined, options?: UseDataOptions): UseCollectionResult<T>;
|
|
174
|
+
/**
|
|
175
|
+
* Hook de tempo real para um documento.
|
|
176
|
+
*
|
|
177
|
+
* Aceita uma `DocumentReference` do `@neetru/sdk/firestore-compat`.
|
|
178
|
+
*
|
|
179
|
+
* ```tsx
|
|
180
|
+
* import { doc } from '@neetru/sdk/firestore-compat';
|
|
181
|
+
* import { useDoc } from '@neetru/sdk/react';
|
|
182
|
+
*
|
|
183
|
+
* const ref = useMemo(() => doc(client.db, 'profiles', uid), [uid]);
|
|
184
|
+
* const { data, isLoading } = useDoc<Profile>(ref);
|
|
185
|
+
* ```
|
|
186
|
+
*
|
|
187
|
+
* Seguro em React 18/19 StrictMode.
|
|
188
|
+
*/
|
|
189
|
+
declare function useDoc<T = Record<string, unknown>>(ref: DocumentReference<T> | null | undefined, options?: UseDataOptions): UseDocResult<T>;
|
|
190
|
+
/**
|
|
191
|
+
* Fonte de auth aceita por `useUser`: o `NeetruClient` inteiro (resolve
|
|
192
|
+
* `client.auth`) ou o `AuthNamespace` direto.
|
|
193
|
+
*/
|
|
194
|
+
type UseUserInput = NeetruClient | {
|
|
195
|
+
auth: AuthNamespace;
|
|
196
|
+
} | AuthNamespace;
|
|
197
|
+
/** Resultado de `useUser`. */
|
|
198
|
+
interface UseUserResult {
|
|
199
|
+
/** Usuário autenticado ou `null`. */
|
|
200
|
+
user: NeetruUser | null;
|
|
201
|
+
/** `true` enquanto o estado de auth inicial não foi resolvido. */
|
|
202
|
+
isLoading: boolean;
|
|
203
|
+
/** Erro na resolução de auth (raro — listener nunca deveria lançar). */
|
|
204
|
+
error: Error | null;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Hook de estado de autenticação.
|
|
208
|
+
*
|
|
209
|
+
* Subscreve a `client.auth.onAuthStateChanged` e devolve `{ user, isLoading,
|
|
210
|
+
* error }`. Inclui um **timeout de seed** (default 8s): se o estado de auth não
|
|
211
|
+
* resolver nesse prazo, `isLoading` cai para `false` com `user: null` — evita
|
|
212
|
+
* spinners infinitos quando o provedor de auth nunca dispara o callback.
|
|
213
|
+
*
|
|
214
|
+
* ```tsx
|
|
215
|
+
* import { useUser } from '@neetru/sdk/react';
|
|
216
|
+
* const { user, isLoading } = useUser(client);
|
|
217
|
+
* if (isLoading) return <Spinner />;
|
|
218
|
+
* if (!user) return <SignInButton />;
|
|
219
|
+
* return <Avatar user={user} />;
|
|
220
|
+
* ```
|
|
221
|
+
*
|
|
222
|
+
* Aceita o `NeetruClient`, `{ auth }` ou o `AuthNamespace` direto.
|
|
223
|
+
* Seguro em React 18/19 StrictMode.
|
|
224
|
+
*/
|
|
225
|
+
declare function useUser(input: UseUserInput, options?: {
|
|
226
|
+
timeoutMs?: number;
|
|
227
|
+
}): UseUserResult;
|
|
114
228
|
|
|
115
|
-
export { CheckoutLink, type CheckoutLinkProps, EntitlementGate, type EntitlementGateMode, type EntitlementGateProps, useEntitlementContext };
|
|
229
|
+
export { CheckoutLink, type CheckoutLinkProps, EntitlementGate, type EntitlementGateMode, type EntitlementGateProps, type UseCollectionResult, type UseDataOptions, type UseDocResult, type UseUserInput, type UseUserResult, useCollection, useDoc, useEntitlementContext, useUser };
|
package/dist/react.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import './
|
|
2
|
+
import { z as NeetruClient, g as CheckoutStartResult, A as AuthNamespace, T as NeetruUser } from './types-BRv8wBxX.js';
|
|
3
|
+
import { CollectionReference, Query, DocumentReference } from './firestore-compat.js';
|
|
4
4
|
import 'drizzle-orm/node-postgres';
|
|
5
5
|
import './errors.js';
|
|
6
6
|
|
|
@@ -111,5 +111,119 @@ interface EntitlementGateProps {
|
|
|
111
111
|
* deve preferir `mode='readonly'` pra preservar engajamento (não hard-block).
|
|
112
112
|
*/
|
|
113
113
|
declare function EntitlementGate({ client, feature, productSlug, mode, fallback, loading, children, }: EntitlementGateProps): React.ReactElement;
|
|
114
|
+
/** Resultado de `useCollection`. */
|
|
115
|
+
interface UseCollectionResult<T> {
|
|
116
|
+
/** Documentos (cada um com `id` embutido) ou `null` enquanto carrega/sem ref. */
|
|
117
|
+
data: Array<T & {
|
|
118
|
+
id: string;
|
|
119
|
+
}> | null;
|
|
120
|
+
/** `true` enquanto o primeiro snapshot não chegou. */
|
|
121
|
+
isLoading: boolean;
|
|
122
|
+
/** Erro do último refresh (dados exibidos podem estar defasados). */
|
|
123
|
+
error: Error | null;
|
|
124
|
+
/** `true` quando um refresh falhou ou o snapshot veio do cache/offline. */
|
|
125
|
+
isStale: boolean;
|
|
126
|
+
/** epoch ms do último snapshot bem-sucedido, ou `null`. */
|
|
127
|
+
lastUpdated: number | null;
|
|
128
|
+
}
|
|
129
|
+
/** Resultado de `useDoc`. */
|
|
130
|
+
interface UseDocResult<T> {
|
|
131
|
+
data: (T & {
|
|
132
|
+
id: string;
|
|
133
|
+
}) | null;
|
|
134
|
+
isLoading: boolean;
|
|
135
|
+
error: Error | null;
|
|
136
|
+
isStale: boolean;
|
|
137
|
+
lastUpdated: number | null;
|
|
138
|
+
}
|
|
139
|
+
/** Opções comuns dos hooks de dados. */
|
|
140
|
+
interface UseDataOptions {
|
|
141
|
+
/**
|
|
142
|
+
* Intervalo de polling de fallback em ms (quando o transporte não oferece
|
|
143
|
+
* realtime nativo). Default 5000. O polling pausa quando a aba está oculta.
|
|
144
|
+
*/
|
|
145
|
+
intervalMs?: number;
|
|
146
|
+
/**
|
|
147
|
+
* Força polling mesmo quando o `onSnapshot` nativo está disponível. Útil para
|
|
148
|
+
* transportes REST onde `onSnapshot` só entrega o snapshot inicial.
|
|
149
|
+
* Default: `false` (usa realtime quando o transporte oferece).
|
|
150
|
+
*/
|
|
151
|
+
poll?: boolean;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Hook de tempo real para uma coleção (ou subconjunto filtrado via `query`).
|
|
155
|
+
*
|
|
156
|
+
* Aceita uma `CollectionReference` ou `Query` do `@neetru/sdk/firestore-compat`.
|
|
157
|
+
* Subscreve ao `onSnapshot` nativo do `client.db`; quando o transporte é REST
|
|
158
|
+
* (sem realtime nativo), faz polling visibility-aware no `intervalMs`.
|
|
159
|
+
*
|
|
160
|
+
* ```tsx
|
|
161
|
+
* import { collection, query, where } from '@neetru/sdk/firestore-compat';
|
|
162
|
+
* import { useCollection } from '@neetru/sdk/react';
|
|
163
|
+
*
|
|
164
|
+
* const ref = useMemo(
|
|
165
|
+
* () => query(collection(client.db, 'orders'), where('status', '==', 'open')),
|
|
166
|
+
* [],
|
|
167
|
+
* );
|
|
168
|
+
* const { data, isLoading, error, isStale } = useCollection<Order>(ref);
|
|
169
|
+
* ```
|
|
170
|
+
*
|
|
171
|
+
* Seguro em React 18/19 StrictMode.
|
|
172
|
+
*/
|
|
173
|
+
declare function useCollection<T = Record<string, unknown>>(source: CollectionReference<T> | Query<T> | null | undefined, options?: UseDataOptions): UseCollectionResult<T>;
|
|
174
|
+
/**
|
|
175
|
+
* Hook de tempo real para um documento.
|
|
176
|
+
*
|
|
177
|
+
* Aceita uma `DocumentReference` do `@neetru/sdk/firestore-compat`.
|
|
178
|
+
*
|
|
179
|
+
* ```tsx
|
|
180
|
+
* import { doc } from '@neetru/sdk/firestore-compat';
|
|
181
|
+
* import { useDoc } from '@neetru/sdk/react';
|
|
182
|
+
*
|
|
183
|
+
* const ref = useMemo(() => doc(client.db, 'profiles', uid), [uid]);
|
|
184
|
+
* const { data, isLoading } = useDoc<Profile>(ref);
|
|
185
|
+
* ```
|
|
186
|
+
*
|
|
187
|
+
* Seguro em React 18/19 StrictMode.
|
|
188
|
+
*/
|
|
189
|
+
declare function useDoc<T = Record<string, unknown>>(ref: DocumentReference<T> | null | undefined, options?: UseDataOptions): UseDocResult<T>;
|
|
190
|
+
/**
|
|
191
|
+
* Fonte de auth aceita por `useUser`: o `NeetruClient` inteiro (resolve
|
|
192
|
+
* `client.auth`) ou o `AuthNamespace` direto.
|
|
193
|
+
*/
|
|
194
|
+
type UseUserInput = NeetruClient | {
|
|
195
|
+
auth: AuthNamespace;
|
|
196
|
+
} | AuthNamespace;
|
|
197
|
+
/** Resultado de `useUser`. */
|
|
198
|
+
interface UseUserResult {
|
|
199
|
+
/** Usuário autenticado ou `null`. */
|
|
200
|
+
user: NeetruUser | null;
|
|
201
|
+
/** `true` enquanto o estado de auth inicial não foi resolvido. */
|
|
202
|
+
isLoading: boolean;
|
|
203
|
+
/** Erro na resolução de auth (raro — listener nunca deveria lançar). */
|
|
204
|
+
error: Error | null;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Hook de estado de autenticação.
|
|
208
|
+
*
|
|
209
|
+
* Subscreve a `client.auth.onAuthStateChanged` e devolve `{ user, isLoading,
|
|
210
|
+
* error }`. Inclui um **timeout de seed** (default 8s): se o estado de auth não
|
|
211
|
+
* resolver nesse prazo, `isLoading` cai para `false` com `user: null` — evita
|
|
212
|
+
* spinners infinitos quando o provedor de auth nunca dispara o callback.
|
|
213
|
+
*
|
|
214
|
+
* ```tsx
|
|
215
|
+
* import { useUser } from '@neetru/sdk/react';
|
|
216
|
+
* const { user, isLoading } = useUser(client);
|
|
217
|
+
* if (isLoading) return <Spinner />;
|
|
218
|
+
* if (!user) return <SignInButton />;
|
|
219
|
+
* return <Avatar user={user} />;
|
|
220
|
+
* ```
|
|
221
|
+
*
|
|
222
|
+
* Aceita o `NeetruClient`, `{ auth }` ou o `AuthNamespace` direto.
|
|
223
|
+
* Seguro em React 18/19 StrictMode.
|
|
224
|
+
*/
|
|
225
|
+
declare function useUser(input: UseUserInput, options?: {
|
|
226
|
+
timeoutMs?: number;
|
|
227
|
+
}): UseUserResult;
|
|
114
228
|
|
|
115
|
-
export { CheckoutLink, type CheckoutLinkProps, EntitlementGate, type EntitlementGateMode, type EntitlementGateProps, useEntitlementContext };
|
|
229
|
+
export { CheckoutLink, type CheckoutLinkProps, EntitlementGate, type EntitlementGateMode, type EntitlementGateProps, type UseCollectionResult, type UseDataOptions, type UseDocResult, type UseUserInput, type UseUserResult, useCollection, useDoc, useEntitlementContext, useUser };
|
package/dist/react.mjs
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
|
|
3
|
+
// src/react.ts
|
|
4
|
+
|
|
5
|
+
// src/firestore-compat.ts
|
|
6
|
+
function toDbQuery(constraints) {
|
|
7
|
+
const whereFilters = [];
|
|
8
|
+
let orderByClause;
|
|
9
|
+
let limitValue;
|
|
10
|
+
for (const c of constraints) {
|
|
11
|
+
if (c.__constraint === "where") {
|
|
12
|
+
const w = c;
|
|
13
|
+
whereFilters.push({ field: w.field, op: normalizeOp(w.op), value: w.value });
|
|
14
|
+
} else if (c.__constraint === "orderBy") {
|
|
15
|
+
const o = c;
|
|
16
|
+
orderByClause = { field: o.field, direction: o.direction };
|
|
17
|
+
} else if (c.__constraint === "limit") {
|
|
18
|
+
limitValue = c.value;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const out = {};
|
|
22
|
+
if (whereFilters.length > 0) out.where = whereFilters;
|
|
23
|
+
if (orderByClause) out.orderBy = orderByClause;
|
|
24
|
+
if (limitValue !== void 0) out.limit = limitValue;
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
function normalizeOp(op) {
|
|
28
|
+
if (op === "array-contains-any") return "array-contains";
|
|
29
|
+
return op;
|
|
30
|
+
}
|
|
31
|
+
|
|
3
32
|
// src/react.ts
|
|
4
33
|
function CheckoutLink({
|
|
5
34
|
client,
|
|
@@ -132,7 +161,242 @@ function EntitlementGate({
|
|
|
132
161
|
children
|
|
133
162
|
);
|
|
134
163
|
}
|
|
164
|
+
var DEFAULT_INTERVAL_MS = 5e3;
|
|
165
|
+
function isDocumentHidden() {
|
|
166
|
+
return typeof document !== "undefined" && document.hidden === true;
|
|
167
|
+
}
|
|
168
|
+
function serializeSource(source) {
|
|
169
|
+
if (!source) return null;
|
|
170
|
+
if (source.__kind === "collection") {
|
|
171
|
+
return `collection:${source.id}`;
|
|
172
|
+
}
|
|
173
|
+
const dbQuery = toDbQuery(source.constraints);
|
|
174
|
+
return `query:${source.collection}:${JSON.stringify(dbQuery)}`;
|
|
175
|
+
}
|
|
176
|
+
function rowsFromList(snap) {
|
|
177
|
+
return snap.docs.map((d) => {
|
|
178
|
+
const data = d.data;
|
|
179
|
+
const { id: _omit, ...rest } = data;
|
|
180
|
+
return { ...rest, id: d.id };
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
function rowFromGet(snap, id) {
|
|
184
|
+
const first = snap?.docs[0];
|
|
185
|
+
if (!first) return null;
|
|
186
|
+
const data = first.data;
|
|
187
|
+
const { id: _omit, ...rest } = data;
|
|
188
|
+
return { ...rest, id };
|
|
189
|
+
}
|
|
190
|
+
function useCollection(source, options) {
|
|
191
|
+
const intervalMs = options?.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
192
|
+
const poll = options?.poll ?? false;
|
|
193
|
+
const sourceKey = serializeSource(
|
|
194
|
+
source
|
|
195
|
+
);
|
|
196
|
+
const [state, setState] = React.useState({
|
|
197
|
+
data: null,
|
|
198
|
+
isLoading: sourceKey !== null,
|
|
199
|
+
error: null,
|
|
200
|
+
isStale: false,
|
|
201
|
+
lastUpdated: null
|
|
202
|
+
});
|
|
203
|
+
const mountedRef = React.useRef(true);
|
|
204
|
+
const sourceRef = React.useRef(source);
|
|
205
|
+
sourceRef.current = source;
|
|
206
|
+
React.useEffect(() => {
|
|
207
|
+
mountedRef.current = true;
|
|
208
|
+
const current = sourceRef.current;
|
|
209
|
+
if (!current || sourceKey === null) {
|
|
210
|
+
setState({ data: null, isLoading: false, error: null, isStale: false, lastUpdated: null });
|
|
211
|
+
return () => {
|
|
212
|
+
mountedRef.current = false;
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
setState((prev) => ({ ...prev, isLoading: prev.data === null }));
|
|
216
|
+
const collectionName = current.__kind === "query" ? current.collection : current.id;
|
|
217
|
+
const dbQuery = current.__kind === "query" ? toDbQuery(current.constraints) : void 0;
|
|
218
|
+
const col = current.db.collection(collectionName);
|
|
219
|
+
const applySnap = (snap) => {
|
|
220
|
+
if (!mountedRef.current) return;
|
|
221
|
+
setState({
|
|
222
|
+
data: rowsFromList(snap),
|
|
223
|
+
isLoading: false,
|
|
224
|
+
error: null,
|
|
225
|
+
isStale: snap.stale === true,
|
|
226
|
+
lastUpdated: Date.now()
|
|
227
|
+
});
|
|
228
|
+
};
|
|
229
|
+
const applyError = (e) => {
|
|
230
|
+
if (!mountedRef.current) return;
|
|
231
|
+
setState((prev) => ({
|
|
232
|
+
...prev,
|
|
233
|
+
// Mantém o último data bom — não limpa em erro de refresh.
|
|
234
|
+
isLoading: false,
|
|
235
|
+
error: e instanceof Error ? e : new Error(String(e)),
|
|
236
|
+
isStale: true
|
|
237
|
+
}));
|
|
238
|
+
};
|
|
239
|
+
if (poll) {
|
|
240
|
+
let cancelled = false;
|
|
241
|
+
const fetchOnce = async () => {
|
|
242
|
+
if (cancelled || isDocumentHidden()) return;
|
|
243
|
+
try {
|
|
244
|
+
const snap = await col.list(dbQuery);
|
|
245
|
+
if (!cancelled) applySnap(snap);
|
|
246
|
+
} catch (e) {
|
|
247
|
+
if (!cancelled) applyError(e);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
void fetchOnce();
|
|
251
|
+
const timer = setInterval(fetchOnce, intervalMs);
|
|
252
|
+
return () => {
|
|
253
|
+
cancelled = true;
|
|
254
|
+
clearInterval(timer);
|
|
255
|
+
mountedRef.current = false;
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
let unsub;
|
|
259
|
+
try {
|
|
260
|
+
unsub = col.onSnapshot(dbQuery, applySnap);
|
|
261
|
+
} catch (e) {
|
|
262
|
+
applyError(e);
|
|
263
|
+
}
|
|
264
|
+
return () => {
|
|
265
|
+
mountedRef.current = false;
|
|
266
|
+
unsub?.();
|
|
267
|
+
};
|
|
268
|
+
}, [sourceKey, intervalMs, poll]);
|
|
269
|
+
return state;
|
|
270
|
+
}
|
|
271
|
+
function useDoc(ref, options) {
|
|
272
|
+
const intervalMs = options?.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
273
|
+
const poll = options?.poll ?? false;
|
|
274
|
+
const refKey = ref && ref.id ? `${ref.collection}/${ref.id}` : null;
|
|
275
|
+
const [state, setState] = React.useState({
|
|
276
|
+
data: null,
|
|
277
|
+
isLoading: refKey !== null,
|
|
278
|
+
error: null,
|
|
279
|
+
isStale: false,
|
|
280
|
+
lastUpdated: null
|
|
281
|
+
});
|
|
282
|
+
const mountedRef = React.useRef(true);
|
|
283
|
+
const refRef = React.useRef(ref);
|
|
284
|
+
refRef.current = ref;
|
|
285
|
+
React.useEffect(() => {
|
|
286
|
+
mountedRef.current = true;
|
|
287
|
+
const current = refRef.current;
|
|
288
|
+
if (!current || refKey === null) {
|
|
289
|
+
setState({ data: null, isLoading: false, error: null, isStale: false, lastUpdated: null });
|
|
290
|
+
return () => {
|
|
291
|
+
mountedRef.current = false;
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
setState((prev) => ({ ...prev, isLoading: prev.data === null }));
|
|
295
|
+
const docRef = current.db.collection(current.collection).doc(current.id);
|
|
296
|
+
const id = current.id;
|
|
297
|
+
const applySnap = (snap) => {
|
|
298
|
+
if (!mountedRef.current) return;
|
|
299
|
+
setState({
|
|
300
|
+
data: rowFromGet(snap, id),
|
|
301
|
+
isLoading: false,
|
|
302
|
+
error: null,
|
|
303
|
+
isStale: snap?.stale === true,
|
|
304
|
+
lastUpdated: Date.now()
|
|
305
|
+
});
|
|
306
|
+
};
|
|
307
|
+
const applyError = (e) => {
|
|
308
|
+
if (!mountedRef.current) return;
|
|
309
|
+
setState((prev) => ({
|
|
310
|
+
...prev,
|
|
311
|
+
isLoading: false,
|
|
312
|
+
error: e instanceof Error ? e : new Error(String(e)),
|
|
313
|
+
isStale: true
|
|
314
|
+
}));
|
|
315
|
+
};
|
|
316
|
+
if (poll) {
|
|
317
|
+
let cancelled = false;
|
|
318
|
+
const fetchOnce = async () => {
|
|
319
|
+
if (cancelled || isDocumentHidden()) return;
|
|
320
|
+
try {
|
|
321
|
+
const snap = await docRef.get();
|
|
322
|
+
if (!cancelled) applySnap(snap);
|
|
323
|
+
} catch (e) {
|
|
324
|
+
if (!cancelled) applyError(e);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
void fetchOnce();
|
|
328
|
+
const timer = setInterval(fetchOnce, intervalMs);
|
|
329
|
+
return () => {
|
|
330
|
+
cancelled = true;
|
|
331
|
+
clearInterval(timer);
|
|
332
|
+
mountedRef.current = false;
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
let unsub;
|
|
336
|
+
try {
|
|
337
|
+
unsub = docRef.onSnapshot(applySnap);
|
|
338
|
+
} catch (e) {
|
|
339
|
+
applyError(e);
|
|
340
|
+
}
|
|
341
|
+
return () => {
|
|
342
|
+
mountedRef.current = false;
|
|
343
|
+
unsub?.();
|
|
344
|
+
};
|
|
345
|
+
}, [refKey, intervalMs, poll]);
|
|
346
|
+
return state;
|
|
347
|
+
}
|
|
348
|
+
function resolveAuth(input) {
|
|
349
|
+
const maybe = input;
|
|
350
|
+
if (maybe.auth && typeof maybe.auth.onAuthStateChanged === "function") {
|
|
351
|
+
return maybe.auth;
|
|
352
|
+
}
|
|
353
|
+
return input;
|
|
354
|
+
}
|
|
355
|
+
function useUser(input, options) {
|
|
356
|
+
const timeoutMs = options?.timeoutMs ?? 8e3;
|
|
357
|
+
const auth = resolveAuth(input);
|
|
358
|
+
const [state, setState] = React.useState(() => {
|
|
359
|
+
let seeded = null;
|
|
360
|
+
try {
|
|
361
|
+
seeded = auth.getUser();
|
|
362
|
+
} catch {
|
|
363
|
+
seeded = null;
|
|
364
|
+
}
|
|
365
|
+
return { user: seeded, isLoading: seeded === null, error: null };
|
|
366
|
+
});
|
|
367
|
+
const mountedRef = React.useRef(true);
|
|
368
|
+
React.useEffect(() => {
|
|
369
|
+
mountedRef.current = true;
|
|
370
|
+
let settled = false;
|
|
371
|
+
const timer = setTimeout(() => {
|
|
372
|
+
if (!mountedRef.current || settled) return;
|
|
373
|
+
setState((prev) => prev.isLoading ? { ...prev, isLoading: false } : prev);
|
|
374
|
+
}, timeoutMs);
|
|
375
|
+
let unsub;
|
|
376
|
+
try {
|
|
377
|
+
unsub = auth.onAuthStateChanged((user) => {
|
|
378
|
+
if (!mountedRef.current) return;
|
|
379
|
+
settled = true;
|
|
380
|
+
setState({ user: user ?? null, isLoading: false, error: null });
|
|
381
|
+
});
|
|
382
|
+
} catch (e) {
|
|
383
|
+
if (mountedRef.current) {
|
|
384
|
+
setState({
|
|
385
|
+
user: null,
|
|
386
|
+
isLoading: false,
|
|
387
|
+
error: e instanceof Error ? e : new Error(String(e))
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return () => {
|
|
392
|
+
mountedRef.current = false;
|
|
393
|
+
clearTimeout(timer);
|
|
394
|
+
unsub?.();
|
|
395
|
+
};
|
|
396
|
+
}, [auth, timeoutMs]);
|
|
397
|
+
return state;
|
|
398
|
+
}
|
|
135
399
|
|
|
136
|
-
export { CheckoutLink, EntitlementGate, useEntitlementContext };
|
|
400
|
+
export { CheckoutLink, EntitlementGate, useCollection, useDoc, useEntitlementContext, useUser };
|
|
137
401
|
//# sourceMappingURL=react.mjs.map
|
|
138
402
|
//# sourceMappingURL=react.mjs.map
|