@neetru/sdk 2.3.3 → 2.3.5

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 CHANGED
@@ -1,483 +1,295 @@
1
- # Changelog
2
-
3
- All notable changes to `@neetru/sdk` will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [2.3.3] - 2026-05-26 — **fix(mocks): pool.types.getTypeParser identity parserbug_44bcc99f [HIGH]**
9
-
10
- ### Corrigido
11
-
12
- - **bug_44bcc99f9d3140d78abd8f7810d1afa4 (HIGH) — `MockDb.sql()` queries Drizzle falhavam em runtime.**
13
- Followup do `bug_a7f2ae25` (SDK 2.3.2): `sql()` retornava handle Drizzle, mas qualquer query (`INSERT`, `SELECT`, etc) jogava `Error: Not supported: getTypeParser is not supported`. `pg-mem` não implementa `pool.types.getTypeParser(oid)` que `drizzle-orm/node-postgres/session.js` chama em TODA query.
14
- Fix: monkey-patch identity parser no Pool retornado por `pg-mem.adapters.createPg()` antes de passar pro Drizzle. Types retornam o valor sem transformação (suficiente pra tests in-memory; produção usa `pg` real com parsers nativos).
15
- 3 linhas. Non-breaking. Tests E2E Drizzle in-memory destravados.
16
-
17
- ## [2.3.2] - 2026-05-26 — **fix(mocks): MockDb.sql() implementado in-memory via pg-mem — bug_a7f2ae25 [HIGH]**
18
-
19
- ### Corrigido
20
-
21
- - **bug_a7f2ae25fda54129b52b6dd55aaa6050 (HIGH) — `MockDb.sql()` lançava `db_unavailable` em vez de implementar Drizzle in-memory.**
22
- Bloqueava vitest server-side dos consumers (pdv-agiliza Fase 4 migration). Política do projeto proíbe workaround instalar `pg-mem` no consumer.
23
- Fix: `MockDb.sql(schema, options)` agora carrega `pg-mem` lazy + cria `NodePgDatabase<TSchema>` Drizzle apontando pra DB in-memory. Suporta `options.initSql: string | string[]` pra rodar DDL no setup (CREATE TABLE etc).
24
- `pg-mem` adicionado como `peerDependency optional` (com `peerDependenciesMeta.optional=true`) + `devDependency` interno. Consumers que querem mock SQL: `npm install -D pg-mem drizzle-orm pg`.
25
- **Não-breaking:** signature `sql(schema, options?)` mantida. SqlOptions ganhou `initSql?` opcional (ignorado por lease real em produção). Comportamento default — `sql()` sem `initSql` — funciona em ambos modos.
26
-
27
- ## [2.3.1] - 2026-05-26 **fix(deps): drizzle-orm + pg como peerDependencies — bug_42ae9f80 [CRITICAL]**
28
-
29
- ### Corrigido
30
-
31
- - **bug_42ae9f80b2884bd583c5246771f7730c (CRITICAL) — `drizzle-orm` em `dependencies` causava type collision com versão do consumer.**
32
- Quando o projeto consumer instalava `drizzle-orm` direto e tentava `client.db.sql(schema)`, o TypeScript via 2 cópias diferentes (uma nested em `node_modules/@neetru/sdk/node_modules/drizzle-orm`, outra no consumer) e reclamava: `'Property config is protected but type Column<...> is not a class derived from Column<...>'` + `'shouldInlineParams private'`. Bloqueava Fases 2-8 da migração pdv-agiliza pra `vm-postgres-single`.
33
- Fix: movidas `drizzle-orm` e `pg` de `dependencies` pra `peerDependencies` (com `peerDependenciesMeta.optional: true`). Consumer instala 1 versão, todos os tipos convergem. Devkit mantém `drizzle-orm` + `pg` em `devDependencies` pra build/test internos.
34
- **Breaking expected on consumer side:** se o projeto consumer NÃO tinha `drizzle-orm` em deps mas usava `client.db.sql()`, agora precisa `npm install drizzle-orm pg`. Consumers que só usam transport REST/Firestore não são afetados (peerDeps optional).
35
-
36
- ## [2.3.0] - 2026-05-25 — **feat(db-config): renomeia engine→transport com deprecation bug_ed9da7fd [HIGH]**
37
-
38
- Fix do bug `bug_ed9da7fd11194eac87f0eb9c75e5fad9` (HIGH) — colisão de terminologia: `engine` no SDK
39
- configurava o transporte SDK↔Core (REST/Firestore/WebSocket), mas `engine` no admin database (CLI
40
- `neetru admin database create --engine=...`) configura o storage engine (firestore-instance,
41
- cloud-sql-postgres, vm-postgres-single…). Devs colocavam `firestore-instance` no SDK e quebravam.
42
-
43
- ### Added
44
-
45
- - **`NeetruDbTransport`** — novo tipo público canônico para o campo de transporte SDK↔Core.
46
- Valores: `'rest' | 'firestore' | 'nosql-vm'` (idênticos aos de `NeetruDbEngine`).
47
-
48
- - **`NeetruDbOptions.transport`** novo campo canônico que substitui `engine`.
49
- ```ts
50
- // v2.3+ (preferido):
51
- createNeetruClient({ db: { transport: 'rest' } });
52
- createNeetruClient({ db: { transport: 'firestore' } });
53
- createNeetruClient({ db: { transport: 'nosql-vm', realtimeGatewayUrl: '...' } });
54
- ```
55
-
56
- ### Deprecated
57
-
58
- - **`NeetruDbOptions.engine`** depreciado em v2.3, será removido em v3.0.
59
- Continua funcionando: se `transport` estiver ausente e `engine` presente, o valor de `engine`
60
- é usado como transport com `console.warn` de deprecação.
61
-
62
- ```ts
63
- // v2.x (depreciado — emite console.warn):
64
- createNeetruClient({ db: { engine: 'firestore' } });
65
- // ⚠ [neetru/sdk] NeetruDbOptions.engine está depreciado...
66
- ```
67
-
68
- - **`NeetruDbEngine`** depreciado em v2.3, será removido em v3.0.
69
- Agora é alias de `NeetruDbTransport`. Use `NeetruDbTransport` em código novo.
70
-
71
- ### Behavior
72
-
73
- - `transport` e `engine` podem coexistir: `transport` sempre prevalece, sem warn.
74
- - Sem `transport` nem `engine`: default `'rest'`, sem warn (comportamento idêntico ao v2.x).
75
- - A relação 1:N entre storage engines e transports é documentada no JSDoc de `NeetruDbTransport`:
76
- qualquer storage engine (cloud-sql-postgres, vm-postgres-single…) pode ser acessado com
77
- `transport: 'rest'`; `transport: 'firestore'` e `transport: 'nosql-vm'` são para bancos
78
- documentais com realtime nativo.
79
-
80
- ### Tests
81
-
82
- - 5 testes novos em `src/__tests__/db-real.test.ts`:
83
- `transport=firestore`, `transport=nosql-vm`, `transport=rest`, colisão `transport+engine`
84
- (transport prevalece, sem warn), default sem campos (sem warn).
85
- - Testes de regressão: `engine=firestore` e `engine=nosql-vm` legados ainda passam + emitem warn.
86
-
87
- ## [2.2.1] - 2026-05-25 — **fix(offline-docs): fallback MemoryStore em Node — bug_ba287a CRITICAL**
88
-
89
- Fix do bug `bug_ba287aefdf6c4327b026d588a239ad06` (CRITICAL) — `client.db.collection().set/add/update`
90
- lançava `ReferenceError: indexedDB is not defined` em Node.js (reportado por pdv-agiliza prod).
91
-
92
- ### Fixed
93
-
94
- - **`createOfflineDocumentsNamespace` — auto-detect Node runtime e usa `MemoryStore`**.
95
- A função agora verifica `typeof indexedDB === 'undefined' || typeof window === 'undefined'`
96
- antes de instanciar `LocalStore` (que depende de `idb` / IndexedDB). Em Node/Bun/Edge Workers
97
- sem IDB, cai automaticamente em `MemoryStore` — implementação in-process com a mesma API,
98
- sem nenhuma flag explícita necessária. Ops de documentos (`set/add/update/remove/get/list`,
99
- `batch`, `onSnapshot`, `onDoc`) funcionam normalmente em Node; dados não persistem entre
100
- reinicializações do processo (comportamento esperado em SSR/server-side).
101
-
102
- ### Added
103
-
104
- - **`src/db/offline/memory-store.ts`** `MemoryStore`: implementação in-memory do contrato
105
- público de `LocalStore`. Cobre todas as 5 stores (documents, mutations, query\_cache,
106
- sync\_meta, conflict\_log) com Maps/Arrays em memória.
107
-
108
- ### Tests
109
-
110
- - **`src/__tests__/offline-docs-node-compat.test.ts`** 5 casos de regressão:
111
- verifica que a stack offline não lança `ReferenceError` em ambiente sem IDB,
112
- e que `set/add/update` + `get` roundtrips funcionam in-memory.
113
-
114
- ## [2.2.0] - 2026-05-25 **auth.verifyToken validação server-side de id_token via JWKS [feature]**
115
-
116
- Fix do bug `bug_a7ef1702680044e3994b74bdccecb2ab` — resolve P0-1 audit security pdv-agiliza.
117
-
118
- ### Added
119
-
120
- - **`client.auth.verifyToken(token, options?): Promise<NeetruUser>`** valida um id_token JWT
121
- emitido por `auth.neetru.com` server-side, sem round-trip de rede por request. Usa JWKS local
122
- cacheado (default TTL 1h, configurável via `options.jwksCacheTtlMs`).
123
-
124
- Erros tipados novos (`NeetruErrorCode`):
125
- - `token_expired` — `exp` no passado
126
- - `token_invalid_signature` — assinatura não verificada pela JWKS
127
- - `token_invalid_audience` `aud` não bate com o `clientId` extraído do `apiKey`
128
- - `token_invalid_issuer` `iss` não é `https://auth.neetru.com`
129
-
130
- Dependência nova: `jose` (Vercel/Cloudflare-friendly, sem Node nativo).
131
-
132
- - **`MockAuth.verifyToken(token)`**decodifica JWT sem verificar assinatura (útil em testes).
133
- Aceita `__mockVerifyToken(fn)` pra injetar falhas determinísticas.
134
-
135
- - **`VerifyTokenOptions`** exportado em `index.ts` pra consumers tiparem as options.
136
-
137
- ### Usage
138
-
139
- ```ts
140
- // Next.js route handler
141
- import { createNeetruClient } from '@neetru/sdk';
142
- const client = createNeetruClient({ apiKey: 'nrt_a1b2c3_...', env: 'prod' });
143
-
144
- export async function POST(req: Request) {
145
- const token = req.cookies.get('neetru-session')?.value ?? '';
146
- try {
147
- const user = await client.auth.verifyToken(token);
148
- // user.uid, user.email, user.isStaff, user.tenantId disponíveis
149
- } catch (err) {
150
- return new Response('Unauthorized', { status: 401 });
151
- }
152
- }
153
- ```
154
-
155
- ## [Unreleased]
156
-
157
- ### Planned (post-2.2)
158
- - size-limit budget no CI (<12KB gz core com db namespace).
159
- - CDN distribution (`cdn.neetru.com/sdk/v2/`).
160
- - Endpoint batch `/api/v1/sdk/telemetry/batch` (substitui drenagem N×1 do `track()` por 1 request).
161
-
162
- ## [2.1.1] - 2026-05-25 — **DbDocRef identity + DbBatchOp discriminated union [patch]**
163
-
164
- Dois fixes LOW reportados por pdv-agiliza (tickets `bug_97b3f0488d1c49cf8c5f94e5d544289b` + `bug_6aab9adc182640218652a65cdcb71b63`).
165
- Sem breaking changestodos os usos de `doc()` existentes continuam funcionando.
166
-
167
- ### Fixed
168
-
169
- - **`DbDocRef.id / .path / .collection`** (fix #13, ticket `bug_97b3f0488d1c49cf8c5f94e5d544289b`):
170
- `DbDocRef` agora expõe três propriedades readonly de identidade:
171
- - `id: string` — ID do documento dentro da coleção.
172
- - `path: string` — Caminho completo `"colecao/docId"`.
173
- - `collection: string` — Nome da coleção parent.
174
- Análogo ao `DocumentReference.id/.path` do Firestore Web SDK. Elimina a necessidade
175
- de adaptadores externos que reattachavam `collection + id` manualmente.
176
- Implementado em `DbCollectionRefImpl.doc()` (offline), `createLazyCollectionRef.doc()`
177
- (client-db) e `MockDb.doc()` (mocks).
178
-
179
- - **`DbBatchOp` discriminated union** (fix #14, ticket `bug_6aab9adc182640218652a65cdcb71b63`):
180
- `DbBatchOp` foi convertido de `interface` opaca (campo `op: 'add'|'set'|'update'|'remove'`
181
- com `id?`/`data?` opcionais) para **discriminated union** pelo campo `kind`:
182
- ```ts
183
- type DbBatchOp<T = Record<string, unknown>> =
184
- | { kind: 'add'; data: Omit<T, 'id'> }
185
- | { kind: 'set'; id: string; data: Omit<T, 'id'> }
186
- | { kind: 'update'; id: string; data: Partial<Omit<T, 'id'>> }
187
- | { kind: 'remove'; id: string };
188
- ```
189
- O `.d.ts` público agora expõe claramente quais campos são obrigatórios por variante.
190
- O campo `collection` foi removido da operação (sempre opera na coleção do `DbCollectionRef`).
191
-
192
- ### Migration (2.0.0 2.1.1)
193
-
194
- Callers que usavam `batch()` devem trocar `op` por `kind` e remover o campo `collection`:
195
-
196
- ```ts
197
- // antes (2.0.0)
198
- col.batch([
199
- { op: 'add', collection: 'orders', data: { total: 10 } },
200
- { op: 'set', collection: 'orders', id: 'x', data: { total: 20 } },
201
- { op: 'update', collection: 'orders', id: 'y', data: { total: 30 } },
202
- { op: 'remove', collection: 'orders', id: 'z' },
203
- ]);
204
-
205
- // depois (2.1.1)
206
- col.batch([
207
- { kind: 'add', data: { total: 10 } },
208
- { kind: 'set', id: 'x', data: { total: 20 } },
209
- { kind: 'update', id: 'y', data: { total: 30 } },
210
- { kind: 'remove', id: 'z' },
211
- ]);
212
- ```
213
-
214
- ## [2.0.0] - 2026-05-22 **client.db NeetruDb (Documentos engine-aware + SQL) [M2]**
215
-
216
- Major bump — breaking changes no namespace `db`. Liga todos os building blocks M2 na superfície pública.
217
-
218
- ### Breaking Changes
219
-
220
- - **`client.db`** troca de `DbNamespace` (v0.3 REST simples) para `NeetruDb` (v2.0 offline-first engine-aware).
221
- - `collection().list()` agora retorna `DbListResult<T>` (com `docs`, `nextCursor`, `fromCache`, `stale`, `hasPendingWrites`, `changes`) em vez de `T[]`.
222
- - `collection().get(id)` retorna `DbGetResult<T> | null` em vez de `T | null`.
223
- - `db_unavailable` não significa mais "sem rede" — só ciclo de vida do banco. Offline é transparente.
224
- - Novos métodos obrigatórios na superfície: `sql()`, `syncState`, `flush()`, `clearCache()`, `getConflicts()`, `onSyncStateChanged()`, `batch()`, `onDoc()`, `onSnapshot()`, `doc()`.
225
- - **`db.sql(schema)` lança `NeetruDbError('db_unavailable')` em `NEETRU_ENV=dev`** sem Postgres local, sem acesso SQL. Use `neetru dev` para subir o container.
226
- - **`initNeetru`** mantido como stub funcional mas não mapeia mais `apiUrl → baseUrl`. Remover em v3.0.
227
- - **`MockDb`** foi atualizado para implementar `NeetruDb` (v2.0) em vez de `DbNamespace` (v0.3).
228
-
229
- ### Added
230
-
231
- - **`client.db.collection<T>(name)`** retorna `DbCollectionRef<T>` offline-first, engine-aware. Funciona sem rede. Sync automático quando conectado.
232
- - CRUD: `add`, `set`, `update`, `remove`, `get`, `list` (com query, cursor, orderBy, where, limit).
233
- - Batch: `batch(ops)` — operações atômicas (client-side).
234
- - Realtime: `onSnapshot(query, cb)`, `onDoc(id, cb)` subscriptions permanentes, delivery imediato do cache.
235
- - Doc ref: `doc(id).get()`, `.set()`, `.update()`, `.remove()`, `.onSnapshot()`.
236
- - **`client.db.sql<TSchema>(schema)`** — lease SQL. Retorna `NeetruSqlClient<TSchema>` com `orm` (Drizzle `NodePgDatabase`) + `transaction()` + `close()`.
237
- - **`client.db.syncState`** estado de sincronização observável (`status`, `pendingWrites`, `lastSyncedAt`, `isLeaderTab`).
238
- - **`client.db.onSyncStateChanged(cb)`** — subscrição de mudanças de sync state.
239
- - **`client.db.flush()`** força ciclo de sync imediato.
240
- - **`client.db.clearCache(collections?)`** — limpa o cache local (IndexedDB).
241
- - **`client.db.getConflicts(collection?)`** — lista conflitos LWW pendentes.
242
- - **Engine detection** via `NeetruDbOptions.engine`: `'firestore'` | `'nosql-vm'` | `'rest'` (default MVP).
243
- - **Subpath `@neetru/sdk/db/react`** — hooks React: `useCollection`, `useDoc` (exporta via `dist/db-react.{mjs,cjs}`).
244
- - **Novos tipos públicos exportados**: `NeetruDb`, `NeetruDbEngine`, `NeetruDbOptions`, `NeetruSqlClient`, `DbSqlLease`, `DbCollectionRef`, `DbDocRef`, `DbQuery`, `DbDoc`, `DbListResult`, `DbGetResult`, `DbBatchOp`, `DbChangeType`, `NeetruDbError`, `NeetruDbErrorCode`.
245
- - **`createNeetruDb(config, opts)`** factory async (para uso fora do `createNeetruClient`).
246
- - **`createNeetruDbSync(config, opts)`** factory sync com lazy init IndexedDB (usada internamente em `createNeetruClient`).
247
-
248
- ### Changed
249
-
250
- - **`NeetruClientConfig.db`** — aceita `NeetruDbOptions` (engine, dbId, collections, dbName, etc.).
251
- - **`client.config.mocks.db`** — agora é `NeetruDb` em vez de `DbNamespace`.
252
- - **`VERSION`** bumped para `'2.0.0'`.
253
-
254
- ## [1.2.0] - 2026-05-19 **Webhooks + Notifications + DX (cache, retry, batch)**
255
-
256
- Release de qualidade fechando 3 CRITICAL + 6 IMPORTANT do code review interno + 4 melhorias de DX. Adiciona `webhooks` + `notifications` no `NeetruClient`.
257
-
258
- ### Added
259
- - **`client.webhooks`** `register`/`list`/`unregister`/`test` pra endpoints HTTP do produto receberem eventos do Core (subscription.activated, usage.quota_exceeded, etc).
260
- - **`verifyWebhookSignature(payload, signature, secret)`** — util pura (HMAC SHA-256 constant-time compare via `node:crypto`) pra consumidor validar payloads recebidos.
261
- - **`client.notifications`** — `send`/`list`/`markRead`/`dismiss` pra produto integrar com a bandeja in-app do Neetru.
262
- - **`client.telemetry.track(input)`** + `client.telemetry.flush()` — fire-and-forget batch (500ms debounce). Ideal pra alta frequência (page-views, cliques). `event()` segue como single-shot com `eventId` ack.
263
- - **`client.entitlements.check(..., { cacheBust })`** — cache LRU em memória (60s TTL, 100 entries). Corta 90%+ requests em renders repetidos do `<EntitlementGate>`. `cacheBust: true` força ida ao servidor.
264
- - **httpRequest retry/backoff** — 2 retries default em `rate_limited` (429), `server_error` (5xx), `network_error`. Honra `Retry-After` header. Caller opt-out com `opts.retries: 0`.
265
- - **Subpaths novos**: `@neetru/sdk/webhooks`, `@neetru/sdk/notifications`.
266
-
267
- ### Changed
268
- - **`client.catalog.list()` / `get()`** agora consomem `/api/sdk/v1/catalog` (público, sem Bearer) em vez de `/api/v1/cli/catalog` (staff). Consumidor não precisa mais de chave staff só pra ler catálogo. `includeDrafts` é ignorado — rascunhos só via CLI.
269
- - **`auth.signOut()`** revoga token em `auth.neetru.com/api/v1/oauth/revoke` (canônico), não mais `api.neetru.com/oauth/revoke` (404 em prod).
270
- - **`<EntitlementGate>`** passa `tenantId` (do `client.config.tenantId`) pra `usage.check`. Sem tenantId, cai pra `entitlements.check` em vez de chamar `usage.check` e travar em `validation_failed` silencioso.
271
- - **`engines.node`** subiu pra `>=20` (alinha com `target: node20` do tsup; Node 18 EOL).
272
-
273
- ### Fixed
274
- - **CRITICAL** — `webhooks` e `notifications` lançavam `'invalid_input'`, código ausente da union `NeetruErrorCode`. Todas as 17 ocorrências viraram `'validation_failed'`.
275
- - **CRITICAL** — `telemetry.log()` chamava `/sdk/v1/telemetry/log` (404). Corrigido pra `/api/sdk/v1/telemetry/log`.
276
- - **CRITICAL** — `db` namespace tinha o mesmo bug em 6 paths (`/sdk/v1/datastore/...`). Corrigido em massa pra `/api/sdk/v1/datastore/...`.
277
- - **CRITICAL** 8 `httpRequest` calls em `webhooks` e `notifications` não passavam `requireAuth: true` todos hits em prod retornavam 401. Corrigido.
278
- - **`MockSupport.mockTicketSeq`** virou instance field (era module-level, poluía tests entre suites).
279
- - **`db.list()`** removeu variável `query` dead code (refactor incompleto de pre-1.1).
280
-
281
- ### Internal
282
- - 51 testes novos (webhooks: 22, notifications: 20, react: 9). Total: 215 testes (16 files), 100% passing. tsc clean.
283
- - `api-surface-stability.test.ts` agora lock `webhooks` + `notifications` no `NeetruClient`.
284
-
285
- ## [1.1.0] - 2026-05-08 **Checkout namespace + React helpers**
286
-
287
- Adiciona o namespace `client.checkout` (start/get/cancel intent) com auto-redirect
288
- em browser. Suporte ao fluxo Sprint 13 onde produtos SaaS delegam checkout pro
289
- portal `minhaconta.neetru.com` via `POST /api/v1/checkout/intents`.
290
-
291
- ### Added
292
- - **`client.checkout.start({productId, planId, callbackUrl, tenantType?, tenantId?})`** — cria
293
- `checkout_intents/{intentId}` no Core e (em browser) redireciona automaticamente
294
- pra `minhaconta.neetru.com/portal/checkout/{intentId}`. Em Node/SSR retorna
295
- `{intentId, redirectUrl}` sem efeito colateral.
296
- - **`client.checkout.get(intentId)`** — lê estado atual do intent.
297
- - **`client.checkout.cancel(intentId)`** — marca intent como `cancelled`.
298
- - **Subpath `@neetru/sdk/react`** — componente `<CheckoutLink>` (wrapper de `<a>`
299
- que dispara `start()` no click) + `<EntitlementGate mode="block|readonly">`
300
- + hook `useEntitlementContext()` pra desabilitar escritas quando free tier
301
- estoura limite (decisão CEO §5 — read-only sempre, não hard-block).
302
- - **`MockCheckout`** — implementação dev (`NEETRU_ENV=dev`) sem network. Retorna
303
- URL fake `https://localhost:9003/portal/checkout/{intentId}` pra dev externo
304
- testar UI sem provisionar conta Neetru.
305
- - **Peer dep `react ^18 || ^19`** marcado opcional (só carregado se import `/react`).
306
-
307
- ### Changed
308
- - `VERSION` const bump `1.0.0 → 1.1.0`.
309
- - `NeetruClient.checkout` adicionado — backward-compatible (caller que não usa
310
- o namespace não é afetado).
311
-
312
- ### Notes
313
- - API stability test (`api-surface-stability.test.ts`) atualizado pra incluir
314
- `checkout` no inventário canônico.
315
-
316
- ## [1.0.0] - 2026-05-06 — **GA (General Availability)**
317
-
318
- Marco de estabilidade. A partir desta versão, **a superfície pública do SDK
319
- está congelada** — breaking changes só em majors (semver estrito).
320
-
321
- ### Added
322
- - **API stability lock** — todos os contratos (`createNeetruClient`,
323
- `NeetruClient`, namespaces `auth`/`catalog`/`entitlements`/`telemetry`/
324
- `usage`/`support`/`db`, `NeetruError`) ficam estáveis em v1.x. Coberto
325
- por `api-surface-stability.test.ts` que falha o build se algum método
326
- muda forma.
327
- - **JSDoc completo** em toda função/método público — base pra `npm run docs:gen`
328
- (typedoc → `sdk/docs-html/`).
329
- - **`docs:gen` script** + `scripts/typedoc.config.json` — gera HTML
330
- navigable das APIs.
331
- - **`engines.node`** relaxado de `>=20` → `>=18` (suporta runtimes Node 18 LTS
332
- ainda em uso por consumers).
333
-
334
- ### Changed
335
- - `VERSION` bump `0.3.0` → `1.0.0`.
336
- - README atualizado com seção de migração v0.3 → v1.0 (no breaking changes —
337
- upgrade transparente).
338
-
339
- ### Migration v0.3 → v1.0
340
- - **Sem breaking changes.** Upgrade direto via `npm install @neetru/sdk@1.0.0`.
341
- - `initNeetru` continua deprecated mas funcional. **Será removido em v2.0.0.**
342
- - `NeetruConfig` continua deprecated. Use `NeetruClientConfig`.
343
-
344
- ### Stability promise (v1.x)
345
- A partir de v1.0:
346
- - Métodos novos podem ser adicionados a namespaces existentes (minor bump).
347
- - Métodos podem ser marcados `@deprecated` em qualquer minor — só removidos
348
- em major.
349
- - Tipos nunca ganham campos required em minor (só optional ou via union).
350
- - `NeetruErrorCode` é union fechado em v1.0 — qualquer novo código bumpa minor.
351
-
352
- ### Notes
353
- - Pacote ainda **privado** (`private: true`). Publicação no npm é
354
- responsabilidade do owner — gate no CHANGELOG continua trancado até decisão.
355
-
356
- ## [0.3.0] - 2026-05-06
357
-
358
- ### Added
359
- - Namespace `client.db` (v0.3) — wrapper minimalista pra coleções tenant-scoped:
360
- - `client.db.collection(name).list({ limit? })`
361
- - `client.db.collection(name).get(id)` (retorna null em not_found)
362
- - `client.db.collection(name).set(id, data)` upsert
363
- - `client.db.collection(name).remove(id)` delete
364
- - Endpoints: `/sdk/v1/datastore/{collection}/{id?}` (placeholder REST — Sprint 9
365
- finaliza; SDK já expõe interface estável).
366
- - `MockDb` (in-memory) ativo em `NEETRU_ENV=dev`.
367
- - Namespace `client.usage` ganhou `report()` e `check()`:
368
- - `usage.report(resource, qty?)` → `POST /sdk/v1/usage/record` canônico
369
- (Sprint 7) — incrementa atomicamente `usage_counters/{tid}_{pid}_{r}_{yyyymm}`.
370
- - `usage.check(resource)` → `GET /sdk/v1/entitlements?productId&tenantId&feature` —
371
- retorna `{allowed, reason, remaining, limit, planId, planFeatures}`.
372
- - `NeetruClientConfig.productId` / `.tenantId` — defaults pra `usage.report` /
373
- `usage.check` / `db.collection` quando não passado em options.
374
- - Mock helpers: `MockDb`, `MockUsage.report()`, `MockUsage.check()`.
375
-
376
- ### Changed
377
- - `VERSION` bump `0.2.0` → `0.3.0`.
378
- - `NeetruClient` agora expõe `.db` além dos antigos.
379
- - `ResolvedConfig` ganhou `productId` + `tenantId` opcionais.
380
-
381
- ### Backend (acompanha esta release)
382
- - Novos endpoints (Sprint 7):
383
- - `POST /sdk/v1/usage/record` — atomic increment + 80%/100% notifications + audit.
384
- - `GET /sdk/v1/entitlements` — lookup canônico subscription→plan→counter.
385
- - `POST /sdk/v1/telemetry/log` — batch logger pra `logs/{productId}/{yyyymmdd}/`.
386
-
387
- ## [0.2.0] - 2026-05-06
388
-
389
- ### Added
390
- - Namespace `client.auth` (v0.2 — OIDC + dev mocks):
391
- - `signIn(options?)` — redireciona pra auth.neetru.com authorize endpoint
392
- (browser) ou retorna mock user em dev (`NEETRU_ENV=dev`).
393
- - `signOut()` — limpa `localStorage.neetru_id_token` + revoga refresh
394
- server-side (best-effort).
395
- - `getUser()` — decodifica id_token cached e retorna `NeetruUser` ou null.
396
- - `onAuthStateChanged(listener)` — sub a mudanças, dispatcha sync no subscribe.
397
- - Namespace `client.usage` (v0.2 — meter):
398
- - `track(event, props?)` → `POST /sdk/v1/usage/record` com Bearer.
399
- - `getQuota(metric)` → `GET /sdk/v1/usage/quota?metric=` returns
400
- `{ metric, used, limit, plan, resetsAt? }`.
401
- - Namespace `client.support` (v0.2 — tickets):
402
- - `createTicket({subject, message, severity?, productSlug?})` →
403
- `POST /api/v1/products/{slug}/tickets` (default `_default`).
404
- - `listMyTickets()` → `GET ...` retorna array (aceita envelope `{tickets:[]}`).
405
- - `mocks` module (`@neetru/sdk/mocks`):
406
- - `MockAuth`, `MockUsage`, `MockSupport`, `MockEntitlements` — overridáveis
407
- via `createNeetruClient({ mocks: { auth: MockAuth(...), ... } })`.
408
- - `DEV_FIXTURE_USER` constante exportada.
409
- - `NEETRU_ENV` resolution chain: config arg > `process.env.NEETRU_ENV` >
410
- `globalThis.NEETRU_ENV` > default `prod`. `dev` ativa mocks automáticos.
411
- - Subpath exports adicionais: `@neetru/sdk/{auth,usage,support,mocks}`.
412
- - Tipos novos: `NeetruUser`, `AuthNamespace`, `UsageNamespace`,
413
- `SupportNamespace`, `SignInOptions`, `SupportTicket`, `UsageQuota`,
414
- `NeetruEnv`, `CreateTicketInput`.
415
-
416
- ### Changed
417
- - `VERSION` bump `0.1.0` → `0.2.0`.
418
- - `NeetruClientConfig` ganhou campos `env` + `mocks`.
419
- - `ResolvedConfig` ganhou `env` resolved.
420
- - `NeetruClient` agora expõe `.auth`, `.usage`, `.support` além dos antigos.
421
-
422
- ### Backend (acompanha esta release — endpoints documentados em `docs/SDK_OPENAPI.md`)
423
- - `POST /sdk/v1/usage/record` — record event meterado.
424
- - `GET /sdk/v1/usage/quota?metric=` — ler quota atual.
425
- - `POST /api/v1/products/{slug}/tickets` — criar ticket.
426
- - `GET /api/v1/products/{slug}/tickets` — listar meus tickets.
427
- - (auth) `auth.neetru.com/oauth/authorize` — já live (Sprint 1 OIDC).
428
- - (auth) `auth.neetru.com/oauth/revoke` — revoga refresh token.
429
-
430
- ### Notes
431
- - Pacote ainda **privado** (`private: true`) — não publicado no npm.
432
- - Carry-over: integração OIDC live aguarda owner habilitar redirect_uri pro
433
- domain do consumer (configurar em `oidc_clients/{client_id}.redirect_uris`).
434
- - TODO: `support.listMyTickets` precisa parametrizar productSlug (fixed
435
- `_default` na v0.2).
436
-
437
- ## [0.1.0] - 2026-05-05
438
-
439
- ### Added
440
- - `createNeetruClient(config)` factory — entry point principal.
441
- - Resolve `apiKey` (arg explícito > `NEETRU_API_KEY` env em Node).
442
- - Resolve `baseUrl` (default `https://api.neetru.com`).
443
- - Resolve `fetch` (arg > `globalThis.fetch`).
444
- - Namespace `client.catalog`:
445
- - `list(opts?)` → `GET /api/v1/cli/catalog`. Aceita `includeDrafts`.
446
- - `get(slug)` → `GET /api/v1/cli/catalog/{slug}`.
447
- - Namespace `client.entitlements`:
448
- - `check(slug, feature)` → boolean.
449
- - `checkDetailed(slug, feature)` → `EntitlementCheck` com `reason`.
450
- - Backed por `GET /api/v1/sdk/entitlements/check`.
451
- - Namespace `client.telemetry`:
452
- - `event({ name, properties?, timestamp? })` → `POST /api/v1/sdk/telemetry/event`.
453
- - Subpath exports map: `@neetru/sdk/{catalog,entitlements,telemetry,errors}`.
454
- - HTTP transport com mapeamento status → `NeetruError.code` estável.
455
- - Tipos exportados: `NeetruClient`, `NeetruClientConfig`, `Product`, `ProductPlan`,
456
- `EntitlementCheck`, `TelemetryEventInput`, `TelemetryEventAck`, `NeetruErrorCode`.
457
- - Compat alias `initNeetru` (deprecated, será removido em v1.0).
458
-
459
- ### Changed
460
- - `VERSION` bump `0.0.1` → `0.1.0`.
461
- - tsup config multi-entry pra subpath imports.
462
-
463
- ### Backend (acompanha esta release)
464
- - `GET /api/v1/cli/catalog/{slug}` — produto único.
465
- - `GET /api/v1/sdk/entitlements/check?slug=&feature=` — verificação por user.
466
- - `POST /api/v1/sdk/telemetry/event` — persiste em `usage_events`.
467
-
468
- ### Notes
469
- - Pacote ainda **privado** (`private: true`). Publicação no npm é
470
- responsabilidade do owner em Sprint 6.
471
- - API instável; pré-1.0 segue regra "breaking allowed em qualquer minor".
472
-
473
- ## [0.0.1] - 2026-05-04
474
-
475
- ### Added
476
- - Scaffold inicial do pacote `@neetru/sdk` (private, não publicado no npm ainda).
477
- - `initNeetru(config)` com validação de `apiUrl` e injeção opcional de `fetch`.
478
- - `NeetruError` com `code`, `status` e `requestId` opcionais.
479
- - `NeetruConfig` e `NeetruClient` types públicos.
480
- - `VERSION` const exportada.
481
- - Build pipeline com tsup (dual ESM + CJS + `.d.ts`).
482
- - README + CHANGELOG.
483
- - `tsconfig.json` strict ES2022 ESNext.
1
+ # Changelog
2
+
3
+ All notable changes to `@neetru/sdk` will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [2.3.5] - 2026-05-26 — **fix(bundle): node deps fora do browser entry bug_0f836014 [CRITICAL]**
9
+
10
+ ### Corrigido
11
+
12
+ - **bug_0f836014055747858adfa8ab0e8cbb47 (CRITICAL) — SDK 2.3.4 publicou com `require('pg')` + `require('drizzle-orm/node-postgres')` literais no `dist/index.mjs`.**
13
+ Webpack do consumer Next.js seguia o module graph e tentava bundle pg puxa fs `Module not found: Can't resolve 'fs'` no client bundle. Bloqueava `npm run build` de qualquer projeto Next.js que importa `createNeetruClient`.
14
+ Fix: dynamic import com `/* webpackIgnore: true */` em `createSqlClientFromConfig`. Instrui webpack a NÃO processar runtime Node resolve. `sql()` é server-only por contrato.
15
+ Resultado: `dist/index.mjs` zero imports literais de pg/drizzle/fs/node:*. Verified via teste antirregressão novo.
16
+
17
+ ### Testes (antirregressão)
18
+
19
+ - **`src/__tests__/regressions/no-node-deps-in-browser-bundle.regression.test.ts`** (novo) — varre 13 entry bundles e falha se encontrar require/import estático de pg/drizzle-orm/node-postgres/pg-mem/@electric-sql/pglite/fs/node:*. Roda APÓS `npm run build`. Pega regressão dessa classe.
20
+
21
+ ## [2.3.4] - 2026-05-26 — **fix(mocks): MockDb.sql() migra pra pglite + pg-mem fallback bug_6bae8674 [HIGH]**
22
+
23
+ ### Corrigido
24
+
25
+ - **bug_6bae8674461f4600809e2f9de13c6043 (HIGH) fix anterior (2.3.3) teve efeito INVERSO em `MockDb.sql()`.**
26
+ O fix de 2.3.3 injetava `getTypeParser` em `pool.types` para resolver o erro "Not supported: getTypeParser is not supported".
27
+ Porém `pg-mem` tem um guard interno: `if (query.types?.getTypeParser) throw NotSupported`.
28
+ `drizzle-orm/node-postgres/session.js:113` passa `rawQuery.types = pool.types` em TODA query — ao adicionar
29
+ `getTypeParser` ao pool, drizzle passou-o para o pg-mem que o rejeitou imediatamente.
30
+ Efeito líquido: qualquer `INSERT`/`SELECT`/`UPDATE` continuava quebrando com o mesmo erro.
31
+
32
+ **Fix 2.3.4 duas soluções em cascata:**
33
+
34
+ 1. **Solução A (preferida): `@electric-sql/pglite` + `drizzle-orm/pglite`** WASM-Postgres nativo
35
+ sem adapter pg-mem. Sem conflito `getTypeParser`, sem monkey-patch. `PGlite.create()` retorna
36
+ instância pronta, DDL via `pglite.query(ddl)`, ORM via `drizzle(pglite, { schema })`. Cast
37
+ `PgliteDatabase → NodePgDatabase` via `unknown` (interfaces Drizzle compatíveis em superfície
38
+ mas com tipos de resultado internos distintos).
39
+
40
+ 2. **Solução B (fallback pg-mem)**: strip interceptor intercepta `pool.query` e deleta
41
+ `types.getTypeParser` do objeto de query ANTES do pg-mem ver. Guard não dispara, query passa
42
+ limpa. Compatível com consumers que só têm pg-mem nas devDeps.
43
+
44
+ **`@electric-sql/pglite` adicionado como `peerDependency` optional** (`^0.4.0`) + `devDependency`
45
+ interno (`^0.4.6`).
46
+
47
+ **8 novos testes** em `src/__tests__/mocks-sql-pglite.test.ts`: criação sem erro, INSERT sem throw,
48
+ SELECT após INSERT, UPDATE, múltiplos INSERTs, sem initSql, close idempotente, transaction().
49
+ Todos passando.
50
+
51
+ **655 testes totais** (era 647 em 2.3.3). `tsc --noEmit` clean. Build clean.
52
+
53
+ ## [2.3.3] - 2026-05-26 — **fix(mocks): pool.types.getTypeParser identity parser — bug_44bcc99f [HIGH]**
54
+
55
+ ### Corrigido
56
+
57
+ - **bug_44bcc99f9d3140d78abd8f7810d1afa4 (HIGH) — `MockDb.sql()` queries Drizzle falhavam em runtime.**
58
+ Followup do `bug_a7f2ae25` (SDK 2.3.2): `sql()` retornava handle Drizzle, mas qualquer query (`INSERT`, `SELECT`, etc) jogava `Error: Not supported: getTypeParser is not supported`. `pg-mem` não implementa `pool.types.getTypeParser(oid)` que `drizzle-orm/node-postgres/session.js` chama em TODA query.
59
+ Fix: monkey-patch identity parser no Pool retornado por `pg-mem.adapters.createPg()` antes de passar pro Drizzle. Types retornam o valor sem transformação (suficiente pra tests in-memory; produção usa `pg` real com parsers nativos).
60
+ 3 linhas. Non-breaking. Tests E2E Drizzle in-memory destravados.
61
+
62
+ ## [2.3.2] - 2026-05-26 — **fix(mocks): MockDb.sql() implementado in-memory via pg-mem — bug_a7f2ae25 [HIGH]**
63
+
64
+ ### Corrigido
65
+
66
+ - **bug_a7f2ae25fda54129b52b6dd55aaa6050 (HIGH) — `MockDb.sql()` lançava `db_unavailable` em vez de implementar Drizzle in-memory.**
67
+ Bloqueava vitest server-side dos consumers (pdv-agiliza Fase 4 migration). Política do projeto proíbe workaround instalar `pg-mem` no consumer.
68
+ Fix: `MockDb.sql(schema, options)` agora carrega `pg-mem` lazy + cria `NodePgDatabase<TSchema>` Drizzle apontando pra DB in-memory. Suporta `options.initSql: string | string[]` pra rodar DDL no setup (CREATE TABLE etc).
69
+ `pg-mem` adicionado como `peerDependency optional` (com `peerDependenciesMeta.optional=true`) + `devDependency` interno. Consumers que querem mock SQL: `npm install -D pg-mem drizzle-orm pg`.
70
+ **Não-breaking:** signature `sql(schema, options?)` mantida. SqlOptions ganhou `initSql?` opcional (ignorado por lease real em produção). Comportamento default — `sql()` sem `initSql` — funciona em ambos modos.
71
+
72
+ ## [2.3.1] - 2026-05-26 — **fix(deps): drizzle-orm + pg como peerDependencies — bug_42ae9f80 [CRITICAL]**
73
+
74
+ ### Corrigido
75
+
76
+ - **bug_42ae9f80b2884bd583c5246771f7730c (CRITICAL) — `drizzle-orm` em `dependencies` causava type collision com versão do consumer.**
77
+ Quando o projeto consumer instalava `drizzle-orm` direto e tentava `client.db.sql(schema)`, o TypeScript via 2 cópias diferentes (uma nested em `node_modules/@neetru/sdk/node_modules/drizzle-orm`, outra no consumer) e reclamava: `'Property config is protected but type Column<...> is not a class derived from Column<...>'` + `'shouldInlineParams private'`. Bloqueava Fases 2-8 da migração pdv-agiliza pra `vm-postgres-single`.
78
+ Fix: movidas `drizzle-orm` e `pg` de `dependencies` pra `peerDependencies` (com `peerDependenciesMeta.optional: true`). Consumer instala 1 versão, todos os tipos convergem. Devkit mantém `drizzle-orm` + `pg` em `devDependencies` pra build/test internos.
79
+ **Breaking expected on consumer side:** se o projeto consumer NÃO tinha `drizzle-orm` em deps mas usava `client.db.sql()`, agora precisa `npm install drizzle-orm pg`. Consumers que só usam transport REST/Firestore não são afetados (peerDeps optional).
80
+
81
+ ## [2.3.0] - 2026-05-25 — **feat(db-config): renomeia engine→transport com deprecation — bug_ed9da7fd [HIGH]**
82
+
83
+ Fix do bug `bug_ed9da7fd11194eac87f0eb9c75e5fad9` (HIGH) — colisão de terminologia: `engine` no SDK
84
+ configurava o transporte SDK↔Core (REST/Firestore/WebSocket), mas `engine` no admin database (CLI
85
+ `neetru admin database create --engine=...`) configura o storage engine (firestore-instance,
86
+ cloud-sql-postgres, vm-postgres-single…). Devs colocavam `firestore-instance` no SDK e quebravam.
87
+
88
+ ### Added
89
+
90
+ - **`NeetruDbTransport`** novo tipo público canônico para o campo de transporte SDK↔Core.
91
+ Valores: `'rest' | 'firestore' | 'nosql-vm'` (idênticos aos de `NeetruDbEngine`).
92
+
93
+ - **`NeetruDbOptions.transport`** — novo campo canônico que substitui `engine`.
94
+ ```ts
95
+ // v2.3+ (preferido):
96
+ createNeetruClient({ db: { transport: 'rest' } });
97
+ createNeetruClient({ db: { transport: 'firestore' } });
98
+ createNeetruClient({ db: { transport: 'nosql-vm', realtimeGatewayUrl: '...' } });
99
+ ```
100
+
101
+ ### Deprecated
102
+
103
+ - **`NeetruDbOptions.engine`** — depreciado em v2.3, será removido em v3.0.
104
+ Continua funcionando: se `transport` estiver ausente e `engine` presente, o valor de `engine`
105
+ é usado como transport com `console.warn` de deprecação.
106
+
107
+ ```ts
108
+ // v2.x (depreciado — emite console.warn):
109
+ createNeetruClient({ db: { engine: 'firestore' } });
110
+ // ⚠ [neetru/sdk] NeetruDbOptions.engine está depreciado...
111
+ ```
112
+
113
+ - **`NeetruDbEngine`** — depreciado em v2.3, será removido em v3.0.
114
+ Agora é alias de `NeetruDbTransport`. Use `NeetruDbTransport` em código novo.
115
+
116
+ ### Behavior
117
+
118
+ - `transport` e `engine` podem coexistir: `transport` sempre prevalece, sem warn.
119
+ - Sem `transport` nem `engine`: default `'rest'`, sem warn (comportamento idêntico ao v2.x).
120
+ - A relação 1:N entre storage engines e transports é documentada no JSDoc de `NeetruDbTransport`:
121
+ qualquer storage engine (cloud-sql-postgres, vm-postgres-single…) pode ser acessado com
122
+ `transport: 'rest'`; `transport: 'firestore'` e `transport: 'nosql-vm'` são para bancos
123
+ documentais com realtime nativo.
124
+
125
+ ### Tests
126
+
127
+ - 5 testes novos em `src/__tests__/db-real.test.ts`:
128
+ `transport=firestore`, `transport=nosql-vm`, `transport=rest`, colisão `transport+engine`
129
+ (transport prevalece, sem warn), default sem campos (sem warn).
130
+ - Testes de regressão: `engine=firestore` e `engine=nosql-vm` legados ainda passam + emitem warn.
131
+
132
+ ## [2.2.1] - 2026-05-25 **fix(offline-docs): fallback MemoryStore em Node bug_ba287a CRITICAL**
133
+
134
+ Fix do bug `bug_ba287aefdf6c4327b026d588a239ad06` (CRITICAL) — `client.db.collection().set/add/update`
135
+ lançava `ReferenceError: indexedDB is not defined` em Node.js (reportado por pdv-agiliza prod).
136
+
137
+ ### Fixed
138
+
139
+ - **`createOfflineDocumentsNamespace` — auto-detect Node runtime e usa `MemoryStore`**.
140
+ A função agora verifica `typeof indexedDB === 'undefined' || typeof window === 'undefined'`
141
+ antes de instanciar `LocalStore` (que depende de `idb` / IndexedDB). Em Node/Bun/Edge Workers
142
+ sem IDB, cai automaticamente em `MemoryStore` implementação in-process com a mesma API,
143
+ sem nenhuma flag explícita necessária. Ops de documentos (`set/add/update/remove/get/list`,
144
+ `batch`, `onSnapshot`, `onDoc`) funcionam normalmente em Node; dados não persistem entre
145
+ reinicializações do processo (comportamento esperado em SSR/server-side).
146
+
147
+ ### Added
148
+
149
+ - **`src/db/offline/memory-store.ts`** `MemoryStore`: implementação in-memory do contrato
150
+ público de `LocalStore`. Cobre todas as 5 stores (documents, mutations, query\_cache,
151
+ sync\_meta, conflict\_log) com Maps/Arrays em memória.
152
+
153
+ ### Tests
154
+
155
+ - **`src/__tests__/offline-docs-node-compat.test.ts`** — 5 casos de regressão:
156
+ verifica que a stack offline não lança `ReferenceError` em ambiente sem IDB,
157
+ e que `set/add/update` + `get` roundtrips funcionam in-memory.
158
+
159
+ ## [2.2.0] - 2026-05-25 **auth.verifyToken — validação server-side de id_token via JWKS [feature]**
160
+
161
+ Fix do bug `bug_a7ef1702680044e3994b74bdccecb2ab` — resolve P0-1 audit security pdv-agiliza.
162
+
163
+ ### Added
164
+
165
+ - **`client.auth.verifyToken(token, options?): Promise<NeetruUser>`** valida um id_token JWT
166
+ emitido por `auth.neetru.com` server-side, sem round-trip de rede por request. Usa JWKS local
167
+ cacheado (default TTL 1h, configurável via `options.jwksCacheTtlMs`).
168
+
169
+ Erros tipados novos (`NeetruErrorCode`):
170
+ - `token_expired` `exp` no passado
171
+ - `token_invalid_signature` — assinatura não verificada pela JWKS
172
+ - `token_invalid_audience` — `aud` não bate com o `clientId` extraído do `apiKey`
173
+ - `token_invalid_issuer` — `iss` não é `https://auth.neetru.com`
174
+
175
+ Dependência nova: `jose` (Vercel/Cloudflare-friendly, sem Node nativo).
176
+
177
+ - **`MockAuth.verifyToken(token)`** — decodifica JWT sem verificar assinatura (útil em testes).
178
+ Aceita `__mockVerifyToken(fn)` pra injetar falhas determinísticas.
179
+
180
+ - **`VerifyTokenOptions`** exportado em `index.ts` pra consumers tiparem as options.
181
+
182
+ ### Usage
183
+
184
+ ```ts
185
+ // Next.js route handler
186
+ import { createNeetruClient } from '@neetru/sdk';
187
+ const client = createNeetruClient({ apiKey: 'nrt_a1b2c3_...', env: 'prod' });
188
+
189
+ export async function POST(req: Request) {
190
+ const token = req.cookies.get('neetru-session')?.value ?? '';
191
+ try {
192
+ const user = await client.auth.verifyToken(token);
193
+ // user.uid, user.email, user.isStaff, user.tenantId disponíveis
194
+ } catch (err) {
195
+ return new Response('Unauthorized', { status: 401 });
196
+ }
197
+ }
198
+ ```
199
+
200
+ ## [Unreleased]
201
+
202
+ ### Planned (post-2.2)
203
+ - size-limit budget no CI (<12KB gz core com db namespace).
204
+ - CDN distribution (`cdn.neetru.com/sdk/v2/`).
205
+ - Endpoint batch `/api/v1/sdk/telemetry/batch` (substitui drenagem N×1 do `track()` por 1 request).
206
+
207
+ ## [2.1.1] - 2026-05-25 **DbDocRef identity + DbBatchOp discriminated union [patch]**
208
+
209
+ Dois fixes LOW reportados por pdv-agiliza (tickets `bug_97b3f0488d1c49cf8c5f94e5d544289b` + `bug_6aab9adc182640218652a65cdcb71b63`).
210
+ Sem breaking changes todos os usos de `doc()` existentes continuam funcionando.
211
+
212
+ ### Fixed
213
+
214
+ - **`DbDocRef.id / .path / .collection`** (fix #13, ticket `bug_97b3f0488d1c49cf8c5f94e5d544289b`):
215
+ `DbDocRef` agora expõe três propriedades readonly de identidade:
216
+ - `id: string` ID do documento dentro da coleção.
217
+ - `path: string` — Caminho completo `"colecao/docId"`.
218
+ - `collection: string` — Nome da coleção parent.
219
+ Análogo ao `DocumentReference.id/.path` do Firestore Web SDK. Elimina a necessidade
220
+ de adaptadores externos que reattachavam `collection + id` manualmente.
221
+ Implementado em `DbCollectionRefImpl.doc()` (offline), `createLazyCollectionRef.doc()`
222
+ (client-db) e `MockDb.doc()` (mocks).
223
+
224
+ - **`DbBatchOp` discriminated union** (fix #14, ticket `bug_6aab9adc182640218652a65cdcb71b63`):
225
+ `DbBatchOp` foi convertido de `interface` opaca (campo `op: 'add'|'set'|'update'|'remove'`
226
+ com `id?`/`data?` opcionais) para **discriminated union** pelo campo `kind`:
227
+ ```ts
228
+ type DbBatchOp<T = Record<string, unknown>> =
229
+ | { kind: 'add'; data: Omit<T, 'id'> }
230
+ | { kind: 'set'; id: string; data: Omit<T, 'id'> }
231
+ | { kind: 'update'; id: string; data: Partial<Omit<T, 'id'>> }
232
+ | { kind: 'remove'; id: string };
233
+ ```
234
+ O `.d.ts` público agora expõe claramente quais campos são obrigatórios por variante.
235
+ O campo `collection` foi removido da operação (sempre opera na coleção do `DbCollectionRef`).
236
+
237
+ ### Migration (2.0.0 2.1.1)
238
+
239
+ Callers que usavam `batch()` devem trocar `op` por `kind` e remover o campo `collection`:
240
+
241
+ ```ts
242
+ // antes (2.0.0)
243
+ col.batch([
244
+ { op: 'add', collection: 'orders', data: { total: 10 } },
245
+ { op: 'set', collection: 'orders', id: 'x', data: { total: 20 } },
246
+ { op: 'update', collection: 'orders', id: 'y', data: { total: 30 } },
247
+ { op: 'remove', collection: 'orders', id: 'z' },
248
+ ]);
249
+
250
+ // depois (2.1.1)
251
+ col.batch([
252
+ { kind: 'add', data: { total: 10 } },
253
+ { kind: 'set', id: 'x', data: { total: 20 } },
254
+ { kind: 'update', id: 'y', data: { total: 30 } },
255
+ { kind: 'remove', id: 'z' },
256
+ ]);
257
+ ```
258
+
259
+ ## [2.0.0] - 2026-05-22 **client.db NeetruDb (Documentos engine-aware + SQL) [M2]**
260
+
261
+ Major bumpbreaking changes no namespace `db`. Liga todos os building blocks M2 na superfície pública.
262
+
263
+ ### Breaking Changes
264
+
265
+ - **`client.db`** troca de `DbNamespace` (v0.3 REST simples) para `NeetruDb` (v2.0 offline-first engine-aware).
266
+ - `collection().list()` agora retorna `DbListResult<T>` (com `docs`, `nextCursor`, `fromCache`, `stale`, `hasPendingWrites`, `changes`) em vez de `T[]`.
267
+ - `collection().get(id)` retorna `DbGetResult<T> | null` em vez de `T | null`.
268
+ - `db_unavailable` não significa mais "sem rede" ciclo de vida do banco. Offline é transparente.
269
+ - Novos métodos obrigatórios na superfície: `sql()`, `syncState`, `flush()`, `clearCache()`, `getConflicts()`, `onSyncStateChanged()`, `batch()`, `onDoc()`, `onSnapshot()`, `doc()`.
270
+ - **`db.sql(schema)` lança `NeetruDbError('db_unavailable')` em `NEETRU_ENV=dev`** sem Postgres local, sem acesso SQL. Use `neetru dev` para subir o container.
271
+ - **`initNeetru`** mantido como stub funcional mas não mapeia mais `apiUrl baseUrl`. Remover em v3.0.
272
+ - **`MockDb`** foi atualizado para implementar `NeetruDb` (v2.0) em vez de `DbNamespace` (v0.3).
273
+
274
+ ### Added
275
+
276
+ - **`client.db.collection<T>(name)`**retorna `DbCollectionRef<T>` offline-first, engine-aware. Funciona sem rede. Sync automático quando conectado.
277
+ - CRUD: `add`, `set`, `update`, `remove`, `get`, `list` (com query, cursor, orderBy, where, limit).
278
+ - Batch: `batch(ops)` operações atômicas (client-side).
279
+ - Realtime: `onSnapshot(query, cb)`, `onDoc(id, cb)` subscriptions permanentes, delivery imediato do cache.
280
+ - Doc ref: `doc(id).get()`, `.set()`, `.update()`, `.remove()`, `.onSnapshot()`.
281
+ - **`client.db.sql<TSchema>(schema)`** — lease SQL. Retorna `NeetruSqlClient<TSchema>` com `orm` (Drizzle `NodePgDatabase`) + `transaction()` + `close()`.
282
+ - **`client.db.syncState`** estado de sincronização observável (`status`, `pendingWrites`, `lastSyncedAt`, `isLeaderTab`).
283
+ - **`client.db.onSyncStateChanged(cb)`** subscrição de mudanças de sync state.
284
+ - **`client.db.flush()`** — força ciclo de sync imediato.
285
+ - **`client.db.clearCache(collections?)`**limpa o cache local (IndexedDB).
286
+ - **`client.db.getConflicts(collection?)`** — lista conflitos LWW pendentes.
287
+ - **Engine detection** via `NeetruDbOptions.engine`: `'firestore'` | `'nosql-vm'` | `'rest'` (default MVP).
288
+ - **Subpath `@neetru/sdk/db/react`** hooks React: `useCollection`, `useDoc` (exporta via `dist/db-react.{mjs,cjs}`).
289
+ - **Novos tipos públicos exportados**: `NeetruDb`, `NeetruDbEngine`, `NeetruDbOptions`, `NeetruSqlClient`, `DbSqlLease`, `DbCollectionRef`, `DbDocRef`, `DbQuery`, `DbDoc`, `DbListResult`, `DbGetResult`, `DbBatchOp`, `DbChangeType`, `NeetruDbError`, `NeetruDbErrorCode`.
290
+ - **`createNeetruDb(config, opts)`** — factory async (para uso fora do `createNeetruClient`).
291
+ - **`createNeetruDbSync(config, opts)`** — factory sync com lazy init IndexedDB (usada internamente em `createNeetruClient`).
292
+
293
+ ### Changed
294
+
295
+ - **`NeetruClientConfig.db`** — aceita `NeetruDbOptions` (engine, dbId, collections, dbName, etc.).