@neetru/sdk 1.1.0 → 1.2.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.
- package/CHANGELOG.md +33 -3
- package/README.md +135 -159
- package/dist/auth.cjs +486 -40
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +1 -1
- package/dist/auth.d.ts +1 -1
- package/dist/auth.mjs +486 -40
- package/dist/auth.mjs.map +1 -1
- package/dist/catalog.cjs +64 -21
- package/dist/catalog.cjs.map +1 -1
- package/dist/catalog.d.cts +2 -2
- package/dist/catalog.d.ts +2 -2
- package/dist/catalog.mjs +64 -21
- package/dist/catalog.mjs.map +1 -1
- package/dist/checkout.cjs +61 -15
- package/dist/checkout.cjs.map +1 -1
- package/dist/checkout.d.cts +1 -1
- package/dist/checkout.d.ts +1 -1
- package/dist/checkout.mjs +61 -15
- package/dist/checkout.mjs.map +1 -1
- package/dist/db.cjs +67 -22
- package/dist/db.cjs.map +1 -1
- package/dist/db.d.cts +1 -1
- package/dist/db.d.ts +1 -1
- package/dist/db.mjs +67 -22
- package/dist/db.mjs.map +1 -1
- package/dist/entitlements.cjs +102 -21
- package/dist/entitlements.cjs.map +1 -1
- package/dist/entitlements.d.cts +11 -5
- package/dist/entitlements.d.ts +11 -5
- package/dist/entitlements.mjs +102 -21
- package/dist/entitlements.mjs.map +1 -1
- package/dist/index.cjs +491 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.mjs +488 -42
- package/dist/index.mjs.map +1 -1
- package/dist/mocks.cjs +3 -2
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.cts +4 -2
- package/dist/mocks.d.ts +4 -2
- package/dist/mocks.mjs +3 -2
- package/dist/mocks.mjs.map +1 -1
- package/dist/notifications.cjs +296 -0
- package/dist/notifications.cjs.map +1 -0
- package/dist/notifications.d.cts +1 -0
- package/dist/notifications.d.ts +1 -0
- package/dist/notifications.mjs +293 -0
- package/dist/notifications.mjs.map +1 -0
- package/dist/react.cjs +7 -3
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.mjs +7 -3
- package/dist/react.mjs.map +1 -1
- package/dist/support.cjs +61 -15
- package/dist/support.cjs.map +1 -1
- package/dist/support.d.cts +1 -1
- package/dist/support.d.ts +1 -1
- package/dist/support.mjs +61 -15
- package/dist/support.mjs.map +1 -1
- package/dist/telemetry.cjs +131 -16
- package/dist/telemetry.cjs.map +1 -1
- package/dist/telemetry.d.cts +17 -1
- package/dist/telemetry.d.ts +17 -1
- package/dist/telemetry.mjs +131 -16
- package/dist/telemetry.mjs.map +1 -1
- package/dist/{types-BA53dd8S.d.cts → types-CQAfwqUS.d.cts} +172 -8
- package/dist/{types-BA53dd8S.d.ts → types-CQAfwqUS.d.ts} +172 -8
- package/dist/usage.cjs +61 -15
- package/dist/usage.cjs.map +1 -1
- package/dist/usage.d.cts +1 -1
- package/dist/usage.d.ts +1 -1
- package/dist/usage.mjs +61 -15
- package/dist/usage.mjs.map +1 -1
- package/dist/webhooks.cjs +316 -0
- package/dist/webhooks.cjs.map +1 -0
- package/dist/webhooks.d.cts +1 -0
- package/dist/webhooks.d.ts +1 -0
- package/dist/webhooks.mjs +312 -0
- package/dist/webhooks.mjs.map +1 -0
- package/package.json +22 -6
package/dist/catalog.cjs
CHANGED
|
@@ -16,6 +16,31 @@ var NeetruError = class _NeetruError extends Error {
|
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
// src/http.ts
|
|
19
|
+
var DEFAULT_RETRIES = 2;
|
|
20
|
+
var RETRYABLE_CODES = /* @__PURE__ */ new Set([
|
|
21
|
+
"rate_limited",
|
|
22
|
+
"server_error",
|
|
23
|
+
"network_error"
|
|
24
|
+
]);
|
|
25
|
+
function backoffMs(attempt) {
|
|
26
|
+
const base = 200 * Math.pow(4, attempt);
|
|
27
|
+
const jitter = base * 0.2 * (Math.random() * 2 - 1);
|
|
28
|
+
return Math.max(50, Math.round(base + jitter));
|
|
29
|
+
}
|
|
30
|
+
function parseRetryAfter(value) {
|
|
31
|
+
if (!value) return null;
|
|
32
|
+
const secs = Number(value);
|
|
33
|
+
if (Number.isFinite(secs) && secs >= 0) return Math.round(secs * 1e3);
|
|
34
|
+
const dateMs = Date.parse(value);
|
|
35
|
+
if (Number.isFinite(dateMs)) {
|
|
36
|
+
const delta = dateMs - Date.now();
|
|
37
|
+
if (delta > 0) return delta;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
function sleep(ms) {
|
|
42
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
43
|
+
}
|
|
19
44
|
function statusToCode(status) {
|
|
20
45
|
if (status === 401) return "unauthorized";
|
|
21
46
|
if (status === 403) return "forbidden";
|
|
@@ -49,6 +74,7 @@ async function safeJson(res) {
|
|
|
49
74
|
async function httpRequest(config, opts) {
|
|
50
75
|
const method = opts.method ?? "GET";
|
|
51
76
|
const url = buildUrl(config.baseUrl, opts.path, opts.query);
|
|
77
|
+
const maxRetries = opts.retries ?? DEFAULT_RETRIES;
|
|
52
78
|
const headers = {
|
|
53
79
|
accept: "application/json",
|
|
54
80
|
...opts.headers
|
|
@@ -62,20 +88,32 @@ async function httpRequest(config, opts) {
|
|
|
62
88
|
}
|
|
63
89
|
headers.authorization = `Bearer ${config.apiKey}`;
|
|
64
90
|
}
|
|
65
|
-
const
|
|
66
|
-
if (
|
|
91
|
+
const bodyString = opts.body !== void 0 && method !== "GET" && method !== "DELETE" ? JSON.stringify(opts.body) : void 0;
|
|
92
|
+
if (bodyString !== void 0) {
|
|
67
93
|
headers["content-type"] = "application/json";
|
|
68
|
-
init.body = JSON.stringify(opts.body);
|
|
69
|
-
}
|
|
70
|
-
let res;
|
|
71
|
-
try {
|
|
72
|
-
res = await config.fetch(url, init);
|
|
73
|
-
} catch (err) {
|
|
74
|
-
const message = err instanceof Error ? err.message : "fetch failed";
|
|
75
|
-
throw new NeetruError("network_error", `Network error: ${message}`);
|
|
76
94
|
}
|
|
77
|
-
|
|
78
|
-
|
|
95
|
+
let lastError = null;
|
|
96
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
97
|
+
const init = { method, headers };
|
|
98
|
+
if (bodyString !== void 0) init.body = bodyString;
|
|
99
|
+
init.signal = AbortSignal.timeout(3e4);
|
|
100
|
+
let res;
|
|
101
|
+
try {
|
|
102
|
+
res = await config.fetch(url, init);
|
|
103
|
+
} catch (err2) {
|
|
104
|
+
const message2 = err2 instanceof DOMException && err2.name === "TimeoutError" ? "Network error: timeout after 30s" : `Network error: ${err2 instanceof Error ? err2.message : "fetch failed"}`;
|
|
105
|
+
lastError = new NeetruError("network_error", message2);
|
|
106
|
+
if (attempt < maxRetries) {
|
|
107
|
+
await sleep(backoffMs(attempt));
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
throw lastError;
|
|
111
|
+
}
|
|
112
|
+
const requestId = res.headers.get("x-request-id") ?? res.headers.get("x-correlation-id") ?? void 0;
|
|
113
|
+
if (res.ok) {
|
|
114
|
+
const parsed = await safeJson(res);
|
|
115
|
+
return parsed;
|
|
116
|
+
}
|
|
79
117
|
const body = await safeJson(res);
|
|
80
118
|
let code = statusToCode(res.status);
|
|
81
119
|
let message = `HTTP ${res.status}`;
|
|
@@ -88,10 +126,18 @@ async function httpRequest(config, opts) {
|
|
|
88
126
|
if (typeof errField.message === "string") message = errField.message;
|
|
89
127
|
}
|
|
90
128
|
}
|
|
91
|
-
|
|
129
|
+
const err = new NeetruError(code, message, res.status, requestId);
|
|
130
|
+
lastError = err;
|
|
131
|
+
const isRetryable = RETRYABLE_CODES.has(code);
|
|
132
|
+
if (isRetryable && attempt < maxRetries) {
|
|
133
|
+
const retryAfter = parseRetryAfter(res.headers.get("retry-after"));
|
|
134
|
+
const delay = retryAfter ?? backoffMs(attempt);
|
|
135
|
+
await sleep(delay);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
throw err;
|
|
92
139
|
}
|
|
93
|
-
|
|
94
|
-
return parsed;
|
|
140
|
+
throw lastError ?? new NeetruError("unknown", "unexpected httpRequest exit");
|
|
95
141
|
}
|
|
96
142
|
|
|
97
143
|
// src/catalog.ts
|
|
@@ -131,12 +177,10 @@ function createCatalogNamespace(config) {
|
|
|
131
177
|
* Lista produtos publicados. Por default só `published=true`; staff
|
|
132
178
|
* pode passar `includeDrafts: true` (requer Bearer com role admin/operator).
|
|
133
179
|
*/
|
|
134
|
-
async list(
|
|
180
|
+
async list(_opts = {}) {
|
|
135
181
|
const raw = await httpRequest(config, {
|
|
136
182
|
method: "GET",
|
|
137
|
-
path: "/api/v1/
|
|
138
|
-
query: opts.includeDrafts ? { drafts: "true" } : void 0,
|
|
139
|
-
requireAuth: true
|
|
183
|
+
path: "/api/sdk/v1/catalog"
|
|
140
184
|
});
|
|
141
185
|
if (!raw || !Array.isArray(raw.products)) {
|
|
142
186
|
throw new NeetruError(
|
|
@@ -160,8 +204,7 @@ function createCatalogNamespace(config) {
|
|
|
160
204
|
}
|
|
161
205
|
const raw = await httpRequest(config, {
|
|
162
206
|
method: "GET",
|
|
163
|
-
path: `/api/v1/
|
|
164
|
-
requireAuth: true
|
|
207
|
+
path: `/api/sdk/v1/catalog/${encodeURIComponent(slug)}`
|
|
165
208
|
});
|
|
166
209
|
if (!raw || !raw.product) {
|
|
167
210
|
throw new NeetruError(
|
package/dist/catalog.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/catalog.ts"],"names":[],"mappings":"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","file":"catalog.cjs","sourcesContent":["/**\n * Erros tipados do SDK.\n *\n * Todo erro lançado pelo SDK estende `NeetruError` — caller pode discriminar\n * por `.code` (string estável) sem parsing de message.\n */\n\n/** Códigos de erro estáveis do SDK. Adicionar aqui requer minor bump. */\nexport type NeetruErrorCode =\n | 'invalid_config'\n | 'missing_api_key'\n | 'unauthorized'\n | 'forbidden'\n | 'not_found'\n | 'rate_limited'\n | 'validation_failed'\n | 'network_error'\n | 'invalid_response'\n | 'server_error'\n | 'unknown';\n\n/**\n * Erro tipado padrão do SDK. Sempre lançado em vez de Error genérico.\n *\n * @example\n * ```ts\n * try { await client.catalog.list(); }\n * catch (e) {\n * if (e instanceof NeetruError && e.code === 'rate_limited') retry();\n * }\n * ```\n */\nexport class NeetruError extends Error {\n public readonly code: NeetruErrorCode | string;\n public readonly status?: number;\n public readonly requestId?: string;\n\n constructor(\n code: NeetruErrorCode | string,\n message: string,\n status?: number,\n requestId?: string,\n ) {\n super(message);\n this.name = 'NeetruError';\n this.code = code;\n this.status = status;\n this.requestId = requestId;\n // Preserva o prototype chain ao herdar de Error (downlevel quirk de TS).\n Object.setPrototypeOf(this, NeetruError.prototype);\n }\n}\n","/**\n * HTTP transport interno do SDK.\n *\n * Responsabilidades:\n * - Construir URL absoluta a partir de `baseUrl` + path\n * - Injetar Bearer token quando `requireAuth=true`\n * - Mapear status HTTP → `NeetruError` com `code` estável\n * - Parse defensivo de JSON (não lança em body vazio em 204)\n *\n * Não faz retry/backoff em v0.1 (carry-over Sprint 3+).\n */\nimport { NeetruError, type NeetruErrorCode } from './errors';\nimport type { ResolvedConfig } from './types';\n\n/** Opções da request HTTP. */\nexport interface HttpRequestOptions {\n method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n /** Path relativo (ex: `/api/v1/cli/catalog`). Concatenado a `baseUrl`. */\n path: string;\n /** Query string params (chave → valor primitivo). Valores `undefined` ignorados. */\n query?: Record<string, string | number | boolean | undefined>;\n /** Body JSON-serializável. Ignorado em GET/DELETE. */\n body?: unknown;\n /**\n * Se true, injeta `Authorization: Bearer <apiKey>`. Lança `missing_api_key`\n * se config.apiKey ausente. Default false.\n */\n requireAuth?: boolean;\n /** Cabeçalhos extras. */\n headers?: Record<string, string>;\n}\n\n/** Mapeamento status → code estável do NeetruError. */\nfunction statusToCode(status: number): NeetruErrorCode {\n if (status === 401) return 'unauthorized';\n if (status === 403) return 'forbidden';\n if (status === 404) return 'not_found';\n if (status === 422 || status === 400) return 'validation_failed';\n if (status === 429) return 'rate_limited';\n if (status >= 500) return 'server_error';\n return 'unknown';\n}\n\nfunction buildUrl(baseUrl: string, path: string, query?: HttpRequestOptions['query']): string {\n // Trim trailing slash em base e leading em path pra evitar `//`.\n const base = baseUrl.replace(/\\/+$/, '');\n const p = path.startsWith('/') ? path : `/${path}`;\n const url = new URL(`${base}${p}`);\n if (query) {\n for (const [k, v] of Object.entries(query)) {\n if (v === undefined) continue;\n url.searchParams.set(k, String(v));\n }\n }\n return url.toString();\n}\n\n/** Parse JSON defensivo — retorna `undefined` em body vazio. */\nasync function safeJson(res: Response): Promise<unknown> {\n const text = await res.text();\n if (!text) return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Executa request HTTP. Em sucesso retorna body parseado; em erro lança\n * `NeetruError` com `code` derivado do status.\n */\nexport async function httpRequest<T>(\n config: ResolvedConfig,\n opts: HttpRequestOptions,\n): Promise<T> {\n const method = opts.method ?? 'GET';\n const url = buildUrl(config.baseUrl, opts.path, opts.query);\n\n const headers: Record<string, string> = {\n accept: 'application/json',\n ...opts.headers,\n };\n\n if (opts.requireAuth) {\n if (!config.apiKey) {\n throw new NeetruError(\n 'missing_api_key',\n 'This operation requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var.',\n );\n }\n headers.authorization = `Bearer ${config.apiKey}`;\n }\n\n const init: RequestInit = { method, headers };\n if (opts.body !== undefined && method !== 'GET' && method !== 'DELETE') {\n headers['content-type'] = 'application/json';\n init.body = JSON.stringify(opts.body);\n }\n\n let res: Response;\n try {\n res = await config.fetch(url, init);\n } catch (err) {\n const message = err instanceof Error ? err.message : 'fetch failed';\n throw new NeetruError('network_error', `Network error: ${message}`);\n }\n\n const requestId = res.headers.get('x-request-id') ?? res.headers.get('x-correlation-id') ?? undefined;\n\n if (!res.ok) {\n const body = (await safeJson(res)) as { error?: { code?: string; message?: string } | string } | undefined;\n let code: string = statusToCode(res.status);\n let message = `HTTP ${res.status}`;\n if (body && typeof body === 'object' && 'error' in body) {\n const errField = body.error;\n if (typeof errField === 'string') {\n message = errField;\n } else if (errField && typeof errField === 'object') {\n if (typeof errField.code === 'string') code = errField.code;\n if (typeof errField.message === 'string') message = errField.message;\n }\n }\n throw new NeetruError(code, message, res.status, requestId);\n }\n\n const parsed = await safeJson(res);\n // Caller é responsável por validar shape; SDK assume backend correto.\n return parsed as T;\n}\n","/**\n * Catálogo público de produtos SaaS Neetru.\n *\n * Endpoints consumidos:\n * - `GET /api/v1/cli/catalog` — listagem (Bearer required)\n * - `GET /api/v1/cli/catalog/{slug}` — produto único (Bearer required)\n *\n * Read-only. v0.1 não expõe escrita — publicação de produto é via CLI\n * `neetru publish`, não cabe em SDK runtime.\n */\nimport { NeetruError } from './errors';\nimport { httpRequest } from './http';\nimport type {\n CatalogListOptions,\n CatalogListResponse,\n Product,\n ResolvedConfig,\n} from './types';\n\ninterface RawCatalogListResponse {\n ok?: boolean;\n count?: number;\n products?: unknown[];\n fetchedAt?: string;\n}\n\ninterface RawCatalogGetResponse {\n ok?: boolean;\n product?: unknown;\n}\n\nfunction toProduct(raw: unknown): Product {\n if (!raw || typeof raw !== 'object') {\n throw new NeetruError('invalid_response', 'Catalog response item is not an object');\n }\n const r = raw as Record<string, unknown>;\n if (typeof r.slug !== 'string' || !r.slug) {\n throw new NeetruError('invalid_response', 'Catalog product missing slug');\n }\n if (typeof r.name !== 'string' || !r.name) {\n throw new NeetruError('invalid_response', 'Catalog product missing name');\n }\n const product: Product = {\n slug: r.slug,\n name: r.name,\n };\n if (typeof r.tagline === 'string') product.tagline = r.tagline;\n if (typeof r.description === 'string') product.description = r.description;\n if (r.status === 'live' || r.status === 'soon' || r.status === 'beta') {\n product.status = r.status;\n }\n if (typeof r.iconKey === 'string') product.iconKey = r.iconKey;\n if (typeof r.ctaHref === 'string') product.ctaHref = r.ctaHref;\n if (typeof r.ctaLabel === 'string') product.ctaLabel = r.ctaLabel;\n // plans é placeholder v0.2 — preservar se backend mandar\n if (Array.isArray(r.plans)) {\n product.plans = r.plans.filter(\n (p): p is { id: string; name: string } =>\n typeof p === 'object' &&\n p !== null &&\n typeof (p as Record<string, unknown>).id === 'string' &&\n typeof (p as Record<string, unknown>).name === 'string',\n ) as Product['plans'];\n }\n return product;\n}\n\nexport function createCatalogNamespace(config: ResolvedConfig) {\n return {\n /**\n * Lista produtos publicados. Por default só `published=true`; staff\n * pode passar `includeDrafts: true` (requer Bearer com role admin/operator).\n */\n async list(opts: CatalogListOptions = {}): Promise<CatalogListResponse> {\n const raw = await httpRequest<RawCatalogListResponse>(config, {\n method: 'GET',\n path: '/api/v1/cli/catalog',\n query: opts.includeDrafts ? { drafts: 'true' } : undefined,\n requireAuth: true,\n });\n\n if (!raw || !Array.isArray(raw.products)) {\n throw new NeetruError(\n 'invalid_response',\n 'Catalog list response missing products array',\n );\n }\n\n return {\n products: raw.products.map(toProduct),\n fetchedAt: typeof raw.fetchedAt === 'string'\n ? raw.fetchedAt\n : new Date().toISOString(),\n };\n },\n\n /**\n * Busca produto único por slug.\n *\n * @throws {NeetruError} `not_found` se slug inexistente ou não publicado.\n */\n async get(slug: string): Promise<Product> {\n if (!slug || typeof slug !== 'string') {\n throw new NeetruError('validation_failed', 'slug is required');\n }\n const raw = await httpRequest<RawCatalogGetResponse>(config, {\n method: 'GET',\n path: `/api/v1/cli/catalog/${encodeURIComponent(slug)}`,\n requireAuth: true,\n });\n\n if (!raw || !raw.product) {\n throw new NeetruError(\n 'invalid_response',\n 'Catalog get response missing product',\n );\n }\n return toProduct(raw.product);\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/catalog.ts"],"names":["err","message"],"mappings":"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","file":"catalog.cjs","sourcesContent":["/**\n * Erros tipados do SDK.\n *\n * Todo erro lançado pelo SDK estende `NeetruError` — caller pode discriminar\n * por `.code` (string estável) sem parsing de message.\n */\n\n/** Códigos de erro estáveis do SDK. Adicionar aqui requer minor bump. */\nexport type NeetruErrorCode =\n | 'invalid_config'\n | 'missing_api_key'\n | 'unauthorized'\n | 'forbidden'\n | 'not_found'\n | 'rate_limited'\n | 'validation_failed'\n | 'network_error'\n | 'invalid_response'\n | 'server_error'\n | 'unknown';\n\n/**\n * Erro tipado padrão do SDK. Sempre lançado em vez de Error genérico.\n *\n * @example\n * ```ts\n * try { await client.catalog.list(); }\n * catch (e) {\n * if (e instanceof NeetruError && e.code === 'rate_limited') retry();\n * }\n * ```\n */\nexport class NeetruError extends Error {\n public readonly code: NeetruErrorCode | string;\n public readonly status?: number;\n public readonly requestId?: string;\n\n constructor(\n code: NeetruErrorCode | string,\n message: string,\n status?: number,\n requestId?: string,\n ) {\n super(message);\n this.name = 'NeetruError';\n this.code = code;\n this.status = status;\n this.requestId = requestId;\n // Preserva o prototype chain ao herdar de Error (downlevel quirk de TS).\n Object.setPrototypeOf(this, NeetruError.prototype);\n }\n}\n","/**\n * HTTP transport interno do SDK.\n *\n * Responsabilidades:\n * - Construir URL absoluta a partir de `baseUrl` + path\n * - Injetar Bearer token quando `requireAuth=true`\n * - Mapear status HTTP → `NeetruError` com `code` estável\n * - Parse defensivo de JSON (não lança em body vazio em 204)\n * - Retry/backoff exponencial em `rate_limited` (429), `server_error` (5xx)\n * e `network_error` (timeout/falha de rede). Honra `Retry-After` quando\n * presente. Default 2 retries (3 tentativas no total). Caller opta-out\n * com `retries: 0`.\n */\nimport { NeetruError, type NeetruErrorCode } from './errors';\nimport type { ResolvedConfig } from './types';\n\n/** Opções da request HTTP. */\nexport interface HttpRequestOptions {\n method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n /** Path relativo (ex: `/api/v1/cli/catalog`). Concatenado a `baseUrl`. */\n path: string;\n /** Query string params (chave → valor primitivo). Valores `undefined` ignorados. */\n query?: Record<string, string | number | boolean | undefined>;\n /** Body JSON-serializável. Ignorado em GET/DELETE. */\n body?: unknown;\n /**\n * Se true, injeta `Authorization: Bearer <apiKey>`. Lança `missing_api_key`\n * se config.apiKey ausente. Default false.\n */\n requireAuth?: boolean;\n /** Cabeçalhos extras. */\n headers?: Record<string, string>;\n /**\n * Número de retries em códigos transientes (`rate_limited`, `server_error`,\n * `network_error`). Default 2 (= 3 tentativas total). Caller passa `0` pra\n * desativar (útil em operações não-idempotentes específicas).\n */\n retries?: number;\n}\n\nconst DEFAULT_RETRIES = 2;\nconst RETRYABLE_CODES = new Set<NeetruErrorCode>([\n 'rate_limited',\n 'server_error',\n 'network_error',\n]);\n\n/** Backoff exponencial com jitter ±20%. attempt: 0=primeira, 1=primeiro retry, ... */\nfunction backoffMs(attempt: number): number {\n const base = 200 * Math.pow(4, attempt); // 200ms, 800ms, 3.2s, ...\n const jitter = base * 0.2 * (Math.random() * 2 - 1);\n return Math.max(50, Math.round(base + jitter));\n}\n\n/**\n * Honra `Retry-After` header (segundos ou HTTP-date). Retorna ms ou null se\n * inválido. RFC 9110 §10.2.3.\n */\nfunction parseRetryAfter(value: string | null): number | null {\n if (!value) return null;\n const secs = Number(value);\n if (Number.isFinite(secs) && secs >= 0) return Math.round(secs * 1000);\n const dateMs = Date.parse(value);\n if (Number.isFinite(dateMs)) {\n const delta = dateMs - Date.now();\n if (delta > 0) return delta;\n }\n return null;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/** Mapeamento status → code estável do NeetruError. */\nfunction statusToCode(status: number): NeetruErrorCode {\n if (status === 401) return 'unauthorized';\n if (status === 403) return 'forbidden';\n if (status === 404) return 'not_found';\n if (status === 422 || status === 400) return 'validation_failed';\n if (status === 429) return 'rate_limited';\n if (status >= 500) return 'server_error';\n return 'unknown';\n}\n\nfunction buildUrl(baseUrl: string, path: string, query?: HttpRequestOptions['query']): string {\n // Trim trailing slash em base e leading em path pra evitar `//`.\n const base = baseUrl.replace(/\\/+$/, '');\n const p = path.startsWith('/') ? path : `/${path}`;\n const url = new URL(`${base}${p}`);\n if (query) {\n for (const [k, v] of Object.entries(query)) {\n if (v === undefined) continue;\n url.searchParams.set(k, String(v));\n }\n }\n return url.toString();\n}\n\n/** Parse JSON defensivo — retorna `undefined` em body vazio. */\nasync function safeJson(res: Response): Promise<unknown> {\n const text = await res.text();\n if (!text) return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Executa request HTTP. Em sucesso retorna body parseado; em erro lança\n * `NeetruError` com `code` derivado do status. Aplica retry/backoff\n * automático em códigos transientes (rate_limited/server_error/network_error)\n * conforme `opts.retries` (default 2 = 3 tentativas).\n */\nexport async function httpRequest<T>(\n config: ResolvedConfig,\n opts: HttpRequestOptions,\n): Promise<T> {\n const method = opts.method ?? 'GET';\n const url = buildUrl(config.baseUrl, opts.path, opts.query);\n const maxRetries = opts.retries ?? DEFAULT_RETRIES;\n\n const headers: Record<string, string> = {\n accept: 'application/json',\n ...opts.headers,\n };\n\n if (opts.requireAuth) {\n if (!config.apiKey) {\n throw new NeetruError(\n 'missing_api_key',\n 'This operation requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var.',\n );\n }\n headers.authorization = `Bearer ${config.apiKey}`;\n }\n\n // Body só é serializado uma vez — reusado entre tentativas.\n const bodyString =\n opts.body !== undefined && method !== 'GET' && method !== 'DELETE'\n ? JSON.stringify(opts.body)\n : undefined;\n if (bodyString !== undefined) {\n headers['content-type'] = 'application/json';\n }\n\n let lastError: NeetruError | null = null;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const init: RequestInit = { method, headers };\n if (bodyString !== undefined) init.body = bodyString;\n // BUG-020 fix (2026-05-13): timeout default 30s. AbortSignal.timeout\n // requer Node 18+ ou browsers recentes.\n init.signal = AbortSignal.timeout(30_000);\n\n let res: Response;\n try {\n res = await config.fetch(url, init);\n } catch (err) {\n const message =\n err instanceof DOMException && err.name === 'TimeoutError'\n ? 'Network error: timeout after 30s'\n : `Network error: ${err instanceof Error ? err.message : 'fetch failed'}`;\n lastError = new NeetruError('network_error', message);\n if (attempt < maxRetries) {\n await sleep(backoffMs(attempt));\n continue;\n }\n throw lastError;\n }\n\n const requestId =\n res.headers.get('x-request-id') ?? res.headers.get('x-correlation-id') ?? undefined;\n\n if (res.ok) {\n const parsed = await safeJson(res);\n return parsed as T;\n }\n\n const body = (await safeJson(res)) as\n | { error?: { code?: string; message?: string } | string }\n | undefined;\n let code: string = statusToCode(res.status);\n let message = `HTTP ${res.status}`;\n if (body && typeof body === 'object' && 'error' in body) {\n const errField = body.error;\n if (typeof errField === 'string') {\n message = errField;\n } else if (errField && typeof errField === 'object') {\n if (typeof errField.code === 'string') code = errField.code;\n if (typeof errField.message === 'string') message = errField.message;\n }\n }\n const err = new NeetruError(code, message, res.status, requestId);\n lastError = err;\n\n const isRetryable = RETRYABLE_CODES.has(code as NeetruErrorCode);\n if (isRetryable && attempt < maxRetries) {\n const retryAfter = parseRetryAfter(res.headers.get('retry-after'));\n const delay = retryAfter ?? backoffMs(attempt);\n await sleep(delay);\n continue;\n }\n throw err;\n }\n\n // Unreachable — loop sempre retorna ou lança. Safety net.\n throw lastError ?? new NeetruError('unknown', 'unexpected httpRequest exit');\n}\n","/**\n * Catálogo público de produtos SaaS Neetru.\n *\n * Endpoints consumidos (v1.2+):\n * - `GET /api/sdk/v1/catalog` — listagem pública (sem auth)\n * - `GET /api/sdk/v1/catalog/{slug}` — produto único (sem auth)\n *\n * v1.1 e anteriores usavam `/api/v1/cli/catalog` (staff CLI), o que forçava\n * SDK consumers a manter Bearer staff-only. v1.2 separa: SDK consome o\n * endpoint público (`published === true`), staff continua usando CLI quando\n * precisa de rascunho.\n *\n * Read-only. Publicação de produto continua via CLI `neetru publish`.\n */\nimport { NeetruError } from './errors';\nimport { httpRequest } from './http';\nimport type {\n CatalogListOptions,\n CatalogListResponse,\n Product,\n ResolvedConfig,\n} from './types';\n\ninterface RawCatalogListResponse {\n ok?: boolean;\n count?: number;\n products?: unknown[];\n fetchedAt?: string;\n}\n\ninterface RawCatalogGetResponse {\n ok?: boolean;\n product?: unknown;\n}\n\nfunction toProduct(raw: unknown): Product {\n if (!raw || typeof raw !== 'object') {\n throw new NeetruError('invalid_response', 'Catalog response item is not an object');\n }\n const r = raw as Record<string, unknown>;\n if (typeof r.slug !== 'string' || !r.slug) {\n throw new NeetruError('invalid_response', 'Catalog product missing slug');\n }\n if (typeof r.name !== 'string' || !r.name) {\n throw new NeetruError('invalid_response', 'Catalog product missing name');\n }\n const product: Product = {\n slug: r.slug,\n name: r.name,\n };\n if (typeof r.tagline === 'string') product.tagline = r.tagline;\n if (typeof r.description === 'string') product.description = r.description;\n if (r.status === 'live' || r.status === 'soon' || r.status === 'beta') {\n product.status = r.status;\n }\n if (typeof r.iconKey === 'string') product.iconKey = r.iconKey;\n if (typeof r.ctaHref === 'string') product.ctaHref = r.ctaHref;\n if (typeof r.ctaLabel === 'string') product.ctaLabel = r.ctaLabel;\n // plans é placeholder v0.2 — preservar se backend mandar\n if (Array.isArray(r.plans)) {\n product.plans = r.plans.filter(\n (p): p is { id: string; name: string } =>\n typeof p === 'object' &&\n p !== null &&\n typeof (p as Record<string, unknown>).id === 'string' &&\n typeof (p as Record<string, unknown>).name === 'string',\n ) as Product['plans'];\n }\n return product;\n}\n\nexport function createCatalogNamespace(config: ResolvedConfig) {\n return {\n /**\n * Lista produtos publicados. Por default só `published=true`; staff\n * pode passar `includeDrafts: true` (requer Bearer com role admin/operator).\n */\n async list(_opts: CatalogListOptions = {}): Promise<CatalogListResponse> {\n // SDK só lê produtos publicados. `includeDrafts` legado é ignorado:\n // rascunhos só via CLI staff (`/api/v1/cli/catalog?drafts=true`).\n const raw = await httpRequest<RawCatalogListResponse>(config, {\n method: 'GET',\n path: '/api/sdk/v1/catalog',\n });\n\n if (!raw || !Array.isArray(raw.products)) {\n throw new NeetruError(\n 'invalid_response',\n 'Catalog list response missing products array',\n );\n }\n\n return {\n products: raw.products.map(toProduct),\n fetchedAt: typeof raw.fetchedAt === 'string'\n ? raw.fetchedAt\n : new Date().toISOString(),\n };\n },\n\n /**\n * Busca produto único por slug.\n *\n * @throws {NeetruError} `not_found` se slug inexistente ou não publicado.\n */\n async get(slug: string): Promise<Product> {\n if (!slug || typeof slug !== 'string') {\n throw new NeetruError('validation_failed', 'slug is required');\n }\n const raw = await httpRequest<RawCatalogGetResponse>(config, {\n method: 'GET',\n path: `/api/sdk/v1/catalog/${encodeURIComponent(slug)}`,\n });\n\n if (!raw || !raw.product) {\n throw new NeetruError(\n 'invalid_response',\n 'Catalog get response missing product',\n );\n }\n return toProduct(raw.product);\n },\n };\n}\n"]}
|
package/dist/catalog.d.cts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { x as ResolvedConfig, C as CatalogListOptions, c as CatalogListResponse, P as Product } from './types-CQAfwqUS.cjs';
|
|
2
2
|
|
|
3
3
|
declare function createCatalogNamespace(config: ResolvedConfig): {
|
|
4
4
|
/**
|
|
5
5
|
* Lista produtos publicados. Por default só `published=true`; staff
|
|
6
6
|
* pode passar `includeDrafts: true` (requer Bearer com role admin/operator).
|
|
7
7
|
*/
|
|
8
|
-
list(
|
|
8
|
+
list(_opts?: CatalogListOptions): Promise<CatalogListResponse>;
|
|
9
9
|
/**
|
|
10
10
|
* Busca produto único por slug.
|
|
11
11
|
*
|
package/dist/catalog.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { x as ResolvedConfig, C as CatalogListOptions, c as CatalogListResponse, P as Product } from './types-CQAfwqUS.js';
|
|
2
2
|
|
|
3
3
|
declare function createCatalogNamespace(config: ResolvedConfig): {
|
|
4
4
|
/**
|
|
5
5
|
* Lista produtos publicados. Por default só `published=true`; staff
|
|
6
6
|
* pode passar `includeDrafts: true` (requer Bearer com role admin/operator).
|
|
7
7
|
*/
|
|
8
|
-
list(
|
|
8
|
+
list(_opts?: CatalogListOptions): Promise<CatalogListResponse>;
|
|
9
9
|
/**
|
|
10
10
|
* Busca produto único por slug.
|
|
11
11
|
*
|
package/dist/catalog.mjs
CHANGED
|
@@ -14,6 +14,31 @@ var NeetruError = class _NeetruError extends Error {
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
// src/http.ts
|
|
17
|
+
var DEFAULT_RETRIES = 2;
|
|
18
|
+
var RETRYABLE_CODES = /* @__PURE__ */ new Set([
|
|
19
|
+
"rate_limited",
|
|
20
|
+
"server_error",
|
|
21
|
+
"network_error"
|
|
22
|
+
]);
|
|
23
|
+
function backoffMs(attempt) {
|
|
24
|
+
const base = 200 * Math.pow(4, attempt);
|
|
25
|
+
const jitter = base * 0.2 * (Math.random() * 2 - 1);
|
|
26
|
+
return Math.max(50, Math.round(base + jitter));
|
|
27
|
+
}
|
|
28
|
+
function parseRetryAfter(value) {
|
|
29
|
+
if (!value) return null;
|
|
30
|
+
const secs = Number(value);
|
|
31
|
+
if (Number.isFinite(secs) && secs >= 0) return Math.round(secs * 1e3);
|
|
32
|
+
const dateMs = Date.parse(value);
|
|
33
|
+
if (Number.isFinite(dateMs)) {
|
|
34
|
+
const delta = dateMs - Date.now();
|
|
35
|
+
if (delta > 0) return delta;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
function sleep(ms) {
|
|
40
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
41
|
+
}
|
|
17
42
|
function statusToCode(status) {
|
|
18
43
|
if (status === 401) return "unauthorized";
|
|
19
44
|
if (status === 403) return "forbidden";
|
|
@@ -47,6 +72,7 @@ async function safeJson(res) {
|
|
|
47
72
|
async function httpRequest(config, opts) {
|
|
48
73
|
const method = opts.method ?? "GET";
|
|
49
74
|
const url = buildUrl(config.baseUrl, opts.path, opts.query);
|
|
75
|
+
const maxRetries = opts.retries ?? DEFAULT_RETRIES;
|
|
50
76
|
const headers = {
|
|
51
77
|
accept: "application/json",
|
|
52
78
|
...opts.headers
|
|
@@ -60,20 +86,32 @@ async function httpRequest(config, opts) {
|
|
|
60
86
|
}
|
|
61
87
|
headers.authorization = `Bearer ${config.apiKey}`;
|
|
62
88
|
}
|
|
63
|
-
const
|
|
64
|
-
if (
|
|
89
|
+
const bodyString = opts.body !== void 0 && method !== "GET" && method !== "DELETE" ? JSON.stringify(opts.body) : void 0;
|
|
90
|
+
if (bodyString !== void 0) {
|
|
65
91
|
headers["content-type"] = "application/json";
|
|
66
|
-
init.body = JSON.stringify(opts.body);
|
|
67
|
-
}
|
|
68
|
-
let res;
|
|
69
|
-
try {
|
|
70
|
-
res = await config.fetch(url, init);
|
|
71
|
-
} catch (err) {
|
|
72
|
-
const message = err instanceof Error ? err.message : "fetch failed";
|
|
73
|
-
throw new NeetruError("network_error", `Network error: ${message}`);
|
|
74
92
|
}
|
|
75
|
-
|
|
76
|
-
|
|
93
|
+
let lastError = null;
|
|
94
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
95
|
+
const init = { method, headers };
|
|
96
|
+
if (bodyString !== void 0) init.body = bodyString;
|
|
97
|
+
init.signal = AbortSignal.timeout(3e4);
|
|
98
|
+
let res;
|
|
99
|
+
try {
|
|
100
|
+
res = await config.fetch(url, init);
|
|
101
|
+
} catch (err2) {
|
|
102
|
+
const message2 = err2 instanceof DOMException && err2.name === "TimeoutError" ? "Network error: timeout after 30s" : `Network error: ${err2 instanceof Error ? err2.message : "fetch failed"}`;
|
|
103
|
+
lastError = new NeetruError("network_error", message2);
|
|
104
|
+
if (attempt < maxRetries) {
|
|
105
|
+
await sleep(backoffMs(attempt));
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
throw lastError;
|
|
109
|
+
}
|
|
110
|
+
const requestId = res.headers.get("x-request-id") ?? res.headers.get("x-correlation-id") ?? void 0;
|
|
111
|
+
if (res.ok) {
|
|
112
|
+
const parsed = await safeJson(res);
|
|
113
|
+
return parsed;
|
|
114
|
+
}
|
|
77
115
|
const body = await safeJson(res);
|
|
78
116
|
let code = statusToCode(res.status);
|
|
79
117
|
let message = `HTTP ${res.status}`;
|
|
@@ -86,10 +124,18 @@ async function httpRequest(config, opts) {
|
|
|
86
124
|
if (typeof errField.message === "string") message = errField.message;
|
|
87
125
|
}
|
|
88
126
|
}
|
|
89
|
-
|
|
127
|
+
const err = new NeetruError(code, message, res.status, requestId);
|
|
128
|
+
lastError = err;
|
|
129
|
+
const isRetryable = RETRYABLE_CODES.has(code);
|
|
130
|
+
if (isRetryable && attempt < maxRetries) {
|
|
131
|
+
const retryAfter = parseRetryAfter(res.headers.get("retry-after"));
|
|
132
|
+
const delay = retryAfter ?? backoffMs(attempt);
|
|
133
|
+
await sleep(delay);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
throw err;
|
|
90
137
|
}
|
|
91
|
-
|
|
92
|
-
return parsed;
|
|
138
|
+
throw lastError ?? new NeetruError("unknown", "unexpected httpRequest exit");
|
|
93
139
|
}
|
|
94
140
|
|
|
95
141
|
// src/catalog.ts
|
|
@@ -129,12 +175,10 @@ function createCatalogNamespace(config) {
|
|
|
129
175
|
* Lista produtos publicados. Por default só `published=true`; staff
|
|
130
176
|
* pode passar `includeDrafts: true` (requer Bearer com role admin/operator).
|
|
131
177
|
*/
|
|
132
|
-
async list(
|
|
178
|
+
async list(_opts = {}) {
|
|
133
179
|
const raw = await httpRequest(config, {
|
|
134
180
|
method: "GET",
|
|
135
|
-
path: "/api/v1/
|
|
136
|
-
query: opts.includeDrafts ? { drafts: "true" } : void 0,
|
|
137
|
-
requireAuth: true
|
|
181
|
+
path: "/api/sdk/v1/catalog"
|
|
138
182
|
});
|
|
139
183
|
if (!raw || !Array.isArray(raw.products)) {
|
|
140
184
|
throw new NeetruError(
|
|
@@ -158,8 +202,7 @@ function createCatalogNamespace(config) {
|
|
|
158
202
|
}
|
|
159
203
|
const raw = await httpRequest(config, {
|
|
160
204
|
method: "GET",
|
|
161
|
-
path: `/api/v1/
|
|
162
|
-
requireAuth: true
|
|
205
|
+
path: `/api/sdk/v1/catalog/${encodeURIComponent(slug)}`
|
|
163
206
|
});
|
|
164
207
|
if (!raw || !raw.product) {
|
|
165
208
|
throw new NeetruError(
|
package/dist/catalog.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/catalog.ts"],"names":[],"mappings":"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","file":"catalog.mjs","sourcesContent":["/**\n * Erros tipados do SDK.\n *\n * Todo erro lançado pelo SDK estende `NeetruError` — caller pode discriminar\n * por `.code` (string estável) sem parsing de message.\n */\n\n/** Códigos de erro estáveis do SDK. Adicionar aqui requer minor bump. */\nexport type NeetruErrorCode =\n | 'invalid_config'\n | 'missing_api_key'\n | 'unauthorized'\n | 'forbidden'\n | 'not_found'\n | 'rate_limited'\n | 'validation_failed'\n | 'network_error'\n | 'invalid_response'\n | 'server_error'\n | 'unknown';\n\n/**\n * Erro tipado padrão do SDK. Sempre lançado em vez de Error genérico.\n *\n * @example\n * ```ts\n * try { await client.catalog.list(); }\n * catch (e) {\n * if (e instanceof NeetruError && e.code === 'rate_limited') retry();\n * }\n * ```\n */\nexport class NeetruError extends Error {\n public readonly code: NeetruErrorCode | string;\n public readonly status?: number;\n public readonly requestId?: string;\n\n constructor(\n code: NeetruErrorCode | string,\n message: string,\n status?: number,\n requestId?: string,\n ) {\n super(message);\n this.name = 'NeetruError';\n this.code = code;\n this.status = status;\n this.requestId = requestId;\n // Preserva o prototype chain ao herdar de Error (downlevel quirk de TS).\n Object.setPrototypeOf(this, NeetruError.prototype);\n }\n}\n","/**\n * HTTP transport interno do SDK.\n *\n * Responsabilidades:\n * - Construir URL absoluta a partir de `baseUrl` + path\n * - Injetar Bearer token quando `requireAuth=true`\n * - Mapear status HTTP → `NeetruError` com `code` estável\n * - Parse defensivo de JSON (não lança em body vazio em 204)\n *\n * Não faz retry/backoff em v0.1 (carry-over Sprint 3+).\n */\nimport { NeetruError, type NeetruErrorCode } from './errors';\nimport type { ResolvedConfig } from './types';\n\n/** Opções da request HTTP. */\nexport interface HttpRequestOptions {\n method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n /** Path relativo (ex: `/api/v1/cli/catalog`). Concatenado a `baseUrl`. */\n path: string;\n /** Query string params (chave → valor primitivo). Valores `undefined` ignorados. */\n query?: Record<string, string | number | boolean | undefined>;\n /** Body JSON-serializável. Ignorado em GET/DELETE. */\n body?: unknown;\n /**\n * Se true, injeta `Authorization: Bearer <apiKey>`. Lança `missing_api_key`\n * se config.apiKey ausente. Default false.\n */\n requireAuth?: boolean;\n /** Cabeçalhos extras. */\n headers?: Record<string, string>;\n}\n\n/** Mapeamento status → code estável do NeetruError. */\nfunction statusToCode(status: number): NeetruErrorCode {\n if (status === 401) return 'unauthorized';\n if (status === 403) return 'forbidden';\n if (status === 404) return 'not_found';\n if (status === 422 || status === 400) return 'validation_failed';\n if (status === 429) return 'rate_limited';\n if (status >= 500) return 'server_error';\n return 'unknown';\n}\n\nfunction buildUrl(baseUrl: string, path: string, query?: HttpRequestOptions['query']): string {\n // Trim trailing slash em base e leading em path pra evitar `//`.\n const base = baseUrl.replace(/\\/+$/, '');\n const p = path.startsWith('/') ? path : `/${path}`;\n const url = new URL(`${base}${p}`);\n if (query) {\n for (const [k, v] of Object.entries(query)) {\n if (v === undefined) continue;\n url.searchParams.set(k, String(v));\n }\n }\n return url.toString();\n}\n\n/** Parse JSON defensivo — retorna `undefined` em body vazio. */\nasync function safeJson(res: Response): Promise<unknown> {\n const text = await res.text();\n if (!text) return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Executa request HTTP. Em sucesso retorna body parseado; em erro lança\n * `NeetruError` com `code` derivado do status.\n */\nexport async function httpRequest<T>(\n config: ResolvedConfig,\n opts: HttpRequestOptions,\n): Promise<T> {\n const method = opts.method ?? 'GET';\n const url = buildUrl(config.baseUrl, opts.path, opts.query);\n\n const headers: Record<string, string> = {\n accept: 'application/json',\n ...opts.headers,\n };\n\n if (opts.requireAuth) {\n if (!config.apiKey) {\n throw new NeetruError(\n 'missing_api_key',\n 'This operation requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var.',\n );\n }\n headers.authorization = `Bearer ${config.apiKey}`;\n }\n\n const init: RequestInit = { method, headers };\n if (opts.body !== undefined && method !== 'GET' && method !== 'DELETE') {\n headers['content-type'] = 'application/json';\n init.body = JSON.stringify(opts.body);\n }\n\n let res: Response;\n try {\n res = await config.fetch(url, init);\n } catch (err) {\n const message = err instanceof Error ? err.message : 'fetch failed';\n throw new NeetruError('network_error', `Network error: ${message}`);\n }\n\n const requestId = res.headers.get('x-request-id') ?? res.headers.get('x-correlation-id') ?? undefined;\n\n if (!res.ok) {\n const body = (await safeJson(res)) as { error?: { code?: string; message?: string } | string } | undefined;\n let code: string = statusToCode(res.status);\n let message = `HTTP ${res.status}`;\n if (body && typeof body === 'object' && 'error' in body) {\n const errField = body.error;\n if (typeof errField === 'string') {\n message = errField;\n } else if (errField && typeof errField === 'object') {\n if (typeof errField.code === 'string') code = errField.code;\n if (typeof errField.message === 'string') message = errField.message;\n }\n }\n throw new NeetruError(code, message, res.status, requestId);\n }\n\n const parsed = await safeJson(res);\n // Caller é responsável por validar shape; SDK assume backend correto.\n return parsed as T;\n}\n","/**\n * Catálogo público de produtos SaaS Neetru.\n *\n * Endpoints consumidos:\n * - `GET /api/v1/cli/catalog` — listagem (Bearer required)\n * - `GET /api/v1/cli/catalog/{slug}` — produto único (Bearer required)\n *\n * Read-only. v0.1 não expõe escrita — publicação de produto é via CLI\n * `neetru publish`, não cabe em SDK runtime.\n */\nimport { NeetruError } from './errors';\nimport { httpRequest } from './http';\nimport type {\n CatalogListOptions,\n CatalogListResponse,\n Product,\n ResolvedConfig,\n} from './types';\n\ninterface RawCatalogListResponse {\n ok?: boolean;\n count?: number;\n products?: unknown[];\n fetchedAt?: string;\n}\n\ninterface RawCatalogGetResponse {\n ok?: boolean;\n product?: unknown;\n}\n\nfunction toProduct(raw: unknown): Product {\n if (!raw || typeof raw !== 'object') {\n throw new NeetruError('invalid_response', 'Catalog response item is not an object');\n }\n const r = raw as Record<string, unknown>;\n if (typeof r.slug !== 'string' || !r.slug) {\n throw new NeetruError('invalid_response', 'Catalog product missing slug');\n }\n if (typeof r.name !== 'string' || !r.name) {\n throw new NeetruError('invalid_response', 'Catalog product missing name');\n }\n const product: Product = {\n slug: r.slug,\n name: r.name,\n };\n if (typeof r.tagline === 'string') product.tagline = r.tagline;\n if (typeof r.description === 'string') product.description = r.description;\n if (r.status === 'live' || r.status === 'soon' || r.status === 'beta') {\n product.status = r.status;\n }\n if (typeof r.iconKey === 'string') product.iconKey = r.iconKey;\n if (typeof r.ctaHref === 'string') product.ctaHref = r.ctaHref;\n if (typeof r.ctaLabel === 'string') product.ctaLabel = r.ctaLabel;\n // plans é placeholder v0.2 — preservar se backend mandar\n if (Array.isArray(r.plans)) {\n product.plans = r.plans.filter(\n (p): p is { id: string; name: string } =>\n typeof p === 'object' &&\n p !== null &&\n typeof (p as Record<string, unknown>).id === 'string' &&\n typeof (p as Record<string, unknown>).name === 'string',\n ) as Product['plans'];\n }\n return product;\n}\n\nexport function createCatalogNamespace(config: ResolvedConfig) {\n return {\n /**\n * Lista produtos publicados. Por default só `published=true`; staff\n * pode passar `includeDrafts: true` (requer Bearer com role admin/operator).\n */\n async list(opts: CatalogListOptions = {}): Promise<CatalogListResponse> {\n const raw = await httpRequest<RawCatalogListResponse>(config, {\n method: 'GET',\n path: '/api/v1/cli/catalog',\n query: opts.includeDrafts ? { drafts: 'true' } : undefined,\n requireAuth: true,\n });\n\n if (!raw || !Array.isArray(raw.products)) {\n throw new NeetruError(\n 'invalid_response',\n 'Catalog list response missing products array',\n );\n }\n\n return {\n products: raw.products.map(toProduct),\n fetchedAt: typeof raw.fetchedAt === 'string'\n ? raw.fetchedAt\n : new Date().toISOString(),\n };\n },\n\n /**\n * Busca produto único por slug.\n *\n * @throws {NeetruError} `not_found` se slug inexistente ou não publicado.\n */\n async get(slug: string): Promise<Product> {\n if (!slug || typeof slug !== 'string') {\n throw new NeetruError('validation_failed', 'slug is required');\n }\n const raw = await httpRequest<RawCatalogGetResponse>(config, {\n method: 'GET',\n path: `/api/v1/cli/catalog/${encodeURIComponent(slug)}`,\n requireAuth: true,\n });\n\n if (!raw || !raw.product) {\n throw new NeetruError(\n 'invalid_response',\n 'Catalog get response missing product',\n );\n }\n return toProduct(raw.product);\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/catalog.ts"],"names":["err","message"],"mappings":"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","file":"catalog.mjs","sourcesContent":["/**\n * Erros tipados do SDK.\n *\n * Todo erro lançado pelo SDK estende `NeetruError` — caller pode discriminar\n * por `.code` (string estável) sem parsing de message.\n */\n\n/** Códigos de erro estáveis do SDK. Adicionar aqui requer minor bump. */\nexport type NeetruErrorCode =\n | 'invalid_config'\n | 'missing_api_key'\n | 'unauthorized'\n | 'forbidden'\n | 'not_found'\n | 'rate_limited'\n | 'validation_failed'\n | 'network_error'\n | 'invalid_response'\n | 'server_error'\n | 'unknown';\n\n/**\n * Erro tipado padrão do SDK. Sempre lançado em vez de Error genérico.\n *\n * @example\n * ```ts\n * try { await client.catalog.list(); }\n * catch (e) {\n * if (e instanceof NeetruError && e.code === 'rate_limited') retry();\n * }\n * ```\n */\nexport class NeetruError extends Error {\n public readonly code: NeetruErrorCode | string;\n public readonly status?: number;\n public readonly requestId?: string;\n\n constructor(\n code: NeetruErrorCode | string,\n message: string,\n status?: number,\n requestId?: string,\n ) {\n super(message);\n this.name = 'NeetruError';\n this.code = code;\n this.status = status;\n this.requestId = requestId;\n // Preserva o prototype chain ao herdar de Error (downlevel quirk de TS).\n Object.setPrototypeOf(this, NeetruError.prototype);\n }\n}\n","/**\n * HTTP transport interno do SDK.\n *\n * Responsabilidades:\n * - Construir URL absoluta a partir de `baseUrl` + path\n * - Injetar Bearer token quando `requireAuth=true`\n * - Mapear status HTTP → `NeetruError` com `code` estável\n * - Parse defensivo de JSON (não lança em body vazio em 204)\n * - Retry/backoff exponencial em `rate_limited` (429), `server_error` (5xx)\n * e `network_error` (timeout/falha de rede). Honra `Retry-After` quando\n * presente. Default 2 retries (3 tentativas no total). Caller opta-out\n * com `retries: 0`.\n */\nimport { NeetruError, type NeetruErrorCode } from './errors';\nimport type { ResolvedConfig } from './types';\n\n/** Opções da request HTTP. */\nexport interface HttpRequestOptions {\n method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n /** Path relativo (ex: `/api/v1/cli/catalog`). Concatenado a `baseUrl`. */\n path: string;\n /** Query string params (chave → valor primitivo). Valores `undefined` ignorados. */\n query?: Record<string, string | number | boolean | undefined>;\n /** Body JSON-serializável. Ignorado em GET/DELETE. */\n body?: unknown;\n /**\n * Se true, injeta `Authorization: Bearer <apiKey>`. Lança `missing_api_key`\n * se config.apiKey ausente. Default false.\n */\n requireAuth?: boolean;\n /** Cabeçalhos extras. */\n headers?: Record<string, string>;\n /**\n * Número de retries em códigos transientes (`rate_limited`, `server_error`,\n * `network_error`). Default 2 (= 3 tentativas total). Caller passa `0` pra\n * desativar (útil em operações não-idempotentes específicas).\n */\n retries?: number;\n}\n\nconst DEFAULT_RETRIES = 2;\nconst RETRYABLE_CODES = new Set<NeetruErrorCode>([\n 'rate_limited',\n 'server_error',\n 'network_error',\n]);\n\n/** Backoff exponencial com jitter ±20%. attempt: 0=primeira, 1=primeiro retry, ... */\nfunction backoffMs(attempt: number): number {\n const base = 200 * Math.pow(4, attempt); // 200ms, 800ms, 3.2s, ...\n const jitter = base * 0.2 * (Math.random() * 2 - 1);\n return Math.max(50, Math.round(base + jitter));\n}\n\n/**\n * Honra `Retry-After` header (segundos ou HTTP-date). Retorna ms ou null se\n * inválido. RFC 9110 §10.2.3.\n */\nfunction parseRetryAfter(value: string | null): number | null {\n if (!value) return null;\n const secs = Number(value);\n if (Number.isFinite(secs) && secs >= 0) return Math.round(secs * 1000);\n const dateMs = Date.parse(value);\n if (Number.isFinite(dateMs)) {\n const delta = dateMs - Date.now();\n if (delta > 0) return delta;\n }\n return null;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/** Mapeamento status → code estável do NeetruError. */\nfunction statusToCode(status: number): NeetruErrorCode {\n if (status === 401) return 'unauthorized';\n if (status === 403) return 'forbidden';\n if (status === 404) return 'not_found';\n if (status === 422 || status === 400) return 'validation_failed';\n if (status === 429) return 'rate_limited';\n if (status >= 500) return 'server_error';\n return 'unknown';\n}\n\nfunction buildUrl(baseUrl: string, path: string, query?: HttpRequestOptions['query']): string {\n // Trim trailing slash em base e leading em path pra evitar `//`.\n const base = baseUrl.replace(/\\/+$/, '');\n const p = path.startsWith('/') ? path : `/${path}`;\n const url = new URL(`${base}${p}`);\n if (query) {\n for (const [k, v] of Object.entries(query)) {\n if (v === undefined) continue;\n url.searchParams.set(k, String(v));\n }\n }\n return url.toString();\n}\n\n/** Parse JSON defensivo — retorna `undefined` em body vazio. */\nasync function safeJson(res: Response): Promise<unknown> {\n const text = await res.text();\n if (!text) return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Executa request HTTP. Em sucesso retorna body parseado; em erro lança\n * `NeetruError` com `code` derivado do status. Aplica retry/backoff\n * automático em códigos transientes (rate_limited/server_error/network_error)\n * conforme `opts.retries` (default 2 = 3 tentativas).\n */\nexport async function httpRequest<T>(\n config: ResolvedConfig,\n opts: HttpRequestOptions,\n): Promise<T> {\n const method = opts.method ?? 'GET';\n const url = buildUrl(config.baseUrl, opts.path, opts.query);\n const maxRetries = opts.retries ?? DEFAULT_RETRIES;\n\n const headers: Record<string, string> = {\n accept: 'application/json',\n ...opts.headers,\n };\n\n if (opts.requireAuth) {\n if (!config.apiKey) {\n throw new NeetruError(\n 'missing_api_key',\n 'This operation requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var.',\n );\n }\n headers.authorization = `Bearer ${config.apiKey}`;\n }\n\n // Body só é serializado uma vez — reusado entre tentativas.\n const bodyString =\n opts.body !== undefined && method !== 'GET' && method !== 'DELETE'\n ? JSON.stringify(opts.body)\n : undefined;\n if (bodyString !== undefined) {\n headers['content-type'] = 'application/json';\n }\n\n let lastError: NeetruError | null = null;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const init: RequestInit = { method, headers };\n if (bodyString !== undefined) init.body = bodyString;\n // BUG-020 fix (2026-05-13): timeout default 30s. AbortSignal.timeout\n // requer Node 18+ ou browsers recentes.\n init.signal = AbortSignal.timeout(30_000);\n\n let res: Response;\n try {\n res = await config.fetch(url, init);\n } catch (err) {\n const message =\n err instanceof DOMException && err.name === 'TimeoutError'\n ? 'Network error: timeout after 30s'\n : `Network error: ${err instanceof Error ? err.message : 'fetch failed'}`;\n lastError = new NeetruError('network_error', message);\n if (attempt < maxRetries) {\n await sleep(backoffMs(attempt));\n continue;\n }\n throw lastError;\n }\n\n const requestId =\n res.headers.get('x-request-id') ?? res.headers.get('x-correlation-id') ?? undefined;\n\n if (res.ok) {\n const parsed = await safeJson(res);\n return parsed as T;\n }\n\n const body = (await safeJson(res)) as\n | { error?: { code?: string; message?: string } | string }\n | undefined;\n let code: string = statusToCode(res.status);\n let message = `HTTP ${res.status}`;\n if (body && typeof body === 'object' && 'error' in body) {\n const errField = body.error;\n if (typeof errField === 'string') {\n message = errField;\n } else if (errField && typeof errField === 'object') {\n if (typeof errField.code === 'string') code = errField.code;\n if (typeof errField.message === 'string') message = errField.message;\n }\n }\n const err = new NeetruError(code, message, res.status, requestId);\n lastError = err;\n\n const isRetryable = RETRYABLE_CODES.has(code as NeetruErrorCode);\n if (isRetryable && attempt < maxRetries) {\n const retryAfter = parseRetryAfter(res.headers.get('retry-after'));\n const delay = retryAfter ?? backoffMs(attempt);\n await sleep(delay);\n continue;\n }\n throw err;\n }\n\n // Unreachable — loop sempre retorna ou lança. Safety net.\n throw lastError ?? new NeetruError('unknown', 'unexpected httpRequest exit');\n}\n","/**\n * Catálogo público de produtos SaaS Neetru.\n *\n * Endpoints consumidos (v1.2+):\n * - `GET /api/sdk/v1/catalog` — listagem pública (sem auth)\n * - `GET /api/sdk/v1/catalog/{slug}` — produto único (sem auth)\n *\n * v1.1 e anteriores usavam `/api/v1/cli/catalog` (staff CLI), o que forçava\n * SDK consumers a manter Bearer staff-only. v1.2 separa: SDK consome o\n * endpoint público (`published === true`), staff continua usando CLI quando\n * precisa de rascunho.\n *\n * Read-only. Publicação de produto continua via CLI `neetru publish`.\n */\nimport { NeetruError } from './errors';\nimport { httpRequest } from './http';\nimport type {\n CatalogListOptions,\n CatalogListResponse,\n Product,\n ResolvedConfig,\n} from './types';\n\ninterface RawCatalogListResponse {\n ok?: boolean;\n count?: number;\n products?: unknown[];\n fetchedAt?: string;\n}\n\ninterface RawCatalogGetResponse {\n ok?: boolean;\n product?: unknown;\n}\n\nfunction toProduct(raw: unknown): Product {\n if (!raw || typeof raw !== 'object') {\n throw new NeetruError('invalid_response', 'Catalog response item is not an object');\n }\n const r = raw as Record<string, unknown>;\n if (typeof r.slug !== 'string' || !r.slug) {\n throw new NeetruError('invalid_response', 'Catalog product missing slug');\n }\n if (typeof r.name !== 'string' || !r.name) {\n throw new NeetruError('invalid_response', 'Catalog product missing name');\n }\n const product: Product = {\n slug: r.slug,\n name: r.name,\n };\n if (typeof r.tagline === 'string') product.tagline = r.tagline;\n if (typeof r.description === 'string') product.description = r.description;\n if (r.status === 'live' || r.status === 'soon' || r.status === 'beta') {\n product.status = r.status;\n }\n if (typeof r.iconKey === 'string') product.iconKey = r.iconKey;\n if (typeof r.ctaHref === 'string') product.ctaHref = r.ctaHref;\n if (typeof r.ctaLabel === 'string') product.ctaLabel = r.ctaLabel;\n // plans é placeholder v0.2 — preservar se backend mandar\n if (Array.isArray(r.plans)) {\n product.plans = r.plans.filter(\n (p): p is { id: string; name: string } =>\n typeof p === 'object' &&\n p !== null &&\n typeof (p as Record<string, unknown>).id === 'string' &&\n typeof (p as Record<string, unknown>).name === 'string',\n ) as Product['plans'];\n }\n return product;\n}\n\nexport function createCatalogNamespace(config: ResolvedConfig) {\n return {\n /**\n * Lista produtos publicados. Por default só `published=true`; staff\n * pode passar `includeDrafts: true` (requer Bearer com role admin/operator).\n */\n async list(_opts: CatalogListOptions = {}): Promise<CatalogListResponse> {\n // SDK só lê produtos publicados. `includeDrafts` legado é ignorado:\n // rascunhos só via CLI staff (`/api/v1/cli/catalog?drafts=true`).\n const raw = await httpRequest<RawCatalogListResponse>(config, {\n method: 'GET',\n path: '/api/sdk/v1/catalog',\n });\n\n if (!raw || !Array.isArray(raw.products)) {\n throw new NeetruError(\n 'invalid_response',\n 'Catalog list response missing products array',\n );\n }\n\n return {\n products: raw.products.map(toProduct),\n fetchedAt: typeof raw.fetchedAt === 'string'\n ? raw.fetchedAt\n : new Date().toISOString(),\n };\n },\n\n /**\n * Busca produto único por slug.\n *\n * @throws {NeetruError} `not_found` se slug inexistente ou não publicado.\n */\n async get(slug: string): Promise<Product> {\n if (!slug || typeof slug !== 'string') {\n throw new NeetruError('validation_failed', 'slug is required');\n }\n const raw = await httpRequest<RawCatalogGetResponse>(config, {\n method: 'GET',\n path: `/api/sdk/v1/catalog/${encodeURIComponent(slug)}`,\n });\n\n if (!raw || !raw.product) {\n throw new NeetruError(\n 'invalid_response',\n 'Catalog get response missing product',\n );\n }\n return toProduct(raw.product);\n },\n };\n}\n"]}
|
package/dist/checkout.cjs
CHANGED
|
@@ -16,6 +16,31 @@ var NeetruError = class _NeetruError extends Error {
|
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
// src/http.ts
|
|
19
|
+
var DEFAULT_RETRIES = 2;
|
|
20
|
+
var RETRYABLE_CODES = /* @__PURE__ */ new Set([
|
|
21
|
+
"rate_limited",
|
|
22
|
+
"server_error",
|
|
23
|
+
"network_error"
|
|
24
|
+
]);
|
|
25
|
+
function backoffMs(attempt) {
|
|
26
|
+
const base = 200 * Math.pow(4, attempt);
|
|
27
|
+
const jitter = base * 0.2 * (Math.random() * 2 - 1);
|
|
28
|
+
return Math.max(50, Math.round(base + jitter));
|
|
29
|
+
}
|
|
30
|
+
function parseRetryAfter(value) {
|
|
31
|
+
if (!value) return null;
|
|
32
|
+
const secs = Number(value);
|
|
33
|
+
if (Number.isFinite(secs) && secs >= 0) return Math.round(secs * 1e3);
|
|
34
|
+
const dateMs = Date.parse(value);
|
|
35
|
+
if (Number.isFinite(dateMs)) {
|
|
36
|
+
const delta = dateMs - Date.now();
|
|
37
|
+
if (delta > 0) return delta;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
function sleep(ms) {
|
|
42
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
43
|
+
}
|
|
19
44
|
function statusToCode(status) {
|
|
20
45
|
if (status === 401) return "unauthorized";
|
|
21
46
|
if (status === 403) return "forbidden";
|
|
@@ -49,6 +74,7 @@ async function safeJson(res) {
|
|
|
49
74
|
async function httpRequest(config, opts) {
|
|
50
75
|
const method = opts.method ?? "GET";
|
|
51
76
|
const url = buildUrl(config.baseUrl, opts.path, opts.query);
|
|
77
|
+
const maxRetries = opts.retries ?? DEFAULT_RETRIES;
|
|
52
78
|
const headers = {
|
|
53
79
|
accept: "application/json",
|
|
54
80
|
...opts.headers
|
|
@@ -62,20 +88,32 @@ async function httpRequest(config, opts) {
|
|
|
62
88
|
}
|
|
63
89
|
headers.authorization = `Bearer ${config.apiKey}`;
|
|
64
90
|
}
|
|
65
|
-
const
|
|
66
|
-
if (
|
|
91
|
+
const bodyString = opts.body !== void 0 && method !== "GET" && method !== "DELETE" ? JSON.stringify(opts.body) : void 0;
|
|
92
|
+
if (bodyString !== void 0) {
|
|
67
93
|
headers["content-type"] = "application/json";
|
|
68
|
-
init.body = JSON.stringify(opts.body);
|
|
69
|
-
}
|
|
70
|
-
let res;
|
|
71
|
-
try {
|
|
72
|
-
res = await config.fetch(url, init);
|
|
73
|
-
} catch (err) {
|
|
74
|
-
const message = err instanceof Error ? err.message : "fetch failed";
|
|
75
|
-
throw new NeetruError("network_error", `Network error: ${message}`);
|
|
76
94
|
}
|
|
77
|
-
|
|
78
|
-
|
|
95
|
+
let lastError = null;
|
|
96
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
97
|
+
const init = { method, headers };
|
|
98
|
+
if (bodyString !== void 0) init.body = bodyString;
|
|
99
|
+
init.signal = AbortSignal.timeout(3e4);
|
|
100
|
+
let res;
|
|
101
|
+
try {
|
|
102
|
+
res = await config.fetch(url, init);
|
|
103
|
+
} catch (err2) {
|
|
104
|
+
const message2 = err2 instanceof DOMException && err2.name === "TimeoutError" ? "Network error: timeout after 30s" : `Network error: ${err2 instanceof Error ? err2.message : "fetch failed"}`;
|
|
105
|
+
lastError = new NeetruError("network_error", message2);
|
|
106
|
+
if (attempt < maxRetries) {
|
|
107
|
+
await sleep(backoffMs(attempt));
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
throw lastError;
|
|
111
|
+
}
|
|
112
|
+
const requestId = res.headers.get("x-request-id") ?? res.headers.get("x-correlation-id") ?? void 0;
|
|
113
|
+
if (res.ok) {
|
|
114
|
+
const parsed = await safeJson(res);
|
|
115
|
+
return parsed;
|
|
116
|
+
}
|
|
79
117
|
const body = await safeJson(res);
|
|
80
118
|
let code = statusToCode(res.status);
|
|
81
119
|
let message = `HTTP ${res.status}`;
|
|
@@ -88,10 +126,18 @@ async function httpRequest(config, opts) {
|
|
|
88
126
|
if (typeof errField.message === "string") message = errField.message;
|
|
89
127
|
}
|
|
90
128
|
}
|
|
91
|
-
|
|
129
|
+
const err = new NeetruError(code, message, res.status, requestId);
|
|
130
|
+
lastError = err;
|
|
131
|
+
const isRetryable = RETRYABLE_CODES.has(code);
|
|
132
|
+
if (isRetryable && attempt < maxRetries) {
|
|
133
|
+
const retryAfter = parseRetryAfter(res.headers.get("retry-after"));
|
|
134
|
+
const delay = retryAfter ?? backoffMs(attempt);
|
|
135
|
+
await sleep(delay);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
throw err;
|
|
92
139
|
}
|
|
93
|
-
|
|
94
|
-
return parsed;
|
|
140
|
+
throw lastError ?? new NeetruError("unknown", "unexpected httpRequest exit");
|
|
95
141
|
}
|
|
96
142
|
|
|
97
143
|
// src/checkout.ts
|