@neetru/sdk 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,84 @@ All notable changes to `@neetru/sdk` will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.3.0] - 2026-05-25 — **feat(db-config): renomeia engine→transport com deprecation — bug_ed9da7fd [HIGH]**
9
+
10
+ Fix do bug `bug_ed9da7fd11194eac87f0eb9c75e5fad9` (HIGH) — colisão de terminologia: `engine` no SDK
11
+ configurava o transporte SDK↔Core (REST/Firestore/WebSocket), mas `engine` no admin database (CLI
12
+ `neetru admin database create --engine=...`) configura o storage engine (firestore-instance,
13
+ cloud-sql-postgres, vm-postgres-single…). Devs colocavam `firestore-instance` no SDK e quebravam.
14
+
15
+ ### Added
16
+
17
+ - **`NeetruDbTransport`** — novo tipo público canônico para o campo de transporte SDK↔Core.
18
+ Valores: `'rest' | 'firestore' | 'nosql-vm'` (idênticos aos de `NeetruDbEngine`).
19
+
20
+ - **`NeetruDbOptions.transport`** — novo campo canônico que substitui `engine`.
21
+ ```ts
22
+ // v2.3+ (preferido):
23
+ createNeetruClient({ db: { transport: 'rest' } });
24
+ createNeetruClient({ db: { transport: 'firestore' } });
25
+ createNeetruClient({ db: { transport: 'nosql-vm', realtimeGatewayUrl: '...' } });
26
+ ```
27
+
28
+ ### Deprecated
29
+
30
+ - **`NeetruDbOptions.engine`** — depreciado em v2.3, será removido em v3.0.
31
+ Continua funcionando: se `transport` estiver ausente e `engine` presente, o valor de `engine`
32
+ é usado como transport com `console.warn` de deprecação.
33
+
34
+ ```ts
35
+ // v2.x (depreciado — emite console.warn):
36
+ createNeetruClient({ db: { engine: 'firestore' } });
37
+ // ⚠ [neetru/sdk] NeetruDbOptions.engine está depreciado...
38
+ ```
39
+
40
+ - **`NeetruDbEngine`** — depreciado em v2.3, será removido em v3.0.
41
+ Agora é alias de `NeetruDbTransport`. Use `NeetruDbTransport` em código novo.
42
+
43
+ ### Behavior
44
+
45
+ - `transport` e `engine` podem coexistir: `transport` sempre prevalece, sem warn.
46
+ - Sem `transport` nem `engine`: default `'rest'`, sem warn (comportamento idêntico ao v2.x).
47
+ - A relação 1:N entre storage engines e transports é documentada no JSDoc de `NeetruDbTransport`:
48
+ qualquer storage engine (cloud-sql-postgres, vm-postgres-single…) pode ser acessado com
49
+ `transport: 'rest'`; `transport: 'firestore'` e `transport: 'nosql-vm'` são para bancos
50
+ documentais com realtime nativo.
51
+
52
+ ### Tests
53
+
54
+ - 5 testes novos em `src/__tests__/db-real.test.ts`:
55
+ `transport=firestore`, `transport=nosql-vm`, `transport=rest`, colisão `transport+engine`
56
+ (transport prevalece, sem warn), default sem campos (sem warn).
57
+ - Testes de regressão: `engine=firestore` e `engine=nosql-vm` legados ainda passam + emitem warn.
58
+
59
+ ## [2.2.1] - 2026-05-25 — **fix(offline-docs): fallback MemoryStore em Node — bug_ba287a CRITICAL**
60
+
61
+ Fix do bug `bug_ba287aefdf6c4327b026d588a239ad06` (CRITICAL) — `client.db.collection().set/add/update`
62
+ lançava `ReferenceError: indexedDB is not defined` em Node.js (reportado por pdv-agiliza prod).
63
+
64
+ ### Fixed
65
+
66
+ - **`createOfflineDocumentsNamespace` — auto-detect Node runtime e usa `MemoryStore`**.
67
+ A função agora verifica `typeof indexedDB === 'undefined' || typeof window === 'undefined'`
68
+ antes de instanciar `LocalStore` (que depende de `idb` / IndexedDB). Em Node/Bun/Edge Workers
69
+ sem IDB, cai automaticamente em `MemoryStore` — implementação in-process com a mesma API,
70
+ sem nenhuma flag explícita necessária. Ops de documentos (`set/add/update/remove/get/list`,
71
+ `batch`, `onSnapshot`, `onDoc`) funcionam normalmente em Node; dados não persistem entre
72
+ reinicializações do processo (comportamento esperado em SSR/server-side).
73
+
74
+ ### Added
75
+
76
+ - **`src/db/offline/memory-store.ts`** — `MemoryStore`: implementação in-memory do contrato
77
+ público de `LocalStore`. Cobre todas as 5 stores (documents, mutations, query\_cache,
78
+ sync\_meta, conflict\_log) com Maps/Arrays em memória.
79
+
80
+ ### Tests
81
+
82
+ - **`src/__tests__/offline-docs-node-compat.test.ts`** — 5 casos de regressão:
83
+ verifica que a stack offline não lança `ReferenceError` em ambiente sem IDB,
84
+ e que `set/add/update` + `get` roundtrips funcionam in-memory.
85
+
8
86
  ## [2.2.0] - 2026-05-25 — **auth.verifyToken — validação server-side de id_token via JWKS [feature]**
