@marginfront/sdk 0.0.1 → 0.1.1
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/README.md +85 -0
- package/dist/chunk-UIM2U5MX.mjs +1288 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1410 -0
- package/dist/cli/index.mjs +128 -0
- package/dist/index.d.mts +11 -15
- package/dist/index.d.ts +11 -15
- package/dist/index.js +3 -25
- package/dist/index.mjs +20 -1291
- package/package.json +7 -3
|
@@ -0,0 +1,1288 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var MarginFrontError = class _MarginFrontError extends Error {
|
|
3
|
+
constructor(message, statusCode = 500, code = "MARGINFRONT_ERROR", details, metadata = {}) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "MarginFrontError";
|
|
6
|
+
this.statusCode = statusCode;
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.details = details;
|
|
9
|
+
this.metadata = metadata;
|
|
10
|
+
this.requestId = metadata.requestId;
|
|
11
|
+
Object.setPrototypeOf(this, _MarginFrontError.prototype);
|
|
12
|
+
if (Error.captureStackTrace) {
|
|
13
|
+
Error.captureStackTrace(this, this.constructor);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var AuthenticationError = class _AuthenticationError extends MarginFrontError {
|
|
18
|
+
constructor(message = "Invalid or missing API key", metadata = {}) {
|
|
19
|
+
super(message, 401, "AUTHENTICATION_ERROR", void 0, metadata);
|
|
20
|
+
this.name = "AuthenticationError";
|
|
21
|
+
Object.setPrototypeOf(this, _AuthenticationError.prototype);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var AuthorizationError = class _AuthorizationError extends MarginFrontError {
|
|
25
|
+
constructor(message = "You do not have permission to perform this action", metadata = {}) {
|
|
26
|
+
super(message, 403, "AUTHORIZATION_ERROR", void 0, metadata);
|
|
27
|
+
this.name = "AuthorizationError";
|
|
28
|
+
Object.setPrototypeOf(this, _AuthorizationError.prototype);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var NotFoundError = class _NotFoundError extends MarginFrontError {
|
|
32
|
+
constructor(message = "Resource not found", resourceType, resourceId, metadata = {}) {
|
|
33
|
+
super(message, 404, "NOT_FOUND_ERROR", void 0, metadata);
|
|
34
|
+
this.name = "NotFoundError";
|
|
35
|
+
this.resourceType = resourceType;
|
|
36
|
+
this.resourceId = resourceId;
|
|
37
|
+
Object.setPrototypeOf(this, _NotFoundError.prototype);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var ValidationError = class _ValidationError extends MarginFrontError {
|
|
41
|
+
constructor(message = "Validation failed", validationErrors, metadata = {}) {
|
|
42
|
+
super(message, 400, "VALIDATION_ERROR", validationErrors, metadata);
|
|
43
|
+
this.name = "ValidationError";
|
|
44
|
+
this.validationErrors = validationErrors;
|
|
45
|
+
Object.setPrototypeOf(this, _ValidationError.prototype);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var RateLimitError = class _RateLimitError extends MarginFrontError {
|
|
49
|
+
constructor(message = "Rate limit exceeded", retryAfter, metadata = {}) {
|
|
50
|
+
super(message, 429, "RATE_LIMIT_ERROR", void 0, metadata);
|
|
51
|
+
this.name = "RateLimitError";
|
|
52
|
+
this.retryAfter = retryAfter;
|
|
53
|
+
Object.setPrototypeOf(this, _RateLimitError.prototype);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
var ConflictError = class _ConflictError extends MarginFrontError {
|
|
57
|
+
constructor(message = "Resource conflict", metadata = {}) {
|
|
58
|
+
super(message, 409, "CONFLICT_ERROR", void 0, metadata);
|
|
59
|
+
this.name = "ConflictError";
|
|
60
|
+
Object.setPrototypeOf(this, _ConflictError.prototype);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var NetworkError = class _NetworkError extends MarginFrontError {
|
|
64
|
+
constructor(message = "Network error occurred", originalError, metadata = {}) {
|
|
65
|
+
super(message, 0, "NETWORK_ERROR", void 0, metadata);
|
|
66
|
+
this.name = "NetworkError";
|
|
67
|
+
this.originalError = originalError;
|
|
68
|
+
Object.setPrototypeOf(this, _NetworkError.prototype);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
var TimeoutError = class _TimeoutError extends MarginFrontError {
|
|
72
|
+
constructor(message = "Request timed out", timeoutMs, metadata = {}) {
|
|
73
|
+
super(message, 0, "TIMEOUT_ERROR", void 0, metadata);
|
|
74
|
+
this.name = "TimeoutError";
|
|
75
|
+
this.timeoutMs = timeoutMs;
|
|
76
|
+
Object.setPrototypeOf(this, _TimeoutError.prototype);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var InternalError = class _InternalError extends MarginFrontError {
|
|
80
|
+
constructor(message = "Internal server error", metadata = {}) {
|
|
81
|
+
super(message, 500, "INTERNAL_ERROR", void 0, metadata);
|
|
82
|
+
this.name = "InternalError";
|
|
83
|
+
Object.setPrototypeOf(this, _InternalError.prototype);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
var InitializationError = class _InitializationError extends MarginFrontError {
|
|
87
|
+
constructor(message = "Failed to initialize the SDK", metadata = {}) {
|
|
88
|
+
super(message, 0, "INITIALIZATION_ERROR", void 0, metadata);
|
|
89
|
+
this.name = "InitializationError";
|
|
90
|
+
Object.setPrototypeOf(this, _InitializationError.prototype);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
var ApiError = class _ApiError extends MarginFrontError {
|
|
94
|
+
constructor(message = "API request failed", statusCode, errorCode, metadata = {}) {
|
|
95
|
+
super(message, statusCode || 500, errorCode || "API_ERROR", void 0, metadata);
|
|
96
|
+
this.name = "ApiError";
|
|
97
|
+
this.errorCode = errorCode;
|
|
98
|
+
Object.setPrototypeOf(this, _ApiError.prototype);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
function createErrorFromResponse(statusCode, message, details, metadata = {}) {
|
|
102
|
+
switch (statusCode) {
|
|
103
|
+
case 400:
|
|
104
|
+
return new ValidationError(message, details, metadata);
|
|
105
|
+
case 401:
|
|
106
|
+
return new AuthenticationError(message, metadata);
|
|
107
|
+
case 403:
|
|
108
|
+
return new AuthorizationError(message, metadata);
|
|
109
|
+
case 404:
|
|
110
|
+
return new NotFoundError(message, void 0, void 0, metadata);
|
|
111
|
+
case 409:
|
|
112
|
+
return new ConflictError(message, metadata);
|
|
113
|
+
case 422:
|
|
114
|
+
return new ValidationError(message, details, metadata);
|
|
115
|
+
case 429:
|
|
116
|
+
const retryAfter = metadata.retryAfter;
|
|
117
|
+
return new RateLimitError(message, retryAfter, metadata);
|
|
118
|
+
case 500:
|
|
119
|
+
case 502:
|
|
120
|
+
case 503:
|
|
121
|
+
case 504:
|
|
122
|
+
return new InternalError(message, metadata);
|
|
123
|
+
default:
|
|
124
|
+
return new ApiError(message, statusCode, "API_ERROR", metadata);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function parseApiError(error) {
|
|
128
|
+
const err = error;
|
|
129
|
+
const message = err.message || "Unknown error";
|
|
130
|
+
const statusCode = err.response?.status;
|
|
131
|
+
const responseData = err.response?.data || {};
|
|
132
|
+
const requestId = err.config?.headers?.["X-Request-ID"] || "unknown";
|
|
133
|
+
const metadata = {
|
|
134
|
+
requestId,
|
|
135
|
+
url: err.config?.url,
|
|
136
|
+
method: err.config?.method,
|
|
137
|
+
responseData,
|
|
138
|
+
retryAfter: err.response?.headers?.["retry-after"] ? parseInt(err.response.headers["retry-after"], 10) : void 0
|
|
139
|
+
};
|
|
140
|
+
if (!statusCode) {
|
|
141
|
+
if (err.code === "ECONNABORTED") {
|
|
142
|
+
return new TimeoutError(message, void 0, metadata);
|
|
143
|
+
}
|
|
144
|
+
return new NetworkError(message, err, metadata);
|
|
145
|
+
}
|
|
146
|
+
return createErrorFromResponse(
|
|
147
|
+
statusCode,
|
|
148
|
+
responseData.message || responseData.error || message,
|
|
149
|
+
responseData.errors,
|
|
150
|
+
metadata
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/utils/validation.ts
|
|
155
|
+
function validateRequired(value, fieldName) {
|
|
156
|
+
if (value === void 0 || value === null || value === "") {
|
|
157
|
+
throw new ValidationError(`${fieldName} is required`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function validateNonNegative(value, fieldName) {
|
|
161
|
+
if (typeof value !== "number" || isNaN(value)) {
|
|
162
|
+
throw new ValidationError(`${fieldName} must be a number`);
|
|
163
|
+
}
|
|
164
|
+
if (value < 0) {
|
|
165
|
+
throw new ValidationError(`${fieldName} must be non-negative`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function validateDateString(value, fieldName) {
|
|
169
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
170
|
+
if (isNaN(date.getTime())) {
|
|
171
|
+
throw new ValidationError(`${fieldName} must be a valid ISO date string`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function validateUsageRecord(record) {
|
|
175
|
+
validateRequired(record.customerExternalId, "customerExternalId");
|
|
176
|
+
validateRequired(record.agentId, "agentId");
|
|
177
|
+
if (!record.signalName) {
|
|
178
|
+
throw new ValidationError("signalName is required");
|
|
179
|
+
}
|
|
180
|
+
if (record.quantity !== void 0 && record.quantity !== null) {
|
|
181
|
+
validateNonNegative(record.quantity, "quantity");
|
|
182
|
+
}
|
|
183
|
+
if (record.usageDate !== void 0) {
|
|
184
|
+
validateDateString(record.usageDate, "usageDate");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function validateUsageRecords(records) {
|
|
188
|
+
if (!Array.isArray(records)) {
|
|
189
|
+
throw new ValidationError("records must be an array");
|
|
190
|
+
}
|
|
191
|
+
if (records.length === 0) {
|
|
192
|
+
throw new ValidationError("records array cannot be empty");
|
|
193
|
+
}
|
|
194
|
+
records.forEach((record, index) => {
|
|
195
|
+
try {
|
|
196
|
+
validateUsageRecord(record);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
if (error instanceof ValidationError) {
|
|
199
|
+
throw new ValidationError(`Invalid record at index ${index}: ${error.message}`);
|
|
200
|
+
}
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
var KEY_PREFIXES = {
|
|
206
|
+
secret: "mf_sk_",
|
|
207
|
+
publishable: "mf_pk_"
|
|
208
|
+
};
|
|
209
|
+
function parseApiKey(apiKey) {
|
|
210
|
+
if (!apiKey || typeof apiKey !== "string") {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
if (apiKey.startsWith(KEY_PREFIXES.secret)) {
|
|
214
|
+
return { type: "secret" };
|
|
215
|
+
}
|
|
216
|
+
if (apiKey.startsWith(KEY_PREFIXES.publishable)) {
|
|
217
|
+
return { type: "publishable" };
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
function isSecretKey(apiKey) {
|
|
222
|
+
const parsed = parseApiKey(apiKey);
|
|
223
|
+
return parsed !== null && parsed.type === "secret";
|
|
224
|
+
}
|
|
225
|
+
function isPublishableKey(apiKey) {
|
|
226
|
+
const parsed = parseApiKey(apiKey);
|
|
227
|
+
return parsed !== null && parsed.type === "publishable";
|
|
228
|
+
}
|
|
229
|
+
function validateApiKey(apiKey) {
|
|
230
|
+
if (!apiKey || typeof apiKey !== "string") {
|
|
231
|
+
throw new ValidationError("API key is required");
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// src/utils/http.ts
|
|
236
|
+
import axios from "axios";
|
|
237
|
+
var DEFAULT_BASE_URL = "https://api-qa.costingly.com/v1";
|
|
238
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
239
|
+
var DEFAULT_RETRIES = 0;
|
|
240
|
+
var DEFAULT_RETRY_DELAY = 300;
|
|
241
|
+
var Logger = class {
|
|
242
|
+
constructor(config) {
|
|
243
|
+
this.levelPriority = {
|
|
244
|
+
debug: 0,
|
|
245
|
+
info: 1,
|
|
246
|
+
warn: 2,
|
|
247
|
+
error: 3
|
|
248
|
+
};
|
|
249
|
+
this.defaultHandler = (level, message, data) => {
|
|
250
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
251
|
+
const logMessage = `[${timestamp}] [MarginFront] [${level.toUpperCase()}] ${message}`;
|
|
252
|
+
const logFn = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
|
|
253
|
+
if (data) {
|
|
254
|
+
logFn(logMessage, data);
|
|
255
|
+
} else {
|
|
256
|
+
logFn(logMessage);
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
this.enabled = config.logging?.enabled ?? config.debug ?? false;
|
|
260
|
+
this.minLevel = config.logging?.level ?? "info";
|
|
261
|
+
this.handler = config.logging?.handler ?? this.defaultHandler;
|
|
262
|
+
}
|
|
263
|
+
shouldLog(level) {
|
|
264
|
+
return this.enabled && this.levelPriority[level] >= this.levelPriority[this.minLevel];
|
|
265
|
+
}
|
|
266
|
+
debug(message, data) {
|
|
267
|
+
if (this.shouldLog("debug")) this.handler("debug", message, data);
|
|
268
|
+
}
|
|
269
|
+
info(message, data) {
|
|
270
|
+
if (this.shouldLog("info")) this.handler("info", message, data);
|
|
271
|
+
}
|
|
272
|
+
warn(message, data) {
|
|
273
|
+
if (this.shouldLog("warn")) this.handler("warn", message, data);
|
|
274
|
+
}
|
|
275
|
+
error(message, data) {
|
|
276
|
+
if (this.shouldLog("error")) this.handler("error", message, data);
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
var Telemetry = class {
|
|
280
|
+
constructor(config) {
|
|
281
|
+
this.activeRequests = /* @__PURE__ */ new Map();
|
|
282
|
+
this.requestCount = 0;
|
|
283
|
+
this.successCount = 0;
|
|
284
|
+
this.errorCount = 0;
|
|
285
|
+
this.totalDuration = 0;
|
|
286
|
+
this.errorTypes = /* @__PURE__ */ new Map();
|
|
287
|
+
this.enabled = config.telemetry?.enabled ?? false;
|
|
288
|
+
this.sampleRate = config.telemetry?.sampleRate ?? 1;
|
|
289
|
+
this.handler = config.telemetry?.handler ?? (() => {
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
shouldCollect() {
|
|
293
|
+
return this.enabled && Math.random() <= this.sampleRate;
|
|
294
|
+
}
|
|
295
|
+
generateRequestId() {
|
|
296
|
+
return `req_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
297
|
+
}
|
|
298
|
+
normalizePath(path) {
|
|
299
|
+
if (!path) return "";
|
|
300
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
301
|
+
return normalizedPath.replace(/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "/:id").replace(/\/\d+(?=\/|$)/g, "/:id");
|
|
302
|
+
}
|
|
303
|
+
startRequest(method, path, requestId) {
|
|
304
|
+
const shouldTrack = this.shouldCollect();
|
|
305
|
+
const id = requestId || this.generateRequestId();
|
|
306
|
+
if (shouldTrack) {
|
|
307
|
+
const metrics = {
|
|
308
|
+
requestId: id,
|
|
309
|
+
method: method.toUpperCase(),
|
|
310
|
+
path: this.normalizePath(path),
|
|
311
|
+
startTime: Date.now()
|
|
312
|
+
};
|
|
313
|
+
this.activeRequests.set(id, metrics);
|
|
314
|
+
}
|
|
315
|
+
return { requestId: id, isTracked: shouldTrack };
|
|
316
|
+
}
|
|
317
|
+
endRequest(requestId, statusCode, error, retryCount) {
|
|
318
|
+
const metrics = this.activeRequests.get(requestId);
|
|
319
|
+
if (!metrics) return;
|
|
320
|
+
metrics.endTime = Date.now();
|
|
321
|
+
metrics.duration = metrics.endTime - metrics.startTime;
|
|
322
|
+
metrics.statusCode = statusCode;
|
|
323
|
+
metrics.success = !error && (statusCode ? statusCode < 400 : true);
|
|
324
|
+
metrics.retryCount = retryCount || 0;
|
|
325
|
+
if (error) {
|
|
326
|
+
metrics.errorMessage = error.message;
|
|
327
|
+
metrics.errorType = error.name || "UnknownError";
|
|
328
|
+
const currentCount = this.errorTypes.get(metrics.errorType) || 0;
|
|
329
|
+
this.errorTypes.set(metrics.errorType, currentCount + 1);
|
|
330
|
+
this.errorCount++;
|
|
331
|
+
} else {
|
|
332
|
+
this.successCount++;
|
|
333
|
+
}
|
|
334
|
+
this.requestCount++;
|
|
335
|
+
this.totalDuration += metrics.duration;
|
|
336
|
+
this.handler(metrics);
|
|
337
|
+
this.activeRequests.delete(requestId);
|
|
338
|
+
}
|
|
339
|
+
getStats() {
|
|
340
|
+
return {
|
|
341
|
+
requestCount: this.requestCount,
|
|
342
|
+
successCount: this.successCount,
|
|
343
|
+
errorCount: this.errorCount,
|
|
344
|
+
successRate: this.requestCount > 0 ? this.successCount / this.requestCount : 1,
|
|
345
|
+
averageDuration: this.requestCount > 0 ? this.totalDuration / this.requestCount : 0,
|
|
346
|
+
errorBreakdown: Object.fromEntries(this.errorTypes)
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
resetStats() {
|
|
350
|
+
this.requestCount = 0;
|
|
351
|
+
this.successCount = 0;
|
|
352
|
+
this.errorCount = 0;
|
|
353
|
+
this.totalDuration = 0;
|
|
354
|
+
this.errorTypes.clear();
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
function calculateBackoff(retryCount, initialDelay) {
|
|
358
|
+
return initialDelay * Math.pow(2, retryCount);
|
|
359
|
+
}
|
|
360
|
+
function sleep(ms) {
|
|
361
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
362
|
+
}
|
|
363
|
+
function isRetryable(error) {
|
|
364
|
+
if (error.response) {
|
|
365
|
+
const status = error.response.status;
|
|
366
|
+
return status === 429 || status >= 500 && status < 600;
|
|
367
|
+
}
|
|
368
|
+
return !error.response;
|
|
369
|
+
}
|
|
370
|
+
var HttpClient = class {
|
|
371
|
+
constructor(config) {
|
|
372
|
+
this.logger = new Logger(config);
|
|
373
|
+
this.telemetry = new Telemetry(config);
|
|
374
|
+
this.retries = config.retries ?? DEFAULT_RETRIES;
|
|
375
|
+
this.retryDelay = config.retryDelay ?? DEFAULT_RETRY_DELAY;
|
|
376
|
+
const baseURL = config.baseUrl || config.baseURL || DEFAULT_BASE_URL;
|
|
377
|
+
const isBrowser = typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
|
|
378
|
+
const headers = {
|
|
379
|
+
"Content-Type": "application/json",
|
|
380
|
+
Accept: "application/json",
|
|
381
|
+
"x-api-key": config.apiKey,
|
|
382
|
+
...config.headers
|
|
383
|
+
};
|
|
384
|
+
if (!isBrowser) {
|
|
385
|
+
headers["User-Agent"] = "marginfront-node/1.0.0";
|
|
386
|
+
}
|
|
387
|
+
this.client = axios.create({
|
|
388
|
+
baseURL,
|
|
389
|
+
timeout: config.timeout || DEFAULT_TIMEOUT,
|
|
390
|
+
headers
|
|
391
|
+
});
|
|
392
|
+
this.client.interceptors.request.use(
|
|
393
|
+
(requestConfig) => {
|
|
394
|
+
const customConfig = requestConfig;
|
|
395
|
+
customConfig.metadata = customConfig.metadata || {};
|
|
396
|
+
const { requestId, isTracked } = this.telemetry.startRequest(
|
|
397
|
+
requestConfig.method || "GET",
|
|
398
|
+
requestConfig.url || "",
|
|
399
|
+
customConfig.metadata.requestId
|
|
400
|
+
);
|
|
401
|
+
requestConfig.headers = requestConfig.headers || {};
|
|
402
|
+
requestConfig.headers["X-Request-ID"] = requestId;
|
|
403
|
+
customConfig.metadata.requestId = requestId;
|
|
404
|
+
customConfig.metadata.telemetryTracked = isTracked;
|
|
405
|
+
this.logger.debug(`Request: ${requestConfig.method?.toUpperCase()} ${requestConfig.url}`, {
|
|
406
|
+
requestId
|
|
407
|
+
});
|
|
408
|
+
return requestConfig;
|
|
409
|
+
},
|
|
410
|
+
(error) => Promise.reject(error)
|
|
411
|
+
);
|
|
412
|
+
this.client.interceptors.response.use(
|
|
413
|
+
(response) => {
|
|
414
|
+
const customConfig = response.config;
|
|
415
|
+
const requestId = customConfig.metadata?.requestId;
|
|
416
|
+
const isTracked = customConfig.metadata?.telemetryTracked;
|
|
417
|
+
if (isTracked && requestId) {
|
|
418
|
+
this.telemetry.endRequest(requestId, response.status);
|
|
419
|
+
}
|
|
420
|
+
this.logger.debug(`Response: ${response.status}`, { requestId });
|
|
421
|
+
return response;
|
|
422
|
+
},
|
|
423
|
+
(error) => {
|
|
424
|
+
const customConfig = error.config;
|
|
425
|
+
const requestId = customConfig?.metadata?.requestId;
|
|
426
|
+
const isTracked = customConfig?.metadata?.telemetryTracked;
|
|
427
|
+
const retryCount = customConfig?.metadata?.retryCount || 0;
|
|
428
|
+
if (isTracked && requestId) {
|
|
429
|
+
this.telemetry.endRequest(requestId, error.response?.status, error, retryCount);
|
|
430
|
+
}
|
|
431
|
+
this.logger.error(`Error: ${error.message}`, {
|
|
432
|
+
requestId,
|
|
433
|
+
status: error.response?.status
|
|
434
|
+
});
|
|
435
|
+
return Promise.reject(error);
|
|
436
|
+
}
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Make a request with retry logic
|
|
441
|
+
*/
|
|
442
|
+
async request(config) {
|
|
443
|
+
let lastError;
|
|
444
|
+
config.metadata = config.metadata || {};
|
|
445
|
+
for (let retry = 0; retry <= this.retries; retry++) {
|
|
446
|
+
try {
|
|
447
|
+
config.metadata.retryCount = retry;
|
|
448
|
+
if (retry > 0) {
|
|
449
|
+
this.logger.info(`Retry attempt ${retry}/${this.retries}`, {
|
|
450
|
+
requestId: config.metadata.requestId
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
const response = await this.client.request(config);
|
|
454
|
+
return response.data;
|
|
455
|
+
} catch (error) {
|
|
456
|
+
lastError = error;
|
|
457
|
+
if (retry < this.retries && isRetryable(lastError)) {
|
|
458
|
+
const retryAfterHeader = lastError.response?.headers?.["retry-after"];
|
|
459
|
+
const backoffTime = retryAfterHeader ? parseInt(retryAfterHeader, 10) * 1e3 : calculateBackoff(retry, this.retryDelay);
|
|
460
|
+
this.logger.debug(`Retrying in ${backoffTime}ms...`);
|
|
461
|
+
await sleep(backoffTime);
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
throw parseApiError(lastError);
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Make a GET request
|
|
471
|
+
*/
|
|
472
|
+
async get(path, params) {
|
|
473
|
+
const config = {
|
|
474
|
+
method: "GET",
|
|
475
|
+
url: path
|
|
476
|
+
};
|
|
477
|
+
if (params) {
|
|
478
|
+
config.params = Object.fromEntries(
|
|
479
|
+
Object.entries(params).filter(([, v]) => v !== void 0 && v !== null)
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
return this.request(config);
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Make a POST request
|
|
486
|
+
*/
|
|
487
|
+
async post(path, data) {
|
|
488
|
+
return this.request({
|
|
489
|
+
method: "POST",
|
|
490
|
+
url: path,
|
|
491
|
+
data
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Make a PUT request
|
|
496
|
+
*/
|
|
497
|
+
async put(path, data) {
|
|
498
|
+
return this.request({
|
|
499
|
+
method: "PUT",
|
|
500
|
+
url: path,
|
|
501
|
+
data
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Make a PATCH request
|
|
506
|
+
*/
|
|
507
|
+
async patch(path, data) {
|
|
508
|
+
return this.request({
|
|
509
|
+
method: "PATCH",
|
|
510
|
+
url: path,
|
|
511
|
+
data
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Make a DELETE request
|
|
516
|
+
*/
|
|
517
|
+
async delete(path) {
|
|
518
|
+
return this.request({
|
|
519
|
+
method: "DELETE",
|
|
520
|
+
url: path
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Get telemetry statistics
|
|
525
|
+
*/
|
|
526
|
+
getTelemetryStats() {
|
|
527
|
+
return this.telemetry.getStats();
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Reset telemetry statistics
|
|
531
|
+
*/
|
|
532
|
+
resetTelemetryStats() {
|
|
533
|
+
this.telemetry.resetStats();
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
// src/resources/usage.ts
|
|
538
|
+
var UsageResource = class {
|
|
539
|
+
constructor(http) {
|
|
540
|
+
this.http = http;
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Track events for a customer - supports both single record and batch operations
|
|
544
|
+
*
|
|
545
|
+
* @param params - Event tracking parameters (single record or batch)
|
|
546
|
+
* @returns Tracked event data
|
|
547
|
+
*
|
|
548
|
+
* @example
|
|
549
|
+
* ```typescript
|
|
550
|
+
* // Single event
|
|
551
|
+
* await client.usage.trackEvent({
|
|
552
|
+
* agentId: 'agent_123',
|
|
553
|
+
* customerExternalId: 'customer_456',
|
|
554
|
+
* signalName: 'api_call',
|
|
555
|
+
* quantity: 1
|
|
556
|
+
* });
|
|
557
|
+
*
|
|
558
|
+
* // Batch tracking
|
|
559
|
+
* await client.usage.trackEvent({
|
|
560
|
+
* records: [
|
|
561
|
+
* { customerExternalId: 'cust_1', agentId: 'agent_123', signalName: 'api_call', quantity: 10 },
|
|
562
|
+
* { customerExternalId: 'cust_2', agentId: 'agent_123', signalName: 'storage', quantity: 100 }
|
|
563
|
+
* ]
|
|
564
|
+
* });
|
|
565
|
+
* ```
|
|
566
|
+
*/
|
|
567
|
+
async trackEvent(params) {
|
|
568
|
+
if ("records" in params) {
|
|
569
|
+
const records = params.records.map((record2) => this.normalizeRecord(record2));
|
|
570
|
+
validateUsageRecords(records);
|
|
571
|
+
const response2 = await this.http.post("/sdk/usage/record", {
|
|
572
|
+
records
|
|
573
|
+
});
|
|
574
|
+
return this.convertToBatchResponse(response2);
|
|
575
|
+
}
|
|
576
|
+
const record = this.normalizeRecord(params);
|
|
577
|
+
validateUsageRecord(record);
|
|
578
|
+
const response = await this.http.post("/sdk/usage/record", {
|
|
579
|
+
records: [record]
|
|
580
|
+
});
|
|
581
|
+
if (response.results.success.length === 1) {
|
|
582
|
+
const successResult = response.results.success[0];
|
|
583
|
+
return {
|
|
584
|
+
id: successResult.eventId,
|
|
585
|
+
success: true,
|
|
586
|
+
eventId: successResult.eventId,
|
|
587
|
+
rawEventId: successResult.rawEventId,
|
|
588
|
+
timestamp: successResult.timestamp
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
return this.convertToBatchResponse(response);
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Record a single usage event
|
|
595
|
+
*
|
|
596
|
+
* @param record - The usage record to track
|
|
597
|
+
* @returns The response containing processing results
|
|
598
|
+
*
|
|
599
|
+
* @example
|
|
600
|
+
* ```typescript
|
|
601
|
+
* const result = await client.usage.record({
|
|
602
|
+
* customerExternalId: 'cust_123',
|
|
603
|
+
* agentId: 'agent_abc',
|
|
604
|
+
* signalName: 'api_call',
|
|
605
|
+
* quantity: 1,
|
|
606
|
+
* metadata: { model: 'gpt-4' }
|
|
607
|
+
* });
|
|
608
|
+
* ```
|
|
609
|
+
*/
|
|
610
|
+
async record(record) {
|
|
611
|
+
const normalizedRecord = this.normalizeRecord(record);
|
|
612
|
+
validateUsageRecord(normalizedRecord);
|
|
613
|
+
return this.http.post("/sdk/usage/record", {
|
|
614
|
+
records: [normalizedRecord]
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Record multiple usage events in a batch
|
|
619
|
+
*
|
|
620
|
+
* @param records - Array of usage records to track
|
|
621
|
+
* @returns The response containing processing results for all records
|
|
622
|
+
*
|
|
623
|
+
* @example
|
|
624
|
+
* ```typescript
|
|
625
|
+
* const result = await client.usage.recordBatch([
|
|
626
|
+
* { customerExternalId: 'cust_123', agentId: 'agent_abc', signalName: 'api_call', quantity: 5 },
|
|
627
|
+
* { customerExternalId: 'cust_456', agentId: 'agent_abc', signalName: 'api_call', quantity: 3 },
|
|
628
|
+
* ]);
|
|
629
|
+
* console.log(`Processed ${result.successful} of ${result.processed} records`);
|
|
630
|
+
* ```
|
|
631
|
+
*/
|
|
632
|
+
async recordBatch(records) {
|
|
633
|
+
const normalizedRecords = records.map((record) => this.normalizeRecord(record));
|
|
634
|
+
validateUsageRecords(normalizedRecords);
|
|
635
|
+
return this.http.post("/sdk/usage/record", {
|
|
636
|
+
records: normalizedRecords
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Normalize a usage record
|
|
641
|
+
*/
|
|
642
|
+
normalizeRecord(record) {
|
|
643
|
+
const normalized = { ...record };
|
|
644
|
+
if (normalized.quantity === void 0) {
|
|
645
|
+
normalized.quantity = 1;
|
|
646
|
+
}
|
|
647
|
+
if (normalized.usageDate instanceof Date) {
|
|
648
|
+
normalized.usageDate = normalized.usageDate.toISOString();
|
|
649
|
+
}
|
|
650
|
+
return normalized;
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Convert native response to BatchEventResponse format
|
|
654
|
+
*/
|
|
655
|
+
convertToBatchResponse(response) {
|
|
656
|
+
return {
|
|
657
|
+
success: response.failed === 0,
|
|
658
|
+
totalRecords: response.processed,
|
|
659
|
+
successCount: response.successful,
|
|
660
|
+
failureCount: response.failed,
|
|
661
|
+
results: [
|
|
662
|
+
...response.results.success.map((result) => ({
|
|
663
|
+
success: true,
|
|
664
|
+
responseData: {
|
|
665
|
+
id: result.eventId,
|
|
666
|
+
success: true,
|
|
667
|
+
eventId: result.eventId,
|
|
668
|
+
rawEventId: result.rawEventId,
|
|
669
|
+
timestamp: result.timestamp
|
|
670
|
+
}
|
|
671
|
+
})),
|
|
672
|
+
...response.results.failed.map((result) => ({
|
|
673
|
+
success: false,
|
|
674
|
+
error: result.error,
|
|
675
|
+
originalData: result.record
|
|
676
|
+
}))
|
|
677
|
+
]
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
// src/resources/customers.ts
|
|
683
|
+
var CustomersResource = class {
|
|
684
|
+
constructor(http) {
|
|
685
|
+
this.http = http;
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Create a new customer
|
|
689
|
+
*
|
|
690
|
+
* @param params - Customer creation parameters
|
|
691
|
+
* @returns The created customer
|
|
692
|
+
*
|
|
693
|
+
* @example
|
|
694
|
+
* ```typescript
|
|
695
|
+
* const customer = await client.customers.create({
|
|
696
|
+
* name: 'Acme Corp',
|
|
697
|
+
* externalId: 'acme_123',
|
|
698
|
+
* email: 'billing@acme.com',
|
|
699
|
+
* agentId: 'agent_abc' // auto-creates subscription
|
|
700
|
+
* });
|
|
701
|
+
* ```
|
|
702
|
+
*/
|
|
703
|
+
async create(params) {
|
|
704
|
+
validateRequired(params.name, "name");
|
|
705
|
+
return this.http.post("/sdk/customers", params);
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Get a single customer by ID
|
|
709
|
+
*
|
|
710
|
+
* @param id - The customer's ID
|
|
711
|
+
* @returns The customer with subscriptions
|
|
712
|
+
*
|
|
713
|
+
* @example
|
|
714
|
+
* ```typescript
|
|
715
|
+
* const customer = await client.customers.get('cust_123');
|
|
716
|
+
* console.log(customer.name);
|
|
717
|
+
* ```
|
|
718
|
+
*/
|
|
719
|
+
async get(id) {
|
|
720
|
+
validateRequired(id, "id");
|
|
721
|
+
return this.http.get(`/sdk/customers/${id}`);
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Update an existing customer
|
|
725
|
+
*
|
|
726
|
+
* @param id - The customer's ID
|
|
727
|
+
* @param params - Fields to update
|
|
728
|
+
* @returns The updated customer
|
|
729
|
+
*
|
|
730
|
+
* @example
|
|
731
|
+
* ```typescript
|
|
732
|
+
* const customer = await client.customers.update('cust_123', {
|
|
733
|
+
* name: 'New Company Name',
|
|
734
|
+
* email: 'new-email@company.com'
|
|
735
|
+
* });
|
|
736
|
+
* ```
|
|
737
|
+
*/
|
|
738
|
+
async update(id, params) {
|
|
739
|
+
validateRequired(id, "id");
|
|
740
|
+
return this.http.patch(`/sdk/customers/${id}`, params);
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Delete a customer
|
|
744
|
+
*
|
|
745
|
+
* @param id - The customer's ID
|
|
746
|
+
*
|
|
747
|
+
* @example
|
|
748
|
+
* ```typescript
|
|
749
|
+
* await client.customers.delete('cust_123');
|
|
750
|
+
* ```
|
|
751
|
+
*/
|
|
752
|
+
async delete(id) {
|
|
753
|
+
validateRequired(id, "id");
|
|
754
|
+
await this.http.delete(`/sdk/customers/${id}`);
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* List customers with pagination and filtering
|
|
758
|
+
*
|
|
759
|
+
* @param params - List parameters
|
|
760
|
+
* @returns Paginated list of customers
|
|
761
|
+
*
|
|
762
|
+
* @example
|
|
763
|
+
* ```typescript
|
|
764
|
+
* const { data, totalResults, hasMore } = await client.customers.list({
|
|
765
|
+
* limit: 10,
|
|
766
|
+
* page: 1
|
|
767
|
+
* });
|
|
768
|
+
* ```
|
|
769
|
+
*/
|
|
770
|
+
async list(params) {
|
|
771
|
+
const response = await this.http.get("/sdk/customers", params);
|
|
772
|
+
if (Array.isArray(response)) {
|
|
773
|
+
return {
|
|
774
|
+
data: response,
|
|
775
|
+
totalResults: response.length,
|
|
776
|
+
page: params?.page || 1,
|
|
777
|
+
limit: params?.limit || response.length,
|
|
778
|
+
hasMore: false
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
return response;
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
// src/resources/invoices.ts
|
|
786
|
+
var InvoicesResource = class {
|
|
787
|
+
constructor(http) {
|
|
788
|
+
this.http = http;
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* List invoices with optional filters
|
|
792
|
+
*
|
|
793
|
+
* @param params - Optional filter parameters
|
|
794
|
+
* @returns Paginated list of invoices
|
|
795
|
+
*
|
|
796
|
+
* @example
|
|
797
|
+
* ```typescript
|
|
798
|
+
* const { invoices, totalResults } = await client.invoices.list({
|
|
799
|
+
* customerId: 'cust_123',
|
|
800
|
+
* status: 'pending',
|
|
801
|
+
* page: 1,
|
|
802
|
+
* limit: 20
|
|
803
|
+
* });
|
|
804
|
+
* ```
|
|
805
|
+
*/
|
|
806
|
+
async list(params) {
|
|
807
|
+
return this.http.get("/sdk/invoices", params);
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Get a single invoice by ID
|
|
811
|
+
*
|
|
812
|
+
* @param invoiceId - The invoice's ID
|
|
813
|
+
* @returns The invoice with full details including line items and payments
|
|
814
|
+
*
|
|
815
|
+
* @example
|
|
816
|
+
* ```typescript
|
|
817
|
+
* const invoice = await client.invoices.get('inv_abc');
|
|
818
|
+
* console.log(`Invoice ${invoice.invoiceNumber}: ${invoice.totalAmount}`);
|
|
819
|
+
* ```
|
|
820
|
+
*/
|
|
821
|
+
async get(invoiceId) {
|
|
822
|
+
validateRequired(invoiceId, "invoiceId");
|
|
823
|
+
return this.http.get(`/sdk/invoices/${invoiceId}`);
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
// src/resources/analytics.ts
|
|
828
|
+
var AnalyticsResource = class {
|
|
829
|
+
constructor(http) {
|
|
830
|
+
this.http = http;
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Get usage analytics for a date range
|
|
834
|
+
*
|
|
835
|
+
* @param params - Analytics query parameters
|
|
836
|
+
* @returns Usage analytics with summary and time-series data
|
|
837
|
+
*
|
|
838
|
+
* @example
|
|
839
|
+
* ```typescript
|
|
840
|
+
* const analytics = await client.analytics.usage({
|
|
841
|
+
* startDate: '2024-01-01',
|
|
842
|
+
* endDate: '2024-01-31',
|
|
843
|
+
* groupBy: 'daily',
|
|
844
|
+
* customerId: 'cust_123'
|
|
845
|
+
* });
|
|
846
|
+
*
|
|
847
|
+
* console.log(`Total usage: ${analytics.summary.totalQuantity}`);
|
|
848
|
+
* console.log(`Total cost: $${analytics.summary.totalCost}`);
|
|
849
|
+
*
|
|
850
|
+
* // Time series data
|
|
851
|
+
* analytics.data.forEach(point => {
|
|
852
|
+
* console.log(`${point.date}: ${point.quantity} units, $${point.cost}`);
|
|
853
|
+
* });
|
|
854
|
+
* ```
|
|
855
|
+
*/
|
|
856
|
+
async usage(params) {
|
|
857
|
+
validateRequired(params.startDate, "startDate");
|
|
858
|
+
validateRequired(params.endDate, "endDate");
|
|
859
|
+
validateDateString(params.startDate, "startDate");
|
|
860
|
+
validateDateString(params.endDate, "endDate");
|
|
861
|
+
return this.http.get("/sdk/analytics/usage", params);
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
|
|
865
|
+
// src/resources/subscriptions.ts
|
|
866
|
+
var SubscriptionsResource = class {
|
|
867
|
+
constructor(http) {
|
|
868
|
+
this.http = http;
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* List subscriptions with optional filters
|
|
872
|
+
*
|
|
873
|
+
* @param params - Optional filter parameters
|
|
874
|
+
* @returns Paginated list of subscriptions
|
|
875
|
+
*
|
|
876
|
+
* @example
|
|
877
|
+
* ```typescript
|
|
878
|
+
* const { subscriptions, totalResults } = await client.subscriptions.list({
|
|
879
|
+
* status: 'active',
|
|
880
|
+
* customerId: 'cust_123',
|
|
881
|
+
* page: 1,
|
|
882
|
+
* limit: 20
|
|
883
|
+
* });
|
|
884
|
+
* ```
|
|
885
|
+
*/
|
|
886
|
+
async list(params) {
|
|
887
|
+
return this.http.get("/sdk/subscriptions", params);
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Get a single subscription by ID
|
|
891
|
+
*
|
|
892
|
+
* @param subscriptionId - The subscription's ID
|
|
893
|
+
* @returns The subscription with full details including usage summary
|
|
894
|
+
*
|
|
895
|
+
* @example
|
|
896
|
+
* ```typescript
|
|
897
|
+
* const sub = await client.subscriptions.get('sub_abc');
|
|
898
|
+
* console.log(`Status: ${sub.status}`);
|
|
899
|
+
* console.log(`Usage this period: ${sub.usage.totalQuantity}`);
|
|
900
|
+
* ```
|
|
901
|
+
*/
|
|
902
|
+
async get(subscriptionId) {
|
|
903
|
+
validateRequired(subscriptionId, "subscriptionId");
|
|
904
|
+
return this.http.get(`/sdk/subscriptions/${subscriptionId}`);
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
// src/resources/portal-sessions.ts
|
|
909
|
+
var PortalSessionsResource = class {
|
|
910
|
+
constructor(http, assertSecretKey) {
|
|
911
|
+
this.http = http;
|
|
912
|
+
this.assertSecretKey = assertSecretKey;
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* Create a new portal session
|
|
916
|
+
*
|
|
917
|
+
* Creates a short-lived portal session for a customer.
|
|
918
|
+
* The returned URL can be used to redirect the customer to their billing portal.
|
|
919
|
+
*
|
|
920
|
+
* @param params - Portal session parameters
|
|
921
|
+
* @returns Created portal session with URL and token
|
|
922
|
+
* @throws ValidationError if neither customerId nor customerExternalId is provided
|
|
923
|
+
* @throws AuthenticationError if using a publishable key
|
|
924
|
+
*
|
|
925
|
+
* @example
|
|
926
|
+
* ```typescript
|
|
927
|
+
* // By customer ID
|
|
928
|
+
* const session = await client.portalSessions.create({
|
|
929
|
+
* customerId: 'cust_123',
|
|
930
|
+
* returnUrl: 'https://myapp.com/account'
|
|
931
|
+
* });
|
|
932
|
+
*
|
|
933
|
+
* // By external ID
|
|
934
|
+
* const session = await client.portalSessions.create({
|
|
935
|
+
* customerExternalId: 'your_customer_id',
|
|
936
|
+
* features: ['invoices', 'usage']
|
|
937
|
+
* });
|
|
938
|
+
*
|
|
939
|
+
* console.log(session.url); // Redirect customer here
|
|
940
|
+
* ```
|
|
941
|
+
*/
|
|
942
|
+
async create(params) {
|
|
943
|
+
this.assertSecretKey();
|
|
944
|
+
if (!params.customerId && !params.customerExternalId) {
|
|
945
|
+
throw new ValidationError(
|
|
946
|
+
"Either customerId or customerExternalId is required"
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
return this.http.post("/sdk/portal-sessions", params);
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Get a portal session by ID
|
|
953
|
+
*
|
|
954
|
+
* @param sessionId - Portal session ID
|
|
955
|
+
* @returns Portal session details
|
|
956
|
+
*
|
|
957
|
+
* @example
|
|
958
|
+
* ```typescript
|
|
959
|
+
* const session = await client.portalSessions.get('ps_123');
|
|
960
|
+
* console.log(session.expiresAt);
|
|
961
|
+
* ```
|
|
962
|
+
*/
|
|
963
|
+
async get(sessionId) {
|
|
964
|
+
this.assertSecretKey();
|
|
965
|
+
if (!sessionId) {
|
|
966
|
+
throw new ValidationError("sessionId is required");
|
|
967
|
+
}
|
|
968
|
+
return this.http.get(`/sdk/portal-sessions/${sessionId}`);
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* List portal sessions
|
|
972
|
+
*
|
|
973
|
+
* @param params - Optional filters
|
|
974
|
+
* @returns List of portal sessions
|
|
975
|
+
*
|
|
976
|
+
* @example
|
|
977
|
+
* ```typescript
|
|
978
|
+
* // List all active sessions
|
|
979
|
+
* const { data } = await client.portalSessions.list();
|
|
980
|
+
*
|
|
981
|
+
* // List sessions for a specific customer
|
|
982
|
+
* const { data } = await client.portalSessions.list({
|
|
983
|
+
* customerId: 'cust_123',
|
|
984
|
+
* includeExpired: true
|
|
985
|
+
* });
|
|
986
|
+
* ```
|
|
987
|
+
*/
|
|
988
|
+
async list(params) {
|
|
989
|
+
this.assertSecretKey();
|
|
990
|
+
return this.http.get("/sdk/portal-sessions", {
|
|
991
|
+
customerId: params?.customerId,
|
|
992
|
+
limit: params?.limit,
|
|
993
|
+
includeExpired: params?.includeExpired
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Revoke a portal session
|
|
998
|
+
*
|
|
999
|
+
* Immediately invalidates the session, preventing further access.
|
|
1000
|
+
*
|
|
1001
|
+
* @param sessionId - Portal session ID to revoke
|
|
1002
|
+
*
|
|
1003
|
+
* @example
|
|
1004
|
+
* ```typescript
|
|
1005
|
+
* await client.portalSessions.revoke('ps_123');
|
|
1006
|
+
* ```
|
|
1007
|
+
*/
|
|
1008
|
+
async revoke(sessionId) {
|
|
1009
|
+
this.assertSecretKey();
|
|
1010
|
+
if (!sessionId) {
|
|
1011
|
+
throw new ValidationError("sessionId is required");
|
|
1012
|
+
}
|
|
1013
|
+
await this.http.delete(`/sdk/portal-sessions/${sessionId}`);
|
|
1014
|
+
}
|
|
1015
|
+
};
|
|
1016
|
+
|
|
1017
|
+
// src/client.ts
|
|
1018
|
+
var MarginFrontClient = class {
|
|
1019
|
+
/**
|
|
1020
|
+
* Create a new MarginFront client
|
|
1021
|
+
*
|
|
1022
|
+
* @param apiKeyOrConfig - Either an API key string or a configuration object
|
|
1023
|
+
* @param options - Optional client options (when first param is API key)
|
|
1024
|
+
*
|
|
1025
|
+
* @example
|
|
1026
|
+
* ```typescript
|
|
1027
|
+
* // Simple initialization
|
|
1028
|
+
* const client = new MarginFrontClient('mf_sk_your_secret_key');
|
|
1029
|
+
*
|
|
1030
|
+
* // With options
|
|
1031
|
+
* const client = new MarginFrontClient('mf_sk_your_secret_key', {
|
|
1032
|
+
* timeout: 10000,
|
|
1033
|
+
* retries: 3
|
|
1034
|
+
* });
|
|
1035
|
+
*
|
|
1036
|
+
* // With config object
|
|
1037
|
+
* const client = new MarginFrontClient({
|
|
1038
|
+
* apiKey: 'mf_sk_your_secret_key',
|
|
1039
|
+
* baseUrl: 'https://api.example.com/v1',
|
|
1040
|
+
* debug: true
|
|
1041
|
+
* });
|
|
1042
|
+
* ```
|
|
1043
|
+
*/
|
|
1044
|
+
constructor(apiKeyOrConfig, options = {}) {
|
|
1045
|
+
this._orgInfo = null;
|
|
1046
|
+
let config;
|
|
1047
|
+
if (typeof apiKeyOrConfig === "string") {
|
|
1048
|
+
config = {
|
|
1049
|
+
apiKey: apiKeyOrConfig,
|
|
1050
|
+
...options
|
|
1051
|
+
};
|
|
1052
|
+
} else {
|
|
1053
|
+
config = apiKeyOrConfig;
|
|
1054
|
+
}
|
|
1055
|
+
validateApiKey(config.apiKey);
|
|
1056
|
+
this._apiKey = config.apiKey;
|
|
1057
|
+
const keyInfo = parseApiKey(config.apiKey);
|
|
1058
|
+
this._keyType = keyInfo?.type ?? "secret";
|
|
1059
|
+
this.http = new HttpClient(config);
|
|
1060
|
+
this.usage = new UsageResource(this.http);
|
|
1061
|
+
this.customers = new CustomersResource(this.http);
|
|
1062
|
+
this.invoices = new InvoicesResource(this.http);
|
|
1063
|
+
this.analytics = new AnalyticsResource(this.http);
|
|
1064
|
+
this.subscriptions = new SubscriptionsResource(this.http);
|
|
1065
|
+
this.portalSessions = new PortalSessionsResource(
|
|
1066
|
+
this.http,
|
|
1067
|
+
() => this.assertSecretKey()
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* No-op — the server enforces key type for secret-only operations.
|
|
1072
|
+
* Kept so PortalSessionsResource can call it without breaking.
|
|
1073
|
+
*/
|
|
1074
|
+
assertSecretKey() {
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Connect to the MarginFront API and verify credentials
|
|
1078
|
+
*
|
|
1079
|
+
* This must be called once before using any API methods that require verification.
|
|
1080
|
+
* Optional but recommended for validating your API key.
|
|
1081
|
+
*
|
|
1082
|
+
* @returns Promise resolving to the client instance for chaining
|
|
1083
|
+
* @throws AuthenticationError if API key is invalid
|
|
1084
|
+
* @throws InitializationError for other initialization errors
|
|
1085
|
+
*
|
|
1086
|
+
* @example
|
|
1087
|
+
* ```typescript
|
|
1088
|
+
* const client = new MarginFrontClient('mf_sk_your_secret_key');
|
|
1089
|
+
*
|
|
1090
|
+
* try {
|
|
1091
|
+
* await client.connect();
|
|
1092
|
+
* console.log('Connected to MarginFront API');
|
|
1093
|
+
* } catch (error) {
|
|
1094
|
+
* console.error('Failed to connect:', error);
|
|
1095
|
+
* }
|
|
1096
|
+
* ```
|
|
1097
|
+
*/
|
|
1098
|
+
async connect() {
|
|
1099
|
+
try {
|
|
1100
|
+
const result = await this.http.get("/sdk/verify");
|
|
1101
|
+
if (!result || !result.organization || !result.organization.id) {
|
|
1102
|
+
throw new InitializationError("Unexpected response format from API key verification");
|
|
1103
|
+
}
|
|
1104
|
+
this._orgInfo = result.organization;
|
|
1105
|
+
return this;
|
|
1106
|
+
} catch (error) {
|
|
1107
|
+
const err = error;
|
|
1108
|
+
const statusCode = err.statusCode || err.response?.status;
|
|
1109
|
+
const errorMessage = err.message || "Unknown error";
|
|
1110
|
+
const requestId = err.requestId || "unknown";
|
|
1111
|
+
if (statusCode === 401) {
|
|
1112
|
+
throw new AuthenticationError(
|
|
1113
|
+
"Invalid API key. Please check your API key and try again.",
|
|
1114
|
+
{ requestId }
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
throw new InitializationError(`Connection failed: ${errorMessage}`, { requestId });
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
/**
|
|
1121
|
+
* Verify the API key and get organization details
|
|
1122
|
+
*
|
|
1123
|
+
* @returns Verification result with organization info
|
|
1124
|
+
*
|
|
1125
|
+
* @example
|
|
1126
|
+
* ```typescript
|
|
1127
|
+
* const result = await client.verify();
|
|
1128
|
+
* if (result.verified) {
|
|
1129
|
+
* console.log(`Connected to ${result.organization.name}`);
|
|
1130
|
+
* }
|
|
1131
|
+
* ```
|
|
1132
|
+
*/
|
|
1133
|
+
async verify() {
|
|
1134
|
+
const result = await this.http.get("/sdk/verify");
|
|
1135
|
+
if (result.organization && !this._orgInfo) {
|
|
1136
|
+
this._orgInfo = result.organization;
|
|
1137
|
+
}
|
|
1138
|
+
return result;
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Get organization information
|
|
1142
|
+
*
|
|
1143
|
+
* @returns Organization information from API key verification
|
|
1144
|
+
* @throws Error if organization info is not available (call connect() first)
|
|
1145
|
+
*
|
|
1146
|
+
* @example
|
|
1147
|
+
* ```typescript
|
|
1148
|
+
* await client.connect();
|
|
1149
|
+
* const org = client.getOrganization();
|
|
1150
|
+
* console.log(`Organization: ${org.name}`);
|
|
1151
|
+
* ```
|
|
1152
|
+
*/
|
|
1153
|
+
getOrganization() {
|
|
1154
|
+
if (!this._orgInfo) {
|
|
1155
|
+
throw new Error(
|
|
1156
|
+
"Organization information is not available. Make sure the API key is valid and you have called connect()"
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
return { ...this._orgInfo };
|
|
1160
|
+
}
|
|
1161
|
+
/**
|
|
1162
|
+
* Track event for a customer (shorthand method)
|
|
1163
|
+
*
|
|
1164
|
+
* @param params - Event tracking parameters
|
|
1165
|
+
* @returns Tracked event data
|
|
1166
|
+
*
|
|
1167
|
+
* @example
|
|
1168
|
+
* ```typescript
|
|
1169
|
+
* // Simple event tracking
|
|
1170
|
+
* await client.trackEvent({
|
|
1171
|
+
* agentId: 'agent_123',
|
|
1172
|
+
* customerExternalId: 'customer_456',
|
|
1173
|
+
* signalName: 'api_call',
|
|
1174
|
+
* quantity: 1
|
|
1175
|
+
* });
|
|
1176
|
+
*
|
|
1177
|
+
* // Batch tracking
|
|
1178
|
+
* await client.trackEvent({
|
|
1179
|
+
* records: [
|
|
1180
|
+
* { customerExternalId: 'cust_1', agentId: 'agent_123', signalName: 'api_call', quantity: 10 },
|
|
1181
|
+
* { customerExternalId: 'cust_2', agentId: 'agent_123', signalName: 'storage', quantity: 100 }
|
|
1182
|
+
* ]
|
|
1183
|
+
* });
|
|
1184
|
+
* ```
|
|
1185
|
+
*/
|
|
1186
|
+
async trackEvent(params) {
|
|
1187
|
+
return this.usage.trackEvent(params);
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Re-verify the API key
|
|
1191
|
+
*
|
|
1192
|
+
* Useful if you suspect the API key status might have changed
|
|
1193
|
+
*
|
|
1194
|
+
* @returns Updated organization information
|
|
1195
|
+
*/
|
|
1196
|
+
async verifyApiKey() {
|
|
1197
|
+
const result = await this.verify();
|
|
1198
|
+
if (!result.organization) {
|
|
1199
|
+
throw new InitializationError("Unexpected response format from API key verification");
|
|
1200
|
+
}
|
|
1201
|
+
this._orgInfo = result.organization;
|
|
1202
|
+
return { ...this._orgInfo };
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* Get current telemetry statistics
|
|
1206
|
+
*
|
|
1207
|
+
* @returns Telemetry statistics
|
|
1208
|
+
*
|
|
1209
|
+
* @example
|
|
1210
|
+
* ```typescript
|
|
1211
|
+
* const stats = client.getTelemetryStats();
|
|
1212
|
+
* console.log(`Total Requests: ${stats.requestCount}`);
|
|
1213
|
+
* console.log(`Success Rate: ${(stats.successRate * 100).toFixed(2)}%`);
|
|
1214
|
+
* ```
|
|
1215
|
+
*/
|
|
1216
|
+
getTelemetryStats() {
|
|
1217
|
+
return this.http.getTelemetryStats();
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Reset telemetry statistics
|
|
1221
|
+
*/
|
|
1222
|
+
resetTelemetryStats() {
|
|
1223
|
+
this.http.resetTelemetryStats();
|
|
1224
|
+
}
|
|
1225
|
+
/**
|
|
1226
|
+
* Get the API key type (secret or publishable)
|
|
1227
|
+
*
|
|
1228
|
+
* @returns 'secret' for mf_sk_* keys, 'publishable' for mf_pk_* keys
|
|
1229
|
+
*
|
|
1230
|
+
* @example
|
|
1231
|
+
* ```typescript
|
|
1232
|
+
* if (client.getKeyType() === 'secret') {
|
|
1233
|
+
* const session = await client.portalSessions.create({ ... });
|
|
1234
|
+
* }
|
|
1235
|
+
* ```
|
|
1236
|
+
*/
|
|
1237
|
+
getKeyType() {
|
|
1238
|
+
return this._keyType;
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Check if the API key is a secret key (mf_sk_*)
|
|
1242
|
+
*
|
|
1243
|
+
* @returns true if the key can perform server-side operations
|
|
1244
|
+
*/
|
|
1245
|
+
isSecretKey() {
|
|
1246
|
+
return this._keyType === "secret";
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Check if the API key is a publishable key (mf_pk_*)
|
|
1250
|
+
*
|
|
1251
|
+
* @returns true if the key is safe for frontend use
|
|
1252
|
+
*/
|
|
1253
|
+
isPublishableKey() {
|
|
1254
|
+
return this._keyType === "publishable";
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Get a masked version of the API key for debugging
|
|
1258
|
+
*
|
|
1259
|
+
* @returns Masked API key showing only prefix and last 4 characters
|
|
1260
|
+
*/
|
|
1261
|
+
getMaskedApiKey() {
|
|
1262
|
+
if (this._apiKey.length <= 20) {
|
|
1263
|
+
return this._apiKey.substring(0, 8) + "...";
|
|
1264
|
+
}
|
|
1265
|
+
return this._apiKey.substring(0, 16) + "..." + this._apiKey.slice(-4);
|
|
1266
|
+
}
|
|
1267
|
+
};
|
|
1268
|
+
|
|
1269
|
+
export {
|
|
1270
|
+
MarginFrontError,
|
|
1271
|
+
AuthenticationError,
|
|
1272
|
+
AuthorizationError,
|
|
1273
|
+
NotFoundError,
|
|
1274
|
+
ValidationError,
|
|
1275
|
+
RateLimitError,
|
|
1276
|
+
ConflictError,
|
|
1277
|
+
NetworkError,
|
|
1278
|
+
TimeoutError,
|
|
1279
|
+
InternalError,
|
|
1280
|
+
InitializationError,
|
|
1281
|
+
ApiError,
|
|
1282
|
+
createErrorFromResponse,
|
|
1283
|
+
parseApiError,
|
|
1284
|
+
parseApiKey,
|
|
1285
|
+
isSecretKey,
|
|
1286
|
+
isPublishableKey,
|
|
1287
|
+
MarginFrontClient
|
|
1288
|
+
};
|