@neetru/sdk 3.0.1 → 3.0.2

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 (46) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/auth.cjs +61 -13
  3. package/dist/auth.cjs.map +1 -1
  4. package/dist/auth.d.cts +2 -2
  5. package/dist/auth.d.ts +2 -2
  6. package/dist/auth.mjs +61 -13
  7. package/dist/auth.mjs.map +1 -1
  8. package/dist/catalog.d.cts +2 -2
  9. package/dist/catalog.d.ts +2 -2
  10. package/dist/checkout.d.cts +2 -2
  11. package/dist/checkout.d.ts +2 -2
  12. package/dist/{collection-ref-DqAAhuhX.d.cts → collection-ref-BDdfD87k.d.cts} +118 -9
  13. package/dist/{collection-ref-DqAAhuhX.d.ts → collection-ref-BDdfD87k.d.ts} +118 -9
  14. package/dist/db-react.d.cts +1 -1
  15. package/dist/db-react.d.ts +1 -1
  16. package/dist/db.cjs +81 -16
  17. package/dist/db.cjs.map +1 -1
  18. package/dist/db.d.cts +2 -2
  19. package/dist/db.d.ts +2 -2
  20. package/dist/db.mjs +76 -17
  21. package/dist/db.mjs.map +1 -1
  22. package/dist/entitlements.d.cts +2 -2
  23. package/dist/entitlements.d.ts +2 -2
  24. package/dist/index.cjs +81 -16
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +2 -2
  27. package/dist/index.d.ts +2 -2
  28. package/dist/index.mjs +76 -17
  29. package/dist/index.mjs.map +1 -1
  30. package/dist/mocks.d.cts +2 -2
  31. package/dist/mocks.d.ts +2 -2
  32. package/dist/notifications.d.cts +2 -2
  33. package/dist/notifications.d.ts +2 -2
  34. package/dist/react.d.cts +2 -2
  35. package/dist/react.d.ts +2 -2
  36. package/dist/support.d.cts +2 -2
  37. package/dist/support.d.ts +2 -2
  38. package/dist/telemetry.d.cts +2 -2
  39. package/dist/telemetry.d.ts +2 -2
  40. package/dist/{types-DALIhcbq.d.ts → types-D7zVkO3n.d.ts} +1 -1
  41. package/dist/{types-Lfd3LiAF.d.cts → types-Dc4bjrrt.d.cts} +1 -1
  42. package/dist/usage.d.cts +2 -2
  43. package/dist/usage.d.ts +2 -2
  44. package/dist/webhooks.d.cts +2 -2
  45. package/dist/webhooks.d.ts +2 -2
  46. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,44 @@ 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
+ ## [3.0.2] - 2026-06-02 — **fix: sentinels de escrita server-side (serverTimestamp + increment) — bug_05aedb8b**
9
+
10
+ ### Fixed / Added
11
+
12
+ Fecha 3 lacunas do DB de documentos (`client.db.collection()`):
13
+
14
+ - **`serverTimestamp()`** — sentinel de timestamp resolvido pelo **servidor**. Antes, `createdAt`/`updatedAt` de produto usavam o relógio do CLIENTE (spoofável + clock-skew). Agora:
15
+
16
+ ```ts
17
+ import { serverTimestamp } from '@neetru/sdk/db';
18
+ await orders.add({ total: 99, createdAt: serverTimestamp() });
19
+ ```
20
+
21
+ O marcador atravessa o transporte como `{ "__neetru__": "serverTimestamp" }` e o Core o resolve em `FieldValue.serverTimestamp()` no momento da escrita.
22
+
23
+ - **`increment(n)`** — incremento **atômico** de campo numérico (antes `update()` era só merge/replace, sujeito a lost-update sob concorrência):
24
+
25
+ ```ts
26
+ import { increment } from '@neetru/sdk/db';
27
+ await counters.update('global', { hits: increment(1) });
28
+ await wallet.update(uid, { balance: increment(-50) });
29
+ ```
30
+
31
+ Resolvido server-side via `FieldValue.increment(n)`. Aplicação otimista local: `serverTimestamp()` → `Date.now()` aproximado; `increment(n)` → soma local sobre o valor atual (valor canônico vem do servidor no sync).
32
+
33
+ - **Tipos alinhados ao runtime** — `add`/`set`/`update`/`batch` e `DbDocRef.set/update` agora aceitam `WithFieldSentinels<T>`: cada campo pode receber seu tipo OU um sentinel, sem erro de tipo. O `.d.ts` é gerado da mesma fonte do runtime (tsup), eliminando divergência declaração↔runtime.
34
+
35
+ ### Novos exports
36
+
37
+ - De `@neetru/sdk` e `@neetru/sdk/db`: `serverTimestamp`, `increment`, `isFieldSentinel`, `isServerTimestampSentinel`, `isIncrementSentinel`, `NEETRU_SENTINEL_KEY`.
38
+ - Tipos: `FieldSentinel`, `ServerTimestampSentinel`, `IncrementSentinel`, `WithFieldSentinels`.
39
+
40
+ ### Segurança
41
+
42
+ - Sentinels só são resolvidos em campos **top-level** (espelha o Core). Marcador desconhecido, `operand` não-numérico, ou sentinel aninhado → o Core rejeita com `400 validation_failed` (fail-closed) e **nunca grava** o objeto cru.
43
+
44
+ > Requer Core com o resolver de sentinels (`src/lib/datastore-sentinels.ts`) nos endpoints `/api/sdk/v1/datastore/*`. Ambos os repos mudam neste fix.
45
+
8
46
  ## [3.0.1] - 2026-05-29 — **fix: sincronizar VERSION + docstrings + contract-staging códigos de erro**
