@neetru/sdk 3.0.0 → 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.
- package/CHANGELOG.md +51 -0
- package/README.md +25 -0
- package/dist/auth.cjs +61 -13
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +2 -3
- package/dist/auth.d.ts +2 -3
- package/dist/auth.mjs +61 -13
- package/dist/auth.mjs.map +1 -1
- package/dist/catalog.d.cts +2 -3
- package/dist/catalog.d.ts +2 -3
- package/dist/checkout.d.cts +2 -3
- package/dist/checkout.d.ts +2 -3
- package/dist/{collection-ref-DqAAhuhX.d.cts → collection-ref-BDdfD87k.d.cts} +118 -9
- package/dist/{collection-ref-DqAAhuhX.d.ts → collection-ref-BDdfD87k.d.ts} +118 -9
- package/dist/db-react.d.cts +1 -1
- package/dist/db-react.d.ts +1 -1
- package/dist/db.cjs +81 -16
- package/dist/db.cjs.map +1 -1
- package/dist/db.d.cts +2 -3
- package/dist/db.d.ts +2 -3
- package/dist/db.mjs +76 -17
- package/dist/db.mjs.map +1 -1
- package/dist/entitlements.d.cts +2 -3
- package/dist/entitlements.d.ts +2 -3
- package/dist/index.cjs +82 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -4
- package/dist/index.d.ts +3 -4
- package/dist/index.mjs +77 -18
- package/dist/index.mjs.map +1 -1
- package/dist/mocks.d.cts +2 -3
- package/dist/mocks.d.ts +2 -3
- package/dist/notifications.d.cts +2 -3
- package/dist/notifications.d.ts +2 -3
- package/dist/react.d.cts +2 -3
- package/dist/react.d.ts +2 -3
- package/dist/support.d.cts +2 -3
- package/dist/support.d.ts +2 -3
- package/dist/telemetry.cjs.map +1 -1
- package/dist/telemetry.d.cts +2 -3
- package/dist/telemetry.d.ts +2 -3
- package/dist/telemetry.mjs.map +1 -1
- package/dist/{types-dw19bdID.d.ts → types-D7zVkO3n.d.ts} +21 -26
- package/dist/{types-DLOvkeAP.d.cts → types-Dc4bjrrt.d.cts} +21 -26
- package/dist/usage.d.cts +2 -3
- package/dist/usage.d.ts +2 -3
- package/dist/webhooks.d.cts +2 -3
- package/dist/webhooks.d.ts +2 -3
- package/package.json +1 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,57 @@ 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
|
+
|
|
46
|
+
## [3.0.1] - 2026-05-29 — **fix: sincronizar VERSION + docstrings + contract-staging códigos de erro**
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- `src/index.ts` — `VERSION` constante sincronizada com `package.json` (`'3.0.0'` → `'3.0.1'`). Consumers que fazem `import { VERSION } from '@neetru/sdk'` recebiam `'3.0.0'` enquanto `npm list` mostrava `3.0.1` (LOW-NEW-SDK-01).
|
|
51
|
+
- `src/telemetry.ts` — JSDoc na linha 6 corrigido: path legacy `/api/v1/sdk/telemetry/event` → canônico `/api/sdk/v1/telemetry/event`. O código sempre usou o path correto; apenas a documentação estava stale (LOW-NEW-SDK-02).
|
|
52
|
+
- `src/__tests__/telemetry.test.ts` — comentário de cabeçalho atualizado para não dizer "(legado)" (LOW-NEW-SDK-02).
|
|
53
|
+
- `src/__tests__/contract-staging.test.ts` — `'http_error'` removido de todos os arrays `toContain`. `NeetruErrorCode` não inclui `'http_error'`; respostas HTTP não mapeáveis caem em `'unknown'`. Substituído por `'unknown'` em todos os 7 sites (LOW-NEW-SDK-03).
|
|
54
|
+
|
|
55
|
+
### Nota
|
|
56
|
+
|
|
57
|
+
Este patch corresponde ao commit `4c40f01` (inline realtime-protocol types) que não atualizou `VERSION` nem o CHANGELOG.
|
|
58
|
+
|
|
8
59
|
## [3.0.0] - 2026-05-27 — **MAJOR: contract hardening + Universal runtime + paths canonical**
|
|
9
60
|
|
|
10
61
|
> Major rewrite resolvendo 5 CRITICAL + 6 HIGH do review `CHEFE_SDK_REVIEW_2026-05-27`.
|
package/README.md
CHANGED
|
@@ -221,6 +221,31 @@ const neetru = createNeetruClient({
|
|
|
221
221
|
- Zero dependências runtime (usa `fetch` global)
|
|
222
222
|
- Bundle minzip <10KB (todos namespaces somados, tree-shake-friendly)
|
|
223
223
|
|
|
224
|
+
## Testes de contrato (staging)
|
|
225
|
+
|
|
226
|
+
O arquivo `src/__tests__/contract-staging.test.ts` contém testes que exercitam o HTTP real contra um ambiente de staging. Por default, todos os testes são skippados quando `NEETRU_STAGING_URL` não estiver definido.
|
|
227
|
+
|
|
228
|
+
Para rodar:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
NEETRU_STAGING_URL=https://api.staging.neetru.com \
|
|
232
|
+
NEETRU_STAGING_API_KEY=nrt_<keyId>_<secret> \
|
|
233
|
+
npx vitest run src/__tests__/contract-staging.test.ts
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Variáveis de ambiente:
|
|
237
|
+
|
|
238
|
+
| Variável | Obrigatória | Descrição |
|
|
239
|
+
|---|---|---|
|
|
240
|
+
| `NEETRU_STAGING_URL` | Sim | URL base da API de staging |
|
|
241
|
+
| `NEETRU_STAGING_API_KEY` | Sim | API key no formato `nrt_<keyId>_<secret>` |
|
|
242
|
+
| `NEETRU_STAGING_PRODUCT_ID` | Não | productId cadastrado no staging (default: `gestovendas`) |
|
|
243
|
+
| `NEETRU_STAGING_TENANT_ID` | Não | tenantId válido no staging (default: `t-acme`) |
|
|
244
|
+
|
|
245
|
+
Namespaces cobertos: `catalog.list`, `catalog.get`, `entitlements.check`, `entitlements.checkDetailed`, `usage.report`, `usage.check`, `telemetry.event`, `telemetry.log`, `auth.getUser`, `auth.signIn`.
|
|
246
|
+
|
|
247
|
+
Os testes assertam apenas o shape/tipos da resposta, nunca valores exatos — staging pode ter dados variáveis mas o contrato de formato deve ser estável.
|
|
248
|
+
|
|
224
249
|
## Mais info
|
|
225
250
|
|
|
226
251
|
- **Repo:** [github.com/Neetru/neetru-core](https://github.com/Neetru/neetru-core)
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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,
|