@neetru/sdk 2.3.2 → 2.3.4

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