@neetru/sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +183 -0
- package/README.md +218 -0
- package/dist/auth.cjs +1137 -0
- package/dist/auth.cjs.map +1 -0
- package/dist/auth.d.cts +25 -0
- package/dist/auth.d.ts +25 -0
- package/dist/auth.mjs +1135 -0
- package/dist/auth.mjs.map +1 -0
- package/dist/catalog.cjs +179 -0
- package/dist/catalog.cjs.map +1 -0
- package/dist/catalog.d.cts +17 -0
- package/dist/catalog.d.ts +17 -0
- package/dist/catalog.mjs +177 -0
- package/dist/catalog.mjs.map +1 -0
- package/dist/db.cjs +232 -0
- package/dist/db.cjs.map +1 -0
- package/dist/db.d.cts +8 -0
- package/dist/db.d.ts +8 -0
- package/dist/db.mjs +230 -0
- package/dist/db.mjs.map +1 -0
- package/dist/entitlements.cjs +140 -0
- package/dist/entitlements.cjs.map +1 -0
- package/dist/entitlements.d.cts +14 -0
- package/dist/entitlements.d.ts +14 -0
- package/dist/entitlements.mjs +138 -0
- package/dist/entitlements.mjs.map +1 -0
- package/dist/errors.cjs +20 -0
- package/dist/errors.cjs.map +1 -0
- package/dist/errors.d.cts +27 -0
- package/dist/errors.d.ts +27 -0
- package/dist/errors.mjs +18 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/index.cjs +1154 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +24 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.mjs +1142 -0
- package/dist/index.mjs.map +1 -0
- package/dist/mocks.cjs +281 -0
- package/dist/mocks.cjs.map +1 -0
- package/dist/mocks.d.cts +138 -0
- package/dist/mocks.d.ts +138 -0
- package/dist/mocks.mjs +274 -0
- package/dist/mocks.mjs.map +1 -0
- package/dist/support.cjs +176 -0
- package/dist/support.cjs.map +1 -0
- package/dist/support.d.cts +5 -0
- package/dist/support.d.ts +5 -0
- package/dist/support.mjs +174 -0
- package/dist/support.mjs.map +1 -0
- package/dist/telemetry.cjs +225 -0
- package/dist/telemetry.cjs.map +1 -0
- package/dist/telemetry.d.cts +35 -0
- package/dist/telemetry.d.ts +35 -0
- package/dist/telemetry.mjs +223 -0
- package/dist/telemetry.mjs.map +1 -0
- package/dist/types-PKUaFtBY.d.cts +408 -0
- package/dist/types-PKUaFtBY.d.ts +408 -0
- package/dist/usage.cjs +235 -0
- package/dist/usage.cjs.map +1 -0
- package/dist/usage.d.cts +5 -0
- package/dist/usage.d.ts +5 -0
- package/dist/usage.mjs +233 -0
- package/dist/usage.mjs.map +1 -0
- package/package.json +79 -0
package/dist/db.mjs
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var NeetruError = class _NeetruError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
status;
|
|
5
|
+
requestId;
|
|
6
|
+
constructor(code, message, status, requestId) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "NeetruError";
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.requestId = requestId;
|
|
12
|
+
Object.setPrototypeOf(this, _NeetruError.prototype);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/http.ts
|
|
17
|
+
function statusToCode(status) {
|
|
18
|
+
if (status === 401) return "unauthorized";
|
|
19
|
+
if (status === 403) return "forbidden";
|
|
20
|
+
if (status === 404) return "not_found";
|
|
21
|
+
if (status === 422 || status === 400) return "validation_failed";
|
|
22
|
+
if (status === 429) return "rate_limited";
|
|
23
|
+
if (status >= 500) return "server_error";
|
|
24
|
+
return "unknown";
|
|
25
|
+
}
|
|
26
|
+
function buildUrl(baseUrl, path, query) {
|
|
27
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
28
|
+
const p = path.startsWith("/") ? path : `/${path}`;
|
|
29
|
+
const url = new URL(`${base}${p}`);
|
|
30
|
+
if (query) {
|
|
31
|
+
for (const [k, v] of Object.entries(query)) {
|
|
32
|
+
if (v === void 0) continue;
|
|
33
|
+
url.searchParams.set(k, String(v));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return url.toString();
|
|
37
|
+
}
|
|
38
|
+
async function safeJson(res) {
|
|
39
|
+
const text = await res.text();
|
|
40
|
+
if (!text) return void 0;
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(text);
|
|
43
|
+
} catch {
|
|
44
|
+
return void 0;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function httpRequest(config, opts) {
|
|
48
|
+
const method = opts.method ?? "GET";
|
|
49
|
+
const url = buildUrl(config.baseUrl, opts.path, opts.query);
|
|
50
|
+
const headers = {
|
|
51
|
+
accept: "application/json",
|
|
52
|
+
...opts.headers
|
|
53
|
+
};
|
|
54
|
+
if (opts.requireAuth) {
|
|
55
|
+
if (!config.apiKey) {
|
|
56
|
+
throw new NeetruError(
|
|
57
|
+
"missing_api_key",
|
|
58
|
+
"This operation requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var."
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
headers.authorization = `Bearer ${config.apiKey}`;
|
|
62
|
+
}
|
|
63
|
+
const init = { method, headers };
|
|
64
|
+
if (opts.body !== void 0 && method !== "GET" && method !== "DELETE") {
|
|
65
|
+
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
|
+
}
|
|
75
|
+
const requestId = res.headers.get("x-request-id") ?? res.headers.get("x-correlation-id") ?? void 0;
|
|
76
|
+
if (!res.ok) {
|
|
77
|
+
const body = await safeJson(res);
|
|
78
|
+
let code = statusToCode(res.status);
|
|
79
|
+
let message = `HTTP ${res.status}`;
|
|
80
|
+
if (body && typeof body === "object" && "error" in body) {
|
|
81
|
+
const errField = body.error;
|
|
82
|
+
if (typeof errField === "string") {
|
|
83
|
+
message = errField;
|
|
84
|
+
} else if (errField && typeof errField === "object") {
|
|
85
|
+
if (typeof errField.code === "string") code = errField.code;
|
|
86
|
+
if (typeof errField.message === "string") message = errField.message;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
throw new NeetruError(code, message, res.status, requestId);
|
|
90
|
+
}
|
|
91
|
+
const parsed = await safeJson(res);
|
|
92
|
+
return parsed;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/db.ts
|
|
96
|
+
var COLL_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/;
|
|
97
|
+
function assertValidCollection(name) {
|
|
98
|
+
if (!COLL_RE.test(name)) {
|
|
99
|
+
throw new NeetruError(
|
|
100
|
+
"validation_failed",
|
|
101
|
+
`Invalid collection name: "${name}". Must match ${COLL_RE}.`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function serializeWhere(filter) {
|
|
106
|
+
const { field, op, value } = filter;
|
|
107
|
+
if (op === "in") {
|
|
108
|
+
if (!Array.isArray(value)) {
|
|
109
|
+
throw new NeetruError(
|
|
110
|
+
"validation_failed",
|
|
111
|
+
`where op="in" requer value array (recebido: ${typeof value})`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
return `${field}:in:${value.map((v) => String(v)).join(",")}`;
|
|
115
|
+
}
|
|
116
|
+
return `${field}:${op}:${String(value)}`;
|
|
117
|
+
}
|
|
118
|
+
function createDbNamespace(config) {
|
|
119
|
+
return {
|
|
120
|
+
collection(name) {
|
|
121
|
+
assertValidCollection(name);
|
|
122
|
+
const headers = {};
|
|
123
|
+
if (config.tenantId) headers["x-neetru-tenant"] = config.tenantId;
|
|
124
|
+
return {
|
|
125
|
+
async list(opts) {
|
|
126
|
+
if (opts?.limit !== void 0) opts.limit;
|
|
127
|
+
let path = `/sdk/v1/datastore/${name}`;
|
|
128
|
+
const params = new URLSearchParams();
|
|
129
|
+
if (opts?.limit !== void 0) params.set("limit", String(opts.limit));
|
|
130
|
+
if (opts?.where && opts.where.length > 0) {
|
|
131
|
+
for (const f of opts.where) {
|
|
132
|
+
params.append("where", serializeWhere(f));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (config.tenantId) params.set("tenantId", config.tenantId);
|
|
136
|
+
const qs = params.toString();
|
|
137
|
+
if (qs) path += `?${qs}`;
|
|
138
|
+
const raw = await httpRequest(config, {
|
|
139
|
+
method: "GET",
|
|
140
|
+
path,
|
|
141
|
+
requireAuth: true,
|
|
142
|
+
headers
|
|
143
|
+
});
|
|
144
|
+
if (!raw || !Array.isArray(raw.items)) {
|
|
145
|
+
throw new NeetruError(
|
|
146
|
+
"invalid_response",
|
|
147
|
+
"datastore.list missing items[]"
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
return raw.items;
|
|
151
|
+
},
|
|
152
|
+
async get(id) {
|
|
153
|
+
if (!id || typeof id !== "string") {
|
|
154
|
+
throw new NeetruError("validation_failed", "id required");
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
const raw = await httpRequest(config, {
|
|
158
|
+
method: "GET",
|
|
159
|
+
path: `/sdk/v1/datastore/${name}/${encodeURIComponent(id)}`,
|
|
160
|
+
requireAuth: true,
|
|
161
|
+
headers
|
|
162
|
+
});
|
|
163
|
+
return raw?.item ?? null;
|
|
164
|
+
} catch (err) {
|
|
165
|
+
if (err instanceof NeetruError && err.code === "not_found") return null;
|
|
166
|
+
throw err;
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
async add(data) {
|
|
170
|
+
if (!data || typeof data !== "object") {
|
|
171
|
+
throw new NeetruError("validation_failed", "data object required");
|
|
172
|
+
}
|
|
173
|
+
const raw = await httpRequest(config, {
|
|
174
|
+
method: "POST",
|
|
175
|
+
path: `/sdk/v1/datastore/${name}`,
|
|
176
|
+
body: { data },
|
|
177
|
+
requireAuth: true,
|
|
178
|
+
headers
|
|
179
|
+
});
|
|
180
|
+
if (!raw || typeof raw.id !== "string") {
|
|
181
|
+
throw new NeetruError("invalid_response", "datastore.add missing id");
|
|
182
|
+
}
|
|
183
|
+
return { ok: true, id: raw.id };
|
|
184
|
+
},
|
|
185
|
+
async set(id, data) {
|
|
186
|
+
if (!id || typeof id !== "string") {
|
|
187
|
+
throw new NeetruError("validation_failed", "id required");
|
|
188
|
+
}
|
|
189
|
+
await httpRequest(config, {
|
|
190
|
+
method: "PUT",
|
|
191
|
+
path: `/sdk/v1/datastore/${name}/${encodeURIComponent(id)}`,
|
|
192
|
+
body: { data },
|
|
193
|
+
requireAuth: true,
|
|
194
|
+
headers
|
|
195
|
+
});
|
|
196
|
+
return { ok: true };
|
|
197
|
+
},
|
|
198
|
+
async update(id, data) {
|
|
199
|
+
if (!id || typeof id !== "string") {
|
|
200
|
+
throw new NeetruError("validation_failed", "id required");
|
|
201
|
+
}
|
|
202
|
+
await httpRequest(config, {
|
|
203
|
+
method: "PATCH",
|
|
204
|
+
path: `/sdk/v1/datastore/${name}/${encodeURIComponent(id)}`,
|
|
205
|
+
body: { data },
|
|
206
|
+
requireAuth: true,
|
|
207
|
+
headers
|
|
208
|
+
});
|
|
209
|
+
return { ok: true };
|
|
210
|
+
},
|
|
211
|
+
async remove(id) {
|
|
212
|
+
if (!id || typeof id !== "string") {
|
|
213
|
+
throw new NeetruError("validation_failed", "id required");
|
|
214
|
+
}
|
|
215
|
+
await httpRequest(config, {
|
|
216
|
+
method: "DELETE",
|
|
217
|
+
path: `/sdk/v1/datastore/${name}/${encodeURIComponent(id)}`,
|
|
218
|
+
requireAuth: true,
|
|
219
|
+
headers
|
|
220
|
+
});
|
|
221
|
+
return { ok: true };
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export { createDbNamespace };
|
|
229
|
+
//# sourceMappingURL=db.mjs.map
|
|
230
|
+
//# sourceMappingURL=db.mjs.map
|
package/dist/db.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/db.ts"],"names":[],"mappings":"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","file":"db.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 * Datastore namespace (v0.3.1) — Sprint 8.\n *\n * `client.db.collection(name)` retorna um `DbCollectionRef` que mapeia para\n * `tenant_{tid}_{name}` no Core. Endpoints REST disponíveis (Sprint 8 LIVE):\n * - `GET /sdk/v1/datastore/{collection}?limit&where`\n * - `POST /sdk/v1/datastore/{collection}` (add)\n * - `GET /sdk/v1/datastore/{collection}/{id}`\n * - `PATCH /sdk/v1/datastore/{collection}/{id}` (update merge)\n * - `PUT /sdk/v1/datastore/{collection}/{id}` (set replace)\n * - `DELETE /sdk/v1/datastore/{collection}/{id}`\n *\n * O Core resolve `tenantId` via Bearer token. SDK passa `x-neetru-tenant`\n * pra dar visibilidade explícita; backend valida match com o token.\n *\n * Em dev (NEETRU_ENV=dev), `client.db` é `MockDb` (in-memory).\n */\nimport { httpRequest } from './http';\nimport { NeetruError } from './errors';\nimport type {\n DbCollectionRef,\n DbListOptions,\n DbNamespace,\n DbWhereFilter,\n ResolvedConfig,\n} from './types';\n\nconst COLL_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/;\n\nfunction assertValidCollection(name: string): void {\n if (!COLL_RE.test(name)) {\n throw new NeetruError(\n 'validation_failed',\n `Invalid collection name: \"${name}\". Must match ${COLL_RE}.`,\n );\n }\n}\n\n/** Serializa filtro pro formato `field:op:value` aceito pelo backend. */\nfunction serializeWhere(filter: DbWhereFilter): string {\n const { field, op, value } = filter;\n if (op === 'in') {\n if (!Array.isArray(value)) {\n throw new NeetruError(\n 'validation_failed',\n `where op=\"in\" requer value array (recebido: ${typeof value})`,\n );\n }\n return `${field}:in:${value.map((v) => String(v)).join(',')}`;\n }\n return `${field}:${op}:${String(value)}`;\n}\n\n/**\n * Constrói o namespace `db` real (HTTP) com binding ao tenantId resolvido.\n */\nexport function createDbNamespace(config: ResolvedConfig): DbNamespace {\n return {\n collection(name: string): DbCollectionRef {\n assertValidCollection(name);\n\n const headers: Record<string, string> = {};\n if (config.tenantId) headers['x-neetru-tenant'] = config.tenantId;\n\n return {\n async list<T = Record<string, unknown>>(\n opts?: DbListOptions,\n ): Promise<T[]> {\n const query: Record<string, string | number> = {};\n if (opts?.limit !== undefined) query.limit = opts.limit;\n\n // multi-where → request URL múltiplo `where=...&where=...`. Como\n // `httpRequest.query` é Record<>, monta path manualmente quando há\n // filtros pra preservar repetição da chave.\n let path = `/sdk/v1/datastore/${name}`;\n const params = new URLSearchParams();\n if (opts?.limit !== undefined) params.set('limit', String(opts.limit));\n if (opts?.where && opts.where.length > 0) {\n for (const f of opts.where) {\n params.append('where', serializeWhere(f));\n }\n }\n if (config.tenantId) params.set('tenantId', config.tenantId);\n const qs = params.toString();\n if (qs) path += `?${qs}`;\n\n const raw = await httpRequest<{ items?: unknown[] }>(config, {\n method: 'GET',\n path,\n requireAuth: true,\n headers,\n });\n if (!raw || !Array.isArray(raw.items)) {\n throw new NeetruError(\n 'invalid_response',\n 'datastore.list missing items[]',\n );\n }\n return raw.items as T[];\n },\n\n async get<T = Record<string, unknown>>(id: string): Promise<T | null> {\n if (!id || typeof id !== 'string') {\n throw new NeetruError('validation_failed', 'id required');\n }\n try {\n const raw = await httpRequest<{ item?: T | null }>(config, {\n method: 'GET',\n path: `/sdk/v1/datastore/${name}/${encodeURIComponent(id)}`,\n requireAuth: true,\n headers,\n });\n return raw?.item ?? null;\n } catch (err) {\n if (err instanceof NeetruError && err.code === 'not_found') return null;\n throw err;\n }\n },\n\n async add(data: Record<string, unknown>): Promise<{ ok: true; id: string }> {\n if (!data || typeof data !== 'object') {\n throw new NeetruError('validation_failed', 'data object required');\n }\n const raw = await httpRequest<{ ok?: boolean; id?: string }>(config, {\n method: 'POST',\n path: `/sdk/v1/datastore/${name}`,\n body: { data },\n requireAuth: true,\n headers,\n });\n if (!raw || typeof raw.id !== 'string') {\n throw new NeetruError('invalid_response', 'datastore.add missing id');\n }\n return { ok: true, id: raw.id };\n },\n\n async set(id: string, data: Record<string, unknown>): Promise<{ ok: true }> {\n if (!id || typeof id !== 'string') {\n throw new NeetruError('validation_failed', 'id required');\n }\n await httpRequest(config, {\n method: 'PUT',\n path: `/sdk/v1/datastore/${name}/${encodeURIComponent(id)}`,\n body: { data },\n requireAuth: true,\n headers,\n });\n return { ok: true };\n },\n\n async update(id: string, data: Record<string, unknown>): Promise<{ ok: true }> {\n if (!id || typeof id !== 'string') {\n throw new NeetruError('validation_failed', 'id required');\n }\n await httpRequest(config, {\n method: 'PATCH',\n path: `/sdk/v1/datastore/${name}/${encodeURIComponent(id)}`,\n body: { data },\n requireAuth: true,\n headers,\n });\n return { ok: true };\n },\n\n async remove(id: string): Promise<{ ok: true }> {\n if (!id || typeof id !== 'string') {\n throw new NeetruError('validation_failed', 'id required');\n }\n await httpRequest(config, {\n method: 'DELETE',\n path: `/sdk/v1/datastore/${name}/${encodeURIComponent(id)}`,\n requireAuth: true,\n headers,\n });\n return { ok: true };\n },\n };\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/errors.ts
|
|
4
|
+
var NeetruError = class _NeetruError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
status;
|
|
7
|
+
requestId;
|
|
8
|
+
constructor(code, message, status, requestId) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "NeetruError";
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.status = status;
|
|
13
|
+
this.requestId = requestId;
|
|
14
|
+
Object.setPrototypeOf(this, _NeetruError.prototype);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// src/http.ts
|
|
19
|
+
function statusToCode(status) {
|
|
20
|
+
if (status === 401) return "unauthorized";
|
|
21
|
+
if (status === 403) return "forbidden";
|
|
22
|
+
if (status === 404) return "not_found";
|
|
23
|
+
if (status === 422 || status === 400) return "validation_failed";
|
|
24
|
+
if (status === 429) return "rate_limited";
|
|
25
|
+
if (status >= 500) return "server_error";
|
|
26
|
+
return "unknown";
|
|
27
|
+
}
|
|
28
|
+
function buildUrl(baseUrl, path, query) {
|
|
29
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
30
|
+
const p = path.startsWith("/") ? path : `/${path}`;
|
|
31
|
+
const url = new URL(`${base}${p}`);
|
|
32
|
+
if (query) {
|
|
33
|
+
for (const [k, v] of Object.entries(query)) {
|
|
34
|
+
if (v === void 0) continue;
|
|
35
|
+
url.searchParams.set(k, String(v));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return url.toString();
|
|
39
|
+
}
|
|
40
|
+
async function safeJson(res) {
|
|
41
|
+
const text = await res.text();
|
|
42
|
+
if (!text) return void 0;
|
|
43
|
+
try {
|
|
44
|
+
return JSON.parse(text);
|
|
45
|
+
} catch {
|
|
46
|
+
return void 0;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function httpRequest(config, opts) {
|
|
50
|
+
const method = opts.method ?? "GET";
|
|
51
|
+
const url = buildUrl(config.baseUrl, opts.path, opts.query);
|
|
52
|
+
const headers = {
|
|
53
|
+
accept: "application/json",
|
|
54
|
+
...opts.headers
|
|
55
|
+
};
|
|
56
|
+
{
|
|
57
|
+
if (!config.apiKey) {
|
|
58
|
+
throw new NeetruError(
|
|
59
|
+
"missing_api_key",
|
|
60
|
+
"This operation requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var."
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
headers.authorization = `Bearer ${config.apiKey}`;
|
|
64
|
+
}
|
|
65
|
+
const init = { method, headers };
|
|
66
|
+
if (opts.body !== void 0 && method !== "GET" && method !== "DELETE") {
|
|
67
|
+
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
|
+
}
|
|
77
|
+
const requestId = res.headers.get("x-request-id") ?? res.headers.get("x-correlation-id") ?? void 0;
|
|
78
|
+
if (!res.ok) {
|
|
79
|
+
const body = await safeJson(res);
|
|
80
|
+
let code = statusToCode(res.status);
|
|
81
|
+
let message = `HTTP ${res.status}`;
|
|
82
|
+
if (body && typeof body === "object" && "error" in body) {
|
|
83
|
+
const errField = body.error;
|
|
84
|
+
if (typeof errField === "string") {
|
|
85
|
+
message = errField;
|
|
86
|
+
} else if (errField && typeof errField === "object") {
|
|
87
|
+
if (typeof errField.code === "string") code = errField.code;
|
|
88
|
+
if (typeof errField.message === "string") message = errField.message;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
throw new NeetruError(code, message, res.status, requestId);
|
|
92
|
+
}
|
|
93
|
+
const parsed = await safeJson(res);
|
|
94
|
+
return parsed;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/entitlements.ts
|
|
98
|
+
function toEntitlementCheck(raw) {
|
|
99
|
+
if (!raw || typeof raw !== "object") {
|
|
100
|
+
throw new NeetruError("invalid_response", "Entitlement response is not an object");
|
|
101
|
+
}
|
|
102
|
+
const r = raw;
|
|
103
|
+
if (typeof r.allowed !== "boolean") {
|
|
104
|
+
throw new NeetruError("invalid_response", "Entitlement response missing `allowed` boolean");
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
allowed: r.allowed,
|
|
108
|
+
productSlug: typeof r.productSlug === "string" ? r.productSlug : "",
|
|
109
|
+
feature: typeof r.feature === "string" ? r.feature : "",
|
|
110
|
+
reason: typeof r.reason === "string" ? r.reason : void 0
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function createEntitlementsNamespace(config) {
|
|
114
|
+
async function checkDetailed(productSlug, feature) {
|
|
115
|
+
if (!productSlug) throw new NeetruError("validation_failed", "productSlug is required");
|
|
116
|
+
if (!feature) throw new NeetruError("validation_failed", "feature is required");
|
|
117
|
+
const raw = await httpRequest(config, {
|
|
118
|
+
method: "GET",
|
|
119
|
+
path: "/api/v1/sdk/entitlements/check",
|
|
120
|
+
query: { slug: productSlug, feature }});
|
|
121
|
+
return toEntitlementCheck(raw);
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
/**
|
|
125
|
+
* Verifica se o caller pode usar `feature` no produto `productSlug`.
|
|
126
|
+
* Retorno simples: `true` libera, `false` bloqueia.
|
|
127
|
+
*
|
|
128
|
+
* Use `checkDetailed` se precisar do `reason` pra mensagem de upgrade.
|
|
129
|
+
*/
|
|
130
|
+
async check(productSlug, feature) {
|
|
131
|
+
const result = await checkDetailed(productSlug, feature);
|
|
132
|
+
return result.allowed;
|
|
133
|
+
},
|
|
134
|
+
checkDetailed
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
exports.createEntitlementsNamespace = createEntitlementsNamespace;
|
|
139
|
+
//# sourceMappingURL=entitlements.cjs.map
|
|
140
|
+
//# sourceMappingURL=entitlements.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/entitlements.ts"],"names":[],"mappings":"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","file":"entitlements.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 * Entitlements — verifica se o portador da apiKey pode usar feature X\n * do produto Y.\n *\n * Endpoint: `GET /api/v1/sdk/entitlements/check?slug=X&feature=Y`\n * Schema Firestore consultado pelo backend:\n * `entitlements/{userId}/products/{slug}` → `{ features: string[], plan, expiresAt }`\n *\n * v0.1 sem cache local — cada `check()` é uma request. Cache LRU é Sprint 3\n * (vide `docs/PLAN_SDK_NEETRU.md` Sprint 3).\n */\nimport { NeetruError } from './errors';\nimport { httpRequest } from './http';\nimport type { EntitlementCheck, ResolvedConfig } from './types';\n\ninterface RawEntitlementCheck {\n allowed?: boolean;\n productSlug?: string;\n feature?: string;\n reason?: string;\n}\n\nfunction toEntitlementCheck(raw: unknown): EntitlementCheck {\n if (!raw || typeof raw !== 'object') {\n throw new NeetruError('invalid_response', 'Entitlement response is not an object');\n }\n const r = raw as RawEntitlementCheck;\n if (typeof r.allowed !== 'boolean') {\n throw new NeetruError('invalid_response', 'Entitlement response missing `allowed` boolean');\n }\n return {\n allowed: r.allowed,\n productSlug: typeof r.productSlug === 'string' ? r.productSlug : '',\n feature: typeof r.feature === 'string' ? r.feature : '',\n reason: typeof r.reason === 'string' ? r.reason : undefined,\n };\n}\n\nexport function createEntitlementsNamespace(config: ResolvedConfig) {\n async function checkDetailed(productSlug: string, feature: string): Promise<EntitlementCheck> {\n if (!productSlug) throw new NeetruError('validation_failed', 'productSlug is required');\n if (!feature) throw new NeetruError('validation_failed', 'feature is required');\n\n const raw = await httpRequest<RawEntitlementCheck>(config, {\n method: 'GET',\n path: '/api/v1/sdk/entitlements/check',\n query: { slug: productSlug, feature },\n requireAuth: true,\n });\n return toEntitlementCheck(raw);\n }\n\n return {\n /**\n * Verifica se o caller pode usar `feature` no produto `productSlug`.\n * Retorno simples: `true` libera, `false` bloqueia.\n *\n * Use `checkDetailed` se precisar do `reason` pra mensagem de upgrade.\n */\n async check(productSlug: string, feature: string): Promise<boolean> {\n const result = await checkDetailed(productSlug, feature);\n return result.allowed;\n },\n checkDetailed,\n };\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { R as ResolvedConfig, E as EntitlementCheck } from './types-PKUaFtBY.cjs';
|
|
2
|
+
|
|
3
|
+
declare function createEntitlementsNamespace(config: ResolvedConfig): {
|
|
4
|
+
/**
|
|
5
|
+
* Verifica se o caller pode usar `feature` no produto `productSlug`.
|
|
6
|
+
* Retorno simples: `true` libera, `false` bloqueia.
|
|
7
|
+
*
|
|
8
|
+
* Use `checkDetailed` se precisar do `reason` pra mensagem de upgrade.
|
|
9
|
+
*/
|
|
10
|
+
check(productSlug: string, feature: string): Promise<boolean>;
|
|
11
|
+
checkDetailed: (productSlug: string, feature: string) => Promise<EntitlementCheck>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export { createEntitlementsNamespace };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { R as ResolvedConfig, E as EntitlementCheck } from './types-PKUaFtBY.js';
|
|
2
|
+
|
|
3
|
+
declare function createEntitlementsNamespace(config: ResolvedConfig): {
|
|
4
|
+
/**
|
|
5
|
+
* Verifica se o caller pode usar `feature` no produto `productSlug`.
|
|
6
|
+
* Retorno simples: `true` libera, `false` bloqueia.
|
|
7
|
+
*
|
|
8
|
+
* Use `checkDetailed` se precisar do `reason` pra mensagem de upgrade.
|
|
9
|
+
*/
|
|
10
|
+
check(productSlug: string, feature: string): Promise<boolean>;
|
|
11
|
+
checkDetailed: (productSlug: string, feature: string) => Promise<EntitlementCheck>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export { createEntitlementsNamespace };
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var NeetruError = class _NeetruError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
status;
|
|
5
|
+
requestId;
|
|
6
|
+
constructor(code, message, status, requestId) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "NeetruError";
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.requestId = requestId;
|
|
12
|
+
Object.setPrototypeOf(this, _NeetruError.prototype);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/http.ts
|
|
17
|
+
function statusToCode(status) {
|
|
18
|
+
if (status === 401) return "unauthorized";
|
|
19
|
+
if (status === 403) return "forbidden";
|
|
20
|
+
if (status === 404) return "not_found";
|
|
21
|
+
if (status === 422 || status === 400) return "validation_failed";
|
|
22
|
+
if (status === 429) return "rate_limited";
|
|
23
|
+
if (status >= 500) return "server_error";
|
|
24
|
+
return "unknown";
|
|
25
|
+
}
|
|
26
|
+
function buildUrl(baseUrl, path, query) {
|
|
27
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
28
|
+
const p = path.startsWith("/") ? path : `/${path}`;
|
|
29
|
+
const url = new URL(`${base}${p}`);
|
|
30
|
+
if (query) {
|
|
31
|
+
for (const [k, v] of Object.entries(query)) {
|
|
32
|
+
if (v === void 0) continue;
|
|
33
|
+
url.searchParams.set(k, String(v));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return url.toString();
|
|
37
|
+
}
|
|
38
|
+
async function safeJson(res) {
|
|
39
|
+
const text = await res.text();
|
|
40
|
+
if (!text) return void 0;
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(text);
|
|
43
|
+
} catch {
|
|
44
|
+
return void 0;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function httpRequest(config, opts) {
|
|
48
|
+
const method = opts.method ?? "GET";
|
|
49
|
+
const url = buildUrl(config.baseUrl, opts.path, opts.query);
|
|
50
|
+
const headers = {
|
|
51
|
+
accept: "application/json",
|
|
52
|
+
...opts.headers
|
|
53
|
+
};
|
|
54
|
+
{
|
|
55
|
+
if (!config.apiKey) {
|
|
56
|
+
throw new NeetruError(
|
|
57
|
+
"missing_api_key",
|
|
58
|
+
"This operation requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var."
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
headers.authorization = `Bearer ${config.apiKey}`;
|
|
62
|
+
}
|
|
63
|
+
const init = { method, headers };
|
|
64
|
+
if (opts.body !== void 0 && method !== "GET" && method !== "DELETE") {
|
|
65
|
+
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
|
+
}
|
|
75
|
+
const requestId = res.headers.get("x-request-id") ?? res.headers.get("x-correlation-id") ?? void 0;
|
|
76
|
+
if (!res.ok) {
|
|
77
|
+
const body = await safeJson(res);
|
|
78
|
+
let code = statusToCode(res.status);
|
|
79
|
+
let message = `HTTP ${res.status}`;
|
|
80
|
+
if (body && typeof body === "object" && "error" in body) {
|
|
81
|
+
const errField = body.error;
|
|
82
|
+
if (typeof errField === "string") {
|
|
83
|
+
message = errField;
|
|
84
|
+
} else if (errField && typeof errField === "object") {
|
|
85
|
+
if (typeof errField.code === "string") code = errField.code;
|
|
86
|
+
if (typeof errField.message === "string") message = errField.message;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
throw new NeetruError(code, message, res.status, requestId);
|
|
90
|
+
}
|
|
91
|
+
const parsed = await safeJson(res);
|
|
92
|
+
return parsed;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/entitlements.ts
|
|
96
|
+
function toEntitlementCheck(raw) {
|
|
97
|
+
if (!raw || typeof raw !== "object") {
|
|
98
|
+
throw new NeetruError("invalid_response", "Entitlement response is not an object");
|
|
99
|
+
}
|
|
100
|
+
const r = raw;
|
|
101
|
+
if (typeof r.allowed !== "boolean") {
|
|
102
|
+
throw new NeetruError("invalid_response", "Entitlement response missing `allowed` boolean");
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
allowed: r.allowed,
|
|
106
|
+
productSlug: typeof r.productSlug === "string" ? r.productSlug : "",
|
|
107
|
+
feature: typeof r.feature === "string" ? r.feature : "",
|
|
108
|
+
reason: typeof r.reason === "string" ? r.reason : void 0
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function createEntitlementsNamespace(config) {
|
|
112
|
+
async function checkDetailed(productSlug, feature) {
|
|
113
|
+
if (!productSlug) throw new NeetruError("validation_failed", "productSlug is required");
|
|
114
|
+
if (!feature) throw new NeetruError("validation_failed", "feature is required");
|
|
115
|
+
const raw = await httpRequest(config, {
|
|
116
|
+
method: "GET",
|
|
117
|
+
path: "/api/v1/sdk/entitlements/check",
|
|
118
|
+
query: { slug: productSlug, feature }});
|
|
119
|
+
return toEntitlementCheck(raw);
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
/**
|
|
123
|
+
* Verifica se o caller pode usar `feature` no produto `productSlug`.
|
|
124
|
+
* Retorno simples: `true` libera, `false` bloqueia.
|
|
125
|
+
*
|
|
126
|
+
* Use `checkDetailed` se precisar do `reason` pra mensagem de upgrade.
|
|
127
|
+
*/
|
|
128
|
+
async check(productSlug, feature) {
|
|
129
|
+
const result = await checkDetailed(productSlug, feature);
|
|
130
|
+
return result.allowed;
|
|
131
|
+
},
|
|
132
|
+
checkDetailed
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export { createEntitlementsNamespace };
|
|
137
|
+
//# sourceMappingURL=entitlements.mjs.map
|
|
138
|
+
//# sourceMappingURL=entitlements.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/entitlements.ts"],"names":[],"mappings":";AAgCO,IAAM,WAAA,GAAN,MAAM,YAAA,SAAoB,KAAA,CAAM;AAAA,EACrB,IAAA;AAAA,EACA,MAAA;AAAA,EACA,SAAA;AAAA,EAEhB,WAAA,CACE,IAAA,EACA,OAAA,EACA,MAAA,EACA,SAAA,EACA;AACA,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,aAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AAEjB,IAAA,MAAA,CAAO,cAAA,CAAe,IAAA,EAAM,YAAA,CAAY,SAAS,CAAA;AAAA,EACnD;AACF,CAAA;;;AClBA,SAAS,aAAa,MAAA,EAAiC;AACrD,EAAA,IAAI,MAAA,KAAW,KAAK,OAAO,cAAA;AAC3B,EAAA,IAAI,MAAA,KAAW,KAAK,OAAO,WAAA;AAC3B,EAAA,IAAI,MAAA,KAAW,KAAK,OAAO,WAAA;AAC3B,EAAA,IAAI,MAAA,KAAW,GAAA,IAAO,MAAA,KAAW,GAAA,EAAK,OAAO,mBAAA;AAC7C,EAAA,IAAI,MAAA,KAAW,KAAK,OAAO,cAAA;AAC3B,EAAA,IAAI,MAAA,IAAU,KAAK,OAAO,cAAA;AAC1B,EAAA,OAAO,SAAA;AACT;AAEA,SAAS,QAAA,CAAS,OAAA,EAAiB,IAAA,EAAc,KAAA,EAA6C;AAE5F,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AACvC,EAAA,MAAM,IAAI,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,GAAI,IAAA,GAAO,IAAI,IAAI,CAAA,CAAA;AAChD,EAAA,MAAM,MAAM,IAAI,GAAA,CAAI,GAAG,IAAI,CAAA,EAAG,CAAC,CAAA,CAAE,CAAA;AACjC,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,KAAA,MAAW,CAAC,CAAA,EAAG,CAAC,KAAK,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,EAAG;AAC1C,MAAA,IAAI,MAAM,MAAA,EAAW;AACrB,MAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,CAAA,EAAG,MAAA,CAAO,CAAC,CAAC,CAAA;AAAA,IACnC;AAAA,EACF;AACA,EAAA,OAAO,IAAI,QAAA,EAAS;AACtB;AAGA,eAAe,SAAS,GAAA,EAAiC;AACvD,EAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAC5B,EAAA,IAAI,CAAC,MAAM,OAAO,MAAA;AAClB,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,EACxB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,MAAA;AAAA,EACT;AACF;AAMA,eAAsB,WAAA,CACpB,QACA,IAAA,EACY;AACZ,EAAA,MAAM,MAAA,GAAS,KAAK,MAAA,IAAU,KAAA;AAC9B,EAAA,MAAM,MAAM,QAAA,CAAS,MAAA,CAAO,SAAS,IAAA,CAAK,IAAA,EAAM,KAAK,KAAK,CAAA;AAE1D,EAAA,MAAM,OAAA,GAAkC;AAAA,IACtC,MAAA,EAAQ,kBAAA;AAAA,IACR,GAAG,IAAA,CAAK;AAAA,GACV;AAEA,EAAsB;AACpB,IAAA,IAAI,CAAC,OAAO,MAAA,EAAQ;AAClB,MAAA,MAAM,IAAI,WAAA;AAAA,QACR,iBAAA;AAAA,QACA;AAAA,OACF;AAAA,IACF;AACA,IAAA,OAAA,CAAQ,aAAA,GAAgB,CAAA,OAAA,EAAU,MAAA,CAAO,MAAM,CAAA,CAAA;AAAA,EACjD;AAEA,EAAA,MAAM,IAAA,GAAoB,EAAE,MAAA,EAAQ,OAAA,EAAQ;AAC5C,EAAA,IAAI,KAAK,IAAA,KAAS,MAAA,IAAa,MAAA,KAAW,KAAA,IAAS,WAAW,QAAA,EAAU;AACtE,IAAA,OAAA,CAAQ,cAAc,CAAA,GAAI,kBAAA;AAC1B,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA,CAAK,SAAA,CAAU,IAAA,CAAK,IAAI,CAAA;AAAA,EACtC;AAEA,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA,GAAA,GAAM,MAAM,MAAA,CAAO,KAAA,CAAM,GAAA,EAAK,IAAI,CAAA;AAAA,EACpC,SAAS,GAAA,EAAK;AACZ,IAAA,MAAM,OAAA,GAAU,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,cAAA;AACrD,IAAA,MAAM,IAAI,WAAA,CAAY,eAAA,EAAiB,CAAA,eAAA,EAAkB,OAAO,CAAA,CAAE,CAAA;AAAA,EACpE;AAEA,EAAA,MAAM,SAAA,GAAY,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,cAAc,KAAK,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,kBAAkB,CAAA,IAAK,MAAA;AAE5F,EAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,IAAA,MAAM,IAAA,GAAQ,MAAM,QAAA,CAAS,GAAG,CAAA;AAChC,IAAA,IAAI,IAAA,GAAe,YAAA,CAAa,GAAA,CAAI,MAAM,CAAA;AAC1C,IAAA,IAAI,OAAA,GAAU,CAAA,KAAA,EAAQ,GAAA,CAAI,MAAM,CAAA,CAAA;AAChC,IAAA,IAAI,IAAA,IAAQ,OAAO,IAAA,KAAS,QAAA,IAAY,WAAW,IAAA,EAAM;AACvD,MAAA,MAAM,WAAW,IAAA,CAAK,KAAA;AACtB,MAAA,IAAI,OAAO,aAAa,QAAA,EAAU;AAChC,QAAA,OAAA,GAAU,QAAA;AAAA,MACZ,CAAA,MAAA,IAAW,QAAA,IAAY,OAAO,QAAA,KAAa,QAAA,EAAU;AACnD,QAAA,IAAI,OAAO,QAAA,CAAS,IAAA,KAAS,QAAA,SAAiB,QAAA,CAAS,IAAA;AACvD,QAAA,IAAI,OAAO,QAAA,CAAS,OAAA,KAAY,QAAA,YAAoB,QAAA,CAAS,OAAA;AAAA,MAC/D;AAAA,IACF;AACA,IAAA,MAAM,IAAI,WAAA,CAAY,IAAA,EAAM,OAAA,EAAS,GAAA,CAAI,QAAQ,SAAS,CAAA;AAAA,EAC5D;AAEA,EAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,GAAG,CAAA;AAEjC,EAAA,OAAO,MAAA;AACT;;;AC3GA,SAAS,mBAAmB,GAAA,EAAgC;AAC1D,EAAA,IAAI,CAAC,GAAA,IAAO,OAAO,GAAA,KAAQ,QAAA,EAAU;AACnC,IAAA,MAAM,IAAI,WAAA,CAAY,kBAAA,EAAoB,uCAAuC,CAAA;AAAA,EACnF;AACA,EAAA,MAAM,CAAA,GAAI,GAAA;AACV,EAAA,IAAI,OAAO,CAAA,CAAE,OAAA,KAAY,SAAA,EAAW;AAClC,IAAA,MAAM,IAAI,WAAA,CAAY,kBAAA,EAAoB,gDAAgD,CAAA;AAAA,EAC5F;AACA,EAAA,OAAO;AAAA,IACL,SAAS,CAAA,CAAE,OAAA;AAAA,IACX,aAAa,OAAO,CAAA,CAAE,WAAA,KAAgB,QAAA,GAAW,EAAE,WAAA,GAAc,EAAA;AAAA,IACjE,SAAS,OAAO,CAAA,CAAE,OAAA,KAAY,QAAA,GAAW,EAAE,OAAA,GAAU,EAAA;AAAA,IACrD,QAAQ,OAAO,CAAA,CAAE,MAAA,KAAW,QAAA,GAAW,EAAE,MAAA,GAAS;AAAA,GACpD;AACF;AAEO,SAAS,4BAA4B,MAAA,EAAwB;AAClE,EAAA,eAAe,aAAA,CAAc,aAAqB,OAAA,EAA4C;AAC5F,IAAA,IAAI,CAAC,WAAA,EAAa,MAAM,IAAI,WAAA,CAAY,qBAAqB,yBAAyB,CAAA;AACtF,IAAA,IAAI,CAAC,OAAA,EAAS,MAAM,IAAI,WAAA,CAAY,qBAAqB,qBAAqB,CAAA;AAE9E,IAAA,MAAM,GAAA,GAAM,MAAM,WAAA,CAAiC,MAAA,EAAQ;AAAA,MACzD,MAAA,EAAQ,KAAA;AAAA,MACR,IAAA,EAAM,gCAAA;AAAA,MACN,KAAA,EAAO,EAAE,IAAA,EAAM,WAAA,EAAa,OAAA,EAE9B,CAAC,CAAA;AACD,IAAA,OAAO,mBAAmB,GAAG,CAAA;AAAA,EAC/B;AAEA,EAAA,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOL,MAAM,KAAA,CAAM,WAAA,EAAqB,OAAA,EAAmC;AAClE,MAAA,MAAM,MAAA,GAAS,MAAM,aAAA,CAAc,WAAA,EAAa,OAAO,CAAA;AACvD,MAAA,OAAO,MAAA,CAAO,OAAA;AAAA,IAChB,CAAA;AAAA,IACA;AAAA,GACF;AACF","file":"entitlements.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 * Entitlements — verifica se o portador da apiKey pode usar feature X\n * do produto Y.\n *\n * Endpoint: `GET /api/v1/sdk/entitlements/check?slug=X&feature=Y`\n * Schema Firestore consultado pelo backend:\n * `entitlements/{userId}/products/{slug}` → `{ features: string[], plan, expiresAt }`\n *\n * v0.1 sem cache local — cada `check()` é uma request. Cache LRU é Sprint 3\n * (vide `docs/PLAN_SDK_NEETRU.md` Sprint 3).\n */\nimport { NeetruError } from './errors';\nimport { httpRequest } from './http';\nimport type { EntitlementCheck, ResolvedConfig } from './types';\n\ninterface RawEntitlementCheck {\n allowed?: boolean;\n productSlug?: string;\n feature?: string;\n reason?: string;\n}\n\nfunction toEntitlementCheck(raw: unknown): EntitlementCheck {\n if (!raw || typeof raw !== 'object') {\n throw new NeetruError('invalid_response', 'Entitlement response is not an object');\n }\n const r = raw as RawEntitlementCheck;\n if (typeof r.allowed !== 'boolean') {\n throw new NeetruError('invalid_response', 'Entitlement response missing `allowed` boolean');\n }\n return {\n allowed: r.allowed,\n productSlug: typeof r.productSlug === 'string' ? r.productSlug : '',\n feature: typeof r.feature === 'string' ? r.feature : '',\n reason: typeof r.reason === 'string' ? r.reason : undefined,\n };\n}\n\nexport function createEntitlementsNamespace(config: ResolvedConfig) {\n async function checkDetailed(productSlug: string, feature: string): Promise<EntitlementCheck> {\n if (!productSlug) throw new NeetruError('validation_failed', 'productSlug is required');\n if (!feature) throw new NeetruError('validation_failed', 'feature is required');\n\n const raw = await httpRequest<RawEntitlementCheck>(config, {\n method: 'GET',\n path: '/api/v1/sdk/entitlements/check',\n query: { slug: productSlug, feature },\n requireAuth: true,\n });\n return toEntitlementCheck(raw);\n }\n\n return {\n /**\n * Verifica se o caller pode usar `feature` no produto `productSlug`.\n * Retorno simples: `true` libera, `false` bloqueia.\n *\n * Use `checkDetailed` se precisar do `reason` pra mensagem de upgrade.\n */\n async check(productSlug: string, feature: string): Promise<boolean> {\n const result = await checkDetailed(productSlug, feature);\n return result.allowed;\n },\n checkDetailed,\n };\n}\n"]}
|