@naturalpay/sdk 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 +261 -0
- package/dist/index.cjs +1229 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +676 -0
- package/dist/index.d.ts +676 -0
- package/dist/index.js +1208 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/cli.cjs +166 -0
- package/dist/mcp/cli.cjs.map +1 -0
- package/dist/mcp/cli.d.cts +1 -0
- package/dist/mcp/cli.d.ts +1 -0
- package/dist/mcp/cli.js +164 -0
- package/dist/mcp/cli.js.map +1 -0
- package/dist/mcp/index.cjs +8 -0
- package/dist/mcp/index.cjs.map +1 -0
- package/dist/mcp/index.d.cts +9 -0
- package/dist/mcp/index.d.ts +9 -0
- package/dist/mcp/index.js +6 -0
- package/dist/mcp/index.js.map +1 -0
- package/package.json +87 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1208 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
2
|
+
|
|
3
|
+
// src/errors.ts
|
|
4
|
+
var NaturalError = class extends Error {
|
|
5
|
+
statusCode;
|
|
6
|
+
code;
|
|
7
|
+
constructor(message, options) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "NaturalError";
|
|
10
|
+
this.statusCode = options?.statusCode;
|
|
11
|
+
this.code = options?.code;
|
|
12
|
+
if (Error.captureStackTrace) {
|
|
13
|
+
Error.captureStackTrace(this, this.constructor);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var AuthenticationError = class extends NaturalError {
|
|
18
|
+
constructor(message = "Invalid or missing API key") {
|
|
19
|
+
super(message, { statusCode: 401, code: "authentication_error" });
|
|
20
|
+
this.name = "AuthenticationError";
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
var InvalidRequestError = class extends NaturalError {
|
|
24
|
+
constructor(message, code = "invalid_request") {
|
|
25
|
+
super(message, { statusCode: 400, code });
|
|
26
|
+
this.name = "InvalidRequestError";
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var PaymentError = class extends NaturalError {
|
|
30
|
+
constructor(message, code = "payment_error") {
|
|
31
|
+
super(message, { statusCode: 400, code });
|
|
32
|
+
this.name = "PaymentError";
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
var InsufficientFundsError = class extends PaymentError {
|
|
36
|
+
constructor(message = "Insufficient funds") {
|
|
37
|
+
super(message, "insufficient_funds");
|
|
38
|
+
this.name = "InsufficientFundsError";
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var RecipientNotFoundError = class extends PaymentError {
|
|
42
|
+
constructor(message = "Recipient not found") {
|
|
43
|
+
super(message, "recipient_not_found");
|
|
44
|
+
this.name = "RecipientNotFoundError";
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var RateLimitError = class extends NaturalError {
|
|
48
|
+
retryAfter;
|
|
49
|
+
constructor(message = "Rate limit exceeded", retryAfter) {
|
|
50
|
+
super(message, { statusCode: 429, code: "rate_limit_exceeded" });
|
|
51
|
+
this.name = "RateLimitError";
|
|
52
|
+
this.retryAfter = retryAfter;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var ServerError = class extends NaturalError {
|
|
56
|
+
constructor(message = "Internal server error") {
|
|
57
|
+
super(message, { statusCode: 500, code: "server_error" });
|
|
58
|
+
this.name = "ServerError";
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// src/version.ts
|
|
63
|
+
var VERSION = "0.1.1";
|
|
64
|
+
|
|
65
|
+
// src/logging.ts
|
|
66
|
+
var LOG_LEVEL_VALUES = {
|
|
67
|
+
debug: 10,
|
|
68
|
+
info: 20,
|
|
69
|
+
warning: 30,
|
|
70
|
+
error: 40
|
|
71
|
+
};
|
|
72
|
+
var asyncContext = new AsyncLocalStorage();
|
|
73
|
+
var globalContext = {};
|
|
74
|
+
function getContext() {
|
|
75
|
+
const asyncStore = asyncContext.getStore();
|
|
76
|
+
if (asyncStore) {
|
|
77
|
+
return { ...asyncStore };
|
|
78
|
+
}
|
|
79
|
+
return { ...globalContext };
|
|
80
|
+
}
|
|
81
|
+
function sanitizeString(value) {
|
|
82
|
+
let sanitized = "";
|
|
83
|
+
for (const char of value) {
|
|
84
|
+
if (char === "\r" || char === "\n") {
|
|
85
|
+
sanitized += "\\n";
|
|
86
|
+
} else if (char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127) {
|
|
87
|
+
sanitized += `\\x${char.charCodeAt(0).toString(16).padStart(2, "0")}`;
|
|
88
|
+
} else {
|
|
89
|
+
sanitized += char;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const maxLength = 1e3;
|
|
93
|
+
if (sanitized.length > maxLength) {
|
|
94
|
+
sanitized = sanitized.slice(0, maxLength) + "...[truncated]";
|
|
95
|
+
}
|
|
96
|
+
return sanitized;
|
|
97
|
+
}
|
|
98
|
+
function sanitizeLogValue(value, depth = 0) {
|
|
99
|
+
const maxDepth = 10;
|
|
100
|
+
if (depth > maxDepth) {
|
|
101
|
+
return "[max depth exceeded]";
|
|
102
|
+
}
|
|
103
|
+
if (value === null || value === void 0) {
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
if (typeof value === "string") {
|
|
107
|
+
return sanitizeString(value);
|
|
108
|
+
}
|
|
109
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
if (Array.isArray(value)) {
|
|
113
|
+
return value.map((item) => sanitizeLogValue(item, depth + 1));
|
|
114
|
+
}
|
|
115
|
+
if (typeof value === "object") {
|
|
116
|
+
const sanitized = {};
|
|
117
|
+
for (const [key, val] of Object.entries(value)) {
|
|
118
|
+
const sanitizedKey = sanitizeString(key);
|
|
119
|
+
sanitized[sanitizedKey] = sanitizeLogValue(val, depth + 1);
|
|
120
|
+
}
|
|
121
|
+
return sanitized;
|
|
122
|
+
}
|
|
123
|
+
return `[${typeof value}]`;
|
|
124
|
+
}
|
|
125
|
+
function bindContext(context) {
|
|
126
|
+
const sanitized = {};
|
|
127
|
+
for (const [key, value] of Object.entries(context)) {
|
|
128
|
+
sanitized[key] = sanitizeLogValue(value);
|
|
129
|
+
}
|
|
130
|
+
const asyncStore = asyncContext.getStore();
|
|
131
|
+
if (asyncStore) {
|
|
132
|
+
Object.assign(asyncStore, sanitized);
|
|
133
|
+
}
|
|
134
|
+
globalContext = { ...globalContext, ...sanitized };
|
|
135
|
+
}
|
|
136
|
+
function clearContext() {
|
|
137
|
+
const asyncStore = asyncContext.getStore();
|
|
138
|
+
if (asyncStore) {
|
|
139
|
+
for (const key of Object.keys(asyncStore)) {
|
|
140
|
+
delete asyncStore[key];
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
globalContext = {};
|
|
144
|
+
}
|
|
145
|
+
function runWithContext(context, fn) {
|
|
146
|
+
const sanitized = {};
|
|
147
|
+
for (const [key, value] of Object.entries(context)) {
|
|
148
|
+
sanitized[key] = sanitizeLogValue(value);
|
|
149
|
+
}
|
|
150
|
+
return asyncContext.run(sanitized, fn);
|
|
151
|
+
}
|
|
152
|
+
var loggingConfig = {
|
|
153
|
+
level: process.env["NATURAL_LOG_LEVEL"]?.toLowerCase() || "info",
|
|
154
|
+
jsonFormat: process.env["NATURAL_LOG_FORMAT"]?.toLowerCase() === "json",
|
|
155
|
+
serviceName: "naturalpay-sdk",
|
|
156
|
+
environment: process.env["NATURAL_ENV"] || process.env["DD_ENV"] || "development"
|
|
157
|
+
};
|
|
158
|
+
function configureLogging(options) {
|
|
159
|
+
if (options?.level) {
|
|
160
|
+
loggingConfig.level = options.level;
|
|
161
|
+
}
|
|
162
|
+
if (options?.jsonFormat !== void 0) {
|
|
163
|
+
loggingConfig.jsonFormat = options.jsonFormat;
|
|
164
|
+
}
|
|
165
|
+
if (options?.serviceName) {
|
|
166
|
+
loggingConfig.serviceName = options.serviceName;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function getSourceInfo() {
|
|
170
|
+
const stack = new Error().stack;
|
|
171
|
+
if (!stack) {
|
|
172
|
+
return { file: "unknown", line: 0, function: "unknown" };
|
|
173
|
+
}
|
|
174
|
+
const lines = stack.split("\n");
|
|
175
|
+
const callerLine = lines[4] || lines[3] || "";
|
|
176
|
+
const match = callerLine.match(/at\s+(?:(.+?)\s+\()?(.*?):(\d+):\d+\)?/);
|
|
177
|
+
if (match) {
|
|
178
|
+
return {
|
|
179
|
+
function: match[1] || "anonymous",
|
|
180
|
+
file: match[2] || "unknown",
|
|
181
|
+
line: parseInt(match[3] || "0", 10)
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return { file: "unknown", line: 0, function: "unknown" };
|
|
185
|
+
}
|
|
186
|
+
function formatJsonLog(level, loggerName, message, extra) {
|
|
187
|
+
const record = {
|
|
188
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
189
|
+
level: level.toUpperCase(),
|
|
190
|
+
logger: loggerName,
|
|
191
|
+
message,
|
|
192
|
+
source: getSourceInfo(),
|
|
193
|
+
service: loggingConfig.serviceName,
|
|
194
|
+
environment: loggingConfig.environment,
|
|
195
|
+
version: VERSION,
|
|
196
|
+
...getContext(),
|
|
197
|
+
...extra
|
|
198
|
+
};
|
|
199
|
+
const ddTraceId = process.env["DD_TRACE_ID"];
|
|
200
|
+
const ddSpanId = process.env["DD_SPAN_ID"];
|
|
201
|
+
if (ddTraceId) {
|
|
202
|
+
record["dd.trace_id"] = ddTraceId;
|
|
203
|
+
record["dd.span_id"] = ddSpanId;
|
|
204
|
+
record["dd.service"] = loggingConfig.serviceName;
|
|
205
|
+
record["dd.version"] = VERSION;
|
|
206
|
+
record["dd.env"] = loggingConfig.environment;
|
|
207
|
+
}
|
|
208
|
+
return JSON.stringify(record);
|
|
209
|
+
}
|
|
210
|
+
function formatTextLog(level, loggerName, message, extra) {
|
|
211
|
+
const contextEntries = Object.entries({ ...getContext(), ...extra });
|
|
212
|
+
const contextStr = contextEntries.length > 0 ? ` [${contextEntries.map(([k, v]) => `${k}=${v}`).join(", ")}]` : "";
|
|
213
|
+
return `${level.toUpperCase().padEnd(8)} ${loggerName}: ${message}${contextStr}`;
|
|
214
|
+
}
|
|
215
|
+
var Logger = class {
|
|
216
|
+
constructor(name) {
|
|
217
|
+
this.name = name;
|
|
218
|
+
}
|
|
219
|
+
log(level, message, extra) {
|
|
220
|
+
if (LOG_LEVEL_VALUES[level] < LOG_LEVEL_VALUES[loggingConfig.level]) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const formatted = loggingConfig.jsonFormat ? formatJsonLog(level, this.name, message, extra) : formatTextLog(level, this.name, message, extra);
|
|
224
|
+
if (level === "error") {
|
|
225
|
+
console.error(formatted);
|
|
226
|
+
} else if (level === "warning") {
|
|
227
|
+
console.warn(formatted);
|
|
228
|
+
} else {
|
|
229
|
+
console.error(formatted);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
debug(message, extra) {
|
|
233
|
+
this.log("debug", message, extra);
|
|
234
|
+
}
|
|
235
|
+
info(message, extra) {
|
|
236
|
+
this.log("info", message, extra);
|
|
237
|
+
}
|
|
238
|
+
warning(message, extra) {
|
|
239
|
+
this.log("warning", message, extra);
|
|
240
|
+
}
|
|
241
|
+
warn(message, extra) {
|
|
242
|
+
this.warning(message, extra);
|
|
243
|
+
}
|
|
244
|
+
error(message, extra) {
|
|
245
|
+
this.log("error", message, extra);
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
function getLogger(name) {
|
|
249
|
+
if (!name.startsWith("naturalpay")) {
|
|
250
|
+
name = `naturalpay.${name}`;
|
|
251
|
+
}
|
|
252
|
+
return new Logger(name);
|
|
253
|
+
}
|
|
254
|
+
function logError(logger2, message, options) {
|
|
255
|
+
const extra = { ...options };
|
|
256
|
+
if (options?.error) {
|
|
257
|
+
extra["errorType"] = options.error.name;
|
|
258
|
+
extra["errorMessage"] = options.error.message;
|
|
259
|
+
if (options.error.stack) {
|
|
260
|
+
extra["errorStack"] = options.error.stack.split("\n").slice(0, 5).join("\n");
|
|
261
|
+
}
|
|
262
|
+
delete extra["error"];
|
|
263
|
+
}
|
|
264
|
+
logger2.error(message, extra);
|
|
265
|
+
}
|
|
266
|
+
function logApiCall(logger2, method, path, options) {
|
|
267
|
+
const extra = {
|
|
268
|
+
method,
|
|
269
|
+
path,
|
|
270
|
+
...options
|
|
271
|
+
};
|
|
272
|
+
if (options?.statusCode) {
|
|
273
|
+
extra["statusCode"] = options.statusCode;
|
|
274
|
+
}
|
|
275
|
+
if (options?.durationMs !== void 0) {
|
|
276
|
+
extra["durationMs"] = Math.round(options.durationMs * 100) / 100;
|
|
277
|
+
}
|
|
278
|
+
if (options?.error) {
|
|
279
|
+
logError(logger2, `API call failed: ${method} ${path}`, extra);
|
|
280
|
+
} else if (options?.statusCode && options.statusCode >= 400) {
|
|
281
|
+
logger2.warning(`API call error: ${method} ${path} -> ${options.statusCode}`, extra);
|
|
282
|
+
} else {
|
|
283
|
+
logger2.info(`API call: ${method} ${path} -> ${options?.statusCode}`, extra);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function logToolCall(logger2, toolName, options) {
|
|
287
|
+
const extra = {
|
|
288
|
+
toolName,
|
|
289
|
+
...options
|
|
290
|
+
};
|
|
291
|
+
if (options?.durationMs !== void 0) {
|
|
292
|
+
extra["durationMs"] = Math.round(options.durationMs * 100) / 100;
|
|
293
|
+
}
|
|
294
|
+
if (options?.error) {
|
|
295
|
+
logError(logger2, `Tool call failed: ${toolName}`, extra);
|
|
296
|
+
} else if (options?.success === false) {
|
|
297
|
+
logger2.warning(`Tool call returned error: ${toolName}`, extra);
|
|
298
|
+
} else {
|
|
299
|
+
logger2.info(`Tool call: ${toolName}`, extra);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
var toolCallStorage = new AsyncLocalStorage();
|
|
303
|
+
function getToolCallHeader() {
|
|
304
|
+
const data = toolCallStorage.getStore();
|
|
305
|
+
if (!data) return void 0;
|
|
306
|
+
return btoa(JSON.stringify(data));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// src/http.ts
|
|
310
|
+
var logger = getLogger("http");
|
|
311
|
+
var DEFAULT_BASE_URL = "https://api.natural.co";
|
|
312
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
313
|
+
function hashString(str) {
|
|
314
|
+
let hash = 0;
|
|
315
|
+
for (let i = 0; i < str.length; i++) {
|
|
316
|
+
const char = str.charCodeAt(i);
|
|
317
|
+
hash = (hash << 5) - hash + char;
|
|
318
|
+
hash = hash & hash;
|
|
319
|
+
}
|
|
320
|
+
return Math.abs(hash).toString(16).slice(0, 16);
|
|
321
|
+
}
|
|
322
|
+
var HTTPClient = class {
|
|
323
|
+
apiKey;
|
|
324
|
+
baseUrl;
|
|
325
|
+
timeout;
|
|
326
|
+
jwtCache = /* @__PURE__ */ new Map();
|
|
327
|
+
constructor(options = {}) {
|
|
328
|
+
this.apiKey = options.apiKey ?? process.env["NATURAL_API_KEY"] ?? "";
|
|
329
|
+
this.baseUrl = (options.baseUrl ?? process.env["NATURAL_SERVER_URL"] ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
330
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Get a cached JWT or exchange API key for a new one.
|
|
334
|
+
*/
|
|
335
|
+
async getJwt() {
|
|
336
|
+
if (!this.apiKey) {
|
|
337
|
+
throw new AuthenticationError();
|
|
338
|
+
}
|
|
339
|
+
if (!this.apiKey.startsWith("sk_ntl_")) {
|
|
340
|
+
return this.apiKey;
|
|
341
|
+
}
|
|
342
|
+
const cacheKey = hashString(this.apiKey);
|
|
343
|
+
const cached = this.jwtCache.get(cacheKey);
|
|
344
|
+
if (cached && Date.now() < cached.expiresAt) {
|
|
345
|
+
return cached.token;
|
|
346
|
+
}
|
|
347
|
+
const controller = new AbortController();
|
|
348
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
349
|
+
logger.debug("Exchanging API key for JWT", { path: "/auth/api/token" });
|
|
350
|
+
try {
|
|
351
|
+
const response = await fetch(`${this.baseUrl}/auth/api/token`, {
|
|
352
|
+
method: "POST",
|
|
353
|
+
headers: {
|
|
354
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
355
|
+
"Content-Type": "application/json"
|
|
356
|
+
},
|
|
357
|
+
signal: controller.signal
|
|
358
|
+
});
|
|
359
|
+
clearTimeout(timeoutId);
|
|
360
|
+
if (!response.ok) {
|
|
361
|
+
const authError = new AuthenticationError(
|
|
362
|
+
`Authentication failed (status=${response.status})`
|
|
363
|
+
);
|
|
364
|
+
logError(logger, "JWT exchange failed", {
|
|
365
|
+
error: authError,
|
|
366
|
+
statusCode: response.status,
|
|
367
|
+
path: "/auth/api/token"
|
|
368
|
+
});
|
|
369
|
+
throw authError;
|
|
370
|
+
}
|
|
371
|
+
const data = await response.json();
|
|
372
|
+
const expiresIn = data.expiresIn ?? 900;
|
|
373
|
+
const expiresAt = Date.now() + (expiresIn - 30) * 1e3;
|
|
374
|
+
this.jwtCache.set(cacheKey, { token: data.accessToken, expiresAt });
|
|
375
|
+
return data.accessToken;
|
|
376
|
+
} catch (error) {
|
|
377
|
+
clearTimeout(timeoutId);
|
|
378
|
+
if (error instanceof AuthenticationError) {
|
|
379
|
+
throw error;
|
|
380
|
+
}
|
|
381
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
382
|
+
const networkError2 = new NaturalError("Request timed out during authentication");
|
|
383
|
+
logError(logger, "JWT exchange network error", {
|
|
384
|
+
error: networkError2,
|
|
385
|
+
path: "/auth/api/token"
|
|
386
|
+
});
|
|
387
|
+
throw networkError2;
|
|
388
|
+
}
|
|
389
|
+
const networkError = new NaturalError(
|
|
390
|
+
`Network error during authentication: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
391
|
+
);
|
|
392
|
+
logError(logger, "JWT exchange network error", {
|
|
393
|
+
error: networkError,
|
|
394
|
+
path: "/auth/api/token"
|
|
395
|
+
});
|
|
396
|
+
throw networkError;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Build URL with query parameters.
|
|
401
|
+
*/
|
|
402
|
+
buildUrl(path, params) {
|
|
403
|
+
const url = new URL(path, this.baseUrl);
|
|
404
|
+
if (params) {
|
|
405
|
+
for (const [key, value] of Object.entries(params)) {
|
|
406
|
+
if (value !== void 0 && value !== null) {
|
|
407
|
+
url.searchParams.set(key, String(value));
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return url.toString();
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Handle API response and throw appropriate errors.
|
|
415
|
+
*/
|
|
416
|
+
async handleResponse(response, method, path, durationMs) {
|
|
417
|
+
if (response.status === 401) {
|
|
418
|
+
const authError = new AuthenticationError();
|
|
419
|
+
logApiCall(logger, method, path, {
|
|
420
|
+
statusCode: response.status,
|
|
421
|
+
durationMs,
|
|
422
|
+
error: authError
|
|
423
|
+
});
|
|
424
|
+
throw authError;
|
|
425
|
+
}
|
|
426
|
+
if (response.status === 429) {
|
|
427
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
428
|
+
const rateError = new RateLimitError(
|
|
429
|
+
"Rate limit exceeded",
|
|
430
|
+
retryAfter ? parseInt(retryAfter, 10) : void 0
|
|
431
|
+
);
|
|
432
|
+
logger.warning(`Rate limited: ${method} ${path}`, {
|
|
433
|
+
method,
|
|
434
|
+
path,
|
|
435
|
+
statusCode: response.status,
|
|
436
|
+
retryAfter,
|
|
437
|
+
durationMs: Math.round(durationMs * 100) / 100
|
|
438
|
+
});
|
|
439
|
+
throw rateError;
|
|
440
|
+
}
|
|
441
|
+
if (response.status >= 500) {
|
|
442
|
+
const serverError = new ServerError(`Server error: ${response.status}`);
|
|
443
|
+
logApiCall(logger, method, path, {
|
|
444
|
+
statusCode: response.status,
|
|
445
|
+
durationMs,
|
|
446
|
+
error: serverError
|
|
447
|
+
});
|
|
448
|
+
throw serverError;
|
|
449
|
+
}
|
|
450
|
+
let data;
|
|
451
|
+
try {
|
|
452
|
+
const text = await response.text();
|
|
453
|
+
data = text ? JSON.parse(text) : {};
|
|
454
|
+
} catch {
|
|
455
|
+
if (response.status >= 400) {
|
|
456
|
+
const parseError = new NaturalError(`Request failed: ${response.status}`, {
|
|
457
|
+
statusCode: response.status
|
|
458
|
+
});
|
|
459
|
+
logApiCall(logger, method, path, {
|
|
460
|
+
statusCode: response.status,
|
|
461
|
+
durationMs,
|
|
462
|
+
error: parseError
|
|
463
|
+
});
|
|
464
|
+
throw parseError;
|
|
465
|
+
}
|
|
466
|
+
logApiCall(logger, method, path, { statusCode: response.status, durationMs });
|
|
467
|
+
return {};
|
|
468
|
+
}
|
|
469
|
+
if (response.status >= 400) {
|
|
470
|
+
const errBody = data;
|
|
471
|
+
const errors = Array.isArray(errBody.errors) ? errBody.errors : [];
|
|
472
|
+
const firstError = errors[0] ?? {};
|
|
473
|
+
const errorMessage = firstError.detail ?? errBody.detail ?? errBody.message ?? errBody.error ?? "Request failed";
|
|
474
|
+
const errorCode = firstError.code ?? errBody.code ?? "unknown_error";
|
|
475
|
+
const requestError = new InvalidRequestError(
|
|
476
|
+
`${errorMessage} (status=${response.status})`,
|
|
477
|
+
errorCode
|
|
478
|
+
);
|
|
479
|
+
logApiCall(logger, method, path, {
|
|
480
|
+
statusCode: response.status,
|
|
481
|
+
durationMs,
|
|
482
|
+
error: requestError
|
|
483
|
+
});
|
|
484
|
+
throw requestError;
|
|
485
|
+
}
|
|
486
|
+
logApiCall(logger, method, path, { statusCode: response.status, durationMs });
|
|
487
|
+
return data;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Make an authenticated request.
|
|
491
|
+
*/
|
|
492
|
+
async request(method, path, options) {
|
|
493
|
+
const jwt = await this.getJwt();
|
|
494
|
+
const url = this.buildUrl(path, options?.params);
|
|
495
|
+
const controller = new AbortController();
|
|
496
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
497
|
+
logger.debug(`API request: ${method} ${path}`, {
|
|
498
|
+
method,
|
|
499
|
+
path,
|
|
500
|
+
hasBody: !!options?.body
|
|
501
|
+
});
|
|
502
|
+
const startTime = Date.now();
|
|
503
|
+
try {
|
|
504
|
+
const headers = {
|
|
505
|
+
Authorization: `Bearer ${jwt}`,
|
|
506
|
+
"Content-Type": "application/json",
|
|
507
|
+
"User-Agent": `naturalpay-ts/${VERSION}`
|
|
508
|
+
};
|
|
509
|
+
const toolCallHeader = getToolCallHeader();
|
|
510
|
+
if (toolCallHeader) {
|
|
511
|
+
headers["X-Tool-Call"] = toolCallHeader;
|
|
512
|
+
}
|
|
513
|
+
if (options?.headers) {
|
|
514
|
+
Object.assign(headers, options.headers);
|
|
515
|
+
}
|
|
516
|
+
const response = await fetch(url, {
|
|
517
|
+
method,
|
|
518
|
+
headers,
|
|
519
|
+
body: options?.body ? JSON.stringify(options.body) : void 0,
|
|
520
|
+
signal: controller.signal
|
|
521
|
+
});
|
|
522
|
+
clearTimeout(timeoutId);
|
|
523
|
+
const durationMs = Date.now() - startTime;
|
|
524
|
+
return this.handleResponse(response, method, path, durationMs);
|
|
525
|
+
} catch (error) {
|
|
526
|
+
clearTimeout(timeoutId);
|
|
527
|
+
const durationMs = Date.now() - startTime;
|
|
528
|
+
if (error instanceof NaturalError || error instanceof AuthenticationError || error instanceof InvalidRequestError || error instanceof RateLimitError || error instanceof ServerError) {
|
|
529
|
+
throw error;
|
|
530
|
+
}
|
|
531
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
532
|
+
const networkError2 = new NaturalError("Request timed out");
|
|
533
|
+
logApiCall(logger, method, path, { durationMs, error: networkError2 });
|
|
534
|
+
throw networkError2;
|
|
535
|
+
}
|
|
536
|
+
const networkError = new NaturalError(
|
|
537
|
+
`Network error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
538
|
+
);
|
|
539
|
+
logApiCall(logger, method, path, { durationMs, error: networkError });
|
|
540
|
+
throw networkError;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
async get(path, options) {
|
|
544
|
+
return this.request("GET", path, options);
|
|
545
|
+
}
|
|
546
|
+
async post(path, options) {
|
|
547
|
+
return this.request("POST", path, options);
|
|
548
|
+
}
|
|
549
|
+
async put(path, options) {
|
|
550
|
+
return this.request("PUT", path, options);
|
|
551
|
+
}
|
|
552
|
+
async delete(path, options) {
|
|
553
|
+
return this.request("DELETE", path, options);
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
// src/resources/base.ts
|
|
558
|
+
var BaseResource = class {
|
|
559
|
+
http;
|
|
560
|
+
constructor(http) {
|
|
561
|
+
this.http = http;
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
// src/resources/transactions.ts
|
|
566
|
+
function unwrapTransactionResource(resource) {
|
|
567
|
+
if (resource.type !== "transaction" || !resource.attributes) {
|
|
568
|
+
throw new NaturalError(
|
|
569
|
+
`Unexpected resource format: expected type "transaction", got "${resource.type}"`
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
const { id, attributes, relationships } = resource;
|
|
573
|
+
return {
|
|
574
|
+
transactionId: id,
|
|
575
|
+
amount: Number(attributes.amount),
|
|
576
|
+
currency: String(attributes.currency),
|
|
577
|
+
status: String(attributes.status),
|
|
578
|
+
description: attributes.description != null ? String(attributes.description) : void 0,
|
|
579
|
+
memo: attributes.memo != null ? String(attributes.memo) : void 0,
|
|
580
|
+
createdAt: String(attributes.createdAt),
|
|
581
|
+
updatedAt: attributes.updatedAt != null ? String(attributes.updatedAt) : void 0,
|
|
582
|
+
isDelegated: Boolean(attributes.isDelegated),
|
|
583
|
+
customerName: attributes.customerName != null ? String(attributes.customerName) : void 0,
|
|
584
|
+
customerAgentId: attributes.customerAgentId != null ? String(attributes.customerAgentId) : void 0,
|
|
585
|
+
senderName: attributes.senderName != null ? String(attributes.senderName) : void 0,
|
|
586
|
+
recipientName: attributes.recipientName != null ? String(attributes.recipientName) : void 0,
|
|
587
|
+
transactionType: String(attributes.transactionType),
|
|
588
|
+
category: String(attributes.category),
|
|
589
|
+
direction: String(attributes.direction),
|
|
590
|
+
sourcePartyId: relationships?.sourceParty?.data?.id,
|
|
591
|
+
destinationPartyId: relationships?.destinationParty?.data?.id,
|
|
592
|
+
sourceWalletId: relationships?.sourceWallet?.data?.id,
|
|
593
|
+
destinationWalletId: relationships?.destinationWallet?.data?.id,
|
|
594
|
+
instanceId: attributes.instanceId != null ? String(attributes.instanceId) : void 0,
|
|
595
|
+
claimLink: attributes.claimLink != null ? String(attributes.claimLink) : void 0
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
var TransactionsResource = class extends BaseResource {
|
|
599
|
+
/**
|
|
600
|
+
* Get a transaction by ID.
|
|
601
|
+
*
|
|
602
|
+
* @param transactionId - Transaction ID (txn_xxx)
|
|
603
|
+
* @param params - Optional parameters for observability
|
|
604
|
+
* @returns Transaction details
|
|
605
|
+
*/
|
|
606
|
+
async get(transactionId, params) {
|
|
607
|
+
const headers = {};
|
|
608
|
+
if (params?.instanceId) {
|
|
609
|
+
headers["X-Instance-ID"] = params.instanceId;
|
|
610
|
+
}
|
|
611
|
+
const queryParams = {};
|
|
612
|
+
if (params?.customerPartyId) {
|
|
613
|
+
queryParams["partyId"] = params.customerPartyId;
|
|
614
|
+
}
|
|
615
|
+
const response = await this.http.get(`/transactions/${transactionId}`, {
|
|
616
|
+
params: Object.keys(queryParams).length > 0 ? queryParams : void 0,
|
|
617
|
+
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
618
|
+
});
|
|
619
|
+
if (!response?.data) {
|
|
620
|
+
throw new NaturalError('Unexpected response format: missing "data" in transaction response');
|
|
621
|
+
}
|
|
622
|
+
return unwrapTransactionResource(response.data);
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* List recent transactions.
|
|
626
|
+
*
|
|
627
|
+
* @param params - List parameters including agent context
|
|
628
|
+
* @returns TransactionListResponse with transactions and pagination info
|
|
629
|
+
*/
|
|
630
|
+
async list(params) {
|
|
631
|
+
const headers = {};
|
|
632
|
+
if (params?.agentId) {
|
|
633
|
+
if (!params?.instanceId) {
|
|
634
|
+
throw new InvalidRequestError("instanceId is required when agentId is provided");
|
|
635
|
+
}
|
|
636
|
+
headers["X-Agent-ID"] = params.agentId;
|
|
637
|
+
}
|
|
638
|
+
if (params?.instanceId) {
|
|
639
|
+
headers["X-Instance-ID"] = params.instanceId;
|
|
640
|
+
}
|
|
641
|
+
const queryParams = {
|
|
642
|
+
limit: params?.limit ?? 50,
|
|
643
|
+
cursor: params?.cursor,
|
|
644
|
+
type: params?.type
|
|
645
|
+
};
|
|
646
|
+
const response = await this.http.get("/transactions", {
|
|
647
|
+
params: queryParams,
|
|
648
|
+
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
649
|
+
});
|
|
650
|
+
if (!response?.data || !Array.isArray(response.data)) {
|
|
651
|
+
throw new NaturalError(
|
|
652
|
+
'Unexpected response format: missing "data" array in transaction list response'
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
const transactions = response.data.map(unwrapTransactionResource);
|
|
656
|
+
const pagination = response.meta?.pagination ?? {};
|
|
657
|
+
return {
|
|
658
|
+
transactions,
|
|
659
|
+
hasMore: pagination.hasMore ?? false,
|
|
660
|
+
nextCursor: pagination.nextCursor ?? null
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
// src/resources/payments.ts
|
|
666
|
+
var PHONE_DIGITS_RE = /^\d{10,}$/;
|
|
667
|
+
var PHONE_PLUS_RE = /^\+\d{7,}$/;
|
|
668
|
+
function validateRecipient(recipient) {
|
|
669
|
+
if (!recipient) {
|
|
670
|
+
throw new InvalidRequestError("recipient cannot be empty");
|
|
671
|
+
}
|
|
672
|
+
if (recipient.startsWith("pty_")) return;
|
|
673
|
+
if (recipient.includes("@")) return;
|
|
674
|
+
if (PHONE_PLUS_RE.test(recipient)) return;
|
|
675
|
+
if (PHONE_DIGITS_RE.test(recipient)) return;
|
|
676
|
+
throw new InvalidRequestError(
|
|
677
|
+
"Invalid recipient format. Expected: party ID (pty_*), email (contains @), or phone (+ prefix or 10+ digits)"
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
function unwrapPaymentResponse(response) {
|
|
681
|
+
if (!response?.data) {
|
|
682
|
+
throw new NaturalError('Unexpected response format: missing "data" field in payment response');
|
|
683
|
+
}
|
|
684
|
+
const base = unwrapTransactionResource(response.data);
|
|
685
|
+
const { relationships, attributes } = response.data;
|
|
686
|
+
return {
|
|
687
|
+
...base,
|
|
688
|
+
// Payment responses may omit these fields; provide sensible defaults.
|
|
689
|
+
transactionType: String(attributes.transactionType ?? "payment"),
|
|
690
|
+
category: String(attributes.category ?? "sent"),
|
|
691
|
+
direction: String(attributes.direction ?? "OUTBOUND"),
|
|
692
|
+
// Mirror description into memo for payment convenience.
|
|
693
|
+
memo: base.description,
|
|
694
|
+
// Payments use customerParty/counterparty relationship keys,
|
|
695
|
+
// falling back to the generic sourceParty/destinationParty.
|
|
696
|
+
sourcePartyId: relationships?.customerParty?.data?.id ?? base.sourcePartyId,
|
|
697
|
+
destinationPartyId: relationships?.counterparty?.data?.id ?? base.destinationPartyId
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
var PaymentsResource = class extends BaseResource {
|
|
701
|
+
/**
|
|
702
|
+
* Create a payment.
|
|
703
|
+
*
|
|
704
|
+
* @param params - Payment creation parameters
|
|
705
|
+
* @returns Transaction object with transaction_id (txn_*), status, etc.
|
|
706
|
+
*/
|
|
707
|
+
async create(params) {
|
|
708
|
+
const recipient = params.recipient.trim();
|
|
709
|
+
validateRecipient(recipient);
|
|
710
|
+
if (!Number.isInteger(params.amount) || params.amount <= 0) {
|
|
711
|
+
throw new InvalidRequestError("amount must be a positive integer (minor units in cents)");
|
|
712
|
+
}
|
|
713
|
+
const attributes = {
|
|
714
|
+
amount: params.amount,
|
|
715
|
+
currency: params.currency ?? "USD",
|
|
716
|
+
counterparty: recipient,
|
|
717
|
+
customerPartyId: params.customerPartyId
|
|
718
|
+
};
|
|
719
|
+
attributes["description"] = params.memo;
|
|
720
|
+
const body = { data: { attributes } };
|
|
721
|
+
const headers = {
|
|
722
|
+
"Idempotency-Key": params.idempotencyKey
|
|
723
|
+
};
|
|
724
|
+
if (params.agentId) {
|
|
725
|
+
if (!params.instanceId) {
|
|
726
|
+
throw new InvalidRequestError("instanceId is required when agentId is provided");
|
|
727
|
+
}
|
|
728
|
+
headers["X-Agent-ID"] = params.agentId;
|
|
729
|
+
}
|
|
730
|
+
if (params.instanceId) {
|
|
731
|
+
headers["X-Instance-ID"] = params.instanceId;
|
|
732
|
+
}
|
|
733
|
+
const response = await this.http.post("/payments", {
|
|
734
|
+
body,
|
|
735
|
+
headers
|
|
736
|
+
});
|
|
737
|
+
return unwrapPaymentResponse(response);
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
// src/resources/wallet.ts
|
|
742
|
+
function toAmountInfo(raw) {
|
|
743
|
+
const obj = raw;
|
|
744
|
+
return {
|
|
745
|
+
amountMinor: Number(obj?.amountMinor ?? 0)
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
function unwrapBalance(response) {
|
|
749
|
+
if (!response?.data) {
|
|
750
|
+
throw new NaturalError(
|
|
751
|
+
'Unexpected response format: missing "data" field in wallet balance response'
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
const { data } = response;
|
|
755
|
+
if (data.type !== "walletBalance" || !data.attributes) {
|
|
756
|
+
throw new NaturalError(
|
|
757
|
+
`Unexpected resource format: expected type "walletBalance", got "${data.type}"`
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
const { id, attributes } = data;
|
|
761
|
+
const rawBreakdown = attributes.breakdown;
|
|
762
|
+
const breakdown = {
|
|
763
|
+
operatingFunded: toAmountInfo(rawBreakdown?.operatingFunded),
|
|
764
|
+
operatingAdvanced: toAmountInfo(rawBreakdown?.operatingAdvanced),
|
|
765
|
+
escrowFundedSettled: toAmountInfo(rawBreakdown?.escrowFundedSettled),
|
|
766
|
+
escrowAdvanced: toAmountInfo(rawBreakdown?.escrowAdvanced),
|
|
767
|
+
holdsOutbound: toAmountInfo(rawBreakdown?.holdsOutbound)
|
|
768
|
+
};
|
|
769
|
+
return {
|
|
770
|
+
walletId: id,
|
|
771
|
+
breakdown,
|
|
772
|
+
available: toAmountInfo(attributes.available),
|
|
773
|
+
pendingClaimAmountMinor: attributes.pendingClaimAmountMinor != null ? Number(attributes.pendingClaimAmountMinor) : void 0,
|
|
774
|
+
pendingClaimCount: attributes.pendingClaimCount != null ? Number(attributes.pendingClaimCount) : void 0
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
function unwrapWithdrawal(response) {
|
|
778
|
+
if (!response?.data) {
|
|
779
|
+
throw new NaturalError(
|
|
780
|
+
'Unexpected response format: missing "data" field in withdrawal response'
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
const { data } = response;
|
|
784
|
+
if (data.type !== "withdrawal" || !data.attributes) {
|
|
785
|
+
throw new NaturalError(
|
|
786
|
+
`Unexpected resource format: expected type "withdrawal", got "${data.type}"`
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
const { id, attributes } = data;
|
|
790
|
+
return {
|
|
791
|
+
transferId: id ?? void 0,
|
|
792
|
+
instructionId: attributes.instructionId != null ? String(attributes.instructionId) : void 0,
|
|
793
|
+
status: String(attributes.status),
|
|
794
|
+
amount: attributes.amount != null ? Number(attributes.amount) : 0,
|
|
795
|
+
currency: String(attributes.currency),
|
|
796
|
+
estimatedSettlement: attributes.estimatedSettlement != null ? String(attributes.estimatedSettlement) : void 0,
|
|
797
|
+
kycRequired: Boolean(attributes.kycRequired),
|
|
798
|
+
kycStatus: attributes.kycStatus != null ? String(attributes.kycStatus) : void 0,
|
|
799
|
+
kycSessionUrl: attributes.kycSessionUrl != null ? String(attributes.kycSessionUrl) : void 0,
|
|
800
|
+
mfaRequired: Boolean(attributes.mfaRequired),
|
|
801
|
+
mfaChallengeId: attributes.mfaChallengeId != null ? String(attributes.mfaChallengeId) : void 0,
|
|
802
|
+
mfaExpiresAt: attributes.mfaExpiresAt != null ? String(attributes.mfaExpiresAt) : void 0,
|
|
803
|
+
error: attributes.error != null ? String(attributes.error) : void 0,
|
|
804
|
+
errorDetails: attributes.errorDetails != null ? String(attributes.errorDetails) : void 0
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
var WalletResource = class extends BaseResource {
|
|
808
|
+
/**
|
|
809
|
+
* Get current wallet balance.
|
|
810
|
+
*
|
|
811
|
+
* @returns AccountBalance with available, current, pending amounts
|
|
812
|
+
*/
|
|
813
|
+
async balance(options) {
|
|
814
|
+
const headers = {};
|
|
815
|
+
if (options?.instanceId) {
|
|
816
|
+
headers["X-Instance-ID"] = options.instanceId;
|
|
817
|
+
}
|
|
818
|
+
const params = {};
|
|
819
|
+
if (options?.customerPartyId) {
|
|
820
|
+
params["partyId"] = options.customerPartyId;
|
|
821
|
+
}
|
|
822
|
+
const response = await this.http.get("/wallet/balance", {
|
|
823
|
+
params: Object.keys(params).length > 0 ? params : void 0,
|
|
824
|
+
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
825
|
+
});
|
|
826
|
+
return unwrapBalance(response);
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Initiate a withdrawal to a linked bank account.
|
|
830
|
+
*
|
|
831
|
+
* @param params - Withdrawal parameters
|
|
832
|
+
* @returns WithdrawResponse with transfer status (may require KYC/MFA)
|
|
833
|
+
*/
|
|
834
|
+
async withdraw(params) {
|
|
835
|
+
if (!Number.isInteger(params.amount) || params.amount <= 0) {
|
|
836
|
+
throw new InvalidRequestError("amount must be a positive integer (minor units in cents)");
|
|
837
|
+
}
|
|
838
|
+
const attributes = {
|
|
839
|
+
amount: params.amount,
|
|
840
|
+
currency: params.currency ?? "USD",
|
|
841
|
+
externalFundingSourceId: params.externalFundingSourceId
|
|
842
|
+
};
|
|
843
|
+
if (params.description) attributes["description"] = params.description;
|
|
844
|
+
const body = { data: { attributes } };
|
|
845
|
+
const response = await this.http.post("/wallet/withdraw", {
|
|
846
|
+
body,
|
|
847
|
+
headers: { "Idempotency-Key": params.idempotencyKey }
|
|
848
|
+
});
|
|
849
|
+
return unwrapWithdrawal(response);
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
// src/resources/agents.ts
|
|
854
|
+
function unwrapAgentResource(resource) {
|
|
855
|
+
if (resource.type !== "agent" || !resource.attributes) {
|
|
856
|
+
throw new NaturalError(
|
|
857
|
+
`Unexpected resource format: expected type "agent", got "${resource.type}"`
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
const { id, attributes, relationships } = resource;
|
|
861
|
+
return {
|
|
862
|
+
id,
|
|
863
|
+
name: String(attributes.name),
|
|
864
|
+
description: attributes.description != null ? String(attributes.description) : void 0,
|
|
865
|
+
status: String(attributes.status),
|
|
866
|
+
partyId: relationships?.party?.data?.id ?? "",
|
|
867
|
+
createdAt: attributes.createdAt != null ? String(attributes.createdAt) : void 0,
|
|
868
|
+
createdBy: attributes.createdBy != null ? String(attributes.createdBy) : void 0,
|
|
869
|
+
updatedAt: attributes.updatedAt != null ? String(attributes.updatedAt) : void 0,
|
|
870
|
+
updatedBy: attributes.updatedBy != null ? String(attributes.updatedBy) : void 0
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
function unwrapAgent(response) {
|
|
874
|
+
if (!response?.data) {
|
|
875
|
+
throw new NaturalError('Unexpected response format: missing "data" field in agent response');
|
|
876
|
+
}
|
|
877
|
+
return unwrapAgentResource(response.data);
|
|
878
|
+
}
|
|
879
|
+
function unwrapAgentList(response) {
|
|
880
|
+
if (!response?.data || !Array.isArray(response.data)) {
|
|
881
|
+
throw new NaturalError(
|
|
882
|
+
'Unexpected response format: missing "data" array in agent list response'
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
const agents = response.data.map(unwrapAgentResource);
|
|
886
|
+
const pagination = response.meta?.pagination ?? {};
|
|
887
|
+
return {
|
|
888
|
+
agents,
|
|
889
|
+
hasMore: pagination.hasMore ?? false,
|
|
890
|
+
nextCursor: pagination.nextCursor ?? null
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
var AgentsResource = class extends BaseResource {
|
|
894
|
+
/**
|
|
895
|
+
* List agents for the partner.
|
|
896
|
+
*
|
|
897
|
+
* @param params - Filter and pagination parameters
|
|
898
|
+
* @returns AgentListResponse with list of agents
|
|
899
|
+
*/
|
|
900
|
+
async list(params) {
|
|
901
|
+
const headers = {};
|
|
902
|
+
if (params?.instanceId) {
|
|
903
|
+
headers["X-Instance-ID"] = params.instanceId;
|
|
904
|
+
}
|
|
905
|
+
const queryParams = {
|
|
906
|
+
status: params?.status,
|
|
907
|
+
limit: params?.limit ?? 50,
|
|
908
|
+
cursor: params?.cursor
|
|
909
|
+
};
|
|
910
|
+
const response = await this.http.get("/agents", {
|
|
911
|
+
params: queryParams,
|
|
912
|
+
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
913
|
+
});
|
|
914
|
+
return unwrapAgentList(response);
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Get agent by ID.
|
|
918
|
+
*
|
|
919
|
+
* @param agentId - The agent ID to retrieve (agt_xxx)
|
|
920
|
+
* @returns Agent details
|
|
921
|
+
*/
|
|
922
|
+
async get(agentId, options) {
|
|
923
|
+
const headers = {};
|
|
924
|
+
if (options?.instanceId) {
|
|
925
|
+
headers["X-Instance-ID"] = options.instanceId;
|
|
926
|
+
}
|
|
927
|
+
const response = await this.http.get(`/agents/${agentId}`, {
|
|
928
|
+
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
929
|
+
});
|
|
930
|
+
return unwrapAgent(response);
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* Create a new agent.
|
|
934
|
+
*
|
|
935
|
+
* @param params - Agent creation parameters
|
|
936
|
+
* @returns AgentCreateResponse with created agent details
|
|
937
|
+
*/
|
|
938
|
+
async create(params) {
|
|
939
|
+
const attributes = {
|
|
940
|
+
name: params.name
|
|
941
|
+
};
|
|
942
|
+
if (params.description) {
|
|
943
|
+
attributes["description"] = params.description;
|
|
944
|
+
}
|
|
945
|
+
if (params.limits) {
|
|
946
|
+
attributes["limits"] = params.limits;
|
|
947
|
+
}
|
|
948
|
+
const body = { data: { attributes } };
|
|
949
|
+
const headers = {};
|
|
950
|
+
if (params.idempotencyKey) {
|
|
951
|
+
headers["Idempotency-Key"] = params.idempotencyKey;
|
|
952
|
+
}
|
|
953
|
+
if (params.instanceId) {
|
|
954
|
+
headers["X-Instance-ID"] = params.instanceId;
|
|
955
|
+
}
|
|
956
|
+
const response = await this.http.post("/agents", {
|
|
957
|
+
body,
|
|
958
|
+
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
959
|
+
});
|
|
960
|
+
return unwrapAgent(response);
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Update an existing agent.
|
|
964
|
+
*
|
|
965
|
+
* @param agentId - The agent ID to update (agt_xxx)
|
|
966
|
+
* @param params - Update parameters
|
|
967
|
+
* @returns AgentUpdateResponse with updated agent details
|
|
968
|
+
*/
|
|
969
|
+
async update(agentId, params) {
|
|
970
|
+
const attributes = {};
|
|
971
|
+
if (params.name !== void 0) attributes["name"] = params.name;
|
|
972
|
+
if (params.description !== void 0) attributes["description"] = params.description;
|
|
973
|
+
if (params.status !== void 0) attributes["status"] = params.status;
|
|
974
|
+
const body = { data: { attributes } };
|
|
975
|
+
const headers = {};
|
|
976
|
+
if (params.idempotencyKey) {
|
|
977
|
+
headers["Idempotency-Key"] = params.idempotencyKey;
|
|
978
|
+
}
|
|
979
|
+
if (params.instanceId) {
|
|
980
|
+
headers["X-Instance-ID"] = params.instanceId;
|
|
981
|
+
}
|
|
982
|
+
const response = await this.http.put(`/agents/${agentId}`, {
|
|
983
|
+
body,
|
|
984
|
+
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
985
|
+
});
|
|
986
|
+
return unwrapAgent(response);
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Delete an agent.
|
|
990
|
+
*
|
|
991
|
+
* @param agentId - The agent ID to delete (agt_xxx)
|
|
992
|
+
* @param options - Optional observability parameters
|
|
993
|
+
*/
|
|
994
|
+
async delete(agentId, options) {
|
|
995
|
+
const headers = {};
|
|
996
|
+
if (options?.instanceId) {
|
|
997
|
+
headers["X-Instance-ID"] = options.instanceId;
|
|
998
|
+
}
|
|
999
|
+
await this.http.delete(`/agents/${agentId}`, {
|
|
1000
|
+
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
};
|
|
1004
|
+
|
|
1005
|
+
// src/resources/delegations.ts
|
|
1006
|
+
function unwrapAgentDelegationResource(resource) {
|
|
1007
|
+
if (resource.type !== "agentDelegation" || !resource.attributes) {
|
|
1008
|
+
throw new NaturalError(
|
|
1009
|
+
`Unexpected resource format: expected type "agentDelegation", got "${resource.type}"`
|
|
1010
|
+
);
|
|
1011
|
+
}
|
|
1012
|
+
const { id, attributes, relationships } = resource;
|
|
1013
|
+
return {
|
|
1014
|
+
id,
|
|
1015
|
+
agentName: attributes.agentName != null ? String(attributes.agentName) : void 0,
|
|
1016
|
+
agentId: relationships?.agent?.data?.id ?? "",
|
|
1017
|
+
delegationId: relationships?.delegation?.data?.id ?? "",
|
|
1018
|
+
delegatorPartyId: relationships?.delegatorParty?.data?.id,
|
|
1019
|
+
delegateePartyId: relationships?.delegateeParty?.data?.id,
|
|
1020
|
+
delegatorPartyName: attributes.delegatorPartyName != null ? String(attributes.delegatorPartyName) : void 0,
|
|
1021
|
+
delegateePartyName: attributes.delegateePartyName != null ? String(attributes.delegateePartyName) : void 0,
|
|
1022
|
+
permissions: Array.isArray(attributes.permissions) ? attributes.permissions : [],
|
|
1023
|
+
limits: attributes.limits != null ? attributes.limits : void 0,
|
|
1024
|
+
expiresAt: attributes.expiresAt != null ? String(attributes.expiresAt) : void 0,
|
|
1025
|
+
createdAt: String(attributes.createdAt),
|
|
1026
|
+
createdBy: attributes.createdBy != null ? String(attributes.createdBy) : void 0,
|
|
1027
|
+
updatedAt: String(attributes.updatedAt)
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
function unwrapAgentDelegation(response) {
|
|
1031
|
+
if (!response?.data) {
|
|
1032
|
+
throw new NaturalError(
|
|
1033
|
+
'Unexpected response format: missing "data" field in agent delegation response'
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
1036
|
+
return unwrapAgentDelegationResource(response.data);
|
|
1037
|
+
}
|
|
1038
|
+
function unwrapAgentDelegationList(response) {
|
|
1039
|
+
if (!response?.data || !Array.isArray(response.data)) {
|
|
1040
|
+
throw new NaturalError(
|
|
1041
|
+
'Unexpected response format: missing "data" array in agent delegation list response'
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
1044
|
+
const agentDelegations = response.data.map(unwrapAgentDelegationResource);
|
|
1045
|
+
const pagination = response.meta?.pagination ?? {};
|
|
1046
|
+
return {
|
|
1047
|
+
agentDelegations,
|
|
1048
|
+
hasMore: pagination.hasMore ?? false,
|
|
1049
|
+
nextCursor: pagination.nextCursor ?? null
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
var DelegationsResource = class extends BaseResource {
|
|
1053
|
+
/**
|
|
1054
|
+
* List agent delegations with optional filters.
|
|
1055
|
+
*
|
|
1056
|
+
* @param params - Filter parameters
|
|
1057
|
+
* @returns AgentDelegationListResponse with list of agent delegations
|
|
1058
|
+
*/
|
|
1059
|
+
async list(params) {
|
|
1060
|
+
const headers = {};
|
|
1061
|
+
if (params?.instanceId) {
|
|
1062
|
+
headers["X-Instance-ID"] = params.instanceId;
|
|
1063
|
+
}
|
|
1064
|
+
const queryParams = {
|
|
1065
|
+
delegationId: params?.delegationId,
|
|
1066
|
+
agentId: params?.agentId,
|
|
1067
|
+
delegatorPartyId: params?.delegatorPartyId,
|
|
1068
|
+
delegateePartyId: params?.delegateePartyId,
|
|
1069
|
+
includeRevoked: params?.includeRevoked,
|
|
1070
|
+
limit: params?.limit ?? 50,
|
|
1071
|
+
cursor: params?.cursor
|
|
1072
|
+
};
|
|
1073
|
+
const response = await this.http.get("/agent-delegations", {
|
|
1074
|
+
params: queryParams,
|
|
1075
|
+
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
1076
|
+
});
|
|
1077
|
+
return unwrapAgentDelegationList(response);
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Get agent delegation by ID.
|
|
1081
|
+
*
|
|
1082
|
+
* @param agentDelegationId - The agent delegation handle (adl_xxx)
|
|
1083
|
+
* @returns AgentDelegation details
|
|
1084
|
+
*/
|
|
1085
|
+
async get(agentDelegationId) {
|
|
1086
|
+
const response = await this.http.get(
|
|
1087
|
+
`/agent-delegations/${agentDelegationId}`
|
|
1088
|
+
);
|
|
1089
|
+
return unwrapAgentDelegation(response);
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
|
|
1093
|
+
// src/resources/customers.ts
|
|
1094
|
+
var VALID_CUSTOMER_TYPES = /* @__PURE__ */ new Set(["party", "delegationInvitation"]);
|
|
1095
|
+
var VALID_WALLET_ACCESS = /* @__PURE__ */ new Set(["granted", "denied", "noWallet"]);
|
|
1096
|
+
function isCustomerType(value) {
|
|
1097
|
+
return VALID_CUSTOMER_TYPES.has(value);
|
|
1098
|
+
}
|
|
1099
|
+
function isWalletAccess(value) {
|
|
1100
|
+
return typeof value === "string" && VALID_WALLET_ACCESS.has(value);
|
|
1101
|
+
}
|
|
1102
|
+
function unwrapCustomerResource(resource) {
|
|
1103
|
+
if (!isCustomerType(resource.type) || !resource.attributes) {
|
|
1104
|
+
throw new NaturalError(
|
|
1105
|
+
`Unexpected resource format: expected type "party" or "delegationInvitation", got "${resource.type}"`
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
const { id, attributes } = resource;
|
|
1109
|
+
const party = typeof attributes.party === "object" && attributes.party != null ? {
|
|
1110
|
+
id: String(attributes.party.id),
|
|
1111
|
+
name: String(attributes.party.name),
|
|
1112
|
+
email: typeof attributes.party.email === "string" ? String(attributes.party.email) : void 0
|
|
1113
|
+
} : void 0;
|
|
1114
|
+
return {
|
|
1115
|
+
id,
|
|
1116
|
+
type: resource.type,
|
|
1117
|
+
party,
|
|
1118
|
+
email: typeof attributes.email === "string" ? attributes.email : void 0,
|
|
1119
|
+
status: typeof attributes.status === "string" ? attributes.status : "",
|
|
1120
|
+
permissions: Array.isArray(attributes.permissions) ? attributes.permissions : [],
|
|
1121
|
+
createdAt: typeof attributes.createdAt === "string" ? attributes.createdAt : "",
|
|
1122
|
+
walletAvailableMinor: typeof attributes.walletAvailableMinor === "number" ? attributes.walletAvailableMinor : void 0,
|
|
1123
|
+
walletAvailableDollars: typeof attributes.walletAvailableDollars === "string" ? attributes.walletAvailableDollars : void 0,
|
|
1124
|
+
walletAccess: isWalletAccess(attributes.walletAccess) ? attributes.walletAccess : "denied"
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
function unwrapCustomerList(response) {
|
|
1128
|
+
if (!response?.data || !Array.isArray(response.data)) {
|
|
1129
|
+
throw new NaturalError(
|
|
1130
|
+
'Unexpected response format: missing "data" array in customer list response'
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1133
|
+
const pagination = response.meta?.pagination ?? {};
|
|
1134
|
+
return {
|
|
1135
|
+
items: response.data.map(unwrapCustomerResource),
|
|
1136
|
+
hasMore: pagination.hasMore ?? false,
|
|
1137
|
+
nextCursor: pagination.nextCursor ?? null
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
var CustomersResource = class extends BaseResource {
|
|
1141
|
+
/**
|
|
1142
|
+
* List customers who have delegated access to the partner.
|
|
1143
|
+
*
|
|
1144
|
+
* @param params - Pagination parameters
|
|
1145
|
+
* @returns CustomerListResponse with items, hasMore, nextCursor
|
|
1146
|
+
*/
|
|
1147
|
+
async list(params) {
|
|
1148
|
+
const headers = {};
|
|
1149
|
+
if (params?.instanceId) {
|
|
1150
|
+
headers["X-Instance-ID"] = params.instanceId;
|
|
1151
|
+
}
|
|
1152
|
+
const queryParams = {
|
|
1153
|
+
limit: params?.limit,
|
|
1154
|
+
cursor: params?.cursor
|
|
1155
|
+
};
|
|
1156
|
+
const response = await this.http.get("/customers", {
|
|
1157
|
+
params: queryParams,
|
|
1158
|
+
headers: Object.keys(headers).length > 0 ? headers : void 0
|
|
1159
|
+
});
|
|
1160
|
+
return unwrapCustomerList(response);
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1163
|
+
|
|
1164
|
+
// src/client.ts
|
|
1165
|
+
var NaturalClient = class {
|
|
1166
|
+
http;
|
|
1167
|
+
/** Payments API resource. */
|
|
1168
|
+
payments;
|
|
1169
|
+
/** Wallet API resource for balance and withdrawals. */
|
|
1170
|
+
wallet;
|
|
1171
|
+
/** Transactions API resource. */
|
|
1172
|
+
transactions;
|
|
1173
|
+
/** Agents API resource for managing agents. */
|
|
1174
|
+
agents;
|
|
1175
|
+
/** Agent delegations API resource. */
|
|
1176
|
+
delegations;
|
|
1177
|
+
/** Customers API resource for listing parties who delegated access. */
|
|
1178
|
+
customers;
|
|
1179
|
+
/**
|
|
1180
|
+
* Initialize the Natural client.
|
|
1181
|
+
*
|
|
1182
|
+
* @param options - Client configuration options
|
|
1183
|
+
* @param options.apiKey - API key (defaults to NATURAL_API_KEY env var)
|
|
1184
|
+
* @param options.baseUrl - API base URL (defaults to https://api.natural.co)
|
|
1185
|
+
* @param options.timeout - Request timeout in milliseconds (default: 30000)
|
|
1186
|
+
*/
|
|
1187
|
+
constructor(options = {}) {
|
|
1188
|
+
this.http = new HTTPClient(options);
|
|
1189
|
+
this.payments = new PaymentsResource(this.http);
|
|
1190
|
+
this.wallet = new WalletResource(this.http);
|
|
1191
|
+
this.transactions = new TransactionsResource(this.http);
|
|
1192
|
+
this.agents = new AgentsResource(this.http);
|
|
1193
|
+
this.delegations = new DelegationsResource(this.http);
|
|
1194
|
+
this.customers = new CustomersResource(this.http);
|
|
1195
|
+
}
|
|
1196
|
+
};
|
|
1197
|
+
|
|
1198
|
+
// src/types/transactions.ts
|
|
1199
|
+
var TransactionTypeFilter = /* @__PURE__ */ ((TransactionTypeFilter2) => {
|
|
1200
|
+
TransactionTypeFilter2["PAYMENT"] = "payment";
|
|
1201
|
+
TransactionTypeFilter2["TRANSFER"] = "transfer";
|
|
1202
|
+
TransactionTypeFilter2["ALL"] = "all";
|
|
1203
|
+
return TransactionTypeFilter2;
|
|
1204
|
+
})(TransactionTypeFilter || {});
|
|
1205
|
+
|
|
1206
|
+
export { AuthenticationError, InsufficientFundsError, InvalidRequestError, NaturalClient, NaturalError, PaymentError, RateLimitError, RecipientNotFoundError, ServerError, TransactionTypeFilter, VERSION, bindContext, clearContext, configureLogging, getContext, getLogger, logApiCall, logError, logToolCall, runWithContext };
|
|
1207
|
+
//# sourceMappingURL=index.js.map
|
|
1208
|
+
//# sourceMappingURL=index.js.map
|