@neetru/sdk 3.0.2 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/CHANGELOG.md +554 -530
  2. package/dist/auth.cjs +219 -54
  3. package/dist/auth.cjs.map +1 -1
  4. package/dist/auth.d.cts +1 -2
  5. package/dist/auth.d.ts +1 -2
  6. package/dist/auth.mjs +219 -54
  7. package/dist/auth.mjs.map +1 -1
  8. package/dist/catalog.cjs +50 -2
  9. package/dist/catalog.cjs.map +1 -1
  10. package/dist/catalog.d.cts +1 -2
  11. package/dist/catalog.d.ts +1 -2
  12. package/dist/catalog.mjs +50 -2
  13. package/dist/catalog.mjs.map +1 -1
  14. package/dist/checkout.cjs +52 -3
  15. package/dist/checkout.cjs.map +1 -1
  16. package/dist/checkout.d.cts +1 -2
  17. package/dist/checkout.d.ts +1 -2
  18. package/dist/checkout.mjs +52 -3
  19. package/dist/checkout.mjs.map +1 -1
  20. package/dist/db-react.cjs +18 -6
  21. package/dist/db-react.cjs.map +1 -1
  22. package/dist/db-react.d.cts +35 -13
  23. package/dist/db-react.d.ts +35 -13
  24. package/dist/db-react.mjs +18 -6
  25. package/dist/db-react.mjs.map +1 -1
  26. package/dist/db.cjs +164 -56
  27. package/dist/db.cjs.map +1 -1
  28. package/dist/db.d.cts +1 -2
  29. package/dist/db.d.ts +1 -2
  30. package/dist/db.mjs +164 -56
  31. package/dist/db.mjs.map +1 -1
  32. package/dist/entitlements.cjs +50 -2
  33. package/dist/entitlements.cjs.map +1 -1
  34. package/dist/entitlements.d.cts +1 -2
  35. package/dist/entitlements.d.ts +1 -2
  36. package/dist/entitlements.mjs +50 -2
  37. package/dist/entitlements.mjs.map +1 -1
  38. package/dist/errors.cjs.map +1 -1
  39. package/dist/errors.d.cts +3 -1
  40. package/dist/errors.d.ts +3 -1
  41. package/dist/errors.mjs.map +1 -1
  42. package/dist/firestore-compat.cjs +411 -0
  43. package/dist/firestore-compat.cjs.map +1 -0
  44. package/dist/firestore-compat.d.cts +319 -0
  45. package/dist/firestore-compat.d.ts +319 -0
  46. package/dist/firestore-compat.mjs +383 -0
  47. package/dist/firestore-compat.mjs.map +1 -0
  48. package/dist/index.cjs +266 -66
  49. package/dist/index.cjs.map +1 -1
  50. package/dist/index.d.cts +9 -4
  51. package/dist/index.d.ts +9 -4
  52. package/dist/index.mjs +265 -67
  53. package/dist/index.mjs.map +1 -1
  54. package/dist/mocks.cjs.map +1 -1
  55. package/dist/mocks.d.cts +1 -2
  56. package/dist/mocks.d.ts +1 -2
  57. package/dist/mocks.mjs.map +1 -1
  58. package/dist/notifications.cjs +50 -2
  59. package/dist/notifications.cjs.map +1 -1
  60. package/dist/notifications.d.cts +1 -2
  61. package/dist/notifications.d.ts +1 -2
  62. package/dist/notifications.mjs +50 -2
  63. package/dist/notifications.mjs.map +1 -1
  64. package/dist/react.cjs +268 -2
  65. package/dist/react.cjs.map +1 -1
  66. package/dist/react.d.cts +135 -3
  67. package/dist/react.d.ts +135 -3
  68. package/dist/react.mjs +266 -3
  69. package/dist/react.mjs.map +1 -1
  70. package/dist/support.cjs +50 -2
  71. package/dist/support.cjs.map +1 -1
  72. package/dist/support.d.cts +1 -2
  73. package/dist/support.d.ts +1 -2
  74. package/dist/support.mjs +50 -2
  75. package/dist/support.mjs.map +1 -1
  76. package/dist/telemetry.cjs +65 -4
  77. package/dist/telemetry.cjs.map +1 -1
  78. package/dist/telemetry.d.cts +1 -2
  79. package/dist/telemetry.d.ts +1 -2
  80. package/dist/telemetry.mjs +65 -4
  81. package/dist/telemetry.mjs.map +1 -1
  82. package/dist/{types-D7zVkO3n.d.ts → types-B3XFHD4A.d.ts} +640 -2
  83. package/dist/{types-Dc4bjrrt.d.cts → types-DePdSU3P.d.cts} +640 -2
  84. package/dist/usage.cjs +50 -2
  85. package/dist/usage.cjs.map +1 -1
  86. package/dist/usage.d.cts +1 -2
  87. package/dist/usage.d.ts +1 -2
  88. package/dist/usage.mjs +50 -2
  89. package/dist/usage.mjs.map +1 -1
  90. package/dist/webhooks.cjs +83 -2
  91. package/dist/webhooks.cjs.map +1 -1
  92. package/dist/webhooks.d.cts +1 -2
  93. package/dist/webhooks.d.ts +1 -2
  94. package/dist/webhooks.mjs +83 -3
  95. package/dist/webhooks.mjs.map +1 -1
  96. package/package.json +156 -151
  97. package/dist/collection-ref-BDdfD87k.d.cts +0 -581
  98. package/dist/collection-ref-BDdfD87k.d.ts +0 -581
