@neetru/sdk 2.3.6 → 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 +172 -0
  2. package/README.md +59 -22
  3. package/dist/auth.cjs +360 -140
  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 +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 -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 +28 -3
  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 +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 -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 +408 -146
  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 +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 -19
  46. package/dist/mocks.d.ts +23 -19
  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 -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
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
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/usage.ts"],"names":["err","message"],"mappings":"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","file":"usage.cjs","sourcesContent":["/**\r\n * Erros tipados do SDK.\r\n *\r\n * Todo erro lançado pelo SDK estende `NeetruError` — caller pode discriminar\r\n * por `.code` (string estável) sem parsing de message.\r\n */\r\n\r\n/** Códigos de erro estáveis do SDK. Adicionar aqui requer minor bump. */\r\nexport type NeetruErrorCode =\r\n | 'invalid_config'\r\n | 'missing_api_key'\r\n | 'unauthorized'\r\n | 'forbidden'\r\n | 'not_found'\r\n | 'rate_limited'\r\n | 'validation_failed'\r\n | 'network_error'\r\n | 'invalid_response'\r\n | 'server_error'\r\n | 'token_expired'\r\n | 'token_invalid_signature'\r\n | 'token_invalid_audience'\r\n | 'token_invalid_issuer'\r\n | 'unknown';\r\n\r\n/**\r\n * Erro tipado padrão do SDK. Sempre lançado em vez de Error genérico.\r\n *\r\n * @example\r\n * ```ts\r\n * try { await client.catalog.list(); }\r\n * catch (e) {\r\n * if (e instanceof NeetruError && e.code === 'rate_limited') retry();\r\n * }\r\n * ```\r\n */\r\nexport class NeetruError extends Error {\r\n public readonly code: NeetruErrorCode | string;\r\n public readonly status?: number;\r\n public readonly requestId?: string;\r\n\r\n constructor(\r\n code: NeetruErrorCode | string,\r\n message: string,\r\n status?: number,\r\n requestId?: string,\r\n ) {\r\n super(message);\r\n this.name = 'NeetruError';\r\n this.code = code;\r\n this.status = status;\r\n this.requestId = requestId;\r\n // Preserva o prototype chain ao herdar de Error (downlevel quirk de TS).\r\n Object.setPrototypeOf(this, NeetruError.prototype);\r\n }\r\n}\r\n","/**\r\n * HTTP transport interno do SDK.\r\n *\r\n * Responsabilidades:\r\n * - Construir URL absoluta a partir de `baseUrl` + path\r\n * - Injetar Bearer token quando `requireAuth=true`\r\n * - Mapear status HTTP → `NeetruError` com `code` estável\r\n * - Parse defensivo de JSON (não lança em body vazio em 204)\r\n * - Retry/backoff exponencial em `rate_limited` (429), `server_error` (5xx)\r\n * e `network_error` (timeout/falha de rede). Honra `Retry-After` quando\r\n * presente. Default 2 retries (3 tentativas no total). Caller opta-out\r\n * com `retries: 0`.\r\n */\r\nimport { NeetruError, type NeetruErrorCode } from './errors';\r\nimport type { ResolvedConfig } from './types';\r\n\r\n/** Opções da request HTTP. */\r\nexport interface HttpRequestOptions {\r\n method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\r\n /** Path relativo (ex: `/api/v1/cli/catalog`). Concatenado a `baseUrl`. */\r\n path: string;\r\n /** Query string params (chave → valor primitivo). Valores `undefined` ignorados. */\r\n query?: Record<string, string | number | boolean | undefined>;\r\n /** Body JSON-serializável. Ignorado em GET/DELETE. */\r\n body?: unknown;\r\n /**\r\n * Se true, injeta `Authorization: Bearer <apiKey>`. Lança `missing_api_key`\r\n * se config.apiKey ausente. Default false.\r\n */\r\n requireAuth?: boolean;\r\n /** Cabeçalhos extras. */\r\n headers?: Record<string, string>;\r\n /**\r\n * Número de retries em códigos transientes (`rate_limited`, `server_error`,\r\n * `network_error`). Default 2 (= 3 tentativas total). Caller passa `0` pra\r\n * desativar (útil em operações não-idempotentes específicas).\r\n */\r\n retries?: number;\r\n}\r\n\r\nconst DEFAULT_RETRIES = 2;\r\nconst RETRYABLE_CODES = new Set<NeetruErrorCode>([\r\n 'rate_limited',\r\n 'server_error',\r\n 'network_error',\r\n]);\r\n\r\n/** Backoff exponencial com jitter ±20%. attempt: 0=primeira, 1=primeiro retry, ... */\r\nfunction backoffMs(attempt: number): number {\r\n const base = 200 * Math.pow(4, attempt); // 200ms, 800ms, 3.2s, ...\r\n const jitter = base * 0.2 * (Math.random() * 2 - 1);\r\n return Math.max(50, Math.round(base + jitter));\r\n}\r\n\r\n/**\r\n * Honra `Retry-After` header (segundos ou HTTP-date). Retorna ms ou null se\r\n * inválido. RFC 9110 §10.2.3.\r\n */\r\nfunction parseRetryAfter(value: string | null): number | null {\r\n if (!value) return null;\r\n const secs = Number(value);\r\n if (Number.isFinite(secs) && secs >= 0) return Math.round(secs * 1000);\r\n const dateMs = Date.parse(value);\r\n if (Number.isFinite(dateMs)) {\r\n const delta = dateMs - Date.now();\r\n if (delta > 0) return delta;\r\n }\r\n return null;\r\n}\r\n\r\nfunction sleep(ms: number): Promise<void> {\r\n return new Promise((resolve) => setTimeout(resolve, ms));\r\n}\r\n\r\n/** Mapeamento status → code estável do NeetruError. */\r\nfunction statusToCode(status: number): NeetruErrorCode {\r\n if (status === 401) return 'unauthorized';\r\n if (status === 403) return 'forbidden';\r\n if (status === 404) return 'not_found';\r\n if (status === 422 || status === 400) return 'validation_failed';\r\n if (status === 429) return 'rate_limited';\r\n if (status >= 500) return 'server_error';\r\n return 'unknown';\r\n}\r\n\r\nfunction buildUrl(baseUrl: string, path: string, query?: HttpRequestOptions['query']): string {\r\n // Trim trailing slash em base e leading em path pra evitar `//`.\r\n const base = baseUrl.replace(/\\/+$/, '');\r\n const p = path.startsWith('/') ? path : `/${path}`;\r\n const url = new URL(`${base}${p}`);\r\n if (query) {\r\n for (const [k, v] of Object.entries(query)) {\r\n if (v === undefined) continue;\r\n url.searchParams.set(k, String(v));\r\n }\r\n }\r\n return url.toString();\r\n}\r\n\r\n/** Parse JSON defensivo — retorna `undefined` em body vazio. */\r\nasync function safeJson(res: Response): Promise<unknown> {\r\n const text = await res.text();\r\n if (!text) return undefined;\r\n try {\r\n return JSON.parse(text);\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\n/**\r\n * Executa request HTTP. Em sucesso retorna body parseado; em erro lança\r\n * `NeetruError` com `code` derivado do status. Aplica retry/backoff\r\n * automático em códigos transientes (rate_limited/server_error/network_error)\r\n * conforme `opts.retries` (default 2 = 3 tentativas).\r\n */\r\nexport async function httpRequest<T>(\r\n config: ResolvedConfig,\r\n opts: HttpRequestOptions,\r\n): Promise<T> {\r\n const method = opts.method ?? 'GET';\r\n const url = buildUrl(config.baseUrl, opts.path, opts.query);\r\n const maxRetries = opts.retries ?? DEFAULT_RETRIES;\r\n\r\n const headers: Record<string, string> = {\r\n accept: 'application/json',\r\n ...opts.headers,\r\n };\r\n\r\n if (opts.requireAuth) {\r\n if (!config.apiKey) {\r\n throw new NeetruError(\r\n 'missing_api_key',\r\n 'This operation requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var.',\r\n );\r\n }\r\n headers.authorization = `Bearer ${config.apiKey}`;\r\n }\r\n\r\n // Body só é serializado uma vez — reusado entre tentativas.\r\n const bodyString =\r\n opts.body !== undefined && method !== 'GET' && method !== 'DELETE'\r\n ? JSON.stringify(opts.body)\r\n : undefined;\r\n if (bodyString !== undefined) {\r\n headers['content-type'] = 'application/json';\r\n }\r\n\r\n let lastError: NeetruError | null = null;\r\n\r\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\r\n const init: RequestInit = { method, headers };\r\n if (bodyString !== undefined) init.body = bodyString;\r\n // BUG-020 fix (2026-05-13): timeout default 30s. AbortSignal.timeout\r\n // requer Node 18+ ou browsers recentes.\r\n init.signal = AbortSignal.timeout(30_000);\r\n\r\n let res: Response;\r\n try {\r\n res = await config.fetch(url, init);\r\n } catch (err) {\r\n const message =\r\n err instanceof DOMException && err.name === 'TimeoutError'\r\n ? 'Network error: timeout after 30s'\r\n : `Network error: ${err instanceof Error ? err.message : 'fetch failed'}`;\r\n lastError = new NeetruError('network_error', message);\r\n if (attempt < maxRetries) {\r\n await sleep(backoffMs(attempt));\r\n continue;\r\n }\r\n throw lastError;\r\n }\r\n\r\n const requestId =\r\n res.headers.get('x-request-id') ?? res.headers.get('x-correlation-id') ?? undefined;\r\n\r\n if (res.ok) {\r\n const parsed = await safeJson(res);\r\n return parsed as T;\r\n }\r\n\r\n const body = (await safeJson(res)) as\r\n | { error?: { code?: string; message?: string } | string }\r\n | undefined;\r\n let code: string = statusToCode(res.status);\r\n let message = `HTTP ${res.status}`;\r\n if (body && typeof body === 'object' && 'error' in body) {\r\n const errField = body.error;\r\n if (typeof errField === 'string') {\r\n message = errField;\r\n } else if (errField && typeof errField === 'object') {\r\n if (typeof errField.code === 'string') code = errField.code;\r\n if (typeof errField.message === 'string') message = errField.message;\r\n }\r\n }\r\n const err = new NeetruError(code, message, res.status, requestId);\r\n lastError = err;\r\n\r\n const isRetryable = RETRYABLE_CODES.has(code as NeetruErrorCode);\r\n if (isRetryable && attempt < maxRetries) {\r\n const retryAfter = parseRetryAfter(res.headers.get('retry-after'));\r\n const delay = retryAfter ?? backoffMs(attempt);\r\n await sleep(delay);\r\n continue;\r\n }\r\n throw err;\r\n }\r\n\r\n // Unreachable — loop sempre retorna ou lança. Safety net.\r\n throw lastError ?? new NeetruError('unknown', 'unexpected httpRequest exit');\r\n}\r\n","/**\r\n * Usage namespace — track usage events e ler quotas (v0.2).\r\n *\r\n * Endpoints consumidos (em prod):\r\n * - `POST /sdk/v1/usage/record` — record event\r\n * - `GET /sdk/v1/usage/quota?metric=X` — ler quota da metric\r\n *\r\n * Comportamento por env:\r\n * - `dev` → MockUsage (in-memory, retornado pelo factory).\r\n * - `workspace`/`prod` → HTTP real.\r\n *\r\n * Diferença vs `telemetry`: telemetry é evento analítico (event sourcing pra\r\n * BigQuery). usage é meterado (consumo cobrado), com quota explícita por plano.\r\n */\r\nimport { NeetruError } from './errors';\r\nimport { httpRequest } from './http';\r\nimport type { ResolvedConfig, UsageNamespace, UsageQuota } from './types';\r\n\r\ninterface RawUsageAck {\r\n ok?: boolean;\r\n recordId?: string;\r\n}\r\n\r\ninterface RawUsageQuota {\r\n metric?: string;\r\n used?: number;\r\n limit?: number;\r\n resetsAt?: string;\r\n plan?: string;\r\n}\r\n\r\nfunction toQuota(metric: string, raw: unknown): UsageQuota {\r\n if (!raw || typeof raw !== 'object') {\r\n throw new NeetruError('invalid_response', 'Quota response is not an object');\r\n }\r\n const r = raw as RawUsageQuota;\r\n if (typeof r.used !== 'number' || typeof r.limit !== 'number') {\r\n throw new NeetruError('invalid_response', 'Quota response missing used/limit numbers');\r\n }\r\n return {\r\n metric: typeof r.metric === 'string' ? r.metric : metric,\r\n used: r.used,\r\n limit: r.limit,\r\n resetsAt: typeof r.resetsAt === 'string' ? r.resetsAt : undefined,\r\n plan: typeof r.plan === 'string' ? r.plan : undefined,\r\n };\r\n}\r\n\r\nexport function createUsageNamespace(config: ResolvedConfig): UsageNamespace {\r\n return {\r\n /**\r\n * Persiste um evento de usage. Em dev (mocks ativos via factory) só loga.\r\n * Em workspace/prod chama POST /sdk/v1/usage/record.\r\n */\r\n async track(\r\n event: string,\r\n properties?: Record<string, string | number | boolean | null>,\r\n ): Promise<{ ok: true }> {\r\n if (!event || typeof event !== 'string') {\r\n throw new NeetruError('validation_failed', 'event name is required');\r\n }\r\n if (event.length > 128) {\r\n throw new NeetruError('validation_failed', 'event name max 128 chars');\r\n }\r\n\r\n const body: Record<string, unknown> = { event };\r\n if (properties && typeof properties === 'object') body.properties = properties;\r\n\r\n const raw = await httpRequest<RawUsageAck>(config, {\r\n method: 'POST',\r\n path: '/sdk/v1/usage/record',\r\n body,\r\n requireAuth: true,\r\n });\r\n\r\n if (!raw || raw.ok !== true) {\r\n throw new NeetruError('invalid_response', 'Usage record response missing ok');\r\n }\r\n return { ok: true };\r\n },\r\n\r\n /**\r\n * Lê quota atual de uma métrica. Cacheável (caller decide), SDK não cacheia.\r\n */\r\n async getQuota(metric: string): Promise<UsageQuota> {\r\n if (!metric || typeof metric !== 'string') {\r\n throw new NeetruError('validation_failed', 'metric is required');\r\n }\r\n const raw = await httpRequest<RawUsageQuota>(config, {\r\n method: 'GET',\r\n path: '/sdk/v1/usage/quota',\r\n query: { metric },\r\n requireAuth: true,\r\n });\r\n return toQuota(metric, raw);\r\n },\r\n\r\n /**\r\n * v0.3 — Reporta consumo metered. Hit no endpoint canônico Sprint 7.\r\n */\r\n async report(\r\n resource: string,\r\n qty: number = 1,\r\n options?: { productId?: string; tenantId?: string },\r\n ) {\r\n if (!resource || typeof resource !== 'string') {\r\n throw new NeetruError('validation_failed', 'resource is required');\r\n }\r\n if (!Number.isFinite(qty) || qty <= 0) {\r\n throw new NeetruError('validation_failed', 'qty must be positive integer');\r\n }\r\n const productId = options?.productId ?? config.productId;\r\n const tenantId = options?.tenantId ?? config.tenantId;\r\n if (!productId) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n 'productId required (pass to options or set on createNeetruClient)',\r\n );\r\n }\r\n if (!tenantId) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n 'tenantId required (pass to options or set on createNeetruClient)',\r\n );\r\n }\r\n\r\n const raw = await httpRequest<{\r\n ok?: boolean;\r\n counterId?: string;\r\n value?: number;\r\n limit?: number;\r\n remaining?: number;\r\n status?: string;\r\n }>(config, {\r\n method: 'POST',\r\n path: '/sdk/v1/usage/record',\r\n body: { productId, tenantId, resource, qty: Math.floor(qty) },\r\n requireAuth: true,\r\n });\r\n if (!raw || raw.ok !== true) {\r\n throw new NeetruError('invalid_response', 'usage.report response missing ok');\r\n }\r\n return {\r\n ok: true as const,\r\n counterId: raw.counterId,\r\n value: raw.value,\r\n limit: raw.limit,\r\n remaining: raw.remaining,\r\n status: raw.status,\r\n };\r\n },\r\n\r\n /**\r\n * v0.3 — Verifica entitlement de um resource via GET /sdk/v1/entitlements.\r\n */\r\n async check(\r\n resource: string,\r\n options?: { productId?: string; tenantId?: string },\r\n ) {\r\n if (!resource || typeof resource !== 'string') {\r\n throw new NeetruError('validation_failed', 'resource is required');\r\n }\r\n const productId = options?.productId ?? config.productId;\r\n const tenantId = options?.tenantId ?? config.tenantId;\r\n if (!productId || !tenantId) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n 'productId and tenantId required',\r\n );\r\n }\r\n const raw = await httpRequest<{\r\n allowed?: boolean;\r\n reason?: string;\r\n remaining?: number;\r\n limit?: number;\r\n planId?: string | null;\r\n planFeatures?: string[];\r\n }>(config, {\r\n method: 'GET',\r\n path: '/sdk/v1/entitlements',\r\n query: { productId, tenantId, feature: resource },\r\n requireAuth: true,\r\n });\r\n if (!raw || typeof raw.allowed !== 'boolean') {\r\n throw new NeetruError('invalid_response', 'usage.check response missing allowed');\r\n }\r\n return {\r\n allowed: raw.allowed,\r\n reason: raw.reason,\r\n remaining: raw.remaining,\r\n limit: raw.limit,\r\n planId: raw.planId,\r\n planFeatures: raw.planFeatures,\r\n };\r\n },\r\n };\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/idempotency.ts","../src/http.ts","../src/validators.ts","../src/usage.ts"],"names":["err","message"],"mappings":";;;AAsCO,IAAM,WAAA,GAAN,MAAM,YAAA,SAAoB,KAAA,CAAM;AAAA,EACrB,IAAA;AAAA,EACA,MAAA;AAAA,EACA,SAAA;AAAA,EAEhB,WAAA,CACE,IAAA,EACA,OAAA,EACA,MAAA,EACA,SAAA,EACA;AACA,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,aAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AAEjB,IAAA,MAAA,CAAO,cAAA,CAAe,IAAA,EAAM,YAAA,CAAY,SAAS,CAAA;AAAA,EACnD;AACF,CAAA;;;ACvBO,SAAS,sBAAA,GAAiC;AAG/C,EAAA,MAAM,YACJ,OAAO,UAAA,KAAe,eAAe,QAAA,IAAY,UAAA,GAC5C,WAA0D,MAAA,GAC3D,MAAA;AACN,EAAA,IAAI,SAAA,IAAa,OAAO,SAAA,CAAU,UAAA,KAAe,UAAA,EAAY;AAC3D,IAAA,OAAO,UAAU,UAAA,EAAW;AAAA,EAC9B;AACA,EAAA,MAAM,IAAI,WAAA;AAAA,IACR,qBAAA;AAAA,IACA;AAAA,GACF;AACF;;;ACiBA,IAAM,oBAAA,GAAuB,CAAA;AAC7B,IAAM,qBAAA,GAAwB,CAAA;AAC9B,IAAM,+BAAe,IAAI,GAAA,CAAI,CAAC,KAAA,EAAO,MAAM,CAAC,CAAA;AAC5C,IAAM,eAAA,uBAAsB,GAAA,CAAqB;AAAA,EAC/C,cAAA;AAAA,EACA,cAAA;AAAA,EACA;AACF,CAAC,CAAA;AAGD,SAAS,UAAU,OAAA,EAAyB;AAC1C,EAAA,MAAM,IAAA,GAAO,GAAA,GAAM,IAAA,CAAK,GAAA,CAAI,GAAG,OAAO,CAAA;AACtC,EAAA,MAAM,SAAS,IAAA,GAAO,GAAA,IAAO,IAAA,CAAK,MAAA,KAAW,CAAA,GAAI,CAAA,CAAA;AACjD,EAAA,OAAO,KAAK,GAAA,CAAI,EAAA,EAAI,KAAK,KAAA,CAAM,IAAA,GAAO,MAAM,CAAC,CAAA;AAC/C;AAMA,SAAS,gBAAgB,KAAA,EAAqC;AAC5D,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,MAAM,IAAA,GAAO,OAAO,KAAK,CAAA;AACzB,EAAA,IAAI,MAAA,CAAO,QAAA,CAAS,IAAI,CAAA,IAAK,IAAA,IAAQ,GAAG,OAAO,IAAA,CAAK,KAAA,CAAM,IAAA,GAAO,GAAI,CAAA;AACrE,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,KAAK,CAAA;AAC/B,EAAA,IAAI,MAAA,CAAO,QAAA,CAAS,MAAM,CAAA,EAAG;AAC3B,IAAA,MAAM,KAAA,GAAQ,MAAA,GAAS,IAAA,CAAK,GAAA,EAAI;AAChC,IAAA,IAAI,KAAA,GAAQ,GAAG,OAAO,KAAA;AAAA,EACxB;AACA,EAAA,OAAO,IAAA;AACT;AAEA,SAAS,MAAM,EAAA,EAA2B;AACxC,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,YAAY,UAAA,CAAW,OAAA,EAAS,EAAE,CAAC,CAAA;AACzD;AAGA,SAAS,aAAa,MAAA,EAAiC;AACrD,EAAA,IAAI,MAAA,KAAW,KAAK,OAAO,cAAA;AAC3B,EAAA,IAAI,MAAA,KAAW,KAAK,OAAO,WAAA;AAC3B,EAAA,IAAI,MAAA,KAAW,KAAK,OAAO,WAAA;AAC3B,EAAA,IAAI,MAAA,KAAW,GAAA,IAAO,MAAA,KAAW,GAAA,EAAK,OAAO,mBAAA;AAC7C,EAAA,IAAI,MAAA,KAAW,KAAK,OAAO,cAAA;AAC3B,EAAA,IAAI,MAAA,IAAU,KAAK,OAAO,cAAA;AAC1B,EAAA,OAAO,SAAA;AACT;AAEA,SAAS,QAAA,CAAS,OAAA,EAAiB,IAAA,EAAc,KAAA,EAA6C;AAE5F,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AACvC,EAAA,MAAM,IAAI,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,GAAI,IAAA,GAAO,IAAI,IAAI,CAAA,CAAA;AAChD,EAAA,MAAM,MAAM,IAAI,GAAA,CAAI,GAAG,IAAI,CAAA,EAAG,CAAC,CAAA,CAAE,CAAA;AACjC,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,KAAA,MAAW,CAAC,CAAA,EAAG,CAAC,KAAK,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,EAAG;AAC1C,MAAA,IAAI,MAAM,MAAA,EAAW;AACrB,MAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,CAAA,EAAG,MAAA,CAAO,CAAC,CAAC,CAAA;AAAA,IACnC;AAAA,EACF;AACA,EAAA,OAAO,IAAI,QAAA,EAAS;AACtB;AAGA,eAAe,SAAS,GAAA,EAAiC;AACvD,EAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAC5B,EAAA,IAAI,CAAC,MAAM,OAAO,MAAA;AAClB,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,EACxB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,MAAA;AAAA,EACT;AACF;AAQA,eAAsB,WAAA,CACpB,QACA,IAAA,EACY;AACZ,EAAA,MAAM,MAAA,GAAS,KAAK,MAAA,IAAU,KAAA;AAC9B,EAAA,MAAM,MAAM,QAAA,CAAS,MAAA,CAAO,SAAS,IAAA,CAAK,IAAA,EAAM,KAAK,KAAK,CAAA;AAE1D,EAAA,MAAM,cAAA,GAAiB,YAAA,CAAa,GAAA,CAAI,MAAM,IAC1C,oBAAA,GACA,qBAAA;AACJ,EAAA,MAAM,UAAA,GAAa,KAAK,OAAA,IAAW,cAAA;AAEnC,EAAA,MAAM,OAAA,GAAkC;AAAA,IACtC,MAAA,EAAQ,kBAAA;AAAA,IACR,GAAG,IAAA,CAAK;AAAA,GACV;AAEA,EAAA,IAAI,KAAK,WAAA,EAAa;AACpB,IAAA,IAAI,CAAC,OAAO,MAAA,EAAQ;AAClB,MAAA,MAAM,IAAI,WAAA;AAAA,QACR,iBAAA;AAAA,QACA;AAAA,OACF;AAAA,IACF;AACA,IAAA,OAAA,CAAQ,aAAA,GAAgB,CAAA,OAAA,EAAU,MAAA,CAAO,MAAM,CAAA,CAAA;AAAA,EACjD;AAGA,EAAA,IAAI,KAAK,cAAA,EAAgB;AACvB,IAAA,MAAM,MACJ,OAAO,IAAA,CAAK,mBAAmB,QAAA,GAC3B,IAAA,CAAK,iBACL,sBAAA,EAAuB;AAC7B,IAAA,OAAA,CAAQ,iBAAiB,CAAA,GAAI,GAAA;AAAA,EAC/B;AAGA,EAAA,MAAM,UAAA,GACJ,IAAA,CAAK,IAAA,KAAS,MAAA,IAAa,MAAA,KAAW,KAAA,IAAS,MAAA,KAAW,QAAA,GACtD,IAAA,CAAK,SAAA,CAAU,IAAA,CAAK,IAAI,CAAA,GACxB,MAAA;AACN,EAAA,IAAI,eAAe,MAAA,EAAW;AAC5B,IAAA,OAAA,CAAQ,cAAc,CAAA,GAAI,kBAAA;AAAA,EAC5B;AAEA,EAAA,IAAI,SAAA,GAAgC,IAAA;AAEpC,EAAA,KAAA,IAAS,OAAA,GAAU,CAAA,EAAG,OAAA,IAAW,UAAA,EAAY,OAAA,EAAA,EAAW;AACtD,IAAA,MAAM,IAAA,GAAoB,EAAE,MAAA,EAAQ,OAAA,EAAQ;AAC5C,IAAA,IAAI,UAAA,KAAe,MAAA,EAAW,IAAA,CAAK,IAAA,GAAO,UAAA;AAG1C,IAAA,IAAA,CAAK,MAAA,GAAS,WAAA,CAAY,OAAA,CAAQ,GAAM,CAAA;AAExC,IAAA,IAAI,GAAA;AACJ,IAAA,IAAI;AACF,MAAA,GAAA,GAAM,MAAM,MAAA,CAAO,KAAA,CAAM,GAAA,EAAK,IAAI,CAAA;AAAA,IACpC,SAASA,IAAAA,EAAK;AACZ,MAAA,MAAMC,QAAAA,GACJD,IAAAA,YAAe,YAAA,IAAgBA,IAAAA,CAAI,IAAA,KAAS,cAAA,GACxC,kCAAA,GACA,CAAA,eAAA,EAAkBA,IAAAA,YAAe,KAAA,GAAQA,IAAAA,CAAI,OAAA,GAAU,cAAc,CAAA,CAAA;AAC3E,MAAA,SAAA,GAAY,IAAI,WAAA,CAAY,eAAA,EAAiBC,QAAO,CAAA;AACpD,MAAA,IAAI,UAAU,UAAA,EAAY;AACxB,QAAA,MAAM,KAAA,CAAM,SAAA,CAAU,OAAO,CAAC,CAAA;AAC9B,QAAA;AAAA,MACF;AACA,MAAA,MAAM,SAAA;AAAA,IACR;AAEA,IAAA,MAAM,SAAA,GACJ,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,cAAc,KAAK,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,kBAAkB,CAAA,IAAK,MAAA;AAE5E,IAAA,IAAI,IAAI,EAAA,EAAI;AACV,MAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,GAAG,CAAA;AACjC,MAAA,OAAO,MAAA;AAAA,IACT;AAEA,IAAA,MAAM,IAAA,GAAQ,MAAM,QAAA,CAAS,GAAG,CAAA;AAGhC,IAAA,IAAI,IAAA,GAAe,YAAA,CAAa,GAAA,CAAI,MAAM,CAAA;AAC1C,IAAA,IAAI,OAAA,GAAU,CAAA,KAAA,EAAQ,GAAA,CAAI,MAAM,CAAA,CAAA;AAChC,IAAA,IAAI,IAAA,IAAQ,OAAO,IAAA,KAAS,QAAA,IAAY,WAAW,IAAA,EAAM;AACvD,MAAA,MAAM,WAAW,IAAA,CAAK,KAAA;AACtB,MAAA,IAAI,OAAO,aAAa,QAAA,EAAU;AAChC,QAAA,OAAA,GAAU,QAAA;AAAA,MACZ,CAAA,MAAA,IAAW,QAAA,IAAY,OAAO,QAAA,KAAa,QAAA,EAAU;AACnD,QAAA,IAAI,OAAO,QAAA,CAAS,IAAA,KAAS,QAAA,SAAiB,QAAA,CAAS,IAAA;AACvD,QAAA,IAAI,OAAO,QAAA,CAAS,OAAA,KAAY,QAAA,YAAoB,QAAA,CAAS,OAAA;AAAA,MAC/D;AAAA,IACF;AACA,IAAA,MAAM,MAAM,IAAI,WAAA,CAAY,MAAM,OAAA,EAAS,GAAA,CAAI,QAAQ,SAAS,CAAA;AAChE,IAAA,SAAA,GAAY,GAAA;AAEZ,IAAA,MAAM,WAAA,GAAc,eAAA,CAAgB,GAAA,CAAI,IAAuB,CAAA;AAC/D,IAAA,IAAI,WAAA,IAAe,UAAU,UAAA,EAAY;AACvC,MAAA,MAAM,aAAa,eAAA,CAAgB,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,aAAa,CAAC,CAAA;AACjE,MAAA,MAAM,KAAA,GAAQ,UAAA,IAAc,SAAA,CAAU,OAAO,CAAA;AAC7C,MAAA,MAAM,MAAM,KAAK,CAAA;AACjB,MAAA;AAAA,IACF;AACA,IAAA,MAAM,GAAA;AAAA,EACR;AAGA,EAAA,MAAM,SAAA,IAAa,IAAI,WAAA,CAAY,SAAA,EAAW,6BAA6B,CAAA;AAC7E;;;AC1OO,IAAM,SAAA,GAAY,4BAAA;AAYlB,SAAS,gBAAA,CAAiB,OAAe,KAAA,EAAqB;AACnE,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,CAAC,SAAA,CAAU,IAAA,CAAK,KAAK,CAAA,EAAG;AACvD,IAAA,MAAM,IAAI,WAAA;AAAA,MACR,mBAAA;AAAA,MACA,CAAA,EAAG,KAAK,CAAA,YAAA,EAAe,SAAA,CAAU,MAAM,CAAA,8EAAA;AAAA,KACzC;AAAA,EACF;AACF;;;ACNO,SAAS,qBAAqB,MAAA,EAAwC;AAC3E,EAAA,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAaL,MAAM,MAAA,CACJ,QAAA,EACA,GAAA,GAAc,GACd,OAAA,EACA;AACA,MAAA,IAAI,CAAC,QAAA,IAAY,OAAO,QAAA,KAAa,QAAA,EAAU;AAC7C,QAAA,MAAM,IAAI,WAAA,CAAY,mBAAA,EAAqB,sBAAsB,CAAA;AAAA,MACnE;AACA,MAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,GAAG,CAAA,IAAK,OAAO,CAAA,EAAG;AACrC,QAAA,MAAM,IAAI,WAAA,CAAY,mBAAA,EAAqB,8BAA8B,CAAA;AAAA,MAC3E;AACA,MAAA,MAAM,SAAA,GAAY,OAAA,EAAS,SAAA,IAAa,MAAA,CAAO,SAAA;AAC/C,MAAA,MAAM,QAAA,GAAW,OAAA,EAAS,QAAA,IAAY,MAAA,CAAO,QAAA;AAC7C,MAAA,IAAI,CAAC,SAAA,EAAW;AACd,QAAA,MAAM,IAAI,WAAA;AAAA,UACR,mBAAA;AAAA,UACA;AAAA,SACF;AAAA,MACF;AACA,MAAA,IAAI,CAAC,QAAA,EAAU;AACb,QAAA,MAAM,IAAI,WAAA;AAAA,UACR,mBAAA;AAAA,UACA;AAAA,SACF;AAAA,MACF;AAEA,MAAA,gBAAA,CAAiB,aAAa,SAAS,CAAA;AACvC,MAAA,gBAAA,CAAiB,YAAY,QAAQ,CAAA;AAErC,MAAA,MAAM,GAAA,GAAM,MAAM,WAAA,CAOf,MAAA,EAAQ;AAAA,QACT,MAAA,EAAQ,MAAA;AAAA,QACR,IAAA,EAAM,0BAAA;AAAA,QACN,IAAA,EAAM,EAAE,SAAA,EAAW,QAAA,EAAU,UAAU,GAAA,EAAK,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,EAAE;AAAA,QAC5D,WAAA,EAAa,IAAA;AAAA,QACb,cAAA,EAAgB;AAAA,OACjB,CAAA;AACD,MAAA,IAAI,CAAC,GAAA,IAAO,GAAA,CAAI,EAAA,KAAO,IAAA,EAAM;AAC3B,QAAA,MAAM,IAAI,WAAA,CAAY,kBAAA,EAAoB,kCAAkC,CAAA;AAAA,MAC9E;AACA,MAAA,OAAO;AAAA,QACL,EAAA,EAAI,IAAA;AAAA,QACJ,WAAW,GAAA,CAAI,SAAA;AAAA,QACf,OAAO,GAAA,CAAI,KAAA;AAAA,QACX,OAAO,GAAA,CAAI,KAAA;AAAA,QACX,WAAW,GAAA,CAAI,SAAA;AAAA,QACf,QAAQ,GAAA,CAAI;AAAA,OACd;AAAA,IACF,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQA,MAAM,KAAA,CACJ,QAAA,EACA,OAAA,EACA;AACA,MAAA,IAAI,CAAC,QAAA,IAAY,OAAO,QAAA,KAAa,QAAA,EAAU;AAC7C,QAAA,MAAM,IAAI,WAAA,CAAY,mBAAA,EAAqB,sBAAsB,CAAA;AAAA,MACnE;AACA,MAAA,MAAM,SAAA,GAAY,OAAA,EAAS,SAAA,IAAa,MAAA,CAAO,SAAA;AAC/C,MAAA,MAAM,QAAA,GAAW,OAAA,EAAS,QAAA,IAAY,MAAA,CAAO,QAAA;AAC7C,MAAA,IAAI,CAAC,SAAA,IAAa,CAAC,QAAA,EAAU;AAC3B,QAAA,MAAM,IAAI,WAAA;AAAA,UACR,mBAAA;AAAA,UACA;AAAA,SACF;AAAA,MACF;AAEA,MAAA,gBAAA,CAAiB,aAAa,SAAS,CAAA;AACvC,MAAA,gBAAA,CAAiB,YAAY,QAAQ,CAAA;AAErC,MAAA,MAAM,GAAA,GAAM,MAAM,WAAA,CAQf,MAAA,EAAQ;AAAA,QACT,MAAA,EAAQ,KAAA;AAAA,QACR,IAAA,EAAM,0BAAA;AAAA,QACN,KAAA,EAAO,EAAE,SAAA,EAAW,QAAA,EAAU,SAAS,QAAA,EAAS;AAAA,QAChD,WAAA,EAAa;AAAA,OACd,CAAA;AACD,MAAA,IAAI,CAAC,GAAA,IAAO,OAAO,GAAA,CAAI,YAAY,SAAA,EAAW;AAC5C,QAAA,MAAM,IAAI,WAAA,CAAY,kBAAA,EAAoB,sCAAsC,CAAA;AAAA,MAClF;AAGA,MAAA,MAAM,WACJ,GAAA,CAAI,QAAA,KAAa,GAAA,CAAI,MAAA,KAAW,mBAAmB,UAAA,GAAa,IAAA,CAAA;AAClE,MAAA,OAAO;AAAA,QACL,SAAS,GAAA,CAAI,OAAA;AAAA,QACb,QAAQ,GAAA,CAAI,MAAA;AAAA,QACZ,WAAW,GAAA,CAAI,SAAA;AAAA,QACf,OAAO,GAAA,CAAI,KAAA;AAAA,QACX,QAAQ,GAAA,CAAI,MAAA;AAAA,QACZ,cAAc,GAAA,CAAI,YAAA;AAAA,QAClB;AAAA,OACF;AAAA,IACF;AAAA,GACF;AACF","file":"usage.cjs","sourcesContent":["/**\r\n * Erros tipados do SDK.\r\n *\r\n * Todo erro lançado pelo SDK estende `NeetruError` — caller pode discriminar\r\n * por `.code` (string estável) sem parsing de message.\r\n */\r\n\r\n/** Códigos de erro estáveis do SDK. Adicionar aqui requer minor bump. */\r\nexport type NeetruErrorCode =\r\n | 'invalid_config'\r\n | 'missing_api_key'\r\n | 'unauthorized'\r\n | 'forbidden'\r\n | 'not_found'\r\n | 'rate_limited'\r\n | 'validation_failed'\r\n | 'network_error'\r\n | 'invalid_response'\r\n | 'server_error'\r\n | 'token_expired'\r\n | 'token_invalid_signature'\r\n | 'token_invalid_audience'\r\n | 'token_invalid_issuer'\r\n /** 3.0: runtime não expõe API necessária (ex: WebCrypto ausente). */\r\n | 'runtime_unsupported'\r\n | 'unknown';\r\n\r\n/**\r\n * Erro tipado padrão do SDK. Sempre lançado em vez de Error genérico.\r\n *\r\n * @example\r\n * ```ts\r\n * try { await client.catalog.list(); }\r\n * catch (e) {\r\n * if (e instanceof NeetruError && e.code === 'rate_limited') retry();\r\n * }\r\n * ```\r\n */\r\nexport class NeetruError extends Error {\r\n public readonly code: NeetruErrorCode | string;\r\n public readonly status?: number;\r\n public readonly requestId?: string;\r\n\r\n constructor(\r\n code: NeetruErrorCode | string,\r\n message: string,\r\n status?: number,\r\n requestId?: string,\r\n ) {\r\n super(message);\r\n this.name = 'NeetruError';\r\n this.code = code;\r\n this.status = status;\r\n this.requestId = requestId;\r\n // Preserva o prototype chain ao herdar de Error (downlevel quirk de TS).\r\n Object.setPrototypeOf(this, NeetruError.prototype);\r\n }\r\n}\r\n","/**\n * Idempotency-Key helper — gera UUID v4 cripto-seguro para identificar\n * mutations não-idempotentes (`usage.report`, `support.createTicket`,\n * `notifications.send`, `webhooks.test`).\n *\n * Estratégia 3.0:\n * - SDK gera UUID v4 por chamada via `randomUUID()` (WebCrypto.subtle não\n * necessário aqui — `crypto.randomUUID()` está em Node 14.17+ e em todos\n * os browsers/Edge runtimes modernos).\n * - Header `Idempotency-Key: <uuid>` é injetado pelo `httpRequest` quando\n * `opts.idempotencyKey === true` OU caller passa explicit `opts.idempotencyKeyValue`.\n * - Core dedup em coleção `usage_idempotency_keys/{key}` TTL 24h (vide\n * `docs/CORE_CHANGES_REQUIRED.md` §2). Se Core ainda não dedupa, o header\n * é benign (ignorado).\n *\n * Defesa em camadas: combinado com `retries: 0` em POSTs (vide http.ts), o\n * risco de double-write em transientes vai pra zero.\n */\nimport { NeetruError } from './errors';\n\n/**\n * Gera UUID v4 criptograficamente seguro. Universal: Node 14.17+, browsers\n * modernos, Edge runtimes (Workers/Vercel Edge/Cloudflare).\n *\n * Fallback puro-JS removido em 3.0 — `crypto.randomUUID()` é ubíquo desde\n * 2022. Se runtime não tem, lança `NeetruError('runtime_unsupported')`\n * (consumer deve polyfillar).\n *\n * **L-01 fix (Opus review 2026-05-27):** antes lançava `Error` cru, que\n * não era pego por `catch (err) { if (err instanceof NeetruError) ... }`\n * dos consumers. Agora throw canonical.\n *\n * @throws NeetruError('runtime_unsupported') se `crypto.randomUUID` não disponível.\n */\nexport function generateIdempotencyKey(): string {\n // WebCrypto (browser/Edge) e Node 14.17+ expõem `crypto.randomUUID()`.\n // Node anterior precisa de `node:crypto` mas SDK 3.0 exige Node ≥20 (vide engines).\n const cryptoObj: { randomUUID?: () => string } | undefined =\n typeof globalThis !== 'undefined' && 'crypto' in globalThis\n ? (globalThis as { crypto?: { randomUUID?: () => string } }).crypto\n : undefined;\n if (cryptoObj && typeof cryptoObj.randomUUID === 'function') {\n return cryptoObj.randomUUID();\n }\n throw new NeetruError(\n 'runtime_unsupported',\n 'crypto.randomUUID not available in this runtime. SDK 3.0 requires Node ≥20 or modern browser/Edge.',\n );\n}\n\n/** Regex UUID v4 — validação para uso interno. */\nexport const UUID_V4_RE =\n /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n","/**\r\n * HTTP transport interno do SDK.\r\n *\r\n * Responsabilidades:\r\n * - Construir URL absoluta a partir de `baseUrl` + path\r\n * - Injetar Bearer token quando `requireAuth=true`\r\n * - Mapear status HTTP → `NeetruError` com `code` estável\r\n * - Parse defensivo de JSON (não lança em body vazio em 204)\r\n * - Retry/backoff exponencial em `rate_limited` (429), `server_error` (5xx)\r\n * e `network_error` (timeout/falha de rede). Honra `Retry-After` quando\r\n * presente.\r\n *\r\n * Política de retries (mudança breaking 3.0):\r\n * - GET/HEAD: default 2 retries (3 tentativas total) — idempotente, seguro.\r\n * - POST/PUT/PATCH/DELETE: default **0 retries** — mutations podem duplicar\r\n * side effects (ex: `usage.report` incrementa contador). Caller pode\r\n * reativar passando `retries: N` explicitamente ou usar `Idempotency-Key`\r\n * header (gerado automaticamente se `opts.idempotencyKey=true`).\r\n *\r\n * Idempotency-Key (3.0):\r\n * - `opts.idempotencyKey === true` → SDK gera UUID v4 e injeta header.\r\n * - `opts.idempotencyKey === 'uuid-string'` → usa o valor passado.\r\n * - Caller mantém retentos client-side se quiser; Core dedup serverside\r\n * (vide `docs/CORE_CHANGES_REQUIRED.md` §2).\r\n */\r\nimport { NeetruError, type NeetruErrorCode } from './errors';\r\nimport { generateIdempotencyKey } from './idempotency';\r\nimport type { ResolvedConfig } from './types';\r\n\r\n/** Opções da request HTTP. */\r\nexport interface HttpRequestOptions {\r\n method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\r\n /** Path relativo (ex: `/api/v1/cli/catalog`). Concatenado a `baseUrl`. */\r\n path: string;\r\n /** Query string params (chave → valor primitivo). Valores `undefined` ignorados. */\r\n query?: Record<string, string | number | boolean | undefined>;\r\n /** Body JSON-serializável. Ignorado em GET/DELETE. */\r\n body?: unknown;\r\n /**\r\n * Se true, injeta `Authorization: Bearer <apiKey>`. Lança `missing_api_key`\r\n * se config.apiKey ausente. Default false.\r\n */\r\n requireAuth?: boolean;\r\n /** Cabeçalhos extras. */\r\n headers?: Record<string, string>;\r\n /**\r\n * Número de retries em códigos transientes (`rate_limited`, `server_error`,\r\n * `network_error`).\r\n *\r\n * Default (3.0):\r\n * - GET/HEAD: 2 (= 3 tentativas total) — idempotente, seguro.\r\n * - POST/PUT/PATCH/DELETE: 0 — mutations podem duplicar side effects.\r\n *\r\n * Caller pode reativar passando explicitamente: `retries: 2`.\r\n */\r\n retries?: number;\r\n /**\r\n * Injeta `Idempotency-Key` header pra dedup serverside (3.0):\r\n * - `true` → SDK gera UUID v4 cripto-seguro automaticamente.\r\n * - string → usa o valor passado (deve ser UUID v4).\r\n * - `undefined`/`false` → sem header.\r\n */\r\n idempotencyKey?: boolean | string;\r\n}\r\n\r\nconst DEFAULT_RETRIES_READ = 2;\r\nconst DEFAULT_RETRIES_WRITE = 0;\r\nconst READ_METHODS = new Set(['GET', 'HEAD']);\r\nconst RETRYABLE_CODES = new Set<NeetruErrorCode>([\r\n 'rate_limited',\r\n 'server_error',\r\n 'network_error',\r\n]);\r\n\r\n/** Backoff exponencial com jitter ±20%. attempt: 0=primeira, 1=primeiro retry, ... */\r\nfunction backoffMs(attempt: number): number {\r\n const base = 200 * Math.pow(4, attempt); // 200ms, 800ms, 3.2s, ...\r\n const jitter = base * 0.2 * (Math.random() * 2 - 1);\r\n return Math.max(50, Math.round(base + jitter));\r\n}\r\n\r\n/**\r\n * Honra `Retry-After` header (segundos ou HTTP-date). Retorna ms ou null se\r\n * inválido. RFC 9110 §10.2.3.\r\n */\r\nfunction parseRetryAfter(value: string | null): number | null {\r\n if (!value) return null;\r\n const secs = Number(value);\r\n if (Number.isFinite(secs) && secs >= 0) return Math.round(secs * 1000);\r\n const dateMs = Date.parse(value);\r\n if (Number.isFinite(dateMs)) {\r\n const delta = dateMs - Date.now();\r\n if (delta > 0) return delta;\r\n }\r\n return null;\r\n}\r\n\r\nfunction sleep(ms: number): Promise<void> {\r\n return new Promise((resolve) => setTimeout(resolve, ms));\r\n}\r\n\r\n/** Mapeamento status → code estável do NeetruError. */\r\nfunction statusToCode(status: number): NeetruErrorCode {\r\n if (status === 401) return 'unauthorized';\r\n if (status === 403) return 'forbidden';\r\n if (status === 404) return 'not_found';\r\n if (status === 422 || status === 400) return 'validation_failed';\r\n if (status === 429) return 'rate_limited';\r\n if (status >= 500) return 'server_error';\r\n return 'unknown';\r\n}\r\n\r\nfunction buildUrl(baseUrl: string, path: string, query?: HttpRequestOptions['query']): string {\r\n // Trim trailing slash em base e leading em path pra evitar `//`.\r\n const base = baseUrl.replace(/\\/+$/, '');\r\n const p = path.startsWith('/') ? path : `/${path}`;\r\n const url = new URL(`${base}${p}`);\r\n if (query) {\r\n for (const [k, v] of Object.entries(query)) {\r\n if (v === undefined) continue;\r\n url.searchParams.set(k, String(v));\r\n }\r\n }\r\n return url.toString();\r\n}\r\n\r\n/** Parse JSON defensivo — retorna `undefined` em body vazio. */\r\nasync function safeJson(res: Response): Promise<unknown> {\r\n const text = await res.text();\r\n if (!text) return undefined;\r\n try {\r\n return JSON.parse(text);\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\n/**\r\n * Executa request HTTP. Em sucesso retorna body parseado; em erro lança\r\n * `NeetruError` com `code` derivado do status. Aplica retry/backoff\r\n * automático em códigos transientes (rate_limited/server_error/network_error)\r\n * conforme `opts.retries` (default 2 = 3 tentativas).\r\n */\r\nexport async function httpRequest<T>(\r\n config: ResolvedConfig,\r\n opts: HttpRequestOptions,\r\n): Promise<T> {\r\n const method = opts.method ?? 'GET';\r\n const url = buildUrl(config.baseUrl, opts.path, opts.query);\r\n // Default por método: reads tentam 2x, writes 0x. Caller override sempre vence.\r\n const defaultRetries = READ_METHODS.has(method)\r\n ? DEFAULT_RETRIES_READ\r\n : DEFAULT_RETRIES_WRITE;\r\n const maxRetries = opts.retries ?? defaultRetries;\r\n\r\n const headers: Record<string, string> = {\r\n accept: 'application/json',\r\n ...opts.headers,\r\n };\r\n\r\n if (opts.requireAuth) {\r\n if (!config.apiKey) {\r\n throw new NeetruError(\r\n 'missing_api_key',\r\n 'This operation requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var.',\r\n );\r\n }\r\n headers.authorization = `Bearer ${config.apiKey}`;\r\n }\r\n\r\n // Idempotency-Key: gerado por chamada se solicitado. UUID v4 cripto-seguro.\r\n if (opts.idempotencyKey) {\r\n const key =\r\n typeof opts.idempotencyKey === 'string'\r\n ? opts.idempotencyKey\r\n : generateIdempotencyKey();\r\n headers['idempotency-key'] = key;\r\n }\r\n\r\n // Body só é serializado uma vez — reusado entre tentativas.\r\n const bodyString =\r\n opts.body !== undefined && method !== 'GET' && method !== 'DELETE'\r\n ? JSON.stringify(opts.body)\r\n : undefined;\r\n if (bodyString !== undefined) {\r\n headers['content-type'] = 'application/json';\r\n }\r\n\r\n let lastError: NeetruError | null = null;\r\n\r\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\r\n const init: RequestInit = { method, headers };\r\n if (bodyString !== undefined) init.body = bodyString;\r\n // BUG-020 fix (2026-05-13): timeout default 30s. AbortSignal.timeout\r\n // requer Node 18+ ou browsers recentes.\r\n init.signal = AbortSignal.timeout(30_000);\r\n\r\n let res: Response;\r\n try {\r\n res = await config.fetch(url, init);\r\n } catch (err) {\r\n const message =\r\n err instanceof DOMException && err.name === 'TimeoutError'\r\n ? 'Network error: timeout after 30s'\r\n : `Network error: ${err instanceof Error ? err.message : 'fetch failed'}`;\r\n lastError = new NeetruError('network_error', message);\r\n if (attempt < maxRetries) {\r\n await sleep(backoffMs(attempt));\r\n continue;\r\n }\r\n throw lastError;\r\n }\r\n\r\n const requestId =\r\n res.headers.get('x-request-id') ?? res.headers.get('x-correlation-id') ?? undefined;\r\n\r\n if (res.ok) {\r\n const parsed = await safeJson(res);\r\n return parsed as T;\r\n }\r\n\r\n const body = (await safeJson(res)) as\r\n | { error?: { code?: string; message?: string } | string }\r\n | undefined;\r\n let code: string = statusToCode(res.status);\r\n let message = `HTTP ${res.status}`;\r\n if (body && typeof body === 'object' && 'error' in body) {\r\n const errField = body.error;\r\n if (typeof errField === 'string') {\r\n message = errField;\r\n } else if (errField && typeof errField === 'object') {\r\n if (typeof errField.code === 'string') code = errField.code;\r\n if (typeof errField.message === 'string') message = errField.message;\r\n }\r\n }\r\n const err = new NeetruError(code, message, res.status, requestId);\r\n lastError = err;\r\n\r\n const isRetryable = RETRYABLE_CODES.has(code as NeetruErrorCode);\r\n if (isRetryable && attempt < maxRetries) {\r\n const retryAfter = parseRetryAfter(res.headers.get('retry-after'));\r\n const delay = retryAfter ?? backoffMs(attempt);\r\n await sleep(delay);\r\n continue;\r\n }\r\n throw err;\r\n }\r\n\r\n // Unreachable — loop sempre retorna ou lança. Safety net.\r\n throw lastError ?? new NeetruError('unknown', 'unexpected httpRequest exit');\r\n}\r\n","/**\n * Validators centralizados pra inputs do SDK que precisam casar com\n * regex do Core.\n *\n * **M-03 fix (Opus review 2026-05-27):** o Core rejeita productId que\n * não bate `/^[a-z0-9][a-z0-9_-]{0,62}$/` com 400 `validation_failed`.\n * O SDK não pré-validava — consumer recebia erro só em runtime de\n * produção, depois do round-trip de rede. Agora SDK throws localmente\n * antes do fetch.\n */\nimport { NeetruError } from './errors';\n\n/**\n * Regex canonical de IDs no Neetru Core (productId, tenantId, etc).\n * Espelha `SDK_ID_RE` em `neetru_core/src/lib/schemas/sdk.ts`.\n */\nexport const SDK_ID_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/;\n\n/**\n * Throws `NeetruError('validation_failed')` se `value` não bater no\n * formato canonical.\n *\n * @example\n * ```ts\n * assertValidSdkId('productId', 'gestovendas'); // ok\n * assertValidSdkId('productId', 'GestoVendas'); // throws\n * ```\n */\nexport function assertValidSdkId(field: string, value: string): void {\n if (typeof value !== 'string' || !SDK_ID_RE.test(value)) {\n throw new NeetruError(\n 'validation_failed',\n `${field} must match ${SDK_ID_RE.source} (lowercase alphanumeric, _, -; first char must be alphanumeric; max 63 chars)`,\n );\n }\n}\n","/**\n * Usage namespace — meterar consumo e verificar entitlements (3.0 — breaking).\n *\n * **Mudanças 3.0:**\n * - Paths canônicos `/api/sdk/v1/*` (eram `/sdk/v1/*` errados).\n * - `track(event, props)` **REMOVIDO** — Core não aceita esse shape; era\n * vaporware testado contra mock fetch. Use `report(resource, qty, opts)`.\n * - `getQuota(metric)` **REMOVIDO** — endpoint `/usage/quota` nunca existiu\n * no Core. `report()` já devolve `{value, limit, remaining}` em todo POST.\n * - `report()` e `check()` **agora exigem** `productId` + `tenantId`\n * (no contexto do client OU passados nas options).\n * - `report()` envia `Idempotency-Key` automaticamente (anti dup de\n * incremento em retry transient — Core dedup TTL 24h).\n *\n * Endpoints consumidos:\n * - `POST /api/sdk/v1/usage/record` — `{productId, tenantId, resource, qty}`.\n * Incrementa `usage_counters/{tenantId}_{productId}_{resource}_{yyyymm}.value`.\n * - `GET /api/sdk/v1/entitlements?productId&tenantId&feature` — read-only\n * check de plano + remaining (cache HTTP 60s).\n *\n * Comportamento por env:\n * - `dev` → MockUsage (in-memory, retornado pelo factory).\n * - `workspace`/`prod` → HTTP real.\n */\nimport { NeetruError } from './errors';\nimport { httpRequest } from './http';\nimport { assertValidSdkId } from './validators';\nimport type { ResolvedConfig, UsageNamespace } from './types';\n\nexport function createUsageNamespace(config: ResolvedConfig): UsageNamespace {\n return {\n /**\n * Reporta consumo metered de um resource. Hit no endpoint canônico\n * `POST /api/sdk/v1/usage/record`. Idempotency-Key gerado por chamada;\n * Core dedup serverside TTL 24h.\n *\n * @example\n * ```ts\n * await client.usage.report('api_call', 1);\n * // ou explicit:\n * await client.usage.report('api_call', 5, { productId: 'gestovendas', tenantId: 't-acme' });\n * ```\n */\n async report(\n resource: string,\n qty: number = 1,\n options?: { productId?: string; tenantId?: string },\n ) {\n if (!resource || typeof resource !== 'string') {\n throw new NeetruError('validation_failed', 'resource is required');\n }\n if (!Number.isFinite(qty) || qty <= 0) {\n throw new NeetruError('validation_failed', 'qty must be positive integer');\n }\n const productId = options?.productId ?? config.productId;\n const tenantId = options?.tenantId ?? config.tenantId;\n if (!productId) {\n throw new NeetruError(\n 'validation_failed',\n 'productId required (pass to options or set on createNeetruClient)',\n );\n }\n if (!tenantId) {\n throw new NeetruError(\n 'validation_failed',\n 'tenantId required (pass to options or set on createNeetruClient)',\n );\n }\n // M-03: valida formato canonical (case-insensitive Core já rejeitaria com 400).\n assertValidSdkId('productId', productId);\n assertValidSdkId('tenantId', tenantId);\n\n const raw = await httpRequest<{\n ok?: boolean;\n counterId?: string;\n value?: number;\n limit?: number;\n remaining?: number;\n status?: string;\n }>(config, {\n method: 'POST',\n path: '/api/sdk/v1/usage/record',\n body: { productId, tenantId, resource, qty: Math.floor(qty) },\n requireAuth: true,\n idempotencyKey: true,\n });\n if (!raw || raw.ok !== true) {\n throw new NeetruError('invalid_response', 'usage.report response missing ok');\n }\n return {\n ok: true as const,\n counterId: raw.counterId,\n value: raw.value,\n limit: raw.limit,\n remaining: raw.remaining,\n status: raw.status,\n };\n },\n\n /**\n * Verifica entitlement de um resource via `GET /api/sdk/v1/entitlements`.\n * Resposta inclui `allowed`, `reason` ('granted' | 'not_subscribed' |\n * 'subscription_inactive' | 'feature_not_in_plan' | 'limit_exceeded'),\n * `remaining`, `limit`, `planId`, `planFeatures`.\n */\n async check(\n resource: string,\n options?: { productId?: string; tenantId?: string },\n ) {\n if (!resource || typeof resource !== 'string') {\n throw new NeetruError('validation_failed', 'resource is required');\n }\n const productId = options?.productId ?? config.productId;\n const tenantId = options?.tenantId ?? config.tenantId;\n if (!productId || !tenantId) {\n throw new NeetruError(\n 'validation_failed',\n 'productId and tenantId required (pass to options or set on createNeetruClient)',\n );\n }\n // M-03: valida formato canonical antes do fetch.\n assertValidSdkId('productId', productId);\n assertValidSdkId('tenantId', tenantId);\n\n const raw = await httpRequest<{\n allowed?: boolean;\n reason?: string;\n remaining?: number;\n limit?: number;\n planId?: string | null;\n planFeatures?: string[];\n behavior?: 'readonly' | null;\n }>(config, {\n method: 'GET',\n path: '/api/sdk/v1/entitlements',\n query: { productId, tenantId, feature: resource },\n requireAuth: true,\n });\n if (!raw || typeof raw.allowed !== 'boolean') {\n throw new NeetruError('invalid_response', 'usage.check response missing allowed');\n }\n // Infere behavior='readonly' quando reason='limit_exceeded' e Core\n // ainda não devolve o field explicitamente (vide CORE_CHANGES_REQUIRED §4).\n const behavior =\n raw.behavior ?? (raw.reason === 'limit_exceeded' ? 'readonly' : null);\n return {\n allowed: raw.allowed,\n reason: raw.reason,\n remaining: raw.remaining,\n limit: raw.limit,\n planId: raw.planId,\n planFeatures: raw.planFeatures,\n behavior,\n };\n },\n };\n}\n"]}
package/dist/usage.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { H as ResolvedConfig, Z as UsageNamespace } from './types-sGGN1vkg.cjs';
1
+ import { I as ResolvedConfig, _ as UsageNamespace } from './types-DLOvkeAP.cjs';
2
2
  import './collection-ref-DqAAhuhX.cjs';
3
3
  import '@neetru/realtime-protocol';
4
4
  import 'drizzle-orm/node-postgres';
package/dist/usage.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { H as ResolvedConfig, Z as UsageNamespace } from './types-CSC-RIdS.js';
1
+ import { I as ResolvedConfig, _ as UsageNamespace } from './types-dw19bdID.js';
2
2
  import './collection-ref-DqAAhuhX.js';
3
3
  import '@neetru/realtime-protocol';
4
4
  import 'drizzle-orm/node-postgres';
package/dist/usage.mjs CHANGED
@@ -13,8 +13,22 @@ var NeetruError = class _NeetruError extends Error {
13
13
  }
14
14
  };
15
15
 
16
+ // src/idempotency.ts
17
+ function generateIdempotencyKey() {
18
+ const cryptoObj = typeof globalThis !== "undefined" && "crypto" in globalThis ? globalThis.crypto : void 0;
19
+ if (cryptoObj && typeof cryptoObj.randomUUID === "function") {
20
+ return cryptoObj.randomUUID();
21
+ }
22
+ throw new NeetruError(
23
+ "runtime_unsupported",
24
+ "crypto.randomUUID not available in this runtime. SDK 3.0 requires Node \u226520 or modern browser/Edge."
25
+ );
26
+ }
27
+
16
28
  // src/http.ts
17
- var DEFAULT_RETRIES = 2;
29
+ var DEFAULT_RETRIES_READ = 2;
30
+ var DEFAULT_RETRIES_WRITE = 0;
31
+ var READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
18
32
  var RETRYABLE_CODES = /* @__PURE__ */ new Set([
19
33
  "rate_limited",
20
34
  "server_error",
@@ -72,7 +86,8 @@ async function safeJson(res) {
72
86
  async function httpRequest(config, opts) {
73
87
  const method = opts.method ?? "GET";
74
88
  const url = buildUrl(config.baseUrl, opts.path, opts.query);
75
- const maxRetries = opts.retries ?? DEFAULT_RETRIES;
89
+ const defaultRetries = READ_METHODS.has(method) ? DEFAULT_RETRIES_READ : DEFAULT_RETRIES_WRITE;
90
+ const maxRetries = opts.retries ?? defaultRetries;
76
91
  const headers = {
77
92
  accept: "application/json",
78
93
  ...opts.headers
@@ -86,6 +101,10 @@ async function httpRequest(config, opts) {
86
101
  }
87
102
  headers.authorization = `Bearer ${config.apiKey}`;
88
103
  }
104
+ if (opts.idempotencyKey) {
105
+ const key = typeof opts.idempotencyKey === "string" ? opts.idempotencyKey : generateIdempotencyKey();
106
+ headers["idempotency-key"] = key;
107
+ }
89
108
  const bodyString = opts.body !== void 0 && method !== "GET" && method !== "DELETE" ? JSON.stringify(opts.body) : void 0;
90
109
  if (bodyString !== void 0) {
91
110
  headers["content-type"] = "application/json";
@@ -138,66 +157,31 @@ async function httpRequest(config, opts) {
138
157
  throw lastError ?? new NeetruError("unknown", "unexpected httpRequest exit");
139
158
  }
140
159
 
141
- // src/usage.ts
142
- function toQuota(metric, raw) {
143
- if (!raw || typeof raw !== "object") {
144
- throw new NeetruError("invalid_response", "Quota response is not an object");
145
- }
146
- const r = raw;
147
- if (typeof r.used !== "number" || typeof r.limit !== "number") {
148
- throw new NeetruError("invalid_response", "Quota response missing used/limit numbers");
160
+ // src/validators.ts
161
+ var SDK_ID_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/;
162
+ function assertValidSdkId(field, value) {
163
+ if (typeof value !== "string" || !SDK_ID_RE.test(value)) {
164
+ throw new NeetruError(
165
+ "validation_failed",
166
+ `${field} must match ${SDK_ID_RE.source} (lowercase alphanumeric, _, -; first char must be alphanumeric; max 63 chars)`
167
+ );
149
168
  }
150
- return {
151
- metric: typeof r.metric === "string" ? r.metric : metric,
152
- used: r.used,
153
- limit: r.limit,
154
- resetsAt: typeof r.resetsAt === "string" ? r.resetsAt : void 0,
155
- plan: typeof r.plan === "string" ? r.plan : void 0
156
- };
157
169
  }
170
+
171
+ // src/usage.ts
158
172
  function createUsageNamespace(config) {
159
173
  return {
160
174
  /**
161
- * Persiste um evento de usage. Em dev (mocks ativos via factory) só loga.
162
- * Em workspace/prod chama POST /sdk/v1/usage/record.
163
- */
164
- async track(event, properties) {
165
- if (!event || typeof event !== "string") {
166
- throw new NeetruError("validation_failed", "event name is required");
167
- }
168
- if (event.length > 128) {
169
- throw new NeetruError("validation_failed", "event name max 128 chars");
170
- }
171
- const body = { event };
172
- if (properties && typeof properties === "object") body.properties = properties;
173
- const raw = await httpRequest(config, {
174
- method: "POST",
175
- path: "/sdk/v1/usage/record",
176
- body,
177
- requireAuth: true
178
- });
179
- if (!raw || raw.ok !== true) {
180
- throw new NeetruError("invalid_response", "Usage record response missing ok");
181
- }
182
- return { ok: true };
183
- },
184
- /**
185
- * Lê quota atual de uma métrica. Cacheável (caller decide), SDK não cacheia.
186
- */
187
- async getQuota(metric) {
188
- if (!metric || typeof metric !== "string") {
189
- throw new NeetruError("validation_failed", "metric is required");
190
- }
191
- const raw = await httpRequest(config, {
192
- method: "GET",
193
- path: "/sdk/v1/usage/quota",
194
- query: { metric },
195
- requireAuth: true
196
- });
197
- return toQuota(metric, raw);
198
- },
199
- /**
200
- * v0.3 — Reporta consumo metered. Hit no endpoint canônico Sprint 7.
175
+ * Reporta consumo metered de um resource. Hit no endpoint canônico
176
+ * `POST /api/sdk/v1/usage/record`. Idempotency-Key gerado por chamada;
177
+ * Core dedup serverside TTL 24h.
178
+ *
179
+ * @example
180
+ * ```ts
181
+ * await client.usage.report('api_call', 1);
182
+ * // ou explicit:
183
+ * await client.usage.report('api_call', 5, { productId: 'gestovendas', tenantId: 't-acme' });
184
+ * ```
201
185
  */
202
186
  async report(resource, qty = 1, options) {
203
187
  if (!resource || typeof resource !== "string") {
@@ -220,11 +204,14 @@ function createUsageNamespace(config) {
220
204
  "tenantId required (pass to options or set on createNeetruClient)"
221
205
  );
222
206
  }
207
+ assertValidSdkId("productId", productId);
208
+ assertValidSdkId("tenantId", tenantId);
223
209
  const raw = await httpRequest(config, {
224
210
  method: "POST",
225
- path: "/sdk/v1/usage/record",
211
+ path: "/api/sdk/v1/usage/record",
226
212
  body: { productId, tenantId, resource, qty: Math.floor(qty) },
227
- requireAuth: true
213
+ requireAuth: true,
214
+ idempotencyKey: true
228
215
  });
229
216
  if (!raw || raw.ok !== true) {
230
217
  throw new NeetruError("invalid_response", "usage.report response missing ok");
@@ -239,7 +226,10 @@ function createUsageNamespace(config) {
239
226
  };
240
227
  },
241
228
  /**
242
- * v0.3 — Verifica entitlement de um resource via GET /sdk/v1/entitlements.
229
+ * Verifica entitlement de um resource via `GET /api/sdk/v1/entitlements`.
230
+ * Resposta inclui `allowed`, `reason` ('granted' | 'not_subscribed' |
231
+ * 'subscription_inactive' | 'feature_not_in_plan' | 'limit_exceeded'),
232
+ * `remaining`, `limit`, `planId`, `planFeatures`.
243
233
  */
244
234
  async check(resource, options) {
245
235
  if (!resource || typeof resource !== "string") {
@@ -250,25 +240,29 @@ function createUsageNamespace(config) {
250
240
  if (!productId || !tenantId) {
251
241
  throw new NeetruError(
252
242
  "validation_failed",
253
- "productId and tenantId required"
243
+ "productId and tenantId required (pass to options or set on createNeetruClient)"
254
244
  );
255
245
  }
246
+ assertValidSdkId("productId", productId);
247
+ assertValidSdkId("tenantId", tenantId);
256
248
  const raw = await httpRequest(config, {
257
249
  method: "GET",
258
- path: "/sdk/v1/entitlements",
250
+ path: "/api/sdk/v1/entitlements",
259
251
  query: { productId, tenantId, feature: resource },
260
252
  requireAuth: true
261
253
  });
262
254
  if (!raw || typeof raw.allowed !== "boolean") {
263
255
  throw new NeetruError("invalid_response", "usage.check response missing allowed");
264
256
  }
257
+ const behavior = raw.behavior ?? (raw.reason === "limit_exceeded" ? "readonly" : null);
265
258
  return {
266
259
  allowed: raw.allowed,
267
260
  reason: raw.reason,
268
261
  remaining: raw.remaining,
269
262
  limit: raw.limit,
270
263
  planId: raw.planId,
271
- planFeatures: raw.planFeatures
264
+ planFeatures: raw.planFeatures,
265
+ behavior
272
266
  };
273
267
  }
274
268
  };