@neetru/sdk 3.4.5 → 3.4.6

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.
@@ -23,276 +23,29 @@ var NeetruError = class _NeetruError extends Error {
23
23
  }
24
24
  };
25
25
 
26
- // src/idempotency.ts
27
- function generateIdempotencyKey() {
28
- const cryptoObj = typeof globalThis !== "undefined" && "crypto" in globalThis ? globalThis.crypto : void 0;
29
- if (cryptoObj && typeof cryptoObj.randomUUID === "function") {
30
- return cryptoObj.randomUUID();
31
- }
32
- throw new NeetruError(
33
- "runtime_unsupported",
34
- "crypto.randomUUID not available in this runtime. SDK 3.0 requires Node \u226520 or modern browser/Edge."
35
- );
36
- }
37
-
38
- // src/http.ts
39
- var COMPAT_MATRIX = {
40
- // SDK major 3.x requer Core ≥ 2026.05.1 (suporte a X-Neetru-Version header,
41
- // runTransaction write-after-read, verifyWebhookRequest, webhooks 3.0+)
42
- 3: "2026.05.1"
43
- };
44
- function stripRevision(v) {
45
- return v.split("+")[0] ?? v;
46
- }
47
- function compareCalVer(a, b) {
48
- const partsA = stripRevision(a).split(".").map(Number);
49
- const partsB = stripRevision(b).split(".").map(Number);
50
- const len = Math.max(partsA.length, partsB.length);
51
- for (let i = 0; i < len; i++) {
52
- const pa = partsA[i] ?? 0;
53
- const pb = partsB[i] ?? 0;
54
- if (pa !== pb) return pa - pb;
55
- }
56
- return 0;
57
- }
58
- function getSdkMajor() {
59
- try {
60
- const ver = globalThis.__NEETRU_SDK_VERSION__;
61
- if (ver) return parseInt(ver.split(".")[0] ?? "3", 10);
62
- } catch {
63
- }
64
- return 3;
65
- }
66
- var _warnedCoreTooOld = /* @__PURE__ */ new Set();
67
- function checkCoreVersionCompat(coreVersion) {
68
- if (!coreVersion) return;
69
- const sdkMajor = getSdkMajor();
70
- const minCore = COMPAT_MATRIX[sdkMajor];
71
- if (!minCore) return;
72
- const stripped = stripRevision(coreVersion);
73
- if (compareCalVer(stripped, minCore) < 0) {
74
- const key = `${sdkMajor}:${stripped}`;
75
- if (!_warnedCoreTooOld.has(key)) {
76
- _warnedCoreTooOld.add(key);
77
- console.warn(
78
- `[Neetru SDK] Aten\xE7\xE3o: Core vers\xE3o "${coreVersion}" \xE9 anterior ao m\xEDnimo recomendado "${minCore}" para SDK major ${sdkMajor}. Alguns recursos podem n\xE3o funcionar corretamente. Atualize o Core para a vers\xE3o mais recente.`
79
- );
80
- }
81
- }
82
- }
83
- var DEFAULT_RETRIES_READ = 2;
84
- var DEFAULT_RETRIES_WRITE = 0;
85
- var READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
86
- var RETRYABLE_CODES = /* @__PURE__ */ new Set([
87
- "rate_limited",
88
- "server_error",
89
- "network_error"
90
- ]);
91
- function backoffMs(attempt) {
92
- const base = 200 * Math.pow(4, attempt);
93
- const jitter = base * 0.2 * (Math.random() * 2 - 1);
94
- return Math.max(50, Math.round(base + jitter));
95
- }
96
- var MAX_RETRY_AFTER_MS = 6e4;
97
- function parseRetryAfter(value) {
98
- if (!value) return null;
99
- const secs = Number(value);
100
- if (Number.isFinite(secs) && secs >= 0) return Math.min(Math.round(secs * 1e3), MAX_RETRY_AFTER_MS);
101
- const dateMs = Date.parse(value);
102
- if (Number.isFinite(dateMs)) {
103
- const delta = dateMs - Date.now();
104
- if (delta > 0) return Math.min(delta, MAX_RETRY_AFTER_MS);
105
- }
106
- return null;
107
- }
108
- function sleep(ms) {
109
- return new Promise((resolve) => setTimeout(resolve, ms));
110
- }
111
- function statusToCode(status) {
112
- if (status === 401) return "unauthorized";
113
- if (status === 403) return "forbidden";
114
- if (status === 404) return "not_found";
115
- if (status === 409) return "conflict";
116
- if (status === 422 || status === 400) return "validation_failed";
117
- if (status === 429) return "rate_limited";
118
- if (status >= 500) return "server_error";
119
- return "unknown";
120
- }
121
- function buildUrl(baseUrl, path, query) {
122
- const base = baseUrl.replace(/\/+$/, "");
123
- const p = path.startsWith("/") ? path : `/${path}`;
124
- const url = new URL(`${base}${p}`);
125
- if (query) {
126
- for (const [k, v] of Object.entries(query)) {
127
- if (v === void 0) continue;
128
- url.searchParams.set(k, String(v));
129
- }
130
- }
131
- return url.toString();
132
- }
133
- async function safeJson(res) {
134
- const text = await res.text();
135
- if (!text) return void 0;
136
- try {
137
- return JSON.parse(text);
138
- } catch {
139
- return void 0;
140
- }
141
- }
142
- async function httpRequest(config, opts) {
143
- const method = opts.method ?? "GET";
144
- const url = buildUrl(config.baseUrl, opts.path, opts.query);
145
- const defaultRetries = READ_METHODS.has(method) ? DEFAULT_RETRIES_READ : DEFAULT_RETRIES_WRITE;
146
- const maxRetries = opts.retries ?? defaultRetries;
147
- const headers = {
148
- accept: "application/json",
149
- ...opts.headers
150
- };
151
- {
152
- if (!config.apiKey) {
153
- throw new NeetruError(
154
- "missing_api_key",
155
- "This operation requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var."
156
- );
157
- }
158
- headers.authorization = `Bearer ${config.apiKey}`;
159
- }
160
- if (opts.idempotencyKey) {
161
- const key = typeof opts.idempotencyKey === "string" ? opts.idempotencyKey : generateIdempotencyKey();
162
- headers["idempotency-key"] = key;
163
- }
164
- const bodyString = opts.body !== void 0 && method !== "GET" && method !== "DELETE" ? JSON.stringify(opts.body) : void 0;
165
- if (bodyString !== void 0) {
166
- headers["content-type"] = "application/json";
167
- }
168
- let lastError = null;
169
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
170
- const init = { method, headers };
171
- if (bodyString !== void 0) init.body = bodyString;
172
- init.signal = AbortSignal.timeout(3e4);
173
- let res;
174
- try {
175
- res = await config.fetch(url, init);
176
- } catch (err2) {
177
- const message2 = err2 instanceof DOMException && err2.name === "TimeoutError" ? "Network error: timeout after 30s" : `Network error: ${err2 instanceof Error ? err2.message : "fetch failed"}`;
178
- lastError = new NeetruError("network_error", message2);
179
- if (attempt < maxRetries) {
180
- await sleep(backoffMs(attempt));
181
- continue;
182
- }
183
- throw lastError;
184
- }
185
- const requestId = res.headers.get("x-request-id") ?? res.headers.get("x-correlation-id") ?? void 0;
186
- if (res.ok) {
187
- const coreVersion = res.headers.get("x-neetru-version");
188
- if (coreVersion) checkCoreVersionCompat(coreVersion);
189
- const parsed = await safeJson(res);
190
- return parsed;
191
- }
192
- const body = await safeJson(res);
193
- let code = statusToCode(res.status);
194
- let message = `HTTP ${res.status}`;
195
- let reason;
196
- if (body && typeof body === "object") {
197
- const errField = body.error;
198
- if (typeof errField === "string") {
199
- message = errField;
200
- } else if (errField && typeof errField === "object") {
201
- if (typeof errField.code === "string") code = errField.code;
202
- if (typeof errField.message === "string") message = errField.message;
203
- if (typeof errField.reason === "string") reason = errField.reason;
204
- } else {
205
- if (typeof body.code === "string") code = body.code;
206
- if (typeof body.message === "string") message = body.message;
207
- }
208
- if (typeof body.reason === "string") reason = body.reason;
209
- }
210
- const err = new NeetruError(code, message, res.status, requestId, reason);
211
- lastError = err;
212
- const isRetryable = RETRYABLE_CODES.has(code);
213
- if (isRetryable && attempt < maxRetries) {
214
- const retryAfter = parseRetryAfter(res.headers.get("retry-after"));
215
- const delay = retryAfter ?? backoffMs(attempt);
216
- await sleep(delay);
217
- continue;
218
- }
219
- throw err;
220
- }
221
- throw lastError ?? new NeetruError("unknown", "unexpected httpRequest exit");
222
- }
223
-
224
26
  // src/entitlements.ts
