@neetru/sdk 2.3.6 → 3.0.1

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 (83) hide show
  1. package/CHANGELOG.md +185 -0
  2. package/README.md +84 -22
  3. package/dist/auth.cjs +360 -140
  4. package/dist/auth.cjs.map +1 -1
  5. package/dist/auth.d.cts +1 -2
  6. package/dist/auth.d.ts +1 -2
  7. package/dist/auth.mjs +360 -140
  8. package/dist/auth.mjs.map +1 -1
  9. package/dist/catalog.cjs +21 -2
  10. package/dist/catalog.cjs.map +1 -1
  11. package/dist/catalog.d.cts +1 -2
  12. package/dist/catalog.d.ts +1 -2
  13. package/dist/catalog.mjs +21 -2
  14. package/dist/catalog.mjs.map +1 -1
  15. package/dist/checkout.cjs +21 -2
  16. package/dist/checkout.cjs.map +1 -1
  17. package/dist/checkout.d.cts +1 -2
  18. package/dist/checkout.d.ts +1 -2
  19. package/dist/checkout.mjs +21 -2
  20. package/dist/checkout.mjs.map +1 -1
  21. package/dist/db.cjs +28 -3
  22. package/dist/db.cjs.map +1 -1
  23. package/dist/db.d.cts +1 -2
  24. package/dist/db.d.ts +1 -2
  25. package/dist/db.mjs +28 -3
  26. package/dist/db.mjs.map +1 -1
  27. package/dist/entitlements.cjs +21 -2
  28. package/dist/entitlements.cjs.map +1 -1
  29. package/dist/entitlements.d.cts +1 -2
  30. package/dist/entitlements.d.ts +1 -2
  31. package/dist/entitlements.mjs +21 -2
  32. package/dist/entitlements.mjs.map +1 -1
  33. package/dist/errors.cjs.map +1 -1
  34. package/dist/errors.d.cts +3 -1
  35. package/dist/errors.d.ts +3 -1
  36. package/dist/errors.mjs.map +1 -1
  37. package/dist/index.cjs +408 -146
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.d.cts +31 -20
  40. package/dist/index.d.ts +31 -20
  41. package/dist/index.mjs +408 -146
  42. package/dist/index.mjs.map +1 -1
  43. package/dist/mocks.cjs +33 -33
  44. package/dist/mocks.cjs.map +1 -1
  45. package/dist/mocks.d.cts +23 -20
  46. package/dist/mocks.d.ts +23 -20
  47. package/dist/mocks.mjs +33 -33
  48. package/dist/mocks.mjs.map +1 -1
  49. package/dist/notifications.cjs +80 -14
  50. package/dist/notifications.cjs.map +1 -1
  51. package/dist/notifications.d.cts +1 -2
  52. package/dist/notifications.d.ts +1 -2
  53. package/dist/notifications.mjs +80 -14
  54. package/dist/notifications.mjs.map +1 -1
  55. package/dist/react.d.cts +1 -2
  56. package/dist/react.d.ts +1 -2
  57. package/dist/support.cjs +72 -12
  58. package/dist/support.cjs.map +1 -1
  59. package/dist/support.d.cts +1 -2
  60. package/dist/support.d.ts +1 -2
  61. package/dist/support.mjs +72 -12
  62. package/dist/support.mjs.map +1 -1
  63. package/dist/telemetry.cjs +23 -4
  64. package/dist/telemetry.cjs.map +1 -1
  65. package/dist/telemetry.d.cts +1 -2
  66. package/dist/telemetry.d.ts +1 -2
  67. package/dist/telemetry.mjs +23 -4
  68. package/dist/telemetry.mjs.map +1 -1
  69. package/dist/{types-CSC-RIdS.d.ts → types-DALIhcbq.d.ts} +130 -74
  70. package/dist/{types-sGGN1vkg.d.cts → types-Lfd3LiAF.d.cts} +130 -74
  71. package/dist/usage.cjs +57 -63
  72. package/dist/usage.cjs.map +1 -1
  73. package/dist/usage.d.cts +1 -2
  74. package/dist/usage.d.ts +1 -2
  75. package/dist/usage.mjs +57 -63
  76. package/dist/usage.mjs.map +1 -1
  77. package/dist/webhooks.cjs +110 -33
  78. package/dist/webhooks.cjs.map +1 -1
  79. package/dist/webhooks.d.cts +1 -2
  80. package/dist/webhooks.d.ts +1 -2
  81. package/dist/webhooks.mjs +110 -33
  82. package/dist/webhooks.mjs.map +1 -1
  83. package/package.json +2 -3
