@neetru/sdk 2.3.5 → 3.0.0

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 +184 -0
  2. package/README.md +59 -22
  3. package/dist/auth.cjs +368 -142
  4. package/dist/auth.cjs.map +1 -1
  5. package/dist/auth.d.cts +1 -1
  6. package/dist/auth.d.ts +1 -1
  7. package/dist/auth.mjs +368 -142
  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 -1
  12. package/dist/catalog.d.ts +1 -1
  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 -1
  18. package/dist/checkout.d.ts +1 -1
  19. package/dist/checkout.mjs +21 -2
  20. package/dist/checkout.mjs.map +1 -1
  21. package/dist/db.cjs +36 -5
  22. package/dist/db.cjs.map +1 -1
  23. package/dist/db.d.cts +1 -1
  24. package/dist/db.d.ts +1 -1
  25. package/dist/db.mjs +36 -5
  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 -1
  30. package/dist/entitlements.d.ts +1 -1
  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 +432 -152
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.d.cts +31 -19
  40. package/dist/index.d.ts +31 -19
  41. package/dist/index.mjs +432 -152
  42. package/dist/index.mjs.map +1 -1
  43. package/dist/mocks.cjs +49 -37
  44. package/dist/mocks.cjs.map +1 -1
  45. package/dist/mocks.d.cts +23 -19
  46. package/dist/mocks.d.ts +23 -19
  47. package/dist/mocks.mjs +49 -37
  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 -1
  52. package/dist/notifications.d.ts +1 -1
  53. package/dist/notifications.mjs +80 -14
  54. package/dist/notifications.mjs.map +1 -1
  55. package/dist/react.d.cts +1 -1
  56. package/dist/react.d.ts +1 -1
  57. package/dist/support.cjs +72 -12
  58. package/dist/support.cjs.map +1 -1
  59. package/dist/support.d.cts +1 -1
  60. package/dist/support.d.ts +1 -1
  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 -1
  66. package/dist/telemetry.d.ts +1 -1
  67. package/dist/telemetry.mjs +23 -4
  68. package/dist/telemetry.mjs.map +1 -1
  69. package/dist/{types-sGGN1vkg.d.cts → types-DLOvkeAP.d.cts} +110 -49
  70. package/dist/{types-CSC-RIdS.d.ts → types-dw19bdID.d.ts} +110 -49
  71. package/dist/usage.cjs +57 -63
  72. package/dist/usage.cjs.map +1 -1
  73. package/dist/usage.d.cts +1 -1
  74. package/dist/usage.d.ts +1 -1
  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 -1
  80. package/dist/webhooks.d.ts +1 -1
  81. package/dist/webhooks.mjs +110 -33
  82. package/dist/webhooks.mjs.map +1 -1
  83. package/package.json +2 -2
@@ -104,6 +104,11 @@ interface ProductNotification {
104
104
  dismissedAt?: string;
105
105
  }
