@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.
Files changed (49) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +25 -0
  3. package/dist/auth.cjs +61 -13
  4. package/dist/auth.cjs.map +1 -1
  5. package/dist/auth.d.cts +2 -3
  6. package/dist/auth.d.ts +2 -3
  7. package/dist/auth.mjs +61 -13
  8. package/dist/auth.mjs.map +1 -1
  9. package/dist/catalog.d.cts +2 -3
  10. package/dist/catalog.d.ts +2 -3
  11. package/dist/checkout.d.cts +2 -3
  12. package/dist/checkout.d.ts +2 -3
  13. package/dist/{collection-ref-DqAAhuhX.d.cts → collection-ref-BDdfD87k.d.cts} +118 -9
  14. package/dist/{collection-ref-DqAAhuhX.d.ts → collection-ref-BDdfD87k.d.ts} +118 -9
  15. package/dist/db-react.d.cts +1 -1
  16. package/dist/db-react.d.ts +1 -1
  17. package/dist/db.cjs +81 -16
  18. package/dist/db.cjs.map +1 -1
  19. package/dist/db.d.cts +2 -3
  20. package/dist/db.d.ts +2 -3
  21. package/dist/db.mjs +76 -17
  22. package/dist/db.mjs.map +1 -1
  23. package/dist/entitlements.d.cts +2 -3
  24. package/dist/entitlements.d.ts +2 -3
  25. package/dist/index.cjs +82 -17
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +3 -4
  28. package/dist/index.d.ts +3 -4
  29. package/dist/index.mjs +77 -18
  30. package/dist/index.mjs.map +1 -1
  31. package/dist/mocks.d.cts +2 -3
  32. package/dist/mocks.d.ts +2 -3
  33. package/dist/notifications.d.cts +2 -3
  34. package/dist/notifications.d.ts +2 -3
  35. package/dist/react.d.cts +2 -3
  36. package/dist/react.d.ts +2 -3
  37. package/dist/support.d.cts +2 -3
  38. package/dist/support.d.ts +2 -3
  39. package/dist/telemetry.cjs.map +1 -1
  40. package/dist/telemetry.d.cts +2 -3
  41. package/dist/telemetry.d.ts +2 -3
  42. package/dist/telemetry.mjs.map +1 -1
  43. package/dist/{types-dw19bdID.d.ts → types-D7zVkO3n.d.ts} +21 -26
  44. package/dist/{types-DLOvkeAP.d.cts → types-Dc4bjrrt.d.cts} +21 -26
  45. package/dist/usage.d.cts +2 -3
  46. package/dist/usage.d.ts +2 -3
  47. package/dist/webhooks.d.cts +2 -3
  48. package/dist/webhooks.d.ts +2 -3
  49. package/package.json +1 -2
@@ -1,6 +1,5 @@
1
- import { I as ResolvedConfig, C as CatalogListOptions, b as CatalogListResponse, P as Product } from './types-DLOvkeAP.cjs';
2
- import './collection-ref-DqAAhuhX.cjs';
3
- import '@neetru/realtime-protocol';
1
+ import { I as ResolvedConfig, C as CatalogListOptions, b as CatalogListResponse, P as Product } from './types-Dc4bjrrt.cjs';
2
+ import './collection-ref-BDdfD87k.cjs';
4
3
  import 'drizzle-orm/node-postgres';
5
4
  import './errors.cjs';
6
5
 
package/dist/catalog.d.ts CHANGED
@@ -1,6 +1,5 @@
1
- import { I as ResolvedConfig, C as CatalogListOptions, b as CatalogListResponse, P as Product } from './types-dw19bdID.js';
2
- import './collection-ref-DqAAhuhX.js';
3
- import '@neetru/realtime-protocol';
1
+ import { I as ResolvedConfig, C as CatalogListOptions, b as CatalogListResponse, P as Product } from './types-D7zVkO3n.js';
2
+ import './collection-ref-BDdfD87k.js';
4
3
  import 'drizzle-orm/node-postgres';
5
4
  import './errors.js';
6
5
 
@@ -1,5 +1,4 @@
1
- export { c as CheckoutIntentInfo, d as CheckoutIntentStatus, e as CheckoutNamespace, f as CheckoutStartInput, g as CheckoutStartResult, h as CheckoutTenantType, M as MockCheckout, a6 as createCheckoutNamespace } from './types-DLOvkeAP.cjs';
2
- import './collection-ref-DqAAhuhX.cjs';
3
- import '@neetru/realtime-protocol';
1
+ export { c as CheckoutIntentInfo, d as CheckoutIntentStatus, e as CheckoutNamespace, f as CheckoutStartInput, g as CheckoutStartResult, h as CheckoutTenantType, M as MockCheckout, a6 as createCheckoutNamespace } from './types-Dc4bjrrt.cjs';
2
+ import './collection-ref-BDdfD87k.cjs';
4
3
  import 'drizzle-orm/node-postgres';
5
4
  import './errors.cjs';