225
- function toEntitlementCheck(raw) {
226
- if (!raw || typeof raw !== "object") {
227
- throw new NeetruError("invalid_response", "Entitlement response is not an object");
228
- }
229
- const r = raw;
230
- if (typeof r.allowed !== "boolean") {
231
- throw new NeetruError("invalid_response", "Entitlement response missing `allowed` boolean");
232
- }
233
- return {
234
- allowed: r.allowed,
235
- productSlug: typeof r.productSlug === "string" ? r.productSlug : "",
236
- feature: typeof r.feature === "string" ? r.feature : "",
237
- reason: typeof r.reason === "string" ? r.reason : void 0
238
- };
27
+ var DEPRECATION_MESSAGE = "client.entitlements.check()/checkDetailed() est\xE3o DEPRECADOS \u2014 apontavam para GET /api/v1/sdk/entitlements/check, um endpoint que o Core nunca escreve (sempre retornava not_subscribed). Use client.usage.check(resource, { productId, tenantId }) no lugar \u2014 mesmo resultado (allowed/reason/remaining/limit/behavior), endpoint real.";
28
+ function throwDeprecated() {
29
+ throw new NeetruError("validation_failed", DEPRECATION_MESSAGE);
239
30
  }
240
- var CACHE_TTL_MS = 6e4;
241
- var CACHE_MAX = 100;
242
- function createEntitlementsNamespace(config) {
243
- const cache = /* @__PURE__ */ new Map();
244
- function cacheKey(productSlug, feature) {
245
- return `${productSlug}::${feature}`;
246
- }
247
- function readCache(key) {
248
- const entry = cache.get(key);
249
- if (!entry) return null;
250
- if (entry.expiresAt < Date.now()) {
251
- cache.delete(key);
252
- return null;
253
- }
254
- cache.delete(key);
255
- cache.set(key, entry);
256
- return entry.value;
257
- }
258
- function writeCache(key, value) {
259
- if (cache.size >= CACHE_MAX) {
260
- const oldest = cache.keys().next().value;
261
- if (oldest !== void 0) cache.delete(oldest);
262
- }
263
- cache.set(key, { value, expiresAt: Date.now() + CACHE_TTL_MS });
264
- }
265
- async function checkDetailed(productSlug, feature, opts = {}) {
266
- if (!productSlug) throw new NeetruError("validation_failed", "productSlug is required");
267
- if (!feature) throw new NeetruError("validation_failed", "feature is required");
268
- const key = cacheKey(productSlug, feature);
269
- if (!opts.cacheBust) {
270
- const cached = readCache(key);
271
- if (cached) return cached;
272
- }
273
- const raw = await httpRequest(config, {
274
- method: "GET",
275
- path: "/api/v1/sdk/entitlements/check",
276
- query: { slug: productSlug, feature }});
277
- const result = toEntitlementCheck(raw);
278
- writeCache(key, result);
279
- return result;
280
- }
31
+ function createEntitlementsNamespace(_config) {
281
32
  return {
282
33
  /**
283
- * Verifica se o caller pode usar `feature` no produto `productSlug`.
284
- * Retorno simples: `true` libera, `false` bloqueia. Cache 60s automático.
285
- *
286
- * Use `checkDetailed` se precisar do `reason` pra mensagem de upgrade.
34
+ * @deprecated Endpoint morto no Core — sempre retornava `not_subscribed`.
35
+ * Lança `NeetruError`. Use `client.usage.check(resource, {productId, tenantId})`.
36
+ */
37
+ async check(_productSlug, _feature, _opts) {
38
+ throwDeprecated();
39
+ },
40
+ /**
41
+ * @deprecated Endpoint morto no Core — sempre retornava `not_subscribed`.
42
+ * Lança `NeetruError`. Use `client.usage.check(resource, {productId, tenantId})`.
287
43
  */
288
- async check(productSlug, feature, opts) {
289
- const result = await checkDetailed(productSlug, feature, opts);
290
- return result.allowed;
44
+ async checkDetailed(_productSlug, _feature, _opts) {
45
+ throwDeprecated();
291
46
  },
292
- checkDetailed,
293
- /** Test helper: limpa o cache LRU. */
47
+ /** Test helper legado — no-op (não há mais cache). Mantido por compat. */
294
48
  __resetCache() {
295
- cache.clear();
296
49
  }
297
50
  };
298
51
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/idempotency.ts","../src/http.ts","../src/entitlements.ts"],"names":["err","message"],"mappings":"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","file":"entitlements.mjs","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 /**\r\n * 3.1.3 (bug_c1c96a00): o `state` retornado pelo IdP no callback OIDC não bate com o gerado em\r\n * `signIn` (CSRF). Também usado quando não há transação OIDC pendente.\r\n */\r\n | 'oidc_state_mismatch'\r\n /**\r\n * 3.1.12 (incidente pdv-agiliza 2026-07-10): o `nonce` do `id_token` retornado\r\n * pelo `/token` não bate com o gerado em `signIn` (possível replay). O token\r\n * é descartado sem tocar o storage.\r\n */\r\n | 'oidc_nonce_mismatch'\r\n /**\r\n * 3.1.12: o POST `/api/v1/oauth/token` (troca do authorization_code por\r\n * tokens em `completeSignIn`) falhou — HTTP não-2xx, corpo com `error`, ou\r\n * resposta sem `id_token`.\r\n */\r\n | 'token_exchange_failed'\r\n /** HTTP 409 — colisão de idempotência ou conflito de recurso. */\r\n | 'conflict'\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 * Motivo semântico opcional propagado pelo Core no corpo do erro (campo\r\n * `reason` no TOPO do body, fora de `error`). Usado principalmente pelo 503\r\n * de lease de DB, onde `reason` discrimina o estado do ambiente\r\n * (`suspended` | `archived` | `revoked` | `provisioning` | `degraded` |\r\n * `no_route` | `expired`). Camadas superiores (ex.: `db/sql`) mapeiam isso\r\n * para um `NeetruDbErrorCode` mais específico (recuperável vs não).\r\n */\r\n public readonly reason?: 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 reason?: 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 this.reason = reason;\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 * Idempotency-Key helper — gera UUID v4 cripto-seguro para identificar\r\n * mutations não-idempotentes (`usage.report`, `support.createTicket`,\r\n * `notifications.send`, `webhooks.test`).\r\n *\r\n * Estratégia 3.0:\r\n * - SDK gera UUID v4 por chamada via `randomUUID()` (WebCrypto.subtle não\r\n * necessário aqui — `crypto.randomUUID()` está em Node 14.17+ e em todos\r\n * os browsers/Edge runtimes modernos).\r\n * - Header `Idempotency-Key: <uuid>` é injetado pelo `httpRequest` quando\r\n * `opts.idempotencyKey === true` OU caller passa explicit `opts.idempotencyKeyValue`.\r\n * - Core dedup em coleção `usage_idempotency_keys/{key}` TTL 24h (vide\r\n * `docs/CORE_CHANGES_REQUIRED.md` §2). Se Core ainda não dedupa, o header\r\n * é benign (ignorado).\r\n *\r\n * Defesa em camadas: combinado com `retries: 0` em POSTs (vide http.ts), o\r\n * risco de double-write em transientes vai pra zero.\r\n */\r\nimport { NeetruError } from './errors';\r\n\r\n/**\r\n * Gera UUID v4 criptograficamente seguro. Universal: Node 14.17+, browsers\r\n * modernos, Edge runtimes (Workers/Vercel Edge/Cloudflare).\r\n *\r\n * Fallback puro-JS removido em 3.0 — `crypto.randomUUID()` é ubíquo desde\r\n * 2022. Se runtime não tem, lança `NeetruError('runtime_unsupported')`\r\n * (consumer deve polyfillar).\r\n *\r\n * **L-01 fix (Opus review 2026-05-27):** antes lançava `Error` cru, que\r\n * não era pego por `catch (err) { if (err instanceof NeetruError) ... }`\r\n * dos consumers. Agora throw canonical.\r\n *\r\n * @throws NeetruError('runtime_unsupported') se `crypto.randomUUID` não disponível.\r\n */\r\nexport function generateIdempotencyKey(): string {\r\n // WebCrypto (browser/Edge) e Node 14.17+ expõem `crypto.randomUUID()`.\r\n // Node anterior precisa de `node:crypto` mas SDK 3.0 exige Node ≥20 (vide engines).\r\n const cryptoObj: { randomUUID?: () => string } | undefined =\r\n typeof globalThis !== 'undefined' && 'crypto' in globalThis\r\n ? (globalThis as { crypto?: { randomUUID?: () => string } }).crypto\r\n : undefined;\r\n if (cryptoObj && typeof cryptoObj.randomUUID === 'function') {\r\n return cryptoObj.randomUUID();\r\n }\r\n throw new NeetruError(\r\n 'runtime_unsupported',\r\n 'crypto.randomUUID not available in this runtime. SDK 3.0 requires Node ≥20 or modern browser/Edge.',\r\n );\r\n}\r\n\r\n/** Regex UUID v4 — validação para uso interno. */\r\nexport const UUID_V4_RE =\r\n /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\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.\r\n * - **Captura `X-Neetru-Version`** das respostas e emite `console.warn` quando\r\n * o Core está abaixo da versão mínima para a major do SDK.\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// ─── Compat Matrix SDK↔Core ──────────────────────────────────────────────────\r\n\r\n/**\r\n * Matriz de compatibilidade SDK major → versão mínima do Core.\r\n *\r\n * Formato do Core: CalVer `YYYY.MM.patch+revision` (ex: `2026.05.1+neetru-core-00096-79t`).\r\n * A comparação é feita apenas nas partes `YYYY.MM.patch` (prefixo antes de `+`).\r\n *\r\n * Quando o Core reporta `X-Neetru-Version` abaixo do mínimo para a SDK major\r\n * corrente, o SDK emite `console.warn` uma única vez por sessão de processo.\r\n *\r\n * Atualize esta tabela a cada major de SDK.\r\n */\r\nconst COMPAT_MATRIX: Record<number, string> = {\r\n // SDK major 3.x requer Core ≥ 2026.05.1 (suporte a X-Neetru-Version header,\r\n // runTransaction write-after-read, verifyWebhookRequest, webhooks 3.0+)\r\n 3: '2026.05.1',\r\n};\r\n\r\n/** CalVer sem sufixo `+revision`. Ex: `2026.05.1+abc` → `2026.05.1`. */\r\nfunction stripRevision(v: string): string {\r\n return v.split('+')[0] ?? v;\r\n}\r\n\r\n/**\r\n * Compara dois CalVer (`YYYY.MM.patch`). Retorna negativo se `a < b`,\r\n * positivo se `a > b`, 0 se iguais. Partes não numéricas tratadas como 0.\r\n */\r\nfunction compareCalVer(a: string, b: string): number {\r\n const partsA = stripRevision(a).split('.').map(Number);\r\n const partsB = stripRevision(b).split('.').map(Number);\r\n const len = Math.max(partsA.length, partsB.length);\r\n for (let i = 0; i < len; i++) {\r\n const pa = partsA[i] ?? 0;\r\n const pb = partsB[i] ?? 0;\r\n if (pa !== pb) return pa - pb;\r\n }\r\n return 0;\r\n}\r\n\r\n/** Lê a SDK major a partir da string `VERSION` exportada de `./index`. Fallback 3. */\r\nfunction getSdkMajor(): number {\r\n try {\r\n // Importação estática não disponível aqui (evita ciclo). Usa variável injetada\r\n // em tempo de build pela tsup via `define`. Fallback para 3 (current major).\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const ver = (globalThis as any).__NEETRU_SDK_VERSION__ as string | undefined;\r\n if (ver) return parseInt(ver.split('.')[0] ?? '3', 10);\r\n } catch {\r\n // ignore\r\n }\r\n return 3;\r\n}\r\n\r\n/** Aviso emitido no máximo uma vez por major detectada pra não spammar logs. */\r\nconst _warnedCoreTooOld = new Set<string>();\r\n\r\n/**\r\n * Verifica se a versão do Core (`X-Neetru-Version`) é compatível com a SDK major.\r\n * Emite `console.warn` quando incompatível (uma vez por processo por versão detectada).\r\n */\r\nexport function checkCoreVersionCompat(coreVersion: string): void {\r\n if (!coreVersion) return;\r\n const sdkMajor = getSdkMajor();\r\n const minCore = COMPAT_MATRIX[sdkMajor];\r\n if (!minCore) return; // sem restrição para esta major\r\n const stripped = stripRevision(coreVersion);\r\n if (compareCalVer(stripped, minCore) < 0) {\r\n const key = `${sdkMajor}:${stripped}`;\r\n if (!_warnedCoreTooOld.has(key)) {\r\n _warnedCoreTooOld.add(key);\r\n console.warn(\r\n `[Neetru SDK] Atenção: Core versão \"${coreVersion}\" é anterior ao mínimo recomendado` +\r\n ` \"${minCore}\" para SDK major ${sdkMajor}. Alguns recursos podem não funcionar` +\r\n ` corretamente. Atualize o Core para a versão mais recente.`,\r\n );\r\n }\r\n }\r\n}\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\n * Bug fix: sem teto, um servidor hostil/mal-configurado poderia retornar\r\n * `Retry-After: 86400` (24h) ou uma HTTP-date anos no futuro, pendurando o\r\n * processo indefinidamente. Aplica teto de MAX_RETRY_AFTER_MS em ambos os caminhos.\r\n */\r\nconst MAX_RETRY_AFTER_MS = 60_000; // 60s — teto razoável para retry automático\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.min(Math.round(secs * 1000), MAX_RETRY_AFTER_MS);\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 Math.min(delta, MAX_RETRY_AFTER_MS);\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 === 409) return 'conflict'; // Bug fix: 409 vira 'conflict' (não 'unknown')\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 // Captura X-Neetru-Version e verifica compat com a SDK major.\r\n const coreVersion = res.headers.get('x-neetru-version');\r\n if (coreVersion) checkCoreVersionCompat(coreVersion);\r\n const parsed = await safeJson(res);\r\n return parsed as T;\r\n }\r\n\r\n // O contrato clássico de erro do Core é `{ error: { code, message } }`.\r\n // O 503 de lease de DB (e alguns endpoints novos) coloca `code`/`reason`/\r\n // `message` no TOPO do corpo, SEM envelope `error`. Lemos ambos: preferimos\r\n // `body.error` quando presente; caímos nos campos do topo caso contrário.\r\n // `reason` é sempre considerado no topo (o 503 de lease o coloca lá mesmo\r\n // quando há `error`), pra que a camada de DB discrimine suspended/archived/\r\n // revoked de provisioning/degraded.\r\n const body = (await safeJson(res)) as\r\n | {\r\n error?: { code?: string; message?: string; reason?: string } | string;\r\n code?: string;\r\n reason?: string;\r\n message?: string;\r\n }\r\n | undefined;\r\n let code: string = statusToCode(res.status);\r\n let message = `HTTP ${res.status}`;\r\n let reason: string | undefined;\r\n if (body && typeof body === 'object') {\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 if (typeof errField.reason === 'string') reason = errField.reason;\r\n } else {\r\n // Sem envelope `error` — lê os campos no TOPO do corpo (contrato do 503\r\n // de lease: `{ code, reason }` no topo).\r\n if (typeof body.code === 'string') code = body.code;\r\n if (typeof body.message === 'string') message = body.message;\r\n }\r\n // `reason` no topo tem prioridade final (o 503 de lease o coloca no topo\r\n // mesmo quando também há `error`).\r\n if (typeof body.reason === 'string') reason = body.reason;\r\n }\r\n const err = new NeetruError(code, message, res.status, requestId, reason);\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 * Entitlements — verifica se o portador da apiKey pode usar feature X\r\n * do produto Y.\r\n *\r\n * **3.0**: endpoint canonical é `GET /api/sdk/v1/entitlements?productId&tenantId&feature`.\r\n * Path legacy `/api/v1/sdk/entitlements/check?slug=X&feature=Y` ainda\r\n * funciona no Core (compat 2.x), mas SDK 3.0 padroniza pra canonical.\r\n *\r\n * **Atenção**: `client.entitlements.check(productSlug, feature)` é o\r\n * **legacy boolean API** (sem tenant). Continua funcional contra\r\n * `/api/v1/sdk/entitlements/check` (que segue ativo). Para o check completo\r\n * com `behavior`/`remaining`/`limit`, use `client.usage.check(resource, opts)`\r\n * que aponta para o canonical `/api/sdk/v1/entitlements`.\r\n *\r\n * v1.2 introduz cache LRU em memória (default 60s TTL, 100 entries). Cada\r\n * (productSlug, feature) pareando o `apiKey` é cacheado. Caller pode invalidar\r\n * via `__resetCache()` (test helper) ou via `cacheBust: true` na chamada.\r\n */\r\nimport { NeetruError } from './errors';\r\nimport { httpRequest } from './http';\r\nimport type { EntitlementCheck, ResolvedConfig } from './types';\r\n\r\ninterface RawEntitlementCheck {\r\n allowed?: boolean;\r\n productSlug?: string;\r\n feature?: string;\r\n reason?: string;\r\n}\r\n\r\nfunction toEntitlementCheck(raw: unknown): EntitlementCheck {\r\n if (!raw || typeof raw !== 'object') {\r\n throw new NeetruError('invalid_response', 'Entitlement response is not an object');\r\n }\r\n const r = raw as RawEntitlementCheck;\r\n if (typeof r.allowed !== 'boolean') {\r\n throw new NeetruError('invalid_response', 'Entitlement response missing `allowed` boolean');\r\n }\r\n return {\r\n allowed: r.allowed,\r\n productSlug: typeof r.productSlug === 'string' ? r.productSlug : '',\r\n feature: typeof r.feature === 'string' ? r.feature : '',\r\n reason: typeof r.reason === 'string' ? r.reason : undefined,\r\n };\r\n}\r\n\r\n/** TTL default — 60s. Pareando com tempo de propagação típico de mudança de plano. */\r\nconst CACHE_TTL_MS = 60_000;\r\n/** Máx entries por instância de namespace — LRU. */\r\nconst CACHE_MAX = 100;\r\n\r\ninterface CacheEntry {\r\n value: EntitlementCheck;\r\n expiresAt: number;\r\n}\r\n\r\nexport interface EntitlementsCheckOptions {\r\n /** Se true, ignora cache local e força ida ao servidor. Default false. */\r\n cacheBust?: boolean;\r\n}\r\n\r\nexport function createEntitlementsNamespace(config: ResolvedConfig) {\r\n // Map preserva ordem de inserção — readequa LRU manualmente em hit.\r\n const cache = new Map<string, CacheEntry>();\r\n\r\n function cacheKey(productSlug: string, feature: string): string {\r\n return `${productSlug}::${feature}`;\r\n }\r\n\r\n function readCache(key: string): EntitlementCheck | null {\r\n const entry = cache.get(key);\r\n if (!entry) return null;\r\n if (entry.expiresAt < Date.now()) {\r\n cache.delete(key);\r\n return null;\r\n }\r\n // LRU touch: reinsere no final.\r\n cache.delete(key);\r\n cache.set(key, entry);\r\n return entry.value;\r\n }\r\n\r\n function writeCache(key: string, value: EntitlementCheck): void {\r\n if (cache.size >= CACHE_MAX) {\r\n const oldest = cache.keys().next().value;\r\n if (oldest !== undefined) cache.delete(oldest);\r\n }\r\n cache.set(key, { value, expiresAt: Date.now() + CACHE_TTL_MS });\r\n }\r\n\r\n async function checkDetailed(\r\n productSlug: string,\r\n feature: string,\r\n opts: EntitlementsCheckOptions = {},\r\n ): Promise<EntitlementCheck> {\r\n if (!productSlug) throw new NeetruError('validation_failed', 'productSlug is required');\r\n if (!feature) throw new NeetruError('validation_failed', 'feature is required');\r\n\r\n const key = cacheKey(productSlug, feature);\r\n if (!opts.cacheBust) {\r\n const cached = readCache(key);\r\n if (cached) return cached;\r\n }\r\n\r\n const raw = await httpRequest<RawEntitlementCheck>(config, {\r\n method: 'GET',\r\n path: '/api/v1/sdk/entitlements/check',\r\n query: { slug: productSlug, feature },\r\n requireAuth: true,\r\n });\r\n const result = toEntitlementCheck(raw);\r\n writeCache(key, result);\r\n return result;\r\n }\r\n\r\n return {\r\n /**\r\n * Verifica se o caller pode usar `feature` no produto `productSlug`.\r\n * Retorno simples: `true` libera, `false` bloqueia. Cache 60s automático.\r\n *\r\n * Use `checkDetailed` se precisar do `reason` pra mensagem de upgrade.\r\n */\r\n async check(\r\n productSlug: string,\r\n feature: string,\r\n opts?: EntitlementsCheckOptions,\r\n ): Promise<boolean> {\r\n const result = await checkDetailed(productSlug, feature, opts);\r\n return result.allowed;\r\n },\r\n checkDetailed,\r\n /** Test helper: limpa o cache LRU. */\r\n __resetCache(): void {\r\n cache.clear();\r\n },\r\n };\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/entitlements.ts"],"names":[],"mappings":";AAyDO,IAAM,WAAA,GAAN,MAAM,YAAA,SAAoB,KAAA,CAAM;AAAA,EACrB,IAAA;AAAA,EACA,MAAA;AAAA,EACA,SAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAA;AAAA,EAEhB,WAAA,CACE,IAAA,EACA,OAAA,EACA,MAAA,EACA,WACA,MAAA,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;AACjB,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAEd,IAAA,MAAA,CAAO,cAAA,CAAe,IAAA,EAAM,YAAA,CAAY,SAAS,CAAA;AAAA,EACnD;AACF,CAAA;;;AChDA,IAAM,mBAAA,GACJ,uVAAA;AAKF,SAAS,eAAA,GAAyB;AAChC,EAAA,MAAM,IAAI,WAAA,CAAY,mBAAA,EAAqB,mBAAmB,CAAA;AAChE;AAEO,SAAS,4BAA4B,OAAA,EAAyB;AACnE,EAAA,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA,IAKL,MAAM,KAAA,CACJ,YAAA,EACA,QAAA,EACA,KAAA,EACkB;AAClB,MAAA,eAAA,EAAgB;AAAA,IAClB,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,MAAM,aAAA,CACJ,YAAA,EACA,QAAA,EACA,KAAA,EAC2B;AAC3B,MAAA,eAAA,EAAgB;AAAA,IAClB,CAAA;AAAA;AAAA,IAEA,YAAA,GAAqB;AAAA,IAAC;AAAA,GACxB;AACF","file":"entitlements.mjs","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 /**\r\n * 3.1.3 (bug_c1c96a00): o `state` retornado pelo IdP no callback OIDC não bate com o gerado em\r\n * `signIn` (CSRF). Também usado quando não há transação OIDC pendente.\r\n */\r\n | 'oidc_state_mismatch'\r\n /**\r\n * 3.1.12 (incidente pdv-agiliza 2026-07-10): o `nonce` do `id_token` retornado\r\n * pelo `/token` não bate com o gerado em `signIn` (possível replay). O token\r\n * é descartado sem tocar o storage.\r\n */\r\n | 'oidc_nonce_mismatch'\r\n /**\r\n * 3.1.12: o POST `/api/v1/oauth/token` (troca do authorization_code por\r\n * tokens em `completeSignIn`) falhou — HTTP não-2xx, corpo com `error`, ou\r\n * resposta sem `id_token`.\r\n */\r\n | 'token_exchange_failed'\r\n /** HTTP 409 — colisão de idempotência ou conflito de recurso. */\r\n | 'conflict'\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 * Motivo semântico opcional propagado pelo Core no corpo do erro (campo\r\n * `reason` no TOPO do body, fora de `error`). Usado principalmente pelo 503\r\n * de lease de DB, onde `reason` discrimina o estado do ambiente\r\n * (`suspended` | `archived` | `revoked` | `provisioning` | `degraded` |\r\n * `no_route` | `expired`). Camadas superiores (ex.: `db/sql`) mapeiam isso\r\n * para um `NeetruDbErrorCode` mais específico (recuperável vs não).\r\n */\r\n public readonly reason?: 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 reason?: 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 this.reason = reason;\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 * Entitlements — legado, DEPRECADO (2026-09).\r\n *\r\n * **Achado (2026-09, auditoria cross-repo do fix de productId mixed-case):**\r\n * `client.entitlements.check()`/`checkDetailed()` chamavam\r\n * `GET /api/v1/sdk/entitlements/check` — endpoint que o PRÓPRIO Core marca\r\n * como órfão (ver `neetru_core/src/app/api/v1/sdk/entitlements/check/route.ts`):\r\n * lê `entitlements/{uid}/products/{slug}`, uma subcoleção que NENHUM código\r\n * do Core (Next.js app ou Cloud Functions) jamais escreve. O mecanismo real\r\n * de concessão grava em `entitlements/{tenantId}` (doc flat, sem subcoleção\r\n * `products`). Resultado: todo consumer que chamava este método recebia\r\n * `not_subscribed` **SEMPRE**, pra qualquer tenant — um gate de feature paga\r\n * permanentemente fechado, silenciosamente.\r\n *\r\n * **Por que não foi re-apontado pro endpoint canônico:** o canônico\r\n * (`GET /api/sdk/v1/entitlements`) exige `productId` (Firestore auto-ID) +\r\n * `tenantId` — dois identificadores que este método legado NUNCA recebeu\r\n * (assinatura é `check(productSlug, feature)`, sem tenant, e `productSlug` é\r\n * o slug PÚBLICO do catálogo, um espaço de identificadores DIFERENTE de\r\n * `productId`). Resolver slug→productId exigiria uma chamada de rede extra\r\n * (lookup no catálogo) que este método nunca fez — re-apontar silenciosamente\r\n * seria um refactor não-trivial e arriscado (mudaria semântica sem o caller\r\n * saber). Decisão: falhar ALTO E CEDO em vez de continuar mentindo\r\n * `not_subscribed` pra sempre.\r\n *\r\n * **Migração:** use `client.usage.check(resource, {productId, tenantId})` —\r\n * aponta pro endpoint canônico real, com `tenantId` explícito.\r\n *\r\n * `check()`/`checkDetailed()` agora lançam `NeetruError('validation_failed')`\r\n * IMEDIATAMENTE (sem tentar fetch) direcionando pra `usage.check()`.\r\n */\r\nimport { NeetruError } from './errors';\r\nimport type { EntitlementCheck, ResolvedConfig } from './types';\r\n\r\nexport interface EntitlementsCheckOptions {\r\n /** @deprecated Sem efeito — `check()`/`checkDetailed()` sempre lançam. Mantido só por compat de tipo. */\r\n cacheBust?: boolean;\r\n}\r\n\r\nconst DEPRECATION_MESSAGE =\r\n 'client.entitlements.check()/checkDetailed() estão DEPRECADOS — apontavam para ' +\r\n 'GET /api/v1/sdk/entitlements/check, um endpoint que o Core nunca escreve (sempre ' +\r\n 'retornava not_subscribed). Use client.usage.check(resource, { productId, tenantId }) ' +\r\n 'no lugar — mesmo resultado (allowed/reason/remaining/limit/behavior), endpoint real.';\r\n\r\nfunction throwDeprecated(): never {\r\n throw new NeetruError('validation_failed', DEPRECATION_MESSAGE);\r\n}\r\n\r\nexport function createEntitlementsNamespace(_config: ResolvedConfig) {\r\n return {\r\n /**\r\n * @deprecated Endpoint morto no Core — sempre retornava `not_subscribed`.\r\n * Lança `NeetruError`. Use `client.usage.check(resource, {productId, tenantId})`.\r\n */\r\n async check(\r\n _productSlug: string,\r\n _feature: string,\r\n _opts?: EntitlementsCheckOptions,\r\n ): Promise<boolean> {\r\n throwDeprecated();\r\n },\r\n /**\r\n * @deprecated Endpoint morto no Core — sempre retornava `not_subscribed`.\r\n * Lança `NeetruError`. Use `client.usage.check(resource, {productId, tenantId})`.\r\n */\r\n async checkDetailed(\r\n _productSlug: string,\r\n _feature: string,\r\n _opts?: EntitlementsCheckOptions,\r\n ): Promise<EntitlementCheck> {\r\n throwDeprecated();\r\n },\r\n /** Test helper legado — no-op (não há mais cache). Mantido por compat. */\r\n __resetCache(): void {},\r\n };\r\n}\r\n"]}
package/dist/index.cjs CHANGED
@@ -1191,80 +1191,28 @@ function createCatalogNamespace(config) {
1191
1191
 
1192
1192
  // src/entitlements.ts
1193
1193
  init_errors();
1194
- init_http();
1195
- function toEntitlementCheck(raw) {
1196
- if (!raw || typeof raw !== "object") {
1197
- throw new exports.NeetruError("invalid_response", "Entitlement response is not an object");
1198
- }
1199
- const r = raw;
1200
- if (typeof r.allowed !== "boolean") {
1201
- throw new exports.NeetruError("invalid_response", "Entitlement response missing `allowed` boolean");
1202
- }
1203
- return {
1204
- allowed: r.allowed,
1205
- productSlug: typeof r.productSlug === "string" ? r.productSlug : "",
1206
- feature: typeof r.feature === "string" ? r.feature : "",
1207
- reason: typeof r.reason === "string" ? r.reason : void 0
1208
- };
1194
+ var DEPRECATION_MESSAGE = "client.entitlements.check()/checkDetailed() est\xE3o DEPRECADOS \u2014 apontavam para GET /api/v1/sdk/entitlements/check, um endpoint que o Core nunca escreve (sempre retornava not_subscribed). Use client.usage.check(resource, { productId, tenantId }) no lugar \u2014 mesmo resultado (allowed/reason/remaining/limit/behavior), endpoint real.";
1195
+ function throwDeprecated() {
1196
+ throw new exports.NeetruError("validation_failed", DEPRECATION_MESSAGE);
1209
1197
  }
1210
- var CACHE_TTL_MS = 6e4;
1211
- var CACHE_MAX = 100;
1212
- function createEntitlementsNamespace(config) {
1213
- const cache = /* @__PURE__ */ new Map();
1214
- function cacheKey(productSlug, feature) {
1215
- return `${productSlug}::${feature}`;
1216
- }
1217
- function readCache(key) {
1218
- const entry = cache.get(key);
1219
- if (!entry) return null;
1220
- if (entry.expiresAt < Date.now()) {
1221
- cache.delete(key);
1222
- return null;
1223
- }
1224
- cache.delete(key);
1225
- cache.set(key, entry);
1226
- return entry.value;
1227
- }
1228
- function writeCache(key, value) {
1229
- if (cache.size >= CACHE_MAX) {
1230
- const oldest = cache.keys().next().value;
1231
- if (oldest !== void 0) cache.delete(oldest);
1232
- }
1233
- cache.set(key, { value, expiresAt: Date.now() + CACHE_TTL_MS });
1234
- }
1235
- async function checkDetailed(productSlug, feature, opts = {}) {
1236
- if (!productSlug) throw new exports.NeetruError("validation_failed", "productSlug is required");
1237
- if (!feature) throw new exports.NeetruError("validation_failed", "feature is required");
1238
- const key = cacheKey(productSlug, feature);
1239
- if (!opts.cacheBust) {
1240
- const cached = readCache(key);
1241
- if (cached) return cached;
1242
- }
1243
- const raw = await httpRequest(config, {
1244
- method: "GET",
1245
- path: "/api/v1/sdk/entitlements/check",
1246
- query: { slug: productSlug, feature },
1247
- requireAuth: true
1248
- });
1249
- const result = toEntitlementCheck(raw);
1250
- writeCache(key, result);
1251
- return result;
1252
- }
1198
+ function createEntitlementsNamespace(_config) {
1253
1199
  return {
1254
1200
  /**
1255
- * Verifica se o caller pode usar `feature` no produto `productSlug`.
1256
- * Retorno simples: `true` libera, `false` bloqueia. Cache 60s automático.
1257
- *
1258
- * Use `checkDetailed` se precisar do `reason` pra mensagem de upgrade.
1201
+ * @deprecated Endpoint morto no Core — sempre retornava `not_subscribed`.
1202
+ * Lança `NeetruError`. Use `client.usage.check(resource, {productId, tenantId})`.
1259
1203
  */
1260
- async check(productSlug, feature, opts) {
1261
- const result = await checkDetailed(productSlug, feature, opts);
1262
- return result.allowed;
1204
+ async check(_productSlug, _feature, _opts) {
1205
+ throwDeprecated();
1263
1206
  },
1264
- checkDetailed,
1265
- /** Test helper: limpa o cache LRU. */
1207
+ /**
1208
+ * @deprecated Endpoint morto no Core — sempre retornava `not_subscribed`.
1209
+ * Lança `NeetruError`. Use `client.usage.check(resource, {productId, tenantId})`.
1210
+ */
1211
+ async checkDetailed(_productSlug, _feature, _opts) {
1212
+ throwDeprecated();
1213
+ },
1214
+ /** Test helper legado — no-op (não há mais cache). Mantido por compat. */
1266
1215
  __resetCache() {
1267
- cache.clear();
1268
1216
  }
1269
1217
  };
1270
1218
  }
@@ -1502,7 +1450,7 @@ init_http();
1502
1450
 
1503
1451
  // src/validators.ts
1504
1452
  init_errors();
1505
- var SDK_ID_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/;
1453
+ var SDK_ID_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/i;
1506
1454
  function assertValidSdkId(field, value) {
1507
1455
  if (typeof value !== "string" || !SDK_ID_RE.test(value)) {
1508
1456
  throw new exports.NeetruError(
@@ -8029,7 +7977,7 @@ function createNeetruClient(config = {}) {
8029
7977
  const auth = config.mocks?.auth ?? (isDev ? new MockAuth({ ...DEV_FIXTURE_USER }) : createOidcAuthNamespace(resolved));
8030
7978
  const usage = config.mocks?.usage ?? (isDev ? new MockUsage() : createUsageNamespace(resolved));
8031
7979
  const support = config.mocks?.support ?? (isDev ? new MockSupport() : createSupportNamespace(resolved));
8032
- const entitlements = config.mocks?.entitlements ?? (isDev ? new MockEntitlements() : createEntitlementsNamespace(resolved));
7980
+ const entitlements = config.mocks?.entitlements ?? (isDev ? new MockEntitlements() : createEntitlementsNamespace());
8033
7981
  const db = config.mocks?.db ?? createNeetruDb(resolved, config.db);
8034
7982
  const webhooks = config.mocks?.webhooks ?? (isDev ? new MockWebhooks() : createWebhooksNamespace(resolved));
8035
7983
  const notifications = config.mocks?.notifications ?? (isDev ? new MockNotifications() : createNotificationsNamespace(resolved));
@@ -8050,12 +7998,20 @@ function createNeetruClient(config = {}) {
8050
7998
  });
8051
7999
  return client;
8052
8000
  }
8001
+ var NEETRU_SERVER_CLIENT_KEY = /* @__PURE__ */ Symbol.for("@neetru/sdk:serverClient");
8002
+ function getServerNeetruClient(config = {}) {
8003
+ const g = globalThis;
8004
+ if (!g[NEETRU_SERVER_CLIENT_KEY]) {
8005
+ g[NEETRU_SERVER_CLIENT_KEY] = createNeetruClient(config);
8006
+ }
8007
+ return g[NEETRU_SERVER_CLIENT_KEY];
8008
+ }
8053
8009
 
8054
8010
  // src/index.ts
8055
8011
  init_errors();
8056
8012
  init_db_errors();
8057
8013
  init_http();
8058
- var VERSION = "3.4.5";
8014
+ var VERSION = "3.4.6";
8059
8015
 
8060
8016
  exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
8061
8017
  exports.DEV_FIXTURE_USER = DEV_FIXTURE_USER;
@@ -8077,6 +8033,7 @@ exports.createNeetruClient = createNeetruClient;
8077
8033
  exports.createNeetruDb = createNeetruDb;
8078
8034
  exports.createNotificationsNamespace = createNotificationsNamespace;
8079
8035
  exports.createWebhooksNamespace = createWebhooksNamespace;
8036
+ exports.getServerNeetruClient = getServerNeetruClient;
8080
8037
  exports.increment = increment;
8081
8038
  exports.isFieldSentinel = isFieldSentinel;
8082
8039
  exports.isIncrementSentinel = isIncrementSentinel;