@@ -1,4 +1,3 @@
1
- import { c as DbCollectionRef$1, k as SyncState, U as Unsubscribe, C as ConflictRecord, R as RealtimeTransport } from './collection-ref-BDdfD87k.cjs';
2
1
  import { NodePgDatabase } from 'drizzle-orm/node-postgres';
3
2
  import { NeetruError } from './errors.cjs';
4
3
 
@@ -254,6 +253,65 @@ interface WebhooksNamespace {
254
253
  * inválida" (return false) de "ambiente não suporta" (throw).
255
254
  */
256
255
  declare function verifyWebhookSignature(payload: string | Uint8Array, signature: string | null | undefined, secret: string): Promise<boolean>;
256
+ /**
257
+ * Opções de `verifyWebhookRequest`.
258
+ */
259
+ interface VerifyWebhookRequestOptions {
260
+ /**
261
+ * Janela de tolerância em milliseconds para o skew de timestamp.
262
+ * Default: 300_000 (5 minutos), conforme documentado na spec de webhooks.
263
+ * Valores menores aumentam a proteção contra replay; valores maiores acomodam
264
+ * clocks com drift. Não use valores acima de 600_000 (10 min).
265
+ */
266
+ toleranceMs?: number;
267
+ }
268
+ /**
269
+ * Resultado de `verifyWebhookRequest`.
270
+ */
271
+ type VerifyWebhookRequestResult = {
272
+ ok: true;
273
+ } | {
274
+ ok: false;
275
+ reason: 'invalid_signature' | 'timestamp_missing' | 'timestamp_expired' | 'runtime_unsupported';
276
+ };
277
+ /**
278
+ * Helper server-only que combina:
279
+ * 1. Verificação de assinatura HMAC-SHA256 (`X-Neetru-Signature`)
280
+ * 2. **Replay-protection** via skew de timestamp (`X-Neetru-Timestamp`)
281
+ *
282
+ * Rejeita o request se o timestamp estiver fora da janela `toleranceMs`
283
+ * (default ±5 min). Isso fecha a janela de replay onde um atacante captura
284
+ * um request válido e o re-envia mais tarde.
285
+ *
286
+ * **Use este helper em vez de `verifyWebhookSignature` diretamente** — ele
287
+ * é a forma correta de validar um webhook Neetru em produção.
288
+ *
289
+ * @param payload Corpo cru da request (string ou Uint8Array).
290
+ * @param headers Headers do request (pode ser um `Headers` Web API ou
291
+ * `Record<string, string | undefined>`). Lidos: `X-Neetru-Signature` e
292
+ * `X-Neetru-Timestamp`.
293
+ * @param secret Secret registrado em `webhooks.register({ secret })`.
294
+ * @param options Opções adicionais (ex: `toleranceMs`).
295
+ * @returns `{ ok: true }` ou `{ ok: false, reason }` — nunca lança, a menos
296
+ * que o runtime não suporte WebCrypto (então propaga `NeetruError('runtime_unsupported')`).
297
+ *
298
+ * @example
299
+ * ```ts
300
+ * // Next.js Route Handler
301
+ * export async function POST(req: Request) {
302
+ * const raw = await req.text();
303
+ * const result = await verifyWebhookRequest(raw, req.headers, process.env.NEETRU_WEBHOOK_SECRET!);
304
+ * if (!result.ok) {
305
+ * console.warn('Webhook rejeitado:', result.reason);
306
+ * return new Response('unauthorized', { status: 401 });
307
+ * }
308
+ * const event = JSON.parse(raw);
309
+ * // ... handle event
310
+ * return Response.json({ ok: true });
311
+ * }
312
+ * ```
313
+ */
314
+ declare function verifyWebhookRequest(payload: string | Uint8Array, headers: Headers | Record<string, string | undefined>, secret: string, options?: VerifyWebhookRequestOptions): Promise<VerifyWebhookRequestResult>;
257
315
  declare function createWebhooksNamespace(config: ResolvedConfig): WebhooksNamespace;
258
316
  declare class MockWebhooks implements WebhooksNamespace {
259
317
  private endpoints;
@@ -266,6 +324,328 @@ declare class MockWebhooks implements WebhooksNamespace {
266
324
  test(id: string, _options?: WebhookScopeOptions): Promise<WebhookTestResult>;
267
325
  }
268
326
 
327
+ /**
328
+ * Tipos internos da camada offline do @neetru/sdk.
329
+ *
330
+ * Estes tipos SÃO internos ao módulo `db/offline/` — não fazem parte da
331
+ * superfície pública do SDK. A superfície pública é o `DbCollectionRef` e
332
+ * o `DbSnapshot` de `src/types.ts`.
333
+ *
334
+ * Derivados de:
335
+ * - I3-sdk-offline.md §3 (store schemas)
336
+ * - I3-sdk-offline.md §4 (fila de escritas)
337
+ * - I3-sdk-offline.md §5 (query descriptor)
338
+ * - I3-sdk-offline.md §7 (conflito LWW)
339
+ * - 02-sdk.md §3.2 (DbSnapshot, DbWhereFilter, DbQuery)
340
+ */
341
+ /** Identificador de um documento (string opaca). */
342
+ type DocId = string;
343
+ /** Identificador de uma mutação (UUID v4 client-generated). */
344
+ type MutationId = string;
345
+ /** Operações possíveis na fila de escritas. */
346
+ type MutationKind = 'add' | 'set' | 'update' | 'remove';
347
+ /**
348
+ * Uma mutação enfileirada (escrita durável pendente).
349
+ * Equivalente a `QueuedMutation` do I3 §3.3 (`mutations` store).
350
+ */
351
+ interface Mutation {
352
+ /** Número de sequência autoincrement — define a ordem total da fila. */
353
+ seq: number;
354
+ /** UUID v4 client-generated — chave de idempotência. */
355
+ mutationId: MutationId;
356
+ /** Nome da coleção. */
357
+ collection: string;
358
+ /** ID do documento alvo (client-gen para `add`, existente para os demais). */
359
+ docId: DocId;
360
+ /** Operação. */
361
+ op: MutationKind;
362
+ /** Dados da operação. `null` para `remove`. */
363
+ payload: Record<string, unknown> | null;
364
+ /**
365
+ * `serverVersion` que o cliente viu ao escrever.
366
+ * `null` para `add` (doc não existia) ou quando o cliente nunca sincronizou.
367
+ * Usado pelo servidor para detectar conflito (I3 §7.4).
368
+ */
369
+ baseVersion: string | null;
370
+ /** Epoch ms do relógio do CLIENTE — só para diagnóstico e ordenação local. */
371
+ enqueuedAt: number;
372
+ /** Número de tentativas de replay. */
373
+ attempts: number;
374
+ /** Último erro de replay (string). `null` se nenhuma tentativa ainda. */
375
+ lastError: string | null;
376
+ /** Estado de envio desta mutação. */
377
+ status: 'queued' | 'inflight' | 'failed';
378
+ /** Agrupa mutações de um `batch()` atômico. `null` se mutação individual. */
379
+ batchId: string | null;
380
+ }
381
+ /**
382
+ * Razão pela qual uma escrita foi descartada no LWW (I3 §3.3 `conflict_log`).
383
+ */
384
+ type ConflictReason = 'lww_server_newer' | 'rejected_permission' | 'rejected_validation';
385
+ /**
386
+ * Registro de uma escrita perdedora — gravado no `conflict_log` (I3 §3.3).
387
+ * Entregue ao produto via `onWriteResult` (status: `'superseded'`).
388
+ */
389
+ interface ConflictRecord {
390
+ id?: number;
391
+ collection: string;
392
+ docId: DocId;
393
+ mutationId: MutationId;
394
+ losingData: Record<string, unknown>;
395
+ winningData: Record<string, unknown>;
396
+ reason: ConflictReason;
397
+ detectedAt: number;
398
+ delivered: boolean;
399
+ }
400
+ /**
401
+ * Estado de sincronização da camada offline (I3 §10 / 02-sdk §3.4).
402
+ * Exposto em `client.db.syncState`.
403
+ *
404
+ * - `idle` — online e em repouso (sem sync ativo).
405
+ * - `syncing` — sync em progresso (push + pull + realtime).
406
+ * - `offline` — conexão perdida ou inexistente.
407
+ * - `error` — erro persistente após retries (reservado para uso futuro).
408
+ */
409
+ type SyncStatus = 'idle' | 'offline' | 'syncing' | 'error';
410
+ interface SyncState {
411
+ status: SyncStatus;
412
+ pendingWrites: number;
413
+ lastSyncedAt: number | null;
414
+ isLeaderTab: boolean;
415
+ }
416
+ /** Função para cancelar uma subscrição (onSnapshot, onDoc, onSyncStateChanged). */
417
+ type Unsubscribe = () => void;
418
+
419
+ /**
420
+ * SyncEngine — orquestrador da camada offline do @neetru/sdk.
421
+ *
422
+ * Implementa as 3 fases de sincronização definidas em I3 §6:
423
+ *
424
+ * FASE 1 — DRENAR A FILA (push)
425
+ * Envia mutações pendentes ao Core via SyncTransport.
426
+ * Cada mutação é marcada inflight, enviada e depois:
427
+ * - CONFIRMADA → removida da fila (markApplied)
428
+ * - SUPERADA (LWW)→ ConflictRecord gravado, cache atualizado
429
+ * - REJEITADA → resolveRejected + ConflictRecord + removida
430
+ * - FALHA TRANSIENTE → markRetry + PARA a drenagem (ordem preservada)
431
+ *
432
+ * FASE 2 — RECONCILIAR O CACHE (pull)
433
+ * Busca mudanças do servidor desde o watermark ou resume token.
434
+ * Cada documento passa pelo ConflictResolver (LWW) e é gravado no LocalStore.
435
+ * Changes são emitidos pelo ChangeBus.
436
+ * Se `resyncRequired` → aciona full resync em vez de pull incremental.
437
+ *
438
+ * FASE 3 — REABRIR LISTENERS (realtime)
439
+ * Atualiza SyncState para refletir que o cache está reconciliado.
440
+ * O ChangeBus já foi alimentado nas fases anteriores — os listeners
441
+ * ativos na camada superior (onSnapshot/onDoc) recebem snapshots frescos.
442
+ *
443
+ * Garantias:
444
+ * - SOMENTE a aba LÍDER executa o sync (TabCoordinator.isLeader()).
445
+ * - Re-entrante-safe: um sync em progresso bloqueia novos disparos.
446
+ * - Fail-closed: falha transiente retém a mutação; falha de pull não
447
+ * corrompe o cache nem o watermark.
448
+ * - Idempotente: o SyncTransport usa mutationId como chave de idempotência.
449
+ *
450
+ * O SyncTransport é INJETADO — a ligação real com o Core acontece na camada
451
+ * de adaptação, não aqui. Isso torna o SyncEngine 100% testável com fake.
452
+ */
453
+
454
+ /**
455
+ * Documento retornado pelo servidor em push/pull/resync.
456
+ */
457
+ interface ServerDoc {
458
+ collection: string;
459
+ id: string;
460
+ data: Record<string, unknown>;
461
+ serverVersion: string;
462
+ serverTimestamp: number;
463
+ /** `true` se o doc foi deletado no servidor. */
464
+ deleted: boolean;
465
+ }
466
+ /**
467
+ * Resultado individual de uma mutação enviada ao Core (FASE 1).
468
+ */
469
+ type MutationResult = {
470
+ mutationId: string;
471
+ outcome: 'confirmed';
472
+ serverVersion: string;
473
+ serverTimestamp: number;
474
+ } | {
475
+ mutationId: string;
476
+ outcome: 'superseded';
477
+ serverVersion: string;
478
+ serverTimestamp: number;
479
+ serverData: Record<string, unknown>;
480
+ } | {
481
+ mutationId: string;
482
+ outcome: 'rejected';
483
+ reason: 'rejected_permission' | 'rejected_validation';
484
+ serverVersion: string | null;
485
+ serverData: Record<string, unknown> | null;
486
+ };
487
+ /** Retorno de pushMutations. */
488
+ interface PushMutationsResult {
489
+ results: MutationResult[];
490
+ }
491
+ /** Retorno de pullChanges. */
492
+ interface PullChangesResult {
493
+ docs: ServerDoc[];
494
+ newWatermark: number | null;
495
+ /** `true` quando o resume token / watermark ficou inválido (gap grande). */
496
+ resyncRequired: boolean;
497
+ }
498
+ /** Retorno de fullResync. */
499
+ interface FullResyncResult {
500
+ docs: ServerDoc[];
501
+ newWatermark: number | null;
502
+ }
503
+ /**
504
+ * Contrato do transporte de sync — injetado no SyncEngine.
505
+ *
506
+ * A implementação real usa os endpoints `/api/sdk/v1/db/*` do Core.
507
+ * Em testes, usa-se um FakeSyncTransport.
508
+ *
509
+ * Per I3 §6:
510
+ * - `pushMutations` → FASE 1: envia mutações em ordem de seq.
511
+ * - `pullChanges` → FASE 2: busca delta desde o watermark/resumeToken.
512
+ * - `fullResync` → FASE 2 (fallback): lista completa das coleções ativas.
513
+ */
514
+ interface SyncTransport {
515
+ /**
516
+ * Envia um lote de mutações ao Core.
517
+ * O Core aplica, verifica conflito LWW e retorna um resultado por mutação.
518
+ * A ordem do array deve ser preservada (seq crescente).
519
+ */
520
+ pushMutations(mutations: Mutation[]): Promise<PushMutationsResult>;
521
+ /**
522
+ * Busca documentos modificados no servidor desde `sinceWatermark`.
523
+ * `sinceWatermark === null` indica primeiro sync (sem base conhecida).
524
+ * `resumeToken` é o token de change stream (nosql-vm); pode ser null.
525
+ *
526
+ * Retorna `resyncRequired: true` se o gap é grande demais para pull incremental.
527
+ */
528
+ pullChanges(sinceWatermark: number | null, resumeToken: string | null): Promise<PullChangesResult>;
529
+ /**
530
+ * Faz um full resync das coleções informadas.
531
+ * Retorna TODOS os documentos atuais (paginado internamente pelo transporte).
532
+ * Docs ausentes na resposta foram deletados durante o gap.
533
+ */
534
+ fullResync(collections: string[]): Promise<FullResyncResult>;
535
+ /**
536
+ * (Opcional) Registra uma subscription realtime para uma coleção específica.
537
+ *
538
+ * Implementado pelo WebSocket transport (nosql-vm): chama
539
+ * `NeetruRealtimeClient.subscribe()` e alimenta os frames delta/resync
540
+ * no `ChangeBus` da camada offline.
541
+ *
542
+ * Transportes REST e Firestore não implementam este método (undefined).
543
+ * O chamador (`DbCollectionRefImpl.onSnapshot`) verifica antes de invocar.
544
+ *
545
+ * @param collection - Nome da coleção a subscrever.
546
+ * @param onChange - Callback que recebe changes quando um delta/resync chega.
547
+ * `null` payload = resync full necessário.
548
+ * @returns Função de unsubscribe.
549
+ */
550
+ subscribeCollection?: (collection: string, onChange: (changes: Array<{
551
+ type: 'added' | 'modified' | 'removed';
552
+ docId: string;
553
+ data: Record<string, unknown> | null;
554
+ }>, needsResync: boolean) => void) => () => void;
555
+ }
556
+
557
+ /**
558
+ * field-value.ts — Sentinels de escrita server-side para o Mundo Documentos.
559
+ *
560
+ * Resolve duas lacunas confirmadas (bug_05aedb8b) da superfície `client.db`:
561
+ *
562
+ * 1. **Timestamp resolvido no servidor** — antes deste módulo, o produto que
563
+ * quisesse `createdAt`/`updatedAt` confiáveis tinha que usar `Date.now()`
564
+ * no CLIENTE, que é spoofável e sofre clock-skew. Agora:
565
+ *
566
+ * await orders.add({ total: 99, createdAt: serverTimestamp() });
567
+ *
568
+ * O marcador atravessa o transporte REST como um objeto JSON namespaceado
569
+ * e o Core o substitui por `FieldValue.serverTimestamp()` no momento da
570
+ * escrita — relógio do SERVIDOR, não do cliente.
571
+ *
572
+ * 2. **Increment atômico** — antes, `update(id, { n: x+1 })` era read-modify-write
573
+ * no cliente, sujeito a lost-update sob concorrência. Agora:
574
+ *
575
+ * await counters.update('global', { hits: increment(1) });
576
+ *
577
+ * O Core aplica `FieldValue.increment(1)` atomicamente no documento.
578
+ *
579
+ * ## Contrato de fio (wire format)
580
+ *
581
+ * Os sentinels são objetos JSON simples — sobrevivem `JSON.stringify` no
582
+ * transporte REST e ao `IndexedDB structured clone` da camada offline:
583
+ *
584
+ * serverTimestamp() → `{ "__neetru__": "serverTimestamp" }`
585
+ * increment(n) → `{ "__neetru__": "increment", "operand": n }`
586
+ *
587
+ * A chave-âncora `__neetru__` é deliberadamente improvável de colidir com
588
+ * dados de produto. O Core valida e resolve esses marcadores; qualquer objeto
589
+ * com `__neetru__` desconhecido é rejeitado server-side (fail-closed).
590
+ *
591
+ * ## Aplicação otimista (offline-first)
592
+ *
593
+ * A camada offline aplica uma APROXIMAÇÃO local imediata (para a UI não piscar):
594
+ * - `serverTimestamp()` → `Date.now()` local (substituído pelo valor real no sync).
595
+ * - `increment(n)` → soma `n` ao valor numérico atual do campo (0 se ausente).
596
+ * O valor canônico vem sempre do servidor no pull/confirm subsequente.
597
+ *
598
+ * @module
599
+ */
600
+ /** Chave-âncora dos marcadores de sentinel no wire format. */
601
+ declare const NEETRU_SENTINEL_KEY: "__neetru__";
602
+ /** Marcador de timestamp resolvido pelo servidor. */
603
+ interface ServerTimestampSentinel {
604
+ readonly __neetru__: 'serverTimestamp';
605
+ }
606
+ /** Marcador de incremento atômico aplicado pelo servidor. */
607
+ interface IncrementSentinel {
608
+ readonly __neetru__: 'increment';
609
+ /** Delta a somar (pode ser negativo para decremento). */
610
+ readonly operand: number;
611
+ }
612
+ /**
613
+ * União de todos os sentinels de campo. Use `serverTimestamp()` / `increment()`
614
+ * para construir — nunca monte o objeto à mão.
615
+ */
616
+ type FieldSentinel = ServerTimestampSentinel | IncrementSentinel;
617
+ /**
618
+ * Sentinel que instrui o servidor a gravar o timestamp do MOMENTO da escrita
619
+ * usando o relógio do servidor (não do cliente).
620
+ *
621
+ * @example
622
+ * ```ts
623
+ * import { serverTimestamp } from '@neetru/sdk/db';
624
+ * await orders.add({ total: 99, createdAt: serverTimestamp() });
625
+ * ```
626
+ */
627
+ declare function serverTimestamp(): ServerTimestampSentinel;
628
+ /**
629
+ * Sentinel que instrui o servidor a incrementar atomicamente o campo numérico
630
+ * por `n` (negativo decrementa). Se o campo não existir/não for numérico, o
631
+ * servidor o trata como `0` antes de somar (semântica `FieldValue.increment`).
632
+ *
633
+ * @param n — delta inteiro ou fracionário a somar.
634
+ * @example
635
+ * ```ts
636
+ * import { increment } from '@neetru/sdk/db';
637
+ * await counters.update('global', { hits: increment(1) });
638
+ * await wallet.update(uid, { balance: increment(-50) });
639
+ * ```
640
+ */
641
+ declare function increment(n: number): IncrementSentinel;
642
+ /** `true` se `v` é qualquer sentinel de campo Neetru. */
643
+ declare function isFieldSentinel(v: unknown): v is FieldSentinel;
644
+ /** `true` se `v` é o sentinel `serverTimestamp()`. */
645
+ declare function isServerTimestampSentinel(v: unknown): v is ServerTimestampSentinel;
646
+ /** `true` se `v` é o sentinel `increment(n)` com `operand` numérico válido. */
647
+ declare function isIncrementSentinel(v: unknown): v is IncrementSentinel;
648
+
269
649
  /**
270
650
  * NeetruDbError — classe de erro especializada para o módulo `db`.
271
651
  *
@@ -323,6 +703,264 @@ declare class NeetruDbError extends NeetruError {
323
703
  constructor(code: NeetruDbErrorCode, message: string, dbId?: string);
324
704
  }
325
705
 
706
+ /**
707
+ * DbCollectionRef — superfície de Documentos offline-first do @neetru/sdk (M2).
708
+ *
709
+ * Implementa a API pública conforme especificada em:
710
+ * - 02-sdk.md §3.2 (contrato TypeScript DbCollectionRef)
711
+ * - I2-mundo-documentos.md §3.2 (superfície dupla CRUD + realtime)
712
+ * - I3-sdk-offline.md §10 (camada offline, lifecycle, SyncState)
713
+ *
714
+ * ### Semântica de Promise de escrita (02-sdk.md §3.2 nota negrito)
715
+ * `add/set/update/remove` resolvem quando a escrita está **durável localmente**
716
+ * (cache + fila), NÃO quando o servidor confirma. Isso é offline-first por design:
717
+ * sem rede, `add()` resolve imediatamente. Para saber quando sincronizou, o produto
718
+ * observa `onWriteResult` (future / SyncEngine). A Promise só rejeita por:
719
+ * - validação síncrona (nome de coleção inválido, id vazio)
720
+ * - `offline_quota_exceeded` (IndexedDB cheio)
721
+ *
722
+ * ### Injeção do transporte realtime
723
+ * O `RealtimeTransport` é injetado via `createOfflineDocumentsNamespace({ transport })`.
724
+ * A implementação atual usa o `SyncEngine` para push/pull (sync offline ↔ servidor).
725
+ * Transportes futuros (Firestore Web SDK / WebSocket gateway) são injetados aqui —
726
+ * sem alterar a superfície pública.
727
+ */
728
+
729
+ /**
730
+ * Permite que cada campo de `T` aceite, além do seu tipo declarado, um sentinel
731
+ * de escrita server-side (`serverTimestamp()` / `increment()`).
732
+ *
733
+ * Sem isso, um produto que tipa `createdAt: number` veria erro de tipo ao passar
734
+ * `serverTimestamp()`. Esta projeção mantém o autocomplete dos campos do produto
735
+ * e ainda autoriza os sentinels onde fizer sentido.
736
+ *
737
+ * @example
738
+ * ```ts
739
+ * interface Order { total: number; createdAt: number; hits: number }
740
+ * orders.add({ total: 10, createdAt: serverTimestamp(), hits: increment(1) });
741
+ * ```
742
+ */
743
+ type WithFieldSentinels<T> = {
744
+ [K in keyof T]: T[K] | FieldSentinel;
745
+ };
746
+ interface DbWhereFilter$1 {
747
+ field: string;
748
+ op: '==' | '!=' | '<' | '<=' | '>' | '>=' | 'array-contains' | 'in' | 'not-in';
749
+ value: unknown;
750
+ }
751
+ interface DbQuery$1 {
752
+ where?: DbWhereFilter$1[];
753
+ orderBy?: {
754
+ field: string;
755
+ direction?: 'asc' | 'desc';
756
+ };
757
+ /** Máximo de documentos. Default 20, max 500. */
758
+ limit?: number;
759
+ /** Cursor opaco — devolvido em `DbListResult.nextCursor`. */
760
+ cursor?: string;
761
+ }
762
+ interface DbDoc<T = Record<string, unknown>> {
763
+ id: string;
764
+ data: T;
765
+ }
766
+ interface DbListResult<T = Record<string, unknown>> {
767
+ docs: DbDoc<T>[];
768
+ nextCursor: string | null;
769
+ fromCache: boolean;
770
+ stale: boolean;
771
+ hasPendingWrites: boolean;
772
+ changes: Array<{
773
+ type: 'added' | 'modified' | 'removed';
774
+ doc: DbDoc<T>;
775
+ }>;
776
+ }
777
+ interface DbGetResult<T = Record<string, unknown>> {
778
+ docs: Array<DbDoc<T>>;
779
+ fromCache: boolean;
780
+ stale: boolean;
781
+ hasPendingWrites: boolean;
782
+ changes: Array<{
783
+ type: 'added' | 'modified' | 'removed';
784
+ doc: DbDoc<T>;
785
+ }>;
786
+ }
787
+ type DbChangeType = 'added' | 'modified' | 'removed';
788
+ /**
789
+ * Operação atômica para uso em `DbCollectionRef.batch()`.
790
+ *
791
+ * Discriminated union pelo campo `kind` — cada variante deixa claro
792
+ * quais campos são obrigatórios:
793
+ *
794
+ * - `add` — insere com id gerado pelo cliente; `data` obrigatório.
795
+ * - `set` — cria ou sobrescreve pelo `id`; `data` obrigatório.
796
+ * - `update` — mescla campos em doc existente pelo `id`; `data` obrigatório.
797
+ * - `remove` — tombstone pelo `id`; sem `data`.
798
+ *
799
+ * @example
800
+ * ```ts
801
+ * const ops: DbBatchOp[] = [
802
+ * { kind: 'add', data: { name: 'produto A', price: 10 } },
803
+ * { kind: 'set', id: 'sku-1', data: { name: 'produto B', price: 20 } },
804
+ * { kind: 'update', id: 'sku-2', data: { price: 25 } },
805
+ * { kind: 'remove', id: 'sku-3' },
806
+ * ];
807
+ * await col.batch(ops);
808
+ * ```
809
+ */
810
+ type DbBatchOp<T = Record<string, unknown>> = {
811
+ kind: 'add';
812
+ data: WithFieldSentinels<Omit<T, 'id'>>;
813
+ } | {
814
+ kind: 'set';
815
+ id: string;
816
+ data: WithFieldSentinels<Omit<T, 'id'>>;
817
+ } | {
818
+ kind: 'update';
819
+ id: string;
820
+ data: Partial<WithFieldSentinels<Omit<T, 'id'>>>;
821
+ } | {
822
+ kind: 'remove';
823
+ id: string;
824
+ };
825
+ /**
826
+ * Contrato do transporte de realtime — injetado no namespace.
827
+ *
828
+ * A implementação real usa os endpoints `/api/sdk/v1/db/*` do Core.
829
+ * Em testes, usa-se um fake in-memory.
830
+ *
831
+ * Este é o **seam de injeção** que conecta a camada offline ao servidor real.
832
+ * Transportes futuros (Firestore Web SDK direto / WebSocket gateway) são
833
+ * implementados aqui, sem modificar a superfície pública `DbCollectionRef`.
834
+ */
835
+ type RealtimeTransport = SyncTransport;
836
+ /**
837
+ * Referência a um documento específico.
838
+ * Parte da superfície `DbCollectionRef.doc(id)`.
839
+ *
840
+ * Expõe as propriedades de identidade do documento de forma análoga ao
841
+ * `DocumentReference` do Firestore SDK, evitando a necessidade de adaptadores
842
+ * externos que reattachavam `collection` + `id` manualmente.
843
+ *
844
+ * @example
845
+ * ```ts
846
+ * const ref = orders.doc('order-123');
847
+ * console.log(ref.id); // "order-123"
848
+ * console.log(ref.collection); // "orders"
849
+ * console.log(ref.path); // "orders/order-123"
850
+ * ```
851
+ */
852
+ interface DbDocRef<T = Record<string, unknown>> {
853
+ /** ID do documento dentro da coleção. */
854
+ readonly id: string;
855
+ /** Caminho completo no formato "collection/docId". */
856
+ readonly path: string;
857
+ /** Nome da coleção parent. */
858
+ readonly collection: string;
859
+ get(): Promise<DbGetResult<T> | null>;
860
+ set(data: WithFieldSentinels<Omit<T, 'id'>>): Promise<{
861
+ ok: true;
862
+ }>;
863
+ update(data: Partial<WithFieldSentinels<Omit<T, 'id'>>>): Promise<{
864
+ ok: true;
865
+ }>;
866
+ remove(): Promise<{
867
+ ok: true;
868
+ }>;
869
+ onSnapshot(cb: (snap: DbGetResult<T> | null) => void): Unsubscribe;
870
+ }
871
+ /**
872
+ * Referência a uma coleção — superfície dupla CRUD + realtime.
873
+ *
874
+ * Conforme 02-sdk.md §3.2 / I2 §3.2.
875
+ */
876
+ interface DbCollectionRef$1<T = Record<string, unknown>> {
877
+ /** Retorna snapshot de um documento. `null` se não existir (nem no cache). */
878
+ get(id: string): Promise<DbGetResult<T> | null>;
879
+ /** Lista documentos com query opcional. */
880
+ list(q?: DbQuery$1): Promise<DbListResult<T>>;
881
+ /** Adiciona doc com id gerado pelo cliente (UUID v4). Durável localmente. */
882
+ add(data: WithFieldSentinels<Omit<T, 'id'>>): Promise<{
883
+ ok: true;
884
+ id: string;
885
+ }>;
886
+ /** Cria ou sobrescreve um doc pelo id. Durável localmente. */
887
+ set(id: string, data: WithFieldSentinels<Omit<T, 'id'>>): Promise<{
888
+ ok: true;
889
+ }>;
890
+ /** Atualiza campos de um doc (merge). Durável localmente. */
891
+ update(id: string, data: Partial<WithFieldSentinels<Omit<T, 'id'>>>): Promise<{
892
+ ok: true;
893
+ }>;
894
+ /** Remove um doc (tombstone local). Durável localmente. */
895
+ remove(id: string): Promise<{
896
+ ok: true;
897
+ }>;
898
+ /** Aplica múltiplas operações atomicamente na fila. */
899
+ batch(ops: DbBatchOp<T>[]): Promise<{
900
+ ok: true;
901
+ }>;
902
+ /** Escuta um documento específico. */
903
+ onDoc(id: string, cb: (doc: T | null) => void): Unsubscribe;
904
+ /** Escuta a coleção inteira (ou subconjunto via query). */
905
+ onSnapshot(q: DbQuery$1 | undefined, cb: (snap: DbListResult<T>) => void): Unsubscribe;
906
+ /** Retorna uma referência ao documento `id`. */
907
+ doc(id: string): DbDocRef<T>;
908
+ }
909
+ /**
910
+ * Namespace `db.documents` — ponto de entrada para coleções de documentos.
911
+ *
912
+ * Analogia a `NeetruDb` de 02-sdk.md §3.1 mas escoped para o Mundo Documentos.
913
+ */
914
+ interface NeetruDbDocuments {
915
+ collection<T = Record<string, unknown>>(name: string): DbCollectionRef$1<T>;
916
+ /** Estado atual de sincronização (offline/online/syncing). */
917
+ readonly syncState: SyncState;
918
+ onSyncStateChanged(cb: (s: SyncState) => void): Unsubscribe;
919
+ /** Força flush da fila de mutações pendentes. */
920
+ flush(): Promise<void>;
921
+ /** Limpa o cache local (IndexedDB) — operação destrutiva. */
922
+ clearCache(): Promise<void>;
923
+ /**
924
+ * Retorna os registros do log de conflitos LWW gravados pelo SyncEngine.
925
+ * Um conflito ocorre quando a mutação local é descartada porque o servidor
926
+ * já avançou a versão do documento (Last-Write-Wins).
927
+ */
928
+ getConflicts(): Promise<ConflictRecord[]>;
929
+ }
930
+ interface CreateOfflineDocumentsOptions {
931
+ /**
932
+ * Nome do banco IndexedDB — deve ser único por produto × dbId × env.
933
+ * Convenção: `neetru-db__{productSlug}__{dbId}__{env}`.
934
+ */
935
+ dbName: string;
936
+ /**
937
+ * Transporte de sync — implementa `SyncTransport` do SyncEngine.
938
+ * Este é o **seam de injeção** do transporte real.
939
+ *
940
+ * Implementações possíveis:
941
+ * - `RestSyncTransport` — REST → Core (padrão em staging/prod)
942
+ * - `FirestoreRealtimeTransport` — Firestore Web SDK direto (engine=firestore)
943
+ * - `WebSocketTransport` — WebSocket → gateway (engine=nosql-vm)
944
+ * - `FakeRealtimeTransport` — in-memory (testes)
945
+ */
946
+ transport: RealtimeTransport;
947
+ /**
948
+ * Estado inicial de conectividade.
949
+ * Default: `navigator.onLine` se disponível, senão `true`.
950
+ */
951
+ startOnline?: boolean;
952
+ /**
953
+ * `true` = sem contenção multi-aba (sempre líder).
954
+ * Padrão em testes; em produção usa Web Locks.
955
+ */
956
+ singleTab?: boolean;
957
+ /**
958
+ * Intervalo do sync periódico em ms. `0` desativa.
959
+ * Default: 5 * 60 * 1000 (5min).
960
+ */
961
+ periodicSyncIntervalMs?: number;
962
+ }
963
+
326
964
  declare const DELTA_OPS: readonly ["delta", "resync", "stale", "error", "pong", "drain"];
327
965
  type DeltaOp = (typeof DELTA_OPS)[number];
328
966
  interface DbQuery {
@@ -1515,4 +2153,4 @@ interface CatalogListOptions {
1515
2153
  includeDrafts?: boolean;
1516
2154
  }
1517
2155
 
1518
- export { type UsageQuota as $, type AuthNamespace as A, type ProductNotification as B, type CatalogListOptions as C, DEFAULT_BASE_URL as D, type EntitlementCheck as E, type ProductNotificationSeverity as F, type ProductPlan as G, type ProductStatus as H, type ResolvedConfig as I, type SignInOptions as J, type SqlOptions as K, type ListNotificationsOptions as L, MockCheckout as M, type NeetruClient as N, type SupportNamespace as O, type Product as P, type SupportSeverity as Q, type RegisterWebhookInput as R, type SendNotificationInput as S, type SupportStatus as T, type SupportTicket as U, type TelemetryEventAck as V, type TelemetryEventInput as W, type TelemetryLogAck as X, type TelemetryLogInput as Y, type UsageEventInput as Z, type UsageNamespace as _, type AuthStateListener as a, type VerifyTokenOptions as a0, type WebhookEndpoint as a1, type WebhookEvent as a2, type WebhookScopeOptions as a3, type WebhookTestResult as a4, type WebhooksNamespace as a5, createCheckoutNamespace as a6, createNeetruDb as a7, createNotificationsNamespace as a8, createRestSyncTransport as a9, createWebhooksNamespace as aa, getWebSocketRealtimeClient as ab, verifyWebhookSignature as ac, 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 NeetruDbTransport as u, type NeetruEnv as v, type NeetruSqlClient as w, type NeetruUser as x, type NotificationScopeOptions as y, type NotificationsNamespace as z };
2156
+ export { type RealtimeTransport as $, type AuthNamespace as A, type NeetruClientConfig as B, type CatalogListOptions as C, DEFAULT_BASE_URL as D, type EntitlementCheck as E, type FieldSentinel as F, type NeetruDb as G, type NeetruDbDocuments as H, type IncrementSentinel as I, type NeetruDbEngine as J, NeetruDbError as K, type ListNotificationsOptions as L, MockCheckout as M, NEETRU_SENTINEL_KEY as N, type NeetruDbErrorCode as O, type NeetruDbOptions as P, type NeetruDbTransport as Q, type NeetruEnv as R, type NeetruSqlClient as S, type NeetruUser as T, type NotificationScopeOptions as U, type NotificationsNamespace as V, type Product as W, type ProductNotification as X, type ProductNotificationSeverity as Y, type ProductPlan as Z, type ProductStatus as _, type AuthStateListener as a, type RegisterWebhookInput as a0, type ResolvedConfig as a1, type SendNotificationInput as a2, type ServerTimestampSentinel as a3, type SignInOptions as a4, type SqlOptions as a5, type SupportNamespace as a6, type SupportSeverity as a7, type SupportStatus as a8, type SupportTicket as a9, isIncrementSentinel as aA, isServerTimestampSentinel as aB, serverTimestamp as aC, verifyWebhookRequest as aD, verifyWebhookSignature as aE, type SyncState as aa, type TelemetryEventAck as ab, type TelemetryEventInput as ac, type TelemetryLogAck as ad, type TelemetryLogInput as ae, type Unsubscribe as af, type UsageEventInput as ag, type UsageNamespace as ah, type UsageQuota as ai, type VerifyTokenOptions as aj, type VerifyWebhookRequestOptions as ak, type VerifyWebhookRequestResult as al, type WebhookEndpoint as am, type WebhookEvent as an, type WebhookScopeOptions as ao, type WebhookTestResult as ap, type WebhooksNamespace as aq, type WithFieldSentinels as ar, createCheckoutNamespace as as, createNeetruDb as at, createNotificationsNamespace as au, createRestSyncTransport as av, createWebhooksNamespace as aw, getWebSocketRealtimeClient as ax, increment as ay, isFieldSentinel as az, 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 ConflictRecord as i, type CreateOfflineDocumentsOptions as j, type CreateTicketInput as k, type DbBatchOp as l, type DbChangeType as m, type DbCollectionRef$1 as n, type DbDoc as o, type DbDocRef as p, type DbGetResult as q, type DbListResult as r, type DbNamespace as s, type DbQuery$1 as t, type DbSqlLease as u, type DbWhereFilter as v, type DbWhereFilter$1 as w, MockNotifications as x, MockWebhooks as y, type NeetruClient as z };