@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,231 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Afriex = exports.API_VERSION = exports.ENVIRONMENTS = void 0;
4
+ const api_promise_js_1 = require("./core/api-promise.js");
5
+ const errors_js_1 = require("./core/errors.js");
6
+ const pagination_js_1 = require("./core/pagination.js");
7
+ const balance_js_1 = require("./resources/balance.js");
8
+ const checkout_js_1 = require("./resources/checkout.js");
9
+ const customers_js_1 = require("./resources/customers.js");
10
+ const media_js_1 = require("./resources/media.js");
11
+ const payment_batches_js_1 = require("./resources/payment-batches.js");
12
+ const payment_methods_js_1 = require("./resources/payment-methods.js");
13
+ const rates_js_1 = require("./resources/rates.js");
14
+ const sme_registration_js_1 = require("./resources/sme-registration.js");
15
+ const transactions_js_1 = require("./resources/transactions.js");
16
+ const webhooks_js_1 = require("./resources/webhooks.js");
17
+ const version_js_1 = require("./version.js");
18
+ /** The base URL of each environment. `staging` is the sandbox's older name. */
19
+ exports.ENVIRONMENTS = {
20
+ sandbox: 'https://sandbox.api.afriex.com',
21
+ staging: 'https://sandbox.api.afriex.com',
22
+ production: 'https://api.afriex.com',
23
+ };
24
+ /**
25
+ * The API version this release of the SDK was built against, sent as
26
+ * `x-api-version`. Each SDK release targets one API version.
27
+ */
28
+ exports.API_VERSION = '2026-05-18';
29
+ const DEFAULT_TIMEOUT = 30_000;
30
+ const DEFAULT_MAX_RETRIES = 2;
31
+ const MAX_RETRY_AFTER_SECONDS = 60;
32
+ class Afriex {
33
+ customers = new customers_js_1.Customers(this);
34
+ transactions = new transactions_js_1.Transactions(this);
35
+ paymentMethods = new payment_methods_js_1.PaymentMethods(this);
36
+ paymentBatches = new payment_batches_js_1.PaymentBatches(this);
37
+ balance = new balance_js_1.Balance(this);
38
+ rates = new rates_js_1.Rates(this);
39
+ checkout = new checkout_js_1.Checkout(this);
40
+ media = new media_js_1.Media(this);
41
+ smeRegistration = new sme_registration_js_1.SmeRegistration(this);
42
+ webhooks;
43
+ baseURL;
44
+ apiVersion;
45
+ timeout;
46
+ maxRetries;
47
+ #apiKey;
48
+ #signRequest;
49
+ #defaultHeaders;
50
+ #fetch;
51
+ constructor(options = {}) {
52
+ if (!options.dangerouslyAllowBrowser && isBrowser()) {
53
+ throw new errors_js_1.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.');
54
+ }
55
+ const apiKey = options.apiKey ?? readEnv('AFRIEX_API_KEY');
56
+ if (!apiKey) {
57
+ throw new errors_js_1.AfriexError('Missing API key. Pass `apiKey` or set the AFRIEX_API_KEY environment variable.');
58
+ }
59
+ const environment = options.environment ?? 'production';
60
+ if (!(environment in exports.ENVIRONMENTS)) {
61
+ throw new errors_js_1.AfriexError(`Unknown environment "${environment}". Use "sandbox" or "production".`);
62
+ }
63
+ this.#apiKey = apiKey;
64
+ this.baseURL = (options.baseURL ?? exports.ENVIRONMENTS[environment]).replace(/\/+$/, '');
65
+ this.apiVersion = options.apiVersion ?? exports.API_VERSION;
66
+ this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
67
+ this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
68
+ this.#signRequest = options.signRequest;
69
+ this.#defaultHeaders = options.defaultHeaders ?? {};
70
+ this.#fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
71
+ this.webhooks = new webhooks_js_1.Webhooks(this, options.webhookPublicKey);
72
+ }
73
+ /**
74
+ * Sends a request and returns the parsed JSON body. The resource methods
75
+ * call this; use it yourself only for an endpoint they don't cover yet.
76
+ */
77
+ request(method, path, init = {}) {
78
+ return new api_promise_js_1.APIPromise(this.#send(method, path, init), (raw) => raw.body);
79
+ }
80
+ /** @internal Fetches one page of a list endpoint. */
81
+ getPage(path, query = {}, options) {
82
+ const fetchPage = (page, limit) => this.getPage(path, { ...query, page, limit }, options);
83
+ return new pagination_js_1.PagePromise(this.#send('get', path, { query, options }), query, fetchPage);
84
+ }
85
+ /** @internal Sends a request with the configured `fetch`, without the API key. For presigned upload URLs. */
86
+ fetchUnauthenticated(url, init) {
87
+ return this.#fetch(url, init);
88
+ }
89
+ async #send(method, path, init) {
90
+ const url = this.#buildURL(path, init.query);
91
+ const body = init.body === undefined ? undefined : JSON.stringify(init.body);
92
+ const headers = await this.#buildHeaders(method, url, body, init.options);
93
+ const maxRetries = init.options?.maxRetries ?? this.maxRetries;
94
+ const timeout = init.options?.timeout ?? this.timeout;
95
+ const signal = init.options?.signal;
96
+ // A request that can't be repeated safely is retried only on 429, which
97
+ // the API sends before it does anything.
98
+ const safeToRepeat = method === 'get' || init.idempotent === true;
99
+ for (let attempt = 0;; attempt++) {
100
+ let raw;
101
+ try {
102
+ raw = await this.#attempt(url, { method: method.toUpperCase(), headers, body }, timeout, signal);
103
+ }
104
+ catch (error) {
105
+ if (error instanceof errors_js_1.APIUserAbortError || !safeToRepeat || attempt >= maxRetries) {
106
+ throw error;
107
+ }
108
+ await sleep(backoff(attempt));
109
+ continue;
110
+ }
111
+ const { response } = raw;
112
+ if (response.ok)
113
+ return raw;
114
+ const retryable = response.status === 429 ||
115
+ (safeToRepeat && (response.status === 408 || response.status >= 500));
116
+ if (retryable && attempt < maxRetries) {
117
+ await sleep(retryAfter(response.headers) ?? backoff(attempt));
118
+ continue;
119
+ }
120
+ throw errors_js_1.APIError.from(response.status, raw.body, response.headers);
121
+ }
122
+ }
123
+ async #attempt(url, init, timeout, signal) {
124
+ if (signal?.aborted)
125
+ throw new errors_js_1.APIUserAbortError();
126
+ const controller = new AbortController();
127
+ let timedOut = false;
128
+ const timer = setTimeout(() => {
129
+ timedOut = true;
130
+ controller.abort();
131
+ }, timeout);
132
+ const onAbort = () => controller.abort();
133
+ signal?.addEventListener('abort', onAbort, { once: true });
134
+ try {
135
+ const response = await this.#fetch(url, { ...init, signal: controller.signal });
136
+ return { response, body: await parseBody(response) };
137
+ }
138
+ catch (error) {
139
+ if (signal?.aborted)
140
+ throw new errors_js_1.APIUserAbortError();
141
+ if (timedOut)
142
+ throw new errors_js_1.APIConnectionTimeoutError();
143
+ throw new errors_js_1.APIConnectionError(undefined, { cause: error });
144
+ }
145
+ finally {
146
+ clearTimeout(timer);
147
+ signal?.removeEventListener('abort', onAbort);
148
+ }
149
+ }
150
+ #buildURL(path, query) {
151
+ const url = new URL(this.baseURL + path);
152
+ for (const [key, value] of Object.entries(query ?? {})) {
153
+ if (value === undefined || value === null)
154
+ continue;
155
+ // The API takes list filters as comma-separated values (form style, explode=false).
156
+ url.searchParams.set(key, Array.isArray(value) ? value.join(',') : String(value));
157
+ }
158
+ return url.toString();
159
+ }
160
+ async #buildHeaders(method, url, body, options) {
161
+ const headers = new Headers({
162
+ Accept: 'application/json',
163
+ 'User-Agent': `afriex-node/${version_js_1.VERSION}`,
164
+ 'x-api-key': this.#apiKey,
165
+ 'x-api-version': this.apiVersion,
166
+ ...this.#defaultHeaders,
167
+ });
168
+ if (body !== undefined)
169
+ headers.set('Content-Type', 'application/json');
170
+ if (this.#signRequest) {
171
+ const signature = await this.#signRequest({
172
+ method: method.toUpperCase(),
173
+ url,
174
+ body: body ?? '',
175
+ });
176
+ if (signature)
177
+ headers.set('x-api-signature', signature);
178
+ }
179
+ for (const [name, value] of Object.entries(options?.headers ?? {})) {
180
+ if (value === null || value === undefined)
181
+ headers.delete(name);
182
+ else
183
+ headers.set(name, value);
184
+ }
185
+ return headers;
186
+ }
187
+ }
188
+ exports.Afriex = Afriex;
189
+ async function parseBody(response) {
190
+ const text = await response.text();
191
+ if (!text)
192
+ return undefined;
193
+ const contentType = response.headers.get('content-type') ?? '';
194
+ if (contentType.includes('json')) {
195
+ try {
196
+ return JSON.parse(text);
197
+ }
198
+ catch {
199
+ return text;
200
+ }
201
+ }
202
+ return text;
203
+ }
204
+ /** 0.5s doubling per attempt, capped at 8s, with up to 25% jitter. */
205
+ function backoff(attempt) {
206
+ const delay = Math.min(0.5 * 2 ** attempt, 8);
207
+ return delay * (1 - Math.random() * 0.25) * 1000;
208
+ }
209
+ /** The server's `Retry-After`, in milliseconds, when it is a sensible value. */
210
+ function retryAfter(headers) {
211
+ const value = headers.get('retry-after');
212
+ if (!value)
213
+ return undefined;
214
+ let seconds = Number(value);
215
+ if (Number.isNaN(seconds))
216
+ seconds = (Date.parse(value) - Date.now()) / 1000;
217
+ if (!Number.isFinite(seconds) || seconds < 0 || seconds > MAX_RETRY_AFTER_SECONDS) {
218
+ return undefined;
219
+ }
220
+ return seconds * 1000;
221
+ }
222
+ function sleep(ms) {
223
+ return new Promise((resolve) => setTimeout(resolve, ms));
224
+ }
225
+ function readEnv(name) {
226
+ const value = globalThis.process?.env?.[name];
227
+ return value?.trim() || undefined;
228
+ }
229
+ function isBrowser() {
230
+ return 'window' in globalThis && 'document' in globalThis;
231
+ }
@@ -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,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.APIPromise = void 0;
4
+ /**
5
+ * The promise every SDK method returns. Await it for the parsed result, or call
6
+ * `withResponse()` to also get the HTTP response and its request id.
7
+ */
8
+ class APIPromise extends Promise {
9
+ #parsed;
10
+ #responsePromise;
11
+ #parse;
12
+ constructor(responsePromise, parse) {
13
+ // The value comes from `then` below, never from this executor.
14
+ super((resolve) => resolve(null));
15
+ this.#responsePromise = responsePromise;
16
+ this.#parse = parse;
17
+ }
18
+ // Lets `then`, `catch` and `finally` return plain promises.
19
+ static get [Symbol.species]() {
20
+ return Promise;
21
+ }
22
+ /** @internal Builds a promise of a value derived from this one's result. */
23
+ _thenUnwrap(fn) {
24
+ return new APIPromise(this.#responsePromise, async (raw) => fn(await this.#parse(raw)));
25
+ }
26
+ /** The parsed result together with the HTTP response it came from. */
27
+ async withResponse() {
28
+ const [data, raw] = await Promise.all([this.#value(), this.#responsePromise]);
29
+ return {
30
+ data,
31
+ response: raw.response,
32
+ requestId: raw.response.headers.get('x-request-id') ?? undefined,
33
+ };
34
+ }
35
+ #value() {
36
+ this.#parsed ??= this.#responsePromise.then((raw) => this.#parse(raw));
37
+ return this.#parsed;
38
+ }
39
+ then(onfulfilled, onrejected) {
40
+ return this.#value().then(onfulfilled, onrejected);
41
+ }
42
+ catch(onrejected) {
43
+ return this.#value().catch(onrejected);
44
+ }
45
+ finally(onfinally) {
46
+ return this.#value().finally(onfinally);
47
+ }
48
+ }
49
+ exports.APIPromise = APIPromise;
@@ -0,0 +1,78 @@
1
+ import type { ErrorResponse } from '../generated/openapi.js';
2
+ /** Base class for every error the SDK throws. */
3
+ export declare class AfriexError extends Error {
4
+ constructor(message: string, options?: {
5
+ cause?: unknown;
6
+ });
7
+ }
8
+ /**
9
+ * The API answered with a status outside 2xx. Catch a subclass to handle one
10
+ * status, or this class to handle any of them.
11
+ */
12
+ export declare class APIError extends AfriexError {
13
+ /** The HTTP status. */
14
+ readonly status: number;
15
+ /** Machine-readable error code from the body, such as `DUPLICATE_REQUEST`. */
16
+ readonly code: string | undefined;
17
+ /** The `details` object from the body, when the API sent one. */
18
+ readonly details: ErrorResponse['details'] | undefined;
19
+ /** The response body as received, parsed when it was JSON. */
20
+ readonly body: unknown;
21
+ readonly headers: Headers;
22
+ /** The `x-request-id` header. Quote it when you contact support. */
23
+ readonly requestId: string | undefined;
24
+ constructor(status: number, body: unknown, headers: Headers);
25
+ /** Builds the subclass that matches the status. */
26
+ static from(status: number, body: unknown, headers: Headers): APIError;
27
+ private static messageFrom;
28
+ }
29
+ /** 400: the request was malformed. Nothing was done. */
30
+ export declare class BadRequestError extends APIError {
31
+ }
32
+ /**
33
+ * 401: the API key is missing, wrong, revoked, or lacks the permission this
34
+ * endpoint needs. The API does not say which.
35
+ */
36
+ export declare class AuthenticationError extends APIError {
37
+ }
38
+ /** 403: the request is not allowed, for example from an address outside your IP allowlist. */
39
+ export declare class PermissionDeniedError extends APIError {
40
+ }
41
+ /** 404: the resource does not exist, or belongs to another business. */
42
+ export declare class NotFoundError extends APIError {
43
+ }
44
+ /**
45
+ * 409: for a transaction, `DUPLICATE_REQUEST` means its idempotency key or
46
+ * reference is already taken. Look the transaction up by its reference rather
47
+ * than sending it again.
48
+ */
49
+ export declare class ConflictError extends APIError {
50
+ }
51
+ /** 422: the request was well formed but failed validation. Nothing was done. */
52
+ export declare class UnprocessableEntityError extends APIError {
53
+ }
54
+ /** 429: the request was not processed. Retry after `retryAfter` seconds. */
55
+ export declare class RateLimitError extends APIError {
56
+ /** Seconds to wait, from the `Retry-After` header. */
57
+ get retryAfter(): number | undefined;
58
+ }
59
+ /** 500 or above. */
60
+ export declare class InternalServerError extends APIError {
61
+ }
62
+ /** No response arrived: DNS, TLS, refused or reset connection. */
63
+ export declare class APIConnectionError extends AfriexError {
64
+ constructor(message?: string, options?: {
65
+ cause?: unknown;
66
+ });
67
+ }
68
+ /** No response arrived within the timeout. */
69
+ export declare class APIConnectionTimeoutError extends APIConnectionError {
70
+ constructor(message?: string);
71
+ }
72
+ /** The request was cancelled through the `signal` you passed. */
73
+ export declare class APIUserAbortError extends AfriexError {
74
+ constructor(message?: string);
75
+ }
76
+ /** A webhook's signature did not match its body, or no public key was configured. */
77
+ export declare class WebhookVerificationError extends AfriexError {
78
+ }
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebhookVerificationError = exports.APIUserAbortError = exports.APIConnectionTimeoutError = exports.APIConnectionError = exports.InternalServerError = exports.RateLimitError = exports.UnprocessableEntityError = exports.ConflictError = exports.NotFoundError = exports.PermissionDeniedError = exports.AuthenticationError = exports.BadRequestError = exports.APIError = exports.AfriexError = void 0;
4
+ /** Base class for every error the SDK throws. */
5
+ class AfriexError extends Error {
6
+ constructor(message, options) {
7
+ super(message, options);
8
+ this.name = new.target.name;
9
+ }
10
+ }
11
+ exports.AfriexError = AfriexError;
12
+ /**
13
+ * The API answered with a status outside 2xx. Catch a subclass to handle one
14
+ * status, or this class to handle any of them.
15
+ */
16
+ class APIError extends AfriexError {
17
+ /** The HTTP status. */
18
+ status;
19
+ /** Machine-readable error code from the body, such as `DUPLICATE_REQUEST`. */
20
+ code;
21
+ /** The `details` object from the body, when the API sent one. */
22
+ details;
23
+ /** The response body as received, parsed when it was JSON. */
24
+ body;
25
+ headers;
26
+ /** The `x-request-id` header. Quote it when you contact support. */
27
+ requestId;
28
+ constructor(status, body, headers) {
29
+ super(APIError.messageFrom(status, body));
30
+ this.status = status;
31
+ this.body = body;
32
+ this.headers = headers;
33
+ this.requestId = headers.get('x-request-id') ?? undefined;
34
+ const parsed = isErrorResponse(body) ? body : undefined;
35
+ this.code = parsed?.code;
36
+ this.details = parsed?.details;
37
+ }
38
+ /** Builds the subclass that matches the status. */
39
+ static from(status, body, headers) {
40
+ if (status === 400)
41
+ return new BadRequestError(status, body, headers);
42
+ if (status === 401)
43
+ return new AuthenticationError(status, body, headers);
44
+ if (status === 403)
45
+ return new PermissionDeniedError(status, body, headers);
46
+ if (status === 404)
47
+ return new NotFoundError(status, body, headers);
48
+ if (status === 409)
49
+ return new ConflictError(status, body, headers);
50
+ if (status === 422) {
51
+ return new UnprocessableEntityError(status, body, headers);
52
+ }
53
+ if (status === 429)
54
+ return new RateLimitError(status, body, headers);
55
+ if (status >= 500)
56
+ return new InternalServerError(status, body, headers);
57
+ return new APIError(status, body, headers);
58
+ }
59
+ static messageFrom(status, body) {
60
+ if (isErrorResponse(body)) {
61
+ const message = body.details?.friendlyMessage ?? body.error ?? body.details?.errorMessage;
62
+ if (message) {
63
+ return body.code ? `${status} ${body.code}: ${message}` : `${status} ${message}`;
64
+ }
65
+ }
66
+ if (typeof body === 'string' && body)
67
+ return `${status} ${body}`;
68
+ return `${status} status code (no body)`;
69
+ }
70
+ }
71
+ exports.APIError = APIError;
72
+ /** 400: the request was malformed. Nothing was done. */
73
+ class BadRequestError extends APIError {
74
+ }
75
+ exports.BadRequestError = BadRequestError;
76
+ /**
77
+ * 401: the API key is missing, wrong, revoked, or lacks the permission this
78
+ * endpoint needs. The API does not say which.
79
+ */
80
+ class AuthenticationError extends APIError {
81
+ }
82
+ exports.AuthenticationError = AuthenticationError;
83
+ /** 403: the request is not allowed, for example from an address outside your IP allowlist. */
84
+ class PermissionDeniedError extends APIError {
85
+ }
86
+ exports.PermissionDeniedError = PermissionDeniedError;
87
+ /** 404: the resource does not exist, or belongs to another business. */
88
+ class NotFoundError extends APIError {
89
+ }
90
+ exports.NotFoundError = NotFoundError;
91
+ /**
92
+ * 409: for a transaction, `DUPLICATE_REQUEST` means its idempotency key or
93
+ * reference is already taken. Look the transaction up by its reference rather
94
+ * than sending it again.
95
+ */
96
+ class ConflictError extends APIError {
97
+ }
98
+ exports.ConflictError = ConflictError;
99
+ /** 422: the request was well formed but failed validation. Nothing was done. */
100
+ class UnprocessableEntityError extends APIError {
101
+ }
102
+ exports.UnprocessableEntityError = UnprocessableEntityError;
103
+ /** 429: the request was not processed. Retry after `retryAfter` seconds. */
104
+ class RateLimitError extends APIError {
105
+ /** Seconds to wait, from the `Retry-After` header. */
106
+ get retryAfter() {
107
+ const value = Number(this.headers.get('retry-after'));
108
+ return Number.isFinite(value) && value >= 0 ? value : undefined;
109
+ }
110
+ }
111
+ exports.RateLimitError = RateLimitError;
112
+ /** 500 or above. */
113
+ class InternalServerError extends APIError {
114
+ }
115
+ exports.InternalServerError = InternalServerError;
116
+ /** No response arrived: DNS, TLS, refused or reset connection. */
117
+ class APIConnectionError extends AfriexError {
118
+ constructor(message = 'Connection error.', options) {
119
+ super(message, options);
120
+ }
121
+ }
122
+ exports.APIConnectionError = APIConnectionError;
123
+ /** No response arrived within the timeout. */
124
+ class APIConnectionTimeoutError extends APIConnectionError {
125
+ constructor(message = 'Request timed out.') {
126
+ super(message);
127
+ }
128
+ }
129
+ exports.APIConnectionTimeoutError = APIConnectionTimeoutError;
130
+ /** The request was cancelled through the `signal` you passed. */
131
+ class APIUserAbortError extends AfriexError {
132
+ constructor(message = 'Request was aborted.') {
133
+ super(message);
134
+ }
135
+ }
136
+ exports.APIUserAbortError = APIUserAbortError;
137
+ /** A webhook's signature did not match its body, or no public key was configured. */
138
+ class WebhookVerificationError extends AfriexError {
139
+ }
140
+ exports.WebhookVerificationError = WebhookVerificationError;
141
+ function isErrorResponse(body) {
142
+ return typeof body === 'object' && body !== null;
143
+ }
@@ -0,0 +1,47 @@
1
+ import { APIPromise, type RawResponse } from './api-promise.js';
2
+ /** Fetches one page of a list, given the page number and size. */
3
+ export type PageFetcher<Item> = (page: number, limit: number) => PagePromise<Item>;
4
+ interface PageBody<Item> {
5
+ data: Item[];
6
+ total: number;
7
+ page: number;
8
+ }
9
+ /**
10
+ * One page of a list endpoint: `{data, total, page}` as the API returns it,
11
+ * plus helpers to fetch the pages after it. Iterate it with `for await` to
12
+ * walk every item on this page and the pages after it.
13
+ */
14
+ export declare class Page<Item> implements AsyncIterable<Item> {
15
+ #private;
16
+ /** The items on this page. */
17
+ readonly data: Item[];
18
+ /** The number of items that match your filters, across all pages. */
19
+ readonly total: number;
20
+ /** This page's number. The first page is `0`. */
21
+ readonly page: number;
22
+ /** The page size used to fetch the next page. */
23
+ readonly limit: number;
24
+ constructor(body: PageBody<Item>, requested: {
25
+ page?: number;
26
+ limit?: number;
27
+ }, fetchPage: PageFetcher<Item>);
28
+ /** Whether another page follows this one. The API has no cursor, so this compares against `total`. */
29
+ hasNextPage(): boolean;
30
+ /** Fetches the next page. Call `hasNextPage()` first. */
31
+ getNextPage(): Promise<Page<Item>>;
32
+ /** Yields this page, then each page after it. */
33
+ iterPages(): AsyncGenerator<Page<Item>>;
34
+ [Symbol.asyncIterator](): AsyncGenerator<Item>;
35
+ }
36
+ /**
37
+ * What a list method returns. Await it for the first `Page`, or iterate it
38
+ * with `for await` to walk every item across all pages.
39
+ */
40
+ export declare class PagePromise<Item> extends APIPromise<Page<Item>> implements AsyncIterable<Item> {
41
+ constructor(responsePromise: Promise<RawResponse>, requested: {
42
+ page?: number;
43
+ limit?: number;
44
+ }, fetchPage: PageFetcher<Item>);
45
+ [Symbol.asyncIterator](): AsyncGenerator<Item>;
46
+ }
47
+ export {};
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PagePromise = exports.Page = void 0;
4
+ const api_promise_js_1 = require("./api-promise.js");
5
+ const errors_js_1 = require("./errors.js");
6
+ /**
7
+ * One page of a list endpoint: `{data, total, page}` as the API returns it,
8
+ * plus helpers to fetch the pages after it. Iterate it with `for await` to
9
+ * walk every item on this page and the pages after it.
10
+ */
11
+ class Page {
12
+ /** The items on this page. */
13
+ data;
14
+ /** The number of items that match your filters, across all pages. */
15
+ total;
16
+ /** This page's number. The first page is `0`. */
17
+ page;
18
+ /** The page size used to fetch the next page. */
19
+ limit;
20
+ #fetchPage;
21
+ constructor(body, requested, fetchPage) {
22
+ this.data = body.data ?? [];
23
+ this.total = body.total ?? 0;
24
+ this.page = body.page ?? requested.page ?? 0;
25
+ // Without an explicit limit the API used its default, which a full page
26
+ // reveals. A short page is the last one whatever the limit was.
27
+ this.limit = requested.limit ?? Math.max(this.data.length, 1);
28
+ this.#fetchPage = fetchPage;
29
+ }
30
+ /** Whether another page follows this one. The API has no cursor, so this compares against `total`. */
31
+ hasNextPage() {
32
+ return this.data.length > 0 && (this.page + 1) * this.limit < this.total;
33
+ }
34
+ /** Fetches the next page. Call `hasNextPage()` first. */
35
+ getNextPage() {
36
+ if (!this.hasNextPage()) {
37
+ throw new errors_js_1.AfriexError('No next page. Check hasNextPage() before calling getNextPage().');
38
+ }
39
+ return this.#fetchPage(this.page + 1, this.limit);
40
+ }
41
+ /** Yields this page, then each page after it. */
42
+ async *iterPages() {
43
+ let current = this;
44
+ yield current;
45
+ while (current.hasNextPage()) {
46
+ current = await current.getNextPage();
47
+ yield current;
48
+ }
49
+ }
50
+ async *[Symbol.asyncIterator]() {
51
+ for await (const page of this.iterPages()) {
52
+ yield* page.data;
53
+ }
54
+ }
55
+ }
56
+ exports.Page = Page;
57
+ /**
58
+ * What a list method returns. Await it for the first `Page`, or iterate it
59
+ * with `for await` to walk every item across all pages.
60
+ */
61
+ class PagePromise extends api_promise_js_1.APIPromise {
62
+ constructor(responsePromise, requested, fetchPage) {
63
+ super(responsePromise, (raw) => new Page(raw.body, requested, fetchPage));
64
+ }
65
+ async *[Symbol.asyncIterator]() {
66
+ const page = await this;
67
+ yield* page;
68
+ }
69
+ }
70
+ exports.PagePromise = PagePromise;
@@ -0,0 +1,18 @@
1
+ import type { Afriex } from '../client.js';
2
+ export declare abstract class APIResource {
3
+ protected readonly _client: Afriex;
4
+ constructor(client: Afriex);
5
+ }
6
+ /**
7
+ * Builds a request path, encoding each interpolated value. An empty id would
8
+ * otherwise turn `GET /customer/{id}` into `GET /customer`, the list endpoint,
9
+ * so it throws instead.
10
+ */
11
+ export declare function path(strings: TemplateStringsArray, ...values: string[]): string;
12
+ /**
13
+ * Returns a response's `data`. The spec marks `data` optional on most
14
+ * responses, but a 2xx always carries it, so its type drops `undefined`.
15
+ */
16
+ export declare function data<T>(response: {
17
+ data?: T;
18
+ }): Exclude<T, undefined>;