9
47
 
10
48
  ### Fixed
package/dist/auth.cjs CHANGED
@@ -3337,6 +3337,43 @@ var TabCoordinator = class {
3337
3337
 
3338
3338
  // src/db/collection-ref.ts
3339
3339
  init_db_errors();
3340
+
3341
+ // src/db/field-value.ts
3342
+ var NEETRU_SENTINEL_KEY = "__neetru__";
3343
+ function isFieldSentinel(v) {
3344
+ return typeof v === "object" && v !== null && NEETRU_SENTINEL_KEY in v && typeof v[NEETRU_SENTINEL_KEY] === "string";
3345
+ }
3346
+ function isServerTimestampSentinel(v) {
3347
+ return isFieldSentinel(v) && v.__neetru__ === "serverTimestamp";
3348
+ }
3349
+ function isIncrementSentinel(v) {
3350
+ return isFieldSentinel(v) && v.__neetru__ === "increment" && typeof v.operand === "number" && Number.isFinite(v.operand);
3351
+ }
3352
+ function resolveSentinelLocally(value, baseValue) {
3353
+ if (isServerTimestampSentinel(value)) {
3354
+ return Date.now();
3355
+ }
3356
+ if (isIncrementSentinel(value)) {
3357
+ const base = typeof baseValue === "number" && Number.isFinite(baseValue) ? baseValue : 0;
3358
+ return base + value.operand;
3359
+ }
3360
+ return value;
3361
+ }
3362
+ function resolveSentinelsLocally(data, existing) {
3363
+ let touched = false;
3364
+ const out = {};
3365
+ for (const [k, v] of Object.entries(data)) {
3366
+ if (isFieldSentinel(v)) {
3367
+ touched = true;
3368
+ out[k] = resolveSentinelLocally(v, existing?.[k]);
3369
+ } else {
3370
+ out[k] = v;
3371
+ }
3372
+ }
3373
+ return touched ? out : data;
3374
+ }
3375
+
3376
+ // src/db/collection-ref.ts
3340
3377
  function isIndexedDBUnavailable() {
3341
3378
  return typeof indexedDB === "undefined" || typeof window === "undefined";
3342
3379
  }
@@ -3465,10 +3502,12 @@ var DbCollectionRefImpl = class {
3465
3502
  return this._buildListResult(q);
3466
3503
  }
3467
3504
  async add(data) {
3505
+ const rawData = data;
3506
+ const localData = resolveSentinelsLocally(rawData, void 0);
3468
3507
  const mutation = await this._queue.enqueue({
3469
3508
  collection: this._collection,
3470
3509
  op: "add",
3471
- payload: data,
3510
+ payload: rawData,
3472
3511
  baseVersion: null,
3473
3512
  batchId: null
3474
3513
  });
@@ -3476,7 +3515,7 @@ var DbCollectionRefImpl = class {
3476
3515
  await this._store.putDoc({
3477
3516
  collection: this._collection,
3478
3517
  id: docId,
3479
- data,
3518
+ data: localData,
3480
3519
  meta: {
3481
3520
  serverVersion: null,
3482
3521
  updatedAtServer: null,
@@ -3490,25 +3529,27 @@ var DbCollectionRefImpl = class {
3490
3529
  this._bus.emit([{
3491
3530
  type: "added",
3492
3531
  collection: this._collection,
3493
- doc: { id: docId, data }
3532
+ doc: { id: docId, data: localData }
3494
3533
  }]);
3495
3534
  return { ok: true, id: docId };
3496
3535
  }
3497
3536
  async set(id, data) {
3498
3537
  assertValidId(id);
3499
3538
  const existing = await this._store.getDoc(this._collection, id);
3539
+ const rawData = data;
3540
+ const localData = resolveSentinelsLocally(rawData, void 0);
3500
3541
  const mutation = await this._queue.enqueue({
3501
3542
  collection: this._collection,
3502
3543
  docId: id,
3503
3544
  op: "set",
3504
- payload: data,
3545
+ payload: rawData,
3505
3546
  baseVersion: existing?.meta.serverVersion ?? null,
3506
3547
  batchId: null
3507
3548
  });
3508
3549
  await this._store.putDoc({
3509
3550
  collection: this._collection,
3510
3551
  id,
3511
- data,
3552
+ data: localData,
3512
3553
  meta: {
3513
3554
  serverVersion: existing?.meta.serverVersion ?? null,
3514
3555
  updatedAtServer: existing?.meta.updatedAtServer ?? null,
@@ -3522,19 +3563,22 @@ var DbCollectionRefImpl = class {
3522
3563
  this._bus.emit([{
3523
3564
  type: existing && !existing.meta.deleted ? "modified" : "added",
3524
3565
  collection: this._collection,
3525
- doc: { id, data }
3566
+ doc: { id, data: localData }
3526
3567
  }]);
3527
3568
  return { ok: true };
3528
3569
  }
3529
3570
  async update(id, data) {
3530
3571
  assertValidId(id);
3531
3572
  const existing = await this._store.getDoc(this._collection, id);
3532
- const mergedData = existing?.meta.deleted ? { ...data } : { ...existing?.data ?? {}, ...data };
3573
+ const rawData = data;
3574
+ const existingBase = existing?.meta.deleted ? void 0 : existing?.data;
3575
+ const localData = resolveSentinelsLocally(rawData, existingBase);
3576
+ const mergedData = existing?.meta.deleted ? { ...localData } : { ...existing?.data ?? {}, ...localData };
3533
3577
  const mutation = await this._queue.enqueue({
3534
3578
  collection: this._collection,
3535
3579
  docId: id,
3536
3580
  op: "update",
3537
- payload: data,
3581
+ payload: rawData,
3538
3582
  baseVersion: existing?.meta.serverVersion ?? null,
3539
3583
  batchId: null
3540
3584
  });
@@ -3591,6 +3635,7 @@ var DbCollectionRefImpl = class {
3591
3635
  const collection = this._collection;
3592
3636
  if (op.kind === "add") {
3593
3637
  const data = op.data;
3638
+ const localData = resolveSentinelsLocally(data, void 0);
3594
3639
  const mutation = await this._queue.enqueue({
3595
3640
  collection,
3596
3641
  op: "add",
@@ -3602,7 +3647,7 @@ var DbCollectionRefImpl = class {
3602
3647
  await this._store.putDoc({
3603
3648
  collection,
3604
3649
  id: docId,
3605
- data,
3650
+ data: localData,
3606
3651
  meta: {
3607
3652
  serverVersion: null,
3608
3653
  updatedAtServer: null,
@@ -3613,11 +3658,12 @@ var DbCollectionRefImpl = class {
3613
3658
  ownerId: null
3614
3659
  }
3615
3660
  });
3616
- busChanges.push({ type: "added", collection, doc: { id: docId, data } });
3661
+ busChanges.push({ type: "added", collection, doc: { id: docId, data: localData } });
3617
3662
  } else if (op.kind === "set") {
3618
3663
  const id = op.id;
3619
3664
  const data = op.data;
3620
3665
  const existing = await this._store.getDoc(collection, id);
3666
+ const localData = resolveSentinelsLocally(data, void 0);
3621
3667
  const mutation = await this._queue.enqueue({
3622
3668
  collection,
3623
3669
  docId: id,
@@ -3629,7 +3675,7 @@ var DbCollectionRefImpl = class {
3629
3675
  await this._store.putDoc({
3630
3676
  collection,
3631
3677
  id,
3632
- data,
3678
+ data: localData,
3633
3679
  meta: {
3634
3680
  serverVersion: existing?.meta.serverVersion ?? null,
3635
3681
  updatedAtServer: existing?.meta.updatedAtServer ?? null,
@@ -3643,13 +3689,15 @@ var DbCollectionRefImpl = class {
3643
3689
  busChanges.push({
3644
3690
  type: existing && !existing.meta.deleted ? "modified" : "added",
3645
3691
  collection,
3646
- doc: { id, data }
3692
+ doc: { id, data: localData }
3647
3693
  });
3648
3694
  } else if (op.kind === "update") {
3649
3695
  const id = op.id;
3650
3696
  const data = op.data;
3651
3697
  const existing = await this._store.getDoc(collection, id);
3652
- const merged = { ...existing?.data ?? {}, ...data };
3698
+ const existingBase = existing?.meta.deleted ? void 0 : existing?.data;
3699
+ const localData = resolveSentinelsLocally(data, existingBase);
3700
+ const merged = { ...existing?.data ?? {}, ...localData };
3653
3701
  const mutation = await this._queue.enqueue({
3654
3702
  collection,
3655
3703
  docId: id,