9
87
 
10
88
  Fix do bug `bug_a7ef1702680044e3994b74bdccecb2ab` — resolve P0-1 audit security pdv-agiliza.
package/dist/auth.cjs CHANGED
@@ -1163,6 +1163,125 @@ var LocalStore = class {
1163
1163
  }
1164
1164
  };
1165
1165
 
1166
+ // src/db/offline/memory-store.ts
1167
+ var MemoryStore = class {
1168
+ // store `documents` — key: `${collection}\0${id}`
1169
+ _docs = /* @__PURE__ */ new Map();
1170
+ // store `mutations` — key: mutationId
1171
+ _mutations = /* @__PURE__ */ new Map();
1172
+ // store `sync_meta` — key: string
1173
+ _meta = /* @__PURE__ */ new Map();
1174
+ // store `conflict_log` — autoIncrement int key
1175
+ _conflicts = [];
1176
+ _conflictSeq = 0;
1177
+ // store `query_cache` — key: queryHash
1178
+ _queryCache = /* @__PURE__ */ new Map();
1179
+ // ─── Ciclo de vida (no-ops) ─────────────────────────────────────────────────
1180
+ async open() {
1181
+ }
1182
+ async close() {
1183
+ this._docs.clear();
1184
+ this._mutations.clear();
1185
+ this._meta.clear();
1186
+ this._conflicts.splice(0);
1187
+ this._conflictSeq = 0;
1188
+ this._queryCache.clear();
1189
+ }
1190
+ // ─── Documents ─────────────────────────────────────────────────────────────
1191
+ async getDoc(collection, id) {
1192
+ const key = `${collection}\0${id}`;
1193
+ return this._docs.get(key) ?? null;
1194
+ }
1195
+ async putDoc(doc) {
1196
+ const key = `${doc.collection}\0${doc.id}`;
1197
+ this._docs.set(key, doc);
1198
+ }
1199
+ async deleteDoc(collection, id) {
1200
+ const key = `${collection}\0${id}`;
1201
+ const existing = this._docs.get(key);
1202
+ if (!existing) return;
1203
+ this._docs.set(key, {
1204
+ ...existing,
1205
+ meta: {
1206
+ ...existing.meta,
1207
+ deleted: true,
1208
+ updatedAtLocal: Date.now()
1209
+ }
1210
+ });
1211
+ }
1212
+ async listDocs(collection, query) {
1213
+ const rawDocs = Array.from(this._docs.values()).filter(
1214
+ (d) => d.collection === collection
1215
+ );
1216
+ return QueryEngine.evaluate(rawDocs, query);
1217
+ }
1218
+ // ─── Sync meta ──────────────────────────────────────────────────────────────
1219
+ async getMeta(key) {
1220
+ return this._meta.get(key) ?? null;
1221
+ }
1222
+ async setMeta(key, value) {
1223
+ this._meta.set(key, value);
1224
+ }
1225
+ // ─── Conflict log ────────────────────────────────────────────────────────────
1226
+ async appendConflict(record) {
1227
+ const id = ++this._conflictSeq;
1228
+ const full = { ...record, id };
1229
+ this._conflicts.push(full);
1230
+ return full;
1231
+ }
1232
+ async listConflicts(options) {
1233
+ if (options?.delivered !== void 0) {
1234
+ return this._conflicts.filter((r) => r.delivered === options.delivered);
1235
+ }
1236
+ return [...this._conflicts];
1237
+ }
1238
+ async markConflictDelivered(id) {
1239
+ const entry = this._conflicts.find((r) => r.id === id);
1240
+ if (entry) entry.delivered = true;
1241
+ }
1242
+ // ─── Mutations ───────────────────────────────────────────────────────────────
1243
+ async putMutation(mutation) {
1244
+ this._mutations.set(mutation.mutationId, { ...mutation });
1245
+ }
1246
+ async getMutation(mutationId) {
1247
+ return this._mutations.get(mutationId) ?? null;
1248
+ }
1249
+ async listMutations(options) {
1250
+ let all = Array.from(this._mutations.values());
1251
+ if (options?.status !== void 0) {
1252
+ all = all.filter((m) => m.status === options.status);
1253
+ } else if (options?.collection !== void 0 && options.docId !== void 0) {
1254
+ all = all.filter(
1255
+ (m) => m.collection === options.collection && m.docId === options.docId
1256
+ );
1257
+ } else if (options?.collection !== void 0) {
1258
+ all = all.filter((m) => m.collection === options.collection);
1259
+ }
1260
+ return all.sort((a, b) => a.seq - b.seq);
1261
+ }
1262
+ async deleteMutation(mutationId) {
1263
+ this._mutations.delete(mutationId);
1264
+ }
1265
+ // ─── Collection discovery ────────────────────────────────────────────────────
1266
+ async listCollections() {
1267
+ const collections = /* @__PURE__ */ new Set();
1268
+ for (const doc of this._docs.values()) {
1269
+ collections.add(doc.collection);
1270
+ }
1271
+ return Array.from(collections);
1272
+ }
1273
+ // ─── Query cache ─────────────────────────────────────────────────────────────
1274
+ async getQueryCache(queryHash) {
1275
+ return this._queryCache.get(queryHash) ?? null;
1276
+ }
1277
+ async putQueryCache(entry) {
1278
+ this._queryCache.set(entry.queryHash, { ...entry });
1279
+ }
1280
+ async deleteQueryCache(queryHash) {
1281
+ this._queryCache.delete(queryHash);
1282
+ }
1283
+ };
1284
+
1166
1285
  // src/db/offline/mutation-queue.ts
