@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
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tipos públicos do SDK Neetru.
|
|
3
|
+
*
|
|
4
|
+
* Vendor-neutral: nada aqui referencia `firebase/*`, `stripe`, ou qualquer
|
|
5
|
+
* provider interno. Caller só vê superfície neutra (Product, Entitlement, ...).
|
|
6
|
+
*/
|
|
7
|
+
/** Default base URL pra API pública Neetru. */
|
|
8
|
+
declare const DEFAULT_BASE_URL = "https://api.neetru.com";
|
|
9
|
+
/**
|
|
10
|
+
* Configuração passada pra `createNeetruClient`.
|
|
11
|
+
*/
|
|
12
|
+
interface NeetruClientConfig {
|
|
13
|
+
/**
|
|
14
|
+
* Bearer token Neetru (formato `nrt_<keyId>_<secret>`). Opcional —
|
|
15
|
+
* se ausente, o SDK tenta `process.env.NEETRU_API_KEY` em Node.
|
|
16
|
+
*
|
|
17
|
+
* Endpoints públicos (catalog) funcionam sem apiKey. Endpoints autenticados
|
|
18
|
+
* (entitlements, telemetry) lançam `missing_api_key` se ausente.
|
|
19
|
+
*/
|
|
20
|
+
apiKey?: string;
|
|
21
|
+
/**
|
|
22
|
+
* URL base da API Neetru. Default: `https://api.neetru.com`.
|
|
23
|
+
* Útil em testes apontando pra staging ou mock server.
|
|
24
|
+
*/
|
|
25
|
+
baseUrl?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Implementação de fetch customizada. Default: `globalThis.fetch`.
|
|
28
|
+
* Necessário em runtimes que não exponham fetch global por default.
|
|
29
|
+
*/
|
|
30
|
+
fetch?: typeof globalThis.fetch;
|
|
31
|
+
/**
|
|
32
|
+
* Modo de runtime. `dev` ativa mocks (auth retorna user fixture, usage só
|
|
33
|
+
* loga, support retorna lista fake). `workspace` e `prod` chamam HTTP real.
|
|
34
|
+
*
|
|
35
|
+
* Default: lê `process.env.NEETRU_ENV` (Node) ou `globalThis.NEETRU_ENV`
|
|
36
|
+
* (browser); fallback `prod`.
|
|
37
|
+
*/
|
|
38
|
+
env?: 'dev' | 'workspace' | 'prod';
|
|
39
|
+
/**
|
|
40
|
+
* Override de mocks — útil em tests do consumer pra injetar fixtures
|
|
41
|
+
* determinísticos (sem depender de NEETRU_ENV=dev).
|
|
42
|
+
*/
|
|
43
|
+
mocks?: {
|
|
44
|
+
auth?: AuthNamespace;
|
|
45
|
+
usage?: UsageNamespace;
|
|
46
|
+
support?: SupportNamespace;
|
|
47
|
+
entitlements?: NeetruClient['entitlements'];
|
|
48
|
+
db?: DbNamespace;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* v0.3 — productId default usado por `usage.report()` / `usage.check()` /
|
|
52
|
+
* `db.collection()` quando não passado explicitamente nas options.
|
|
53
|
+
*/
|
|
54
|
+
productId?: string;
|
|
55
|
+
/**
|
|
56
|
+
* v0.3 — tenantId default usado por `usage.report()` / `usage.check()` /
|
|
57
|
+
* `db.collection()` quando não passado explicitamente nas options.
|
|
58
|
+
*/
|
|
59
|
+
tenantId?: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Status do produto no catálogo público.
|
|
63
|
+
*/
|
|
64
|
+
type ProductStatus = 'live' | 'soon' | 'beta';
|
|
65
|
+
/**
|
|
66
|
+
* Produto SaaS publicado pelo Neetru Core.
|
|
67
|
+
*
|
|
68
|
+
* Schema neutro — alinhado com `public_products/{slug}` no backend mas
|
|
69
|
+
* nunca expõe campos internos (Firestore Timestamps, draft state, etc).
|
|
70
|
+
*/
|
|
71
|
+
interface Product {
|
|
72
|
+
/** Identificador estável do produto, ex: `neetru-pulse`. */
|
|
73
|
+
slug: string;
|
|
74
|
+
/** Nome de exibição, ex: `Neetru Pulse`. */
|
|
75
|
+
name: string;
|
|
76
|
+
/** Subtítulo curto, ex: `Gestão de operações`. */
|
|
77
|
+
tagline?: string;
|
|
78
|
+
/** Descrição longa em prosa. */
|
|
79
|
+
description?: string;
|
|
80
|
+
/** Status público do produto. */
|
|
81
|
+
status?: ProductStatus;
|
|
82
|
+
/** Hint de ícone (catálogo de keys interno do Core). */
|
|
83
|
+
iconKey?: string;
|
|
84
|
+
/** Link override do CTA principal (default: página do produto). */
|
|
85
|
+
ctaHref?: string;
|
|
86
|
+
/** Label override do CTA. */
|
|
87
|
+
ctaLabel?: string;
|
|
88
|
+
/** Lista opcional de planos cobrados. Pode ser preenchida v0.2+. */
|
|
89
|
+
plans?: ProductPlan[];
|
|
90
|
+
}
|
|
91
|
+
/** Plano cobrado de um produto (placeholder v0.1 — schema final v0.2). */
|
|
92
|
+
interface ProductPlan {
|
|
93
|
+
id: string;
|
|
94
|
+
name: string;
|
|
95
|
+
/** Preço mensal em centavos (BRL). Pode ser undefined em planos custom. */
|
|
96
|
+
amountCents?: number;
|
|
97
|
+
features?: string[];
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Resposta do `client.catalog.list()`.
|
|
101
|
+
*/
|
|
102
|
+
interface CatalogListResponse {
|
|
103
|
+
products: Product[];
|
|
104
|
+
fetchedAt: string;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Resposta do `client.entitlements.check(productSlug, feature)`.
|
|
108
|
+
*
|
|
109
|
+
* `allowed` é o sinal forte; `reason` ajuda a debugar (não exibir pro user
|
|
110
|
+
* final).
|
|
111
|
+
*/
|
|
112
|
+
interface EntitlementCheck {
|
|
113
|
+
allowed: boolean;
|
|
114
|
+
productSlug: string;
|
|
115
|
+
feature: string;
|
|
116
|
+
/** Reason code estável: `granted` | `not_subscribed` | `feature_not_in_plan` | `expired`. */
|
|
117
|
+
reason?: string;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Payload pra `client.telemetry.event()`.
|
|
121
|
+
*/
|
|
122
|
+
interface TelemetryEventInput {
|
|
123
|
+
/** Nome do evento, ex: `dashboard_opened`, `report_exported`. */
|
|
124
|
+
name: string;
|
|
125
|
+
/** Atributos adicionais (chave → valor primitivo). */
|
|
126
|
+
properties?: Record<string, string | number | boolean | null>;
|
|
127
|
+
/** Timestamp ISO opcional; default = server time. */
|
|
128
|
+
timestamp?: string;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Confirmação de aceitação do evento de telemetria.
|
|
132
|
+
*/
|
|
133
|
+
interface TelemetryEventAck {
|
|
134
|
+
ok: true;
|
|
135
|
+
/** ID gerado pelo backend pra evento (`usage_events/{id}`). */
|
|
136
|
+
eventId: string;
|
|
137
|
+
}
|
|
138
|
+
type TelemetryLogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
|
139
|
+
interface TelemetryLogInput {
|
|
140
|
+
level: TelemetryLogLevel;
|
|
141
|
+
message: string;
|
|
142
|
+
metadata?: Record<string, unknown>;
|
|
143
|
+
/** Override correlationId (default: lê dos headers/env automaticamente). */
|
|
144
|
+
correlationId?: string;
|
|
145
|
+
/** Slug do produto referenciado. Backend infere do escopo do token se ausente. */
|
|
146
|
+
productSlug?: string;
|
|
147
|
+
}
|
|
148
|
+
interface TelemetryLogAck {
|
|
149
|
+
ok: true;
|
|
150
|
+
/** ID do log gravado (`logs/{productId}/{yyyymmdd}/{logId}`) — opcional em mocks. */
|
|
151
|
+
logId?: string;
|
|
152
|
+
/** Indicador de modo (mock vs http). */
|
|
153
|
+
mode: 'mock' | 'http';
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Handle imutável retornado por `createNeetruClient`. Carrega namespaces
|
|
157
|
+
* `auth`, `catalog`, `entitlements`, `telemetry`, `usage`, `support`.
|
|
158
|
+
*
|
|
159
|
+
* Cada namespace é exposto como objeto chamável — espelha API Firebase
|
|
160
|
+
* (`auth`, `firestore`) ou Stripe (`paymentIntents`, `customers`).
|
|
161
|
+
*/
|
|
162
|
+
interface NeetruClient {
|
|
163
|
+
/** Configuração resolvida (apiKey, baseUrl, fetch). Read-only. */
|
|
164
|
+
readonly config: ResolvedConfig;
|
|
165
|
+
/** Namespace auth (v0.2 — OIDC + mocks dev). */
|
|
166
|
+
readonly auth: AuthNamespace;
|
|
167
|
+
/** Namespace catálogo público de produtos. */
|
|
168
|
+
readonly catalog: {
|
|
169
|
+
/** Lista produtos publicados. */
|
|
170
|
+
list(opts?: CatalogListOptions): Promise<CatalogListResponse>;
|
|
171
|
+
/** Busca produto único por slug. */
|
|
172
|
+
get(slug: string): Promise<Product>;
|
|
173
|
+
};
|
|
174
|
+
/** Namespace entitlements. */
|
|
175
|
+
readonly entitlements: {
|
|
176
|
+
/**
|
|
177
|
+
* Verifica se o portador da apiKey pode usar `feature` do produto `slug`.
|
|
178
|
+
* Retorna boolean por contrato simplificado v0.1.
|
|
179
|
+
*/
|
|
180
|
+
check(productSlug: string, feature: string): Promise<boolean>;
|
|
181
|
+
/** Variante que retorna o objeto completo com `reason`. */
|
|
182
|
+
checkDetailed(productSlug: string, feature: string): Promise<EntitlementCheck>;
|
|
183
|
+
};
|
|
184
|
+
/** Namespace telemetria. */
|
|
185
|
+
readonly telemetry: {
|
|
186
|
+
/** Persiste um evento. Não lança em rate-limit não-fatal — relança outros erros. */
|
|
187
|
+
event(input: TelemetryEventInput): Promise<TelemetryEventAck>;
|
|
188
|
+
/**
|
|
189
|
+
* Registra um log estruturado per-product (Sprint 6).
|
|
190
|
+
*
|
|
191
|
+
* - Em `NEETRU_ENV=dev`: apenas console.{level} (sem network).
|
|
192
|
+
* - Em `workspace`/`prod`: POST `/sdk/v1/telemetry/log` com Bearer + correlationId.
|
|
193
|
+
*/
|
|
194
|
+
log(input: TelemetryLogInput): Promise<TelemetryLogAck>;
|
|
195
|
+
};
|
|
196
|
+
/** Namespace usage (v0.2 — track + quota; v0.3 — report + check). */
|
|
197
|
+
readonly usage: UsageNamespace;
|
|
198
|
+
/** Namespace support (v0.2 — tickets). */
|
|
199
|
+
readonly support: SupportNamespace;
|
|
200
|
+
/** Namespace db (v0.3 — coleções tenant-scoped). */
|
|
201
|
+
readonly db: DbNamespace;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* User retornado pelo OIDC ID token. Schema neutro — não vaza Firebase
|
|
205
|
+
* decoded token shape. Subset estável do RFC 7519 + custom claims Neetru.
|
|
206
|
+
*/
|
|
207
|
+
interface NeetruUser {
|
|
208
|
+
/** Subject — uid estável. */
|
|
209
|
+
uid: string;
|
|
210
|
+
email: string;
|
|
211
|
+
emailVerified?: boolean;
|
|
212
|
+
displayName?: string;
|
|
213
|
+
photoURL?: string;
|
|
214
|
+
/** Custom claim Neetru — true quando staff. */
|
|
215
|
+
isStaff?: boolean;
|
|
216
|
+
/** Custom claim Neetru — true quando customer enrolled. */
|
|
217
|
+
isCustomer?: boolean;
|
|
218
|
+
/** Tenant assigned (multi-tenant deployments). */
|
|
219
|
+
tenantId?: string;
|
|
220
|
+
/** Outros claims OIDC (aud, iss, iat, exp). */
|
|
221
|
+
[extra: string]: unknown;
|
|
222
|
+
}
|
|
223
|
+
interface SignInOptions {
|
|
224
|
+
/** OIDC redirect_uri override. Default = window.location origin. */
|
|
225
|
+
redirectUri?: string;
|
|
226
|
+
/** OIDC scope. Default `openid profile email`. */
|
|
227
|
+
scope?: string;
|
|
228
|
+
/** Onde mandar após login completo. Default = window.location.href. */
|
|
229
|
+
postLoginRedirect?: string;
|
|
230
|
+
}
|
|
231
|
+
type AuthStateListener = (user: NeetruUser | null) => void;
|
|
232
|
+
interface AuthNamespace {
|
|
233
|
+
/**
|
|
234
|
+
* Inicia fluxo de login OIDC. Em dev (`NEETRU_ENV=dev`) retorna mock user
|
|
235
|
+
* direto sem redirect. Em prod redireciona pro authorize endpoint.
|
|
236
|
+
*/
|
|
237
|
+
signIn(options?: SignInOptions): Promise<NeetruUser | void>;
|
|
238
|
+
/** Limpa session local + revoga refresh token no servidor. */
|
|
239
|
+
signOut(): Promise<void>;
|
|
240
|
+
/** Retorna o user atual (do id_token cached) ou `null` se não logado. */
|
|
241
|
+
getUser(): NeetruUser | null;
|
|
242
|
+
/**
|
|
243
|
+
* Subscreve a mudanças de estado de auth. Listener é invocado imediatamente
|
|
244
|
+
* com user atual. Retorna função de unsubscribe.
|
|
245
|
+
*/
|
|
246
|
+
onAuthStateChanged(listener: AuthStateListener): () => void;
|
|
247
|
+
}
|
|
248
|
+
interface UsageEventInput {
|
|
249
|
+
/** Nome do evento, ex: `report_generated`, `api_call`. */
|
|
250
|
+
event: string;
|
|
251
|
+
/** Atributos do evento. Valores primitivos serializáveis. */
|
|
252
|
+
properties?: Record<string, string | number | boolean | null>;
|
|
253
|
+
/** Quantidade — default 1 (1 evento). Útil pra batch. */
|
|
254
|
+
quantity?: number;
|
|
255
|
+
}
|
|
256
|
+
interface UsageQuota {
|
|
257
|
+
metric: string;
|
|
258
|
+
/** Quantidade já consumida no período. */
|
|
259
|
+
used: number;
|
|
260
|
+
/** Limite total. -1 = unlimited. */
|
|
261
|
+
limit: number;
|
|
262
|
+
/** ISO timestamp do reset (próximo período). */
|
|
263
|
+
resetsAt?: string;
|
|
264
|
+
/** Plano que define o limite. */
|
|
265
|
+
plan?: string;
|
|
266
|
+
}
|
|
267
|
+
interface UsageNamespace {
|
|
268
|
+
/** Persiste um evento de uso. Mock em dev, POST `/sdk/v1/usage/record` em prod. */
|
|
269
|
+
track(event: string, props?: UsageEventInput['properties']): Promise<{
|
|
270
|
+
ok: true;
|
|
271
|
+
}>;
|
|
272
|
+
/** Lê quota atual de uma métrica. Mock em dev, GET `/sdk/v1/usage/quota` em prod. */
|
|
273
|
+
getQuota(metric: string): Promise<UsageQuota>;
|
|
274
|
+
/**
|
|
275
|
+
* v0.3 — Reporta consumo de um resource metered (POST /sdk/v1/usage/record
|
|
276
|
+
* com `{productId, tenantId, resource, qty}`). Em dev acumula no mock.
|
|
277
|
+
*
|
|
278
|
+
* Diferente de `track()`: usa o endpoint canônico Sprint 7 que incrementa
|
|
279
|
+
* `usage_counters/{tenantId}_{productId}_{resource}_{yyyymm}` atomicamente.
|
|
280
|
+
*
|
|
281
|
+
* `productId`/`tenantId` são lidos do contexto resolvido do client se
|
|
282
|
+
* ausentes nas options.
|
|
283
|
+
*/
|
|
284
|
+
report(resource: string, qty?: number, options?: {
|
|
285
|
+
productId?: string;
|
|
286
|
+
tenantId?: string;
|
|
287
|
+
}): Promise<{
|
|
288
|
+
ok: true;
|
|
289
|
+
counterId?: string;
|
|
290
|
+
value?: number;
|
|
291
|
+
limit?: number;
|
|
292
|
+
remaining?: number;
|
|
293
|
+
status?: string;
|
|
294
|
+
}>;
|
|
295
|
+
/**
|
|
296
|
+
* v0.3 — Verifica entitlement de um resource/feature. Wrapper em
|
|
297
|
+
* GET /sdk/v1/entitlements. Em dev consulta MockEntitlements + MockUsage.
|
|
298
|
+
*/
|
|
299
|
+
check(resource: string, options?: {
|
|
300
|
+
productId?: string;
|
|
301
|
+
tenantId?: string;
|
|
302
|
+
}): Promise<{
|
|
303
|
+
allowed: boolean;
|
|
304
|
+
reason?: string;
|
|
305
|
+
remaining?: number;
|
|
306
|
+
limit?: number;
|
|
307
|
+
planId?: string | null;
|
|
308
|
+
planFeatures?: string[];
|
|
309
|
+
}>;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Namespace `db` (v0.3) — wrapper minimalista para CRUD em coleções tenant-scoped
|
|
313
|
+
* via Core REST. Em dev retorna fixtures in-memory por collection.
|
|
314
|
+
*
|
|
315
|
+
* O Core injeta automaticamente o tenantId no path do Firestore:
|
|
316
|
+
* `tenant_{tid}_{name}/{docId}`.
|
|
317
|
+
*/
|
|
318
|
+
/** Filtro `where` simples — alinhado com endpoint REST `field:op:value`. */
|
|
319
|
+
interface DbWhereFilter {
|
|
320
|
+
field: string;
|
|
321
|
+
op: '==' | '!=' | '<' | '<=' | '>' | '>=' | 'in';
|
|
322
|
+
value: string | number | boolean | null | Array<string | number | boolean>;
|
|
323
|
+
}
|
|
324
|
+
interface DbListOptions {
|
|
325
|
+
limit?: number;
|
|
326
|
+
/** Lista de filtros — máximo 5 (alinhado ao backend). */
|
|
327
|
+
where?: DbWhereFilter[];
|
|
328
|
+
}
|
|
329
|
+
interface DbCollectionRef {
|
|
330
|
+
/** Lista documentos com filtros opcionais. */
|
|
331
|
+
list<T = Record<string, unknown>>(opts?: DbListOptions): Promise<T[]>;
|
|
332
|
+
/** Lê um documento por id. Retorna `null` se não existe. */
|
|
333
|
+
get<T = Record<string, unknown>>(id: string): Promise<T | null>;
|
|
334
|
+
/** Cria/upsert um documento com id explícito. */
|
|
335
|
+
set(id: string, data: Record<string, unknown>): Promise<{
|
|
336
|
+
ok: true;
|
|
337
|
+
}>;
|
|
338
|
+
/**
|
|
339
|
+
* v0.3.1 — Adiciona doc com id auto-gerado pelo backend. Retorna `{id}` do
|
|
340
|
+
* doc criado. Diferente de `set(id, data)` que requer caller fornecer id.
|
|
341
|
+
*/
|
|
342
|
+
add(data: Record<string, unknown>): Promise<{
|
|
343
|
+
ok: true;
|
|
344
|
+
id: string;
|
|
345
|
+
}>;
|
|
346
|
+
/** Atualiza doc com merge — só campos passados em `data`. */
|
|
347
|
+
update(id: string, data: Record<string, unknown>): Promise<{
|
|
348
|
+
ok: true;
|
|
349
|
+
}>;
|
|
350
|
+
/** Deleta um documento. */
|
|
351
|
+
remove(id: string): Promise<{
|
|
352
|
+
ok: true;
|
|
353
|
+
}>;
|
|
354
|
+
}
|
|
355
|
+
interface DbNamespace {
|
|
356
|
+
collection(name: string): DbCollectionRef;
|
|
357
|
+
}
|
|
358
|
+
type SupportSeverity = 'low' | 'normal' | 'high' | 'urgent';
|
|
359
|
+
type SupportStatus = 'open' | 'pending' | 'resolved' | 'closed';
|
|
360
|
+
interface SupportTicket {
|
|
361
|
+
id: string;
|
|
362
|
+
subject: string;
|
|
363
|
+
message: string;
|
|
364
|
+
severity: SupportSeverity;
|
|
365
|
+
status: SupportStatus;
|
|
366
|
+
createdAt: string;
|
|
367
|
+
updatedAt?: string;
|
|
368
|
+
/** Slug do produto referenciado. */
|
|
369
|
+
productSlug?: string;
|
|
370
|
+
}
|
|
371
|
+
interface CreateTicketInput {
|
|
372
|
+
subject: string;
|
|
373
|
+
message: string;
|
|
374
|
+
severity?: SupportSeverity;
|
|
375
|
+
productSlug?: string;
|
|
376
|
+
}
|
|
377
|
+
interface SupportNamespace {
|
|
378
|
+
/** Cria um novo ticket. Mock em dev, POST `/api/v1/products/{slug}/tickets` em prod. */
|
|
379
|
+
createTicket(input: CreateTicketInput): Promise<SupportTicket>;
|
|
380
|
+
/** Lista meus tickets abertos. */
|
|
381
|
+
listMyTickets(): Promise<SupportTicket[]>;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Modo do SDK. `dev` ativa mocks automáticos (úteis pra testes e
|
|
385
|
+
* desenvolvimento sem precisar de backend rodando).
|
|
386
|
+
*/
|
|
387
|
+
type NeetruEnv = 'dev' | 'workspace' | 'prod';
|
|
388
|
+
/**
|
|
389
|
+
* Configuração resolvida (defaults aplicados, fetch garantido).
|
|
390
|
+
*/
|
|
391
|
+
interface ResolvedConfig {
|
|
392
|
+
readonly apiKey?: string;
|
|
393
|
+
readonly baseUrl: string;
|
|
394
|
+
readonly fetch: typeof globalThis.fetch;
|
|
395
|
+
/** Resolved env — `dev` | `workspace` | `prod`. Default `prod`. */
|
|
396
|
+
readonly env: NeetruEnv;
|
|
397
|
+
/** Default productId (v0.3). */
|
|
398
|
+
readonly productId?: string;
|
|
399
|
+
/** Default tenantId (v0.3). */
|
|
400
|
+
readonly tenantId?: string;
|
|
401
|
+
}
|
|
402
|
+
/** Opções pra `catalog.list()`. */
|
|
403
|
+
interface CatalogListOptions {
|
|
404
|
+
/** Inclui rascunhos (apenas com Bearer staff). Default false. */
|
|
405
|
+
includeDrafts?: boolean;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export { type AuthNamespace as A, type CatalogListOptions as C, DEFAULT_BASE_URL as D, type EntitlementCheck as E, type NeetruClientConfig as N, type Product as P, type ResolvedConfig as R, type SignInOptions as S, type TelemetryEventAck as T, type UsageEventInput as U, type NeetruClient as a, type AuthStateListener as b, type CatalogListResponse as c, type CreateTicketInput as d, type DbCollectionRef as e, type DbListOptions as f, type DbNamespace as g, type DbWhereFilter as h, type NeetruEnv as i, type NeetruUser as j, type ProductPlan as k, type ProductStatus as l, type SupportNamespace as m, type SupportSeverity as n, type SupportStatus as o, type SupportTicket as p, type TelemetryEventInput as q, type UsageNamespace as r, type UsageQuota as s, type TelemetryLogInput as t, type TelemetryLogAck as u };
|
package/dist/usage.cjs
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
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
|
+
if (opts.requireAuth) {
|
|
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/usage.ts
|
|
98
|
+
function toQuota(metric, raw) {
|
|
99
|
+
if (!raw || typeof raw !== "object") {
|
|
100
|
+
throw new NeetruError("invalid_response", "Quota response is not an object");
|
|
101
|
+
}
|
|
102
|
+
const r = raw;
|
|
103
|
+
if (typeof r.used !== "number" || typeof r.limit !== "number") {
|
|
104
|
+
throw new NeetruError("invalid_response", "Quota response missing used/limit numbers");
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
metric: typeof r.metric === "string" ? r.metric : metric,
|
|
108
|
+
used: r.used,
|
|
109
|
+
limit: r.limit,
|
|
110
|
+
resetsAt: typeof r.resetsAt === "string" ? r.resetsAt : void 0,
|
|
111
|
+
plan: typeof r.plan === "string" ? r.plan : void 0
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function createUsageNamespace(config) {
|
|
115
|
+
return {
|
|
116
|
+
/**
|
|
117
|
+
* Persiste um evento de usage. Em dev (mocks ativos via factory) só loga.
|
|
118
|
+
* Em workspace/prod chama POST /sdk/v1/usage/record.
|
|
119
|
+
*/
|
|
120
|
+
async track(event, properties) {
|
|
121
|
+
if (!event || typeof event !== "string") {
|
|
122
|
+
throw new NeetruError("validation_failed", "event name is required");
|
|
123
|
+
}
|
|
124
|
+
if (event.length > 128) {
|
|
125
|
+
throw new NeetruError("validation_failed", "event name max 128 chars");
|
|
126
|
+
}
|
|
127
|
+
const body = { event };
|
|
128
|
+
if (properties && typeof properties === "object") body.properties = properties;
|
|
129
|
+
const raw = await httpRequest(config, {
|
|
130
|
+
method: "POST",
|
|
131
|
+
path: "/sdk/v1/usage/record",
|
|
132
|
+
body,
|
|
133
|
+
requireAuth: true
|
|
134
|
+
});
|
|
135
|
+
if (!raw || raw.ok !== true) {
|
|
136
|
+
throw new NeetruError("invalid_response", "Usage record response missing ok");
|
|
137
|
+
}
|
|
138
|
+
return { ok: true };
|
|
139
|
+
},
|
|
140
|
+
/**
|
|
141
|
+
* Lê quota atual de uma métrica. Cacheável (caller decide), SDK não cacheia.
|
|
142
|
+
*/
|
|
143
|
+
async getQuota(metric) {
|
|
144
|
+
if (!metric || typeof metric !== "string") {
|
|
145
|
+
throw new NeetruError("validation_failed", "metric is required");
|
|
146
|
+
}
|
|
147
|
+
const raw = await httpRequest(config, {
|
|
148
|
+
method: "GET",
|
|
149
|
+
path: "/sdk/v1/usage/quota",
|
|
150
|
+
query: { metric },
|
|
151
|
+
requireAuth: true
|
|
152
|
+
});
|
|
153
|
+
return toQuota(metric, raw);
|
|
154
|
+
},
|
|
155
|
+
/**
|
|
156
|
+
* v0.3 — Reporta consumo metered. Hit no endpoint canônico Sprint 7.
|
|
157
|
+
*/
|
|
158
|
+
async report(resource, qty = 1, options) {
|
|
159
|
+
if (!resource || typeof resource !== "string") {
|
|
160
|
+
throw new NeetruError("validation_failed", "resource is required");
|
|
161
|
+
}
|
|
162
|
+
if (!Number.isFinite(qty) || qty <= 0) {
|
|
163
|
+
throw new NeetruError("validation_failed", "qty must be positive integer");
|
|
164
|
+
}
|
|
165
|
+
const productId = options?.productId ?? config.productId;
|
|
166
|
+
const tenantId = options?.tenantId ?? config.tenantId;
|
|
167
|
+
if (!productId) {
|
|
168
|
+
throw new NeetruError(
|
|
169
|
+
"validation_failed",
|
|
170
|
+
"productId required (pass to options or set on createNeetruClient)"
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
if (!tenantId) {
|
|
174
|
+
throw new NeetruError(
|
|
175
|
+
"validation_failed",
|
|
176
|
+
"tenantId required (pass to options or set on createNeetruClient)"
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
const raw = await httpRequest(config, {
|
|
180
|
+
method: "POST",
|
|
181
|
+
path: "/sdk/v1/usage/record",
|
|
182
|
+
body: { productId, tenantId, resource, qty: Math.floor(qty) },
|
|
183
|
+
requireAuth: true
|
|
184
|
+
});
|
|
185
|
+
if (!raw || raw.ok !== true) {
|
|
186
|
+
throw new NeetruError("invalid_response", "usage.report response missing ok");
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
ok: true,
|
|
190
|
+
counterId: raw.counterId,
|
|
191
|
+
value: raw.value,
|
|
192
|
+
limit: raw.limit,
|
|
193
|
+
remaining: raw.remaining,
|
|
194
|
+
status: raw.status
|
|
195
|
+
};
|
|
196
|
+
},
|
|
197
|
+
/**
|
|
198
|
+
* v0.3 — Verifica entitlement de um resource via GET /sdk/v1/entitlements.
|
|
199
|
+
*/
|
|
200
|
+
async check(resource, options) {
|
|
201
|
+
if (!resource || typeof resource !== "string") {
|
|
202
|
+
throw new NeetruError("validation_failed", "resource is required");
|
|
203
|
+
}
|
|
204
|
+
const productId = options?.productId ?? config.productId;
|
|
205
|
+
const tenantId = options?.tenantId ?? config.tenantId;
|
|
206
|
+
if (!productId || !tenantId) {
|
|
207
|
+
throw new NeetruError(
|
|
208
|
+
"validation_failed",
|
|
209
|
+
"productId and tenantId required"
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
const raw = await httpRequest(config, {
|
|
213
|
+
method: "GET",
|
|
214
|
+
path: "/sdk/v1/entitlements",
|
|
215
|
+
query: { productId, tenantId, feature: resource },
|
|
216
|
+
requireAuth: true
|
|
217
|
+
});
|
|
218
|
+
if (!raw || typeof raw.allowed !== "boolean") {
|
|
219
|
+
throw new NeetruError("invalid_response", "usage.check response missing allowed");
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
allowed: raw.allowed,
|
|
223
|
+
reason: raw.reason,
|
|
224
|
+
remaining: raw.remaining,
|
|
225
|
+
limit: raw.limit,
|
|
226
|
+
planId: raw.planId,
|
|
227
|
+
planFeatures: raw.planFeatures
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
exports.createUsageNamespace = createUsageNamespace;
|
|
234
|
+
//# sourceMappingURL=usage.cjs.map
|
|
235
|
+
//# sourceMappingURL=usage.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/usage.ts"],"names":[],"mappings":"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","file":"usage.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 * Usage namespace — track usage events e ler quotas (v0.2).\n *\n * Endpoints consumidos (em prod):\n * - `POST /sdk/v1/usage/record` — record event\n * - `GET /sdk/v1/usage/quota?metric=X` — ler quota da metric\n *\n * Comportamento por env:\n * - `dev` → MockUsage (in-memory, retornado pelo factory).\n * - `workspace`/`prod` → HTTP real.\n *\n * Diferença vs `telemetry`: telemetry é evento analítico (event sourcing pra\n * BigQuery). usage é meterado (consumo cobrado), com quota explícita por plano.\n */\nimport { NeetruError } from './errors';\nimport { httpRequest } from './http';\nimport type { ResolvedConfig, UsageNamespace, UsageQuota } from './types';\n\ninterface RawUsageAck {\n ok?: boolean;\n recordId?: string;\n}\n\ninterface RawUsageQuota {\n metric?: string;\n used?: number;\n limit?: number;\n resetsAt?: string;\n plan?: string;\n}\n\nfunction toQuota(metric: string, raw: unknown): UsageQuota {\n if (!raw || typeof raw !== 'object') {\n throw new NeetruError('invalid_response', 'Quota response is not an object');\n }\n const r = raw as RawUsageQuota;\n if (typeof r.used !== 'number' || typeof r.limit !== 'number') {\n throw new NeetruError('invalid_response', 'Quota response missing used/limit numbers');\n }\n return {\n metric: typeof r.metric === 'string' ? r.metric : metric,\n used: r.used,\n limit: r.limit,\n resetsAt: typeof r.resetsAt === 'string' ? r.resetsAt : undefined,\n plan: typeof r.plan === 'string' ? r.plan : undefined,\n };\n}\n\nexport function createUsageNamespace(config: ResolvedConfig): UsageNamespace {\n return {\n /**\n * Persiste um evento de usage. Em dev (mocks ativos via factory) só loga.\n * Em workspace/prod chama POST /sdk/v1/usage/record.\n */\n async track(\n event: string,\n properties?: Record<string, string | number | boolean | null>,\n ): Promise<{ ok: true }> {\n if (!event || typeof event !== 'string') {\n throw new NeetruError('validation_failed', 'event name is required');\n }\n if (event.length > 128) {\n throw new NeetruError('validation_failed', 'event name max 128 chars');\n }\n\n const body: Record<string, unknown> = { event };\n if (properties && typeof properties === 'object') body.properties = properties;\n\n const raw = await httpRequest<RawUsageAck>(config, {\n method: 'POST',\n path: '/sdk/v1/usage/record',\n body,\n requireAuth: true,\n });\n\n if (!raw || raw.ok !== true) {\n throw new NeetruError('invalid_response', 'Usage record response missing ok');\n }\n return { ok: true };\n },\n\n /**\n * Lê quota atual de uma métrica. Cacheável (caller decide), SDK não cacheia.\n */\n async getQuota(metric: string): Promise<UsageQuota> {\n if (!metric || typeof metric !== 'string') {\n throw new NeetruError('validation_failed', 'metric is required');\n }\n const raw = await httpRequest<RawUsageQuota>(config, {\n method: 'GET',\n path: '/sdk/v1/usage/quota',\n query: { metric },\n requireAuth: true,\n });\n return toQuota(metric, raw);\n },\n\n /**\n * v0.3 — Reporta consumo metered. Hit no endpoint canônico Sprint 7.\n */\n async report(\n resource: string,\n qty: number = 1,\n options?: { productId?: string; tenantId?: string },\n ) {\n if (!resource || typeof resource !== 'string') {\n throw new NeetruError('validation_failed', 'resource is required');\n }\n if (!Number.isFinite(qty) || qty <= 0) {\n throw new NeetruError('validation_failed', 'qty must be positive integer');\n }\n const productId = options?.productId ?? config.productId;\n const tenantId = options?.tenantId ?? config.tenantId;\n if (!productId) {\n throw new NeetruError(\n 'validation_failed',\n 'productId required (pass to options or set on createNeetruClient)',\n );\n }\n if (!tenantId) {\n throw new NeetruError(\n 'validation_failed',\n 'tenantId required (pass to options or set on createNeetruClient)',\n );\n }\n\n const raw = await httpRequest<{\n ok?: boolean;\n counterId?: string;\n value?: number;\n limit?: number;\n remaining?: number;\n status?: string;\n }>(config, {\n method: 'POST',\n path: '/sdk/v1/usage/record',\n body: { productId, tenantId, resource, qty: Math.floor(qty) },\n requireAuth: true,\n });\n if (!raw || raw.ok !== true) {\n throw new NeetruError('invalid_response', 'usage.report response missing ok');\n }\n return {\n ok: true as const,\n counterId: raw.counterId,\n value: raw.value,\n limit: raw.limit,\n remaining: raw.remaining,\n status: raw.status,\n };\n },\n\n /**\n * v0.3 — Verifica entitlement de um resource via GET /sdk/v1/entitlements.\n */\n async check(\n resource: string,\n options?: { productId?: string; tenantId?: string },\n ) {\n if (!resource || typeof resource !== 'string') {\n throw new NeetruError('validation_failed', 'resource is required');\n }\n const productId = options?.productId ?? config.productId;\n const tenantId = options?.tenantId ?? config.tenantId;\n if (!productId || !tenantId) {\n throw new NeetruError(\n 'validation_failed',\n 'productId and tenantId required',\n );\n }\n const raw = await httpRequest<{\n allowed?: boolean;\n reason?: string;\n remaining?: number;\n limit?: number;\n planId?: string | null;\n planFeatures?: string[];\n }>(config, {\n method: 'GET',\n path: '/sdk/v1/entitlements',\n query: { productId, tenantId, feature: resource },\n requireAuth: true,\n });\n if (!raw || typeof raw.allowed !== 'boolean') {\n throw new NeetruError('invalid_response', 'usage.check response missing allowed');\n }\n return {\n allowed: raw.allowed,\n reason: raw.reason,\n remaining: raw.remaining,\n limit: raw.limit,\n planId: raw.planId,\n planFeatures: raw.planFeatures,\n };\n },\n };\n}\n"]}
|