relay-surfih-sdk 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/README.md +147 -2
- package/dist/client.d.ts +77 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +183 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +58 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +175 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/origin.d.ts +12 -0
- package/dist/origin.d.ts.map +1 -0
- package/dist/origin.js +54 -0
- package/dist/origin.js.map +1 -0
- package/dist/read-header.d.ts +10 -0
- package/dist/read-header.d.ts.map +1 -0
- package/dist/read-header.js +48 -0
- package/dist/read-header.js.map +1 -0
- package/dist/request.d.ts +25 -0
- package/dist/request.d.ts.map +1 -0
- package/dist/request.js +77 -0
- package/dist/request.js.map +1 -0
- package/dist/sign.d.ts +34 -0
- package/dist/sign.d.ts.map +1 -0
- package/dist/sign.js +192 -0
- package/dist/sign.js.map +1 -0
- package/dist/webhook.d.ts +42 -0
- package/dist/webhook.d.ts.map +1 -0
- package/dist/webhook.js +103 -0
- package/dist/webhook.js.map +1 -0
- package/package.json +29 -4
package/README.md
CHANGED
|
@@ -1,3 +1,148 @@
|
|
|
1
|
-
#
|
|
1
|
+
# relay-sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Signed client for Relay `/api/v1`, and a verifier for webhook deliveries. ESM, Node 20+. The only runtime dependency is `relay-types`.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Build `relay-types` first so this package can resolve its types:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
cd ../relay-types && npm install && npm run build
|
|
11
|
+
cd ../relay-sdk-nodejs && npm install && npm run build
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## HMAC
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { createRelay } from "relay-sdk";
|
|
18
|
+
|
|
19
|
+
const relay = createRelay({
|
|
20
|
+
apiKey: process.env.RELAY_API_KEY!,
|
|
21
|
+
secret: process.env.RELAY_API_SECRET!,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const created = await relay.queues.create(
|
|
25
|
+
{ name: "orders", webhook_url: "https://app.example.com/hooks/relay" },
|
|
26
|
+
{ idempotencyKey: "queue-orders" },
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const accepted = await relay.events.submit(
|
|
30
|
+
created.queue.id,
|
|
31
|
+
{ event_type: "order.created", event: { id: "ord_1" } },
|
|
32
|
+
{ idempotencyKey: "order-ord_1" },
|
|
33
|
+
);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The client signs `apiKey.timestamp.METHOD.pathAndQuery.rawBody` over the same UTF-8 bytes it sends. `GET` and `DELETE` sign an empty body (the string ends with `.`) and omit `Content-Type`.
|
|
37
|
+
|
|
38
|
+
## API
|
|
39
|
+
|
|
40
|
+
| Method | HTTP | Idempotency-Key |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| `relay.queues.create(body, { idempotencyKey })` | `POST /api/v1/queues` | required |
|
|
43
|
+
| `relay.events.submit(queueId, body, { idempotencyKey })` | `POST /api/v1/queues/{queueId}/events` | required |
|
|
44
|
+
| `relay.events.get(eventId)` | `GET /api/v1/events/{eventId}` | — |
|
|
45
|
+
| `relay.events.get(eventId, { attempts: true })` | `GET …?attempts=1` | — |
|
|
46
|
+
| `relay.events.result(eventId, body, { idempotencyKey })` | `POST /api/v1/events/{eventId}/result` | required |
|
|
47
|
+
| `relay.events.checkpoints(eventId, checkpoints)` | `POST /api/v1/events/{eventId}/checkpoints` | — |
|
|
48
|
+
| `relay.schedules.list(queueId)` | `GET /api/v1/queues/{queueId}/schedules` | — |
|
|
49
|
+
| `relay.schedules.create(queueId, body)` | `POST /api/v1/queues/{queueId}/schedules` | — |
|
|
50
|
+
| `relay.schedules.delete(scheduleId)` | `DELETE /api/v1/schedules/{scheduleId}` | — |
|
|
51
|
+
|
|
52
|
+
Request and response shapes come from `relay-types` and are re-exported from `relay-sdk`.
|
|
53
|
+
|
|
54
|
+
### Events
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
// Delay delivery (at most one of deliver_at / delay_seconds; omit both for ASAP).
|
|
58
|
+
await relay.events.submit(
|
|
59
|
+
queueId,
|
|
60
|
+
{ event_type: "order.created", event: { id: "ord_1" }, delay_seconds: 60 },
|
|
61
|
+
{ idempotencyKey: "order-ord_1" },
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const event = await relay.events.get(accepted.event_id);
|
|
65
|
+
const withAttempts = await relay.events.get(accepted.event_id, { attempts: true });
|
|
66
|
+
// withAttempts.attempts, withAttempts.checkpoints
|
|
67
|
+
|
|
68
|
+
await relay.events.result(
|
|
69
|
+
accepted.event_id,
|
|
70
|
+
{ status: "completed", result: { provider_id: "msg_1" } },
|
|
71
|
+
{ idempotencyKey: "result-ord_1" },
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
await relay.events.checkpoints(accepted.event_id, [
|
|
75
|
+
{ tag: "received", at: new Date().toISOString() },
|
|
76
|
+
{ tag: "done" },
|
|
77
|
+
]);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`send_message` events must carry only `{ notification_id }` in `event`.
|
|
81
|
+
|
|
82
|
+
### Schedules
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const { schedules, limit } = await relay.schedules.list(queueId);
|
|
86
|
+
|
|
87
|
+
const { schedule } = await relay.schedules.create(queueId, {
|
|
88
|
+
name: "daily-digest",
|
|
89
|
+
cron: "0 9 * * *",
|
|
90
|
+
timezone: "America/Sao_Paulo",
|
|
91
|
+
event_type: "digest.run",
|
|
92
|
+
event: { kind: "daily" },
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
await relay.schedules.delete(schedule.id);
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`enabled` on create is accepted by the API but not stored; new schedules always start enabled.
|
|
99
|
+
|
|
100
|
+
## Ed25519 or KMS
|
|
101
|
+
|
|
102
|
+
Pass a PKCS#8 Ed25519 `privateKeyPem`, or a `sign` hook. The hook receives those raw UTF-8 bytes and returns the full header value: `sha256=` plus 64 lowercase hex characters, or `ed25519=` plus standard base64 of the 64-byte signature.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
const relay = createRelay({
|
|
106
|
+
apiKey,
|
|
107
|
+
sign: async (bytes) => {
|
|
108
|
+
const signature = await kmsSignEd25519(bytes); // 64 raw bytes
|
|
109
|
+
return "ed25519=" + Buffer.from(signature).toString("base64");
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Register the matching SPKI public key on the queue before the first `ed25519=` call. `request_public_key_pem` must be a public key; a private key PEM is rejected before the request is sent.
|
|
115
|
+
|
|
116
|
+
## Webhooks
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { verifyWebhook } from "relay-sdk";
|
|
120
|
+
|
|
121
|
+
const delivery = verifyWebhook({
|
|
122
|
+
apiKey,
|
|
123
|
+
secret,
|
|
124
|
+
rawBody, // exact bytes, before JSON.parse
|
|
125
|
+
headers: req.headers,
|
|
126
|
+
deliveryPublicKeyPem, // set when webhook_auth_mode is "ed25519"
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
delivery.eventId;
|
|
130
|
+
delivery.attempt;
|
|
131
|
+
delivery.idempotencyKey;
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`Idempotency-Key` is unsigned. A request with that header swapped still verifies. Dedupe on the signed pair `(event_id, attempt)` as well as on the idempotency key. When `deliveryPublicKeyPem` is set, the Ed25519 signature covers `v1.{timestamp}.{eventId}.{sha256_hex(body)}` and the timestamp must be within ±300 seconds. HMAC-only deliveries have no timestamp.
|
|
135
|
+
|
|
136
|
+
A header value that arrives as a `string[]` must contain exactly one entry.
|
|
137
|
+
|
|
138
|
+
## Errors
|
|
139
|
+
|
|
140
|
+
Failed `/api/v1` calls throw `RelayError` (`status`, `message`, optional `code`, `requestId`, `retryAfter`, `issues`, quota fields). Webhook verification failures throw `WebhookVerificationError`.
|
|
141
|
+
|
|
142
|
+
## Fetch and base URL
|
|
143
|
+
|
|
144
|
+
`baseUrl` defaults to `https://relay.surfih.com` (`DEFAULT_BASE_URL`). Pass it only to target another origin (local, staging).
|
|
145
|
+
|
|
146
|
+
Every call sets `redirect: "error"`. A custom `fetch` must honor that and must not follow redirects.
|
|
147
|
+
|
|
148
|
+
When set, `baseUrl` is parsed with `new URL`. It must be `https`, or `http` for `localhost`, `127.0.0.1`, or `::1`. Other `http` origins need `allowInsecureHttp: true`. Userinfo, query, hash, and a non-root path are rejected.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { AcceptedEventResponse, CheckpointInput, CreateQueueRequest, CreateQueueResponse, CreateScheduleRequest, DeletedScheduleResponse, Event, EventResultResponse, EventWithAttempts, PostCheckpointsResponse, PostEventResultRequest, ScheduleListResponse, ScheduleResponse, SubmitEventRequest } from "relay-types";
|
|
2
|
+
import { type RelayFetch } from "./request.js";
|
|
3
|
+
import { type SignFn } from "./sign.js";
|
|
4
|
+
export type { RelayFetch, SignFn };
|
|
5
|
+
/** Production Relay origin used when `CreateRelayOptions.baseUrl` is omitted. */
|
|
6
|
+
export declare const DEFAULT_BASE_URL = "https://relay.surfih.com";
|
|
7
|
+
/** `now` returns milliseconds since the Unix epoch, the same as `Date.now`. */
|
|
8
|
+
export interface CreateRelayOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Relay origin. Defaults to {@link DEFAULT_BASE_URL}.
|
|
11
|
+
* Parsed with `new URL`; must be `https`, or `http` for loopback unless `allowInsecureHttp`.
|
|
12
|
+
*/
|
|
13
|
+
baseUrl?: string;
|
|
14
|
+
apiKey: string;
|
|
15
|
+
/** HMAC key (`cs_live_…`). Mutually exclusive with `privateKeyPem` and `sign`. */
|
|
16
|
+
secret?: string;
|
|
17
|
+
/** PKCS#8 Ed25519 private key. Mutually exclusive with `secret` and `sign`. */
|
|
18
|
+
privateKeyPem?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Signs the raw UTF-8 bytes of `apiKey.timestamp.METHOD.pathAndQuery.rawBody`.
|
|
21
|
+
* Return `sha256=<64 lowercase hex>` or `ed25519=<standard base64 of 64 bytes>`.
|
|
22
|
+
*/
|
|
23
|
+
sign?: SignFn;
|
|
24
|
+
/** Milliseconds since the Unix epoch. Defaults to `Date.now`. */
|
|
25
|
+
now?: () => number;
|
|
26
|
+
/**
|
|
27
|
+
* Replacement for global `fetch`. Every call passes `redirect: "error"`.
|
|
28
|
+
* The implementation must not follow redirects.
|
|
29
|
+
*/
|
|
30
|
+
fetch?: RelayFetch;
|
|
31
|
+
/** Allow `http://` to a non-loopback host. Loopback http is always allowed. */
|
|
32
|
+
allowInsecureHttp?: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface RequestOptions {
|
|
35
|
+
signal?: AbortSignal;
|
|
36
|
+
}
|
|
37
|
+
export interface IdempotentOptions extends RequestOptions {
|
|
38
|
+
idempotencyKey: string;
|
|
39
|
+
}
|
|
40
|
+
export interface GetEventOptions extends RequestOptions {
|
|
41
|
+
/** `true` sends `?attempts=1` and returns attempts plus checkpoints. */
|
|
42
|
+
attempts?: boolean;
|
|
43
|
+
}
|
|
44
|
+
export interface RelayQueues {
|
|
45
|
+
create(body: CreateQueueRequest, options: IdempotentOptions): Promise<CreateQueueResponse>;
|
|
46
|
+
}
|
|
47
|
+
export interface RelayEvents {
|
|
48
|
+
submit(queueId: string, body: SubmitEventRequest, options: IdempotentOptions): Promise<AcceptedEventResponse>;
|
|
49
|
+
get(eventId: string, options: GetEventOptions & {
|
|
50
|
+
attempts: true;
|
|
51
|
+
}): Promise<EventWithAttempts>;
|
|
52
|
+
get(eventId: string, options?: GetEventOptions): Promise<Event>;
|
|
53
|
+
result(eventId: string, body: PostEventResultRequest, options: IdempotentOptions): Promise<EventResultResponse>;
|
|
54
|
+
checkpoints(eventId: string, checkpoints: readonly CheckpointInput[], options?: RequestOptions): Promise<PostCheckpointsResponse>;
|
|
55
|
+
}
|
|
56
|
+
export interface RelaySchedules {
|
|
57
|
+
list(queueId: string, options?: RequestOptions): Promise<ScheduleListResponse>;
|
|
58
|
+
create(queueId: string, body: CreateScheduleRequest, options?: RequestOptions): Promise<ScheduleResponse>;
|
|
59
|
+
delete(scheduleId: string, options?: RequestOptions): Promise<DeletedScheduleResponse>;
|
|
60
|
+
}
|
|
61
|
+
export interface Relay {
|
|
62
|
+
queues: RelayQueues;
|
|
63
|
+
events: RelayEvents;
|
|
64
|
+
schedules: RelaySchedules;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Client for `/api/v1`. Signing headers are applied internally.
|
|
68
|
+
* Pass exactly one of `secret`, `privateKeyPem`, or `sign`.
|
|
69
|
+
*
|
|
70
|
+
* `baseUrl` defaults to {@link DEFAULT_BASE_URL}. When set, it is parsed with `new URL`
|
|
71
|
+
* and must be `https`, or `http` for `localhost` / `127.0.0.1` / `::1`, unless
|
|
72
|
+
* `allowInsecureHttp` is set. Credentials, query, hash, and a non-root path are rejected.
|
|
73
|
+
* Each request is built with `new URL(pathAndQuery, origin)` and must not escape that origin.
|
|
74
|
+
* `redirect: "error"` is set on every fetch.
|
|
75
|
+
*/
|
|
76
|
+
export declare function createRelay(options: CreateRelayOptions): Relay;
|
|
77
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { paths } from "relay-types";
|
|
2
|
+
import { assertRelayOrigin } from "./origin.js";
|
|
3
|
+
import { signedRequest } from "./request.js";
|
|
4
|
+
import { assertApiKey, assertPublicRequestKey, assertSecret, customSigner, ed25519Signer, hmacSigner, } from "./sign.js";
|
|
5
|
+
/** Production Relay origin used when `CreateRelayOptions.baseUrl` is omitted. */
|
|
6
|
+
export const DEFAULT_BASE_URL = "https://relay.surfih.com";
|
|
7
|
+
/**
|
|
8
|
+
* Client for `/api/v1`. Signing headers are applied internally.
|
|
9
|
+
* Pass exactly one of `secret`, `privateKeyPem`, or `sign`.
|
|
10
|
+
*
|
|
11
|
+
* `baseUrl` defaults to {@link DEFAULT_BASE_URL}. When set, it is parsed with `new URL`
|
|
12
|
+
* and must be `https`, or `http` for `localhost` / `127.0.0.1` / `::1`, unless
|
|
13
|
+
* `allowInsecureHttp` is set. Credentials, query, hash, and a non-root path are rejected.
|
|
14
|
+
* Each request is built with `new URL(pathAndQuery, origin)` and must not escape that origin.
|
|
15
|
+
* `redirect: "error"` is set on every fetch.
|
|
16
|
+
*/
|
|
17
|
+
export function createRelay(options) {
|
|
18
|
+
const apiKey = assertApiKey(options.apiKey);
|
|
19
|
+
const origin = assertRelayOrigin(options.baseUrl ?? DEFAULT_BASE_URL, options.allowInsecureHttp === true);
|
|
20
|
+
const sign = resolveSigner(options);
|
|
21
|
+
if (options.now !== undefined && typeof options.now !== "function") {
|
|
22
|
+
throw new TypeError("now must be a function");
|
|
23
|
+
}
|
|
24
|
+
if (options.fetch !== undefined && typeof options.fetch !== "function") {
|
|
25
|
+
throw new TypeError("fetch must be a function");
|
|
26
|
+
}
|
|
27
|
+
const now = options.now ?? Date.now;
|
|
28
|
+
const fetchImpl = options.fetch ??
|
|
29
|
+
((input, init) => {
|
|
30
|
+
if (typeof globalThis.fetch !== "function") {
|
|
31
|
+
throw new TypeError("fetch is not available");
|
|
32
|
+
}
|
|
33
|
+
return globalThis.fetch(input, init);
|
|
34
|
+
});
|
|
35
|
+
const transport = { origin, apiKey, sign, now, fetch: fetchImpl };
|
|
36
|
+
return {
|
|
37
|
+
queues: new QueuesApi(transport),
|
|
38
|
+
events: new EventsApi(transport),
|
|
39
|
+
schedules: new SchedulesApi(transport),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function resolveSigner(options) {
|
|
43
|
+
const selected = [options.secret !== undefined, options.privateKeyPem !== undefined, options.sign !== undefined];
|
|
44
|
+
const count = selected.filter(Boolean).length;
|
|
45
|
+
if (count !== 1) {
|
|
46
|
+
throw new TypeError("createRelay requires exactly one of secret, privateKeyPem, or sign");
|
|
47
|
+
}
|
|
48
|
+
if (options.secret !== undefined)
|
|
49
|
+
return hmacSigner(assertSecret(options.secret));
|
|
50
|
+
if (options.privateKeyPem !== undefined)
|
|
51
|
+
return ed25519Signer(options.privateKeyPem);
|
|
52
|
+
return customSigner(options.sign);
|
|
53
|
+
}
|
|
54
|
+
function call(input) {
|
|
55
|
+
const next = {
|
|
56
|
+
method: input.method,
|
|
57
|
+
pathAndQuery: input.pathAndQuery,
|
|
58
|
+
successStatuses: input.successStatuses,
|
|
59
|
+
};
|
|
60
|
+
if (input.jsonBody !== undefined)
|
|
61
|
+
next.jsonBody = input.jsonBody;
|
|
62
|
+
if (input.idempotencyKey !== undefined)
|
|
63
|
+
next.idempotencyKey = input.idempotencyKey;
|
|
64
|
+
if (input.signal !== undefined)
|
|
65
|
+
next.signal = input.signal;
|
|
66
|
+
return next;
|
|
67
|
+
}
|
|
68
|
+
function assertId(value, label) {
|
|
69
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
70
|
+
throw new TypeError(`${label} must be a non-empty string`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
class QueuesApi {
|
|
74
|
+
transport;
|
|
75
|
+
constructor(transport) {
|
|
76
|
+
this.transport = transport;
|
|
77
|
+
}
|
|
78
|
+
async create(body, options) {
|
|
79
|
+
assertPublicRequestKey(body.request_public_key_pem);
|
|
80
|
+
return signedRequest(this.transport, call({
|
|
81
|
+
method: "POST",
|
|
82
|
+
pathAndQuery: paths.createQueue,
|
|
83
|
+
jsonBody: body,
|
|
84
|
+
idempotencyKey: options.idempotencyKey,
|
|
85
|
+
successStatuses: [200, 201],
|
|
86
|
+
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
class EventsApi {
|
|
91
|
+
transport;
|
|
92
|
+
constructor(transport) {
|
|
93
|
+
this.transport = transport;
|
|
94
|
+
}
|
|
95
|
+
async submit(queueId, body, options) {
|
|
96
|
+
assertId(queueId, "queueId");
|
|
97
|
+
return signedRequest(this.transport, call({
|
|
98
|
+
method: "POST",
|
|
99
|
+
pathAndQuery: paths.submitEvent(queueId),
|
|
100
|
+
jsonBody: body,
|
|
101
|
+
idempotencyKey: options.idempotencyKey,
|
|
102
|
+
successStatuses: [202],
|
|
103
|
+
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
async get(eventId, options) {
|
|
107
|
+
assertId(eventId, "eventId");
|
|
108
|
+
const path = paths.getEvent(eventId);
|
|
109
|
+
const attempts = options?.attempts === true;
|
|
110
|
+
const pathAndQuery = attempts ? `${path}?attempts=1` : path;
|
|
111
|
+
const signal = options?.signal;
|
|
112
|
+
if (attempts) {
|
|
113
|
+
return signedRequest(this.transport, call({
|
|
114
|
+
method: "GET",
|
|
115
|
+
pathAndQuery,
|
|
116
|
+
successStatuses: [200],
|
|
117
|
+
...(signal !== undefined ? { signal } : {}),
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
return signedRequest(this.transport, call({
|
|
121
|
+
method: "GET",
|
|
122
|
+
pathAndQuery,
|
|
123
|
+
successStatuses: [200],
|
|
124
|
+
...(signal !== undefined ? { signal } : {}),
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
async result(eventId, body, options) {
|
|
128
|
+
assertId(eventId, "eventId");
|
|
129
|
+
return signedRequest(this.transport, call({
|
|
130
|
+
method: "POST",
|
|
131
|
+
pathAndQuery: paths.postEventResult(eventId),
|
|
132
|
+
jsonBody: body,
|
|
133
|
+
idempotencyKey: options.idempotencyKey,
|
|
134
|
+
successStatuses: [200],
|
|
135
|
+
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
async checkpoints(eventId, checkpoints, options) {
|
|
139
|
+
assertId(eventId, "eventId");
|
|
140
|
+
return signedRequest(this.transport, call({
|
|
141
|
+
method: "POST",
|
|
142
|
+
pathAndQuery: paths.postCheckpoints(eventId),
|
|
143
|
+
jsonBody: { checkpoints },
|
|
144
|
+
successStatuses: [200],
|
|
145
|
+
...(options?.signal !== undefined ? { signal: options.signal } : {}),
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
class SchedulesApi {
|
|
150
|
+
transport;
|
|
151
|
+
constructor(transport) {
|
|
152
|
+
this.transport = transport;
|
|
153
|
+
}
|
|
154
|
+
async list(queueId, options) {
|
|
155
|
+
assertId(queueId, "queueId");
|
|
156
|
+
return signedRequest(this.transport, call({
|
|
157
|
+
method: "GET",
|
|
158
|
+
pathAndQuery: paths.listSchedules(queueId),
|
|
159
|
+
successStatuses: [200],
|
|
160
|
+
...(options?.signal !== undefined ? { signal: options.signal } : {}),
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
163
|
+
async create(queueId, body, options) {
|
|
164
|
+
assertId(queueId, "queueId");
|
|
165
|
+
return signedRequest(this.transport, call({
|
|
166
|
+
method: "POST",
|
|
167
|
+
pathAndQuery: paths.createSchedule(queueId),
|
|
168
|
+
jsonBody: body,
|
|
169
|
+
successStatuses: [201],
|
|
170
|
+
...(options?.signal !== undefined ? { signal: options.signal } : {}),
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
async delete(scheduleId, options) {
|
|
174
|
+
assertId(scheduleId, "scheduleId");
|
|
175
|
+
return signedRequest(this.transport, call({
|
|
176
|
+
method: "DELETE",
|
|
177
|
+
pathAndQuery: paths.deleteSchedule(scheduleId),
|
|
178
|
+
successStatuses: [200],
|
|
179
|
+
...(options?.signal !== undefined ? { signal: options.signal } : {}),
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ValidationIssue } from "relay-types";
|
|
2
|
+
/** Server `error.message` is kept up to this many Unicode code points. */
|
|
3
|
+
export declare const MAX_ERROR_MESSAGE_CODE_POINTS = 500;
|
|
4
|
+
export declare function capCodePoints(value: string, max: number): string;
|
|
5
|
+
export interface RelayErrorInit {
|
|
6
|
+
status: number;
|
|
7
|
+
message: string;
|
|
8
|
+
code?: string;
|
|
9
|
+
requestId?: string;
|
|
10
|
+
retryAfter?: number;
|
|
11
|
+
existingEventId?: string;
|
|
12
|
+
issues?: readonly ValidationIssue[];
|
|
13
|
+
eventAllowance?: number;
|
|
14
|
+
eventsUsed?: number;
|
|
15
|
+
scheduleLimit?: number;
|
|
16
|
+
expiresAt?: string | null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Failure from `/api/v1`. `message` is the server `error.message`, capped, and never includes
|
|
20
|
+
* request credentials. `existingEventId` is set from a 409 `existing_event_id`. `retryAfter`
|
|
21
|
+
* is the `Retry-After` delta-seconds, when the header is a non-negative integer.
|
|
22
|
+
*/
|
|
23
|
+
export declare class RelayError extends Error {
|
|
24
|
+
readonly name = "RelayError";
|
|
25
|
+
readonly status: number;
|
|
26
|
+
readonly code?: string;
|
|
27
|
+
readonly requestId?: string;
|
|
28
|
+
readonly retryAfter?: number;
|
|
29
|
+
readonly existingEventId?: string;
|
|
30
|
+
readonly issues?: readonly ValidationIssue[];
|
|
31
|
+
readonly eventAllowance?: number;
|
|
32
|
+
readonly eventsUsed?: number;
|
|
33
|
+
readonly scheduleLimit?: number;
|
|
34
|
+
readonly expiresAt?: string | null;
|
|
35
|
+
constructor(init: RelayErrorInit);
|
|
36
|
+
}
|
|
37
|
+
export declare const WEBHOOK_REASONS: readonly ["headers", "signature", "timestamp", "delivery", "body", "mismatch"];
|
|
38
|
+
export type WebhookVerificationReason = (typeof WEBHOOK_REASONS)[number];
|
|
39
|
+
/** Verification failure. `message` is fixed for `reason` and does not include key material. */
|
|
40
|
+
export declare class WebhookVerificationError extends Error {
|
|
41
|
+
readonly name = "WebhookVerificationError";
|
|
42
|
+
readonly reason: WebhookVerificationReason;
|
|
43
|
+
constructor(reason: WebhookVerificationReason);
|
|
44
|
+
}
|
|
45
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
46
|
+
export declare function invalidJsonMessage(): string;
|
|
47
|
+
export declare function fallbackErrorMessage(): string;
|
|
48
|
+
export declare function serverMessage(value: unknown): string;
|
|
49
|
+
export declare function toRelayError(input: {
|
|
50
|
+
status: number;
|
|
51
|
+
parsed: unknown;
|
|
52
|
+
requestId: string | undefined;
|
|
53
|
+
retryAfter: number | undefined;
|
|
54
|
+
}): RelayError;
|
|
55
|
+
export declare function readRequestId(header: string | null): string | undefined;
|
|
56
|
+
/** `Retry-After` delta-seconds. HTTP-date values are ignored. */
|
|
57
|
+
export declare function readRetryAfter(header: string | null): number | undefined;
|
|
58
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnE,0EAA0E;AAC1E,eAAO,MAAM,6BAA6B,MAAM,CAAC;AAKjD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAIhE;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACpC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;GAIG;AACH,qBAAa,UAAW,SAAQ,KAAK;IACnC,SAAkB,IAAI,gBAAgB;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IAC7C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;gBAEvB,IAAI,EAAE,cAAc;CAcjC;AAED,eAAO,MAAM,eAAe,gFAAiF,CAAC;AAE9G,MAAM,MAAM,yBAAyB,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAWzE,+FAA+F;AAC/F,qBAAa,wBAAyB,SAAQ,KAAK;IACjD,SAAkB,IAAI,8BAA8B;IACpD,QAAQ,CAAC,MAAM,EAAE,yBAAyB,CAAC;gBAE/B,MAAM,EAAE,yBAAyB;CAK9C;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGpD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC,GAAG,UAAU,CAwCb;AA4BD,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAKvE;AAED,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAOxE"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { isPrintableAscii } from "./sign.js";
|
|
2
|
+
/** Server `error.message` is kept up to this many Unicode code points. */
|
|
3
|
+
export const MAX_ERROR_MESSAGE_CODE_POINTS = 500;
|
|
4
|
+
const FALLBACK_MESSAGE = "Relay request failed";
|
|
5
|
+
const INVALID_JSON_MESSAGE = "Relay response was not valid JSON";
|
|
6
|
+
export function capCodePoints(value, max) {
|
|
7
|
+
const points = [...value];
|
|
8
|
+
if (points.length <= max)
|
|
9
|
+
return value;
|
|
10
|
+
return points.slice(0, max).join("");
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Failure from `/api/v1`. `message` is the server `error.message`, capped, and never includes
|
|
14
|
+
* request credentials. `existingEventId` is set from a 409 `existing_event_id`. `retryAfter`
|
|
15
|
+
* is the `Retry-After` delta-seconds, when the header is a non-negative integer.
|
|
16
|
+
*/
|
|
17
|
+
export class RelayError extends Error {
|
|
18
|
+
name = "RelayError";
|
|
19
|
+
status;
|
|
20
|
+
code;
|
|
21
|
+
requestId;
|
|
22
|
+
retryAfter;
|
|
23
|
+
existingEventId;
|
|
24
|
+
issues;
|
|
25
|
+
eventAllowance;
|
|
26
|
+
eventsUsed;
|
|
27
|
+
scheduleLimit;
|
|
28
|
+
expiresAt;
|
|
29
|
+
constructor(init) {
|
|
30
|
+
super(init.message);
|
|
31
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
32
|
+
this.status = init.status;
|
|
33
|
+
if (init.code !== undefined)
|
|
34
|
+
this.code = init.code;
|
|
35
|
+
if (init.requestId !== undefined)
|
|
36
|
+
this.requestId = init.requestId;
|
|
37
|
+
if (init.retryAfter !== undefined)
|
|
38
|
+
this.retryAfter = init.retryAfter;
|
|
39
|
+
if (init.existingEventId !== undefined)
|
|
40
|
+
this.existingEventId = init.existingEventId;
|
|
41
|
+
if (init.issues !== undefined)
|
|
42
|
+
this.issues = init.issues;
|
|
43
|
+
if (init.eventAllowance !== undefined)
|
|
44
|
+
this.eventAllowance = init.eventAllowance;
|
|
45
|
+
if (init.eventsUsed !== undefined)
|
|
46
|
+
this.eventsUsed = init.eventsUsed;
|
|
47
|
+
if (init.scheduleLimit !== undefined)
|
|
48
|
+
this.scheduleLimit = init.scheduleLimit;
|
|
49
|
+
if (init.expiresAt !== undefined)
|
|
50
|
+
this.expiresAt = init.expiresAt;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export const WEBHOOK_REASONS = ["headers", "signature", "timestamp", "delivery", "body", "mismatch"];
|
|
54
|
+
const WEBHOOK_MESSAGES = {
|
|
55
|
+
headers: "webhook headers are missing or invalid",
|
|
56
|
+
signature: "webhook signature verification failed",
|
|
57
|
+
timestamp: "webhook timestamp is invalid",
|
|
58
|
+
delivery: "webhook delivery signature verification failed",
|
|
59
|
+
body: "webhook body is invalid",
|
|
60
|
+
mismatch: "webhook payload does not match signed headers",
|
|
61
|
+
};
|
|
62
|
+
/** Verification failure. `message` is fixed for `reason` and does not include key material. */
|
|
63
|
+
export class WebhookVerificationError extends Error {
|
|
64
|
+
name = "WebhookVerificationError";
|
|
65
|
+
reason;
|
|
66
|
+
constructor(reason) {
|
|
67
|
+
super(WEBHOOK_MESSAGES[reason]);
|
|
68
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
69
|
+
this.reason = reason;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export function isRecord(value) {
|
|
73
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
74
|
+
}
|
|
75
|
+
export function invalidJsonMessage() {
|
|
76
|
+
return INVALID_JSON_MESSAGE;
|
|
77
|
+
}
|
|
78
|
+
export function fallbackErrorMessage() {
|
|
79
|
+
return FALLBACK_MESSAGE;
|
|
80
|
+
}
|
|
81
|
+
export function serverMessage(value) {
|
|
82
|
+
if (typeof value !== "string" || value.length === 0)
|
|
83
|
+
return FALLBACK_MESSAGE;
|
|
84
|
+
return capCodePoints(value, MAX_ERROR_MESSAGE_CODE_POINTS);
|
|
85
|
+
}
|
|
86
|
+
export function toRelayError(input) {
|
|
87
|
+
const init = {
|
|
88
|
+
status: input.status,
|
|
89
|
+
message: FALLBACK_MESSAGE,
|
|
90
|
+
};
|
|
91
|
+
if (input.requestId !== undefined)
|
|
92
|
+
init.requestId = input.requestId;
|
|
93
|
+
if (input.retryAfter !== undefined)
|
|
94
|
+
init.retryAfter = input.retryAfter;
|
|
95
|
+
if (!isRecord(input.parsed) || !isRecord(input.parsed.error)) {
|
|
96
|
+
return new RelayError(init);
|
|
97
|
+
}
|
|
98
|
+
const error = input.parsed.error;
|
|
99
|
+
init.message = serverMessage(error.message);
|
|
100
|
+
if (typeof error.code === "string" && isPrintableAscii(error.code) && error.code.length > 0 && error.code.length <= 64) {
|
|
101
|
+
init.code = error.code;
|
|
102
|
+
}
|
|
103
|
+
if (typeof error.existing_event_id === "string" && error.existing_event_id.length > 0) {
|
|
104
|
+
init.existingEventId = capCodePoints(error.existing_event_id, 200);
|
|
105
|
+
}
|
|
106
|
+
const issues = readIssues(error.issues);
|
|
107
|
+
if (issues !== undefined)
|
|
108
|
+
init.issues = issues;
|
|
109
|
+
const eventAllowance = readCount(error.event_allowance);
|
|
110
|
+
if (eventAllowance !== undefined)
|
|
111
|
+
init.eventAllowance = eventAllowance;
|
|
112
|
+
const eventsUsed = readCount(error.events_used);
|
|
113
|
+
if (eventsUsed !== undefined)
|
|
114
|
+
init.eventsUsed = eventsUsed;
|
|
115
|
+
const scheduleLimit = readCount(error.schedule_limit);
|
|
116
|
+
if (scheduleLimit !== undefined)
|
|
117
|
+
init.scheduleLimit = scheduleLimit;
|
|
118
|
+
if (error.expires_at === null) {
|
|
119
|
+
init.expiresAt = null;
|
|
120
|
+
}
|
|
121
|
+
else if (typeof error.expires_at === "string" && isPrintableAscii(error.expires_at) && error.expires_at.length <= 64) {
|
|
122
|
+
init.expiresAt = error.expires_at;
|
|
123
|
+
}
|
|
124
|
+
return new RelayError(init);
|
|
125
|
+
}
|
|
126
|
+
function readCount(value) {
|
|
127
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value))
|
|
128
|
+
return undefined;
|
|
129
|
+
return value;
|
|
130
|
+
}
|
|
131
|
+
function readIssues(value) {
|
|
132
|
+
if (!Array.isArray(value))
|
|
133
|
+
return undefined;
|
|
134
|
+
const issues = [];
|
|
135
|
+
for (const item of value) {
|
|
136
|
+
if (!isRecord(item) || typeof item.message !== "string" || !Array.isArray(item.path))
|
|
137
|
+
continue;
|
|
138
|
+
const path = [];
|
|
139
|
+
let valid = true;
|
|
140
|
+
for (const part of item.path) {
|
|
141
|
+
if (typeof part !== "string") {
|
|
142
|
+
valid = false;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
path.push(capCodePoints(part, 200));
|
|
146
|
+
}
|
|
147
|
+
if (!valid)
|
|
148
|
+
continue;
|
|
149
|
+
issues.push({ path, message: capCodePoints(item.message, MAX_ERROR_MESSAGE_CODE_POINTS) });
|
|
150
|
+
if (issues.length === 20)
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
return issues.length > 0 ? issues : undefined;
|
|
154
|
+
}
|
|
155
|
+
export function readRequestId(header) {
|
|
156
|
+
if (header === null)
|
|
157
|
+
return undefined;
|
|
158
|
+
const trimmed = header.trim();
|
|
159
|
+
if (trimmed.length === 0 || !isPrintableAscii(trimmed))
|
|
160
|
+
return undefined;
|
|
161
|
+
return capCodePoints(trimmed, 200);
|
|
162
|
+
}
|
|
163
|
+
/** `Retry-After` delta-seconds. HTTP-date values are ignored. */
|
|
164
|
+
export function readRetryAfter(header) {
|
|
165
|
+
if (header === null)
|
|
166
|
+
return undefined;
|
|
167
|
+
const trimmed = header.trim();
|
|
168
|
+
if (!/^[0-9]+$/.test(trimmed))
|
|
169
|
+
return undefined;
|
|
170
|
+
const value = Number(trimmed);
|
|
171
|
+
if (!Number.isSafeInteger(value))
|
|
172
|
+
return undefined;
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=errors.js.map
|