@@ -1,5 +1,4 @@
1
- export { c as CheckoutIntentInfo, d as CheckoutIntentStatus, e as CheckoutNamespace, f as CheckoutStartInput, g as CheckoutStartResult, h as CheckoutTenantType, M as MockCheckout, a6 as createCheckoutNamespace } from './types-dw19bdID.js';
2
- import './collection-ref-DqAAhuhX.js';
3
- import '@neetru/realtime-protocol';
1
+ export { c as CheckoutIntentInfo, d as CheckoutIntentStatus, e as CheckoutNamespace, f as CheckoutStartInput, g as CheckoutStartResult, h as CheckoutTenantType, M as MockCheckout, a6 as createCheckoutNamespace } from './types-D7zVkO3n.js';
2
+ import './collection-ref-BDdfD87k.js';
4
3
  import 'drizzle-orm/node-postgres';
5
4
  import './errors.js';
@@ -228,6 +228,98 @@ interface SyncTransport {
228
228
  }>, needsResync: boolean) => void) => () => void;
229
229
  }
230
230
 
231
+ /**
232
+ * field-value.ts — Sentinels de escrita server-side para o Mundo Documentos.
233
+ *
234
+ * Resolve duas lacunas confirmadas (bug_05aedb8b) da superfície `client.db`:
235
+ *
236
+ * 1. **Timestamp resolvido no servidor** — antes deste módulo, o produto que
237
+ * quisesse `createdAt`/`updatedAt` confiáveis tinha que usar `Date.now()`
238
+ * no CLIENTE, que é spoofável e sofre clock-skew. Agora:
239
+ *
240
+ * await orders.add({ total: 99, createdAt: serverTimestamp() });
241
+ *
242
+ * O marcador atravessa o transporte REST como um objeto JSON namespaceado
243
+ * e o Core o substitui por `FieldValue.serverTimestamp()` no momento da
244
+ * escrita — relógio do SERVIDOR, não do cliente.
245
+ *
246
+ * 2. **Increment atômico** — antes, `update(id, { n: x+1 })` era read-modify-write
247
+ * no cliente, sujeito a lost-update sob concorrência. Agora:
248
+ *
249
+ * await counters.update('global', { hits: increment(1) });
250
+ *
251
+ * O Core aplica `FieldValue.increment(1)` atomicamente no documento.
252
+ *
253
+ * ## Contrato de fio (wire format)
254
+ *
255
+ * Os sentinels são objetos JSON simples — sobrevivem `JSON.stringify` no
256
+ * transporte REST e ao `IndexedDB structured clone` da camada offline:
257
+ *
258
+ * serverTimestamp() → `{ "__neetru__": "serverTimestamp" }`
259
+ * increment(n) → `{ "__neetru__": "increment", "operand": n }`
260
+ *
261
+ * A chave-âncora `__neetru__` é deliberadamente improvável de colidir com
262
+ * dados de produto. O Core valida e resolve esses marcadores; qualquer objeto
263
+ * com `__neetru__` desconhecido é rejeitado server-side (fail-closed).
264
+ *
265
+ * ## Aplicação otimista (offline-first)
266
+ *
267
+ * A camada offline aplica uma APROXIMAÇÃO local imediata (para a UI não piscar):
268
+ * - `serverTimestamp()` → `Date.now()` local (substituído pelo valor real no sync).
269
+ * - `increment(n)` → soma `n` ao valor numérico atual do campo (0 se ausente).
270
+ * O valor canônico vem sempre do servidor no pull/confirm subsequente.
271
+ *
272
+ * @module
273
+ */
274
+ /** Chave-âncora dos marcadores de sentinel no wire format. */
275
+ declare const NEETRU_SENTINEL_KEY: "__neetru__";
276
+ /** Marcador de timestamp resolvido pelo servidor. */
277
+ interface ServerTimestampSentinel {
278
+ readonly __neetru__: 'serverTimestamp';
279
+ }
280
+ /** Marcador de incremento atômico aplicado pelo servidor. */
281
+ interface IncrementSentinel {
282
+ readonly __neetru__: 'increment';
283
+ /** Delta a somar (pode ser negativo para decremento). */
284
+ readonly operand: number;
285
+ }
286
+ /**
287
+ * União de todos os sentinels de campo. Use `serverTimestamp()` / `increment()`
288
+ * para construir — nunca monte o objeto à mão.
289
+ */
290
+ type FieldSentinel = ServerTimestampSentinel | IncrementSentinel;
291
+ /**
292
+ * Sentinel que instrui o servidor a gravar o timestamp do MOMENTO da escrita
293
+ * usando o relógio do servidor (não do cliente).
294
+ *
295
+ * @example
296
+ * ```ts
297
+ * import { serverTimestamp } from '@neetru/sdk/db';
298
+ * await orders.add({ total: 99, createdAt: serverTimestamp() });
299
+ * ```
300
+ */
301
+ declare function serverTimestamp(): ServerTimestampSentinel;
302
+ /**
303
+ * Sentinel que instrui o servidor a incrementar atomicamente o campo numérico
304
+ * por `n` (negativo decrementa). Se o campo não existir/não for numérico, o
305
+ * servidor o trata como `0` antes de somar (semântica `FieldValue.increment`).
306
+ *
307
+ * @param n — delta inteiro ou fracionário a somar.
308
+ * @example
309
+ * ```ts
310
+ * import { increment } from '@neetru/sdk/db';
311
+ * await counters.update('global', { hits: increment(1) });
312
+ * await wallet.update(uid, { balance: increment(-50) });
313
+ * ```
314
+ */
315
+ declare function increment(n: number): IncrementSentinel;
316
+ /** `true` se `v` é qualquer sentinel de campo Neetru. */
317
+ declare function isFieldSentinel(v: unknown): v is FieldSentinel;
318
+ /** `true` se `v` é o sentinel `serverTimestamp()`. */
319
+ declare function isServerTimestampSentinel(v: unknown): v is ServerTimestampSentinel;
320
+ /** `true` se `v` é o sentinel `increment(n)` com `operand` numérico válido. */
321
+ declare function isIncrementSentinel(v: unknown): v is IncrementSentinel;
322
+
231
323
  /**
232
324
  * DbCollectionRef — superfície de Documentos offline-first do @neetru/sdk (M2).
233
325
  *
@@ -251,6 +343,23 @@ interface SyncTransport {
251
343
  * sem alterar a superfície pública.
252
344
  */
