@afriex/sdk 5.0.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +155 -148
  3. package/dist/cjs/client.d.ts +110 -0
  4. package/dist/cjs/client.js +231 -0
  5. package/dist/cjs/core/api-promise.d.ts +25 -0
  6. package/dist/cjs/core/api-promise.js +49 -0
  7. package/dist/cjs/core/errors.d.ts +78 -0
  8. package/dist/cjs/core/errors.js +143 -0
  9. package/dist/cjs/core/pagination.d.ts +47 -0
  10. package/dist/cjs/core/pagination.js +70 -0
  11. package/dist/cjs/core/resource.d.ts +18 -0
  12. package/dist/cjs/core/resource.js +36 -0
  13. package/dist/cjs/core/types.d.ts +43 -0
  14. package/dist/cjs/core/types.js +2 -0
  15. package/dist/cjs/generated/openapi.d.ts +5750 -0
  16. package/dist/cjs/generated/openapi.js +6 -0
  17. package/dist/cjs/index.d.ts +21 -0
  18. package/dist/cjs/index.js +34 -0
  19. package/dist/cjs/package.json +1 -0
  20. package/dist/cjs/resources/balance.d.ts +16 -0
  21. package/dist/cjs/resources/balance.js +19 -0
  22. package/dist/cjs/resources/checkout.d.ts +12 -0
  23. package/dist/cjs/resources/checkout.js +16 -0
  24. package/dist/cjs/resources/customers.d.ts +27 -0
  25. package/dist/cjs/resources/customers.js +56 -0
  26. package/dist/cjs/resources/media.d.ts +24 -0
  27. package/dist/cjs/resources/media.js +39 -0
  28. package/dist/cjs/resources/payment-batches.d.ts +41 -0
  29. package/dist/cjs/resources/payment-batches.js +76 -0
  30. package/dist/cjs/resources/payment-methods.d.ts +49 -0
  31. package/dist/cjs/resources/payment-methods.js +95 -0
  32. package/dist/cjs/resources/rates.d.ts +9 -0
  33. package/dist/cjs/resources/rates.js +13 -0
  34. package/dist/cjs/resources/sme-registration.d.ts +24 -0
  35. package/dist/cjs/resources/sme-registration.js +31 -0
  36. package/dist/cjs/resources/transactions.d.ts +39 -0
  37. package/dist/cjs/resources/transactions.js +67 -0
  38. package/dist/cjs/resources/webhooks.d.ts +43 -0
  39. package/dist/cjs/resources/webhooks.js +53 -0
  40. package/dist/cjs/version.d.ts +1 -0
  41. package/dist/cjs/version.js +5 -0
  42. package/dist/esm/client.d.ts +110 -0
  43. package/dist/esm/client.js +227 -0
  44. package/dist/esm/core/api-promise.d.ts +25 -0
  45. package/dist/esm/core/api-promise.js +45 -0
  46. package/dist/esm/core/errors.d.ts +78 -0
  47. package/dist/esm/core/errors.js +126 -0
  48. package/dist/esm/core/pagination.d.ts +47 -0
  49. package/dist/esm/core/pagination.js +65 -0
  50. package/dist/esm/core/resource.d.ts +18 -0
  51. package/dist/esm/core/resource.js +30 -0
  52. package/dist/esm/core/types.d.ts +43 -0
  53. package/dist/esm/core/types.js +1 -0
  54. package/dist/esm/generated/openapi.d.ts +5750 -0
  55. package/dist/esm/generated/openapi.js +5 -0
  56. package/dist/esm/index.d.ts +21 -0
  57. package/dist/esm/index.js +7 -0
  58. package/dist/esm/resources/balance.d.ts +16 -0
  59. package/dist/esm/resources/balance.js +15 -0
  60. package/dist/esm/resources/checkout.d.ts +12 -0
  61. package/dist/esm/resources/checkout.js +12 -0
  62. package/dist/esm/resources/customers.d.ts +27 -0
  63. package/dist/esm/resources/customers.js +52 -0
  64. package/dist/esm/resources/media.d.ts +24 -0
  65. package/dist/esm/resources/media.js +35 -0
  66. package/dist/esm/resources/payment-batches.d.ts +41 -0
  67. package/dist/esm/resources/payment-batches.js +72 -0
  68. package/dist/esm/resources/payment-methods.d.ts +49 -0
  69. package/dist/esm/resources/payment-methods.js +91 -0
  70. package/dist/esm/resources/rates.d.ts +9 -0
  71. package/dist/esm/resources/rates.js +9 -0
  72. package/dist/esm/resources/sme-registration.d.ts +24 -0
  73. package/dist/esm/resources/sme-registration.js +27 -0
  74. package/dist/esm/resources/transactions.d.ts +39 -0
  75. package/dist/esm/resources/transactions.js +63 -0
  76. package/dist/esm/resources/webhooks.d.ts +43 -0
  77. package/dist/esm/resources/webhooks.js +49 -0
  78. package/dist/esm/version.d.ts +1 -0
  79. package/dist/esm/version.js +2 -0
  80. package/package.json +52 -68
  81. package/dist/index.d.ts +0 -54
  82. package/dist/index.js +0 -54
  83. package/dist/version.d.ts +0 -8
  84. package/dist/version.js +0 -8
  85. package/skills/afriex-go-live/SKILL.md +0 -386
  86. package/skills/afriex-sdk/SKILL.md +0 -275
