@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/support.cjs
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
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/support.ts
|
|
98
|
+
var VALID_SEVERITIES = ["low", "normal", "high", "urgent"];
|
|
99
|
+
var VALID_STATUSES = ["open", "pending", "resolved", "closed"];
|
|
100
|
+
function toTicket(raw) {
|
|
101
|
+
if (!raw || typeof raw !== "object") {
|
|
102
|
+
throw new NeetruError("invalid_response", "Ticket response is not an object");
|
|
103
|
+
}
|
|
104
|
+
const r = raw;
|
|
105
|
+
if (typeof r.id !== "string") {
|
|
106
|
+
throw new NeetruError("invalid_response", "Ticket missing id");
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
id: r.id,
|
|
110
|
+
subject: typeof r.subject === "string" ? r.subject : "",
|
|
111
|
+
message: typeof r.message === "string" ? r.message : "",
|
|
112
|
+
severity: VALID_SEVERITIES.includes(r.severity) ? r.severity : "normal",
|
|
113
|
+
status: VALID_STATUSES.includes(r.status) ? r.status : "open",
|
|
114
|
+
createdAt: typeof r.createdAt === "string" ? r.createdAt : (/* @__PURE__ */ new Date()).toISOString(),
|
|
115
|
+
updatedAt: typeof r.updatedAt === "string" ? r.updatedAt : void 0,
|
|
116
|
+
productSlug: typeof r.productSlug === "string" ? r.productSlug : void 0
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function createSupportNamespace(config) {
|
|
120
|
+
return {
|
|
121
|
+
/**
|
|
122
|
+
* Cria um ticket de suporte. Requer Bearer auth. Se `productSlug` não é
|
|
123
|
+
* passado, o backend infere do escopo do token.
|
|
124
|
+
*/
|
|
125
|
+
async createTicket(input) {
|
|
126
|
+
if (!input || typeof input !== "object") {
|
|
127
|
+
throw new NeetruError("validation_failed", "input is required");
|
|
128
|
+
}
|
|
129
|
+
if (!input.subject || typeof input.subject !== "string") {
|
|
130
|
+
throw new NeetruError("validation_failed", "subject is required");
|
|
131
|
+
}
|
|
132
|
+
if (input.subject.length > 200) {
|
|
133
|
+
throw new NeetruError("validation_failed", "subject max 200 chars");
|
|
134
|
+
}
|
|
135
|
+
if (!input.message || typeof input.message !== "string") {
|
|
136
|
+
throw new NeetruError("validation_failed", "message is required");
|
|
137
|
+
}
|
|
138
|
+
if (input.message.length > 1e4) {
|
|
139
|
+
throw new NeetruError("validation_failed", "message max 10000 chars");
|
|
140
|
+
}
|
|
141
|
+
if (input.severity && !VALID_SEVERITIES.includes(input.severity)) {
|
|
142
|
+
throw new NeetruError("validation_failed", `severity must be one of ${VALID_SEVERITIES.join(", ")}`);
|
|
143
|
+
}
|
|
144
|
+
const slug = input.productSlug ?? "_default";
|
|
145
|
+
const body = {
|
|
146
|
+
subject: input.subject,
|
|
147
|
+
message: input.message,
|
|
148
|
+
severity: input.severity ?? "normal"
|
|
149
|
+
};
|
|
150
|
+
const raw = await httpRequest(config, {
|
|
151
|
+
method: "POST",
|
|
152
|
+
path: `/api/v1/products/${encodeURIComponent(slug)}/tickets`,
|
|
153
|
+
body,
|
|
154
|
+
requireAuth: true
|
|
155
|
+
});
|
|
156
|
+
const candidate = raw && typeof raw === "object" && "ticket" in raw ? raw.ticket : raw;
|
|
157
|
+
return toTicket(candidate);
|
|
158
|
+
},
|
|
159
|
+
/**
|
|
160
|
+
* Lista tickets do customer no produto atual (escopo do token).
|
|
161
|
+
*/
|
|
162
|
+
async listMyTickets() {
|
|
163
|
+
const raw = await httpRequest(config, {
|
|
164
|
+
method: "GET",
|
|
165
|
+
path: "/api/v1/products/_default/tickets",
|
|
166
|
+
requireAuth: true
|
|
167
|
+
});
|
|
168
|
+
const list = Array.isArray(raw) ? raw : raw && typeof raw === "object" && "tickets" in raw ? raw.tickets ?? [] : [];
|
|
169
|
+
return list.map(toTicket);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
exports.createSupportNamespace = createSupportNamespace;
|
|
175
|
+
//# sourceMappingURL=support.cjs.map
|
|
176
|
+
//# sourceMappingURL=support.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/support.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,EAAA,IAAI,KAAK,WAAA,EAAa;AACpB,IAAA,IAAI,CAAC,OAAO,MAAA,EAAQ;AAClB,MAAA,MAAM,IAAI,WAAA;AAAA,QACR,iBAAA;AAAA,QACA;AAAA,OACF;AAAA,IACF;AACA,IAAA,OAAA,CAAQ,aAAA,GAAgB,CAAA,OAAA,EAAU,MAAA,CAAO,MAAM,CAAA,CAAA;AAAA,EACjD;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;;;AC1GA,IAAM,gBAAA,GAA+C,CAAC,KAAA,EAAO,QAAA,EAAU,QAAQ,QAAQ,CAAA;AACvF,IAAM,cAAA,GAA2C,CAAC,MAAA,EAAQ,SAAA,EAAW,YAAY,QAAQ,CAAA;AAazF,SAAS,SAAS,GAAA,EAA6B;AAC7C,EAAA,IAAI,CAAC,GAAA,IAAO,OAAO,GAAA,KAAQ,QAAA,EAAU;AACnC,IAAA,MAAM,IAAI,WAAA,CAAY,kBAAA,EAAoB,kCAAkC,CAAA;AAAA,EAC9E;AACA,EAAA,MAAM,CAAA,GAAI,GAAA;AACV,EAAA,IAAI,OAAO,CAAA,CAAE,EAAA,KAAO,QAAA,EAAU;AAC5B,IAAA,MAAM,IAAI,WAAA,CAAY,kBAAA,EAAoB,mBAAmB,CAAA;AAAA,EAC/D;AACA,EAAA,OAAO;AAAA,IACL,IAAI,CAAA,CAAE,EAAA;AAAA,IACN,SAAS,OAAO,CAAA,CAAE,OAAA,KAAY,QAAA,GAAW,EAAE,OAAA,GAAU,EAAA;AAAA,IACrD,SAAS,OAAO,CAAA,CAAE,OAAA,KAAY,QAAA,GAAW,EAAE,OAAA,GAAU,EAAA;AAAA,IACrD,UAAU,gBAAA,CAAiB,QAAA,CAAS,EAAE,QAA2B,CAAA,GAC5D,EAAE,QAAA,GACH,QAAA;AAAA,IACJ,QAAQ,cAAA,CAAe,QAAA,CAAS,EAAE,MAAuB,CAAA,GACpD,EAAE,MAAA,GACH,MAAA;AAAA,IACJ,SAAA,EAAW,OAAO,CAAA,CAAE,SAAA,KAAc,QAAA,GAAW,EAAE,SAAA,GAAA,iBAAY,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAA,IAClF,WAAW,OAAO,CAAA,CAAE,SAAA,KAAc,QAAA,GAAW,EAAE,SAAA,GAAY,MAAA;AAAA,IAC3D,aAAa,OAAO,CAAA,CAAE,WAAA,KAAgB,QAAA,GAAW,EAAE,WAAA,GAAc;AAAA,GACnE;AACF;AAEO,SAAS,uBAAuB,MAAA,EAA0C;AAC/E,EAAA,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA,IAKL,MAAM,aAAa,KAAA,EAAkD;AACnE,MAAA,IAAI,CAAC,KAAA,IAAS,OAAO,KAAA,KAAU,QAAA,EAAU;AACvC,QAAA,MAAM,IAAI,WAAA,CAAY,mBAAA,EAAqB,mBAAmB,CAAA;AAAA,MAChE;AACA,MAAA,IAAI,CAAC,KAAA,CAAM,OAAA,IAAW,OAAO,KAAA,CAAM,YAAY,QAAA,EAAU;AACvD,QAAA,MAAM,IAAI,WAAA,CAAY,mBAAA,EAAqB,qBAAqB,CAAA;AAAA,MAClE;AACA,MAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,MAAA,GAAS,GAAA,EAAK;AAC9B,QAAA,MAAM,IAAI,WAAA,CAAY,mBAAA,EAAqB,uBAAuB,CAAA;AAAA,MACpE;AACA,MAAA,IAAI,CAAC,KAAA,CAAM,OAAA,IAAW,OAAO,KAAA,CAAM,YAAY,QAAA,EAAU;AACvD,QAAA,MAAM,IAAI,WAAA,CAAY,mBAAA,EAAqB,qBAAqB,CAAA;AAAA,MAClE;AACA,MAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,MAAA,GAAS,GAAA,EAAQ;AACjC,QAAA,MAAM,IAAI,WAAA,CAAY,mBAAA,EAAqB,yBAAyB,CAAA;AAAA,MACtE;AACA,MAAA,IAAI,MAAM,QAAA,IAAY,CAAC,iBAAiB,QAAA,CAAS,KAAA,CAAM,QAAQ,CAAA,EAAG;AAChE,QAAA,MAAM,IAAI,YAAY,mBAAA,EAAqB,CAAA,wBAAA,EAA2B,iBAAiB,IAAA,CAAK,IAAI,CAAC,CAAA,CAAE,CAAA;AAAA,MACrG;AAEA,MAAA,MAAM,IAAA,GAAO,MAAM,WAAA,IAAe,UAAA;AAClC,MAAA,MAAM,IAAA,GAAgC;AAAA,QACpC,SAAS,KAAA,CAAM,OAAA;AAAA,QACf,SAAS,KAAA,CAAM,OAAA;AAAA,QACf,QAAA,EAAU,MAAM,QAAA,IAAY;AAAA,OAC9B;AAEA,MAAA,MAAM,GAAA,GAAM,MAAM,WAAA,CAAgD,MAAA,EAAQ;AAAA,QACxE,MAAA,EAAQ,MAAA;AAAA,QACR,IAAA,EAAM,CAAA,iBAAA,EAAoB,kBAAA,CAAmB,IAAI,CAAC,CAAA,QAAA,CAAA;AAAA,QAClD,IAAA;AAAA,QACA,WAAA,EAAa;AAAA,OACd,CAAA;AAGD,MAAA,MAAM,SAAA,GACJ,OAAO,OAAO,GAAA,KAAQ,YAAY,QAAA,IAAY,GAAA,GACzC,IAA+B,MAAA,GAChC,GAAA;AACN,MAAA,OAAO,SAAS,SAAS,CAAA;AAAA,IAC3B,CAAA;AAAA;AAAA;AAAA;AAAA,IAKA,MAAM,aAAA,GAA0C;AAC9C,MAAA,MAAM,GAAA,GAAM,MAAM,WAAA,CAAiD,MAAA,EAAQ;AAAA,QACzE,MAAA,EAAQ,KAAA;AAAA,QACR,IAAA,EAAM,mCAAA;AAAA,QACN,WAAA,EAAa;AAAA,OACd,CAAA;AACD,MAAA,MAAM,OAAO,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA,GAAI,MAAM,GAAA,IAAO,OAAO,GAAA,KAAQ,QAAA,IAAY,aAAa,GAAA,GAAO,GAAA,CAAgC,OAAA,IAAW,KAAK,EAAC;AAC/I,MAAA,OAAO,IAAA,CAAK,IAAI,QAAQ,CAAA;AAAA,IAC1B;AAAA,GACF;AACF","file":"support.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 * Support namespace — criar e listar tickets do customer (v0.2).\n *\n * Endpoints (em prod):\n * - `POST /api/v1/products/{slug}/tickets` — criar ticket\n * - `GET /api/v1/products/{slug}/tickets` — listar meus tickets do produto\n *\n * Em dev (mocks ativos via factory) tudo é in-memory.\n *\n * Decisão: severity default é `normal`. `low` indica feature requests; `urgent`\n * pra outages production (paginating ops imediatamente em prod).\n */\nimport { NeetruError } from './errors';\nimport { httpRequest } from './http';\nimport type {\n CreateTicketInput,\n ResolvedConfig,\n SupportNamespace,\n SupportSeverity,\n SupportStatus,\n SupportTicket,\n} from './types';\n\nconst VALID_SEVERITIES: readonly SupportSeverity[] = ['low', 'normal', 'high', 'urgent'];\nconst VALID_STATUSES: readonly SupportStatus[] = ['open', 'pending', 'resolved', 'closed'];\n\ninterface RawTicket {\n id?: string;\n subject?: string;\n message?: string;\n severity?: string;\n status?: string;\n createdAt?: string;\n updatedAt?: string;\n productSlug?: string;\n}\n\nfunction toTicket(raw: unknown): SupportTicket {\n if (!raw || typeof raw !== 'object') {\n throw new NeetruError('invalid_response', 'Ticket response is not an object');\n }\n const r = raw as RawTicket;\n if (typeof r.id !== 'string') {\n throw new NeetruError('invalid_response', 'Ticket missing id');\n }\n return {\n id: r.id,\n subject: typeof r.subject === 'string' ? r.subject : '',\n message: typeof r.message === 'string' ? r.message : '',\n severity: VALID_SEVERITIES.includes(r.severity as SupportSeverity)\n ? (r.severity as SupportSeverity)\n : 'normal',\n status: VALID_STATUSES.includes(r.status as SupportStatus)\n ? (r.status as SupportStatus)\n : 'open',\n createdAt: typeof r.createdAt === 'string' ? r.createdAt : new Date().toISOString(),\n updatedAt: typeof r.updatedAt === 'string' ? r.updatedAt : undefined,\n productSlug: typeof r.productSlug === 'string' ? r.productSlug : undefined,\n };\n}\n\nexport function createSupportNamespace(config: ResolvedConfig): SupportNamespace {\n return {\n /**\n * Cria um ticket de suporte. Requer Bearer auth. Se `productSlug` não é\n * passado, o backend infere do escopo do token.\n */\n async createTicket(input: CreateTicketInput): Promise<SupportTicket> {\n if (!input || typeof input !== 'object') {\n throw new NeetruError('validation_failed', 'input is required');\n }\n if (!input.subject || typeof input.subject !== 'string') {\n throw new NeetruError('validation_failed', 'subject is required');\n }\n if (input.subject.length > 200) {\n throw new NeetruError('validation_failed', 'subject max 200 chars');\n }\n if (!input.message || typeof input.message !== 'string') {\n throw new NeetruError('validation_failed', 'message is required');\n }\n if (input.message.length > 10_000) {\n throw new NeetruError('validation_failed', 'message max 10000 chars');\n }\n if (input.severity && !VALID_SEVERITIES.includes(input.severity)) {\n throw new NeetruError('validation_failed', `severity must be one of ${VALID_SEVERITIES.join(', ')}`);\n }\n\n const slug = input.productSlug ?? '_default';\n const body: Record<string, unknown> = {\n subject: input.subject,\n message: input.message,\n severity: input.severity ?? 'normal',\n };\n\n const raw = await httpRequest<RawTicket | { ticket?: RawTicket }>(config, {\n method: 'POST',\n path: `/api/v1/products/${encodeURIComponent(slug)}/tickets`,\n body,\n requireAuth: true,\n });\n\n // Backend pode envelopar como { ticket: {...} } ou retornar direto.\n const candidate =\n raw && typeof raw === 'object' && 'ticket' in raw\n ? (raw as { ticket?: RawTicket }).ticket\n : raw;\n return toTicket(candidate);\n },\n\n /**\n * Lista tickets do customer no produto atual (escopo do token).\n */\n async listMyTickets(): Promise<SupportTicket[]> {\n const raw = await httpRequest<{ tickets?: unknown[] } | unknown[]>(config, {\n method: 'GET',\n path: '/api/v1/products/_default/tickets',\n requireAuth: true,\n });\n const list = Array.isArray(raw) ? raw : raw && typeof raw === 'object' && 'tickets' in raw ? (raw as { tickets?: unknown[] }).tickets ?? [] : [];\n return list.map(toTicket);\n },\n };\n}\n"]}
|
package/dist/support.mjs
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
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/support.ts
|
|
96
|
+
var VALID_SEVERITIES = ["low", "normal", "high", "urgent"];
|
|
97
|
+
var VALID_STATUSES = ["open", "pending", "resolved", "closed"];
|
|
98
|
+
function toTicket(raw) {
|
|
99
|
+
if (!raw || typeof raw !== "object") {
|
|
100
|
+
throw new NeetruError("invalid_response", "Ticket response is not an object");
|
|
101
|
+
}
|
|
102
|
+
const r = raw;
|
|
103
|
+
if (typeof r.id !== "string") {
|
|
104
|
+
throw new NeetruError("invalid_response", "Ticket missing id");
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
id: r.id,
|
|
108
|
+
subject: typeof r.subject === "string" ? r.subject : "",
|
|
109
|
+
message: typeof r.message === "string" ? r.message : "",
|
|
110
|
+
severity: VALID_SEVERITIES.includes(r.severity) ? r.severity : "normal",
|
|
111
|
+
status: VALID_STATUSES.includes(r.status) ? r.status : "open",
|
|
112
|
+
createdAt: typeof r.createdAt === "string" ? r.createdAt : (/* @__PURE__ */ new Date()).toISOString(),
|
|
113
|
+
updatedAt: typeof r.updatedAt === "string" ? r.updatedAt : void 0,
|
|
114
|
+
productSlug: typeof r.productSlug === "string" ? r.productSlug : void 0
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function createSupportNamespace(config) {
|
|
118
|
+
return {
|
|
119
|
+
/**
|
|
120
|
+
* Cria um ticket de suporte. Requer Bearer auth. Se `productSlug` não é
|
|
121
|
+
* passado, o backend infere do escopo do token.
|
|
122
|
+
*/
|
|
123
|
+
async createTicket(input) {
|
|
124
|
+
if (!input || typeof input !== "object") {
|
|
125
|
+
throw new NeetruError("validation_failed", "input is required");
|
|
126
|
+
}
|
|
127
|
+
if (!input.subject || typeof input.subject !== "string") {
|
|
128
|
+
throw new NeetruError("validation_failed", "subject is required");
|
|
129
|
+
}
|
|
130
|
+
if (input.subject.length > 200) {
|
|
131
|
+
throw new NeetruError("validation_failed", "subject max 200 chars");
|
|
132
|
+
}
|
|
133
|
+
if (!input.message || typeof input.message !== "string") {
|
|
134
|
+
throw new NeetruError("validation_failed", "message is required");
|
|
135
|
+
}
|
|
136
|
+
if (input.message.length > 1e4) {
|
|
137
|
+
throw new NeetruError("validation_failed", "message max 10000 chars");
|
|
138
|
+
}
|
|
139
|
+
if (input.severity && !VALID_SEVERITIES.includes(input.severity)) {
|
|
140
|
+
throw new NeetruError("validation_failed", `severity must be one of ${VALID_SEVERITIES.join(", ")}`);
|
|
141
|
+
}
|
|
142
|
+
const slug = input.productSlug ?? "_default";
|
|
143
|
+
const body = {
|
|
144
|
+
subject: input.subject,
|
|
145
|
+
message: input.message,
|
|
146
|
+
severity: input.severity ?? "normal"
|
|
147
|
+
};
|
|
148
|
+
const raw = await httpRequest(config, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
path: `/api/v1/products/${encodeURIComponent(slug)}/tickets`,
|
|
151
|
+
body,
|
|
152
|
+
requireAuth: true
|
|
153
|
+
});
|
|
154
|
+
const candidate = raw && typeof raw === "object" && "ticket" in raw ? raw.ticket : raw;
|
|
155
|
+
return toTicket(candidate);
|
|
156
|
+
},
|
|
157
|
+
/**
|
|
158
|
+
* Lista tickets do customer no produto atual (escopo do token).
|
|
159
|
+
*/
|
|
160
|
+
async listMyTickets() {
|
|
161
|
+
const raw = await httpRequest(config, {
|
|
162
|
+
method: "GET",
|
|
163
|
+
path: "/api/v1/products/_default/tickets",
|
|
164
|
+
requireAuth: true
|
|
165
|
+
});
|
|
166
|
+
const list = Array.isArray(raw) ? raw : raw && typeof raw === "object" && "tickets" in raw ? raw.tickets ?? [] : [];
|
|
167
|
+
return list.map(toTicket);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export { createSupportNamespace };
|
|
173
|
+
//# sourceMappingURL=support.mjs.map
|
|
174
|
+
//# sourceMappingURL=support.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/support.ts"],"names":[],"mappings":"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","file":"support.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 * Support namespace — criar e listar tickets do customer (v0.2).\n *\n * Endpoints (em prod):\n * - `POST /api/v1/products/{slug}/tickets` — criar ticket\n * - `GET /api/v1/products/{slug}/tickets` — listar meus tickets do produto\n *\n * Em dev (mocks ativos via factory) tudo é in-memory.\n *\n * Decisão: severity default é `normal`. `low` indica feature requests; `urgent`\n * pra outages production (paginating ops imediatamente em prod).\n */\nimport { NeetruError } from './errors';\nimport { httpRequest } from './http';\nimport type {\n CreateTicketInput,\n ResolvedConfig,\n SupportNamespace,\n SupportSeverity,\n SupportStatus,\n SupportTicket,\n} from './types';\n\nconst VALID_SEVERITIES: readonly SupportSeverity[] = ['low', 'normal', 'high', 'urgent'];\nconst VALID_STATUSES: readonly SupportStatus[] = ['open', 'pending', 'resolved', 'closed'];\n\ninterface RawTicket {\n id?: string;\n subject?: string;\n message?: string;\n severity?: string;\n status?: string;\n createdAt?: string;\n updatedAt?: string;\n productSlug?: string;\n}\n\nfunction toTicket(raw: unknown): SupportTicket {\n if (!raw || typeof raw !== 'object') {\n throw new NeetruError('invalid_response', 'Ticket response is not an object');\n }\n const r = raw as RawTicket;\n if (typeof r.id !== 'string') {\n throw new NeetruError('invalid_response', 'Ticket missing id');\n }\n return {\n id: r.id,\n subject: typeof r.subject === 'string' ? r.subject : '',\n message: typeof r.message === 'string' ? r.message : '',\n severity: VALID_SEVERITIES.includes(r.severity as SupportSeverity)\n ? (r.severity as SupportSeverity)\n : 'normal',\n status: VALID_STATUSES.includes(r.status as SupportStatus)\n ? (r.status as SupportStatus)\n : 'open',\n createdAt: typeof r.createdAt === 'string' ? r.createdAt : new Date().toISOString(),\n updatedAt: typeof r.updatedAt === 'string' ? r.updatedAt : undefined,\n productSlug: typeof r.productSlug === 'string' ? r.productSlug : undefined,\n };\n}\n\nexport function createSupportNamespace(config: ResolvedConfig): SupportNamespace {\n return {\n /**\n * Cria um ticket de suporte. Requer Bearer auth. Se `productSlug` não é\n * passado, o backend infere do escopo do token.\n */\n async createTicket(input: CreateTicketInput): Promise<SupportTicket> {\n if (!input || typeof input !== 'object') {\n throw new NeetruError('validation_failed', 'input is required');\n }\n if (!input.subject || typeof input.subject !== 'string') {\n throw new NeetruError('validation_failed', 'subject is required');\n }\n if (input.subject.length > 200) {\n throw new NeetruError('validation_failed', 'subject max 200 chars');\n }\n if (!input.message || typeof input.message !== 'string') {\n throw new NeetruError('validation_failed', 'message is required');\n }\n if (input.message.length > 10_000) {\n throw new NeetruError('validation_failed', 'message max 10000 chars');\n }\n if (input.severity && !VALID_SEVERITIES.includes(input.severity)) {\n throw new NeetruError('validation_failed', `severity must be one of ${VALID_SEVERITIES.join(', ')}`);\n }\n\n const slug = input.productSlug ?? '_default';\n const body: Record<string, unknown> = {\n subject: input.subject,\n message: input.message,\n severity: input.severity ?? 'normal',\n };\n\n const raw = await httpRequest<RawTicket | { ticket?: RawTicket }>(config, {\n method: 'POST',\n path: `/api/v1/products/${encodeURIComponent(slug)}/tickets`,\n body,\n requireAuth: true,\n });\n\n // Backend pode envelopar como { ticket: {...} } ou retornar direto.\n const candidate =\n raw && typeof raw === 'object' && 'ticket' in raw\n ? (raw as { ticket?: RawTicket }).ticket\n : raw;\n return toTicket(candidate);\n },\n\n /**\n * Lista tickets do customer no produto atual (escopo do token).\n */\n async listMyTickets(): Promise<SupportTicket[]> {\n const raw = await httpRequest<{ tickets?: unknown[] } | unknown[]>(config, {\n method: 'GET',\n path: '/api/v1/products/_default/tickets',\n requireAuth: true,\n });\n const list = Array.isArray(raw) ? raw : raw && typeof raw === 'object' && 'tickets' in raw ? (raw as { tickets?: unknown[] }).tickets ?? [] : [];\n return list.map(toTicket);\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,225 @@
|
|
|
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/telemetry.ts
|
|
98
|
+
var VALID_LOG_LEVELS = ["debug", "info", "warn", "error", "fatal"];
|
|
99
|
+
function consoleFor(level) {
|
|
100
|
+
switch (level) {
|
|
101
|
+
case "debug":
|
|
102
|
+
return console.debug.bind(console);
|
|
103
|
+
case "info":
|
|
104
|
+
return console.info.bind(console);
|
|
105
|
+
case "warn":
|
|
106
|
+
return console.warn.bind(console);
|
|
107
|
+
case "error":
|
|
108
|
+
return console.error.bind(console);
|
|
109
|
+
case "fatal":
|
|
110
|
+
return console.error.bind(console);
|
|
111
|
+
default:
|
|
112
|
+
return console.log.bind(console);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function createTelemetryNamespace(config) {
|
|
116
|
+
return {
|
|
117
|
+
/**
|
|
118
|
+
* Persiste um evento de uso. Lança `NeetruError` em qualquer falha
|
|
119
|
+
* (incluindo rate-limit).
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```ts
|
|
123
|
+
* await client.telemetry.event({
|
|
124
|
+
* name: 'dashboard_opened',
|
|
125
|
+
* properties: { plan: 'pro', tab: 'overview' },
|
|
126
|
+
* });
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
async event(input) {
|
|
130
|
+
if (!input || typeof input !== "object") {
|
|
131
|
+
throw new NeetruError("validation_failed", "event input is required");
|
|
132
|
+
}
|
|
133
|
+
if (typeof input.name !== "string" || input.name.length === 0) {
|
|
134
|
+
throw new NeetruError("validation_failed", "event.name is required");
|
|
135
|
+
}
|
|
136
|
+
if (input.name.length > 128) {
|
|
137
|
+
throw new NeetruError("validation_failed", "event.name max 128 chars");
|
|
138
|
+
}
|
|
139
|
+
const body = { name: input.name };
|
|
140
|
+
if (input.properties && typeof input.properties === "object") {
|
|
141
|
+
body.properties = input.properties;
|
|
142
|
+
}
|
|
143
|
+
if (input.timestamp) body.timestamp = input.timestamp;
|
|
144
|
+
const raw = await httpRequest(config, {
|
|
145
|
+
method: "POST",
|
|
146
|
+
path: "/api/v1/sdk/telemetry/event",
|
|
147
|
+
body,
|
|
148
|
+
requireAuth: true
|
|
149
|
+
});
|
|
150
|
+
if (!raw || raw.ok !== true || typeof raw.eventId !== "string") {
|
|
151
|
+
throw new NeetruError("invalid_response", "Telemetry response missing eventId");
|
|
152
|
+
}
|
|
153
|
+
return { ok: true, eventId: raw.eventId };
|
|
154
|
+
},
|
|
155
|
+
/**
|
|
156
|
+
* Registra um log estruturado per-product (Sprint 6).
|
|
157
|
+
*
|
|
158
|
+
* - `NEETRU_ENV=dev`: console.{level}, retorna ack mock.
|
|
159
|
+
* - workspace/prod: HTTP POST com Bearer auth + correlationId no header.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```ts
|
|
163
|
+
* await client.telemetry.log({
|
|
164
|
+
* level: 'error',
|
|
165
|
+
* message: 'Falha ao calcular total',
|
|
166
|
+
* metadata: { orderId: 'o-123' },
|
|
167
|
+
* });
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
async log(input) {
|
|
171
|
+
if (!input || typeof input !== "object") {
|
|
172
|
+
throw new NeetruError("validation_failed", "log input is required");
|
|
173
|
+
}
|
|
174
|
+
if (!VALID_LOG_LEVELS.includes(input.level)) {
|
|
175
|
+
throw new NeetruError("validation_failed", `level must be one of ${VALID_LOG_LEVELS.join(", ")}`);
|
|
176
|
+
}
|
|
177
|
+
if (typeof input.message !== "string" || input.message.length === 0) {
|
|
178
|
+
throw new NeetruError("validation_failed", "message is required");
|
|
179
|
+
}
|
|
180
|
+
if (input.message.length > 4e3) {
|
|
181
|
+
throw new NeetruError("validation_failed", "message max 4000 chars");
|
|
182
|
+
}
|
|
183
|
+
if (config.env === "dev") {
|
|
184
|
+
const fn = consoleFor(input.level);
|
|
185
|
+
fn(`[neetru-sdk] ${input.level}: ${input.message}`, input.metadata ?? {});
|
|
186
|
+
return { ok: true, mode: "mock" };
|
|
187
|
+
}
|
|
188
|
+
const body = {
|
|
189
|
+
level: input.level,
|
|
190
|
+
message: input.message
|
|
191
|
+
};
|
|
192
|
+
if (input.metadata) body.metadata = input.metadata;
|
|
193
|
+
if (input.productSlug) body.productSlug = input.productSlug;
|
|
194
|
+
let cid = input.correlationId;
|
|
195
|
+
if (!cid) {
|
|
196
|
+
try {
|
|
197
|
+
const g = globalThis.NEETRU_CORRELATION_ID;
|
|
198
|
+
if (typeof g === "string" && g.length > 0) cid = g;
|
|
199
|
+
} catch {
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const headers = {};
|
|
203
|
+
if (cid) headers["x-correlation-id"] = cid;
|
|
204
|
+
const raw = await httpRequest(config, {
|
|
205
|
+
method: "POST",
|
|
206
|
+
path: "/sdk/v1/telemetry/log",
|
|
207
|
+
body,
|
|
208
|
+
requireAuth: true,
|
|
209
|
+
headers
|
|
210
|
+
});
|
|
211
|
+
if (!raw || raw.ok !== true) {
|
|
212
|
+
throw new NeetruError("invalid_response", "Telemetry log response missing ok");
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
ok: true,
|
|
216
|
+
logId: typeof raw.logId === "string" ? raw.logId : void 0,
|
|
217
|
+
mode: "http"
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
exports.createTelemetryNamespace = createTelemetryNamespace;
|
|
224
|
+
//# sourceMappingURL=telemetry.cjs.map
|
|
225
|
+
//# sourceMappingURL=telemetry.cjs.map
|