106
106
  interface SendNotificationInput {
107
+ /**
108
+ * **3.0 (breaking):** productId é obrigatório (Core valida via Zod). Default
109
+ * resolve do `config.productId`; lança `validation_failed` se ausente.
110
+ */
111
+ productId?: string;
107
112
  userId: string;
108
113
  kind: string;
109
114
  title: string;
@@ -118,6 +123,11 @@ interface SendNotificationInput {
118
123
  fingerprint?: string;
119
124
  }
120
125
  interface ListNotificationsOptions {
126
+ /**
127
+ * **3.0 (breaking):** productId é obrigatório no Core. Default resolve do
128
+ * `config.productId`.
129
+ */
130
+ productId?: string;
121
131
  /** Inclui notificações dismissed (default false). */
122
132
  includeDismissed?: boolean;
123
133
  /** Filtro por unread. */
@@ -125,17 +135,21 @@ interface ListNotificationsOptions {
125
135
  /** Máx itens (default 50, máx 200). */
126
136
  limit?: number;
127
137
  }
138
+ /** 3.0: scope option pra markRead/dismiss (productId obrigatório no Core). */
139
+ interface NotificationScopeOptions {
140
+ productId?: string;
141
+ }
128
142
  interface NotificationsNamespace {
129
143
  /** Produto envia uma notificação pra um usuário do produto. */
130
144
  send(input: SendNotificationInput): Promise<ProductNotification>;
131
145
  /** Lista notificações de um usuário. */
132
146
  list(userId: string, options?: ListNotificationsOptions): Promise<ProductNotification[]>;
133
147
  /** Marca como lida. */
134
- markRead(id: string): Promise<{
148
+ markRead(id: string, options?: NotificationScopeOptions): Promise<{
135
149
  ok: true;
136
150
  }>;
137
151
  /** Descarta. */
138
- dismiss(id: string): Promise<{
152
+ dismiss(id: string, options?: NotificationScopeOptions): Promise<{
139
153
  ok: true;
140
154
  }>;
141
155
  }
@@ -145,10 +159,10 @@ declare class MockNotifications implements NotificationsNamespace {
145
159
  private nextId;
146
160
  send(input: SendNotificationInput): Promise<ProductNotification>;
147
161
  list(userId: string, options?: ListNotificationsOptions): Promise<ProductNotification[]>;
148
- markRead(id: string): Promise<{
162
+ markRead(id: string, _options?: NotificationScopeOptions): Promise<{
149
163
  ok: true;
150
164
  }>;
151
- dismiss(id: string): Promise<{
165
+ dismiss(id: string, _options?: NotificationScopeOptions): Promise<{
152
166
  ok: true;
153
167
  }>;
154
168
  }
@@ -169,6 +183,12 @@ interface WebhookEndpoint {
169
183
  createdAt: string;
170
184
  }
171
185
  interface RegisterWebhookInput {
186
+ /**
187
+ * **3.0 (breaking):** `productId` é obrigatório no body. Se ausente,
188
+ * SDK tenta resolver do `config.productId` (default no `createNeetruClient`);
189
+ * lança `validation_failed` se ambos faltarem.
190
+ */
191
+ productId?: string;
172
192
  url: string;
173
193
  events: WebhookEvent[];
174
194
  /**
@@ -184,53 +204,67 @@ interface WebhookTestResult {
184
204
  durationMs?: number;
185
205
  error?: string;
186
206
  }
207
+ /**
208
+ * 3.0: opções de scoping — todos os verbos exigem `productId` (default do
209
+ * `config.productId` se ausente).
210
+ */
211
+ interface WebhookScopeOptions {
212
+ productId?: string;
213
+ }
187
214
  interface WebhooksNamespace {
188
215
  /** Registra um novo endpoint. */
189
216
  register(input: RegisterWebhookInput): Promise<WebhookEndpoint>;
190
217
  /** Lista endpoints registrados pelo produto. */
191
- list(): Promise<WebhookEndpoint[]>;
218
+ list(options?: WebhookScopeOptions): Promise<WebhookEndpoint[]>;
192
219
  /** Remove um endpoint. */
193
- unregister(id: string): Promise<{
220
+ unregister(id: string, options?: WebhookScopeOptions): Promise<{
194
221
  ok: true;
195
222
  }>;
196
223
  /** Dispara evento de teste pra validar conectividade. */
197
- test(id: string): Promise<WebhookTestResult>;
224
+ test(id: string, options?: WebhookScopeOptions): Promise<WebhookTestResult>;
198
225
  }
199
226
  /**
200
227
  * Verifica assinatura HMAC SHA-256 de payload de webhook recebido pelo
201
- * produto consumer. Constant-time compare pra resistir a timing attack.
228
+ * produto consumer. **3.0**: Universal (browser/Node/Edge) via WebCrypto.subtle.
229
+ *
230
+ * Constant-time compare via byte-level XOR accumulator (não usa
231
+ * `timingSafeEqual` do Node porque WebCrypto não tem equivalente direto).
202
232
  *
203
- * @param payload Corpo cru da request (string ou Buffer). NÃO use o objeto
233
+ * @param payload Corpo cru da request (string ou Uint8Array). NÃO use o objeto
204
234
  * parseado — JSON.stringify pode diferir do que foi assinado.
205
235
  * @param signature Header `X-Neetru-Signature` recebido (formato `sha256=<hex>`).
206
236
  * @param secret Secret registrado em `webhooks.register({ secret })`.
207
- * @returns true se assinatura confere, false caso contrário.
237
+ * @returns Promise<boolean> — true se assinatura confere, false caso contrário.
208
238
  *
209
239
  * @example
210
240
  * ```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'));
241
+ * // Next.js Route Handler (Edge ou Node runtime — funciona em ambos)
242
+ * export async function POST(req: Request) {
243
+ * const raw = await req.text();
244
+ * const sig = req.headers.get('X-Neetru-Signature');
245
+ * const ok = await verifyWebhookSignature(raw, sig, process.env.NEETRU_WEBHOOK_SECRET!);
246
+ * if (!ok) return new Response('unauthorized', { status: 401 });
247
+ * const event = JSON.parse(raw);
218
248
  * // ... handle event
219
- * res.json({ ok: true });
220
- * });
249
+ * return Response.json({ ok: true });
250
+ * }
221
251
  * ```
252
+ *
253
+ * @throws NeetruError('runtime_unsupported') se runtime não expõe WebCrypto.
254
+ * Não retorna `false` silencioso — caller precisa distinguir "assinatura
255
+ * inválida" (return false) de "ambiente não suporta" (throw).
222
256
  */
223
- declare function verifyWebhookSignature(payload: string | Uint8Array, signature: string | null | undefined, secret: string): boolean;
257
+ declare function verifyWebhookSignature(payload: string | Uint8Array, signature: string | null | undefined, secret: string): Promise<boolean>;
224
258
  declare function createWebhooksNamespace(config: ResolvedConfig): WebhooksNamespace;
225
259
  declare class MockWebhooks implements WebhooksNamespace {
226
260
  private endpoints;
227
261
  private nextId;
228
262
  register(input: RegisterWebhookInput): Promise<WebhookEndpoint>;
229
- list(): Promise<WebhookEndpoint[]>;
230
- unregister(id: string): Promise<{
263
+ list(_options?: WebhookScopeOptions): Promise<WebhookEndpoint[]>;
264
+ unregister(id: string, _options?: WebhookScopeOptions): Promise<{
231
265
  ok: true;
232
266
  }>;
233
- test(id: string): Promise<WebhookTestResult>;
267
+ test(id: string, _options?: WebhookScopeOptions): Promise<WebhookTestResult>;
234
268
  }
235
269
 
236
270
  /**
@@ -1043,6 +1077,14 @@ interface NeetruClientConfig {
1043
1077
  * Valores: `'rest'` | `'firestore'` | `'nosql-vm'`.
1044
1078
  */
1045
1079
  db?: NeetruDbOptions;
1080
+ /**
1081
+ * 3.0 — hosts adicionais permitidos para `baseUrl`. Por default, SDK aceita
1082
+ * `*.neetru.com` HTTPS + localhost. Para apontar pra staging/proxy custom em
1083
+ * `https://meu-proxy.empresa.com`, adicione `['empresa.com']`.
1084
+ *
1085
+ * Bypass total: `NEETRU_ALLOW_INSECURE_BASEURL=1` no env.
1086
+ */
1087
+ allowedHosts?: string[];
1046
1088
  }
1047
1089
  /**
1048
1090
  * Status do produto no catálogo público.
@@ -1299,41 +1341,41 @@ interface VerifyTokenOptions {
1299
1341
  */
1300
1342
  jwksCacheTtlMs?: number;
1301
1343
  }
1344
+ /**
1345
+ * 3.0 BREAKING: `UsageEventInput` mantido só como tipo para tests/legacy
1346
+ * imports — não é mais retorno de método. `usage.track`/`usage.getQuota`
1347
+ * **REMOVIDOS** em 3.0. Use `usage.report(resource, qty, options)`.
1348
+ */
1302
1349
  interface UsageEventInput {
1303
- /** Nome do evento, ex: `report_generated`, `api_call`. */
1304
1350
  event: string;
1305
- /** Atributos do evento. Valores primitivos serializáveis. */
1306
1351
  properties?: Record<string, string | number | boolean | null>;
1307
- /** Quantidade — default 1 (1 evento). Útil pra batch. */
1308
1352
  quantity?: number;
1309
1353
  }
1354
+ /**
1355
+ * 3.0 BREAKING: `UsageQuota` mantido só para compat de imports. Não é mais
1356
+ * retorno de método ativo. `usage.report()` devolve `{value, limit, remaining}`
1357
+ * inline; `usage.check()` devolve detalhes do entitlement.
1358
+ */
1310
1359
  interface UsageQuota {
1311
1360
  metric: string;
1312
- /** Quantidade já consumida no período. */
1313
1361
  used: number;
1314
- /** Limite total. -1 = unlimited. */
1315
1362
  limit: number;
1316
- /** ISO timestamp do reset (próximo período). */
1317
1363
  resetsAt?: string;
1318
- /** Plano que define o limite. */
1319
1364
  plan?: string;
1320
1365
  }
1366
+ /**
1367
+ * 3.0 — superfície limpa: `report` (POST metered) + `check` (GET entitlement).
1368
+ *
1369
+ * Métodos removidos vs 2.x:
1370
+ * - `track(event, props)` — vaporware contra Core. Use `report()`.
1371
+ * - `getQuota(metric)` — endpoint nunca existiu. `report()` devolve quota.
1372
+ */
1321
1373
  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
1374
  /**
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.
1331
- *
1332
- * Diferente de `track()`: usa o endpoint canônico Sprint 7 que incrementa
1333
- * `usage_counters/{tenantId}_{productId}_{resource}_{yyyymm}` atomicamente.
1375
+ * Reporta consumo metered. Incrementa `usage_counters` no Core atomicamente.
1376
+ * Idempotency-Key gerado por chamada (defesa anti-dup em retry transient).
1334
1377
  *
1335
- * `productId`/`tenantId` são lidos do contexto resolvido do client se
1336
- * ausentes nas options.
1378
+ * `productId`/`tenantId` lidos do contexto do client se ausentes nas options.
1337
1379
  */
1338
1380
  report(resource: string, qty?: number, options?: {
1339
1381
  productId?: string;
@@ -1347,8 +1389,9 @@ interface UsageNamespace {
1347
1389
  status?: string;
1348
1390
  }>;
1349
1391
  /**
1350
- * v0.3 — Verifica entitlement de um resource/feature. Wrapper em
1351
- * GET /sdk/v1/entitlements. Em dev consulta MockEntitlements + MockUsage.
1392
+ * Verifica entitlement de um resource/feature. GET cacheável (HTTP 60s).
1393
+ * Resposta inclui `allowed`, `reason`, `remaining`, `limit`, `planId`,
1394
+ * `planFeatures` e `behavior` ('readonly' quando limit_exceeded).
1352
1395
  */
1353
1396
  check(resource: string, options?: {
1354
1397
  productId?: string;
@@ -1360,6 +1403,7 @@ interface UsageNamespace {
1360
1403
  limit?: number;
1361
1404
  planId?: string | null;
1362
1405
  planFeatures?: string[];
1406
+ behavior?: 'readonly' | null;
1363
1407
  }>;
1364
1408
  }
1365
1409
  /**
@@ -1427,12 +1471,29 @@ interface CreateTicketInput {
1427
1471
  message: string;
1428
1472
  severity?: SupportSeverity;
1429
1473
  productSlug?: string;
1474
+ /**
1475
+ * **L-05 fix (Opus review 2026-05-27):** metadata opcional (Core já aceita
1476
+ * via `metadata: z.record(...)` no Zod). Útil pra anexar contexto como
1477
+ * `{ orderId, userAgent, route, sessionId }` sem inflar subject/message.
1478
+ *
1479
+ * Chaves limitadas a 64 chars; valores primitive (string/number/boolean/null).
1480
+ */
1481
+ metadata?: Record<string, string | number | boolean | null>;
1430
1482
  }
1431
1483
  interface SupportNamespace {
1432
- /** Cria um novo ticket. Mock em dev, POST `/api/v1/products/{slug}/tickets` em prod. */
1484
+ /**
1485
+ * Cria um novo ticket. **3.0**: POST `/api/sdk/v1/support/tickets`
1486
+ * (canonical, requer `productId` no body — default `config.productId`).
1487
+ * Mock em dev.
1488
+ */
1433
1489
  createTicket(input: CreateTicketInput): Promise<SupportTicket>;
1434
- /** Lista meus tickets abertos. */
1435
- listMyTickets(): Promise<SupportTicket[]>;
1490
+ /**
1491
+ * Lista meus tickets abertos. **3.0**: GET `/api/sdk/v1/support/tickets?productId=...`
1492
+ * (canonical). `options.productSlug` override do `config.productId` default.
1493
+ */
1494
+ listMyTickets(options?: {
1495
+ productSlug?: string;
1496
+ }): Promise<SupportTicket[]>;
1436
1497
  }
1437
1498
  /**
1438
1499
  * Modo do SDK. `dev` ativa mocks automáticos (úteis pra testes e
@@ -1459,4 +1520,4 @@ interface CatalogListOptions {
1459
1520
  includeDrafts?: boolean;
1460
1521
  }
1461
1522
 
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 };
1523
+ 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 };
@@ -104,6 +104,11 @@ interface ProductNotification {
104
104
  dismissedAt?: string;
105
105
  }
106
106
  interface SendNotificationInput {
107
+ /**
108
+ * **3.0 (breaking):** productId é obrigatório (Core valida via Zod). Default
109
+ * resolve do `config.productId`; lança `validation_failed` se ausente.
110
+ */
111
+ productId?: string;
107
112
  userId: string;
108
113
  kind: string;
109
114
  title: string;
@@ -118,6 +123,11 @@ interface SendNotificationInput {
118
123
  fingerprint?: string;
119
124
  }
120
125
  interface ListNotificationsOptions {
126
+ /**
127
+ * **3.0 (breaking):** productId é obrigatório no Core. Default resolve do
128
+ * `config.productId`.
129
+ */
130
+ productId?: string;
121
131
  /** Inclui notificações dismissed (default false). */
122
132
  includeDismissed?: boolean;
123
133
  /** Filtro por unread. */
@@ -125,17 +135,21 @@ interface ListNotificationsOptions {
125
135
  /** Máx itens (default 50, máx 200). */
126
136
  limit?: number;
127
137
  }
138
+ /** 3.0: scope option pra markRead/dismiss (productId obrigatório no Core). */
139
+ interface NotificationScopeOptions {
140
+ productId?: string;
141
+ }
128
142
  interface NotificationsNamespace {
129
143
  /** Produto envia uma notificação pra um usuário do produto. */
130
144
  send(input: SendNotificationInput): Promise<ProductNotification>;
131
145
  /** Lista notificações de um usuário. */
132
146
  list(userId: string, options?: ListNotificationsOptions): Promise<ProductNotification[]>;
133
147
  /** Marca como lida. */
134
- markRead(id: string): Promise<{
148
+ markRead(id: string, options?: NotificationScopeOptions): Promise<{
135
149
  ok: true;
136
150
  }>;
137
151
  /** Descarta. */
138
- dismiss(id: string): Promise<{
152
+ dismiss(id: string, options?: NotificationScopeOptions): Promise<{
139
153
  ok: true;
140
154
  }>;
141
155
  }
@@ -145,10 +159,10 @@ declare class MockNotifications implements NotificationsNamespace {
145
159
  private nextId;
146
160
  send(input: SendNotificationInput): Promise<ProductNotification>;
147
161
  list(userId: string, options?: ListNotificationsOptions): Promise<ProductNotification[]>;
148
- markRead(id: string): Promise<{
162
+ markRead(id: string, _options?: NotificationScopeOptions): Promise<{
149
163
  ok: true;
150
164
  }>;
151
- dismiss(id: string): Promise<{
165
+ dismiss(id: string, _options?: NotificationScopeOptions): Promise<{
152
166
  ok: true;
153
167
  }>;
154
168
  }
@@ -169,6 +183,12 @@ interface WebhookEndpoint {
169
183
  createdAt: string;
170
184
  }
171
185
  interface RegisterWebhookInput {
186
+ /**
187
+ * **3.0 (breaking):** `productId` é obrigatório no body. Se ausente,
188
+ * SDK tenta resolver do `config.productId` (default no `createNeetruClient`);
189
+ * lança `validation_failed` se ambos faltarem.
190
+ */
191
+ productId?: string;
172
192
  url: string;
173
193
  events: WebhookEvent[];
174
194
  /**
@@ -184,53 +204,67 @@ interface WebhookTestResult {
184
204
  durationMs?: number;
185
205
  error?: string;
186
206
  }
207
+ /**
208
+ * 3.0: opções de scoping — todos os verbos exigem `productId` (default do
209
+ * `config.productId` se ausente).
210
+ */
211
+ interface WebhookScopeOptions {
212
+ productId?: string;
213
+ }
187
214
  interface WebhooksNamespace {
188
215
  /** Registra um novo endpoint. */
189
216
  register(input: RegisterWebhookInput): Promise<WebhookEndpoint>;
190
217
  /** Lista endpoints registrados pelo produto. */
191
- list(): Promise<WebhookEndpoint[]>;
218
+ list(options?: WebhookScopeOptions): Promise<WebhookEndpoint[]>;
192
219
  /** Remove um endpoint. */
193
- unregister(id: string): Promise<{
220
+ unregister(id: string, options?: WebhookScopeOptions): Promise<{
194
221
  ok: true;
195
222
  }>;
196
223
  /** Dispara evento de teste pra validar conectividade. */
197
- test(id: string): Promise<WebhookTestResult>;
224
+ test(id: string, options?: WebhookScopeOptions): Promise<WebhookTestResult>;
198
225
  }
199
226
  /**
200
227
  * Verifica assinatura HMAC SHA-256 de payload de webhook recebido pelo
201
- * produto consumer. Constant-time compare pra resistir a timing attack.
228
+ * produto consumer. **3.0**: Universal (browser/Node/Edge) via WebCrypto.subtle.
229
+ *
230
+ * Constant-time compare via byte-level XOR accumulator (não usa
231
+ * `timingSafeEqual` do Node porque WebCrypto não tem equivalente direto).
202
232
  *
203
- * @param payload Corpo cru da request (string ou Buffer). NÃO use o objeto
233
+ * @param payload Corpo cru da request (string ou Uint8Array). NÃO use o objeto
204
234
  * parseado — JSON.stringify pode diferir do que foi assinado.
205
235
  * @param signature Header `X-Neetru-Signature` recebido (formato `sha256=<hex>`).
206
236
  * @param secret Secret registrado em `webhooks.register({ secret })`.
207
- * @returns true se assinatura confere, false caso contrário.
237
+ * @returns Promise<boolean> — true se assinatura confere, false caso contrário.
208
238
  *
209
239
  * @example
210
240
  * ```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'));
241
+ * // Next.js Route Handler (Edge ou Node runtime — funciona em ambos)
242
+ * export async function POST(req: Request) {
243
+ * const raw = await req.text();
244
+ * const sig = req.headers.get('X-Neetru-Signature');
245
+ * const ok = await verifyWebhookSignature(raw, sig, process.env.NEETRU_WEBHOOK_SECRET!);
246
+ * if (!ok) return new Response('unauthorized', { status: 401 });
247
+ * const event = JSON.parse(raw);
218
248
  * // ... handle event
219
- * res.json({ ok: true });
220
- * });
249
+ * return Response.json({ ok: true });
250
+ * }
221
251
  * ```
252
+ *
253
+ * @throws NeetruError('runtime_unsupported') se runtime não expõe WebCrypto.
254
+ * Não retorna `false` silencioso — caller precisa distinguir "assinatura
255
+ * inválida" (return false) de "ambiente não suporta" (throw).
222
256
  */
223
- declare function verifyWebhookSignature(payload: string | Uint8Array, signature: string | null | undefined, secret: string): boolean;
257
+ declare function verifyWebhookSignature(payload: string | Uint8Array, signature: string | null | undefined, secret: string): Promise<boolean>;
224
258
  declare function createWebhooksNamespace(config: ResolvedConfig): WebhooksNamespace;
225
259
  declare class MockWebhooks implements WebhooksNamespace {
226
260
  private endpoints;
227
261
  private nextId;
228
262
  register(input: RegisterWebhookInput): Promise<WebhookEndpoint>;
229
- list(): Promise<WebhookEndpoint[]>;
230
- unregister(id: string): Promise<{
263
+ list(_options?: WebhookScopeOptions): Promise<WebhookEndpoint[]>;
264
+ unregister(id: string, _options?: WebhookScopeOptions): Promise<{
231
265
  ok: true;
232
266
  }>;
233
- test(id: string): Promise<WebhookTestResult>;
267
+ test(id: string, _options?: WebhookScopeOptions): Promise<WebhookTestResult>;
234
268
  }
235
269
 
236
270
  /**
@@ -1043,6 +1077,14 @@ interface NeetruClientConfig {
1043
1077
  * Valores: `'rest'` | `'firestore'` | `'nosql-vm'`.
1044
1078
  */
1045
1079
  db?: NeetruDbOptions;
1080
+ /**
1081
+ * 3.0 — hosts adicionais permitidos para `baseUrl`. Por default, SDK aceita
1082
+ * `*.neetru.com` HTTPS + localhost. Para apontar pra staging/proxy custom em
1083
+ * `https://meu-proxy.empresa.com`, adicione `['empresa.com']`.
1084
+ *
1085
+ * Bypass total: `NEETRU_ALLOW_INSECURE_BASEURL=1` no env.
1086
+ */
1087
+ allowedHosts?: string[];
1046
1088
  }
1047
1089
  /**
1048
1090
  * Status do produto no catálogo público.
@@ -1299,41 +1341,41 @@ interface VerifyTokenOptions {
1299
1341
  */
1300
1342
  jwksCacheTtlMs?: number;
1301
1343
  }
1344
+ /**
1345
+ * 3.0 BREAKING: `UsageEventInput` mantido só como tipo para tests/legacy
1346
+ * imports — não é mais retorno de método. `usage.track`/`usage.getQuota`
1347
+ * **REMOVIDOS** em 3.0. Use `usage.report(resource, qty, options)`.
1348
+ */
1302
1349
  interface UsageEventInput {
1303
- /** Nome do evento, ex: `report_generated`, `api_call`. */
1304
1350
  event: string;
1305
- /** Atributos do evento. Valores primitivos serializáveis. */
1306
1351
  properties?: Record<string, string | number | boolean | null>;
1307
- /** Quantidade — default 1 (1 evento). Útil pra batch. */
1308
1352
  quantity?: number;
1309
1353
  }
1354
+ /**
1355
+ * 3.0 BREAKING: `UsageQuota` mantido só para compat de imports. Não é mais
1356
+ * retorno de método ativo. `usage.report()` devolve `{value, limit, remaining}`
1357
+ * inline; `usage.check()` devolve detalhes do entitlement.
1358
+ */
1310
1359
  interface UsageQuota {
1311
1360
  metric: string;
1312
- /** Quantidade já consumida no período. */
1313
1361
  used: number;
1314
- /** Limite total. -1 = unlimited. */
1315
1362
  limit: number;
1316
- /** ISO timestamp do reset (próximo período). */
1317
1363
  resetsAt?: string;
1318
- /** Plano que define o limite. */
1319
1364
  plan?: string;
1320
1365
  }
1366
+ /**
1367
+ * 3.0 — superfície limpa: `report` (POST metered) + `check` (GET entitlement).
1368
+ *
1369
+ * Métodos removidos vs 2.x:
1370
+ * - `track(event, props)` — vaporware contra Core. Use `report()`.
1371
+ * - `getQuota(metric)` — endpoint nunca existiu. `report()` devolve quota.
1372
+ */
1321
1373
  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
1374
  /**
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.
1331
- *
1332
- * Diferente de `track()`: usa o endpoint canônico Sprint 7 que incrementa
1333
- * `usage_counters/{tenantId}_{productId}_{resource}_{yyyymm}` atomicamente.
1375
+ * Reporta consumo metered. Incrementa `usage_counters` no Core atomicamente.
1376
+ * Idempotency-Key gerado por chamada (defesa anti-dup em retry transient).
1334
1377
  *
1335
- * `productId`/`tenantId` são lidos do contexto resolvido do client se
1336
- * ausentes nas options.
1378
+ * `productId`/`tenantId` lidos do contexto do client se ausentes nas options.
1337
1379
  */
1338
1380
  report(resource: string, qty?: number, options?: {
1339
1381
  productId?: string;
@@ -1347,8 +1389,9 @@ interface UsageNamespace {
1347
1389
  status?: string;
1348
1390
  }>;
1349
1391
  /**
1350
- * v0.3 — Verifica entitlement de um resource/feature. Wrapper em
1351
- * GET /sdk/v1/entitlements. Em dev consulta MockEntitlements + MockUsage.
1392
+ * Verifica entitlement de um resource/feature. GET cacheável (HTTP 60s).
1393
+ * Resposta inclui `allowed`, `reason`, `remaining`, `limit`, `planId`,
1394
+ * `planFeatures` e `behavior` ('readonly' quando limit_exceeded).
1352
1395
  */
1353
1396
  check(resource: string, options?: {
1354
1397
  productId?: string;
@@ -1360,6 +1403,7 @@ interface UsageNamespace {
1360
1403
  limit?: number;
1361
1404
  planId?: string | null;
1362
1405
  planFeatures?: string[];
1406
+ behavior?: 'readonly' | null;
1363
1407
  }>;
1364
1408
  }
1365
1409
  /**
@@ -1427,12 +1471,29 @@ interface CreateTicketInput {
1427
1471
  message: string;
1428
1472
  severity?: SupportSeverity;
1429
1473
  productSlug?: string;
1474
+ /**
1475
+ * **L-05 fix (Opus review 2026-05-27):** metadata opcional (Core já aceita
1476
+ * via `metadata: z.record(...)` no Zod). Útil pra anexar contexto como
1477
+ * `{ orderId, userAgent, route, sessionId }` sem inflar subject/message.
1478
+ *
1479
+ * Chaves limitadas a 64 chars; valores primitive (string/number/boolean/null).
1480
+ */
1481
+ metadata?: Record<string, string | number | boolean | null>;
1430
1482
  }
1431
1483
  interface SupportNamespace {
1432
- /** Cria um novo ticket. Mock em dev, POST `/api/v1/products/{slug}/tickets` em prod. */
1484
+ /**
1485
+ * Cria um novo ticket. **3.0**: POST `/api/sdk/v1/support/tickets`
1486
+ * (canonical, requer `productId` no body — default `config.productId`).
1487
+ * Mock em dev.
1488
+ */
1433
1489
  createTicket(input: CreateTicketInput): Promise<SupportTicket>;
1434
- /** Lista meus tickets abertos. */
1435
- listMyTickets(): Promise<SupportTicket[]>;
1490
+ /**
1491
+ * Lista meus tickets abertos. **3.0**: GET `/api/sdk/v1/support/tickets?productId=...`
1492
+ * (canonical). `options.productSlug` override do `config.productId` default.
1493
+ */
1494
+ listMyTickets(options?: {
1495
+ productSlug?: string;
1496
+ }): Promise<SupportTicket[]>;
1436
1497
  }
1437
1498
  /**
1438
1499
  * Modo do SDK. `dev` ativa mocks automáticos (úteis pra testes e
@@ -1459,4 +1520,4 @@ interface CatalogListOptions {
1459
1520
  includeDrafts?: boolean;
1460
1521
  }
1461
1522
 
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 };
1523
+ 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 };