253
345
 
346
+ /**
347
+ * Permite que cada campo de `T` aceite, além do seu tipo declarado, um sentinel
348
+ * de escrita server-side (`serverTimestamp()` / `increment()`).
349
+ *
350
+ * Sem isso, um produto que tipa `createdAt: number` veria erro de tipo ao passar
351
+ * `serverTimestamp()`. Esta projeção mantém o autocomplete dos campos do produto
352
+ * e ainda autoriza os sentinels onde fizer sentido.
353
+ *
354
+ * @example
355
+ * ```ts
356
+ * interface Order { total: number; createdAt: number; hits: number }
357
+ * orders.add({ total: 10, createdAt: serverTimestamp(), hits: increment(1) });
358
+ * ```
359
+ */
360
+ type WithFieldSentinels<T> = {
361
+ [K in keyof T]: T[K] | FieldSentinel;
362
+ };
254
363
  interface DbWhereFilter {
255
364
  field: string;
256
365
  op: '==' | '!=' | '<' | '<=' | '>' | '>=' | 'array-contains' | 'in' | 'not-in';
@@ -317,15 +426,15 @@ type DbChangeType = 'added' | 'modified' | 'removed';
317
426
  */
318
427
  type DbBatchOp<T = Record<string, unknown>> = {
319
428
  kind: 'add';
320
- data: Omit<T, 'id'>;
429
+ data: WithFieldSentinels<Omit<T, 'id'>>;
321
430
  } | {
322
431
  kind: 'set';
323
432
  id: string;
324
- data: Omit<T, 'id'>;
433
+ data: WithFieldSentinels<Omit<T, 'id'>>;
325
434
  } | {
326
435
  kind: 'update';
327
436
  id: string;
328
- data: Partial<Omit<T, 'id'>>;
437
+ data: Partial<WithFieldSentinels<Omit<T, 'id'>>>;
329
438
  } | {
330
439
  kind: 'remove';
331
440
  id: string;
@@ -365,10 +474,10 @@ interface DbDocRef<T = Record<string, unknown>> {
365
474
  /** Nome da coleção parent. */
366
475
  readonly collection: string;
367
476
  get(): Promise<DbGetResult<T> | null>;
368
- set(data: Omit<T, 'id'>): Promise<{
477
+ set(data: WithFieldSentinels<Omit<T, 'id'>>): Promise<{
369
478
  ok: true;
370
479
  }>;
371
- update(data: Partial<Omit<T, 'id'>>): Promise<{
480
+ update(data: Partial<WithFieldSentinels<Omit<T, 'id'>>>): Promise<{
372
481
  ok: true;
373
482
  }>;
374
483
  remove(): Promise<{
@@ -387,16 +496,16 @@ interface DbCollectionRef<T = Record<string, unknown>> {
387
496
  /** Lista documentos com query opcional. */
388
497
  list(q?: DbQuery): Promise<DbListResult<T>>;
389
498
  /** Adiciona doc com id gerado pelo cliente (UUID v4). Durável localmente. */
390
- add(data: Omit<T, 'id'>): Promise<{
499
+ add(data: WithFieldSentinels<Omit<T, 'id'>>): Promise<{
391
500
  ok: true;
392
501
  id: string;
393
502
  }>;
394
503
  /** Cria ou sobrescreve um doc pelo id. Durável localmente. */
395
- set(id: string, data: Omit<T, 'id'>): Promise<{
504
+ set(id: string, data: WithFieldSentinels<Omit<T, 'id'>>): Promise<{
396
505
  ok: true;
397
506
  }>;
398
507
  /** Atualiza campos de um doc (merge). Durável localmente. */
399
- update(id: string, data: Partial<Omit<T, 'id'>>): Promise<{
508
+ update(id: string, data: Partial<WithFieldSentinels<Omit<T, 'id'>>>): Promise<{
400
509
  ok: true;
401
510
  }>;
402
511
  /** Remove um doc (tombstone local). Durável localmente. */
@@ -469,4 +578,4 @@ interface CreateOfflineDocumentsOptions {
469
578
  periodicSyncIntervalMs?: number;
470
579
  }
471
580
 
472
- export type { ConflictRecord as C, DbBatchOp as D, NeetruDbDocuments as N, RealtimeTransport as R, SyncState as S, Unsubscribe as U, CreateOfflineDocumentsOptions as a, DbChangeType as b, DbCollectionRef as c, DbDoc as d, DbDocRef as e, DbGetResult as f, DbListResult as g, DbQuery as h, DbWhereFilter as i };
581
+ export { type ConflictRecord as C, type DbBatchOp as D, type FieldSentinel as F, type IncrementSentinel as I, NEETRU_SENTINEL_KEY as N, type RealtimeTransport as R, type ServerTimestampSentinel as S, type Unsubscribe as U, type WithFieldSentinels as W, type CreateOfflineDocumentsOptions as a, type DbChangeType as b, type DbCollectionRef as c, type DbDoc as d, type DbDocRef as e, type DbGetResult as f, type DbListResult as g, type DbQuery as h, type DbWhereFilter as i, type NeetruDbDocuments as j, type SyncState as k, increment as l, isFieldSentinel as m, isIncrementSentinel as n, isServerTimestampSentinel as o, serverTimestamp as s };
@@ -228,6 +228,98 @@ interface SyncTransport {
228
228
  }>, needsResync: boolean) => void) => () => void;
229
229
  }
230
230
 
231
+ /**
232
+ * field-value.ts — Sentinels de escrita server-side para o Mundo Documentos.
233
+ *
234
+ * Resolve duas lacunas confirmadas (bug_05aedb8b) da superfície `client.db`:
235
+ *
236
+ * 1. **Timestamp resolvido no servidor** — antes deste módulo, o produto que
237
+ * quisesse `createdAt`/`updatedAt` confiáveis tinha que usar `Date.now()`
238
+ * no CLIENTE, que é spoofável e sofre clock-skew. Agora:
239
+ *
240
+ * await orders.add({ total: 99, createdAt: serverTimestamp() });
241
+ *
242
+ * O marcador atravessa o transporte REST como um objeto JSON namespaceado
243
+ * e o Core o substitui por `FieldValue.serverTimestamp()` no momento da
244
+ * escrita — relógio do SERVIDOR, não do cliente.
245
+ *
246
+ * 2. **Increment atômico** — antes, `update(id, { n: x+1 })` era read-modify-write
247
+ * no cliente, sujeito a lost-update sob concorrência. Agora:
248
+ *
249
+ * await counters.update('global', { hits: increment(1) });
250
+ *
251
+ * O Core aplica `FieldValue.increment(1)` atomicamente no documento.
252
+ *
253
+ * ## Contrato de fio (wire format)
254
+ *
255
+ * Os sentinels são objetos JSON simples — sobrevivem `JSON.stringify` no
256
+ * transporte REST e ao `IndexedDB structured clone` da camada offline:
257
+ *
258
+ * serverTimestamp() → `{ "__neetru__": "serverTimestamp" }`
259
+ * increment(n) → `{ "__neetru__": "increment", "operand": n }`
260
+ *
261
+ * A chave-âncora `__neetru__` é deliberadamente improvável de colidir com
262
+ * dados de produto. O Core valida e resolve esses marcadores; qualquer objeto
263
+ * com `__neetru__` desconhecido é rejeitado server-side (fail-closed).
264
+ *
265
+ * ## Aplicação otimista (offline-first)
266
+ *
267
+ * A camada offline aplica uma APROXIMAÇÃO local imediata (para a UI não piscar):
268
+ * - `serverTimestamp()` → `Date.now()` local (substituído pelo valor real no sync).
269
+ * - `increment(n)` → soma `n` ao valor numérico atual do campo (0 se ausente).
270
+ * O valor canônico vem sempre do servidor no pull/confirm subsequente.
271
+ *
272
+ * @module
273
+ */
274
+ /** Chave-âncora dos marcadores de sentinel no wire format. */
275
+ declare const NEETRU_SENTINEL_KEY: "__neetru__";
276
+ /** Marcador de timestamp resolvido pelo servidor. */
277
+ interface ServerTimestampSentinel {
278
+ readonly __neetru__: 'serverTimestamp';
279
+ }
280
+ /** Marcador de incremento atômico aplicado pelo servidor. */
281
+ interface IncrementSentinel {
282
+ readonly __neetru__: 'increment';
283
+ /** Delta a somar (pode ser negativo para decremento). */
284
+ readonly operand: number;
285
+ }
286
+ /**
287
+ * União de todos os sentinels de campo. Use `serverTimestamp()` / `increment()`
288
+ * para construir — nunca monte o objeto à mão.
289
+ */
290
+ type FieldSentinel = ServerTimestampSentinel | IncrementSentinel;
291
+ /**
292
+ * Sentinel que instrui o servidor a gravar o timestamp do MOMENTO da escrita
293
+ * usando o relógio do servidor (não do cliente).
294
+ *
295
+ * @example
296
+ * ```ts
297
+ * import { serverTimestamp } from '@neetru/sdk/db';
298
+ * await orders.add({ total: 99, createdAt: serverTimestamp() });
299
+ * ```
300
+ */
301
+ declare function serverTimestamp(): ServerTimestampSentinel;
302
+ /**
303
+ * Sentinel que instrui o servidor a incrementar atomicamente o campo numérico
304
+ * por `n` (negativo decrementa). Se o campo não existir/não for numérico, o
305
+ * servidor o trata como `0` antes de somar (semântica `FieldValue.increment`).
306
+ *
307
+ * @param n — delta inteiro ou fracionário a somar.
308
+ * @example
309
+ * ```ts
310
+ * import { increment } from '@neetru/sdk/db';
311
+ * await counters.update('global', { hits: increment(1) });
312
+ * await wallet.update(uid, { balance: increment(-50) });
313
+ * ```
314
+ */
315
+ declare function increment(n: number): IncrementSentinel;
316
+ /** `true` se `v` é qualquer sentinel de campo Neetru. */
317
+ declare function isFieldSentinel(v: unknown): v is FieldSentinel;
318
+ /** `true` se `v` é o sentinel `serverTimestamp()`. */
319
+ declare function isServerTimestampSentinel(v: unknown): v is ServerTimestampSentinel;
320
+ /** `true` se `v` é o sentinel `increment(n)` com `operand` numérico válido. */
321
+ declare function isIncrementSentinel(v: unknown): v is IncrementSentinel;
322
+
231
323
  /**
232
324
  * DbCollectionRef — superfície de Documentos offline-first do @neetru/sdk (M2).
233
325
  *
@@ -251,6 +343,23 @@ interface SyncTransport {
251
343
  * sem alterar a superfície pública.
252
344
  */
253
345
 
346
+ /**
347
+ * Permite que cada campo de `T` aceite, além do seu tipo declarado, um sentinel
348
+ * de escrita server-side (`serverTimestamp()` / `increment()`).
349
+ *
350
+ * Sem isso, um produto que tipa `createdAt: number` veria erro de tipo ao passar
351
+ * `serverTimestamp()`. Esta projeção mantém o autocomplete dos campos do produto
352
+ * e ainda autoriza os sentinels onde fizer sentido.
353
+ *
354
+ * @example
355
+ * ```ts
356
+ * interface Order { total: number; createdAt: number; hits: number }
357
+ * orders.add({ total: 10, createdAt: serverTimestamp(), hits: increment(1) });
358
+ * ```
359
+ */
360
+ type WithFieldSentinels<T> = {
361
+ [K in keyof T]: T[K] | FieldSentinel;
362
+ };
254
363
  interface DbWhereFilter {
255
364
  field: string;
256
365
  op: '==' | '!=' | '<' | '<=' | '>' | '>=' | 'array-contains' | 'in' | 'not-in';
@@ -317,15 +426,15 @@ type DbChangeType = 'added' | 'modified' | 'removed';
317
426
  */
318
427
  type DbBatchOp<T = Record<string, unknown>> = {
319
428
  kind: 'add';
320
- data: Omit<T, 'id'>;
429
+ data: WithFieldSentinels<Omit<T, 'id'>>;
321
430
  } | {
322
431
  kind: 'set';
323
432
  id: string;
324
- data: Omit<T, 'id'>;
433
+ data: WithFieldSentinels<Omit<T, 'id'>>;
325
434
  } | {
326
435
  kind: 'update';
327
436
  id: string;
328
- data: Partial<Omit<T, 'id'>>;
437
+ data: Partial<WithFieldSentinels<Omit<T, 'id'>>>;
329
438
  } | {
330
439
  kind: 'remove';
331
440
  id: string;
@@ -365,10 +474,10 @@ interface DbDocRef<T = Record<string, unknown>> {
365
474
  /** Nome da coleção parent. */
366
475
  readonly collection: string;
367
476
  get(): Promise<DbGetResult<T> | null>;
368
- set(data: Omit<T, 'id'>): Promise<{
477
+ set(data: WithFieldSentinels<Omit<T, 'id'>>): Promise<{
369
478
  ok: true;
370
479
  }>;
371
- update(data: Partial<Omit<T, 'id'>>): Promise<{
480
+ update(data: Partial<WithFieldSentinels<Omit<T, 'id'>>>): Promise<{
372
481
  ok: true;
373
482
  }>;
374
483
  remove(): Promise<{
@@ -387,16 +496,16 @@ interface DbCollectionRef<T = Record<string, unknown>> {
387
496
  /** Lista documentos com query opcional. */
388
497
  list(q?: DbQuery): Promise<DbListResult<T>>;
389
498
  /** Adiciona doc com id gerado pelo cliente (UUID v4). Durável localmente. */
390
- add(data: Omit<T, 'id'>): Promise<{
499
+ add(data: WithFieldSentinels<Omit<T, 'id'>>): Promise<{
391
500
  ok: true;
392
501
  id: string;
393
502
  }>;
394
503
  /** Cria ou sobrescreve um doc pelo id. Durável localmente. */
395
- set(id: string, data: Omit<T, 'id'>): Promise<{
504
+ set(id: string, data: WithFieldSentinels<Omit<T, 'id'>>): Promise<{
396
505
  ok: true;
397
506
  }>;
398
507
  /** Atualiza campos de um doc (merge). Durável localmente. */
399
- update(id: string, data: Partial<Omit<T, 'id'>>): Promise<{
508
+ update(id: string, data: Partial<WithFieldSentinels<Omit<T, 'id'>>>): Promise<{
400
509
  ok: true;
401
510
  }>;
402
511
  /** Remove um doc (tombstone local). Durável localmente. */
@@ -469,4 +578,4 @@ interface CreateOfflineDocumentsOptions {
469
578
  periodicSyncIntervalMs?: number;
470
579
  }
471
580
 
472
- export type { ConflictRecord as C, DbBatchOp as D, NeetruDbDocuments as N, RealtimeTransport as R, SyncState as S, Unsubscribe as U, CreateOfflineDocumentsOptions as a, DbChangeType as b, DbCollectionRef as c, DbDoc as d, DbDocRef as e, DbGetResult as f, DbListResult as g, DbQuery as h, DbWhereFilter as i };
581
+ export { type ConflictRecord as C, type DbBatchOp as D, type FieldSentinel as F, type IncrementSentinel as I, NEETRU_SENTINEL_KEY as N, type RealtimeTransport as R, type ServerTimestampSentinel as S, type Unsubscribe as U, type WithFieldSentinels as W, type CreateOfflineDocumentsOptions as a, type DbChangeType as b, type DbCollectionRef as c, type DbDoc as d, type DbDocRef as e, type DbGetResult as f, type DbListResult as g, type DbQuery as h, type DbWhereFilter as i, type NeetruDbDocuments as j, type SyncState as k, increment as l, isFieldSentinel as m, isIncrementSentinel as n, isServerTimestampSentinel as o, serverTimestamp as s };
@@ -1,4 +1,4 @@
1
- import { S as SyncState, U as Unsubscribe, d as DbDoc, c as DbCollectionRef, h as DbQuery } from './collection-ref-DqAAhuhX.cjs';
1
+ import { k as SyncState, U as Unsubscribe, d as DbDoc, c as DbCollectionRef, h as DbQuery } from './collection-ref-BDdfD87k.cjs';
2
2
 
3
3
  /**
4
4
  * Resultado de `useDocument`.
@@ -1,4 +1,4 @@
1
- import { S as SyncState, U as Unsubscribe, d as DbDoc, c as DbCollectionRef, h as DbQuery } from './collection-ref-DqAAhuhX.js';
1
+ import { k as SyncState, U as Unsubscribe, d as DbDoc, c as DbCollectionRef, h as DbQuery } from './collection-ref-BDdfD87k.js';
2
2
 
3
3
  /**
4
4
  * Resultado de `useDocument`.
package/dist/db.cjs CHANGED
@@ -1540,7 +1540,7 @@ var SyncEngine = class {
1540
1540
  return true;
1541
1541
  }
1542
1542
  /** Atualiza o cache após confirmação de escrita (outcome=confirmed). */
1543
- async _applySyncedDoc(collection, docId, serverVersion, serverTimestamp, mut, busChanges) {
1543
+ async _applySyncedDoc(collection, docId, serverVersion, serverTimestamp2, mut, busChanges) {
1544
1544
  const existing = await this._store.getDoc(collection, docId);
1545
1545
  if (mut.op === "remove") {
1546
1546
  if (existing) {
@@ -1549,7 +1549,7 @@ var SyncEngine = class {
1549
1549
  meta: {
1550
1550
  ...existing.meta,
1551
1551
  serverVersion,
1552
- updatedAtServer: serverTimestamp,
1552
+ updatedAtServer: serverTimestamp2,
1553
1553
  updatedAtLocal: Date.now(),
1554
1554
  state: "synced",
1555
1555
  deleted: true,
@@ -1567,7 +1567,7 @@ var SyncEngine = class {
1567
1567
  data: mut.op === "update" ? { ...existing?.data ?? {}, ...newData } : newData,
1568
1568
  meta: {
1569
1569
  serverVersion,
1570
- updatedAtServer: serverTimestamp,
1570
+ updatedAtServer: serverTimestamp2,
1571
1571
  updatedAtLocal: Date.now(),
1572
1572
  state: "synced",
1573
1573
  pendingMutationIds: [],
@@ -2646,6 +2646,54 @@ var TabCoordinator = class {
2646
2646
 
2647
2647
  // src/db/collection-ref.ts
2648
2648
  init_db_errors();
2649
+
2650
+ // src/db/field-value.ts
2651
+ var NEETRU_SENTINEL_KEY = "__neetru__";
2652
+ function serverTimestamp() {
2653
+ return { __neetru__: "serverTimestamp" };
2654
+ }
2655
+ function increment(n) {
2656
+ if (typeof n !== "number" || !Number.isFinite(n)) {
2657
+ throw new TypeError(
2658
+ `increment(n): n deve ser um n\xFAmero finito (recebido: ${String(n)}).`
2659
+ );
2660
+ }
2661
+ return { __neetru__: "increment", operand: n };
2662
+ }
2663
+ function isFieldSentinel(v) {
2664
+ return typeof v === "object" && v !== null && NEETRU_SENTINEL_KEY in v && typeof v[NEETRU_SENTINEL_KEY] === "string";
2665
+ }
2666
+ function isServerTimestampSentinel(v) {
2667
+ return isFieldSentinel(v) && v.__neetru__ === "serverTimestamp";
2668
+ }
2669
+ function isIncrementSentinel(v) {
2670
+ return isFieldSentinel(v) && v.__neetru__ === "increment" && typeof v.operand === "number" && Number.isFinite(v.operand);
2671
+ }
2672
+ function resolveSentinelLocally(value, baseValue) {
2673
+ if (isServerTimestampSentinel(value)) {
2674
+ return Date.now();
2675
+ }
2676
+ if (isIncrementSentinel(value)) {
2677
+ const base = typeof baseValue === "number" && Number.isFinite(baseValue) ? baseValue : 0;
2678
+ return base + value.operand;
2679
+ }
2680
+ return value;
2681
+ }
2682
+ function resolveSentinelsLocally(data, existing) {
2683
+ let touched = false;
2684
+ const out = {};
2685
+ for (const [k, v] of Object.entries(data)) {
2686
+ if (isFieldSentinel(v)) {
2687
+ touched = true;
2688
+ out[k] = resolveSentinelLocally(v, existing?.[k]);
2689
+ } else {
2690
+ out[k] = v;
2691
+ }
2692
+ }
2693
+ return touched ? out : data;
2694
+ }
2695
+
2696
+ // src/db/collection-ref.ts
2649
2697
  function isIndexedDBUnavailable() {
2650
2698
  return typeof indexedDB === "undefined" || typeof window === "undefined";
2651
2699
  }
@@ -2774,10 +2822,12 @@ var DbCollectionRefImpl = class {
2774
2822
  return this._buildListResult(q);
2775
2823
  }
2776
2824
  async add(data) {
2825
+ const rawData = data;
2826
+ const localData = resolveSentinelsLocally(rawData, void 0);
2777
2827
  const mutation = await this._queue.enqueue({
2778
2828
  collection: this._collection,
2779
2829
  op: "add",
2780
- payload: data,
2830
+ payload: rawData,
2781
2831
  baseVersion: null,
2782
2832
  batchId: null
2783
2833
  });
@@ -2785,7 +2835,7 @@ var DbCollectionRefImpl = class {
2785
2835
  await this._store.putDoc({
2786
2836
  collection: this._collection,
2787
2837
  id: docId,
2788
- data,
2838
+ data: localData,
2789
2839
  meta: {
2790
2840
  serverVersion: null,
2791
2841
  updatedAtServer: null,
@@ -2799,25 +2849,27 @@ var DbCollectionRefImpl = class {
2799
2849
  this._bus.emit([{
2800
2850
  type: "added",
2801
2851
  collection: this._collection,
2802
- doc: { id: docId, data }
2852
+ doc: { id: docId, data: localData }
2803
2853
  }]);
2804
2854
  return { ok: true, id: docId };
2805
2855
  }
2806
2856
  async set(id, data) {
2807
2857
  assertValidId(id);
2808
2858
  const existing = await this._store.getDoc(this._collection, id);
2859
+ const rawData = data;
2860
+ const localData = resolveSentinelsLocally(rawData, void 0);
2809
2861
  const mutation = await this._queue.enqueue({
2810
2862
  collection: this._collection,
2811
2863
  docId: id,
2812
2864
  op: "set",
2813
- payload: data,
2865
+ payload: rawData,
2814
2866
  baseVersion: existing?.meta.serverVersion ?? null,
2815
2867
  batchId: null
2816
2868
  });
2817
2869
  await this._store.putDoc({
2818
2870
  collection: this._collection,
2819
2871
  id,
2820
- data,
2872
+ data: localData,
2821
2873
  meta: {
2822
2874
  serverVersion: existing?.meta.serverVersion ?? null,
2823
2875
  updatedAtServer: existing?.meta.updatedAtServer ?? null,
@@ -2831,19 +2883,22 @@ var DbCollectionRefImpl = class {
2831
2883
  this._bus.emit([{
2832
2884
  type: existing && !existing.meta.deleted ? "modified" : "added",
2833
2885
  collection: this._collection,
2834
- doc: { id, data }
2886
+ doc: { id, data: localData }
2835
2887
  }]);
2836
2888
  return { ok: true };
2837
2889
  }
2838
2890
  async update(id, data) {
2839
2891
  assertValidId(id);
2840
2892
  const existing = await this._store.getDoc(this._collection, id);
2841
- const mergedData = existing?.meta.deleted ? { ...data } : { ...existing?.data ?? {}, ...data };
2893
+ const rawData = data;
2894
+ const existingBase = existing?.meta.deleted ? void 0 : existing?.data;
2895
+ const localData = resolveSentinelsLocally(rawData, existingBase);
2896
+ const mergedData = existing?.meta.deleted ? { ...localData } : { ...existing?.data ?? {}, ...localData };
2842
2897
  const mutation = await this._queue.enqueue({
2843
2898
  collection: this._collection,
2844
2899
  docId: id,
2845
2900
  op: "update",
2846
- payload: data,
2901
+ payload: rawData,
2847
2902
  baseVersion: existing?.meta.serverVersion ?? null,
2848
2903
  batchId: null
2849
2904
  });
@@ -2900,6 +2955,7 @@ var DbCollectionRefImpl = class {
2900
2955
  const collection = this._collection;
2901
2956
  if (op.kind === "add") {
2902
2957
  const data = op.data;
2958
+ const localData = resolveSentinelsLocally(data, void 0);
2903
2959
  const mutation = await this._queue.enqueue({
2904
2960
  collection,
2905
2961
  op: "add",
@@ -2911,7 +2967,7 @@ var DbCollectionRefImpl = class {
2911
2967
  await this._store.putDoc({
2912
2968
  collection,
2913
2969
  id: docId,
2914
- data,
2970
+ data: localData,
2915
2971
  meta: {
2916
2972
  serverVersion: null,
2917
2973
  updatedAtServer: null,
@@ -2922,11 +2978,12 @@ var DbCollectionRefImpl = class {
2922
2978
  ownerId: null
2923
2979
  }
2924
2980
  });
2925
- busChanges.push({ type: "added", collection, doc: { id: docId, data } });
2981
+ busChanges.push({ type: "added", collection, doc: { id: docId, data: localData } });
2926
2982
  } else if (op.kind === "set") {
2927
2983
  const id = op.id;
2928
2984
  const data = op.data;
2929
2985
  const existing = await this._store.getDoc(collection, id);
2986
+ const localData = resolveSentinelsLocally(data, void 0);
2930
2987
  const mutation = await this._queue.enqueue({
2931
2988
  collection,
2932
2989
  docId: id,
@@ -2938,7 +2995,7 @@ var DbCollectionRefImpl = class {
2938
2995
  await this._store.putDoc({
2939
2996
  collection,
2940
2997
  id,
2941
- data,
2998
+ data: localData,
2942
2999
  meta: {
2943
3000
  serverVersion: existing?.meta.serverVersion ?? null,
2944
3001
  updatedAtServer: existing?.meta.updatedAtServer ?? null,
@@ -2952,13 +3009,15 @@ var DbCollectionRefImpl = class {
2952
3009
  busChanges.push({
2953
3010
  type: existing && !existing.meta.deleted ? "modified" : "added",
2954
3011
  collection,
2955
- doc: { id, data }
3012
+ doc: { id, data: localData }
2956
3013
  });
2957
3014
  } else if (op.kind === "update") {
2958
3015
  const id = op.id;
2959
3016
  const data = op.data;
2960
3017
  const existing = await this._store.getDoc(collection, id);
2961
- const merged = { ...existing?.data ?? {}, ...data };
3018
+ const existingBase = existing?.meta.deleted ? void 0 : existing?.data;
3019
+ const localData = resolveSentinelsLocally(data, existingBase);
3020
+ const merged = { ...existing?.data ?? {}, ...localData };
2962
3021
  const mutation = await this._queue.enqueue({
2963
3022
  collection,
2964
3023
  docId: id,
@@ -3990,8 +4049,14 @@ function createLazyCollectionRef(name, getDocsPromise) {
3990
4049
  };
3991
4050
  }
3992
4051
 
4052
+ exports.NEETRU_SENTINEL_KEY = NEETRU_SENTINEL_KEY;
3993
4053
  exports.createNeetruDb = createNeetruDb;
3994
4054
  exports.createRestSyncTransport = createRestSyncTransport;
3995
4055
  exports.getWebSocketRealtimeClient = getWebSocketRealtimeClient;
4056
+ exports.increment = increment;
4057
+ exports.isFieldSentinel = isFieldSentinel;
4058
+ exports.isIncrementSentinel = isIncrementSentinel;
4059
+ exports.isServerTimestampSentinel = isServerTimestampSentinel;
4060
+ exports.serverTimestamp = serverTimestamp;
3996
4061
  //# sourceMappingURL=db.cjs.map
3997
4062
  //# sourceMappingURL=db.cjs.map