@neetru/sdk 3.1.7 → 3.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/auth.cjs +65 -22
  2. package/dist/auth.cjs.map +1 -1
  3. package/dist/auth.d.cts +1 -1
  4. package/dist/auth.d.ts +1 -1
  5. package/dist/auth.mjs +65 -22
  6. package/dist/auth.mjs.map +1 -1
  7. package/dist/catalog.d.cts +1 -1
  8. package/dist/catalog.d.ts +1 -1
  9. package/dist/checkout.d.cts +1 -1
  10. package/dist/checkout.d.ts +1 -1
  11. package/dist/db-react.d.cts +1 -1
  12. package/dist/db-react.d.ts +1 -1
  13. package/dist/db.cjs +55 -16
  14. package/dist/db.cjs.map +1 -1
  15. package/dist/db.d.cts +1 -1
  16. package/dist/db.d.ts +1 -1
  17. package/dist/db.mjs +55 -16
  18. package/dist/db.mjs.map +1 -1
  19. package/dist/entitlements.d.cts +1 -1
  20. package/dist/entitlements.d.ts +1 -1
  21. package/dist/firestore-compat.cjs +4 -9
  22. package/dist/firestore-compat.cjs.map +1 -1
  23. package/dist/firestore-compat.d.cts +2 -2
  24. package/dist/firestore-compat.d.ts +2 -2
  25. package/dist/firestore-compat.mjs +4 -9
  26. package/dist/firestore-compat.mjs.map +1 -1
  27. package/dist/index.cjs +66 -23
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +2 -2
  30. package/dist/index.d.ts +2 -2
  31. package/dist/index.mjs +66 -23
  32. package/dist/index.mjs.map +1 -1
  33. package/dist/mocks.d.cts +1 -1
  34. package/dist/mocks.d.ts +1 -1
  35. package/dist/notifications.d.cts +1 -1
  36. package/dist/notifications.d.ts +1 -1
  37. package/dist/react.cjs.map +1 -1
  38. package/dist/react.d.cts +1 -1
  39. package/dist/react.d.ts +1 -1
  40. package/dist/react.mjs.map +1 -1
  41. package/dist/support.d.cts +1 -1
  42. package/dist/support.d.ts +1 -1
  43. package/dist/telemetry.cjs +5 -1
  44. package/dist/telemetry.cjs.map +1 -1
  45. package/dist/telemetry.d.cts +1 -1
  46. package/dist/telemetry.d.ts +1 -1
  47. package/dist/telemetry.mjs +5 -1
  48. package/dist/telemetry.mjs.map +1 -1
  49. package/dist/{types-BXvGHppn.d.cts → types-DDiVzgCT.d.cts} +19 -2
  50. package/dist/{types-CQVak2a3.d.ts → types-HWDOUmlC.d.ts} +19 -2
  51. package/dist/usage.cjs +2 -2
  52. package/dist/usage.cjs.map +1 -1
  53. package/dist/usage.d.cts +1 -1
  54. package/dist/usage.d.ts +1 -1
  55. package/dist/usage.mjs +2 -2
  56. package/dist/usage.mjs.map +1 -1
  57. package/dist/webhooks.d.cts +1 -1
  58. package/dist/webhooks.d.ts +1 -1
  59. package/package.json +1 -1