1167
1286
  function generateUUIDv4() {
1168
1287
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
@@ -2709,6 +2828,9 @@ var TabCoordinator = class {
2709
2828
  };
2710
2829
 
2711
2830
  // src/db/collection-ref.ts
2831
+ function isIndexedDBUnavailable() {
2832
+ return typeof indexedDB === "undefined" || typeof window === "undefined";
2833
+ }
2712
2834
  var COLL_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/;
2713
2835
  function assertValidCollection(name) {
2714
2836
  if (!COLL_RE.test(name)) {
@@ -3250,7 +3372,7 @@ var NeetruDbDocumentsImpl = class {
3250
3372
  }
3251
3373
  };
3252
3374
  async function createOfflineDocumentsNamespace(opts) {
3253
- const store = new LocalStore(opts.dbName);
3375
+ const store = isIndexedDBUnavailable() ? new MemoryStore() : new LocalStore(opts.dbName);
3254
3376
  await store.open();
3255
3377
  const queue = new MutationQueue(store);
3256
3378
  const resolver = new ConflictResolver();
@@ -4020,16 +4142,16 @@ var defaultWebSocketFactory = (url) => {
4020
4142
  // src/db/client-db.ts
4021
4143
  var DATASTORE_COLLECTION_ENDPOINT = (collection) => `/api/sdk/v1/datastore/${encodeURIComponent(collection)}`;
4022
4144
  var DATASTORE_DOC_ENDPOINT = (collection, docId) => `/api/sdk/v1/datastore/${encodeURIComponent(collection)}/${encodeURIComponent(docId)}`;
4023
- function resolveTransport(engine, opts, config) {
4145
+ function resolveTransport(transport, opts, config) {
4024
4146
  if (opts._transport) {
4025
4147
  return opts._transport;
4026
4148
  }
4027
- if (engine === "firestore") {
4149
+ if (transport === "firestore") {
4028
4150
  if (opts.firestoreTransport) {
4029
4151
  return opts.firestoreTransport;
4030
4152
  }
4031
4153
  }
4032
- if (engine === "nosql-vm" && opts.realtimeGatewayUrl) {
4154
+ if (transport === "nosql-vm" && opts.realtimeGatewayUrl) {
4033
4155
  return createWebSocketSyncTransport(opts.realtimeGatewayUrl, config, opts);
4034
4156
  }
4035
4157
  return createRestSyncTransport(config);
@@ -4173,8 +4295,18 @@ function buildTicketProvider(config, opts) {
4173
4295
  };
4174
4296
  }
4175
4297
  function createNeetruDb(config, dbOpts = {}) {
4176
- const engine = dbOpts.engine ?? "rest";
4177
- const transport = resolveTransport(engine, dbOpts, config);
4298
+ let resolvedTransport;
4299
+ if (dbOpts.transport !== void 0) {
4300
+ resolvedTransport = dbOpts.transport;
4301
+ } else if (dbOpts.engine !== void 0) {
4302
+ console.warn(
4303
+ '[neetru/sdk] NeetruDbOptions.engine est\xE1 depreciado e ser\xE1 removido em v3.0. Use NeetruDbOptions.transport em vez de engine. Valores v\xE1lidos: "rest" | "firestore" | "nosql-vm". Nota: "engine" no SDK configura o TRANSPORTE (REST/Firestore/WebSocket), n\xE3o o storage engine do admin database (firestore-instance, cloud-sql-postgres, \u2026).'
4304
+ );
4305
+ resolvedTransport = dbOpts.engine;
4306
+ } else {
4307
+ resolvedTransport = "rest";
4308
+ }
4309
+ const transport = resolveTransport(resolvedTransport, dbOpts, config);
4178
4310
  const dbId = dbOpts.dbId ?? "default";
4179
4311
  const dbName = dbOpts.dbName ?? `neetru-db__${config.productId ?? "sdk"}__${dbId}__${config.env}`;
4180
4312
  let _docsPromise = null;