@@ -0,0 +1,39 @@
1
+ import type { APIPromise } from '../core/api-promise.js';
2
+ import type { PagePromise } from '../core/pagination.js';
3
+ import { APIResource } from '../core/resource.js';
4
+ import type { Body, Data, Item, Query, RequestOptions } from '../core/types.js';
5
+ import type { Transaction, TransactionStatus } from '../generated/openapi.js';
6
+ export type { Transaction, TransactionStatus };
7
+ export type TransactionCreateParams = Body<'createTransaction'>;
8
+ export type TransactionListParams = Query<'listTransactions'>;
9
+ export type TransactionQuoteParams = Query<'getTransactionQuote'>;
10
+ export type TransactionQuote = Data<'getTransactionQuote'>;
11
+ export type TransactionAuthorizeParams = Body<'authorizeTransaction'>;
12
+ export type TransactionSimulateParams = Body<'simulateTransaction'>;
13
+ export type TransactionAdvice = Data<'getTransactionAdvice'>;
14
+ export type PoolAccountProofParams = Body<'submitPoolAccountPaymentProof'>;
15
+ export declare class Transactions extends APIResource {
16
+ /**
17
+ * Creates a deposit, withdrawal or swap.
18
+ *
19
+ * `meta.idempotencyKey` makes this safe to send again, so the SDK retries it
20
+ * on timeouts and server errors with the same body. If a retry gets
21
+ * `409 DUPLICATE_REQUEST` (a `ConflictError`), the first attempt went
22
+ * through: look the transaction up by `meta.reference`.
23
+ */
24
+ create(params: TransactionCreateParams, options?: RequestOptions): APIPromise<Data<'createTransaction'>>;
25
+ /** Lists transactions, newest first. Iterate the result with `for await` to walk every page. */
26
+ list(params?: TransactionListParams, options?: RequestOptions): PagePromise<Item<'listTransactions'>>;
27
+ /** Quotes a withdrawal: the rate, fee and amounts, without creating anything. */
28
+ quote(params: TransactionQuoteParams, options?: RequestOptions): APIPromise<TransactionQuote>;
29
+ /** Retrieves a transaction. */
30
+ get(transactionId: string, options?: RequestOptions): APIPromise<Data<'getTransaction'>>;
31
+ /** Authorizes a transaction that is waiting for it, for example with an OTP. */
32
+ authorize(transactionId: string, params: TransactionAuthorizeParams, options?: RequestOptions): APIPromise<Data<'authorizeTransaction'>>;
33
+ /** Sandbox only: moves a transaction to the outcome you choose. */
34
+ simulate(transactionId: string, params: TransactionSimulateParams, options?: RequestOptions): APIPromise<Data<'simulateTransaction'>>;
35
+ /** Retrieves the settlement advice for a completed transaction. */
36
+ getAdvice(transactionId: string, options?: RequestOptions): APIPromise<TransactionAdvice>;
37
+ /** Submits proof of a payment into a pool account. */
38
+ submitPoolAccountProof(params: PoolAccountProofParams, options?: RequestOptions): APIPromise<Data<'submitPoolAccountPaymentProof'>>;
39
+ }
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Transactions = void 0;
4
+ const resource_js_1 = require("../core/resource.js");
5
+ class Transactions extends resource_js_1.APIResource {
6
+ /**
7
+ * Creates a deposit, withdrawal or swap.
8
+ *
9
+ * `meta.idempotencyKey` makes this safe to send again, so the SDK retries it
10
+ * on timeouts and server errors with the same body. If a retry gets
11
+ * `409 DUPLICATE_REQUEST` (a `ConflictError`), the first attempt went
12
+ * through: look the transaction up by `meta.reference`.
13
+ */
14
+ create(params, options) {
15
+ return this._client
16
+ .request('post', '/api/v1/transaction', {
17
+ body: params,
18
+ idempotent: true,
19
+ options,
20
+ })
21
+ ._thenUnwrap(resource_js_1.data);
22
+ }
23
+ /** Lists transactions, newest first. Iterate the result with `for await` to walk every page. */
24
+ list(params = {}, options) {
25
+ return this._client.getPage('/api/v1/transaction', params, options);
26
+ }
27
+ /** Quotes a withdrawal: the rate, fee and amounts, without creating anything. */
28
+ quote(params, options) {
29
+ return this._client
30
+ .request('get', '/api/v1/transaction/quote', { query: params, options })
31
+ ._thenUnwrap(resource_js_1.data);
32
+ }
33
+ /** Retrieves a transaction. */
34
+ get(transactionId, options) {
35
+ return this._client
36
+ .request('get', (0, resource_js_1.path) `/api/v1/transaction/${transactionId}`, { options })
37
+ ._thenUnwrap(resource_js_1.data);
38
+ }
39
+ /** Authorizes a transaction that is waiting for it, for example with an OTP. */
40
+ authorize(transactionId, params, options) {
41
+ return this._client
42
+ .request('post', (0, resource_js_1.path) `/api/v1/transaction/${transactionId}/authorize`, { body: params, options })
43
+ ._thenUnwrap(resource_js_1.data);
44
+ }
45
+ /** Sandbox only: moves a transaction to the outcome you choose. */
46
+ simulate(transactionId, params, options) {
47
+ return this._client
48
+ .request('post', (0, resource_js_1.path) `/api/v1/transaction/${transactionId}/simulate`, { body: params, options })
49
+ ._thenUnwrap(resource_js_1.data);
50
+ }
51
+ /** Retrieves the settlement advice for a completed transaction. */
52
+ getAdvice(transactionId, options) {
53
+ return this._client
54
+ .request('get', (0, resource_js_1.path) `/api/v1/transaction/${transactionId}/advice`, { options })
55
+ ._thenUnwrap(resource_js_1.data);
56
+ }
57
+ /** Submits proof of a payment into a pool account. */
58
+ submitPoolAccountProof(params, options) {
59
+ return this._client
60
+ .request('post', '/api/v1/transaction/pool-account', {
61
+ body: params,
62
+ options,
63
+ })
64
+ ._thenUnwrap(resource_js_1.data);
65
+ }
66
+ }
67
+ exports.Transactions = Transactions;
@@ -0,0 +1,43 @@
1
+ import type { Afriex } from '../client.js';
2
+ import type { APIPromise } from '../core/api-promise.js';
3
+ import { APIResource } from '../core/resource.js';
4
+ import type { Body, Data, RequestOptions } from '../core/types.js';
5
+ import type { webhooks } from '../generated/openapi.js';
6
+ /** The header that carries a webhook's signature. */
7
+ export declare const WEBHOOK_SIGNATURE_HEADER = "x-webhook-signature";
8
+ type WebhookBody<K extends keyof webhooks> = NonNullable<webhooks[K]['post']> extends {
9
+ requestBody: {
10
+ content: {
11
+ 'application/json': infer B;
12
+ };
13
+ };
14
+ } ? B : never;
15
+ /** Any event Afriex delivers. Switch on `event` to narrow it. */
16
+ export type WebhookEvent = {
17
+ [K in keyof webhooks]: WebhookBody<K>;
18
+ }[keyof webhooks];
19
+ export type WebhookEventType = WebhookEvent['event'];
20
+ export type CustomerWebhookEvent = WebhookBody<'customer.events'>;
21
+ export type PaymentMethodWebhookEvent = WebhookBody<'payment_method.events'>;
22
+ export type TransactionWebhookEvent = WebhookBody<'transaction.events'>;
23
+ export type PoolDepositRequestWebhookEvent = WebhookBody<'pool_deposit_request.events'>;
24
+ export type TriggerWebhookParams = Body<'triggerWebhook'>;
25
+ export type TriggerWebhookResult = Data<'triggerWebhook'>;
26
+ export declare class Webhooks extends APIResource {
27
+ #private;
28
+ constructor(client: Afriex, publicKey?: string);
29
+ /**
30
+ * Whether `signature` is Afriex's signature of `payload`. Pass the raw body
31
+ * exactly as received: a re-serialized object never matches.
32
+ */
33
+ verify(payload: string | Uint8Array, signature: string | string[] | null | undefined): boolean;
34
+ /**
35
+ * Verifies a webhook and returns its parsed event.
36
+ *
37
+ * @throws WebhookVerificationError when no public key is configured or the signature does not match.
38
+ */
39
+ verifyAndParse(payload: string | Uint8Array, signature: string | string[] | null | undefined): WebhookEvent;
40
+ /** Sandbox only: sends a test webhook for an event and entity to your configured URL. */
41
+ triggerTestWebhook(params: TriggerWebhookParams, options?: RequestOptions): APIPromise<TriggerWebhookResult>;
42
+ }
43
+ export {};
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Webhooks = exports.WEBHOOK_SIGNATURE_HEADER = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const errors_js_1 = require("../core/errors.js");
6
+ const resource_js_1 = require("../core/resource.js");
7
+ /** The header that carries a webhook's signature. */
8
+ exports.WEBHOOK_SIGNATURE_HEADER = 'x-webhook-signature';
9
+ class Webhooks extends resource_js_1.APIResource {
10
+ #publicKey;
11
+ constructor(client, publicKey) {
12
+ super(client);
13
+ this.#publicKey = publicKey;
14
+ }
15
+ /**
16
+ * Whether `signature` is Afriex's signature of `payload`. Pass the raw body
17
+ * exactly as received: a re-serialized object never matches.
18
+ */
19
+ verify(payload, signature) {
20
+ const header = Array.isArray(signature) ? signature[0] : signature;
21
+ if (!this.#publicKey || !header || payload.length === 0)
22
+ return false;
23
+ try {
24
+ // ECDSA P-384 over SHA-256, DER-encoded then base64-encoded.
25
+ return (0, node_crypto_1.createVerify)('SHA256').update(payload).verify(this.#publicKey, header, 'base64');
26
+ }
27
+ catch {
28
+ return false;
29
+ }
30
+ }
31
+ /**
32
+ * Verifies a webhook and returns its parsed event.
33
+ *
34
+ * @throws WebhookVerificationError when no public key is configured or the signature does not match.
35
+ */
36
+ verifyAndParse(payload, signature) {
37
+ if (!this.#publicKey) {
38
+ throw new errors_js_1.WebhookVerificationError('No webhook public key. Pass `webhookPublicKey` when you create the client.');
39
+ }
40
+ if (!this.verify(payload, signature)) {
41
+ throw new errors_js_1.WebhookVerificationError('The webhook signature does not match its body.');
42
+ }
43
+ const text = typeof payload === 'string' ? payload : new TextDecoder().decode(payload);
44
+ return JSON.parse(text);
45
+ }
46
+ /** Sandbox only: sends a test webhook for an event and entity to your configured URL. */
47
+ triggerTestWebhook(params, options) {
48
+ return this._client
49
+ .request('post', '/api/v1/webhooks/trigger', { body: params, options })
50
+ ._thenUnwrap(resource_js_1.data);
51
+ }
52
+ }
53
+ exports.Webhooks = Webhooks;
@@ -0,0 +1 @@
1
+ export declare const VERSION = "6.0.0";
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VERSION = void 0;
4
+ // Kept equal to package.json's version by a unit test.
5
+ exports.VERSION = '6.0.0';
@@ -0,0 +1,110 @@
1
+ import { APIPromise } from './core/api-promise.js';
2
+ import { PagePromise } from './core/pagination.js';
3
+ import type { RequestOptions } from './core/types.js';
4
+ import { Balance } from './resources/balance.js';
5
+ import { Checkout } from './resources/checkout.js';
6
+ import { Customers } from './resources/customers.js';
7
+ import { Media } from './resources/media.js';
8
+ import { PaymentBatches } from './resources/payment-batches.js';
9
+ import { PaymentMethods } from './resources/payment-methods.js';
10
+ import { Rates } from './resources/rates.js';
11
+ import { SmeRegistration } from './resources/sme-registration.js';
12
+ import { Transactions } from './resources/transactions.js';
13
+ import { Webhooks } from './resources/webhooks.js';
14
+ /** The base URL of each environment. `staging` is the sandbox's older name. */
15
+ export declare const ENVIRONMENTS: {
16
+ readonly sandbox: "https://sandbox.api.afriex.com";
17
+ readonly staging: "https://sandbox.api.afriex.com";
18
+ readonly production: "https://api.afriex.com";
19
+ };
20
+ export type Environment = keyof typeof ENVIRONMENTS;
21
+ /**
22
+ * The API version this release of the SDK was built against, sent as
23
+ * `x-api-version`. Each SDK release targets one API version.
24
+ */
25
+ export declare const API_VERSION = "2026-05-18";
26
+ /** What `signRequest` receives: the request exactly as it will be sent. */
27
+ export interface SignableRequest {
28
+ method: string;
29
+ url: string;
30
+ body: string;
31
+ }
32
+ export interface ClientOptions {
33
+ /** Your Afriex API key. Defaults to the `AFRIEX_API_KEY` environment variable. */
34
+ apiKey?: string;
35
+ /**
36
+ * `'sandbox'` or `'production'`. Defaults to `'production'`, so set it
37
+ * explicitly while you integrate. `'staging'` is accepted as the sandbox.
38
+ */
39
+ environment?: Environment;
40
+ /** Overrides the base URL that `environment` selects. */
41
+ baseURL?: string;
42
+ /** Sent as `x-api-version`. Defaults to `API_VERSION`. */
43
+ apiVersion?: string;
44
+ /** Milliseconds to wait for each attempt. Defaults to 30 seconds. */
45
+ timeout?: number;
46
+ /**
47
+ * How many times to retry a failed request. Defaults to 2. See the README
48
+ * for which requests are retried.
49
+ */
50
+ maxRetries?: number;
51
+ /**
52
+ * Returns the `x-api-signature` for a request, if your business has request
53
+ * signing enabled.
54
+ */
55
+ signRequest?: (request: SignableRequest) => string | undefined | Promise<string | undefined>;
56
+ /** Afriex's public key for webhook signatures, from **Developer → Webhook** in the dashboard. */
57
+ webhookPublicKey?: string;
58
+ /** Headers sent with every request. */
59
+ defaultHeaders?: Record<string, string>;
60
+ /** A `fetch` implementation to use instead of the global one. */
61
+ fetch?: typeof fetch;
62
+ /**
63
+ * The SDK refuses to run in a browser, where your API key would be visible
64
+ * to anyone. Set this only if you understand that.
65
+ */
66
+ dangerouslyAllowBrowser?: boolean;
67
+ }
68
+ type Method = 'get' | 'post' | 'patch' | 'delete';
69
+ export interface APIRequestInit {
70
+ query?: object;
71
+ body?: unknown;
72
+ /**
73
+ * Safe to send twice: retried on timeouts and server errors as well as on
74
+ * 429. True for every GET; set it for a POST whose body carries an
75
+ * idempotency key.
76
+ */
77
+ idempotent?: boolean;
78
+ options?: RequestOptions;
79
+ }
80
+ export declare class Afriex {
81
+ #private;
82
+ readonly customers: Customers;
83
+ readonly transactions: Transactions;
84
+ readonly paymentMethods: PaymentMethods;
85
+ readonly paymentBatches: PaymentBatches;
86
+ readonly balance: Balance;
87
+ readonly rates: Rates;
88
+ readonly checkout: Checkout;
89
+ readonly media: Media;
90
+ readonly smeRegistration: SmeRegistration;
91
+ readonly webhooks: Webhooks;
92
+ readonly baseURL: string;
93
+ readonly apiVersion: string;
94
+ readonly timeout: number;
95
+ readonly maxRetries: number;
96
+ constructor(options?: ClientOptions);
97
+ /**
98
+ * Sends a request and returns the parsed JSON body. The resource methods
99
+ * call this; use it yourself only for an endpoint they don't cover yet.
100
+ */
101
+ request<T>(method: Method, path: string, init?: APIRequestInit): APIPromise<T>;
102
+ /** @internal Fetches one page of a list endpoint. */
103
+ getPage<Item>(path: string, query?: {
104
+ page?: number;
105
+ limit?: number;
106
+ } & object, options?: RequestOptions): PagePromise<Item>;
107
+ /** @internal Sends a request with the configured `fetch`, without the API key. For presigned upload URLs. */
108
+ fetchUnauthenticated(url: string, init: RequestInit): Promise<Response>;
109
+ }
110
+ export {};
@@ -0,0 +1,227 @@
1
+ import { APIPromise } from './core/api-promise.js';
2
+ import { AfriexError, APIConnectionError, APIConnectionTimeoutError, APIError, APIUserAbortError, } from './core/errors.js';
3
+ import { PagePromise } from './core/pagination.js';
4
+ import { Balance } from './resources/balance.js';
5
+ import { Checkout } from './resources/checkout.js';
6
+ import { Customers } from './resources/customers.js';
7
+ import { Media } from './resources/media.js';
8
+ import { PaymentBatches } from './resources/payment-batches.js';
9
+ import { PaymentMethods } from './resources/payment-methods.js';
10
+ import { Rates } from './resources/rates.js';
11
+ import { SmeRegistration } from './resources/sme-registration.js';
12
+ import { Transactions } from './resources/transactions.js';
13
+ import { Webhooks } from './resources/webhooks.js';
14
+ import { VERSION } from './version.js';
15
+ /** The base URL of each environment. `staging` is the sandbox's older name. */
16
+ export const ENVIRONMENTS = {
17
+ sandbox: 'https://sandbox.api.afriex.com',
18
+ staging: 'https://sandbox.api.afriex.com',
19
+ production: 'https://api.afriex.com',
20
+ };
21
+ /**
22
+ * The API version this release of the SDK was built against, sent as
23
+ * `x-api-version`. Each SDK release targets one API version.
24
+ */
25
+ export const API_VERSION = '2026-05-18';
26
+ const DEFAULT_TIMEOUT = 30_000;
27
+ const DEFAULT_MAX_RETRIES = 2;
28
+ const MAX_RETRY_AFTER_SECONDS = 60;
29
+ export class Afriex {
30
+ customers = new Customers(this);
31
+ transactions = new Transactions(this);
32
+ paymentMethods = new PaymentMethods(this);
33
+ paymentBatches = new PaymentBatches(this);
34
+ balance = new Balance(this);
35
+ rates = new Rates(this);
36
+ checkout = new Checkout(this);
37
+ media = new Media(this);
38
+ smeRegistration = new SmeRegistration(this);
39
+ webhooks;
40
+ baseURL;
41
+ apiVersion;
42
+ timeout;
43
+ maxRetries;
44
+ #apiKey;
45
+ #signRequest;
46
+ #defaultHeaders;
47
+ #fetch;
48
+ constructor(options = {}) {
49
+ if (!options.dangerouslyAllowBrowser && isBrowser()) {
50
+ throw new AfriexError('The Afriex SDK must not run in a browser, where your API key would be visible to anyone. Call the API from your server.');
51
+ }
52
+ const apiKey = options.apiKey ?? readEnv('AFRIEX_API_KEY');
53
+ if (!apiKey) {
54
+ throw new AfriexError('Missing API key. Pass `apiKey` or set the AFRIEX_API_KEY environment variable.');
55
+ }
56
+ const environment = options.environment ?? 'production';
57
+ if (!(environment in ENVIRONMENTS)) {
58
+ throw new AfriexError(`Unknown environment "${environment}". Use "sandbox" or "production".`);
59
+ }
60
+ this.#apiKey = apiKey;
61
+ this.baseURL = (options.baseURL ?? ENVIRONMENTS[environment]).replace(/\/+$/, '');
62
+ this.apiVersion = options.apiVersion ?? API_VERSION;
63
+ this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
64
+ this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
65
+ this.#signRequest = options.signRequest;
66
+ this.#defaultHeaders = options.defaultHeaders ?? {};
67
+ this.#fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
68
+ this.webhooks = new Webhooks(this, options.webhookPublicKey);
69
+ }
70
+ /**
71
+ * Sends a request and returns the parsed JSON body. The resource methods
72
+ * call this; use it yourself only for an endpoint they don't cover yet.
73
+ */
74
+ request(method, path, init = {}) {
75
+ return new APIPromise(this.#send(method, path, init), (raw) => raw.body);
76
+ }
77
+ /** @internal Fetches one page of a list endpoint. */
78
+ getPage(path, query = {}, options) {
79
+ const fetchPage = (page, limit) => this.getPage(path, { ...query, page, limit }, options);
80
+ return new PagePromise(this.#send('get', path, { query, options }), query, fetchPage);
81
+ }
82
+ /** @internal Sends a request with the configured `fetch`, without the API key. For presigned upload URLs. */
83
+ fetchUnauthenticated(url, init) {
84
+ return this.#fetch(url, init);
85
+ }
86
+ async #send(method, path, init) {
87
+ const url = this.#buildURL(path, init.query);
88
+ const body = init.body === undefined ? undefined : JSON.stringify(init.body);
89
+ const headers = await this.#buildHeaders(method, url, body, init.options);
90
+ const maxRetries = init.options?.maxRetries ?? this.maxRetries;
91
+ const timeout = init.options?.timeout ?? this.timeout;
92
+ const signal = init.options?.signal;
93
+ // A request that can't be repeated safely is retried only on 429, which
94
+ // the API sends before it does anything.
95
+ const safeToRepeat = method === 'get' || init.idempotent === true;
96
+ for (let attempt = 0;; attempt++) {
97
+ let raw;
98
+ try {
99
+ raw = await this.#attempt(url, { method: method.toUpperCase(), headers, body }, timeout, signal);
100
+ }
101
+ catch (error) {
102
+ if (error instanceof APIUserAbortError || !safeToRepeat || attempt >= maxRetries) {
103
+ throw error;
104
+ }
105
+ await sleep(backoff(attempt));
106
+ continue;
107
+ }
108
+ const { response } = raw;
109
+ if (response.ok)
110
+ return raw;
111
+ const retryable = response.status === 429 ||
112
+ (safeToRepeat && (response.status === 408 || response.status >= 500));
113
+ if (retryable && attempt < maxRetries) {
114
+ await sleep(retryAfter(response.headers) ?? backoff(attempt));
115
+ continue;
116
+ }
117
+ throw APIError.from(response.status, raw.body, response.headers);
118
+ }
119
+ }
120
+ async #attempt(url, init, timeout, signal) {
121
+ if (signal?.aborted)
122
+ throw new APIUserAbortError();
123
+ const controller = new AbortController();
124
+ let timedOut = false;
125
+ const timer = setTimeout(() => {
126
+ timedOut = true;
127
+ controller.abort();
128
+ }, timeout);
129
+ const onAbort = () => controller.abort();
130
+ signal?.addEventListener('abort', onAbort, { once: true });
131
+ try {
132
+ const response = await this.#fetch(url, { ...init, signal: controller.signal });
133
+ return { response, body: await parseBody(response) };
134
+ }
135
+ catch (error) {
136
+ if (signal?.aborted)
137
+ throw new APIUserAbortError();
138
+ if (timedOut)
139
+ throw new APIConnectionTimeoutError();
140
+ throw new APIConnectionError(undefined, { cause: error });
141
+ }
142
+ finally {
143
+ clearTimeout(timer);
144
+ signal?.removeEventListener('abort', onAbort);
145
+ }
146
+ }
147
+ #buildURL(path, query) {
148
+ const url = new URL(this.baseURL + path);
149
+ for (const [key, value] of Object.entries(query ?? {})) {
150
+ if (value === undefined || value === null)
151
+ continue;
152
+ // The API takes list filters as comma-separated values (form style, explode=false).
153
+ url.searchParams.set(key, Array.isArray(value) ? value.join(',') : String(value));
154
+ }
155
+ return url.toString();
156
+ }
157
+ async #buildHeaders(method, url, body, options) {
158
+ const headers = new Headers({
159
+ Accept: 'application/json',
160
+ 'User-Agent': `afriex-node/${VERSION}`,
161
+ 'x-api-key': this.#apiKey,
162
+ 'x-api-version': this.apiVersion,
163
+ ...this.#defaultHeaders,
164
+ });
165
+ if (body !== undefined)
166
+ headers.set('Content-Type', 'application/json');
167
+ if (this.#signRequest) {
168
+ const signature = await this.#signRequest({
169
+ method: method.toUpperCase(),
170
+ url,
171
+ body: body ?? '',
172
+ });
173
+ if (signature)
174
+ headers.set('x-api-signature', signature);
175
+ }
176
+ for (const [name, value] of Object.entries(options?.headers ?? {})) {
177
+ if (value === null || value === undefined)
178
+ headers.delete(name);
179
+ else
180
+ headers.set(name, value);
181
+ }
182
+ return headers;
183
+ }
184
+ }
185
+ async function parseBody(response) {
186
+ const text = await response.text();
187
+ if (!text)
188
+ return undefined;
189
+ const contentType = response.headers.get('content-type') ?? '';
190
+ if (contentType.includes('json')) {
191
+ try {
192
+ return JSON.parse(text);
193
+ }
194
+ catch {
195
+ return text;
196
+ }
197
+ }
198
+ return text;
199
+ }
200
+ /** 0.5s doubling per attempt, capped at 8s, with up to 25% jitter. */
201
+ function backoff(attempt) {
202
+ const delay = Math.min(0.5 * 2 ** attempt, 8);
203
+ return delay * (1 - Math.random() * 0.25) * 1000;
204
+ }
205
+ /** The server's `Retry-After`, in milliseconds, when it is a sensible value. */
206
+ function retryAfter(headers) {
207
+ const value = headers.get('retry-after');
208
+ if (!value)
209
+ return undefined;
210
+ let seconds = Number(value);
211
+ if (Number.isNaN(seconds))
212
+ seconds = (Date.parse(value) - Date.now()) / 1000;
213
+ if (!Number.isFinite(seconds) || seconds < 0 || seconds > MAX_RETRY_AFTER_SECONDS) {
214
+ return undefined;
215
+ }
216
+ return seconds * 1000;
217
+ }
218
+ function sleep(ms) {
219
+ return new Promise((resolve) => setTimeout(resolve, ms));
220
+ }
221
+ function readEnv(name) {
222
+ const value = globalThis.process?.env?.[name];
223
+ return value?.trim() || undefined;
224
+ }
225
+ function isBrowser() {
226
+ return 'window' in globalThis && 'document' in globalThis;
227
+ }
@@ -0,0 +1,25 @@
1
+ /** A response as the client received it, with its body already parsed. */
2
+ export interface RawResponse {
3
+ response: Response;
4
+ body: unknown;
5
+ }
6
+ /**
7
+ * The promise every SDK method returns. Await it for the parsed result, or call
8
+ * `withResponse()` to also get the HTTP response and its request id.
9
+ */
10
+ export declare class APIPromise<T> extends Promise<T> {
11
+ #private;
12
+ constructor(responsePromise: Promise<RawResponse>, parse: (raw: RawResponse) => T | Promise<T>);
13
+ static get [Symbol.species](): PromiseConstructor;
14
+ /** @internal Builds a promise of a value derived from this one's result. */
15
+ _thenUnwrap<U>(fn: (value: T) => U): APIPromise<U>;
16
+ /** The parsed result together with the HTTP response it came from. */
17
+ withResponse(): Promise<{
18
+ data: T;
19
+ response: Response;
20
+ requestId: string | undefined;
21
+ }>;
22
+ then<R1 = T, R2 = never>(onfulfilled?: ((value: T) => R1 | PromiseLike<R1>) | null, onrejected?: ((reason: unknown) => R2 | PromiseLike<R2>) | null): Promise<R1 | R2>;
23
+ catch<R = never>(onrejected?: ((reason: unknown) => R | PromiseLike<R>) | null): Promise<T | R>;
24
+ finally(onfinally?: (() => void) | null): Promise<T>;
25
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The promise every SDK method returns. Await it for the parsed result, or call
3
+ * `withResponse()` to also get the HTTP response and its request id.
4
+ */
5
+ export class APIPromise extends Promise {
6
+ #parsed;
7
+ #responsePromise;
8
+ #parse;
9
+ constructor(responsePromise, parse) {
10
+ // The value comes from `then` below, never from this executor.
11
+ super((resolve) => resolve(null));
12
+ this.#responsePromise = responsePromise;
13
+ this.#parse = parse;
14
+ }
15
+ // Lets `then`, `catch` and `finally` return plain promises.
16
+ static get [Symbol.species]() {
17
+ return Promise;
18
+ }
19
+ /** @internal Builds a promise of a value derived from this one's result. */
20
+ _thenUnwrap(fn) {
21
+ return new APIPromise(this.#responsePromise, async (raw) => fn(await this.#parse(raw)));
22
+ }
23
+ /** The parsed result together with the HTTP response it came from. */
24
+ async withResponse() {
25
+ const [data, raw] = await Promise.all([this.#value(), this.#responsePromise]);
26
+ return {
27
+ data,
28
+ response: raw.response,
29
+ requestId: raw.response.headers.get('x-request-id') ?? undefined,
30
+ };
31
+ }
32
+ #value() {
33
+ this.#parsed ??= this.#responsePromise.then((raw) => this.#parse(raw));
34
+ return this.#parsed;
35
+ }
36
+ then(onfulfilled, onrejected) {
37
+ return this.#value().then(onfulfilled, onrejected);
38
+ }
39
+ catch(onrejected) {
40
+ return this.#value().catch(onrejected);
41
+ }
42
+ finally(onfinally) {
43
+ return this.#value().finally(onfinally);
44
+ }
45
+ }