getricky 0.0.0-stage → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +138 -2
- package/dist/cjs/client.d.ts +51 -0
- package/dist/cjs/client.js +124 -0
- package/dist/cjs/errors.d.ts +35 -0
- package/dist/cjs/errors.js +55 -0
- package/dist/cjs/generated/client/client.gen.d.ts +2 -0
- package/dist/cjs/generated/client/client.gen.js +220 -0
- package/dist/cjs/generated/client/index.d.ts +10 -0
- package/dist/cjs/generated/client/index.js +17 -0
- package/dist/cjs/generated/client/types.gen.d.ts +120 -0
- package/dist/cjs/generated/client/types.gen.js +3 -0
- package/dist/cjs/generated/client/utils.gen.d.ts +37 -0
- package/dist/cjs/generated/client/utils.gen.js +239 -0
- package/dist/cjs/generated/client.gen.d.ts +12 -0
- package/dist/cjs/generated/client.gen.js +6 -0
- package/dist/cjs/generated/core/auth.gen.d.ts +25 -0
- package/dist/cjs/generated/core/auth.gen.js +18 -0
- package/dist/cjs/generated/core/bodySerializer.gen.d.ts +25 -0
- package/dist/cjs/generated/core/bodySerializer.gen.js +60 -0
- package/dist/cjs/generated/core/params.gen.d.ts +43 -0
- package/dist/cjs/generated/core/params.gen.js +112 -0
- package/dist/cjs/generated/core/pathSerializer.gen.d.ts +33 -0
- package/dist/cjs/generated/core/pathSerializer.gen.js +115 -0
- package/dist/cjs/generated/core/queryKeySerializer.gen.d.ts +18 -0
- package/dist/cjs/generated/core/queryKeySerializer.gen.js +98 -0
- package/dist/cjs/generated/core/serverSentEvents.gen.d.ts +71 -0
- package/dist/cjs/generated/core/serverSentEvents.gen.js +135 -0
- package/dist/cjs/generated/core/types.gen.d.ts +83 -0
- package/dist/cjs/generated/core/types.gen.js +3 -0
- package/dist/cjs/generated/core/utils.gen.d.ts +19 -0
- package/dist/cjs/generated/core/utils.gen.js +93 -0
- package/dist/cjs/generated/idempotency.gen.d.ts +2 -0
- package/dist/cjs/generated/idempotency.gen.js +16 -0
- package/dist/cjs/generated/index.d.ts +2 -0
- package/dist/cjs/generated/index.js +6 -0
- package/dist/cjs/generated/sdk.gen.d.ts +259 -0
- package/dist/cjs/generated/sdk.gen.js +493 -0
- package/dist/cjs/generated/types.gen.d.ts +2557 -0
- package/dist/cjs/generated/types.gen.js +3 -0
- package/dist/cjs/index.d.ts +7 -0
- package/dist/cjs/index.js +12 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/webhooks.d.ts +25 -0
- package/dist/cjs/webhooks.js +59 -0
- package/dist/esm/client.d.ts +51 -0
- package/dist/esm/client.js +120 -0
- package/dist/esm/errors.d.ts +35 -0
- package/dist/esm/errors.js +49 -0
- package/dist/esm/generated/client/client.gen.d.ts +2 -0
- package/dist/esm/generated/client/client.gen.js +216 -0
- package/dist/esm/generated/client/index.d.ts +10 -0
- package/dist/esm/generated/client/index.js +6 -0
- package/dist/esm/generated/client/types.gen.d.ts +120 -0
- package/dist/esm/generated/client/types.gen.js +2 -0
- package/dist/esm/generated/client/utils.gen.d.ts +37 -0
- package/dist/esm/generated/client/utils.gen.js +228 -0
- package/dist/esm/generated/client.gen.d.ts +12 -0
- package/dist/esm/generated/client.gen.js +3 -0
- package/dist/esm/generated/core/auth.gen.d.ts +25 -0
- package/dist/esm/generated/core/auth.gen.js +14 -0
- package/dist/esm/generated/core/bodySerializer.gen.d.ts +25 -0
- package/dist/esm/generated/core/bodySerializer.gen.js +57 -0
- package/dist/esm/generated/core/params.gen.d.ts +43 -0
- package/dist/esm/generated/core/params.gen.js +109 -0
- package/dist/esm/generated/core/pathSerializer.gen.d.ts +33 -0
- package/dist/esm/generated/core/pathSerializer.gen.js +106 -0
- package/dist/esm/generated/core/queryKeySerializer.gen.d.ts +18 -0
- package/dist/esm/generated/core/queryKeySerializer.gen.js +92 -0
- package/dist/esm/generated/core/serverSentEvents.gen.d.ts +71 -0
- package/dist/esm/generated/core/serverSentEvents.gen.js +132 -0
- package/dist/esm/generated/core/types.gen.d.ts +83 -0
- package/dist/esm/generated/core/types.gen.js +2 -0
- package/dist/esm/generated/core/utils.gen.d.ts +19 -0
- package/dist/esm/generated/core/utils.gen.js +87 -0
- package/dist/esm/generated/idempotency.gen.d.ts +2 -0
- package/dist/esm/generated/idempotency.gen.js +13 -0
- package/dist/esm/generated/index.d.ts +2 -0
- package/dist/esm/generated/index.js +2 -0
- package/dist/esm/generated/sdk.gen.d.ts +259 -0
- package/dist/esm/generated/sdk.gen.js +489 -0
- package/dist/esm/generated/types.gen.d.ts +2557 -0
- package/dist/esm/generated/types.gen.js +2 -0
- package/dist/esm/index.d.ts +7 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/webhooks.d.ts +25 -0
- package/dist/esm/webhooks.js +56 -0
- package/package.json +64 -4
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { DEFAULT_BASE_URL, Ricky } from './client.js';
|
|
2
|
+
export type { Page, Paginator, RickyOptions } from './client.js';
|
|
3
|
+
export { RickyConnectionError, RickyError, WebhookVerificationError } from './errors.js';
|
|
4
|
+
export type { RickyErrorCode } from './errors.js';
|
|
5
|
+
export type * from './generated/types.gen.js';
|
|
6
|
+
export { verifyWebhook } from './webhooks.js';
|
|
7
|
+
export type { WebhookEvent, WebhookHeaders } from './webhooks.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.verifyWebhook = exports.WebhookVerificationError = exports.RickyError = exports.RickyConnectionError = exports.Ricky = exports.DEFAULT_BASE_URL = void 0;
|
|
4
|
+
var client_js_1 = require("./client.js");
|
|
5
|
+
Object.defineProperty(exports, "DEFAULT_BASE_URL", { enumerable: true, get: function () { return client_js_1.DEFAULT_BASE_URL; } });
|
|
6
|
+
Object.defineProperty(exports, "Ricky", { enumerable: true, get: function () { return client_js_1.Ricky; } });
|
|
7
|
+
var errors_js_1 = require("./errors.js");
|
|
8
|
+
Object.defineProperty(exports, "RickyConnectionError", { enumerable: true, get: function () { return errors_js_1.RickyConnectionError; } });
|
|
9
|
+
Object.defineProperty(exports, "RickyError", { enumerable: true, get: function () { return errors_js_1.RickyError; } });
|
|
10
|
+
Object.defineProperty(exports, "WebhookVerificationError", { enumerable: true, get: function () { return errors_js_1.WebhookVerificationError; } });
|
|
11
|
+
var webhooks_js_1 = require("./webhooks.js");
|
|
12
|
+
Object.defineProperty(exports, "verifyWebhook", { enumerable: true, get: function () { return webhooks_js_1.verifyWebhook; } });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { WebhookEvent as PublishedWebhookEvent } from './generated/types.gen.js';
|
|
2
|
+
/**
|
|
3
|
+
* The JSON Ricky posts to a webhook endpoint. `type` is open: new event types
|
|
4
|
+
* ship without a new API version, so handle the ones you know and ignore the
|
|
5
|
+
* rest.
|
|
6
|
+
*/
|
|
7
|
+
export type WebhookEvent = Omit<PublishedWebhookEvent, 'type' | 'data'> & {
|
|
8
|
+
type: PublishedWebhookEvent['type'] | (string & {});
|
|
9
|
+
data: PublishedWebhookEvent['data'] & Record<string, unknown>;
|
|
10
|
+
};
|
|
11
|
+
/** Request headers as `Headers`, or the plain object Node.js, Express and most frameworks give. */
|
|
12
|
+
export type WebhookHeaders = Headers | Record<string, string | string[] | undefined>;
|
|
13
|
+
/**
|
|
14
|
+
* Verifies a delivery and returns its event. Pass the raw request body exactly
|
|
15
|
+
* as received, before any JSON parsing.
|
|
16
|
+
*
|
|
17
|
+
* `X-Ricky-Signature` holds one `v1=<hex>` value, or several separated by
|
|
18
|
+
* commas while a signing secret is being rotated; the delivery is valid when
|
|
19
|
+
* any of them is the HMAC-SHA256 of `<X-Ricky-Timestamp>.<body>` with
|
|
20
|
+
* `secret`. Deliveries whose timestamp is more than `tolerance` seconds from
|
|
21
|
+
* now are rejected, which defeats replays.
|
|
22
|
+
*
|
|
23
|
+
* @throws {WebhookVerificationError} when the delivery must be rejected.
|
|
24
|
+
*/
|
|
25
|
+
export declare function verifyWebhook(payload: string | Uint8Array, headers: WebhookHeaders, secret: string, tolerance?: number): WebhookEvent;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.verifyWebhook = verifyWebhook;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const errors_js_1 = require("./errors.js");
|
|
6
|
+
/**
|
|
7
|
+
* Verifies a delivery and returns its event. Pass the raw request body exactly
|
|
8
|
+
* as received, before any JSON parsing.
|
|
9
|
+
*
|
|
10
|
+
* `X-Ricky-Signature` holds one `v1=<hex>` value, or several separated by
|
|
11
|
+
* commas while a signing secret is being rotated; the delivery is valid when
|
|
12
|
+
* any of them is the HMAC-SHA256 of `<X-Ricky-Timestamp>.<body>` with
|
|
13
|
+
* `secret`. Deliveries whose timestamp is more than `tolerance` seconds from
|
|
14
|
+
* now are rejected, which defeats replays.
|
|
15
|
+
*
|
|
16
|
+
* @throws {WebhookVerificationError} when the delivery must be rejected.
|
|
17
|
+
*/
|
|
18
|
+
function verifyWebhook(payload, headers, secret, tolerance = 300) {
|
|
19
|
+
if (typeof payload !== 'string' && !(payload instanceof Uint8Array)) {
|
|
20
|
+
throw new errors_js_1.WebhookVerificationError('Pass the raw request body, not parsed JSON.');
|
|
21
|
+
}
|
|
22
|
+
if (!secret)
|
|
23
|
+
throw new errors_js_1.WebhookVerificationError('The webhook signing secret is empty.');
|
|
24
|
+
const timestamp = header(headers, 'x-ricky-timestamp');
|
|
25
|
+
const signatures = header(headers, 'x-ricky-signature');
|
|
26
|
+
if (!timestamp || !signatures) {
|
|
27
|
+
throw new errors_js_1.WebhookVerificationError('Missing X-Ricky-Timestamp or X-Ricky-Signature header.');
|
|
28
|
+
}
|
|
29
|
+
if (!/^\d+$/.test(timestamp)) {
|
|
30
|
+
throw new errors_js_1.WebhookVerificationError('X-Ricky-Timestamp is not a Unix timestamp.');
|
|
31
|
+
}
|
|
32
|
+
if (Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp)) > tolerance) {
|
|
33
|
+
throw new errors_js_1.WebhookVerificationError('X-Ricky-Timestamp is outside the tolerance.');
|
|
34
|
+
}
|
|
35
|
+
const body = typeof payload === 'string' ? Buffer.from(payload, 'utf8') : Buffer.from(payload);
|
|
36
|
+
const expected = Buffer.from(`v1=${(0, node_crypto_1.createHmac)('sha256', secret).update(`${timestamp}.`).update(body).digest('hex')}`);
|
|
37
|
+
const matches = signatures
|
|
38
|
+
.split(',')
|
|
39
|
+
.map((candidate) => Buffer.from(candidate.trim()))
|
|
40
|
+
.filter((candidate) => candidate.length === expected.length)
|
|
41
|
+
.some((candidate) => (0, node_crypto_1.timingSafeEqual)(candidate, expected));
|
|
42
|
+
if (!matches)
|
|
43
|
+
throw new errors_js_1.WebhookVerificationError('No signature matches this payload.');
|
|
44
|
+
try {
|
|
45
|
+
return JSON.parse(body.toString('utf8'));
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
throw new errors_js_1.WebhookVerificationError('The payload is not JSON.');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function header(headers, name) {
|
|
52
|
+
if (headers instanceof Headers)
|
|
53
|
+
return headers.get(name) ?? undefined;
|
|
54
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
55
|
+
if (key.toLowerCase() === name)
|
|
56
|
+
return Array.isArray(value) ? value.join(',') : value;
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { RickyApi } from './generated/sdk.gen.js';
|
|
2
|
+
export declare const DEFAULT_BASE_URL = "https://getricky.ai/api/v1";
|
|
3
|
+
export type RickyOptions = {
|
|
4
|
+
/** Defaults to `https://getricky.ai/api/v1`. */
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
/** A fetch implementation; defaults to the global `fetch`. */
|
|
7
|
+
fetch?: typeof fetch;
|
|
8
|
+
/** Retries after the first attempt for retryable failures. Defaults to 2. */
|
|
9
|
+
maxRetries?: number;
|
|
10
|
+
};
|
|
11
|
+
/** One page of a list: `next_cursor` is null on the last page. */
|
|
12
|
+
export type Page<TItem> = {
|
|
13
|
+
data: TItem[];
|
|
14
|
+
next_cursor: string | null;
|
|
15
|
+
};
|
|
16
|
+
/** Any list operation of the client: it resolves to `{ data: [...], next_cursor }`. */
|
|
17
|
+
type ListOperation = (options: any) => Promise<unknown>;
|
|
18
|
+
/** The item type of a list operation. */
|
|
19
|
+
type ItemOf<TList extends ListOperation> = Awaited<ReturnType<TList>> extends infer TPage ? TPage extends {
|
|
20
|
+
data: (infer TItem)[];
|
|
21
|
+
} ? TItem : never : never;
|
|
22
|
+
/** Items of every page, in order; `pages()` yields whole pages instead. */
|
|
23
|
+
export type Paginator<TItem> = AsyncIterable<TItem> & {
|
|
24
|
+
pages(): AsyncIterable<Page<TItem>>;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* A client for one organization's API key. Every operation in the API is a
|
|
28
|
+
* method named after its `operationId`, taking `{ path, query, body, headers }`
|
|
29
|
+
* and resolving to the response body (`{ data }` or `{ data, next_cursor }`).
|
|
30
|
+
* Failures reject with `RickyError` or `RickyConnectionError`.
|
|
31
|
+
*
|
|
32
|
+
* Writes that accept an `Idempotency-Key` get a random one unless the caller
|
|
33
|
+
* passes `headers: { 'Idempotency-Key': … }`. Reads and keyed writes are
|
|
34
|
+
* retried on connection errors, 429 and 5xx, honoring `Retry-After`.
|
|
35
|
+
*/
|
|
36
|
+
export declare class Ricky extends RickyApi {
|
|
37
|
+
/**
|
|
38
|
+
* @param apiKey An organization API key (`rky_live_…`); defaults to the
|
|
39
|
+
* `RICKY_API_KEY` environment variable. Use it only from a server.
|
|
40
|
+
*/
|
|
41
|
+
constructor(apiKey?: string, options?: RickyOptions);
|
|
42
|
+
/**
|
|
43
|
+
* Walks a list operation page by page, passing each `next_cursor` back as
|
|
44
|
+
* `cursor`.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* for await (const contact of ricky.paginate(ricky.listContacts, { query: { tag_id } })) { … }
|
|
48
|
+
*/
|
|
49
|
+
paginate<TList extends ListOperation>(list: TList, options?: Parameters<TList>[0]): Paginator<ItemOf<TList>>;
|
|
50
|
+
}
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { RickyConnectionError, RickyError } from './errors.js';
|
|
3
|
+
import { createClient, createConfig } from './generated/client/index.js';
|
|
4
|
+
import { idempotentOperations } from './generated/idempotency.gen.js';
|
|
5
|
+
import { RickyApi } from './generated/sdk.gen.js';
|
|
6
|
+
export const DEFAULT_BASE_URL = 'https://getricky.ai/api/v1';
|
|
7
|
+
const RETRYABLE_STATUSES = new Set([429, 500, 502, 503, 504]);
|
|
8
|
+
const MAX_RETRY_AFTER_SECONDS = 60;
|
|
9
|
+
/**
|
|
10
|
+
* A client for one organization's API key. Every operation in the API is a
|
|
11
|
+
* method named after its `operationId`, taking `{ path, query, body, headers }`
|
|
12
|
+
* and resolving to the response body (`{ data }` or `{ data, next_cursor }`).
|
|
13
|
+
* Failures reject with `RickyError` or `RickyConnectionError`.
|
|
14
|
+
*
|
|
15
|
+
* Writes that accept an `Idempotency-Key` get a random one unless the caller
|
|
16
|
+
* passes `headers: { 'Idempotency-Key': … }`. Reads and keyed writes are
|
|
17
|
+
* retried on connection errors, 429 and 5xx, honoring `Retry-After`.
|
|
18
|
+
*/
|
|
19
|
+
export class Ricky extends RickyApi {
|
|
20
|
+
/**
|
|
21
|
+
* @param apiKey An organization API key (`rky_live_…`); defaults to the
|
|
22
|
+
* `RICKY_API_KEY` environment variable. Use it only from a server.
|
|
23
|
+
*/
|
|
24
|
+
constructor(apiKey, options = {}) {
|
|
25
|
+
const key = apiKey ?? globalThis.process?.env?.RICKY_API_KEY;
|
|
26
|
+
if (!key)
|
|
27
|
+
throw new Error('Pass an API key or set RICKY_API_KEY.');
|
|
28
|
+
const client = createClient(createConfig({
|
|
29
|
+
auth: key,
|
|
30
|
+
baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,
|
|
31
|
+
fetch: retryingFetch(options.fetch ?? globalThis.fetch, options.maxRetries ?? 2),
|
|
32
|
+
throwOnError: true,
|
|
33
|
+
}));
|
|
34
|
+
client.interceptors.request.use((request, { url }) => {
|
|
35
|
+
if (!request.headers.has('Idempotency-Key') &&
|
|
36
|
+
idempotentOperations.has(`${request.method} ${url}`)) {
|
|
37
|
+
request.headers.set('Idempotency-Key', randomUUID());
|
|
38
|
+
}
|
|
39
|
+
return request;
|
|
40
|
+
});
|
|
41
|
+
client.interceptors.error.use((error, response) => response ? RickyError.fromResponse(response, error) : asConnectionError(error));
|
|
42
|
+
super({ client });
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Walks a list operation page by page, passing each `next_cursor` back as
|
|
46
|
+
* `cursor`.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* for await (const contact of ricky.paginate(ricky.listContacts, { query: { tag_id } })) { … }
|
|
50
|
+
*/
|
|
51
|
+
paginate(list, options) {
|
|
52
|
+
const initial = options;
|
|
53
|
+
const fetchPage = (cursor) => list.call(this, {
|
|
54
|
+
...initial,
|
|
55
|
+
query: { ...initial?.query, ...(cursor === undefined ? {} : { cursor }) },
|
|
56
|
+
});
|
|
57
|
+
async function* pages() {
|
|
58
|
+
let cursor = initial?.query?.cursor;
|
|
59
|
+
do {
|
|
60
|
+
const page = await fetchPage(cursor);
|
|
61
|
+
yield page;
|
|
62
|
+
cursor = page.next_cursor ?? undefined;
|
|
63
|
+
} while (cursor !== undefined);
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
async *[Symbol.asyncIterator]() {
|
|
67
|
+
for await (const page of pages())
|
|
68
|
+
yield* page.data;
|
|
69
|
+
},
|
|
70
|
+
pages,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function retryingFetch(baseFetch, maxRetries) {
|
|
75
|
+
return async (input, init) => {
|
|
76
|
+
const request = new Request(input, init);
|
|
77
|
+
const retryable = request.method === 'GET' ||
|
|
78
|
+
request.method === 'HEAD' ||
|
|
79
|
+
request.headers.has('Idempotency-Key');
|
|
80
|
+
for (let attempt = 0;; attempt += 1) {
|
|
81
|
+
const last = !retryable || attempt >= maxRetries;
|
|
82
|
+
let response;
|
|
83
|
+
try {
|
|
84
|
+
response = await baseFetch(last ? request : request.clone());
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (last || request.signal.aborted)
|
|
88
|
+
throw asConnectionError(error);
|
|
89
|
+
await sleep(backoffSeconds(attempt));
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (last || !RETRYABLE_STATUSES.has(response.status))
|
|
93
|
+
return response;
|
|
94
|
+
const delay = retryAfterSeconds(response.headers.get('retry-after')) ?? backoffSeconds(attempt);
|
|
95
|
+
await response.body?.cancel();
|
|
96
|
+
await sleep(delay);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/** `Retry-After` as seconds (delta or HTTP date), capped; null when absent or invalid. */
|
|
101
|
+
function retryAfterSeconds(header) {
|
|
102
|
+
if (header === null || header.trim() === '')
|
|
103
|
+
return null;
|
|
104
|
+
const seconds = /^\d+$/.test(header.trim())
|
|
105
|
+
? Number(header.trim())
|
|
106
|
+
: (Date.parse(header) - Date.now()) / 1000;
|
|
107
|
+
return Number.isFinite(seconds) ? Math.min(Math.max(seconds, 0), MAX_RETRY_AFTER_SECONDS) : null;
|
|
108
|
+
}
|
|
109
|
+
/** 0.5 s, 1 s, 2 s … up to 8 s, with up to 25 % jitter below. */
|
|
110
|
+
function backoffSeconds(attempt) {
|
|
111
|
+
return Math.min(0.5 * 2 ** attempt, 8) * (1 - Math.random() * 0.25);
|
|
112
|
+
}
|
|
113
|
+
function sleep(seconds) {
|
|
114
|
+
return new Promise((resolve) => setTimeout(resolve, seconds * 1000));
|
|
115
|
+
}
|
|
116
|
+
function asConnectionError(error) {
|
|
117
|
+
return error instanceof RickyError || error instanceof RickyConnectionError
|
|
118
|
+
? error
|
|
119
|
+
: new RickyConnectionError(error);
|
|
120
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Error as ErrorBody } from './generated/types.gen.js';
|
|
2
|
+
/** A stable error code from the API's catalog. New codes may appear at any time. */
|
|
3
|
+
export type RickyErrorCode = ErrorBody['error']['code'] | (string & {});
|
|
4
|
+
/**
|
|
5
|
+
* The API answered with an error. `code` is stable and safe to branch on;
|
|
6
|
+
* `message` is for people. `code` and `requestId` are null only when the
|
|
7
|
+
* response did not come from the API itself (a proxy page, for example).
|
|
8
|
+
*/
|
|
9
|
+
export declare class RickyError extends Error {
|
|
10
|
+
readonly name = "RickyError";
|
|
11
|
+
readonly status: number;
|
|
12
|
+
readonly code: RickyErrorCode | null;
|
|
13
|
+
readonly param: string | null;
|
|
14
|
+
readonly requestId: string | null;
|
|
15
|
+
readonly headers: Headers;
|
|
16
|
+
constructor(options: {
|
|
17
|
+
code: RickyErrorCode | null;
|
|
18
|
+
headers: Headers;
|
|
19
|
+
message: string;
|
|
20
|
+
param: string | null;
|
|
21
|
+
requestId: string | null;
|
|
22
|
+
status: number;
|
|
23
|
+
});
|
|
24
|
+
/** Builds the error from a non-2xx response and its body, parsed when it was JSON. */
|
|
25
|
+
static fromResponse(response: Response, body: unknown): RickyError;
|
|
26
|
+
}
|
|
27
|
+
/** The request never got an answer: DNS, TLS, a dropped connection or an abort. */
|
|
28
|
+
export declare class RickyConnectionError extends Error {
|
|
29
|
+
readonly name = "RickyConnectionError";
|
|
30
|
+
constructor(cause: unknown);
|
|
31
|
+
}
|
|
32
|
+
/** A webhook delivery failed verification and must be rejected. */
|
|
33
|
+
export declare class WebhookVerificationError extends Error {
|
|
34
|
+
readonly name = "WebhookVerificationError";
|
|
35
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The API answered with an error. `code` is stable and safe to branch on;
|
|
3
|
+
* `message` is for people. `code` and `requestId` are null only when the
|
|
4
|
+
* response did not come from the API itself (a proxy page, for example).
|
|
5
|
+
*/
|
|
6
|
+
export class RickyError extends Error {
|
|
7
|
+
name = 'RickyError';
|
|
8
|
+
status;
|
|
9
|
+
code;
|
|
10
|
+
param;
|
|
11
|
+
requestId;
|
|
12
|
+
headers;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
super(options.message);
|
|
15
|
+
this.status = options.status;
|
|
16
|
+
this.code = options.code;
|
|
17
|
+
this.param = options.param;
|
|
18
|
+
this.requestId = options.requestId;
|
|
19
|
+
this.headers = options.headers;
|
|
20
|
+
}
|
|
21
|
+
/** Builds the error from a non-2xx response and its body, parsed when it was JSON. */
|
|
22
|
+
static fromResponse(response, body) {
|
|
23
|
+
const error = isRecord(body) && isRecord(body.error) ? body.error : null;
|
|
24
|
+
const text = (value) => (typeof value === 'string' ? value : null);
|
|
25
|
+
return new RickyError({
|
|
26
|
+
code: text(error?.code),
|
|
27
|
+
headers: response.headers,
|
|
28
|
+
message: text(error?.message) ??
|
|
29
|
+
`HTTP ${response.status}${typeof body === 'string' && body ? `: ${body.slice(0, 200)}` : ''}`,
|
|
30
|
+
param: text(error?.param),
|
|
31
|
+
requestId: text(error?.request_id) ?? response.headers.get('x-request-id'),
|
|
32
|
+
status: response.status,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** The request never got an answer: DNS, TLS, a dropped connection or an abort. */
|
|
37
|
+
export class RickyConnectionError extends Error {
|
|
38
|
+
name = 'RickyConnectionError';
|
|
39
|
+
constructor(cause) {
|
|
40
|
+
super(cause instanceof Error ? cause.message : 'The request to Ricky failed', { cause });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** A webhook delivery failed verification and must be rejected. */
|
|
44
|
+
export class WebhookVerificationError extends Error {
|
|
45
|
+
name = 'WebhookVerificationError';
|
|
46
|
+
}
|
|
47
|
+
function isRecord(value) {
|
|
48
|
+
return typeof value === 'object' && value !== null;
|
|
49
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// This file is auto-generated by @hey-api/openapi-ts
|
|
2
|
+
import { createSseClient } from '../core/serverSentEvents.gen.js';
|
|
3
|
+
import { getValidRequestBody } from '../core/utils.gen.js';
|
|
4
|
+
import { buildUrl, createConfig, createInterceptors, getParseAs, mergeConfigs, mergeHeaders, setAuthParams, } from './utils.gen.js';
|
|
5
|
+
export const createClient = (config = {}) => {
|
|
6
|
+
let _config = mergeConfigs(createConfig(), config);
|
|
7
|
+
const getConfig = () => ({ ..._config });
|
|
8
|
+
const setConfig = (config) => {
|
|
9
|
+
_config = mergeConfigs(_config, config);
|
|
10
|
+
return getConfig();
|
|
11
|
+
};
|
|
12
|
+
const interceptors = createInterceptors();
|
|
13
|
+
const beforeRequest = async (options) => {
|
|
14
|
+
const opts = {
|
|
15
|
+
..._config,
|
|
16
|
+
...options,
|
|
17
|
+
fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
|
|
18
|
+
headers: mergeHeaders(_config.headers, options.headers),
|
|
19
|
+
serializedBody: undefined,
|
|
20
|
+
};
|
|
21
|
+
if (opts.security) {
|
|
22
|
+
await setAuthParams(opts);
|
|
23
|
+
}
|
|
24
|
+
if (opts.requestValidator) {
|
|
25
|
+
await opts.requestValidator(opts);
|
|
26
|
+
}
|
|
27
|
+
if (opts.body !== undefined && opts.bodySerializer) {
|
|
28
|
+
opts.serializedBody = opts.bodySerializer(opts.body);
|
|
29
|
+
}
|
|
30
|
+
// remove Content-Type header if body is empty to avoid sending invalid requests
|
|
31
|
+
if (opts.body === undefined || opts.serializedBody === '') {
|
|
32
|
+
opts.headers.delete('Content-Type');
|
|
33
|
+
}
|
|
34
|
+
const resolvedOpts = opts;
|
|
35
|
+
const url = buildUrl(resolvedOpts);
|
|
36
|
+
return { opts: resolvedOpts, url };
|
|
37
|
+
};
|
|
38
|
+
const request = async (options) => {
|
|
39
|
+
const throwOnError = options.throwOnError ?? _config.throwOnError;
|
|
40
|
+
const responseStyle = options.responseStyle ?? _config.responseStyle;
|
|
41
|
+
let request;
|
|
42
|
+
let response;
|
|
43
|
+
try {
|
|
44
|
+
const { opts, url } = await beforeRequest(options);
|
|
45
|
+
const requestInit = {
|
|
46
|
+
redirect: 'follow',
|
|
47
|
+
...opts,
|
|
48
|
+
body: getValidRequestBody(opts),
|
|
49
|
+
};
|
|
50
|
+
request = new Request(url, requestInit);
|
|
51
|
+
for (const fn of interceptors.request.fns) {
|
|
52
|
+
if (fn) {
|
|
53
|
+
request = await fn(request, opts);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// fetch must be assigned here, otherwise it would throw the error:
|
|
57
|
+
// TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
|
|
58
|
+
const _fetch = opts.fetch;
|
|
59
|
+
response = await _fetch(request);
|
|
60
|
+
for (const fn of interceptors.response.fns) {
|
|
61
|
+
if (fn) {
|
|
62
|
+
response = await fn(response, request, opts);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const result = {
|
|
66
|
+
request,
|
|
67
|
+
response,
|
|
68
|
+
};
|
|
69
|
+
if (response.ok) {
|
|
70
|
+
const parseAs = (opts.parseAs === 'auto'
|
|
71
|
+
? getParseAs(response.headers.get('Content-Type'))
|
|
72
|
+
: opts.parseAs) ?? 'json';
|
|
73
|
+
if (response.status === 204 || response.headers.get('Content-Length') === '0') {
|
|
74
|
+
let emptyData;
|
|
75
|
+
switch (parseAs) {
|
|
76
|
+
case 'arrayBuffer':
|
|
77
|
+
case 'blob':
|
|
78
|
+
case 'text':
|
|
79
|
+
emptyData = await response[parseAs]();
|
|
80
|
+
break;
|
|
81
|
+
case 'formData':
|
|
82
|
+
emptyData = new FormData();
|
|
83
|
+
break;
|
|
84
|
+
case 'stream':
|
|
85
|
+
emptyData = response.body;
|
|
86
|
+
break;
|
|
87
|
+
case 'json':
|
|
88
|
+
default:
|
|
89
|
+
emptyData = {};
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
return opts.responseStyle === 'data'
|
|
93
|
+
? emptyData
|
|
94
|
+
: {
|
|
95
|
+
data: emptyData,
|
|
96
|
+
...result,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
let data;
|
|
100
|
+
switch (parseAs) {
|
|
101
|
+
case 'arrayBuffer':
|
|
102
|
+
case 'blob':
|
|
103
|
+
case 'formData':
|
|
104
|
+
case 'text':
|
|
105
|
+
data = await response[parseAs]();
|
|
106
|
+
break;
|
|
107
|
+
case 'json': {
|
|
108
|
+
// Some servers return 200 with no Content-Length and empty body.
|
|
109
|
+
// response.json() would throw; read as text and parse if non-empty.
|
|
110
|
+
const text = await response.text();
|
|
111
|
+
data = text ? JSON.parse(text) : {};
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
case 'stream':
|
|
115
|
+
return opts.responseStyle === 'data'
|
|
116
|
+
? response.body
|
|
117
|
+
: {
|
|
118
|
+
data: response.body,
|
|
119
|
+
...result,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (parseAs === 'json') {
|
|
123
|
+
if (opts.responseValidator) {
|
|
124
|
+
await opts.responseValidator(data);
|
|
125
|
+
}
|
|
126
|
+
if (opts.responseTransformer) {
|
|
127
|
+
data = await opts.responseTransformer(data);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return opts.responseStyle === 'data'
|
|
131
|
+
? data
|
|
132
|
+
: {
|
|
133
|
+
data,
|
|
134
|
+
...result,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const textError = await response.text();
|
|
138
|
+
let jsonError;
|
|
139
|
+
try {
|
|
140
|
+
jsonError = JSON.parse(textError);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// noop
|
|
144
|
+
}
|
|
145
|
+
throw jsonError ?? textError;
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
let finalError = error;
|
|
149
|
+
for (const fn of interceptors.error.fns) {
|
|
150
|
+
if (fn) {
|
|
151
|
+
finalError = await fn(finalError, response, request, options);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
finalError = finalError || {};
|
|
155
|
+
if (throwOnError) {
|
|
156
|
+
throw finalError;
|
|
157
|
+
}
|
|
158
|
+
// TODO: we probably want to return error and improve types
|
|
159
|
+
return responseStyle === 'data'
|
|
160
|
+
? undefined
|
|
161
|
+
: {
|
|
162
|
+
error: finalError,
|
|
163
|
+
request,
|
|
164
|
+
response,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const makeMethodFn = (method) => (options) => request({ ...options, method });
|
|
169
|
+
const makeSseFn = (method) => async (options) => {
|
|
170
|
+
const { opts, url } = await beforeRequest(options);
|
|
171
|
+
return createSseClient({
|
|
172
|
+
...opts,
|
|
173
|
+
body: opts.body,
|
|
174
|
+
method,
|
|
175
|
+
onRequest: async (url, init) => {
|
|
176
|
+
let request = new Request(url, init);
|
|
177
|
+
for (const fn of interceptors.request.fns) {
|
|
178
|
+
if (fn) {
|
|
179
|
+
request = await fn(request, opts);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return request;
|
|
183
|
+
},
|
|
184
|
+
serializedBody: getValidRequestBody(opts),
|
|
185
|
+
url,
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
const _buildUrl = (options) => buildUrl({ ..._config, ...options });
|
|
189
|
+
return {
|
|
190
|
+
buildUrl: _buildUrl,
|
|
191
|
+
connect: makeMethodFn('CONNECT'),
|
|
192
|
+
delete: makeMethodFn('DELETE'),
|
|
193
|
+
get: makeMethodFn('GET'),
|
|
194
|
+
getConfig,
|
|
195
|
+
head: makeMethodFn('HEAD'),
|
|
196
|
+
interceptors,
|
|
197
|
+
options: makeMethodFn('OPTIONS'),
|
|
198
|
+
patch: makeMethodFn('PATCH'),
|
|
199
|
+
post: makeMethodFn('POST'),
|
|
200
|
+
put: makeMethodFn('PUT'),
|
|
201
|
+
request,
|
|
202
|
+
setConfig,
|
|
203
|
+
sse: {
|
|
204
|
+
connect: makeSseFn('CONNECT'),
|
|
205
|
+
delete: makeSseFn('DELETE'),
|
|
206
|
+
get: makeSseFn('GET'),
|
|
207
|
+
head: makeSseFn('HEAD'),
|
|
208
|
+
options: makeSseFn('OPTIONS'),
|
|
209
|
+
patch: makeSseFn('PATCH'),
|
|
210
|
+
post: makeSseFn('POST'),
|
|
211
|
+
put: makeSseFn('PUT'),
|
|
212
|
+
trace: makeSseFn('TRACE'),
|
|
213
|
+
},
|
|
214
|
+
trace: makeMethodFn('TRACE'),
|
|
215
|
+
};
|
|
216
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type { Auth } from '../core/auth.gen.js';
|
|
2
|
+
export type { QuerySerializerOptions } from '../core/bodySerializer.gen.js';
|
|
3
|
+
export { formDataBodySerializer, jsonBodySerializer, urlSearchParamsBodySerializer, } from '../core/bodySerializer.gen.js';
|
|
4
|
+
export { buildClientParams } from '../core/params.gen.js';
|
|
5
|
+
export { serializeQueryKeyValue } from '../core/queryKeySerializer.gen.js';
|
|
6
|
+
export type { ServerSentEventsResult } from '../core/serverSentEvents.gen.js';
|
|
7
|
+
export type { ClientMeta } from '../core/types.gen.js';
|
|
8
|
+
export { createClient } from './client.gen.js';
|
|
9
|
+
export type { Client, ClientOptions, Config, CreateClientConfig, Options, RequestOptions, RequestResult, ResolvedRequestOptions, ResponseStyle, TDataShape, } from './types.gen.js';
|
|
10
|
+
export { createConfig, mergeHeaders } from './utils.gen.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// This file is auto-generated by @hey-api/openapi-ts
|
|
2
|
+
export { formDataBodySerializer, jsonBodySerializer, urlSearchParamsBodySerializer, } from '../core/bodySerializer.gen.js';
|
|
3
|
+
export { buildClientParams } from '../core/params.gen.js';
|
|
4
|
+
export { serializeQueryKeyValue } from '../core/queryKeySerializer.gen.js';
|
|
5
|
+
export { createClient } from './client.gen.js';
|
|
6
|
+
export { createConfig, mergeHeaders } from './utils.gen.js';
|