package/dist/auth.cjs CHANGED
@@ -448,7 +448,7 @@ __export(sql_client_exports, {
448
448
  function mapEnvToCore(sdkEnv) {
449
449
  if (sdkEnv === "workspace") return "staging";
450
450
  if (sdkEnv === "prod") return "production";
451
- return "dev";
451
+ return "dev-local";
452
452
  }
453
453
  function createHttpLeaseFetcher(config) {
454
454
  return async (opts) => {
@@ -957,7 +957,11 @@ function createTelemetryNamespace(config) {
957
957
  path: "/api/sdk/v1/telemetry/event",
958
958
  body,
959
959
  requireAuth: true,
960
- retries: 1
960
+ retries: 1,
961
+ // caça-bruxas: retry sem Idempotency-Key duplicava o evento se o
962
+ // servidor já tinha persistido antes da falha transiente. Mesmo
963
+ // padrão do usage.report (key estável por chamada, reusada no retry).
964
+ idempotencyKey: true
961
965
  });
962
966
  } catch (err) {
963
967
  if (typeof console !== "undefined") {
@@ -1161,8 +1165,8 @@ function createUsageNamespace(config) {
1161
1165
  if (!resource || typeof resource !== "string") {
1162
1166
  throw new NeetruError("validation_failed", "resource is required");
1163
1167
  }
1164
- if (!Number.isFinite(qty) || qty <= 0) {
1165
- throw new NeetruError("validation_failed", "qty must be positive integer");
1168
+ if (!Number.isFinite(qty) || qty <= 0 || !Number.isInteger(qty)) {
1169
+ throw new NeetruError("validation_failed", "qty must be a positive integer");
1166
1170
  }
1167
1171
  const productId = options?.productId ?? config.productId;
1168
1172
  const tenantId = options?.tenantId ?? config.tenantId;
@@ -1411,7 +1415,12 @@ function evaluateFilter(data, filter) {
1411
1415
  if (!Array.isArray(value)) return true;
1412
1416
  return !value.includes(fieldValue);
1413
1417
  default:
1414
- return false;
1418
+ if (op === "array-contains-any") {
1419
+ return Array.isArray(fieldValue) && Array.isArray(value) && value.some((v) => fieldValue.includes(v));
1420
+ }
1421
+ throw new Error(
1422
+ `QueryEngine: operador de filtro '${String(op)}' n\xE3o suportado em leitura offline/cache. Use um operador suportado (==, !=, <, <=, >, >=, array-contains, array-contains-any, in, not-in) ou fa\xE7a a query server-side.`
1423
+ );
1415
1424
  }
1416
1425
  }
1417
1426
  function matchesAllFilters(data, filters) {
@@ -1941,7 +1950,7 @@ var MutationQueue = class {
1941
1950
  * - `{ removed: true }` — a mutação existente deve ser deletada (add+remove)
1942
1951
  * - `null` — não é possível coalescir
1943
1952
  */
1944
- tryCoalesce(existing, newOp, newPayload, newBatchId) {
1953
+ tryCoalesce(existing, newOp, newPayload, newBatchId, newUpsert) {
1945
1954
  if (existing.batchId !== newBatchId) return null;
1946
1955
  if (existing.status === "inflight") return null;
1947
1956
  const existingOp = existing.op;
@@ -1949,10 +1958,12 @@ var MutationQueue = class {
1949
1958
  return { removed: true };
1950
1959
  }
1951
1960
  if (existingOp === "update" && newOp === "update") {
1961
+ const mergedUpsert = existing.upsert || newUpsert;
1952
1962
  return {
1953
1963
  coalesced: {
1954
1964
  ...existing,
1955
- payload: { ...existing.payload ?? {}, ...newPayload ?? {} }
1965
+ payload: { ...existing.payload ?? {}, ...newPayload ?? {} },
1966
+ ...mergedUpsert ? { upsert: true } : {}
1956
1967
  }
1957
1968
  };
1958
1969
  }
@@ -2001,13 +2012,13 @@ var MutationQueue = class {
2001
2012
  * Atômico: a persistência final é uma única operação putMutation.
2002
2013
  */
2003
2014
  async enqueue(params) {
2004
- const { collection, op, payload, baseVersion, batchId } = params;
2015
+ const { collection, op, payload, baseVersion, batchId, upsert } = params;
2005
2016
  const docId = params.docId ?? (op === "add" ? generateUUIDv4() : "");
2006
2017
  await this.syncSeq();
2007
2018
  const existingMutations = await this.store.listMutations({ collection, docId });
2008
2019
  const lastQueued = existingMutations.filter((m) => m.status === "queued").sort((a, b) => b.seq - a.seq)[0];
2009
2020
  if (lastQueued !== void 0) {
2010
- const coalesceResult = this.tryCoalesce(lastQueued, op, payload, batchId);
2021
+ const coalesceResult = this.tryCoalesce(lastQueued, op, payload, batchId, upsert);
2011
2022
  if (coalesceResult !== null) {
2012
2023
  if ("removed" in coalesceResult) {
2013
2024
  await this.store.deleteMutation(lastQueued.mutationId);
@@ -2045,7 +2056,9 @@ var MutationQueue = class {
2045
2056
  attempts: 0,
2046
2057
  lastError: null,
2047
2058
  status: "queued",
2048
- batchId
2059
+ batchId,
2060
+ // bug_0273e4dd: só persiste o flag quando verdadeiro (mantém o doc limpo).
2061
+ ...upsert ? { upsert: true } : {}
2049
2062
  };
2050
2063
  await this.store.putMutation(mutation);
2051
2064
  return mutation;
@@ -2374,10 +2387,15 @@ var SyncEngine = class {
2374
2387
  await this._applyRejected(mut, res, busChanges);
2375
2388
  }
2376
2389
  }
2390
+ const processedIds = new Set(result.results.map((r) => r.mutationId));
2391
+ const unprocessed = mutations.filter((m) => !processedIds.has(m.mutationId));
2392
+ for (const mut of unprocessed) {
2393
+ await this._queue.markRetry(mut.mutationId, "partial_push: muta\xE7\xE3o n\xE3o processada no lote");
2394
+ }
2377
2395
  if (busChanges.length > 0) {
2378
2396
  this._bus.emit(busChanges);
2379
2397
  }
2380
- return true;
2398
+ return unprocessed.length === 0;
2381
2399
  }
2382
2400
  /** Atualiza o cache após confirmação de escrita (outcome=confirmed). */
2383
2401
  async _applySyncedDoc(collection, docId, serverVersion, serverTimestamp, mut, busChanges) {
@@ -3684,7 +3702,7 @@ var DbCollectionRefImpl = class {
3684
3702
  }]);
3685
3703
  return { ok: true };
3686
3704
  }
3687
- async update(id, data) {
3705
+ async update(id, data, opts) {
3688
3706
  assertValidId(id);
3689
3707
  const existing = await this._store.getDoc(this._collection, id);
3690
3708
  const rawData = data;
@@ -3697,7 +3715,9 @@ var DbCollectionRefImpl = class {
3697
3715
  op: "update",
3698
3716
  payload: rawData,
3699
3717
  baseVersion: existing?.meta.serverVersion ?? null,
3700
- batchId: null
3718
+ batchId: null,
3719
+ // bug_0273e4dd: propaga merge-upsert (setDoc({merge:true}) cria-se-ausente).
3720
+ upsert: opts?.upsert
3701
3721
  });
3702
3722
  await this._store.putDoc({
3703
3723
  collection: this._collection,
@@ -3821,7 +3841,9 @@ var DbCollectionRefImpl = class {
3821
3841
  op: "update",
3822
3842
  payload: data,
3823
3843
  baseVersion: existing?.meta.serverVersion ?? null,
3824
- batchId
3844
+ batchId,
3845
+ // bug_0273e4dd: propaga merge-upsert (writeBatch.set(merge) cria-se-ausente).
3846
+ upsert: op.upsert
3825
3847
  });
3826
3848
  await this._store.putDoc({
3827
3849
  collection,
@@ -4083,6 +4105,10 @@ async function createOfflineDocumentsNamespace(opts) {
4083
4105
  connectivity,
4084
4106
  periodicSyncIntervalMs: opts.periodicSyncIntervalMs ?? 5 * 60 * 1e3
4085
4107
  });
4108
+ connectivity.start();
4109
+ if (!opts.singleTab && tabCoordinatorLike instanceof TabCoordinator) {
4110
+ tabCoordinatorLike.start();
4111
+ }
4086
4112
  return new NeetruDbDocumentsImpl({
4087
4113
  store,
4088
4114
  queue,
@@ -4552,11 +4578,11 @@ function createRestSyncTransport(config) {
4552
4578
  let raw;
4553
4579
  if (m.op === "add") {
4554
4580
  raw = await httpRequest2(config, {
4555
- method: "POST",
4556
- path: DATASTORE_COLLECTION_ENDPOINT(m.collection),
4581
+ method: "PUT",
4582
+ path: DATASTORE_DOC_ENDPOINT(m.collection, m.docId),
4557
4583
  body: { data: m.payload ?? {} },
4558
4584
  requireAuth: true,
4559
- retries: 0,
4585
+ retries: 2,
4560
4586
  headers: tenantHeaders
4561
4587
  });
4562
4588
  } else if (m.op === "set") {
@@ -4572,7 +4598,7 @@ function createRestSyncTransport(config) {
4572
4598
  raw = await httpRequest2(config, {
4573
4599
  method: "PATCH",
4574
4600
  path: DATASTORE_DOC_ENDPOINT(m.collection, m.docId),
4575
- body: { data: m.payload ?? {} },
4601
+ body: { data: m.payload ?? {}, ...m.upsert ? { upsert: true } : {} },
4576
4602
  requireAuth: true,
4577
4603
  retries: 2,
4578
4604
  headers: tenantHeaders
@@ -4595,7 +4621,24 @@ function createRestSyncTransport(config) {
4595
4621
  });
4596
4622
  } catch (err) {
4597
4623
  const msg = err instanceof Error ? err.message : String(err);
4598
- throw new NeetruDbError("db_unavailable", `pushMutations falhou: ${msg}`);
4624
+ const code = err?.code;
4625
+ const status = err?.status;
4626
+ const isPermanent = code === "forbidden" || code === "unauthorized" || code === "not_found" || code === "conflict" || code === "validation_failed" || typeof status === "number" && status >= 400 && status < 500 && status !== 408 && status !== 429;
4627
+ if (isPermanent) {
4628
+ const reason = code === "forbidden" || code === "unauthorized" ? "rejected_permission" : "rejected_validation";
4629
+ results.push({
4630
+ mutationId: m.mutationId,
4631
+ outcome: "rejected",
4632
+ reason,
4633
+ serverVersion: null,
4634
+ serverData: null
4635
+ });
4636
+ continue;
4637
+ }
4638
+ if (results.length === 0) {
4639
+ throw new NeetruDbError("db_unavailable", `pushMutations falhou: ${msg}`);
4640
+ }
4641
+ break;
4599
4642
  }
4600
4643
  }
4601
4644
  return { results };
@@ -6111,15 +6154,15 @@ function createOidcAuthNamespace(config) {
6111
6154
  const overrideAuthUrl = typeof globalThis.NEETRU_AUTH_URL === "string" ? globalThis.NEETRU_AUTH_URL : null;
6112
6155
  const idpOrigin = overrideAuthUrl ?? config.baseUrl.replace(/^https?:\/\/api\./, "https://auth.");
6113
6156
  const issuer = idpOrigin;
6114
- const jwksUrl = `${idpOrigin}/api/v1/oauth/.well-known/jwks.json`;
6157
+ const jwksUrl = `${idpOrigin}/api/v1/oauth/.well-known/jwks`;
6115
6158
  const cacheTtlMs = options?.jwksCacheTtlMs ?? DEFAULT_JWKS_TTL_MS;
6116
6159
  const audienceFromOptions = options?.audience;
6117
6160
  const parsedKey = tryParseApiKey(config.apiKey);
6118
- const audience = audienceFromOptions ?? parsedKey?.keyId;
6161
+ const audience = audienceFromOptions ?? config.oidcClientId ?? parsedKey?.keyId;
6119
6162
  if (!audience) {
6120
6163
  throw new NeetruError(
6121
6164
  "invalid_config",
6122
- "verifyToken requires an audience: provide apiKey on the client or pass options.audience. Calling jwtVerify without an audience allows tokens issued for other OIDC clients to pass."
6165
+ "verifyToken requires an audience: provide oidcClientId/apiKey on the client or pass options.audience. Calling jwtVerify without an audience allows tokens issued for other OIDC clients to pass."
6123
6166
  );
6124
6167
  }
6125
6168
  const jwks = _getJwks(jwksUrl, cacheTtlMs);