@naturalpay/sdk 0.0.2

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/dist/index.cjs ADDED
@@ -0,0 +1,968 @@
1
+ 'use strict';
2
+
3
+ var async_hooks = require('async_hooks');
4
+ var crypto = require('crypto');
5
+
6
+ // src/errors.ts
7
+ var NaturalError = class extends Error {
8
+ statusCode;
9
+ code;
10
+ constructor(message, options) {
11
+ super(message);
12
+ this.name = "NaturalError";
13
+ this.statusCode = options?.statusCode;
14
+ this.code = options?.code;
15
+ if (Error.captureStackTrace) {
16
+ Error.captureStackTrace(this, this.constructor);
17
+ }
18
+ }
19
+ };
20
+ var AuthenticationError = class extends NaturalError {
21
+ constructor(message = "Invalid or missing API key") {
22
+ super(message, { statusCode: 401, code: "authentication_error" });
23
+ this.name = "AuthenticationError";
24
+ }
25
+ };
26
+ var InvalidRequestError = class extends NaturalError {
27
+ constructor(message, code = "invalid_request") {
28
+ super(message, { statusCode: 400, code });
29
+ this.name = "InvalidRequestError";
30
+ }
31
+ };
32
+ var PaymentError = class extends NaturalError {
33
+ constructor(message, code = "payment_error") {
34
+ super(message, { statusCode: 400, code });
35
+ this.name = "PaymentError";
36
+ }
37
+ };
38
+ var InsufficientFundsError = class extends PaymentError {
39
+ constructor(message = "Insufficient funds") {
40
+ super(message, "insufficient_funds");
41
+ this.name = "InsufficientFundsError";
42
+ }
43
+ };
44
+ var RecipientNotFoundError = class extends PaymentError {
45
+ constructor(message = "Recipient not found") {
46
+ super(message, "recipient_not_found");
47
+ this.name = "RecipientNotFoundError";
48
+ }
49
+ };
50
+ var RateLimitError = class extends NaturalError {
51
+ retryAfter;
52
+ constructor(message = "Rate limit exceeded", retryAfter) {
53
+ super(message, { statusCode: 429, code: "rate_limit_exceeded" });
54
+ this.name = "RateLimitError";
55
+ this.retryAfter = retryAfter;
56
+ }
57
+ };
58
+ var ServerError = class extends NaturalError {
59
+ constructor(message = "Internal server error") {
60
+ super(message, { statusCode: 500, code: "server_error" });
61
+ this.name = "ServerError";
62
+ }
63
+ };
64
+ var SDK_VERSION = "0.0.1";
65
+ var LOG_LEVEL_VALUES = {
66
+ debug: 10,
67
+ info: 20,
68
+ warning: 30,
69
+ error: 40
70
+ };
71
+ var asyncContext = new async_hooks.AsyncLocalStorage();
72
+ var globalContext = {};
73
+ function getContext() {
74
+ const asyncStore = asyncContext.getStore();
75
+ if (asyncStore) {
76
+ return { ...asyncStore };
77
+ }
78
+ return { ...globalContext };
79
+ }
80
+ function sanitizeString(value) {
81
+ let sanitized = "";
82
+ for (const char of value) {
83
+ if (char === "\r" || char === "\n") {
84
+ sanitized += "\\n";
85
+ } else if (char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127) {
86
+ sanitized += `\\x${char.charCodeAt(0).toString(16).padStart(2, "0")}`;
87
+ } else {
88
+ sanitized += char;
89
+ }
90
+ }
91
+ const maxLength = 1e3;
92
+ if (sanitized.length > maxLength) {
93
+ sanitized = sanitized.slice(0, maxLength) + "...[truncated]";
94
+ }
95
+ return sanitized;
96
+ }
97
+ function sanitizeLogValue(value, depth = 0) {
98
+ const maxDepth = 10;
99
+ if (depth > maxDepth) {
100
+ return "[max depth exceeded]";
101
+ }
102
+ if (value === null || value === void 0) {
103
+ return value;
104
+ }
105
+ if (typeof value === "string") {
106
+ return sanitizeString(value);
107
+ }
108
+ if (typeof value === "number" || typeof value === "boolean") {
109
+ return value;
110
+ }
111
+ if (Array.isArray(value)) {
112
+ return value.map((item) => sanitizeLogValue(item, depth + 1));
113
+ }
114
+ if (typeof value === "object") {
115
+ const sanitized = {};
116
+ for (const [key, val] of Object.entries(value)) {
117
+ const sanitizedKey = sanitizeString(key);
118
+ sanitized[sanitizedKey] = sanitizeLogValue(val, depth + 1);
119
+ }
120
+ return sanitized;
121
+ }
122
+ return `[${typeof value}]`;
123
+ }
124
+ function bindContext(context) {
125
+ const sanitized = {};
126
+ for (const [key, value] of Object.entries(context)) {
127
+ sanitized[key] = sanitizeLogValue(value);
128
+ }
129
+ const asyncStore = asyncContext.getStore();
130
+ if (asyncStore) {
131
+ Object.assign(asyncStore, sanitized);
132
+ }
133
+ globalContext = { ...globalContext, ...sanitized };
134
+ }
135
+ function clearContext() {
136
+ const asyncStore = asyncContext.getStore();
137
+ if (asyncStore) {
138
+ for (const key of Object.keys(asyncStore)) {
139
+ delete asyncStore[key];
140
+ }
141
+ }
142
+ globalContext = {};
143
+ }
144
+ function runWithContext(context, fn) {
145
+ const sanitized = {};
146
+ for (const [key, value] of Object.entries(context)) {
147
+ sanitized[key] = sanitizeLogValue(value);
148
+ }
149
+ return asyncContext.run(sanitized, fn);
150
+ }
151
+ var loggingConfig = {
152
+ level: process.env["NATURAL_LOG_LEVEL"]?.toLowerCase() || "info",
153
+ jsonFormat: process.env["NATURAL_LOG_FORMAT"]?.toLowerCase() === "json",
154
+ serviceName: "naturalpay-sdk",
155
+ environment: process.env["NATURAL_ENV"] || process.env["DD_ENV"] || "development"
156
+ };
157
+ function configureLogging(options) {
158
+ if (options?.level) {
159
+ loggingConfig.level = options.level;
160
+ }
161
+ if (options?.jsonFormat !== void 0) {
162
+ loggingConfig.jsonFormat = options.jsonFormat;
163
+ }
164
+ if (options?.serviceName) {
165
+ loggingConfig.serviceName = options.serviceName;
166
+ }
167
+ }
168
+ function getSourceInfo() {
169
+ const stack = new Error().stack;
170
+ if (!stack) {
171
+ return { file: "unknown", line: 0, function: "unknown" };
172
+ }
173
+ const lines = stack.split("\n");
174
+ const callerLine = lines[4] || lines[3] || "";
175
+ const match = callerLine.match(/at\s+(?:(.+?)\s+\()?(.*?):(\d+):\d+\)?/);
176
+ if (match) {
177
+ return {
178
+ function: match[1] || "anonymous",
179
+ file: match[2] || "unknown",
180
+ line: parseInt(match[3] || "0", 10)
181
+ };
182
+ }
183
+ return { file: "unknown", line: 0, function: "unknown" };
184
+ }
185
+ function formatJsonLog(level, loggerName, message, extra) {
186
+ const record = {
187
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
188
+ level: level.toUpperCase(),
189
+ logger: loggerName,
190
+ message,
191
+ source: getSourceInfo(),
192
+ service: loggingConfig.serviceName,
193
+ environment: loggingConfig.environment,
194
+ version: SDK_VERSION,
195
+ ...getContext(),
196
+ ...extra
197
+ };
198
+ const ddTraceId = process.env["DD_TRACE_ID"];
199
+ const ddSpanId = process.env["DD_SPAN_ID"];
200
+ if (ddTraceId) {
201
+ record["dd.trace_id"] = ddTraceId;
202
+ record["dd.span_id"] = ddSpanId;
203
+ record["dd.service"] = loggingConfig.serviceName;
204
+ record["dd.version"] = SDK_VERSION;
205
+ record["dd.env"] = loggingConfig.environment;
206
+ }
207
+ return JSON.stringify(record);
208
+ }
209
+ function formatTextLog(level, loggerName, message, extra) {
210
+ const contextEntries = Object.entries({ ...getContext(), ...extra });
211
+ const contextStr = contextEntries.length > 0 ? ` [${contextEntries.map(([k, v]) => `${k}=${v}`).join(", ")}]` : "";
212
+ return `${level.toUpperCase().padEnd(8)} ${loggerName}: ${message}${contextStr}`;
213
+ }
214
+ var Logger = class {
215
+ constructor(name) {
216
+ this.name = name;
217
+ }
218
+ log(level, message, extra) {
219
+ if (LOG_LEVEL_VALUES[level] < LOG_LEVEL_VALUES[loggingConfig.level]) {
220
+ return;
221
+ }
222
+ const formatted = loggingConfig.jsonFormat ? formatJsonLog(level, this.name, message, extra) : formatTextLog(level, this.name, message, extra);
223
+ if (level === "error") {
224
+ console.error(formatted);
225
+ } else if (level === "warning") {
226
+ console.warn(formatted);
227
+ } else {
228
+ console.error(formatted);
229
+ }
230
+ }
231
+ debug(message, extra) {
232
+ this.log("debug", message, extra);
233
+ }
234
+ info(message, extra) {
235
+ this.log("info", message, extra);
236
+ }
237
+ warning(message, extra) {
238
+ this.log("warning", message, extra);
239
+ }
240
+ warn(message, extra) {
241
+ this.warning(message, extra);
242
+ }
243
+ error(message, extra) {
244
+ this.log("error", message, extra);
245
+ }
246
+ };
247
+ function getLogger(name) {
248
+ if (!name.startsWith("naturalpay")) {
249
+ name = `naturalpay.${name}`;
250
+ }
251
+ return new Logger(name);
252
+ }
253
+ function logError(logger2, message, options) {
254
+ const extra = { ...options };
255
+ if (options?.error) {
256
+ extra["errorType"] = options.error.name;
257
+ extra["errorMessage"] = options.error.message;
258
+ if (options.error.stack) {
259
+ extra["errorStack"] = options.error.stack.split("\n").slice(0, 5).join("\n");
260
+ }
261
+ delete extra["error"];
262
+ }
263
+ logger2.error(message, extra);
264
+ }
265
+ function logApiCall(logger2, method, path, options) {
266
+ const extra = {
267
+ method,
268
+ path,
269
+ ...options
270
+ };
271
+ if (options?.statusCode) {
272
+ extra["statusCode"] = options.statusCode;
273
+ }
274
+ if (options?.durationMs !== void 0) {
275
+ extra["durationMs"] = Math.round(options.durationMs * 100) / 100;
276
+ }
277
+ if (options?.error) {
278
+ logError(logger2, `API call failed: ${method} ${path}`, extra);
279
+ } else if (options?.statusCode && options.statusCode >= 400) {
280
+ logger2.warning(`API call error: ${method} ${path} -> ${options.statusCode}`, extra);
281
+ } else {
282
+ logger2.info(`API call: ${method} ${path} -> ${options?.statusCode}`, extra);
283
+ }
284
+ }
285
+ function logToolCall(logger2, toolName, options) {
286
+ const extra = {
287
+ toolName,
288
+ ...options
289
+ };
290
+ if (options?.durationMs !== void 0) {
291
+ extra["durationMs"] = Math.round(options.durationMs * 100) / 100;
292
+ }
293
+ if (options?.error) {
294
+ logError(logger2, `Tool call failed: ${toolName}`, extra);
295
+ } else if (options?.success === false) {
296
+ logger2.warning(`Tool call returned error: ${toolName}`, extra);
297
+ } else {
298
+ logger2.info(`Tool call: ${toolName}`, extra);
299
+ }
300
+ }
301
+
302
+ // src/http.ts
303
+ var logger = getLogger("http");
304
+ var DEFAULT_BASE_URL = "https://api.natural.co";
305
+ var DEFAULT_TIMEOUT = 3e4;
306
+ var SDK_VERSION2 = "0.0.1";
307
+ function hashString(str) {
308
+ let hash = 0;
309
+ for (let i = 0; i < str.length; i++) {
310
+ const char = str.charCodeAt(i);
311
+ hash = (hash << 5) - hash + char;
312
+ hash = hash & hash;
313
+ }
314
+ return Math.abs(hash).toString(16).slice(0, 16);
315
+ }
316
+ function snakeToCamel(obj) {
317
+ if (obj === null || obj === void 0) {
318
+ return obj;
319
+ }
320
+ if (Array.isArray(obj)) {
321
+ return obj.map(snakeToCamel);
322
+ }
323
+ if (typeof obj === "object") {
324
+ const result = {};
325
+ for (const [key, value] of Object.entries(obj)) {
326
+ const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
327
+ result[camelKey] = snakeToCamel(value);
328
+ }
329
+ return result;
330
+ }
331
+ return obj;
332
+ }
333
+ function camelToSnake(obj) {
334
+ if (obj === null || obj === void 0) {
335
+ return obj;
336
+ }
337
+ if (Array.isArray(obj)) {
338
+ return obj.map(camelToSnake);
339
+ }
340
+ if (typeof obj === "object") {
341
+ const result = {};
342
+ for (const [key, value] of Object.entries(obj)) {
343
+ const snakeKey = key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
344
+ result[snakeKey] = camelToSnake(value);
345
+ }
346
+ return result;
347
+ }
348
+ return obj;
349
+ }
350
+ var HTTPClient = class {
351
+ apiKey;
352
+ baseUrl;
353
+ timeout;
354
+ jwtCache = /* @__PURE__ */ new Map();
355
+ constructor(options = {}) {
356
+ this.apiKey = options.apiKey ?? process.env["NATURAL_API_KEY"] ?? "";
357
+ this.baseUrl = (options.baseUrl ?? process.env["NATURAL_SERVER_URL"] ?? DEFAULT_BASE_URL).replace(/\/$/, "");
358
+ this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
359
+ }
360
+ /**
361
+ * Get a cached JWT or exchange API key for a new one.
362
+ */
363
+ async getJwt() {
364
+ if (!this.apiKey) {
365
+ throw new AuthenticationError();
366
+ }
367
+ if (!this.apiKey.startsWith("pk_")) {
368
+ return this.apiKey;
369
+ }
370
+ const cacheKey = hashString(this.apiKey);
371
+ const cached = this.jwtCache.get(cacheKey);
372
+ if (cached && Date.now() < cached.expiresAt) {
373
+ return cached.token;
374
+ }
375
+ const controller = new AbortController();
376
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
377
+ logger.debug("Exchanging API key for JWT", { path: "/auth/partner/token" });
378
+ try {
379
+ const response = await fetch(`${this.baseUrl}/auth/partner/token`, {
380
+ method: "POST",
381
+ headers: {
382
+ Authorization: `Bearer ${this.apiKey}`,
383
+ "Content-Type": "application/json"
384
+ },
385
+ signal: controller.signal
386
+ });
387
+ clearTimeout(timeoutId);
388
+ if (!response.ok) {
389
+ const authError = new AuthenticationError(
390
+ `Authentication failed (status=${response.status})`
391
+ );
392
+ logError(logger, "JWT exchange failed", {
393
+ error: authError,
394
+ statusCode: response.status,
395
+ path: "/auth/partner/token"
396
+ });
397
+ throw authError;
398
+ }
399
+ const data = await response.json();
400
+ const expiresIn = data.expires_in ?? 900;
401
+ const expiresAt = Date.now() + (expiresIn - 30) * 1e3;
402
+ this.jwtCache.set(cacheKey, { token: data.access_token, expiresAt });
403
+ return data.access_token;
404
+ } catch (error) {
405
+ clearTimeout(timeoutId);
406
+ if (error instanceof AuthenticationError) {
407
+ throw error;
408
+ }
409
+ if (error instanceof Error && error.name === "AbortError") {
410
+ const networkError2 = new NaturalError("Request timed out during authentication");
411
+ logError(logger, "JWT exchange network error", {
412
+ error: networkError2,
413
+ path: "/auth/partner/token"
414
+ });
415
+ throw networkError2;
416
+ }
417
+ const networkError = new NaturalError(
418
+ `Network error during authentication: ${error instanceof Error ? error.message : "Unknown error"}`
419
+ );
420
+ logError(logger, "JWT exchange network error", {
421
+ error: networkError,
422
+ path: "/auth/partner/token"
423
+ });
424
+ throw networkError;
425
+ }
426
+ }
427
+ /**
428
+ * Build URL with query parameters.
429
+ */
430
+ buildUrl(path, params) {
431
+ const url = new URL(path, this.baseUrl);
432
+ if (params) {
433
+ for (const [key, value] of Object.entries(params)) {
434
+ if (value !== void 0 && value !== null) {
435
+ const snakeKey = key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
436
+ url.searchParams.set(snakeKey, String(value));
437
+ }
438
+ }
439
+ }
440
+ return url.toString();
441
+ }
442
+ /**
443
+ * Handle API response and throw appropriate errors.
444
+ */
445
+ async handleResponse(response, method, path, durationMs) {
446
+ if (response.status === 401) {
447
+ const authError = new AuthenticationError();
448
+ logApiCall(logger, method, path, {
449
+ statusCode: response.status,
450
+ durationMs,
451
+ error: authError
452
+ });
453
+ throw authError;
454
+ }
455
+ if (response.status === 429) {
456
+ const retryAfter = response.headers.get("Retry-After");
457
+ const rateError = new RateLimitError(
458
+ "Rate limit exceeded",
459
+ retryAfter ? parseInt(retryAfter, 10) : void 0
460
+ );
461
+ logger.warning(`Rate limited: ${method} ${path}`, {
462
+ method,
463
+ path,
464
+ statusCode: response.status,
465
+ retryAfter,
466
+ durationMs: Math.round(durationMs * 100) / 100
467
+ });
468
+ throw rateError;
469
+ }
470
+ if (response.status >= 500) {
471
+ const serverError = new ServerError(`Server error: ${response.status}`);
472
+ logApiCall(logger, method, path, {
473
+ statusCode: response.status,
474
+ durationMs,
475
+ error: serverError
476
+ });
477
+ throw serverError;
478
+ }
479
+ let data;
480
+ try {
481
+ const text = await response.text();
482
+ data = text ? JSON.parse(text) : {};
483
+ } catch {
484
+ if (response.status >= 400) {
485
+ const parseError = new NaturalError(`Request failed: ${response.status}`, {
486
+ statusCode: response.status
487
+ });
488
+ logApiCall(logger, method, path, {
489
+ statusCode: response.status,
490
+ durationMs,
491
+ error: parseError
492
+ });
493
+ throw parseError;
494
+ }
495
+ logApiCall(logger, method, path, { statusCode: response.status, durationMs });
496
+ return {};
497
+ }
498
+ if (response.status >= 400) {
499
+ const errorData = data;
500
+ const errorMessage = errorData.detail ?? errorData.message ?? errorData.error ?? "Request failed";
501
+ const errorCode = errorData.code ?? "unknown_error";
502
+ const requestError = new InvalidRequestError(
503
+ `${errorMessage} (status=${response.status})`,
504
+ errorCode
505
+ );
506
+ logApiCall(logger, method, path, {
507
+ statusCode: response.status,
508
+ durationMs,
509
+ error: requestError
510
+ });
511
+ throw requestError;
512
+ }
513
+ logApiCall(logger, method, path, { statusCode: response.status, durationMs });
514
+ return snakeToCamel(data);
515
+ }
516
+ /**
517
+ * Make an authenticated request.
518
+ */
519
+ async request(method, path, options) {
520
+ const jwt = await this.getJwt();
521
+ const url = this.buildUrl(path, options?.params);
522
+ const controller = new AbortController();
523
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
524
+ logger.debug(`API request: ${method} ${path}`, {
525
+ method,
526
+ path,
527
+ hasBody: !!options?.body
528
+ });
529
+ const startTime = Date.now();
530
+ try {
531
+ const headers = {
532
+ Authorization: `Bearer ${jwt}`,
533
+ "Content-Type": "application/json",
534
+ "User-Agent": `naturalpay-ts/${SDK_VERSION2}`,
535
+ ...options?.headers
536
+ };
537
+ const response = await fetch(url, {
538
+ method,
539
+ headers,
540
+ body: options?.body ? JSON.stringify(camelToSnake(options.body)) : void 0,
541
+ signal: controller.signal
542
+ });
543
+ clearTimeout(timeoutId);
544
+ const durationMs = Date.now() - startTime;
545
+ return this.handleResponse(response, method, path, durationMs);
546
+ } catch (error) {
547
+ clearTimeout(timeoutId);
548
+ const durationMs = Date.now() - startTime;
549
+ if (error instanceof NaturalError || error instanceof AuthenticationError || error instanceof InvalidRequestError || error instanceof RateLimitError || error instanceof ServerError) {
550
+ throw error;
551
+ }
552
+ if (error instanceof Error && error.name === "AbortError") {
553
+ const networkError2 = new NaturalError("Request timed out");
554
+ logApiCall(logger, method, path, { durationMs, error: networkError2 });
555
+ throw networkError2;
556
+ }
557
+ const networkError = new NaturalError(
558
+ `Network error: ${error instanceof Error ? error.message : "Unknown error"}`
559
+ );
560
+ logApiCall(logger, method, path, { durationMs, error: networkError });
561
+ throw networkError;
562
+ }
563
+ }
564
+ async get(path, options) {
565
+ return this.request("GET", path, options);
566
+ }
567
+ async post(path, options) {
568
+ return this.request("POST", path, options);
569
+ }
570
+ async put(path, options) {
571
+ return this.request("PUT", path, options);
572
+ }
573
+ async delete(path) {
574
+ return this.request("DELETE", path);
575
+ }
576
+ };
577
+
578
+ // src/resources/base.ts
579
+ var BaseResource = class {
580
+ http;
581
+ constructor(http) {
582
+ this.http = http;
583
+ }
584
+ };
585
+
586
+ // src/resources/payments.ts
587
+ var IDEMPOTENCY_WINDOW_SECONDS = 300;
588
+ var PaymentsResource = class extends BaseResource {
589
+ /**
590
+ * Generate idempotency key based on payment details + time window.
591
+ */
592
+ generateIdempotencyKey(recipient, amount, memo) {
593
+ const timeWindow = Math.floor(Date.now() / 1e3 / IDEMPOTENCY_WINDOW_SECONDS);
594
+ const data = [recipient, String(amount), memo ?? "", String(timeWindow)].join(":");
595
+ return crypto.createHash("sha256").update(data).digest("hex").slice(0, 32);
596
+ }
597
+ /**
598
+ * Create a payment.
599
+ *
600
+ * Must provide exactly one of: recipientEmail, recipientPhone, or recipientPartyId.
601
+ *
602
+ * @param params - Payment creation parameters
603
+ * @returns Payment object with transfer_id, status, etc.
604
+ */
605
+ async create(params) {
606
+ const recipientCount = [
607
+ params.recipientEmail,
608
+ params.recipientPhone,
609
+ params.recipientPartyId
610
+ ].filter((r) => r !== void 0).length;
611
+ if (recipientCount !== 1) {
612
+ throw new Error(
613
+ "Must provide exactly one of: recipientEmail, recipientPhone, or recipientPartyId"
614
+ );
615
+ }
616
+ const recipient = params.recipientEmail ?? params.recipientPhone ?? params.recipientPartyId;
617
+ const idempotencyKey = params.idempotencyKey ?? this.generateIdempotencyKey(recipient, params.amount, params.memo);
618
+ const body = {
619
+ amount: params.amount
620
+ };
621
+ if (params.recipientEmail) body["recipientEmail"] = params.recipientEmail;
622
+ if (params.recipientPhone) body["recipientPhone"] = params.recipientPhone;
623
+ if (params.recipientPartyId) body["recipientPartyId"] = params.recipientPartyId;
624
+ if (params.memo) body["memo"] = params.memo;
625
+ if (params.agentId) body["agentId"] = params.agentId;
626
+ if (params.customerPartyId) body["customerPartyId"] = params.customerPartyId;
627
+ if (params.instanceId) body["instanceId"] = params.instanceId;
628
+ return this.http.post("/payments/initiate", {
629
+ body,
630
+ headers: { "Idempotency-Key": idempotencyKey }
631
+ });
632
+ }
633
+ /**
634
+ * Get payment status by transfer ID.
635
+ *
636
+ * @param transferId - The transfer ID to look up
637
+ * @returns Payment object with current status
638
+ */
639
+ async retrieve(transferId) {
640
+ return this.http.get(`/payments/${transferId}`);
641
+ }
642
+ /**
643
+ * Cancel a pending payment.
644
+ *
645
+ * @param transferId - The transfer ID to cancel
646
+ * @returns Cancellation result with status and message
647
+ */
648
+ async cancel(transferId) {
649
+ return this.http.post(`/payments/${transferId}/cancel`);
650
+ }
651
+ };
652
+
653
+ // src/resources/wallet.ts
654
+ var WalletResource = class extends BaseResource {
655
+ /**
656
+ * Get current wallet balance.
657
+ *
658
+ * @returns AccountBalance with available, current, pending amounts
659
+ */
660
+ async balance() {
661
+ return this.http.get("/wallet/balance");
662
+ }
663
+ /**
664
+ * Initiate a deposit from a linked bank account.
665
+ *
666
+ * @param params - Deposit parameters
667
+ * @returns DepositResponse with transfer status
668
+ */
669
+ async deposit(params) {
670
+ const body = {
671
+ amount: params.amount,
672
+ currency: params.currency ?? "USD",
673
+ paymentInstrumentId: params.paymentInstrumentId
674
+ };
675
+ if (params.description) {
676
+ body["description"] = params.description;
677
+ }
678
+ return this.http.post("/wallet/deposit", {
679
+ body,
680
+ headers: { "Idempotency-Key": params.idempotencyKey }
681
+ });
682
+ }
683
+ /**
684
+ * Initiate a withdrawal to a linked bank account.
685
+ *
686
+ * @param params - Withdrawal parameters
687
+ * @returns WithdrawResponse with transfer status (may require KYC/MFA)
688
+ */
689
+ async withdraw(params) {
690
+ const body = {
691
+ amount: params.amount,
692
+ currency: params.currency ?? "USD",
693
+ paymentInstrumentId: params.paymentInstrumentId
694
+ };
695
+ if (params.description) {
696
+ body["description"] = params.description;
697
+ }
698
+ return this.http.post("/wallet/withdraw", {
699
+ body,
700
+ headers: { "Idempotency-Key": params.idempotencyKey }
701
+ });
702
+ }
703
+ };
704
+
705
+ // src/resources/transactions.ts
706
+ var TransactionsResource = class extends BaseResource {
707
+ /**
708
+ * List recent transactions.
709
+ *
710
+ * @param params - List parameters including agent context
711
+ * @returns List of Transaction objects
712
+ */
713
+ async list(params) {
714
+ const headers = {};
715
+ if (params?.agentId) {
716
+ headers["X-Agent-ID"] = params.agentId;
717
+ }
718
+ if (params?.customerPartyId) {
719
+ headers["X-On-Behalf-Of"] = params.customerPartyId;
720
+ }
721
+ const data = await this.http.get("/transactions", {
722
+ params: {
723
+ limit: params?.limit ?? 10,
724
+ offset: params?.offset ?? 0,
725
+ customerFilter: params?.customerFilter,
726
+ type: params?.type
727
+ },
728
+ headers: Object.keys(headers).length > 0 ? headers : void 0
729
+ });
730
+ return data.transfers ?? data.transactions ?? [];
731
+ }
732
+ };
733
+
734
+ // src/resources/agents.ts
735
+ var AgentsResource = class extends BaseResource {
736
+ /**
737
+ * List agents for the partner.
738
+ *
739
+ * @param params - Filter and pagination parameters
740
+ * @returns AgentListResponse with list of agents
741
+ */
742
+ async list(params) {
743
+ return this.http.get("/agents", {
744
+ params: {
745
+ status: params?.status,
746
+ partyId: params?.partyId,
747
+ limit: params?.limit ?? 50,
748
+ offset: params?.offset ?? 0
749
+ }
750
+ });
751
+ }
752
+ /**
753
+ * Get agent by ID.
754
+ *
755
+ * @param agentId - The agent ID to retrieve (agt_xxx)
756
+ * @returns Agent details
757
+ */
758
+ async get(agentId) {
759
+ return this.http.get(`/agents/${agentId}`);
760
+ }
761
+ /**
762
+ * Create a new agent.
763
+ *
764
+ * @param params - Agent creation parameters
765
+ * @returns AgentCreateResponse with created agent details
766
+ */
767
+ async create(params) {
768
+ const body = {
769
+ name: params.name,
770
+ partyId: params.partyId
771
+ };
772
+ if (params.description) {
773
+ body["description"] = params.description;
774
+ }
775
+ const headers = {};
776
+ if (params.idempotencyKey) {
777
+ headers["Idempotency-Key"] = params.idempotencyKey;
778
+ }
779
+ return this.http.post("/agents", {
780
+ body,
781
+ headers: Object.keys(headers).length > 0 ? headers : void 0
782
+ });
783
+ }
784
+ /**
785
+ * Update an existing agent.
786
+ *
787
+ * @param agentId - The agent ID to update (agt_xxx)
788
+ * @param params - Update parameters
789
+ * @returns AgentUpdateResponse with updated agent details
790
+ */
791
+ async update(agentId, params) {
792
+ const body = {};
793
+ if (params.name !== void 0) body["name"] = params.name;
794
+ if (params.description !== void 0) body["description"] = params.description;
795
+ if (params.status !== void 0) body["status"] = params.status;
796
+ const headers = {};
797
+ if (params.idempotencyKey) {
798
+ headers["Idempotency-Key"] = params.idempotencyKey;
799
+ }
800
+ return this.http.put(`/agents/${agentId}`, {
801
+ body,
802
+ headers: Object.keys(headers).length > 0 ? headers : void 0
803
+ });
804
+ }
805
+ /**
806
+ * Delete an agent.
807
+ *
808
+ * @param agentId - The agent ID to delete (agt_xxx)
809
+ */
810
+ async delete(agentId) {
811
+ await this.http.delete(`/agents/${agentId}`);
812
+ }
813
+ };
814
+
815
+ // src/resources/delegations.ts
816
+ var DelegationsResource = class extends BaseResource {
817
+ /**
818
+ * List delegations with optional filters.
819
+ *
820
+ * @param params - Filter parameters
821
+ * @returns DelegationListResponse with list of delegations
822
+ */
823
+ async list(params) {
824
+ return this.http.get("/delegations", {
825
+ params: {
826
+ status: params?.status,
827
+ delegatingPartyId: params?.delegatingPartyId,
828
+ delegatedPartyId: params?.delegatedPartyId
829
+ }
830
+ });
831
+ }
832
+ /**
833
+ * Get delegation by ID.
834
+ *
835
+ * @param delegationId - The delegation handle (dlg_xxx)
836
+ * @returns Delegation details
837
+ */
838
+ async get(delegationId) {
839
+ return this.http.get(`/delegations/${delegationId}`);
840
+ }
841
+ /**
842
+ * Create a new delegation (party-to-party trust relationship).
843
+ *
844
+ * @param params - Delegation creation parameters
845
+ * @returns Created Delegation
846
+ */
847
+ async create(params) {
848
+ const body = {
849
+ delegatingPartyId: params.delegatingPartyId,
850
+ delegatedPartyId: params.delegatedPartyId,
851
+ permissions: params.permissions
852
+ };
853
+ if (params.expiresAt) {
854
+ body["expiresAt"] = params.expiresAt;
855
+ }
856
+ const headers = {};
857
+ if (params.idempotencyKey) {
858
+ headers["Idempotency-Key"] = params.idempotencyKey;
859
+ }
860
+ return this.http.post("/delegations", {
861
+ body,
862
+ headers: Object.keys(headers).length > 0 ? headers : void 0
863
+ });
864
+ }
865
+ /**
866
+ * Update an existing delegation.
867
+ *
868
+ * @param delegationId - Delegation handle (dlg_xxx)
869
+ * @param params - Update parameters
870
+ * @returns Updated Delegation
871
+ */
872
+ async update(delegationId, params) {
873
+ const body = {};
874
+ if (params.status !== void 0) body["status"] = params.status;
875
+ if (params.permissions !== void 0) body["permissions"] = params.permissions;
876
+ if (params.expiresAt !== void 0) body["expiresAt"] = params.expiresAt;
877
+ return this.http.put(`/delegations/${delegationId}`, { body });
878
+ }
879
+ /**
880
+ * Revoke a delegation (soft delete by setting status to REVOKED).
881
+ *
882
+ * @param delegationId - Delegation handle (dlg_xxx)
883
+ * @returns Revoked Delegation
884
+ */
885
+ async revoke(delegationId) {
886
+ return this.http.put(`/delegations/${delegationId}/revoke`);
887
+ }
888
+ };
889
+
890
+ // src/resources/customers.ts
891
+ var CustomersResource = class extends BaseResource {
892
+ /**
893
+ * List customers onboarded by the partner via delegation.
894
+ *
895
+ * @returns List of Customer objects with party info and delegation details
896
+ */
897
+ async list() {
898
+ return this.http.get("/customers");
899
+ }
900
+ };
901
+
902
+ // src/client.ts
903
+ var NaturalClient = class {
904
+ http;
905
+ /** Payments API resource. */
906
+ payments;
907
+ /** Wallet API resource for balance, transfers, deposits, and withdrawals. */
908
+ wallet;
909
+ /** Transactions API resource. */
910
+ transactions;
911
+ /** Agents API resource for managing agents. */
912
+ agents;
913
+ /** Delegations API resource for managing party-to-party delegations. */
914
+ delegations;
915
+ /** Customers API resource for listing customers onboarded via delegation. */
916
+ customers;
917
+ /**
918
+ * Initialize the Natural client.
919
+ *
920
+ * @param options - Client configuration options
921
+ * @param options.apiKey - API key (defaults to NATURAL_API_KEY env var)
922
+ * @param options.baseUrl - API base URL (defaults to https://api.natural.co)
923
+ * @param options.timeout - Request timeout in milliseconds (default: 30000)
924
+ */
925
+ constructor(options = {}) {
926
+ this.http = new HTTPClient(options);
927
+ this.payments = new PaymentsResource(this.http);
928
+ this.wallet = new WalletResource(this.http);
929
+ this.transactions = new TransactionsResource(this.http);
930
+ this.agents = new AgentsResource(this.http);
931
+ this.delegations = new DelegationsResource(this.http);
932
+ this.customers = new CustomersResource(this.http);
933
+ }
934
+ };
935
+
936
+ // src/types/transactions.ts
937
+ var TransactionTypeFilter = /* @__PURE__ */ ((TransactionTypeFilter2) => {
938
+ TransactionTypeFilter2["PAYMENT"] = "payment";
939
+ TransactionTypeFilter2["TRANSFER"] = "transfer";
940
+ TransactionTypeFilter2["ALL"] = "all";
941
+ return TransactionTypeFilter2;
942
+ })(TransactionTypeFilter || {});
943
+
944
+ // src/index.ts
945
+ var VERSION = "0.0.1";
946
+
947
+ exports.AuthenticationError = AuthenticationError;
948
+ exports.InsufficientFundsError = InsufficientFundsError;
949
+ exports.InvalidRequestError = InvalidRequestError;
950
+ exports.NaturalClient = NaturalClient;
951
+ exports.NaturalError = NaturalError;
952
+ exports.PaymentError = PaymentError;
953
+ exports.RateLimitError = RateLimitError;
954
+ exports.RecipientNotFoundError = RecipientNotFoundError;
955
+ exports.ServerError = ServerError;
956
+ exports.TransactionTypeFilter = TransactionTypeFilter;
957
+ exports.VERSION = VERSION;
958
+ exports.bindContext = bindContext;
959
+ exports.clearContext = clearContext;
960
+ exports.configureLogging = configureLogging;
961
+ exports.getContext = getContext;
962
+ exports.getLogger = getLogger;
963
+ exports.logApiCall = logApiCall;
964
+ exports.logError = logError;
965
+ exports.logToolCall = logToolCall;
966
+ exports.runWithContext = runWithContext;
967
+ //# sourceMappingURL=index.cjs.map
968
+ //# sourceMappingURL=index.cjs.map