@@ -1,5 +1,4 @@
1
1
  import { c as DbCollectionRef$1, S as SyncState, U as Unsubscribe, C as ConflictRecord, R as RealtimeTransport } from './collection-ref-DqAAhuhX.cjs';
2
- import { DbQuery, DeltaFrame } from '@neetru/realtime-protocol';
3
2
  import { NodePgDatabase } from 'drizzle-orm/node-postgres';
4
3
  import { NeetruError } from './errors.cjs';
5
4
 
@@ -104,6 +103,11 @@ interface ProductNotification {
104
103
  dismissedAt?: string;
105
104
  }
106
105
  interface SendNotificationInput {
106
+ /**
107
+ * **3.0 (breaking):** productId é obrigatório (Core valida via Zod). Default
108
+ * resolve do `config.productId`; lança `validation_failed` se ausente.
109
+ */
110
+ productId?: string;
107
111
  userId: string;
108
112
  kind: string;
109
113
  title: string;
@@ -118,6 +122,11 @@ interface SendNotificationInput {
118
122
  fingerprint?: string;
119
123
  }
120
124
  interface ListNotificationsOptions {
125
+ /**
126
+ * **3.0 (breaking):** productId é obrigatório no Core. Default resolve do
127
+ * `config.productId`.
128
+ */
129
+ productId?: string;
121
130
  /** Inclui notificações dismissed (default false). */
122
131
  includeDismissed?: boolean;
123
132
  /** Filtro por unread. */
@@ -125,17 +134,21 @@ interface ListNotificationsOptions {
125
134
  /** Máx itens (default 50, máx 200). */
126
135
  limit?: number;
127
136
  }
137
+ /** 3.0: scope option pra markRead/dismiss (productId obrigatório no Core). */
138
+ interface NotificationScopeOptions {
139
+ productId?: string;
140
+ }
128
141
  interface NotificationsNamespace {
129
142
  /** Produto envia uma notificação pra um usuário do produto. */
130
143
  send(input: SendNotificationInput): Promise<ProductNotification>;
131
144
  /** Lista notificações de um usuário. */
132
145
  list(userId: string, options?: ListNotificationsOptions): Promise<ProductNotification[]>;
133
146
  /** Marca como lida. */
134
- markRead(id: string): Promise<{
147
+ markRead(id: string, options?: NotificationScopeOptions): Promise<{
135
148
  ok: true;
136
149
  }>;
137
150
  /** Descarta. */
138
- dismiss(id: string): Promise<{
151
+ dismiss(id: string, options?: NotificationScopeOptions): Promise<{
139
152
  ok: true;
140
153
  }>;
141
154
  }
@@ -145,10 +158,10 @@ declare class MockNotifications implements NotificationsNamespace {
145
158
  private nextId;
146
159
  send(input: SendNotificationInput): Promise<ProductNotification>;
147
160
  list(userId: string, options?: ListNotificationsOptions): Promise<ProductNotification[]>;
148
- markRead(id: string): Promise<{
161
+ markRead(id: string, _options?: NotificationScopeOptions): Promise<{
149
162
  ok: true;
150
163
  }>;
151
- dismiss(id: string): Promise<{
164
+ dismiss(id: string, _options?: NotificationScopeOptions): Promise<{
152
165
  ok: true;
153
166
  }>;
154
167
  }
@@ -169,6 +182,12 @@ interface WebhookEndpoint {
169
182
  createdAt: string;
170
183
  }
171
184
  interface RegisterWebhookInput {
185
+ /**
186
+ * **3.0 (breaking):** `productId` é obrigatório no body. Se ausente,
187
+ * SDK tenta resolver do `config.productId` (default no `createNeetruClient`);
188
+ * lança `validation_failed` se ambos faltarem.
189
+ */
190
+ productId?: string;
172
191
  url: string;
173
192
  events: WebhookEvent[];
174
193
  /**
@@ -184,53 +203,67 @@ interface WebhookTestResult {
184
203
  durationMs?: number;
185
204
  error?: string;
186
205
  }
206
+ /**
207
+ * 3.0: opções de scoping — todos os verbos exigem `productId` (default do
208
+ * `config.productId` se ausente).
209
+ */
210
+ interface WebhookScopeOptions {
211
+ productId?: string;
212
+ }
187
213
  interface WebhooksNamespace {
188
214
  /** Registra um novo endpoint. */
189
215
  register(input: RegisterWebhookInput): Promise<WebhookEndpoint>;
190
216
  /** Lista endpoints registrados pelo produto. */
191
- list(): Promise<WebhookEndpoint[]>;
217
+ list(options?: WebhookScopeOptions): Promise<WebhookEndpoint[]>;
192
218
  /** Remove um endpoint. */
193
- unregister(id: string): Promise<{
219
+ unregister(id: string, options?: WebhookScopeOptions): Promise<{
194
220
  ok: true;
195
221
  }>;
196
222
  /** Dispara evento de teste pra validar conectividade. */
197
- test(id: string): Promise<WebhookTestResult>;
223
+ test(id: string, options?: WebhookScopeOptions): Promise<WebhookTestResult>;
198
224
  }
199
225
  /**
200
226
  * Verifica assinatura HMAC SHA-256 de payload de webhook recebido pelo
201
- * produto consumer. Constant-time compare pra resistir a timing attack.
227
+ * produto consumer. **3.0**: Universal (browser/Node/Edge) via WebCrypto.subtle.
202
228
  *
203
- * @param payload Corpo cru da request (string ou Buffer). NÃO use o objeto
229
+ * Constant-time compare via byte-level XOR accumulator (não usa
230
+ * `timingSafeEqual` do Node porque WebCrypto não tem equivalente direto).
231
+ *
232
+ * @param payload Corpo cru da request (string ou Uint8Array). NÃO use o objeto
204
233
  * parseado — JSON.stringify pode diferir do que foi assinado.
205
234
  * @param signature Header `X-Neetru-Signature` recebido (formato `sha256=<hex>`).
206
235
  * @param secret Secret registrado em `webhooks.register({ secret })`.
207
- * @returns true se assinatura confere, false caso contrário.
236
+ * @returns Promise<boolean> — true se assinatura confere, false caso contrário.
208
237
  *
209
238
  * @example
210
239
  * ```ts
211
- * // Express handler
212
- * app.post('/webhooks/neetru', express.raw({ type: 'application/json' }), (req, res) => {
213
- * const sig = req.header('X-Neetru-Signature');
214
- * if (!verifyWebhookSignature(req.body, sig, process.env.WEBHOOK_SECRET!)) {
215
- * return res.status(401).end();
216
- * }
217
- * const event = JSON.parse(req.body.toString('utf8'));
240
+ * // Next.js Route Handler (Edge ou Node runtime — funciona em ambos)
241
+ * export async function POST(req: Request) {
242
+ * const raw = await req.text();
243
+ * const sig = req.headers.get('X-Neetru-Signature');
244
+ * const ok = await verifyWebhookSignature(raw, sig, process.env.NEETRU_WEBHOOK_SECRET!);
245
+ * if (!ok) return new Response('unauthorized', { status: 401 });
246
+ * const event = JSON.parse(raw);
218
247
  * // ... handle event
219
- * res.json({ ok: true });
220
- * });
248
+ * return Response.json({ ok: true });
249
+ * }
221
250
  * ```
251
+ *
252
+ * @throws NeetruError('runtime_unsupported') se runtime não expõe WebCrypto.
253
+ * Não retorna `false` silencioso — caller precisa distinguir "assinatura
254
+ * inválida" (return false) de "ambiente não suporta" (throw).
222
255
  */
223
- declare function verifyWebhookSignature(payload: string | Uint8Array, signature: string | null | undefined, secret: string): boolean;
256
+ declare function verifyWebhookSignature(payload: string | Uint8Array, signature: string | null | undefined, secret: string): Promise<boolean>;
224
257
  declare function createWebhooksNamespace(config: ResolvedConfig): WebhooksNamespace;
225
258
  declare class MockWebhooks implements WebhooksNamespace {
226
259
  private endpoints;
227
260
  private nextId;
228
261
  register(input: RegisterWebhookInput): Promise<WebhookEndpoint>;
229
- list(): Promise<WebhookEndpoint[]>;
230
- unregister(id: string): Promise<{
262
+ list(_options?: WebhookScopeOptions): Promise<WebhookEndpoint[]>;
263
+ unregister(id: string, _options?: WebhookScopeOptions): Promise<{
231
264
  ok: true;
232
265
  }>;
233
- test(id: string): Promise<WebhookTestResult>;
266
+ test(id: string, _options?: WebhookScopeOptions): Promise<WebhookTestResult>;
234
267
  }
235
268
 
236
269
  /**
@@ -290,29 +323,25 @@ declare class NeetruDbError extends NeetruError {
290
323
  constructor(code: NeetruDbErrorCode, message: string, dbId?: string);
291
324
  }
292
325
 
293
- /**
294
- * Tipos internos do NeetruRealtimeClient.
295
- *
296
- * Os tipos de wire protocol (frames, ops, query descriptor, change) sao
297
- * importados de `@neetru/realtime-protocol` — fonte unica de verdade
298
- * compartilhada com o gateway (@neetru/realtime-transport e
299
- * @neetru/realtime-changestream). Isso garante que cliente e gateway
300
- * NUNCA derivem silenciosamente.
301
- *
302
- * Este arquivo define apenas os tipos internos ao cliente:
303
- * - ConnectionState / ConnectionStateListener : estado observavel da conexao
304
- * - WebSocketLike / WebSocketFactory : injecao de dependencia pra testes
305
- * - RealtimeTicket / TicketProvider : autenticacao realtime
306
- * - RealtimeClientOptions : configuracao do cliente
307
- * - SubscriptionCallback / SubscriptionEntry : estado interno de subscriptions
308
- *
309
- * Re-exports de conveniencia:
310
- * - RealtimeQuery (alias de DbQuery)
311
- * - OutboundFrame (alias de SubscribeFrame)
312
- * - InboundFrame (alias de DeltaFrame)
313
- * Os aliases preservam a API publica do cliente sem expor os nomes do gateway.
314
- */
315
-
326
+ declare const DELTA_OPS: readonly ["delta", "resync", "stale", "error", "pong", "drain"];
327
+ type DeltaOp = (typeof DELTA_OPS)[number];
328
+ interface DbQuery {
329
+ filter?: Record<string, unknown>;
330
+ limit?: number;
331
+ orderBy?: string;
332
+ orderDir?: 'asc' | 'desc';
333
+ }
334
+ interface DbChange {
335
+ type: string;
336
+ documentId: string;
337
+ data?: Record<string, unknown>;
338
+ }
339
+ interface DeltaFrame {
340
+ op: DeltaOp;
341
+ subscriptionId: string;
342
+ changes?: DbChange[];
343
+ reason?: string;
344
+ }
316
345
  type InboundFrame = DeltaFrame;
317
346
  type RealtimeQuery = DbQuery;
318
347
  /**
@@ -560,7 +589,7 @@ interface SqlOptions {
560
589
  * - Rotear frames inbound para o callback correto por `subscriptionId`.
561
590
  * - Emitir mudanças de `ConnectionState` para observadores externos.
562
591
  *
563
- * Protocolo de frames (contrato compartilhado via `@neetru/realtime-protocol`):
592
+ * Protocolo de frames (contrato inlined em `./types.ts` — MASTER-CRIT-038):
564
593
  * ENVIA → `subscribe` | `unsubscribe` | `ping`
565
594
  * RECEBE ← `delta` | `resync` | `stale` | `error` | `pong` | `drain`
566
595
  *
@@ -1043,6 +1072,14 @@ interface NeetruClientConfig {
1043
1072
  * Valores: `'rest'` | `'firestore'` | `'nosql-vm'`.
1044
1073
  */
1045
1074
  db?: NeetruDbOptions;
1075
+ /**
1076
+ * 3.0 — hosts adicionais permitidos para `baseUrl`. Por default, SDK aceita
1077
+ * `*.neetru.com` HTTPS + localhost. Para apontar pra staging/proxy custom em
1078
+ * `https://meu-proxy.empresa.com`, adicione `['empresa.com']`.
1079
+ *
1080
+ * Bypass total: `NEETRU_ALLOW_INSECURE_BASEURL=1` no env.
1081
+ */
1082
+ allowedHosts?: string[];
1046
1083
  }
1047
1084
  /**
1048
1085
  * Status do produto no catálogo público.
@@ -1299,41 +1336,41 @@ interface VerifyTokenOptions {
1299
1336
  */
1300
1337
  jwksCacheTtlMs?: number;
1301
1338
  }
1339
+ /**
1340
+ * 3.0 BREAKING: `UsageEventInput` mantido só como tipo para tests/legacy
1341
+ * imports — não é mais retorno de método. `usage.track`/`usage.getQuota`
1342
+ * **REMOVIDOS** em 3.0. Use `usage.report(resource, qty, options)`.
1343
+ */
1302
1344
  interface UsageEventInput {
1303
- /** Nome do evento, ex: `report_generated`, `api_call`. */
1304
1345
  event: string;
1305
- /** Atributos do evento. Valores primitivos serializáveis. */
1306
1346
  properties?: Record<string, string | number | boolean | null>;
1307
- /** Quantidade — default 1 (1 evento). Útil pra batch. */
1308
1347
  quantity?: number;
1309
1348
  }
1349
+ /**
1350
+ * 3.0 BREAKING: `UsageQuota` mantido só para compat de imports. Não é mais
1351
+ * retorno de método ativo. `usage.report()` devolve `{value, limit, remaining}`
1352
+ * inline; `usage.check()` devolve detalhes do entitlement.
1353
+ */
1310
1354
  interface UsageQuota {
1311
1355
  metric: string;
1312
- /** Quantidade já consumida no período. */
1313
1356
  used: number;
1314
- /** Limite total. -1 = unlimited. */
1315
1357
  limit: number;
1316
- /** ISO timestamp do reset (próximo período). */
1317
1358
  resetsAt?: string;
1318
- /** Plano que define o limite. */
1319
1359
  plan?: string;
1320
1360
  }
1361
+ /**
1362
+ * 3.0 — superfície limpa: `report` (POST metered) + `check` (GET entitlement).
1363
+ *
1364
+ * Métodos removidos vs 2.x:
1365
+ * - `track(event, props)` — vaporware contra Core. Use `report()`.
1366
+ * - `getQuota(metric)` — endpoint nunca existiu. `report()` devolve quota.
1367
+ */
1321
1368
  interface UsageNamespace {
1322
- /** Persiste um evento de uso. Mock em dev, POST `/sdk/v1/usage/record` em prod. */
1323
- track(event: string, props?: UsageEventInput['properties']): Promise<{
1324
- ok: true;
1325
- }>;
1326
- /** Lê quota atual de uma métrica. Mock em dev, GET `/sdk/v1/usage/quota` em prod. */
1327
- getQuota(metric: string): Promise<UsageQuota>;
1328
1369
  /**
1329
- * v0.3 — Reporta consumo de um resource metered (POST /sdk/v1/usage/record
1330
- * com `{productId, tenantId, resource, qty}`). Em dev acumula no mock.
1370
+ * Reporta consumo metered. Incrementa `usage_counters` no Core atomicamente.
1371
+ * Idempotency-Key gerado por chamada (defesa anti-dup em retry transient).
1331
1372
  *
1332
- * Diferente de `track()`: usa o endpoint canônico Sprint 7 que incrementa
1333
- * `usage_counters/{tenantId}_{productId}_{resource}_{yyyymm}` atomicamente.
1334
- *
1335
- * `productId`/`tenantId` são lidos do contexto resolvido do client se
1336
- * ausentes nas options.
1373
+ * `productId`/`tenantId` lidos do contexto do client se ausentes nas options.
1337
1374
  */
1338
1375
  report(resource: string, qty?: number, options?: {
1339
1376
  productId?: string;
@@ -1347,8 +1384,9 @@ interface UsageNamespace {
1347
1384
  status?: string;
1348
1385
  }>;
1349
1386
  /**
1350
- * v0.3 — Verifica entitlement de um resource/feature. Wrapper em
1351
- * GET /sdk/v1/entitlements. Em dev consulta MockEntitlements + MockUsage.
1387
+ * Verifica entitlement de um resource/feature. GET cacheável (HTTP 60s).
1388
+ * Resposta inclui `allowed`, `reason`, `remaining`, `limit`, `planId`,
1389
+ * `planFeatures` e `behavior` ('readonly' quando limit_exceeded).
1352
1390
  */
1353
1391
  check(resource: string, options?: {
1354
1392
  productId?: string;
@@ -1360,6 +1398,7 @@ interface UsageNamespace {
1360
1398
  limit?: number;
1361
1399
  planId?: string | null;
1362
1400
  planFeatures?: string[];
1401
+ behavior?: 'readonly' | null;
1363
1402
  }>;
1364
1403
  }
1365
1404
  /**
@@ -1427,12 +1466,29 @@ interface CreateTicketInput {
1427
1466
  message: string;
1428
1467
  severity?: SupportSeverity;
1429
1468
  productSlug?: string;
1469
+ /**
1470
+ * **L-05 fix (Opus review 2026-05-27):** metadata opcional (Core já aceita
1471
+ * via `metadata: z.record(...)` no Zod). Útil pra anexar contexto como
1472
+ * `{ orderId, userAgent, route, sessionId }` sem inflar subject/message.
1473
+ *
1474
+ * Chaves limitadas a 64 chars; valores primitive (string/number/boolean/null).
1475
+ */
1476
+ metadata?: Record<string, string | number | boolean | null>;
1430
1477
  }
1431
1478
  interface SupportNamespace {
1432
- /** Cria um novo ticket. Mock em dev, POST `/api/v1/products/{slug}/tickets` em prod. */
1479
+ /**
1480
+ * Cria um novo ticket. **3.0**: POST `/api/sdk/v1/support/tickets`
1481
+ * (canonical, requer `productId` no body — default `config.productId`).
1482
+ * Mock em dev.
1483
+ */
1433
1484
  createTicket(input: CreateTicketInput): Promise<SupportTicket>;
1434
- /** Lista meus tickets abertos. */
1435
- listMyTickets(): Promise<SupportTicket[]>;
1485
+ /**
1486
+ * Lista meus tickets abertos. **3.0**: GET `/api/sdk/v1/support/tickets?productId=...`
1487
+ * (canonical). `options.productSlug` override do `config.productId` default.
1488
+ */
1489
+ listMyTickets(options?: {
1490
+ productSlug?: string;
1491
+ }): Promise<SupportTicket[]>;
1436
1492
  }
1437
1493
  /**
1438
1494
  * Modo do SDK. `dev` ativa mocks automáticos (úteis pra testes e
@@ -1459,4 +1515,4 @@ interface CatalogListOptions {
1459
1515
  includeDrafts?: boolean;
1460
1516
  }
1461
1517
 
1462
- export { type VerifyTokenOptions as $, type AuthNamespace as A, type ProductNotificationSeverity as B, type CatalogListOptions as C, DEFAULT_BASE_URL as D, type EntitlementCheck as E, type ProductPlan as F, type ProductStatus as G, type ResolvedConfig as H, type SignInOptions as I, type SqlOptions as J, type SupportNamespace as K, type ListNotificationsOptions as L, MockCheckout as M, type NeetruClient as N, type SupportSeverity as O, type Product as P, type SupportStatus as Q, type RegisterWebhookInput as R, type SendNotificationInput as S, type SupportTicket as T, type TelemetryEventAck as U, type TelemetryEventInput as V, type TelemetryLogAck as W, type TelemetryLogInput as X, type UsageEventInput as Y, type UsageNamespace as Z, type UsageQuota as _, type AuthStateListener as a, type WebhookEndpoint as a0, type WebhookEvent as a1, type WebhookTestResult as a2, type WebhooksNamespace as a3, createCheckoutNamespace as a4, createNeetruDb as a5, createNotificationsNamespace as a6, createRestSyncTransport as a7, createWebhooksNamespace as a8, getWebSocketRealtimeClient as a9, verifyWebhookSignature as aa, type CatalogListResponse as b, type CheckoutIntentInfo as c, type CheckoutIntentStatus as d, type CheckoutNamespace as e, type CheckoutStartInput as f, type CheckoutStartResult as g, type CheckoutTenantType as h, type CreateTicketInput as i, type DbNamespace as j, type DbSqlLease as k, type DbWhereFilter as l, MockNotifications as m, MockWebhooks as n, type NeetruClientConfig as o, type NeetruDb as p, type NeetruDbEngine as q, NeetruDbError as r, type NeetruDbErrorCode as s, type NeetruDbOptions as t, type NeetruDbTransport as u, type NeetruEnv as v, type NeetruSqlClient as w, type NeetruUser as x, type NotificationsNamespace as y, type ProductNotification as z };
1518
+ export { type UsageQuota as $, type AuthNamespace as A, type ProductNotification as B, type CatalogListOptions as C, DEFAULT_BASE_URL as D, type EntitlementCheck as E, type ProductNotificationSeverity as F, type ProductPlan as G, type ProductStatus as H, type ResolvedConfig as I, type SignInOptions as J, type SqlOptions as K, type ListNotificationsOptions as L, MockCheckout as M, type NeetruClient as N, type SupportNamespace as O, type Product as P, type SupportSeverity as Q, type RegisterWebhookInput as R, type SendNotificationInput as S, type SupportStatus as T, type SupportTicket as U, type TelemetryEventAck as V, type TelemetryEventInput as W, type TelemetryLogAck as X, type TelemetryLogInput as Y, type UsageEventInput as Z, type UsageNamespace as _, type AuthStateListener as a, type VerifyTokenOptions as a0, type WebhookEndpoint as a1, type WebhookEvent as a2, type WebhookScopeOptions as a3, type WebhookTestResult as a4, type WebhooksNamespace as a5, createCheckoutNamespace as a6, createNeetruDb as a7, createNotificationsNamespace as a8, createRestSyncTransport as a9, createWebhooksNamespace as aa, getWebSocketRealtimeClient as ab, verifyWebhookSignature as ac, type CatalogListResponse as b, type CheckoutIntentInfo as c, type CheckoutIntentStatus as d, type CheckoutNamespace as e, type CheckoutStartInput as f, type CheckoutStartResult as g, type CheckoutTenantType as h, type CreateTicketInput as i, type DbNamespace as j, type DbSqlLease as k, type DbWhereFilter as l, MockNotifications as m, MockWebhooks as n, type NeetruClientConfig as o, type NeetruDb as p, type NeetruDbEngine as q, NeetruDbError as r, type NeetruDbErrorCode as s, type NeetruDbOptions as t, type NeetruDbTransport as u, type NeetruEnv as v, type NeetruSqlClient as w, type NeetruUser as x, type NotificationScopeOptions as y, type NotificationsNamespace as z };
package/dist/usage.cjs CHANGED
@@ -15,8 +15,22 @@ var NeetruError = class _NeetruError extends Error {
15
15
  }
16
16
  };
17
17
 
18
+ // src/idempotency.ts
19
+ function generateIdempotencyKey() {
20
+ const cryptoObj = typeof globalThis !== "undefined" && "crypto" in globalThis ? globalThis.crypto : void 0;
21
+ if (cryptoObj && typeof cryptoObj.randomUUID === "function") {
22
+ return cryptoObj.randomUUID();
23
+ }
24
+ throw new NeetruError(
25
+ "runtime_unsupported",
26
+ "crypto.randomUUID not available in this runtime. SDK 3.0 requires Node \u226520 or modern browser/Edge."
27
+ );
28
+ }
29
+
18
30
  // src/http.ts
19
- var DEFAULT_RETRIES = 2;
31
+ var DEFAULT_RETRIES_READ = 2;
32
+ var DEFAULT_RETRIES_WRITE = 0;
33
+ var READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
20
34
  var RETRYABLE_CODES = /* @__PURE__ */ new Set([
21
35
  "rate_limited",
22
36
  "server_error",
@@ -74,7 +88,8 @@ async function safeJson(res) {
74
88
  async function httpRequest(config, opts) {
75
89
  const method = opts.method ?? "GET";
76
90
  const url = buildUrl(config.baseUrl, opts.path, opts.query);
77
- const maxRetries = opts.retries ?? DEFAULT_RETRIES;
91
+ const defaultRetries = READ_METHODS.has(method) ? DEFAULT_RETRIES_READ : DEFAULT_RETRIES_WRITE;
92
+ const maxRetries = opts.retries ?? defaultRetries;
78
93
  const headers = {
79
94
  accept: "application/json",
80
95
  ...opts.headers
@@ -88,6 +103,10 @@ async function httpRequest(config, opts) {
88
103
  }
89
104
  headers.authorization = `Bearer ${config.apiKey}`;
90
105
  }
106
+ if (opts.idempotencyKey) {
107
+ const key = typeof opts.idempotencyKey === "string" ? opts.idempotencyKey : generateIdempotencyKey();
108
+ headers["idempotency-key"] = key;
109
+ }
91
110
  const bodyString = opts.body !== void 0 && method !== "GET" && method !== "DELETE" ? JSON.stringify(opts.body) : void 0;
92
111
  if (bodyString !== void 0) {
93
112
  headers["content-type"] = "application/json";
@@ -140,66 +159,31 @@ async function httpRequest(config, opts) {
140
159
  throw lastError ?? new NeetruError("unknown", "unexpected httpRequest exit");
141
160
  }
142
161
 
143
- // src/usage.ts
144
- function toQuota(metric, raw) {
145
- if (!raw || typeof raw !== "object") {
146
- throw new NeetruError("invalid_response", "Quota response is not an object");
147
- }
148
- const r = raw;
149
- if (typeof r.used !== "number" || typeof r.limit !== "number") {
150
- throw new NeetruError("invalid_response", "Quota response missing used/limit numbers");
162
+ // src/validators.ts
163
+ var SDK_ID_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/;
164
+ function assertValidSdkId(field, value) {
165
+ if (typeof value !== "string" || !SDK_ID_RE.test(value)) {
166
+ throw new NeetruError(
167
+ "validation_failed",
168
+ `${field} must match ${SDK_ID_RE.source} (lowercase alphanumeric, _, -; first char must be alphanumeric; max 63 chars)`
169
+ );
151
170
  }
152
- return {
153
- metric: typeof r.metric === "string" ? r.metric : metric,
154
- used: r.used,
155
- limit: r.limit,
156
- resetsAt: typeof r.resetsAt === "string" ? r.resetsAt : void 0,
157
- plan: typeof r.plan === "string" ? r.plan : void 0
158
- };
159
171
  }
172
+
173
+ // src/usage.ts
160
174
  function createUsageNamespace(config) {
161
175
  return {
162
176
  /**
163
- * Persiste um evento de usage. Em dev (mocks ativos via factory) só loga.
164
- * Em workspace/prod chama POST /sdk/v1/usage/record.
165
- */
166
- async track(event, properties) {
167
- if (!event || typeof event !== "string") {
168
- throw new NeetruError("validation_failed", "event name is required");
169
- }
170
- if (event.length > 128) {
171
- throw new NeetruError("validation_failed", "event name max 128 chars");
172
- }
173
- const body = { event };
174
- if (properties && typeof properties === "object") body.properties = properties;
175
- const raw = await httpRequest(config, {
176
- method: "POST",
177
- path: "/sdk/v1/usage/record",
178
- body,
179
- requireAuth: true
180
- });
181
- if (!raw || raw.ok !== true) {
182
- throw new NeetruError("invalid_response", "Usage record response missing ok");
183
- }
184
- return { ok: true };
185
- },
186
- /**
187
- * Lê quota atual de uma métrica. Cacheável (caller decide), SDK não cacheia.
188
- */
189
- async getQuota(metric) {
190
- if (!metric || typeof metric !== "string") {
191
- throw new NeetruError("validation_failed", "metric is required");
192
- }
193
- const raw = await httpRequest(config, {
194
- method: "GET",
195
- path: "/sdk/v1/usage/quota",
196
- query: { metric },
197
- requireAuth: true
198
- });
199
- return toQuota(metric, raw);
200
- },
201
- /**
202
- * v0.3 — Reporta consumo metered. Hit no endpoint canônico Sprint 7.
177
+ * Reporta consumo metered de um resource. Hit no endpoint canônico
178
+ * `POST /api/sdk/v1/usage/record`. Idempotency-Key gerado por chamada;
179
+ * Core dedup serverside TTL 24h.
180
+ *
181
+ * @example
182
+ * ```ts
183
+ * await client.usage.report('api_call', 1);
184
+ * // ou explicit:
185
+ * await client.usage.report('api_call', 5, { productId: 'gestovendas', tenantId: 't-acme' });
186
+ * ```
203
187
  */
204
188
  async report(resource, qty = 1, options) {
205
189
  if (!resource || typeof resource !== "string") {
@@ -222,11 +206,14 @@ function createUsageNamespace(config) {
222
206
  "tenantId required (pass to options or set on createNeetruClient)"
223
207
  );
224
208
  }
209
+ assertValidSdkId("productId", productId);
210
+ assertValidSdkId("tenantId", tenantId);
225
211
  const raw = await httpRequest(config, {
226
212
  method: "POST",
227
- path: "/sdk/v1/usage/record",
213
+ path: "/api/sdk/v1/usage/record",
228
214
  body: { productId, tenantId, resource, qty: Math.floor(qty) },
229
- requireAuth: true
215
+ requireAuth: true,
216
+ idempotencyKey: true
230
217
  });
231
218
  if (!raw || raw.ok !== true) {
232
219
  throw new NeetruError("invalid_response", "usage.report response missing ok");
@@ -241,7 +228,10 @@ function createUsageNamespace(config) {
241
228
  };
242
229
  },
243
230
  /**
244
- * v0.3 — Verifica entitlement de um resource via GET /sdk/v1/entitlements.
231
+ * Verifica entitlement de um resource via `GET /api/sdk/v1/entitlements`.
232
+ * Resposta inclui `allowed`, `reason` ('granted' | 'not_subscribed' |
233
+ * 'subscription_inactive' | 'feature_not_in_plan' | 'limit_exceeded'),
234
+ * `remaining`, `limit`, `planId`, `planFeatures`.
245
235
  */
246
236
  async check(resource, options) {
247
237
  if (!resource || typeof resource !== "string") {
@@ -252,25 +242,29 @@ function createUsageNamespace(config) {
252
242
  if (!productId || !tenantId) {
253
243
  throw new NeetruError(
254
244
  "validation_failed",
255
- "productId and tenantId required"
245
+ "productId and tenantId required (pass to options or set on createNeetruClient)"
256
246
  );
257
247
  }
248
+ assertValidSdkId("productId", productId);
249
+ assertValidSdkId("tenantId", tenantId);
258
250
  const raw = await httpRequest(config, {
259
251
  method: "GET",
260
- path: "/sdk/v1/entitlements",
252
+ path: "/api/sdk/v1/entitlements",
261
253
  query: { productId, tenantId, feature: resource },
262
254
  requireAuth: true
263
255
  });
264
256
  if (!raw || typeof raw.allowed !== "boolean") {
265
257
  throw new NeetruError("invalid_response", "usage.check response missing allowed");
266
258
  }
259
+ const behavior = raw.behavior ?? (raw.reason === "limit_exceeded" ? "readonly" : null);
267
260
  return {
268
261
  allowed: raw.allowed,
269
262
  reason: raw.reason,
270
263
  remaining: raw.remaining,
271
264
  limit: raw.limit,
272
265
  planId: raw.planId,
273
- planFeatures: raw.planFeatures
266
+ planFeatures: raw.planFeatures,
267
+ behavior
274
268
  };
275
269
  }
276
270
  };