@neetru/sdk 1.1.1 → 2.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 +284 -214
- package/README.md +194 -218
- package/dist/auth.cjs +4181 -346
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +5 -1
- package/dist/auth.d.ts +5 -1
- package/dist/auth.mjs +4181 -346
- package/dist/auth.mjs.map +1 -1
- package/dist/catalog.cjs +63 -24
- package/dist/catalog.cjs.map +1 -1
- package/dist/catalog.d.cts +6 -2
- package/dist/catalog.d.ts +6 -2
- package/dist/catalog.mjs +63 -24
- package/dist/catalog.mjs.map +1 -1
- package/dist/checkout.cjs +60 -18
- package/dist/checkout.cjs.map +1 -1
- package/dist/checkout.d.cts +5 -1
- package/dist/checkout.d.ts +5 -1
- package/dist/checkout.mjs +60 -18
- package/dist/checkout.mjs.map +1 -1
- package/dist/collection-ref-BBvTTXoG.d.cts +423 -0
- package/dist/collection-ref-BBvTTXoG.d.ts +423 -0
- package/dist/db-react.cjs +136 -0
- package/dist/db-react.cjs.map +1 -0
- package/dist/db-react.d.cts +99 -0
- package/dist/db-react.d.ts +99 -0
- package/dist/db-react.mjs +112 -0
- package/dist/db-react.mjs.map +1 -0
- package/dist/db.cjs +3652 -143
- package/dist/db.cjs.map +1 -1
- package/dist/db.d.cts +5 -8
- package/dist/db.d.ts +5 -8
- package/dist/db.mjs +3649 -143
- package/dist/db.mjs.map +1 -1
- package/dist/entitlements.cjs +101 -24
- package/dist/entitlements.cjs.map +1 -1
- package/dist/entitlements.d.cts +15 -5
- package/dist/entitlements.d.ts +15 -5
- package/dist/entitlements.mjs +101 -24
- package/dist/entitlements.mjs.map +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.mjs.map +1 -1
- package/dist/index.cjs +4341 -282
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -6
- package/dist/index.d.ts +13 -6
- package/dist/index.mjs +4243 -189
- package/dist/index.mjs.map +1 -1
- package/dist/mocks.cjs +186 -9
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.cts +21 -6
- package/dist/mocks.d.ts +21 -6
- package/dist/mocks.mjs +186 -9
- package/dist/mocks.mjs.map +1 -1
- package/dist/notifications.cjs +296 -0
- package/dist/notifications.cjs.map +1 -0
- package/dist/notifications.d.cts +5 -0
- package/dist/notifications.d.ts +5 -0
- package/dist/notifications.mjs +293 -0
- package/dist/notifications.mjs.map +1 -0
- package/dist/react.cjs +7 -3
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +5 -1
- package/dist/react.d.ts +5 -1
- package/dist/react.mjs +7 -3
- package/dist/react.mjs.map +1 -1
- package/dist/support.cjs +60 -18
- package/dist/support.cjs.map +1 -1
- package/dist/support.d.cts +5 -1
- package/dist/support.d.ts +5 -1
- package/dist/support.mjs +60 -18
- package/dist/support.mjs.map +1 -1
- package/dist/telemetry.cjs +130 -19
- package/dist/telemetry.cjs.map +1 -1
- package/dist/telemetry.d.cts +21 -1
- package/dist/telemetry.d.ts +21 -1
- package/dist/telemetry.mjs +130 -19
- package/dist/telemetry.mjs.map +1 -1
- package/dist/types-B1jylbMC.d.ts +1364 -0
- package/dist/types-Kmt4y1FQ.d.cts +1364 -0
- package/dist/usage.cjs +60 -18
- package/dist/usage.cjs.map +1 -1
- package/dist/usage.d.cts +5 -1
- package/dist/usage.d.ts +5 -1
- package/dist/usage.mjs +60 -18
- package/dist/usage.mjs.map +1 -1
- package/dist/webhooks.cjs +316 -0
- package/dist/webhooks.cjs.map +1 -0
- package/dist/webhooks.d.cts +5 -0
- package/dist/webhooks.d.ts +5 -0
- package/dist/webhooks.mjs +312 -0
- package/dist/webhooks.mjs.map +1 -0
- package/package.json +133 -101
- package/dist/types-BA53dd8S.d.cts +0 -490
- package/dist/types-BA53dd8S.d.ts +0 -490
|
@@ -0,0 +1,1364 @@
|
|
|
1
|
+
import { c as DbCollectionRef$1, S as SyncState, U as Unsubscribe, C as ConflictRecord, R as RealtimeTransport } from './collection-ref-BBvTTXoG.cjs';
|
|
2
|
+
import { DbQuery, DeltaFrame } from '@neetru/realtime-protocol';
|
|
3
|
+
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
|
4
|
+
import { NeetruError } from './errors.cjs';
|
|
5
|
+
|
|
6
|
+
/** Tipo do tenant alvo da subscription (PF=uid, PJ=orgId). */
|
|
7
|
+
type CheckoutTenantType = 'pf' | 'pj';
|
|
8
|
+
/** Status do `checkout_intents/{intentId}` no Core. */
|
|
9
|
+
type CheckoutIntentStatus = 'pending' | 'kyc_in_progress' | 'stripe_redirected' | 'completed' | 'expired' | 'cancelled';
|
|
10
|
+
interface CheckoutStartInput {
|
|
11
|
+
/** Slug do produto no catálogo (ex: `neetru-pulse`). */
|
|
12
|
+
productId: string;
|
|
13
|
+
/** Slug do plano no catálogo do produto (ex: `pro_monthly`). */
|
|
14
|
+
planId: string;
|
|
15
|
+
/** Pra onde o produto SaaS quer voltar pós-checkout. https obrigatório (ou http://localhost em dev). */
|
|
16
|
+
callbackUrl: string;
|
|
17
|
+
/** Override pra checkout em nome de uma org. Default: PF do uid logado. */
|
|
18
|
+
tenantType?: CheckoutTenantType;
|
|
19
|
+
/** Quando `tenantType='pj'`, id da org. Ignorado quando PF. */
|
|
20
|
+
tenantId?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Em browsers, default `true` — SDK faz `window.location.href = redirectUrl`.
|
|
23
|
+
* Passe `false` se quiser controlar o redirect manualmente.
|
|
24
|
+
*/
|
|
25
|
+
autoRedirect?: boolean;
|
|
26
|
+
}
|
|
27
|
+
interface CheckoutStartResult {
|
|
28
|
+
intentId: string;
|
|
29
|
+
redirectUrl: string;
|
|
30
|
+
status: CheckoutIntentStatus;
|
|
31
|
+
expiresAt: string;
|
|
32
|
+
/** True se KYC ainda precisa ser coletado no portal. UI pode mostrar hint. */
|
|
33
|
+
requiresKyc: boolean;
|
|
34
|
+
}
|
|
35
|
+
interface CheckoutIntentInfo {
|
|
36
|
+
intentId: string;
|
|
37
|
+
uid: string;
|
|
38
|
+
targetTenantId: string;
|
|
39
|
+
targetTenantType: CheckoutTenantType;
|
|
40
|
+
productId: string;
|
|
41
|
+
planId: string;
|
|
42
|
+
callbackUrl: string;
|
|
43
|
+
status: CheckoutIntentStatus;
|
|
44
|
+
/** Stripe Checkout Session id quando avançou pra `stripe_redirected`. */
|
|
45
|
+
stripeSessionId?: string | null;
|
|
46
|
+
expiresAt: string;
|
|
47
|
+
/** True se já passou `expiresAt` mas Core ainda não marcou expired. */
|
|
48
|
+
isStale?: boolean;
|
|
49
|
+
}
|
|
50
|
+
interface CheckoutNamespace {
|
|
51
|
+
/**
|
|
52
|
+
* Inicia checkout. Em browser, redireciona automaticamente pro portal.
|
|
53
|
+
* Em Node/SSR, retorna o resultado sem efeitos colaterais — caller decide
|
|
54
|
+
* o que fazer com `redirectUrl`.
|
|
55
|
+
*
|
|
56
|
+
* Dev mode (`NEETRU_ENV=dev`): retorna URL fake `https://localhost:9003/portal/checkout/mock-XXXX`.
|
|
57
|
+
*/
|
|
58
|
+
start(input: CheckoutStartInput): Promise<CheckoutStartResult>;
|
|
59
|
+
/** Lê estado atual do intent (Core). */
|
|
60
|
+
get(intentId: string): Promise<CheckoutIntentInfo>;
|
|
61
|
+
/** Cancela um intent que ainda não virou `stripe_redirected`. */
|
|
62
|
+
cancel(intentId: string): Promise<{
|
|
63
|
+
ok: true;
|
|
64
|
+
alreadyCancelled: boolean;
|
|
65
|
+
}>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Mock dev — retorna URL fake `https://localhost:9003/portal/checkout/mock-XXXX`
|
|
69
|
+
* sem tocar no Core. Útil pra dev externo SaaS testar UI/redirect sem precisar
|
|
70
|
+
* de Bearer token Neetru.
|
|
71
|
+
*
|
|
72
|
+
* Dev mode persiste intents em mapa in-memory pro `get`/`cancel` retornarem
|
|
73
|
+
* resultado consistente.
|
|
74
|
+
*/
|
|
75
|
+
declare class MockCheckout implements CheckoutNamespace {
|
|
76
|
+
private readonly intents;
|
|
77
|
+
start(input: CheckoutStartInput): Promise<CheckoutStartResult>;
|
|
78
|
+
get(intentId: string): Promise<CheckoutIntentInfo>;
|
|
79
|
+
cancel(intentId: string): Promise<{
|
|
80
|
+
ok: true;
|
|
81
|
+
alreadyCancelled: boolean;
|
|
82
|
+
}>;
|
|
83
|
+
}
|
|
84
|
+
declare function createCheckoutNamespace(config: ResolvedConfig): CheckoutNamespace;
|
|
85
|
+
|
|
86
|
+
type ProductNotificationSeverity = 'info' | 'success' | 'warning' | 'error';
|
|
87
|
+
interface ProductNotification {
|
|
88
|
+
id: string;
|
|
89
|
+
/** UID do usuário final (do produto, não staff Neetru). */
|
|
90
|
+
userId: string;
|
|
91
|
+
/** Identificador semântico do evento (ex: `order.received`). */
|
|
92
|
+
kind: string;
|
|
93
|
+
severity: ProductNotificationSeverity;
|
|
94
|
+
title: string;
|
|
95
|
+
body?: string;
|
|
96
|
+
/** Link opcional pra ação. */
|
|
97
|
+
link?: string;
|
|
98
|
+
/** Metadata livre (serializável). */
|
|
99
|
+
metadata?: Record<string, string | number | boolean | null>;
|
|
100
|
+
createdAt: string;
|
|
101
|
+
/** ISO quando user marcou lida. */
|
|
102
|
+
readAt?: string;
|
|
103
|
+
/** ISO quando user descartou. */
|
|
104
|
+
dismissedAt?: string;
|
|
105
|
+
}
|
|
106
|
+
interface SendNotificationInput {
|
|
107
|
+
userId: string;
|
|
108
|
+
kind: string;
|
|
109
|
+
title: string;
|
|
110
|
+
severity?: ProductNotificationSeverity;
|
|
111
|
+
body?: string;
|
|
112
|
+
link?: string;
|
|
113
|
+
metadata?: Record<string, string | number | boolean | null>;
|
|
114
|
+
/**
|
|
115
|
+
* Dedup — se já existir notificação com mesmo fingerprint dentro de 24h,
|
|
116
|
+
* NÃO cria duplicata. Útil pra evitar spam quando webhook reentrega.
|
|
117
|
+
*/
|
|
118
|
+
fingerprint?: string;
|
|
119
|
+
}
|
|
120
|
+
interface ListNotificationsOptions {
|
|
121
|
+
/** Inclui notificações dismissed (default false). */
|
|
122
|
+
includeDismissed?: boolean;
|
|
123
|
+
/** Filtro por unread. */
|
|
124
|
+
onlyUnread?: boolean;
|
|
125
|
+
/** Máx itens (default 50, máx 200). */
|
|
126
|
+
limit?: number;
|
|
127
|
+
}
|
|
128
|
+
interface NotificationsNamespace {
|
|
129
|
+
/** Produto envia uma notificação pra um usuário do produto. */
|
|
130
|
+
send(input: SendNotificationInput): Promise<ProductNotification>;
|
|
131
|
+
/** Lista notificações de um usuário. */
|
|
132
|
+
list(userId: string, options?: ListNotificationsOptions): Promise<ProductNotification[]>;
|
|
133
|
+
/** Marca como lida. */
|
|
134
|
+
markRead(id: string): Promise<{
|
|
135
|
+
ok: true;
|
|
136
|
+
}>;
|
|
137
|
+
/** Descarta. */
|
|
138
|
+
dismiss(id: string): Promise<{
|
|
139
|
+
ok: true;
|
|
140
|
+
}>;
|
|
141
|
+
}
|
|
142
|
+
declare function createNotificationsNamespace(config: ResolvedConfig): NotificationsNamespace;
|
|
143
|
+
declare class MockNotifications implements NotificationsNamespace {
|
|
144
|
+
private notifications;
|
|
145
|
+
private nextId;
|
|
146
|
+
send(input: SendNotificationInput): Promise<ProductNotification>;
|
|
147
|
+
list(userId: string, options?: ListNotificationsOptions): Promise<ProductNotification[]>;
|
|
148
|
+
markRead(id: string): Promise<{
|
|
149
|
+
ok: true;
|
|
150
|
+
}>;
|
|
151
|
+
dismiss(id: string): Promise<{
|
|
152
|
+
ok: true;
|
|
153
|
+
}>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Eventos Neetru que produtos podem assinar. Lista expansível por minor bump. */
|
|
157
|
+
type WebhookEvent = 'subscription.activated' | 'subscription.cancelled' | 'subscription.payment_failed' | 'subscription.trial_ending' | 'usage.quota_exceeded' | 'account.suspended' | 'account.reactivated' | 'support.ticket_replied';
|
|
158
|
+
interface WebhookEndpoint {
|
|
159
|
+
id: string;
|
|
160
|
+
url: string;
|
|
161
|
+
events: WebhookEvent[];
|
|
162
|
+
/** Quando true, `X-Neetru-Signature` é enviado em cada dispatch. */
|
|
163
|
+
hasSecret: boolean;
|
|
164
|
+
status: 'active' | 'degraded' | 'disabled';
|
|
165
|
+
/** ISO timestamp do último dispatch bem-sucedido. */
|
|
166
|
+
lastDeliveryAt?: string;
|
|
167
|
+
/** Quantos dispatches falharam consecutivos (resetado em sucesso). */
|
|
168
|
+
consecutiveFailures?: number;
|
|
169
|
+
createdAt: string;
|
|
170
|
+
}
|
|
171
|
+
interface RegisterWebhookInput {
|
|
172
|
+
url: string;
|
|
173
|
+
events: WebhookEvent[];
|
|
174
|
+
/**
|
|
175
|
+
* Secret pra HMAC SHA-256 (mín 16 chars). Recomendado.
|
|
176
|
+
* Quando ausente, dispatches vão sem assinatura — produto deve ter
|
|
177
|
+
* IP allowlist ou outra defesa.
|
|
178
|
+
*/
|
|
179
|
+
secret?: string;
|
|
180
|
+
}
|
|
181
|
+
interface WebhookTestResult {
|
|
182
|
+
ok: boolean;
|
|
183
|
+
statusCode?: number;
|
|
184
|
+
durationMs?: number;
|
|
185
|
+
error?: string;
|
|
186
|
+
}
|
|
187
|
+
interface WebhooksNamespace {
|
|
188
|
+
/** Registra um novo endpoint. */
|
|
189
|
+
register(input: RegisterWebhookInput): Promise<WebhookEndpoint>;
|
|
190
|
+
/** Lista endpoints registrados pelo produto. */
|
|
191
|
+
list(): Promise<WebhookEndpoint[]>;
|
|
192
|
+
/** Remove um endpoint. */
|
|
193
|
+
unregister(id: string): Promise<{
|
|
194
|
+
ok: true;
|
|
195
|
+
}>;
|
|
196
|
+
/** Dispara evento de teste pra validar conectividade. */
|
|
197
|
+
test(id: string): Promise<WebhookTestResult>;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Verifica assinatura HMAC SHA-256 de payload de webhook recebido pelo
|
|
201
|
+
* produto consumer. Constant-time compare pra resistir a timing attack.
|
|
202
|
+
*
|
|
203
|
+
* @param payload Corpo cru da request (string ou Buffer). NÃO use o objeto
|
|
204
|
+
* parseado — JSON.stringify pode diferir do que foi assinado.
|
|
205
|
+
* @param signature Header `X-Neetru-Signature` recebido (formato `sha256=<hex>`).
|
|
206
|
+
* @param secret Secret registrado em `webhooks.register({ secret })`.
|
|
207
|
+
* @returns true se assinatura confere, false caso contrário.
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```ts
|
|
211
|
+
* // Express handler
|
|
212
|
+
* app.post('/webhooks/neetru', express.raw({ type: 'application/json' }), (req, res) => {
|
|
213
|
+
* const sig = req.header('X-Neetru-Signature');
|
|
214
|
+
* if (!verifyWebhookSignature(req.body, sig, process.env.WEBHOOK_SECRET!)) {
|
|
215
|
+
* return res.status(401).end();
|
|
216
|
+
* }
|
|
217
|
+
* const event = JSON.parse(req.body.toString('utf8'));
|
|
218
|
+
* // ... handle event
|
|
219
|
+
* res.json({ ok: true });
|
|
220
|
+
* });
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
declare function verifyWebhookSignature(payload: string | Uint8Array, signature: string | null | undefined, secret: string): boolean;
|
|
224
|
+
declare function createWebhooksNamespace(config: ResolvedConfig): WebhooksNamespace;
|
|
225
|
+
declare class MockWebhooks implements WebhooksNamespace {
|
|
226
|
+
private endpoints;
|
|
227
|
+
private nextId;
|
|
228
|
+
register(input: RegisterWebhookInput): Promise<WebhookEndpoint>;
|
|
229
|
+
list(): Promise<WebhookEndpoint[]>;
|
|
230
|
+
unregister(id: string): Promise<{
|
|
231
|
+
ok: true;
|
|
232
|
+
}>;
|
|
233
|
+
test(id: string): Promise<WebhookTestResult>;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* NeetruDbError — classe de erro especializada para o módulo `db`.
|
|
238
|
+
*
|
|
239
|
+
* Conforma com 02-sdk.md §3.5: `NeetruDbError extends NeetruError`,
|
|
240
|
+
* campo `code` fechado em `NeetruDbErrorCode`, campo `retryable` booleano,
|
|
241
|
+
* campo `dbId` opaco para correlação.
|
|
242
|
+
*
|
|
243
|
+
* Herda de `NeetruError` para que um `catch (e) { if (e instanceof NeetruError) }`
|
|
244
|
+
* genérico continue pegando erros de DB. Quem quer granularidade usa
|
|
245
|
+
* `instanceof NeetruDbError` e o `code` fechado.
|
|
246
|
+
*/
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Códigos de erro específicos do módulo `db`.
|
|
250
|
+
*
|
|
251
|
+
* - `db_unavailable` — lifecycle: provisionando / falhou / arquivado.
|
|
252
|
+
* NÃO significa "sem rede" — offline é transparente.
|
|
253
|
+
* - `db_not_found` — documento/coleção não encontrado no servidor.
|
|
254
|
+
* - `db_conflict` — conflito de escrita detectado.
|
|
255
|
+
* - `db_quota_exceeded` — quota de escrita/leitura excedida no banco.
|
|
256
|
+
* - `db_permission_denied` — sem permissão para a operação.
|
|
257
|
+
* - `db_invalid_query` — query inválida (campo, operador, tipo).
|
|
258
|
+
* - `db_timeout` — operação excedeu o timeout.
|
|
259
|
+
* - `offline_quota_exceeded`— IndexedDB cheio; a escrita não pode ser enfileirada.
|
|
260
|
+
* - `offline_unavailable` — IndexedDB indisponível; SDK degrado para cache RAM.
|
|
261
|
+
*/
|
|
262
|
+
type NeetruDbErrorCode = 'db_unavailable' | 'db_not_found' | 'db_conflict' | 'db_quota_exceeded' | 'db_permission_denied' | 'db_invalid_query' | 'db_timeout' | 'offline_quota_exceeded' | 'offline_unavailable';
|
|
263
|
+
/**
|
|
264
|
+
* Erro tipado do módulo `db` — estende `NeetruError` para compatibilidade.
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* ```ts
|
|
268
|
+
* try {
|
|
269
|
+
* await client.db.collection('orders').add({ ... });
|
|
270
|
+
* } catch (e) {
|
|
271
|
+
* if (e instanceof NeetruDbError && e.code === 'offline_quota_exceeded') {
|
|
272
|
+
* // IndexedDB cheio — notificar usuário
|
|
273
|
+
* }
|
|
274
|
+
* }
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
277
|
+
declare class NeetruDbError extends NeetruError {
|
|
278
|
+
/** Código de erro fechado — específico de DB. */
|
|
279
|
+
readonly code: NeetruDbErrorCode;
|
|
280
|
+
/**
|
|
281
|
+
* `true` para erros transientes que o produto pode tentar novamente.
|
|
282
|
+
* São retryable: `db_unavailable`, `db_conflict`, `db_timeout`.
|
|
283
|
+
*/
|
|
284
|
+
readonly retryable: boolean;
|
|
285
|
+
/**
|
|
286
|
+
* ID opaco do banco lógico — só para correlação com logs do Core.
|
|
287
|
+
* Nunca deve ser exibido ao usuário final.
|
|
288
|
+
*/
|
|
289
|
+
readonly dbId?: string;
|
|
290
|
+
constructor(code: NeetruDbErrorCode, message: string, dbId?: string);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Tipos internos do NeetruRealtimeClient.
|
|
295
|
+
*
|
|
296
|
+
* Os tipos de wire protocol (frames, ops, query descriptor, change) sao
|
|
297
|
+
* importados de `@neetru/realtime-protocol` — fonte unica de verdade
|
|
298
|
+
* compartilhada com o gateway (@neetru/realtime-transport e
|
|
299
|
+
* @neetru/realtime-changestream). Isso garante que cliente e gateway
|
|
300
|
+
* NUNCA derivem silenciosamente.
|
|
301
|
+
*
|
|
302
|
+
* Este arquivo define apenas os tipos internos ao cliente:
|
|
303
|
+
* - ConnectionState / ConnectionStateListener : estado observavel da conexao
|
|
304
|
+
* - WebSocketLike / WebSocketFactory : injecao de dependencia pra testes
|
|
305
|
+
* - RealtimeTicket / TicketProvider : autenticacao realtime
|
|
306
|
+
* - RealtimeClientOptions : configuracao do cliente
|
|
307
|
+
* - SubscriptionCallback / SubscriptionEntry : estado interno de subscriptions
|
|
308
|
+
*
|
|
309
|
+
* Re-exports de conveniencia:
|
|
310
|
+
* - RealtimeQuery (alias de DbQuery)
|
|
311
|
+
* - OutboundFrame (alias de SubscribeFrame)
|
|
312
|
+
* - InboundFrame (alias de DeltaFrame)
|
|
313
|
+
* Os aliases preservam a API publica do cliente sem expor os nomes do gateway.
|
|
314
|
+
*/
|
|
315
|
+
|
|
316
|
+
type InboundFrame = DeltaFrame;
|
|
317
|
+
type RealtimeQuery = DbQuery;
|
|
318
|
+
/**
|
|
319
|
+
* Estado observavel da conexao WebSocket.
|
|
320
|
+
*
|
|
321
|
+
* - 'connecting' : tentando (re)conectar — socket em aberto ou backoff ativo.
|
|
322
|
+
* - 'connected' : WebSocket aberto e pronto para enviar/receber.
|
|
323
|
+
* - 'disconnected' : sem conexao ativa, sem tentativa pendente.
|
|
324
|
+
* - 'draining' : gateway enviou drain; aguardando reconexao com backoff.
|
|
325
|
+
*/
|
|
326
|
+
type ConnectionState = 'connecting' | 'connected' | 'disconnected' | 'draining';
|
|
327
|
+
/** Listener de mudanca de estado da conexao. */
|
|
328
|
+
type ConnectionStateListener = (state: ConnectionState) => void;
|
|
329
|
+
/**
|
|
330
|
+
* Callback entregue a cada `subscribe()`.
|
|
331
|
+
* Recebe o frame inbound (delta/resync/stale/error) ja roteado para esta
|
|
332
|
+
* subscription especifica.
|
|
333
|
+
*/
|
|
334
|
+
type SubscriptionCallback = (frame: InboundFrame) => void;
|
|
335
|
+
/**
|
|
336
|
+
* Subconjunto do WebSocket nativo necessario para o NeetruRealtimeClient.
|
|
337
|
+
* Injetavel em testes via FakeWebSocket.
|
|
338
|
+
*
|
|
339
|
+
* Cobre o que usamos: envio de mensagem, leitura de estado e event handlers.
|
|
340
|
+
* Nao inclui streams, arraybuffer ou blob — o protocolo so usa JSON text.
|
|
341
|
+
*/
|
|
342
|
+
interface WebSocketLike {
|
|
343
|
+
readonly readyState: number;
|
|
344
|
+
send(data: string): void;
|
|
345
|
+
close(code?: number, reason?: string): void;
|
|
346
|
+
onopen: ((event: Event) => void) | null;
|
|
347
|
+
onmessage: ((event: MessageEvent) => void) | null;
|
|
348
|
+
onclose: ((event: CloseEvent) => void) | null;
|
|
349
|
+
onerror: ((event: Event) => void) | null;
|
|
350
|
+
}
|
|
351
|
+
/** Factory que cria instancias de WebSocketLike. Injetavel nos testes. */
|
|
352
|
+
type WebSocketFactory = (url: string) => WebSocketLike;
|
|
353
|
+
/**
|
|
354
|
+
* Ticket obtido de `POST /api/sdk/v1/db/realtime/ticket` pelo Core.
|
|
355
|
+
*
|
|
356
|
+
* O `token` e embedding em `query.filter._ticket` de cada frame `subscribe`
|
|
357
|
+
* enviado ao gateway, que o valida via
|
|
358
|
+
* `POST /api/sdk/v1/db/realtime/validate`.
|
|
359
|
+
*
|
|
360
|
+
* TTL tipico: 60s. Single-use-ish (o gateway valida na primeira mensagem).
|
|
361
|
+
*/
|
|
362
|
+
interface RealtimeTicket {
|
|
363
|
+
/** ID do ticket (opaco, para correlacao no lado do servidor). */
|
|
364
|
+
ticketId: string;
|
|
365
|
+
/** Token curto que o gateway extrai e valida. */
|
|
366
|
+
token: string;
|
|
367
|
+
/** ISO timestamp de expiracao. */
|
|
368
|
+
expiresAt: string;
|
|
369
|
+
/** TTL em milissegundos (atalho — igual a `expiresAt - now`). */
|
|
370
|
+
ttlMs: number;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Provedor de ticket injetavel.
|
|
374
|
+
*
|
|
375
|
+
* - Producao: implementacao padrao que chama
|
|
376
|
+
* `POST /api/sdk/v1/db/realtime/ticket` via camada HTTP do SDK.
|
|
377
|
+
* - Testes: stub que retorna um ticket fixo (ou rejeita para simular falha).
|
|
378
|
+
*
|
|
379
|
+
* Deve retornar um ticket fresco a cada chamada — tickets tem TTL 60s e
|
|
380
|
+
* sao single-use-ish (um por conexao WS).
|
|
381
|
+
*/
|
|
382
|
+
type TicketProvider = () => Promise<RealtimeTicket>;
|
|
383
|
+
/** Configuracao do NeetruRealtimeClient. */
|
|
384
|
+
interface RealtimeClientOptions {
|
|
385
|
+
/**
|
|
386
|
+
* URL do gateway `neetru-realtime`.
|
|
387
|
+
* Ex.: `wss://realtime.neetru.com/ws`
|
|
388
|
+
*/
|
|
389
|
+
gatewayUrl: string;
|
|
390
|
+
/**
|
|
391
|
+
* Provedor de ticket de autenticacao realtime injetavel.
|
|
392
|
+
*
|
|
393
|
+
* - Chamado antes de CADA abertura de WebSocket (primeira conexao e
|
|
394
|
+
* reconexoes). Se falhar, a conexao e abortada e o backoff se aplica.
|
|
395
|
+
* - Producao: implementacao padrao usa `POST /api/sdk/v1/db/realtime/ticket`
|
|
396
|
+
* via HTTP do SDK (requer `apiKey` configurado).
|
|
397
|
+
* - Testes: stub determinístico (nao precisa de Core rodando).
|
|
398
|
+
*
|
|
399
|
+
* OBRIGATORIO: o cliente nao abre WebSocket sem ticket valido.
|
|
400
|
+
*/
|
|
401
|
+
ticketProvider: TicketProvider;
|
|
402
|
+
/**
|
|
403
|
+
* ID opaco do banco logico (BL-8 fix).
|
|
404
|
+
*
|
|
405
|
+
* Embutido em `query.filter._dbId` de cada frame `subscribe` enviado ao
|
|
406
|
+
* gateway. O gateway (`@neetru/realtime-changestream._extractDbId`) exige
|
|
407
|
+
* este campo para rotear a subscription ao banco correto — a ausencia causa
|
|
408
|
+
* rejeicao silenciosa.
|
|
409
|
+
*
|
|
410
|
+
* Opcional para compatibilidade reversa; ausente → nenhum `_dbId` no frame.
|
|
411
|
+
*/
|
|
412
|
+
dbId?: string;
|
|
413
|
+
/**
|
|
414
|
+
* Factory de WebSocket injetavel.
|
|
415
|
+
* Default: `(url) => new WebSocket(url)`.
|
|
416
|
+
* Injetar em testes para evitar rede real.
|
|
417
|
+
*/
|
|
418
|
+
webSocketFactory?: WebSocketFactory;
|
|
419
|
+
/**
|
|
420
|
+
* Injecao de `setTimeout`/`clearTimeout` para testes de backoff sem relogio
|
|
421
|
+
* real (fake timers). Default: `globalThis.setTimeout/clearTimeout`.
|
|
422
|
+
*/
|
|
423
|
+
setTimeoutFn?: typeof setTimeout;
|
|
424
|
+
clearTimeoutFn?: typeof clearTimeout;
|
|
425
|
+
/**
|
|
426
|
+
* Delay base do backoff exponencial em ms. Default: 1000.
|
|
427
|
+
* O delay efetivo no attempt N e: `min(baseMs * 2^N, maxMs) * jitter`.
|
|
428
|
+
*/
|
|
429
|
+
backoffBaseMs?: number;
|
|
430
|
+
/**
|
|
431
|
+
* Delay maximo do backoff em ms. Default: 30_000.
|
|
432
|
+
*/
|
|
433
|
+
backoffMaxMs?: number;
|
|
434
|
+
/**
|
|
435
|
+
* Intervalo de heartbeat ping/pong em ms. Default: 25_000.
|
|
436
|
+
* Usar 0 para desabilitar (util em testes).
|
|
437
|
+
*/
|
|
438
|
+
heartbeatIntervalMs?: number;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Tipos do Mundo SQL — `client.db.sql()`.
|
|
443
|
+
*
|
|
444
|
+
* Alinhado com 02-sdk.md §3.3 e 00-RELATORIO-CENTRAL.md §6.1 (peça 7+8 —
|
|
445
|
+
* `NeetruLeaseIssuer` + `SqlLeaseManager`).
|
|
446
|
+
*
|
|
447
|
+
* REGRA DURA: `DbSqlLease` nunca é logado, nunca é persistido, nunca é exposto
|
|
448
|
+
* ao produto. Vive só na RAM do processo.
|
|
449
|
+
*/
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Lease SQL de curta duração emitido pelo Core via `POST /api/sdk/v1/db/lease`.
|
|
453
|
+
*
|
|
454
|
+
* Alinhado com `DbSqlLease` do Core (`src/lib/db/lease/types.ts`).
|
|
455
|
+
*
|
|
456
|
+
* - `port` sempre 5433 — o lease aponta pro PgBouncer, nunca à porta crua.
|
|
457
|
+
* - `credentialVersion` — bump indica rotação; o `SqlLeaseManager` reabre
|
|
458
|
+
* o pool ao detectar versão diferente.
|
|
459
|
+
* - `expiresAt` — ISO string; o SDK renova aos ~80% do TTL (12 min em TTL=15 min).
|
|
460
|
+
*/
|
|
461
|
+
interface DbSqlLease {
|
|
462
|
+
leaseId: string;
|
|
463
|
+
host: string;
|
|
464
|
+
port: 5433;
|
|
465
|
+
dbName: string;
|
|
466
|
+
user: string;
|
|
467
|
+
/** Plaintext — trafega só sobre HTTPS, NUNCA logado/persistido. */
|
|
468
|
+
password: string;
|
|
469
|
+
/** CA do servidor para TLS — null até mTLS do Core (TODO P0-3). */
|
|
470
|
+
sslca: string | null;
|
|
471
|
+
/** Cert de cliente — null até mTLS do Core. */
|
|
472
|
+
clientCert: string | null;
|
|
473
|
+
/** Chave de cliente — null até mTLS do Core. */
|
|
474
|
+
clientKey: string | null;
|
|
475
|
+
/**
|
|
476
|
+
* Versão da credencial no Secret Manager. Bump = rotação de senha.
|
|
477
|
+
* O manager fecha o pool antigo e abre um novo quando esta versão muda.
|
|
478
|
+
*/
|
|
479
|
+
credentialVersion: number;
|
|
480
|
+
/** Expiração ISO — SDK renova ~2 min antes. */
|
|
481
|
+
expiresAt: string;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Handle SQL retornado por `client.db.sql(schema)`.
|
|
485
|
+
*
|
|
486
|
+
* Superfície pública exata de 02-sdk.md §3.3.
|
|
487
|
+
*
|
|
488
|
+
* @template TSchema — schema Drizzle do produto (`pgTable(...)`)
|
|
489
|
+
*/
|
|
490
|
+
interface NeetruSqlClient<TSchema extends Record<string, unknown>> {
|
|
491
|
+
/** Handle Drizzle tipado pelo schema. */
|
|
492
|
+
readonly orm: NodePgDatabase<TSchema>;
|
|
493
|
+
/**
|
|
494
|
+
* Transação ACID real com rollback automático em exceção.
|
|
495
|
+
*
|
|
496
|
+
* @param fn — callback que recebe o handle Drizzle transacional.
|
|
497
|
+
* @param opts — `isolationLevel` opcional.
|
|
498
|
+
*/
|
|
499
|
+
transaction<T>(fn: (tx: NodePgDatabase<TSchema>) => Promise<T>, opts?: {
|
|
500
|
+
isolationLevel?: 'read committed' | 'repeatable read' | 'serializable';
|
|
501
|
+
}): Promise<T>;
|
|
502
|
+
/** Drena o pool — chamar no shutdown do produto. */
|
|
503
|
+
close(): Promise<void>;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Opções do segundo argumento de `client.db.sql(schema, options?)`.
|
|
507
|
+
*
|
|
508
|
+
* Contrato C-H (00-RELATORIO-CENTRAL.md §3.8):
|
|
509
|
+
* `client.db.sql(schema, { database? })` — quando `database` é fornecido,
|
|
510
|
+
* identifica o banco SQL alvo entre N bancos do produto. Quando ausente,
|
|
511
|
+
* resolve para o banco padrão (comportamento v1.x preservado).
|
|
512
|
+
*/
|
|
513
|
+
interface SqlOptions {
|
|
514
|
+
/**
|
|
515
|
+
* ID opaco do banco SQL — emitido pelo Core em `POST /api/sdk/v1/db`.
|
|
516
|
+
*
|
|
517
|
+
* Necessário quando o produto registrou mais de um banco SQL (ex.: `caixa`
|
|
518
|
+
* + `analytics`). O valor flui para o lease request e o Core emite um
|
|
519
|
+
* `DbSqlLease` escopado a esse banco.
|
|
520
|
+
*
|
|
521
|
+
* Quando omitido, o Core resolve o banco padrão do produto (geralmente o
|
|
522
|
+
* único registrado, ou o banco homônimo do produto).
|
|
523
|
+
*/
|
|
524
|
+
database?: string;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* NeetruRealtimeClient — cliente WebSocket multiplexado do gateway (M3).
|
|
529
|
+
*
|
|
530
|
+
* Componente #18 do relatório central do Conselho Neetru Core DB.
|
|
531
|
+
* Par cliente do serviço `neetru-realtime` (componente #19,
|
|
532
|
+
* `@neetru/realtime-transport`).
|
|
533
|
+
*
|
|
534
|
+
* Responsabilidades:
|
|
535
|
+
* - Manter EXATAMENTE UMA conexão WebSocket ao gateway.
|
|
536
|
+
* - Multiplexar N subscriptions independentes sobre ela.
|
|
537
|
+
* - Reconectar com backoff exponencial + jitter em caso de queda.
|
|
538
|
+
* - Em reconexão, reenviar todos os frames `subscribe` ativos.
|
|
539
|
+
* - Responder / enviar heartbeat ping/pong por protocolo.
|
|
540
|
+
* - Rotear frames inbound para o callback correto por `subscriptionId`.
|
|
541
|
+
* - Emitir mudanças de `ConnectionState` para observadores externos.
|
|
542
|
+
*
|
|
543
|
+
* Protocolo de frames (contrato compartilhado via `@neetru/realtime-protocol`):
|
|
544
|
+
* ENVIA → `subscribe` | `unsubscribe` | `ping`
|
|
545
|
+
* RECEBE ← `delta` | `resync` | `stale` | `error` | `pong` | `drain`
|
|
546
|
+
*
|
|
547
|
+
* Deps: ZERO externas. Apenas WebSocket nativo (injetável).
|
|
548
|
+
*
|
|
549
|
+
* Nota: a integração de `NeetruRealtimeClient` na superfície pública
|
|
550
|
+
* `client.db` é um follow-up deliberado — ver seção de pendências no
|
|
551
|
+
* relatório de entrega.
|
|
552
|
+
*/
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Cliente WebSocket multiplexado do gateway `neetru-realtime`.
|
|
556
|
+
*
|
|
557
|
+
* @example
|
|
558
|
+
* ```ts
|
|
559
|
+
* const rt = new NeetruRealtimeClient({
|
|
560
|
+
* gatewayUrl: 'wss://realtime.neetru.com/ws',
|
|
561
|
+
* });
|
|
562
|
+
*
|
|
563
|
+
* const subId = rt.subscribe('orders', { filter: { status: 'open' } }, (frame) => {
|
|
564
|
+
* if (frame.op === 'delta') applyChanges(frame.changes);
|
|
565
|
+
* if (frame.op === 'resync') fullReload();
|
|
566
|
+
* });
|
|
567
|
+
*
|
|
568
|
+
* rt.onConnectionState((state) => console.log('connection:', state));
|
|
569
|
+
*
|
|
570
|
+
* // Para parar:
|
|
571
|
+
* rt.unsubscribe(subId);
|
|
572
|
+
* rt.close();
|
|
573
|
+
* ```
|
|
574
|
+
*/
|
|
575
|
+
declare class NeetruRealtimeClient {
|
|
576
|
+
private readonly _gatewayUrl;
|
|
577
|
+
private readonly _wsFactory;
|
|
578
|
+
private readonly _setTimeout;
|
|
579
|
+
private readonly _clearTimeout;
|
|
580
|
+
private readonly _backoffBaseMs;
|
|
581
|
+
private readonly _backoffMaxMs;
|
|
582
|
+
private readonly _heartbeatIntervalMs;
|
|
583
|
+
private readonly _ticketProvider;
|
|
584
|
+
/** ID do banco lógico (BL-8) — embutido em query.filter._dbId de cada subscribe. */
|
|
585
|
+
private readonly _dbId;
|
|
586
|
+
/** Ticket atual — obtido antes de cada abertura de WS e embutido nos frames. */
|
|
587
|
+
private _currentTicket;
|
|
588
|
+
/** Subscriptions ativas: id → entry */
|
|
589
|
+
private _subscriptions;
|
|
590
|
+
/** Listeners do estado da conexão. */
|
|
591
|
+
private _stateListeners;
|
|
592
|
+
/** Estado atual da conexão. */
|
|
593
|
+
private _connectionState;
|
|
594
|
+
/** Socket atual (pode ser null entre tentativas). */
|
|
595
|
+
private _ws;
|
|
596
|
+
/** Número da tentativa de reconexão corrente (reseta após conexão bem-sucedida). */
|
|
597
|
+
private _reconnectAttempt;
|
|
598
|
+
/** Handle do timer de backoff pendente. */
|
|
599
|
+
private _reconnectTimer;
|
|
600
|
+
/** Handle do timer de heartbeat. */
|
|
601
|
+
private _heartbeatTimer;
|
|
602
|
+
/** Indica que o cliente foi encerrado via `close()`. Não reconecta mais. */
|
|
603
|
+
private _closed;
|
|
604
|
+
/**
|
|
605
|
+
* Frames de subscribe que precisam ser enviados mas a socket ainda não
|
|
606
|
+
* está aberta (estado CONNECTING). Drenados no onopen.
|
|
607
|
+
*/
|
|
608
|
+
private _pendingFrames;
|
|
609
|
+
constructor(options: RealtimeClientOptions);
|
|
610
|
+
/**
|
|
611
|
+
* Registra uma subscription em `collection` com `query` opcional.
|
|
612
|
+
*
|
|
613
|
+
* Envia um frame `subscribe` ao gateway (ou enfileira se o socket ainda
|
|
614
|
+
* não está aberto). O `callback` é invocado com cada frame `delta`,
|
|
615
|
+
* `resync`, `stale` ou `error` roteado para esta subscription.
|
|
616
|
+
*
|
|
617
|
+
* @returns O `subscriptionId` opaco — usar para cancelar via `unsubscribe()`.
|
|
618
|
+
*/
|
|
619
|
+
subscribe(collection: string, query: RealtimeQuery, callback: SubscriptionCallback): string;
|
|
620
|
+
/**
|
|
621
|
+
* Cancela uma subscription.
|
|
622
|
+
*
|
|
623
|
+
* Envia um frame `unsubscribe` ao gateway e remove o listener local.
|
|
624
|
+
* Frames subsequentes com este `subscriptionId` são silenciosamente descartados.
|
|
625
|
+
*/
|
|
626
|
+
unsubscribe(subscriptionId: string): void;
|
|
627
|
+
/**
|
|
628
|
+
* Registra um listener de mudanças no estado da conexão.
|
|
629
|
+
*
|
|
630
|
+
* O listener é invocado imediatamente com o estado atual e depois a cada
|
|
631
|
+
* transição. Retorna uma função `unsubscribe`.
|
|
632
|
+
*/
|
|
633
|
+
onConnectionState(listener: ConnectionStateListener): () => void;
|
|
634
|
+
/**
|
|
635
|
+
* Encerra o cliente definitivamente.
|
|
636
|
+
*
|
|
637
|
+
* Fecha o socket ativo, cancela timers pendentes e marca o cliente como
|
|
638
|
+
* encerrado (sem mais reconexões).
|
|
639
|
+
*/
|
|
640
|
+
close(): void;
|
|
641
|
+
/**
|
|
642
|
+
* Busca um ticket de autenticação e, em seguida, cria um novo WebSocket e
|
|
643
|
+
* instala os handlers de evento.
|
|
644
|
+
*
|
|
645
|
+
* A busca de ticket é obrigatória antes de abrir qualquer WS (primeira
|
|
646
|
+
* conexão e reconexões). Se a busca falhar, a conexão é abortada com
|
|
647
|
+
* estado `'connecting'` e o backoff reconectará em seguida.
|
|
648
|
+
*
|
|
649
|
+
* Fail-closed: o WebSocket NUNCA é aberto sem um ticket válido.
|
|
650
|
+
*/
|
|
651
|
+
private _connect;
|
|
652
|
+
/** Implementação assíncrona de _connect — busca ticket e abre WS. */
|
|
653
|
+
private _connectWithTicket;
|
|
654
|
+
private _handleOpen;
|
|
655
|
+
private _handleMessage;
|
|
656
|
+
private _handleClose;
|
|
657
|
+
private _handleError;
|
|
658
|
+
private _handleDrain;
|
|
659
|
+
private _routeToSubscription;
|
|
660
|
+
/**
|
|
661
|
+
* Reenvía frames `subscribe` para todas as subscriptions ativas.
|
|
662
|
+
* Chamado no `onopen` de reconexões para restaurar o estado remoto.
|
|
663
|
+
*
|
|
664
|
+
* Durante a PRIMEIRA conexão: _pendingFrames já foi drenado com os
|
|
665
|
+
* subscribes iniciais acima. ResubscribeAll envia novamente — como a
|
|
666
|
+
* lista de subscriptions foi populada pelos `subscribe()` calls anteriores
|
|
667
|
+
* ao open, e os frames drenados também os incluem, teríamos duplicatas.
|
|
668
|
+
*
|
|
669
|
+
* Solução: resubscribeAll só é invocado após drenar _pendingFrames, e
|
|
670
|
+
* como o gateway é idempotente para o mesmo subscriptionId (segundo o
|
|
671
|
+
* design do componente #19), a duplicata é inócua. Em reconexões, onde
|
|
672
|
+
* _pendingFrames está vazio, os frames são os únicos enviados.
|
|
673
|
+
*
|
|
674
|
+
* Para o caso do primeiro open com subscribes anteriores ao open, os
|
|
675
|
+
* frames já foram drenados de _pendingFrames — não chamamos resubscribeAll
|
|
676
|
+
* se era a primeira conexão (_reconnectAttempt era 0 antes de resetar).
|
|
677
|
+
* Rastreamos isso com _hasConnectedOnce.
|
|
678
|
+
*/
|
|
679
|
+
private _hasConnectedOnce;
|
|
680
|
+
private _resubscribeAll;
|
|
681
|
+
/**
|
|
682
|
+
* Constrói o descriptor `query` para um frame `subscribe`, embutindo o
|
|
683
|
+
* `token` do ticket corrente em `query.filter._ticket` e o `_dbId` do
|
|
684
|
+
* banco lógico em `query.filter._dbId` (BL-8 fix).
|
|
685
|
+
*
|
|
686
|
+
* O gateway extrai `filter._ticket` do primeiro frame `subscribe` e valida
|
|
687
|
+
* contra `POST /api/sdk/v1/db/realtime/validate`. `filter._dbId` é exigido
|
|
688
|
+
* pelo `@neetru/realtime-changestream._extractDbId` para rotear a
|
|
689
|
+
* subscription ao banco correto — sem ele a subscription é rejeitada.
|
|
690
|
+
*
|
|
691
|
+
* Retorna `undefined` se a query seria vazia E não há ticket (estado
|
|
692
|
+
* transitório antes do primeiro ticket — não deve ocorrer normalmente pois
|
|
693
|
+
* `_connect` só abre o WS após obter o ticket).
|
|
694
|
+
*/
|
|
695
|
+
private _buildSubscribeQuery;
|
|
696
|
+
/** Agenda uma tentativa de reconexão com backoff exponencial + jitter. */
|
|
697
|
+
private _scheduleReconnect;
|
|
698
|
+
/**
|
|
699
|
+
* Calcula o delay de backoff para o attempt N.
|
|
700
|
+
*
|
|
701
|
+
* Fórmula: `min(baseMs * 2^N, maxMs) * jitter`
|
|
702
|
+
* onde `jitter ∈ [JITTER_MIN, JITTER_MAX]` (±50%).
|
|
703
|
+
*/
|
|
704
|
+
private _computeBackoffDelay;
|
|
705
|
+
private _cancelReconnectTimer;
|
|
706
|
+
/** Agenda o próximo ping. Não-operacional se heartbeatIntervalMs === 0. */
|
|
707
|
+
private _scheduleHeartbeat;
|
|
708
|
+
private _cancelHeartbeatTimer;
|
|
709
|
+
private _sendPing;
|
|
710
|
+
/**
|
|
711
|
+
* Envia o frame imediatamente se o socket está aberto; caso contrário,
|
|
712
|
+
* enfileira em `_pendingFrames` para envio no próximo `onopen`.
|
|
713
|
+
*/
|
|
714
|
+
private _sendOrBuffer;
|
|
715
|
+
/** Serializa e envia o frame imediatamente via WebSocket. */
|
|
716
|
+
private _sendNow;
|
|
717
|
+
private _setState;
|
|
718
|
+
/** Remove todos os handlers de um WebSocket (evita disparo após close()). */
|
|
719
|
+
private _detachHandlers;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Engine do banco lógico — determina o transporte de realtime.
|
|
724
|
+
*
|
|
725
|
+
* - `'firestore'` — Firestore Web SDK como transporte (memoryLocalCache).
|
|
726
|
+
* - `'nosql-vm'` — WebSocket via gateway `neetru-realtime`.
|
|
727
|
+
* - `'rest'` — REST puro (default MVP; sem realtime nativo).
|
|
728
|
+
*
|
|
729
|
+
* O produto NUNCA vê a diferença de transporte — o comportamento offline
|
|
730
|
+
* é idêntico nos dois engines de Documentos (02-sdk.md §3.7).
|
|
731
|
+
*/
|
|
732
|
+
type NeetruDbEngine = 'firestore' | 'nosql-vm' | 'rest';
|
|
733
|
+
/**
|
|
734
|
+
* Opções de configuração do banco lógico.
|
|
735
|
+
*
|
|
736
|
+
* Normalmente geradas pelo scaffold `neetru db init` em `db/client.ts`.
|
|
737
|
+
* Em testes, injetar diretamente.
|
|
738
|
+
*/
|
|
739
|
+
interface NeetruDbOptions {
|
|
740
|
+
/**
|
|
741
|
+
* ID opaco do banco lógico — emitido pelo Core em `POST /api/sdk/v1/db`.
|
|
742
|
+
* Necessário para o lease SQL e para scoping das coleções.
|
|
743
|
+
*/
|
|
744
|
+
dbId?: string;
|
|
745
|
+
/**
|
|
746
|
+
* Engine do banco lógico — determina o transporte de realtime.
|
|
747
|
+
* Default: `'rest'` (MVP — funciona para todos os engines SQL e Documentos).
|
|
748
|
+
*/
|
|
749
|
+
engine?: NeetruDbEngine;
|
|
750
|
+
/**
|
|
751
|
+
* Opções do transporte Firestore (somente quando `engine === 'firestore'`).
|
|
752
|
+
* O `firestore` handle (inicializado com `memoryLocalCache`) é injetado aqui.
|
|
753
|
+
*
|
|
754
|
+
* Injeção explícita — o SDK não importa `firebase/firestore` diretamente
|
|
755
|
+
* para não poluir o bundle de quem não usa este engine.
|
|
756
|
+
*/
|
|
757
|
+
firestoreTransport?: RealtimeTransport;
|
|
758
|
+
/**
|
|
759
|
+
* Opções do transporte WebSocket (somente quando `engine === 'nosql-vm'`).
|
|
760
|
+
* URL do gateway `neetru-realtime`.
|
|
761
|
+
*/
|
|
762
|
+
realtimeGatewayUrl?: string;
|
|
763
|
+
/**
|
|
764
|
+
* Coleções declaradas pelo produto — necessário para pullChanges/fullResync
|
|
765
|
+
* com os transportes Firestore e nosql-vm.
|
|
766
|
+
*
|
|
767
|
+
* Default: `[]` (o SyncEngine sincroniza coleções conforme são acessadas).
|
|
768
|
+
*/
|
|
769
|
+
collections?: string[];
|
|
770
|
+
/**
|
|
771
|
+
* Nome do banco IndexedDB. Convenção: `neetru-db__{productSlug}__{dbId}__{env}`.
|
|
772
|
+
* Gerado automaticamente se ausente.
|
|
773
|
+
*/
|
|
774
|
+
dbName?: string;
|
|
775
|
+
/**
|
|
776
|
+
* `true` = sem contenção multi-aba (sempre líder).
|
|
777
|
+
* Padrão em SSR/testes; em produção usa Web Locks.
|
|
778
|
+
*/
|
|
779
|
+
singleTab?: boolean;
|
|
780
|
+
/**
|
|
781
|
+
* Transporte de sync customizado (injetado em testes).
|
|
782
|
+
* Se presente, sobrepõe a detecção por `engine`.
|
|
783
|
+
*/
|
|
784
|
+
_transport?: RealtimeTransport;
|
|
785
|
+
/**
|
|
786
|
+
* Factory de WebSocket injetável (somente quando `engine === 'nosql-vm'`).
|
|
787
|
+
* Default: `(url) => new WebSocket(url)`.
|
|
788
|
+
* Injetar em testes para evitar rede real.
|
|
789
|
+
*/
|
|
790
|
+
_wsFactory?: WebSocketFactory;
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Superfície pública do namespace `client.db` — alinhada com 02-sdk.md §3.1.
|
|
794
|
+
*
|
|
795
|
+
* ```ts
|
|
796
|
+
* const client = createNeetruClient({ ... });
|
|
797
|
+
*
|
|
798
|
+
* // Mundo Documentos (offline-first):
|
|
799
|
+
* const orders = client.db.collection<Order>('orders');
|
|
800
|
+
* await orders.add({ ... });
|
|
801
|
+
* orders.onSnapshot(query, snap => renderOrders(snap.docs));
|
|
802
|
+
*
|
|
803
|
+
* // Mundo SQL (lease + pg.Pool + Drizzle):
|
|
804
|
+
* const sql = await client.db.sql(schema);
|
|
805
|
+
* const rows = await sql.orm.select().from(schema.orders);
|
|
806
|
+
*
|
|
807
|
+
* // Camada offline:
|
|
808
|
+
* const state = client.db.syncState;
|
|
809
|
+
* client.db.onSyncStateChanged(s => console.log(s.pendingWrites));
|
|
810
|
+
* await client.db.flush(); // força sync
|
|
811
|
+
* ```
|
|
812
|
+
*/
|
|
813
|
+
interface NeetruDb {
|
|
814
|
+
/**
|
|
815
|
+
* Retorna uma `DbCollectionRef` para a coleção `name`.
|
|
816
|
+
*
|
|
817
|
+
* Offline-first: reads vêm do cache local; writes são aplicados
|
|
818
|
+
* otimisticamente e enfileirados para sync. Engine-aware: o transporte
|
|
819
|
+
* realtime é selecionado automaticamente pelo `engine` configurado.
|
|
820
|
+
*/
|
|
821
|
+
collection<T = Record<string, unknown>>(name: string): DbCollectionRef$1<T>;
|
|
822
|
+
/**
|
|
823
|
+
* Abre o handle SQL: busca o lease no Core, abre o `pg.Pool`, cria o
|
|
824
|
+
* handle Drizzle tipado pelo `schema`. Operação async, 1x no startup.
|
|
825
|
+
*
|
|
826
|
+
* Em `NEETRU_ENV=dev`, retorna um `MockSqlClient` que não abre socket.
|
|
827
|
+
*
|
|
828
|
+
* @param schema — Schema Drizzle do produto (`pgTable(...)` em `db/schema.ts`).
|
|
829
|
+
* @param options — Opções opcionais (contrato C-H).
|
|
830
|
+
* `options.database` desambigua quando o produto tem N>1 bancos SQL.
|
|
831
|
+
* Quando omitido, o Core resolve o banco padrão/homônimo do produto.
|
|
832
|
+
*
|
|
833
|
+
* @example
|
|
834
|
+
* ```ts
|
|
835
|
+
* import * as schema from './db/schema';
|
|
836
|
+
* // banco padrão (único ou homônimo):
|
|
837
|
+
* export const db = await client.db.sql(schema);
|
|
838
|
+
* // banco específico entre N bancos SQL:
|
|
839
|
+
* export const analytics = await client.db.sql(schema, { database: 'analytics' });
|
|
840
|
+
* ```
|
|
841
|
+
*/
|
|
842
|
+
sql<TSchema extends Record<string, unknown>>(schema: TSchema, options?: SqlOptions): Promise<NeetruSqlClient<TSchema>>;
|
|
843
|
+
/** Estado atual de sincronização da camada offline. */
|
|
844
|
+
readonly syncState: SyncState;
|
|
845
|
+
/**
|
|
846
|
+
* Subscreve a mudanças de `SyncState`.
|
|
847
|
+
* Retorna função de unsubscribe.
|
|
848
|
+
*/
|
|
849
|
+
onSyncStateChanged(cb: (s: SyncState) => void): Unsubscribe;
|
|
850
|
+
/**
|
|
851
|
+
* Força flush da fila de mutações pendentes.
|
|
852
|
+
* Útil antes de um `beforeunload` ou shutdown do produto.
|
|
853
|
+
*/
|
|
854
|
+
flush(): Promise<void>;
|
|
855
|
+
/**
|
|
856
|
+
* Limpa o cache local (IndexedDB) — operação destrutiva.
|
|
857
|
+
* Use com cuidado: dados não sincronizados serão perdidos.
|
|
858
|
+
*/
|
|
859
|
+
clearCache(): Promise<void>;
|
|
860
|
+
/**
|
|
861
|
+
* Retorna os registros de conflito LWW pendentes de entrega.
|
|
862
|
+
* Permite ao produto inspecionar escritas perdidas.
|
|
863
|
+
*/
|
|
864
|
+
getConflicts(): Promise<ConflictRecord[]>;
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Transporte REST puro — usa os endpoints `/api/sdk/v1/datastore/*` do Core.
|
|
868
|
+
*
|
|
869
|
+
* BL-2 fix: `pushMutations` agora chama o Core de verdade via `httpRequest`.
|
|
870
|
+
* Sem `config`, lança `NeetruDbError('db_unavailable')` — NUNCA retorna
|
|
871
|
+
* `confirmed` para uma escrita que não foi enviada.
|
|
872
|
+
*
|
|
873
|
+
* - `pushMutations` → POST/PATCH/PUT/DELETE `/api/sdk/v1/datastore/{coll}/{id}`
|
|
874
|
+
* - `pullChanges` → GET `/api/sdk/v1/datastore/{coll}` (best-effort por coleção)
|
|
875
|
+
* - `fullResync` → GET por coleção (lista completa)
|
|
876
|
+
*
|
|
877
|
+
* Exportado para testes.
|
|
878
|
+
*/
|
|
879
|
+
declare function createRestSyncTransport(config?: ResolvedConfig): RealtimeTransport;
|
|
880
|
+
/**
|
|
881
|
+
* Retorna o `NeetruRealtimeClient` para uso em `subscribeRealtime` fora do
|
|
882
|
+
* `SyncTransport` (extensão nosql-vm — não faz parte do contrato base).
|
|
883
|
+
*
|
|
884
|
+
* H-10 fix: agora aceita um `dbId` opcional que é passado para o cliente
|
|
885
|
+
* (BL-8 — o cliente embute `_dbId` nos frames de subscribe).
|
|
886
|
+
*
|
|
887
|
+
* @param gatewayUrl URL do gateway WebSocket.
|
|
888
|
+
* @param ticketProvider Provedor de ticket obrigatório.
|
|
889
|
+
* @param dbId ID do banco lógico (BL-8).
|
|
890
|
+
* @param wsFactory Factory de WebSocket injetável (testes).
|
|
891
|
+
*/
|
|
892
|
+
declare function getWebSocketRealtimeClient(gatewayUrl: string, ticketProvider: TicketProvider, dbId?: string, wsFactory?: WebSocketFactory): NeetruRealtimeClient;
|
|
893
|
+
/**
|
|
894
|
+
* Cria o namespace `client.db` completo.
|
|
895
|
+
*
|
|
896
|
+
* **Factory única** — `createNeetruDb` é a única factory pública.
|
|
897
|
+
* O split sync/async é um detalhe de implementação interno: o `NeetruDb`
|
|
898
|
+
* retornado inicializa a camada offline (IndexedDB) de forma lazy na
|
|
899
|
+
* primeira operação real, portanto:
|
|
900
|
+
*
|
|
901
|
+
* - Pode ser chamado de contextos síncronos (como `createNeetruClient`).
|
|
902
|
+
* - Pode ser chamado com `await` quando se deseja garantir que o store
|
|
903
|
+
* esteja aquecido antes da primeira op (equivalente a chamar `flush()`).
|
|
904
|
+
* - `client.db.flush()` garante que o IndexedDB está aberto.
|
|
905
|
+
*
|
|
906
|
+
* @param config — config resolvida do cliente (apiKey, baseUrl, env, etc.)
|
|
907
|
+
* @param dbOpts — opções do banco lógico (engine, dbId, collections, etc.)
|
|
908
|
+
*
|
|
909
|
+
* @example
|
|
910
|
+
* ```ts
|
|
911
|
+
* // Uso síncrono (dentro de createNeetruClient):
|
|
912
|
+
* const db = createNeetruDb(resolved, config.db);
|
|
913
|
+
*
|
|
914
|
+
* // Uso com await (quando se quer garantir warmup):
|
|
915
|
+
* const db = await createNeetruDb(resolved, config.db);
|
|
916
|
+
* await db.flush(); // opcional — já está aquecido
|
|
917
|
+
*
|
|
918
|
+
* // O produto usa:
|
|
919
|
+
* const orders = db.collection<Order>('orders');
|
|
920
|
+
* const sqlDb = await db.sql(schema);
|
|
921
|
+
* ```
|
|
922
|
+
*/
|
|
923
|
+
declare function createNeetruDb(config: ResolvedConfig, dbOpts?: NeetruDbOptions): NeetruDb;
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* Tipos públicos do SDK Neetru.
|
|
927
|
+
*
|
|
928
|
+
* Vendor-neutral: nada aqui referencia `firebase/*`, `stripe`, ou qualquer
|
|
929
|
+
* provider interno. Caller só vê superfície neutra (Product, Entitlement, ...).
|
|
930
|
+
*/
|
|
931
|
+
/** Default base URL pra API pública Neetru. */
|
|
932
|
+
declare const DEFAULT_BASE_URL = "https://api.neetru.com";
|
|
933
|
+
/**
|
|
934
|
+
* Configuração passada pra `createNeetruClient`.
|
|
935
|
+
*/
|
|
936
|
+
interface NeetruClientConfig {
|
|
937
|
+
/**
|
|
938
|
+
* Bearer token Neetru (formato `nrt_<keyId>_<secret>`). Opcional —
|
|
939
|
+
* se ausente, o SDK tenta `process.env.NEETRU_API_KEY` em Node.
|
|
940
|
+
*
|
|
941
|
+
* Endpoints públicos (catalog) funcionam sem apiKey. Endpoints autenticados
|
|
942
|
+
* (entitlements, telemetry) lançam `missing_api_key` se ausente.
|
|
943
|
+
*/
|
|
944
|
+
apiKey?: string;
|
|
945
|
+
/**
|
|
946
|
+
* URL base da API Neetru. Default: `https://api.neetru.com`.
|
|
947
|
+
* Útil em testes apontando pra staging ou mock server.
|
|
948
|
+
*/
|
|
949
|
+
baseUrl?: string;
|
|
950
|
+
/**
|
|
951
|
+
* Implementação de fetch customizada. Default: `globalThis.fetch`.
|
|
952
|
+
* Necessário em runtimes que não exponham fetch global por default.
|
|
953
|
+
*/
|
|
954
|
+
fetch?: typeof globalThis.fetch;
|
|
955
|
+
/**
|
|
956
|
+
* Modo de runtime. `dev` ativa mocks (auth retorna user fixture, usage só
|
|
957
|
+
* loga, support retorna lista fake). `workspace` e `prod` chamam HTTP real.
|
|
958
|
+
*
|
|
959
|
+
* Default: lê `process.env.NEETRU_ENV` (Node) ou `globalThis.NEETRU_ENV`
|
|
960
|
+
* (browser); fallback `prod`.
|
|
961
|
+
*/
|
|
962
|
+
env?: 'dev' | 'workspace' | 'prod';
|
|
963
|
+
/**
|
|
964
|
+
* Override de mocks — útil em tests do consumer pra injetar fixtures
|
|
965
|
+
* determinísticos (sem depender de NEETRU_ENV=dev).
|
|
966
|
+
*/
|
|
967
|
+
mocks?: {
|
|
968
|
+
auth?: AuthNamespace;
|
|
969
|
+
usage?: UsageNamespace;
|
|
970
|
+
support?: SupportNamespace;
|
|
971
|
+
entitlements?: NeetruClient['entitlements'];
|
|
972
|
+
/** v2.0: aceita NeetruDb (inclui MockDb). DbNamespace legado não é mais aceito. */
|
|
973
|
+
db?: NeetruDb;
|
|
974
|
+
webhooks?: WebhooksNamespace;
|
|
975
|
+
notifications?: NotificationsNamespace;
|
|
976
|
+
};
|
|
977
|
+
/**
|
|
978
|
+
* v0.3 — productId default usado por `usage.report()` / `usage.check()` /
|
|
979
|
+
* `db.collection()` quando não passado explicitamente nas options.
|
|
980
|
+
*/
|
|
981
|
+
productId?: string;
|
|
982
|
+
/**
|
|
983
|
+
* v0.3 — tenantId default usado por `usage.report()` / `usage.check()` /
|
|
984
|
+
* `db.collection()` quando não passado explicitamente nas options.
|
|
985
|
+
*/
|
|
986
|
+
tenantId?: string;
|
|
987
|
+
/**
|
|
988
|
+
* v2.0 — Configuração do banco lógico: engine, dbId, coleções, transport.
|
|
989
|
+
* Gerado pelo scaffold `neetru db init`. Opcional — padrão é engine='rest'.
|
|
990
|
+
*/
|
|
991
|
+
db?: NeetruDbOptions;
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* Status do produto no catálogo público.
|
|
995
|
+
*/
|
|
996
|
+
type ProductStatus = 'live' | 'soon' | 'beta';
|
|
997
|
+
/**
|
|
998
|
+
* Produto SaaS publicado pelo Neetru Core.
|
|
999
|
+
*
|
|
1000
|
+
* Schema neutro — alinhado com `public_products/{slug}` no backend mas
|
|
1001
|
+
* nunca expõe campos internos (Firestore Timestamps, draft state, etc).
|
|
1002
|
+
*/
|
|
1003
|
+
interface Product {
|
|
1004
|
+
/** Identificador estável do produto, ex: `neetru-pulse`. */
|
|
1005
|
+
slug: string;
|
|
1006
|
+
/** Nome de exibição, ex: `Neetru Pulse`. */
|
|
1007
|
+
name: string;
|
|
1008
|
+
/** Subtítulo curto, ex: `Gestão de operações`. */
|
|
1009
|
+
tagline?: string;
|
|
1010
|
+
/** Descrição longa em prosa. */
|
|
1011
|
+
description?: string;
|
|
1012
|
+
/** Status público do produto. */
|
|
1013
|
+
status?: ProductStatus;
|
|
1014
|
+
/** Hint de ícone (catálogo de keys interno do Core). */
|
|
1015
|
+
iconKey?: string;
|
|
1016
|
+
/** Link override do CTA principal (default: página do produto). */
|
|
1017
|
+
ctaHref?: string;
|
|
1018
|
+
/** Label override do CTA. */
|
|
1019
|
+
ctaLabel?: string;
|
|
1020
|
+
/** Lista opcional de planos cobrados. Pode ser preenchida v0.2+. */
|
|
1021
|
+
plans?: ProductPlan[];
|
|
1022
|
+
}
|
|
1023
|
+
/** Plano cobrado de um produto (placeholder v0.1 — schema final v0.2). */
|
|
1024
|
+
interface ProductPlan {
|
|
1025
|
+
id: string;
|
|
1026
|
+
name: string;
|
|
1027
|
+
/** Preço mensal em centavos (BRL). Pode ser undefined em planos custom. */
|
|
1028
|
+
amountCents?: number;
|
|
1029
|
+
features?: string[];
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Resposta do `client.catalog.list()`.
|
|
1033
|
+
*/
|
|
1034
|
+
interface CatalogListResponse {
|
|
1035
|
+
products: Product[];
|
|
1036
|
+
fetchedAt: string;
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Resposta do `client.entitlements.check(productSlug, feature)`.
|
|
1040
|
+
*
|
|
1041
|
+
* `allowed` é o sinal forte; `reason` ajuda a debugar (não exibir pro user
|
|
1042
|
+
* final).
|
|
1043
|
+
*/
|
|
1044
|
+
interface EntitlementCheck {
|
|
1045
|
+
allowed: boolean;
|
|
1046
|
+
productSlug: string;
|
|
1047
|
+
feature: string;
|
|
1048
|
+
/** Reason code estável: `granted` | `not_subscribed` | `feature_not_in_plan` | `expired`. */
|
|
1049
|
+
reason?: string;
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Payload pra `client.telemetry.event()`.
|
|
1053
|
+
*/
|
|
1054
|
+
interface TelemetryEventInput {
|
|
1055
|
+
/** Nome do evento, ex: `dashboard_opened`, `report_exported`. */
|
|
1056
|
+
name: string;
|
|
1057
|
+
/** Atributos adicionais (chave → valor primitivo). */
|
|
1058
|
+
properties?: Record<string, string | number | boolean | null>;
|
|
1059
|
+
/** Timestamp ISO opcional; default = server time. */
|
|
1060
|
+
timestamp?: string;
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Confirmação de aceitação do evento de telemetria.
|
|
1064
|
+
*/
|
|
1065
|
+
interface TelemetryEventAck {
|
|
1066
|
+
ok: true;
|
|
1067
|
+
/** ID gerado pelo backend pra evento (`usage_events/{id}`). */
|
|
1068
|
+
eventId: string;
|
|
1069
|
+
}
|
|
1070
|
+
type TelemetryLogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
|
1071
|
+
interface TelemetryLogInput {
|
|
1072
|
+
level: TelemetryLogLevel;
|
|
1073
|
+
message: string;
|
|
1074
|
+
metadata?: Record<string, unknown>;
|
|
1075
|
+
/** Override correlationId (default: lê dos headers/env automaticamente). */
|
|
1076
|
+
correlationId?: string;
|
|
1077
|
+
/** Slug do produto referenciado. Backend infere do escopo do token se ausente. */
|
|
1078
|
+
productSlug?: string;
|
|
1079
|
+
}
|
|
1080
|
+
interface TelemetryLogAck {
|
|
1081
|
+
ok: true;
|
|
1082
|
+
/** ID do log gravado (`logs/{productId}/{yyyymmdd}/{logId}`) — opcional em mocks. */
|
|
1083
|
+
logId?: string;
|
|
1084
|
+
/** Indicador de modo (mock vs http). */
|
|
1085
|
+
mode: 'mock' | 'http';
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Handle imutável retornado por `createNeetruClient`. Carrega namespaces
|
|
1089
|
+
* `auth`, `catalog`, `entitlements`, `telemetry`, `usage`, `support`.
|
|
1090
|
+
*
|
|
1091
|
+
* Cada namespace é exposto como objeto chamável — espelha API Firebase
|
|
1092
|
+
* (`auth`, `firestore`) ou Stripe (`paymentIntents`, `customers`).
|
|
1093
|
+
*/
|
|
1094
|
+
interface NeetruClient {
|
|
1095
|
+
/** Configuração resolvida (apiKey, baseUrl, fetch). Read-only. */
|
|
1096
|
+
readonly config: ResolvedConfig;
|
|
1097
|
+
/** Namespace auth (v0.2 — OIDC + mocks dev). */
|
|
1098
|
+
readonly auth: AuthNamespace;
|
|
1099
|
+
/** Namespace catálogo público de produtos. */
|
|
1100
|
+
readonly catalog: {
|
|
1101
|
+
/** Lista produtos publicados. */
|
|
1102
|
+
list(opts?: CatalogListOptions): Promise<CatalogListResponse>;
|
|
1103
|
+
/** Busca produto único por slug. */
|
|
1104
|
+
get(slug: string): Promise<Product>;
|
|
1105
|
+
};
|
|
1106
|
+
/** Namespace entitlements (v1.2: cache LRU 60s TTL). */
|
|
1107
|
+
readonly entitlements: {
|
|
1108
|
+
/**
|
|
1109
|
+
* Verifica se o portador da apiKey pode usar `feature` do produto `slug`.
|
|
1110
|
+
* Cacheado 60s. `{ cacheBust: true }` força ida ao servidor.
|
|
1111
|
+
*/
|
|
1112
|
+
check(productSlug: string, feature: string, opts?: {
|
|
1113
|
+
cacheBust?: boolean;
|
|
1114
|
+
}): Promise<boolean>;
|
|
1115
|
+
/** Variante que retorna o objeto completo com `reason`. */
|
|
1116
|
+
checkDetailed(productSlug: string, feature: string, opts?: {
|
|
1117
|
+
cacheBust?: boolean;
|
|
1118
|
+
}): Promise<EntitlementCheck>;
|
|
1119
|
+
};
|
|
1120
|
+
/** Namespace telemetria (v1.2: track fire-and-forget + flush). */
|
|
1121
|
+
readonly telemetry: {
|
|
1122
|
+
/** Persiste um evento single-shot. await retorna { ok, eventId }. */
|
|
1123
|
+
event(input: TelemetryEventInput): Promise<TelemetryEventAck>;
|
|
1124
|
+
/** Fire-and-forget: enqueue + flush 500ms debounce. Sem retorno. */
|
|
1125
|
+
track(input: TelemetryEventInput): void;
|
|
1126
|
+
/** Força flush da queue do track() (chamar antes de unload). */
|
|
1127
|
+
flush(): Promise<void>;
|
|
1128
|
+
/**
|
|
1129
|
+
* Registra um log estruturado per-product (Sprint 6).
|
|
1130
|
+
*
|
|
1131
|
+
* - Em `NEETRU_ENV=dev`: apenas console.{level} (sem network).
|
|
1132
|
+
* - Em `workspace`/`prod`: POST `/api/sdk/v1/telemetry/log` com Bearer + correlationId.
|
|
1133
|
+
*/
|
|
1134
|
+
log(input: TelemetryLogInput): Promise<TelemetryLogAck>;
|
|
1135
|
+
};
|
|
1136
|
+
/** Namespace usage (v0.2 — track + quota; v0.3 — report + check). */
|
|
1137
|
+
readonly usage: UsageNamespace;
|
|
1138
|
+
/** Namespace support (v0.2 — tickets). */
|
|
1139
|
+
readonly support: SupportNamespace;
|
|
1140
|
+
/**
|
|
1141
|
+
* Namespace db (v2.0 — Mundo Documentos offline-first + Mundo SQL).
|
|
1142
|
+
*
|
|
1143
|
+
* Breaking change v2.0: `client.db` agora é `NeetruDb` em vez de `DbNamespace`.
|
|
1144
|
+
* - `db.collection(name)` → `DbCollectionRef` (offline-first, com `onSnapshot`,
|
|
1145
|
+
* `batch`, `doc(id)`, `onDoc`; `list()` retorna `DbListResult` com cursor).
|
|
1146
|
+
* - `db.sql(schema)` → `Promise<NeetruSqlClient>` (Mundo SQL).
|
|
1147
|
+
* - `db.syncState` / `db.flush()` / `db.clearCache()` (camada offline).
|
|
1148
|
+
*
|
|
1149
|
+
* O antigo `DbNamespace` (list retornava `T[]`) foi removido em v2.0.
|
|
1150
|
+
*/
|
|
1151
|
+
readonly db: NeetruDb;
|
|
1152
|
+
/** Namespace checkout (v1.1 — start/get/cancel intent + auto-redirect). */
|
|
1153
|
+
readonly checkout: CheckoutNamespace;
|
|
1154
|
+
/** Namespace webhooks (v1.2 — register/list/unregister/test). */
|
|
1155
|
+
readonly webhooks: WebhooksNamespace;
|
|
1156
|
+
/** Namespace notifications (v1.2 — send/list/markRead/dismiss). */
|
|
1157
|
+
readonly notifications: NotificationsNamespace;
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* User retornado pelo OIDC ID token. Schema neutro — não vaza Firebase
|
|
1161
|
+
* decoded token shape. Subset estável do RFC 7519 + custom claims Neetru.
|
|
1162
|
+
*/
|
|
1163
|
+
interface NeetruUser {
|
|
1164
|
+
/** Subject — uid estável. */
|
|
1165
|
+
uid: string;
|
|
1166
|
+
email: string;
|
|
1167
|
+
emailVerified?: boolean;
|
|
1168
|
+
displayName?: string;
|
|
1169
|
+
photoURL?: string;
|
|
1170
|
+
/** Custom claim Neetru — true quando staff. */
|
|
1171
|
+
isStaff?: boolean;
|
|
1172
|
+
/** Custom claim Neetru — true quando customer enrolled. */
|
|
1173
|
+
isCustomer?: boolean;
|
|
1174
|
+
/** Tenant assigned (multi-tenant deployments). */
|
|
1175
|
+
tenantId?: string;
|
|
1176
|
+
/** Outros claims OIDC (aud, iss, iat, exp). */
|
|
1177
|
+
[extra: string]: unknown;
|
|
1178
|
+
}
|
|
1179
|
+
interface SignInOptions {
|
|
1180
|
+
/** OIDC redirect_uri override. Default = window.location origin. */
|
|
1181
|
+
redirectUri?: string;
|
|
1182
|
+
/** OIDC scope. Default `openid profile email`. */
|
|
1183
|
+
scope?: string;
|
|
1184
|
+
/** Onde mandar após login completo. Default = window.location.href. */
|
|
1185
|
+
postLoginRedirect?: string;
|
|
1186
|
+
}
|
|
1187
|
+
type AuthStateListener = (user: NeetruUser | null) => void;
|
|
1188
|
+
interface AuthNamespace {
|
|
1189
|
+
/**
|
|
1190
|
+
* Inicia fluxo de login OIDC. Em dev (`NEETRU_ENV=dev`) retorna mock user
|
|
1191
|
+
* direto sem redirect. Em prod redireciona pro authorize endpoint.
|
|
1192
|
+
*/
|
|
1193
|
+
signIn(options?: SignInOptions): Promise<NeetruUser | void>;
|
|
1194
|
+
/** Limpa session local + revoga refresh token no servidor. */
|
|
1195
|
+
signOut(): Promise<void>;
|
|
1196
|
+
/** Retorna o user atual (do id_token cached) ou `null` se não logado. */
|
|
1197
|
+
getUser(): NeetruUser | null;
|
|
1198
|
+
/**
|
|
1199
|
+
* Subscreve a mudanças de estado de auth. Listener é invocado imediatamente
|
|
1200
|
+
* com user atual. Retorna função de unsubscribe.
|
|
1201
|
+
*/
|
|
1202
|
+
onAuthStateChanged(listener: AuthStateListener): () => void;
|
|
1203
|
+
}
|
|
1204
|
+
interface UsageEventInput {
|
|
1205
|
+
/** Nome do evento, ex: `report_generated`, `api_call`. */
|
|
1206
|
+
event: string;
|
|
1207
|
+
/** Atributos do evento. Valores primitivos serializáveis. */
|
|
1208
|
+
properties?: Record<string, string | number | boolean | null>;
|
|
1209
|
+
/** Quantidade — default 1 (1 evento). Útil pra batch. */
|
|
1210
|
+
quantity?: number;
|
|
1211
|
+
}
|
|
1212
|
+
interface UsageQuota {
|
|
1213
|
+
metric: string;
|
|
1214
|
+
/** Quantidade já consumida no período. */
|
|
1215
|
+
used: number;
|
|
1216
|
+
/** Limite total. -1 = unlimited. */
|
|
1217
|
+
limit: number;
|
|
1218
|
+
/** ISO timestamp do reset (próximo período). */
|
|
1219
|
+
resetsAt?: string;
|
|
1220
|
+
/** Plano que define o limite. */
|
|
1221
|
+
plan?: string;
|
|
1222
|
+
}
|
|
1223
|
+
interface UsageNamespace {
|
|
1224
|
+
/** Persiste um evento de uso. Mock em dev, POST `/sdk/v1/usage/record` em prod. */
|
|
1225
|
+
track(event: string, props?: UsageEventInput['properties']): Promise<{
|
|
1226
|
+
ok: true;
|
|
1227
|
+
}>;
|
|
1228
|
+
/** Lê quota atual de uma métrica. Mock em dev, GET `/sdk/v1/usage/quota` em prod. */
|
|
1229
|
+
getQuota(metric: string): Promise<UsageQuota>;
|
|
1230
|
+
/**
|
|
1231
|
+
* v0.3 — Reporta consumo de um resource metered (POST /sdk/v1/usage/record
|
|
1232
|
+
* com `{productId, tenantId, resource, qty}`). Em dev acumula no mock.
|
|
1233
|
+
*
|
|
1234
|
+
* Diferente de `track()`: usa o endpoint canônico Sprint 7 que incrementa
|
|
1235
|
+
* `usage_counters/{tenantId}_{productId}_{resource}_{yyyymm}` atomicamente.
|
|
1236
|
+
*
|
|
1237
|
+
* `productId`/`tenantId` são lidos do contexto resolvido do client se
|
|
1238
|
+
* ausentes nas options.
|
|
1239
|
+
*/
|
|
1240
|
+
report(resource: string, qty?: number, options?: {
|
|
1241
|
+
productId?: string;
|
|
1242
|
+
tenantId?: string;
|
|
1243
|
+
}): Promise<{
|
|
1244
|
+
ok: true;
|
|
1245
|
+
counterId?: string;
|
|
1246
|
+
value?: number;
|
|
1247
|
+
limit?: number;
|
|
1248
|
+
remaining?: number;
|
|
1249
|
+
status?: string;
|
|
1250
|
+
}>;
|
|
1251
|
+
/**
|
|
1252
|
+
* v0.3 — Verifica entitlement de um resource/feature. Wrapper em
|
|
1253
|
+
* GET /sdk/v1/entitlements. Em dev consulta MockEntitlements + MockUsage.
|
|
1254
|
+
*/
|
|
1255
|
+
check(resource: string, options?: {
|
|
1256
|
+
productId?: string;
|
|
1257
|
+
tenantId?: string;
|
|
1258
|
+
}): Promise<{
|
|
1259
|
+
allowed: boolean;
|
|
1260
|
+
reason?: string;
|
|
1261
|
+
remaining?: number;
|
|
1262
|
+
limit?: number;
|
|
1263
|
+
planId?: string | null;
|
|
1264
|
+
planFeatures?: string[];
|
|
1265
|
+
}>;
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Namespace `db` (v0.3) — wrapper minimalista para CRUD em coleções tenant-scoped
|
|
1269
|
+
* via Core REST. Em dev retorna fixtures in-memory por collection.
|
|
1270
|
+
*
|
|
1271
|
+
* O Core injeta automaticamente o tenantId no path do Firestore:
|
|
1272
|
+
* `tenant_{tid}_{name}/{docId}`.
|
|
1273
|
+
*/
|
|
1274
|
+
/** Filtro `where` simples — alinhado com endpoint REST `field:op:value`. */
|
|
1275
|
+
interface DbWhereFilter {
|
|
1276
|
+
field: string;
|
|
1277
|
+
op: '==' | '!=' | '<' | '<=' | '>' | '>=' | 'in';
|
|
1278
|
+
value: string | number | boolean | null | Array<string | number | boolean>;
|
|
1279
|
+
}
|
|
1280
|
+
interface DbListOptions {
|
|
1281
|
+
limit?: number;
|
|
1282
|
+
/** Lista de filtros — máximo 5 (alinhado ao backend). */
|
|
1283
|
+
where?: DbWhereFilter[];
|
|
1284
|
+
}
|
|
1285
|
+
interface DbCollectionRef {
|
|
1286
|
+
/** Lista documentos com filtros opcionais. */
|
|
1287
|
+
list<T = Record<string, unknown>>(opts?: DbListOptions): Promise<T[]>;
|
|
1288
|
+
/** Lê um documento por id. Retorna `null` se não existe. */
|
|
1289
|
+
get<T = Record<string, unknown>>(id: string): Promise<T | null>;
|
|
1290
|
+
/** Cria/upsert um documento com id explícito. */
|
|
1291
|
+
set(id: string, data: Record<string, unknown>): Promise<{
|
|
1292
|
+
ok: true;
|
|
1293
|
+
}>;
|
|
1294
|
+
/**
|
|
1295
|
+
* v0.3.1 — Adiciona doc com id auto-gerado pelo backend. Retorna `{id}` do
|
|
1296
|
+
* doc criado. Diferente de `set(id, data)` que requer caller fornecer id.
|
|
1297
|
+
*/
|
|
1298
|
+
add(data: Record<string, unknown>): Promise<{
|
|
1299
|
+
ok: true;
|
|
1300
|
+
id: string;
|
|
1301
|
+
}>;
|
|
1302
|
+
/** Atualiza doc com merge — só campos passados em `data`. */
|
|
1303
|
+
update(id: string, data: Record<string, unknown>): Promise<{
|
|
1304
|
+
ok: true;
|
|
1305
|
+
}>;
|
|
1306
|
+
/** Deleta um documento. */
|
|
1307
|
+
remove(id: string): Promise<{
|
|
1308
|
+
ok: true;
|
|
1309
|
+
}>;
|
|
1310
|
+
}
|
|
1311
|
+
interface DbNamespace {
|
|
1312
|
+
collection(name: string): DbCollectionRef;
|
|
1313
|
+
}
|
|
1314
|
+
type SupportSeverity = 'low' | 'normal' | 'high' | 'urgent';
|
|
1315
|
+
type SupportStatus = 'open' | 'pending' | 'resolved' | 'closed';
|
|
1316
|
+
interface SupportTicket {
|
|
1317
|
+
id: string;
|
|
1318
|
+
subject: string;
|
|
1319
|
+
message: string;
|
|
1320
|
+
severity: SupportSeverity;
|
|
1321
|
+
status: SupportStatus;
|
|
1322
|
+
createdAt: string;
|
|
1323
|
+
updatedAt?: string;
|
|
1324
|
+
/** Slug do produto referenciado. */
|
|
1325
|
+
productSlug?: string;
|
|
1326
|
+
}
|
|
1327
|
+
interface CreateTicketInput {
|
|
1328
|
+
subject: string;
|
|
1329
|
+
message: string;
|
|
1330
|
+
severity?: SupportSeverity;
|
|
1331
|
+
productSlug?: string;
|
|
1332
|
+
}
|
|
1333
|
+
interface SupportNamespace {
|
|
1334
|
+
/** Cria um novo ticket. Mock em dev, POST `/api/v1/products/{slug}/tickets` em prod. */
|
|
1335
|
+
createTicket(input: CreateTicketInput): Promise<SupportTicket>;
|
|
1336
|
+
/** Lista meus tickets abertos. */
|
|
1337
|
+
listMyTickets(): Promise<SupportTicket[]>;
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Modo do SDK. `dev` ativa mocks automáticos (úteis pra testes e
|
|
1341
|
+
* desenvolvimento sem precisar de backend rodando).
|
|
1342
|
+
*/
|
|
1343
|
+
type NeetruEnv = 'dev' | 'workspace' | 'prod';
|
|
1344
|
+
/**
|
|
1345
|
+
* Configuração resolvida (defaults aplicados, fetch garantido).
|
|
1346
|
+
*/
|
|
1347
|
+
interface ResolvedConfig {
|
|
1348
|
+
readonly apiKey?: string;
|
|
1349
|
+
readonly baseUrl: string;
|
|
1350
|
+
readonly fetch: typeof globalThis.fetch;
|
|
1351
|
+
/** Resolved env — `dev` | `workspace` | `prod`. Default `prod`. */
|
|
1352
|
+
readonly env: NeetruEnv;
|
|
1353
|
+
/** Default productId (v0.3). */
|
|
1354
|
+
readonly productId?: string;
|
|
1355
|
+
/** Default tenantId (v0.3). */
|
|
1356
|
+
readonly tenantId?: string;
|
|
1357
|
+
}
|
|
1358
|
+
/** Opções pra `catalog.list()`. */
|
|
1359
|
+
interface CatalogListOptions {
|
|
1360
|
+
/** Inclui rascunhos (apenas com Bearer staff). Default false. */
|
|
1361
|
+
includeDrafts?: boolean;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
export { type WebhookEvent as $, type AuthNamespace as A, type ProductPlan as B, type CatalogListOptions as C, DEFAULT_BASE_URL as D, type EntitlementCheck as E, type ProductStatus as F, type ResolvedConfig as G, type SignInOptions as H, type SqlOptions as I, type SupportNamespace as J, type SupportSeverity as K, type ListNotificationsOptions as L, MockCheckout as M, type NeetruClient as N, type SupportStatus as O, type Product as P, type SupportTicket as Q, type RegisterWebhookInput as R, type SendNotificationInput as S, type TelemetryEventAck as T, type TelemetryEventInput as U, type TelemetryLogAck as V, type TelemetryLogInput as W, type UsageEventInput as X, type UsageNamespace as Y, type UsageQuota as Z, type WebhookEndpoint as _, type AuthStateListener as a, type WebhookTestResult as a0, type WebhooksNamespace as a1, createCheckoutNamespace as a2, createNeetruDb as a3, createNotificationsNamespace as a4, createRestSyncTransport as a5, createWebhooksNamespace as a6, getWebSocketRealtimeClient as a7, verifyWebhookSignature as a8, type CatalogListResponse as b, type CheckoutIntentInfo as c, type CheckoutIntentStatus as d, type CheckoutNamespace as e, type CheckoutStartInput as f, type CheckoutStartResult as g, type CheckoutTenantType as h, type CreateTicketInput as i, type DbNamespace as j, type DbSqlLease as k, type DbWhereFilter as l, MockNotifications as m, MockWebhooks as n, type NeetruClientConfig as o, type NeetruDb as p, type NeetruDbEngine as q, NeetruDbError as r, type NeetruDbErrorCode as s, type NeetruDbOptions as t, type NeetruEnv as u, type NeetruSqlClient as v, type NeetruUser as w, type NotificationsNamespace as x, type ProductNotification as y, type ProductNotificationSeverity as z };
|