@railhook/node 2.12.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 +476 -0
- package/dist/__tests__/client.test.d.ts +1 -0
- package/dist/__tests__/client.test.js +105 -0
- package/dist/__tests__/incoming.test.d.ts +1 -0
- package/dist/__tests__/incoming.test.js +316 -0
- package/dist/__tests__/package-identity.test.d.ts +1 -0
- package/dist/__tests__/package-identity.test.js +19 -0
- package/dist/__tests__/standardWebhooks.test.d.ts +1 -0
- package/dist/__tests__/standardWebhooks.test.js +102 -0
- package/dist/__tests__/webhooks.test.d.ts +1 -0
- package/dist/__tests__/webhooks.test.js +190 -0
- package/dist/client.d.ts +103 -0
- package/dist/client.js +344 -0
- package/dist/errors.d.ts +28 -0
- package/dist/errors.js +53 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +37 -0
- package/dist/types.d.ts +293 -0
- package/dist/types.js +3 -0
- package/dist/webhooks.d.ts +83 -0
- package/dist/webhooks.js +241 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
# @railhook/node
|
|
2
|
+
|
|
3
|
+
Official Node.js SDK for [Railhook](https://github.com/vadymkykalo/railhook).
|
|
4
|
+
|
|
5
|
+
**Zero runtime dependencies.** This SDK talks to the API using Node's built-in
|
|
6
|
+
`node:https` module — no third-party HTTP client, no transitive dependency
|
|
7
|
+
tree to audit. `npm ls --prod` on this package prints nothing.
|
|
8
|
+
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
This SDK covers the **send + verify** surface of the API: Events, Endpoints,
|
|
12
|
+
Subscriptions, Deliveries, Incoming Sources, Incoming Events, and webhook
|
|
13
|
+
signature verification — 7 of the platform's 35 API controllers. It
|
|
14
|
+
does **not** wrap Transformations, Rules, Workflows, Schemas, DLQ, Analytics,
|
|
15
|
+
Usage, Alerts, Incidents, PII rules, Audit Log, Tunnels, API keys, Members,
|
|
16
|
+
or Projects. Those are dashboard/API-only today; use the [Generic
|
|
17
|
+
Requests](#generic-requests) escape hatch below to call them directly if you
|
|
18
|
+
need them before the SDK grows to cover them.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @railhook/node
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { Railhook } from '@railhook/node';
|
|
30
|
+
|
|
31
|
+
const client = new Railhook({
|
|
32
|
+
apiKey: process.env.RAILHOOK_API_KEY, // e.g. 'Kz1uAIM8VeJUQN7yGSYCst64WxNLabBHfOYbrPlJ1yk'
|
|
33
|
+
baseUrl: 'http://localhost:8080', // optional, defaults to localhost
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Send an event
|
|
37
|
+
const event = await client.events.send({
|
|
38
|
+
type: 'order.completed',
|
|
39
|
+
data: {
|
|
40
|
+
orderId: 'ord_12345',
|
|
41
|
+
amount: 99.99,
|
|
42
|
+
currency: 'USD',
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
console.log(`Event created: ${event.eventId}`);
|
|
47
|
+
console.log(`Deliveries created: ${event.deliveriesCreated}`);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## API Reference
|
|
51
|
+
|
|
52
|
+
### Events
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// Send event with idempotency key
|
|
56
|
+
const event = await client.events.send(
|
|
57
|
+
{ type: 'order.completed', data: { orderId: '123' } },
|
|
58
|
+
'unique-idempotency-key'
|
|
59
|
+
);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Endpoints
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// Create endpoint
|
|
66
|
+
const endpoint = await client.endpoints.create(projectId, {
|
|
67
|
+
url: 'https://api.example.com/webhooks',
|
|
68
|
+
description: 'Production webhooks',
|
|
69
|
+
enabled: true,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// List endpoints — the API paginates this one, so the endpoints are in .content
|
|
73
|
+
const page = await client.endpoints.list(projectId, { page: 0, size: 20 });
|
|
74
|
+
for (const endpoint of page.content) {
|
|
75
|
+
console.log(endpoint.url);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Update endpoint
|
|
79
|
+
await client.endpoints.update(projectId, endpointId, {
|
|
80
|
+
enabled: false,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Delete endpoint
|
|
84
|
+
await client.endpoints.delete(projectId, endpointId);
|
|
85
|
+
|
|
86
|
+
// Rotate secret
|
|
87
|
+
const updated = await client.endpoints.rotateSecret(projectId, endpointId);
|
|
88
|
+
console.log(`New secret: ${updated.secret}`);
|
|
89
|
+
|
|
90
|
+
// Test endpoint connectivity
|
|
91
|
+
const result = await client.endpoints.test(projectId, endpointId);
|
|
92
|
+
console.log(`Test ${result.success ? 'passed' : 'failed'}: ${result.latencyMs}ms`);
|
|
93
|
+
console.log(`${result.httpStatusCode} — ${result.message}`);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Subscriptions
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// Subscribe endpoint to an event type
|
|
100
|
+
const subscription = await client.subscriptions.create(projectId, {
|
|
101
|
+
endpointId: endpoint.id,
|
|
102
|
+
eventType: 'order.completed',
|
|
103
|
+
enabled: true,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// List subscriptions — a bare array; unlike endpoints, this one is not paginated
|
|
107
|
+
const subscriptions = await client.subscriptions.list(projectId);
|
|
108
|
+
|
|
109
|
+
// Update subscription
|
|
110
|
+
await client.subscriptions.update(projectId, subscriptionId, {
|
|
111
|
+
eventType: 'order.shipped',
|
|
112
|
+
enabled: true,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Delete subscription
|
|
116
|
+
await client.subscriptions.delete(projectId, subscriptionId);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Deliveries
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// List deliveries with filters
|
|
123
|
+
const deliveries = await client.deliveries.list(projectId, {
|
|
124
|
+
status: 'FAILED',
|
|
125
|
+
page: 0,
|
|
126
|
+
size: 20,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
console.log(`Total failed: ${deliveries.totalElements}`);
|
|
130
|
+
|
|
131
|
+
// Get delivery attempts
|
|
132
|
+
const attempts = await client.deliveries.getAttempts(deliveryId);
|
|
133
|
+
for (const attempt of attempts) {
|
|
134
|
+
console.log(`Attempt ${attempt.attemptNumber}: ${attempt.httpStatusCode} (${attempt.durationMs}ms)`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Replay failed delivery
|
|
138
|
+
await client.deliveries.replay(deliveryId);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Incoming Webhooks
|
|
142
|
+
|
|
143
|
+
Receive, validate, and forward webhooks from third-party providers (Stripe, GitHub, Twilio, etc.).
|
|
144
|
+
|
|
145
|
+
### Incoming Sources
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// Create an incoming source with HMAC verification
|
|
149
|
+
const source = await client.incomingSources.create(projectId, {
|
|
150
|
+
name: 'Stripe Webhooks',
|
|
151
|
+
slug: 'stripe',
|
|
152
|
+
providerType: 'STRIPE',
|
|
153
|
+
verificationMode: 'HMAC_GENERIC',
|
|
154
|
+
hmacSecret: 'whsec_...',
|
|
155
|
+
hmacHeaderName: 'Stripe-Signature',
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
console.log(`Ingress URL: ${source.ingressUrl}`);
|
|
159
|
+
|
|
160
|
+
// List sources
|
|
161
|
+
const sources = await client.incomingSources.list(projectId);
|
|
162
|
+
|
|
163
|
+
// Update source
|
|
164
|
+
await client.incomingSources.update(projectId, sourceId, {
|
|
165
|
+
name: 'Stripe Production',
|
|
166
|
+
rateLimitPerSecond: 100,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Delete source
|
|
170
|
+
await client.incomingSources.delete(projectId, sourceId);
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Incoming Destinations
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
// Add a forwarding destination
|
|
177
|
+
const dest = await client.incomingSources.createDestination(projectId, sourceId, {
|
|
178
|
+
url: 'https://your-api.com/webhooks/stripe',
|
|
179
|
+
enabled: true,
|
|
180
|
+
maxAttempts: 5,
|
|
181
|
+
timeoutSeconds: 30,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// List destinations
|
|
185
|
+
const dests = await client.incomingSources.listDestinations(projectId, sourceId);
|
|
186
|
+
|
|
187
|
+
// Update destination
|
|
188
|
+
await client.incomingSources.updateDestination(projectId, sourceId, destId, {
|
|
189
|
+
enabled: false,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Delete destination
|
|
193
|
+
await client.incomingSources.deleteDestination(projectId, sourceId, destId);
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Incoming Events
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
// List incoming events (with optional source filter)
|
|
200
|
+
const events = await client.incomingEvents.list(projectId, {
|
|
201
|
+
sourceId: source.id,
|
|
202
|
+
page: 0,
|
|
203
|
+
size: 20,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Get event details
|
|
207
|
+
const event = await client.incomingEvents.get(projectId, eventId);
|
|
208
|
+
|
|
209
|
+
// Get forward attempts for an event
|
|
210
|
+
const attempts = await client.incomingEvents.getAttempts(projectId, eventId);
|
|
211
|
+
|
|
212
|
+
// Replay event to all destinations
|
|
213
|
+
const result = await client.incomingEvents.replay(projectId, eventId);
|
|
214
|
+
console.log(`Replayed to ${result.destinationsCount} destinations`);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Webhook Signature Verification
|
|
218
|
+
|
|
219
|
+
Verify incoming webhooks in your endpoint:
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
import { verifySignature, constructEvent } from '@railhook/node';
|
|
223
|
+
|
|
224
|
+
app.post('/webhooks', (req, res) => {
|
|
225
|
+
const payload = req.body; // raw body string
|
|
226
|
+
const signature = req.headers['x-signature'];
|
|
227
|
+
const secret = process.env.WEBHOOK_SECRET;
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
// Option 1: Just verify
|
|
231
|
+
verifySignature(payload, signature, secret);
|
|
232
|
+
|
|
233
|
+
// Option 2: Verify and parse
|
|
234
|
+
const event = constructEvent(payload, req.headers, secret);
|
|
235
|
+
|
|
236
|
+
// event.data is the parsed body; event.eventId / event.deliveryId /
|
|
237
|
+
// event.timestamp come from the X-Event-Id / X-Delivery-Id / X-Timestamp
|
|
238
|
+
// headers. See "What lands on your endpoint" below for event.type.
|
|
239
|
+
console.log(`Delivery ${event.deliveryId} of event ${event.eventId}:`, event.data);
|
|
240
|
+
handleOrderCompleted(event.data);
|
|
241
|
+
|
|
242
|
+
res.status(200).send('OK');
|
|
243
|
+
} catch (err) {
|
|
244
|
+
console.error('Webhook verification failed:', err.message);
|
|
245
|
+
res.status(400).send('Invalid signature');
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### What lands on your endpoint
|
|
251
|
+
|
|
252
|
+
Railhook PUTs the event's **payload** on the wire, not an envelope. This:
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
await client.events.send({ type: 'order.completed', data: { orderId: 'ord_1' } });
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
arrives at your endpoint as the `data` object alone —
|
|
259
|
+
|
|
260
|
+
```http
|
|
261
|
+
POST /webhooks HTTP/1.1
|
|
262
|
+
Content-Type: application/json
|
|
263
|
+
X-Signature: t=1738000000000,v1=<hex hmac-sha256>
|
|
264
|
+
X-Timestamp: 1738000000000
|
|
265
|
+
X-Event-Id: 6f0e…
|
|
266
|
+
X-Delivery-Id: 91ab…
|
|
267
|
+
X-Sequence-Number: 0
|
|
268
|
+
Idempotency-Key: 6f0e…-<endpoint-id>
|
|
269
|
+
|
|
270
|
+
{"orderId":"ord_1"}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
So `constructEvent` fills `eventId`, `deliveryId` and `timestamp` from the
|
|
274
|
+
headers and `data` from the body, but **`type` is empty**: the event type is
|
|
275
|
+
not on the wire for a default subscription. Route on the payload, on the
|
|
276
|
+
endpoint you registered, or set the subscription's `payloadTemplate` to wrap
|
|
277
|
+
the event so `type` becomes part of the body.
|
|
278
|
+
|
|
279
|
+
The signature is computed over `` `${timestamp}.${rawBody}` `` with HMAC-SHA256
|
|
280
|
+
and the endpoint secret, and the server rejects timestamps more than **300
|
|
281
|
+
seconds** old — verify against the *raw* body bytes, before any JSON parse and
|
|
282
|
+
re-serialize.
|
|
283
|
+
|
|
284
|
+
### Express.js Example
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
import express from 'express';
|
|
288
|
+
import { constructEvent } from '@railhook/node';
|
|
289
|
+
|
|
290
|
+
const app = express();
|
|
291
|
+
|
|
292
|
+
// Important: Use raw body for signature verification
|
|
293
|
+
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
|
|
294
|
+
const event = constructEvent(
|
|
295
|
+
req.body.toString(),
|
|
296
|
+
req.headers,
|
|
297
|
+
process.env.WEBHOOK_SECRET
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
// Process event...
|
|
301
|
+
res.sendStatus(200);
|
|
302
|
+
});
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Error Handling
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
import {
|
|
309
|
+
RailhookError,
|
|
310
|
+
RateLimitError,
|
|
311
|
+
AuthenticationError,
|
|
312
|
+
ValidationError
|
|
313
|
+
} from '@railhook/node';
|
|
314
|
+
|
|
315
|
+
try {
|
|
316
|
+
await client.events.send({ type: 'test', data: {} });
|
|
317
|
+
} catch (err) {
|
|
318
|
+
if (err instanceof RateLimitError) {
|
|
319
|
+
// retryAfter is milliseconds. err.rateLimitInfo.reset is the raw
|
|
320
|
+
// X-RateLimit-Reset header, which the API sends in Unix *seconds*.
|
|
321
|
+
console.log(`Rate limited. Retry after ${err.retryAfter}ms`);
|
|
322
|
+
await sleep(err.retryAfter);
|
|
323
|
+
} else if (err instanceof AuthenticationError) {
|
|
324
|
+
console.error('Invalid API key');
|
|
325
|
+
} else if (err instanceof ValidationError) {
|
|
326
|
+
console.error('Validation failed:', err.fieldErrors);
|
|
327
|
+
} else if (err instanceof RailhookError) {
|
|
328
|
+
console.error(`Error ${err.status}: ${err.message}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Error Response Format
|
|
334
|
+
|
|
335
|
+
All API errors return a consistent JSON body:
|
|
336
|
+
|
|
337
|
+
```json
|
|
338
|
+
{
|
|
339
|
+
"error": "error_code",
|
|
340
|
+
"message": "Human-readable description",
|
|
341
|
+
"status": 400,
|
|
342
|
+
"fieldErrors": { "field": "reason" }
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
- **`error`** — machine-readable error code (`snake_case`), always present
|
|
347
|
+
- **`message`** — human-readable description, always present
|
|
348
|
+
- **`status`** — HTTP status code (integer), always present
|
|
349
|
+
- **`fieldErrors`** — field-level validation details (only present for `validation_error`)
|
|
350
|
+
|
|
351
|
+
### Error Codes Reference
|
|
352
|
+
|
|
353
|
+
| HTTP Status | `error` Code | SDK Exception | Description |
|
|
354
|
+
|---|---|---|---|
|
|
355
|
+
| 400 | `validation_error` | `ValidationError` | Invalid request parameters; see `fieldErrors` |
|
|
356
|
+
| 400 | `invalid_request` | `RailhookError` | Malformed or semantically invalid request |
|
|
357
|
+
| 401 | `unauthorized` | `AuthenticationError` | Missing or invalid API key / expired token |
|
|
358
|
+
| 403 | `forbidden` | `RailhookError` | Insufficient permissions for the action |
|
|
359
|
+
| 404 | `not_found` | `NotFoundError` | Requested resource does not exist |
|
|
360
|
+
| 413 | `payload_too_large` | `RailhookError` | Request body exceeds maximum allowed size |
|
|
361
|
+
| 422 | `unprocessable_entity` | `RailhookError` | Valid syntax but violates business rules |
|
|
362
|
+
| 429 | `rate_limit_exceeded` | `RateLimitError` | Too many requests; check `X-RateLimit-*` headers |
|
|
363
|
+
| 500 | `internal_error` | `RailhookError` | Unexpected server error |
|
|
364
|
+
|
|
365
|
+
## Generic Requests
|
|
366
|
+
|
|
367
|
+
As the API expands, you can call any endpoint directly without waiting for SDK updates:
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
// GET
|
|
371
|
+
const schemas = await client.get<any[]>('/api/v1/projects/proj_123/schemas');
|
|
372
|
+
|
|
373
|
+
// POST with body and idempotency key
|
|
374
|
+
const result = await client.post('/api/v1/some/new/endpoint', { key: 'value' }, 'idempotency-key');
|
|
375
|
+
|
|
376
|
+
// PUT
|
|
377
|
+
await client.put('/api/v1/projects/proj_123/settings', { timezone: 'UTC' });
|
|
378
|
+
|
|
379
|
+
// PATCH
|
|
380
|
+
await client.patch('/api/v1/projects/proj_123/settings', { timezone: 'UTC' });
|
|
381
|
+
|
|
382
|
+
// DELETE
|
|
383
|
+
await client.delete('/api/v1/projects/proj_123/tags/old-tag');
|
|
384
|
+
|
|
385
|
+
// Fully custom request (any HTTP method)
|
|
386
|
+
const data = await client.request<any>('OPTIONS', '/api/v1/some/path');
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
All generic methods use the same authentication, error handling, and rate-limit logic as the built-in methods.
|
|
390
|
+
|
|
391
|
+
## Configuration
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
const client = new Railhook({
|
|
395
|
+
apiKey: process.env.RAILHOOK_API_KEY, // Required: Your project API key
|
|
396
|
+
baseUrl: 'https://api.example.com', // Optional: API base URL (default: http://localhost:8080)
|
|
397
|
+
timeout: 30000, // Optional: Request timeout in ms (default: 30000)
|
|
398
|
+
});
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### Timeouts and retries
|
|
402
|
+
|
|
403
|
+
`timeout` is a per-request socket timeout; hitting it rejects with
|
|
404
|
+
`RailhookError` (`code: 'timeout'`, `status: 0`). A connection-level failure
|
|
405
|
+
rejects the same way with `code: 'network_error'`.
|
|
406
|
+
|
|
407
|
+
**The client does not retry.** One SDK call is exactly one HTTP request — no
|
|
408
|
+
backoff, no idempotent replay. That is deliberate: `events.send` accepts an
|
|
409
|
+
`Idempotency-Key`, so a retry policy belongs to the caller who knows whether
|
|
410
|
+
reissuing the request is safe. What *is* retried is the delivery itself, by the
|
|
411
|
+
platform, on the subscription's `retryDelays` ladder.
|
|
412
|
+
|
|
413
|
+
## Authentication
|
|
414
|
+
|
|
415
|
+
Every request the client makes carries `X-API-Key: <your key>` — the project
|
|
416
|
+
API key, created in the dashboard or via
|
|
417
|
+
`POST /api/v1/projects/{projectId}/api-keys`. The SDK never sends a bearer
|
|
418
|
+
token and has no login surface: JWT-authenticated endpoints (auth, projects,
|
|
419
|
+
organizations, members, API keys) are not part of it. Bootstrapping a project
|
|
420
|
+
and a key is a one-time step you do with the dashboard, the CLI, or plain
|
|
421
|
+
HTTP.
|
|
422
|
+
|
|
423
|
+
## TypeScript Support
|
|
424
|
+
|
|
425
|
+
This SDK is written in TypeScript and includes full type definitions:
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
import type {
|
|
429
|
+
Event,
|
|
430
|
+
EventResponse,
|
|
431
|
+
Endpoint,
|
|
432
|
+
Delivery,
|
|
433
|
+
DeliveryStatus
|
|
434
|
+
} from '@railhook/node';
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
## Development
|
|
438
|
+
|
|
439
|
+
### Running Tests
|
|
440
|
+
|
|
441
|
+
**Local (requires Node.js 16+):**
|
|
442
|
+
```bash
|
|
443
|
+
npm install
|
|
444
|
+
npm test
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
**Docker:**
|
|
448
|
+
```bash
|
|
449
|
+
docker run --rm -v $(pwd):/app -w /app node:20-alpine sh -c "npm install && npm test"
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Live-API smoke check
|
|
453
|
+
|
|
454
|
+
`npm test` stubs the transport, so it cannot see a renamed field. To drive the
|
|
455
|
+
SDK against a real instance:
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
make up # from the repo root
|
|
459
|
+
npm run smoke:live # SMOKE_API_BASE_URL overrides the target
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
It registers a throwaway org, walks endpoint → subscription → event →
|
|
463
|
+
deliveries → attempts → incoming, checks each error envelope, and verifies a
|
|
464
|
+
signature the running server itself produced. It is a script, not a test —
|
|
465
|
+
`jest.config.js` roots at `src/`, so `npm test` never collects it and still
|
|
466
|
+
passes with no backend.
|
|
467
|
+
|
|
468
|
+
### Building
|
|
469
|
+
|
|
470
|
+
```bash
|
|
471
|
+
npm run build
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
## License
|
|
475
|
+
|
|
476
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const client_1 = require("../client");
|
|
4
|
+
const errors_1 = require("../errors");
|
|
5
|
+
describe('Railhook Client', () => {
|
|
6
|
+
describe('constructor', () => {
|
|
7
|
+
it('should create client with API key', () => {
|
|
8
|
+
const client = new client_1.Railhook({ apiKey: 'test_api_key' });
|
|
9
|
+
expect(client).toBeInstanceOf(client_1.Railhook);
|
|
10
|
+
});
|
|
11
|
+
it('should throw error without API key', () => {
|
|
12
|
+
expect(() => new client_1.Railhook({ apiKey: '' })).toThrow('API key is required');
|
|
13
|
+
});
|
|
14
|
+
it('should use default base URL', () => {
|
|
15
|
+
const client = new client_1.Railhook({ apiKey: 'test_api_key' });
|
|
16
|
+
expect(client).toBeDefined();
|
|
17
|
+
});
|
|
18
|
+
it('should accept custom base URL', () => {
|
|
19
|
+
const client = new client_1.Railhook({
|
|
20
|
+
apiKey: 'test_api_key',
|
|
21
|
+
baseUrl: 'https://api.example.com',
|
|
22
|
+
});
|
|
23
|
+
expect(client).toBeDefined();
|
|
24
|
+
});
|
|
25
|
+
it('should accept custom timeout', () => {
|
|
26
|
+
const client = new client_1.Railhook({
|
|
27
|
+
apiKey: 'test_api_key',
|
|
28
|
+
timeout: 60000,
|
|
29
|
+
});
|
|
30
|
+
expect(client).toBeDefined();
|
|
31
|
+
});
|
|
32
|
+
it('should initialize all API modules', () => {
|
|
33
|
+
const client = new client_1.Railhook({ apiKey: 'test_api_key' });
|
|
34
|
+
expect(client.events).toBeDefined();
|
|
35
|
+
expect(client.endpoints).toBeDefined();
|
|
36
|
+
expect(client.subscriptions).toBeDefined();
|
|
37
|
+
expect(client.deliveries).toBeDefined();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
describe('Generic Request Methods', () => {
|
|
42
|
+
it('should expose get method', () => {
|
|
43
|
+
const client = new client_1.Railhook({ apiKey: 'test_api_key' });
|
|
44
|
+
expect(typeof client.get).toBe('function');
|
|
45
|
+
});
|
|
46
|
+
it('should expose post method', () => {
|
|
47
|
+
const client = new client_1.Railhook({ apiKey: 'test_api_key' });
|
|
48
|
+
expect(typeof client.post).toBe('function');
|
|
49
|
+
});
|
|
50
|
+
it('should expose put method', () => {
|
|
51
|
+
const client = new client_1.Railhook({ apiKey: 'test_api_key' });
|
|
52
|
+
expect(typeof client.put).toBe('function');
|
|
53
|
+
});
|
|
54
|
+
it('should expose patch method', () => {
|
|
55
|
+
const client = new client_1.Railhook({ apiKey: 'test_api_key' });
|
|
56
|
+
expect(typeof client.patch).toBe('function');
|
|
57
|
+
});
|
|
58
|
+
it('should expose delete method', () => {
|
|
59
|
+
const client = new client_1.Railhook({ apiKey: 'test_api_key' });
|
|
60
|
+
expect(typeof client.delete).toBe('function');
|
|
61
|
+
});
|
|
62
|
+
it('should expose request method for fully custom calls', () => {
|
|
63
|
+
const client = new client_1.Railhook({ apiKey: 'test_api_key' });
|
|
64
|
+
expect(typeof client.request).toBe('function');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe('Error Classes', () => {
|
|
68
|
+
describe('RailhookError', () => {
|
|
69
|
+
it('should have correct properties', () => {
|
|
70
|
+
const error = new errors_1.RailhookError('Test error', 500, 'test_code');
|
|
71
|
+
expect(error.message).toBe('Test error');
|
|
72
|
+
expect(error.status).toBe(500);
|
|
73
|
+
expect(error.code).toBe('test_code');
|
|
74
|
+
expect(error.name).toBe('RailhookError');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
describe('AuthenticationError', () => {
|
|
78
|
+
it('should have correct defaults', () => {
|
|
79
|
+
const error = new errors_1.AuthenticationError();
|
|
80
|
+
expect(error.message).toBe('Invalid API key');
|
|
81
|
+
expect(error.status).toBe(401);
|
|
82
|
+
expect(error.code).toBe('authentication_error');
|
|
83
|
+
expect(error.name).toBe('AuthenticationError');
|
|
84
|
+
});
|
|
85
|
+
it('should accept custom message', () => {
|
|
86
|
+
const error = new errors_1.AuthenticationError('Custom auth error');
|
|
87
|
+
expect(error.message).toBe('Custom auth error');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
describe('ValidationError', () => {
|
|
91
|
+
it('should have field errors', () => {
|
|
92
|
+
const fieldErrors = { email: 'Invalid email', url: 'Invalid URL' };
|
|
93
|
+
const error = new errors_1.ValidationError('Validation failed', fieldErrors);
|
|
94
|
+
expect(error.message).toBe('Validation failed');
|
|
95
|
+
expect(error.status).toBe(400);
|
|
96
|
+
expect(error.code).toBe('validation_error');
|
|
97
|
+
expect(error.fieldErrors).toEqual(fieldErrors);
|
|
98
|
+
});
|
|
99
|
+
it('should default to empty field errors', () => {
|
|
100
|
+
const error = new errors_1.ValidationError('Validation failed');
|
|
101
|
+
expect(error.fieldErrors).toEqual({});
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpZW50LnRlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvX190ZXN0c19fL2NsaWVudC50ZXN0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsc0NBQXFDO0FBQ3JDLHNDQUFnRjtBQUVoRixRQUFRLENBQUMsaUJBQWlCLEVBQUUsR0FBRyxFQUFFO0lBQy9CLFFBQVEsQ0FBQyxhQUFhLEVBQUUsR0FBRyxFQUFFO1FBQzNCLEVBQUUsQ0FBQyxtQ0FBbUMsRUFBRSxHQUFHLEVBQUU7WUFDM0MsTUFBTSxNQUFNLEdBQUcsSUFBSSxpQkFBUSxDQUFDLEVBQUUsTUFBTSxFQUFFLGNBQWMsRUFBRSxDQUFDLENBQUM7WUFDeEQsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDLGNBQWMsQ0FBQyxpQkFBUSxDQUFDLENBQUM7UUFDMUMsQ0FBQyxDQUFDLENBQUM7UUFFSCxFQUFFLENBQUMsb0NBQW9DLEVBQUUsR0FBRyxFQUFFO1lBQzVDLE1BQU0sQ0FBQyxHQUFHLEVBQUUsQ0FBQyxJQUFJLGlCQUFRLENBQUMsRUFBRSxNQUFNLEVBQUUsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxxQkFBcUIsQ0FBQyxDQUFDO1FBQzVFLENBQUMsQ0FBQyxDQUFDO1FBRUgsRUFBRSxDQUFDLDZCQUE2QixFQUFFLEdBQUcsRUFBRTtZQUNyQyxNQUFNLE1BQU0sR0FBRyxJQUFJLGlCQUFRLENBQUMsRUFBRSxNQUFNLEVBQUUsY0FBYyxFQUFFLENBQUMsQ0FBQztZQUN4RCxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUMsV0FBVyxFQUFFLENBQUM7UUFDL0IsQ0FBQyxDQUFDLENBQUM7UUFFSCxFQUFFLENBQUMsK0JBQStCLEVBQUUsR0FBRyxFQUFFO1lBQ3ZDLE1BQU0sTUFBTSxHQUFHLElBQUksaUJBQVEsQ0FBQztnQkFDMUIsTUFBTSxFQUFFLGNBQWM7Z0JBQ3RCLE9BQU8sRUFBRSx5QkFBeUI7YUFDbkMsQ0FBQyxDQUFDO1lB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QyxDQUFDO1lBQ3ZELE1BQU0sQ0FBQyxLQUFLLENBQUMsV0FBVyxDQUFDLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxDQUFDO1FBQ3hDLENBQUMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQyxDQUFDLENBQUM7QUFDTCxDQUFDLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IFJhaWxob29rIH0gZnJvbSAnLi4vY2xpZW50JztcbmltcG9ydCB7IFJhaWxob29rRXJyb3IsIEF1dGhlbnRpY2F0aW9uRXJyb3IsIFZhbGlkYXRpb25FcnJvciB9IGZyb20gJy4uL2Vycm9ycyc7XG5cbmRlc2NyaWJlKCdSYWlsaG9vayBDbGllbnQnLCAoKSA9PiB7XG4gIGRlc2NyaWJlKCdjb25zdHJ1Y3RvcicsICgpID0+IHtcbiAgICBpdCgnc2hvdWxkIGNyZWF0ZSBjbGllbnQgd2l0aCBBUEkga2V5JywgKCkgPT4ge1xuICAgICAgY29uc3QgY2xpZW50ID0gbmV3IFJhaWxob29rKHsgYXBpS2V5OiAndGVzdF9hcGlfa2V5JyB9KTtcbiAgICAgIGV4cGVjdChjbGllbnQpLnRvQmVJbnN0YW5jZU9mKFJhaWxob29rKTtcbiAgICB9KTtcblxuICAgIGl0KCdzaG91bGQgdGhyb3cgZXJyb3Igd2l0aG91dCBBUEkga2V5JywgKCkgPT4ge1xuICAgICAgZXhwZWN0KCgpID0+IG5ldyBSYWlsaG9vayh7IGFwaUtleTogJycgfSkpLnRvVGhyb3coJ0FQSSBrZXkgaXMgcmVxdWlyZWQnKTtcbiAgICB9KTtcblxuICAgIGl0KCdzaG91bGQgdXNlIGRlZmF1bHQgYmFzZSBVUkwnLCAoKSA9PiB7XG4gICAgICBjb25zdCBjbGllbnQgPSBuZXcgUmFpbGhvb2soeyBhcGlLZXk6ICd0ZXN0X2FwaV9rZXknIH0pO1xuICAgICAgZXhwZWN0KGNsaWVudCkudG9CZURlZmluZWQoKTtcbiAgICB9KTtcblxuICAgIGl0KCdzaG91bGQgYWNjZXB0IGN1c3RvbSBiYXNlIFVSTCcsICgpID0+IHtcbiAgICAgIGNvbnN0IGNsaWVudCA9IG5ldyBSYWlsaG9vayh7XG4gICAgICAgIGFwaUtleTogJ3Rlc3RfYXBpX2tleScsXG4gICAgICAgIGJhc2VVcmw6ICdodHRwczovL2FwaS5leGFtcGxlLmNvbScsXG4gICAgICB9KTtcbiAgICAgIGV4cGVjdChjbGllbnQpLnRvQmVEZWZpbmVkKCk7XG4gICAgfSk7XG5cbiAgICBpdCgnc2hvdWxkIGFjY2VwdCBjdXN0b20gdGltZW91dCcsICgpID0+IHtcbiAgICAgIGNvbnN0IGNsaWVudCA9IG5ldyBSYWlsaG9vayh7XG4gICAgICAgIGFwaUtleTogJ3Rlc3RfYXBpX2tleScsXG4gICAgICAgIHRpbWVvdXQ6IDYwMDAwLFxuICAgICAgfSk7XG4gICAgICBleHBlY3QoY2xpZW50KS50b0JlRGVmaW5lZCgpO1xuICAgIH0pO1xuXG4gICAgaXQoJ3Nob3VsZCBpbml0aWFsaXplIGFsbCBBUEkgbW9kdWxlcycsICgpID0+IHtcbiAgICAgIGNvbnN0IGNsaWVudCA9IG5ldyBSYWlsaG9vayh7IGFwaUtleTogJ3Rlc3RfYXBpX2tleScgfSk7XG4gICAgICBleHBlY3QoY2xpZW50LmV2ZW50cykudG9CZURlZmluZWQoKTtcbiAgICAgIGV4cGVjdChjbGllbnQuZW5kcG9pbnRzKS50b0JlRGVmaW5lZCgpO1xuICAgICAgZXhwZWN0KGNsaWVudC5zdWJzY3JpcHRpb25zKS50b0JlRGVmaW5lZCgpO1xuICAgICAgZXhwZWN0KGNsaWVudC5kZWxpdmVyaWVzKS50b0JlRGVmaW5lZCgpO1xuICAgIH0pO1xuICB9KTtcbn0pO1xuXG5kZXNjcmliZSgnR2VuZXJpYyBSZXF1ZXN0IE1ldGhvZHMnLCAoKSA9PiB7XG4gIGl0KCdzaG91bGQgZXhwb3NlIGdldCBtZXRob2QnLCAoKSA9PiB7XG4gICAgY29uc3QgY2xpZW50ID0gbmV3IFJhaWxob29rKHsgYXBpS2V5OiAndGVzdF9hcGlfa2V5JyB9KTtcbiAgICBleHBlY3QodHlwZW9mIGNsaWVudC5nZXQpLnRvQmUoJ2Z1bmN0aW9uJyk7XG4gIH0pO1xuXG4gIGl0KCdzaG91bGQgZXhwb3NlIHBvc3QgbWV0aG9kJywgKCkgPT4ge1xuICAgIGNvbnN0IGNsaWVudCA9IG5ldyBSYWlsaG9vayh7IGFwaUtleTogJ3Rlc3RfYXBpX2tleScgfSk7XG4gICAgZXhwZWN0KHR5cGVvZiBjbGllbnQucG9zdCkudG9CZSgnZnVuY3Rpb24nKTtcbiAgfSk7XG5cbiAgaXQoJ3Nob3VsZCBleHBvc2UgcHV0IG1ldGhvZCcsICgpID0+IHtcbiAgICBjb25zdCBjbGllbnQgPSBuZXcgUmFpbGhvb2soeyBhcGlLZXk6ICd0ZXN0X2FwaV9rZXknIH0pO1xuICAgIGV4cGVjdCh0eXBlb2YgY2xpZW50LnB1dCkudG9CZSgnZnVuY3Rpb24nKTtcbiAgfSk7XG5cbiAgaXQoJ3Nob3VsZCBleHBvc2UgcGF0Y2ggbWV0aG9kJywgKCkgPT4ge1xuICAgIGNvbnN0IGNsaWVudCA9IG5ldyBSYWlsaG9vayh7IGFwaUtleTogJ3Rlc3RfYXBpX2tleScgfSk7XG4gICAgZXhwZWN0KHR5cGVvZiBjbGllbnQucGF0Y2gpLnRvQmUoJ2Z1bmN0aW9uJyk7XG4gIH0pO1xuXG4gIGl0KCdzaG91bGQgZXhwb3NlIGRlbGV0ZSBtZXRob2QnLCAoKSA9PiB7XG4gICAgY29uc3QgY2xpZW50ID0gbmV3IFJhaWxob29rKHsgYXBpS2V5OiAndGVzdF9hcGlfa2V5JyB9KTtcbiAgICBleHBlY3QodHlwZW9mIGNsaWVudC5kZWxldGUpLnRvQmUoJ2Z1bmN0aW9uJyk7XG4gIH0pO1xuXG4gIGl0KCdzaG91bGQgZXhwb3NlIHJlcXVlc3QgbWV0aG9kIGZvciBmdWxseSBjdXN0b20gY2FsbHMnLCAoKSA9PiB7XG4gICAgY29uc3QgY2xpZW50ID0gbmV3IFJhaWxob29rKHsgYXBpS2V5OiAndGVzdF9hcGlfa2V5JyB9KTtcbiAgICBleHBlY3QodHlwZW9mIGNsaWVudC5yZXF1ZXN0KS50b0JlKCdmdW5jdGlvbicpO1xuICB9KTtcbn0pO1xuXG5kZXNjcmliZSgnRXJyb3IgQ2xhc3NlcycsICgpID0+IHtcbiAgZGVzY3JpYmUoJ1JhaWxob29rRXJyb3InLCAoKSA9PiB7XG4gICAgaXQoJ3Nob3VsZCBoYXZlIGNvcnJlY3QgcHJvcGVydGllcycsICgpID0+IHtcbiAgICAgIGNvbnN0IGVycm9yID0gbmV3IFJhaWxob29rRXJyb3IoJ1Rlc3QgZXJyb3InLCA1MDAsICd0ZXN0X2NvZGUnKTtcbiAgICAgIGV4cGVjdChlcnJvci5tZXNzYWdlKS50b0JlKCdUZXN0IGVycm9yJyk7XG4gICAgICBleHBlY3QoZXJyb3Iuc3RhdHVzKS50b0JlKDUwMCk7XG4gICAgICBleHBlY3QoZXJyb3IuY29kZSkudG9CZSgndGVzdF9jb2RlJyk7XG4gICAgICBleHBlY3QoZXJyb3IubmFtZSkudG9CZSgnUmFpbGhvb2tFcnJvcicpO1xuICAgIH0pO1xuICB9KTtcblxuICBkZXNjcmliZSgnQXV0aGVudGljYXRpb25FcnJvcicsICgpID0+IHtcbiAgICBpdCgnc2hvdWxkIGhhdmUgY29ycmVjdCBkZWZhdWx0cycsICgpID0+IHtcbiAgICAgIGNvbnN0IGVycm9yID0gbmV3IEF1dGhlbnRpY2F0aW9uRXJyb3IoKTtcbiAgICAgIGV4cGVjdChlcnJvci5tZXNzYWdlKS50b0JlKCdJbnZhbGlkIEFQSSBrZXknKTtcbiAgICAgIGV4cGVjdChlcnJvci5zdGF0dXMpLnRvQmUoNDAxKTtcbiAgICAgIGV4cGVjdChlcnJvci5jb2RlKS50b0JlKCdhdXRoZW50aWNhdGlvbl9lcnJvcicpO1xuICAgICAgZXhwZWN0KGVycm9yLm5hbWUpLnRvQmUoJ0F1dGhlbnRpY2F0aW9uRXJyb3InKTtcbiAgICB9KTtcblxuICAgIGl0KCdzaG91bGQgYWNjZXB0IGN1c3RvbSBtZXNzYWdlJywgKCkgPT4ge1xuICAgICAgY29uc3QgZXJyb3IgPSBuZXcgQXV0aGVudGljYXRpb25FcnJvcignQ3VzdG9tIGF1dGggZXJyb3InKTtcbiAgICAgIGV4cGVjdChlcnJvci5tZXNzYWdlKS50b0JlKCdDdXN0b20gYXV0aCBlcnJvcicpO1xuICAgIH0pO1xuICB9KTtcblxuICBkZXNjcmliZSgnVmFsaWRhdGlvbkVycm9yJywgKCkgPT4ge1xuICAgIGl0KCdzaG91bGQgaGF2ZSBmaWVsZCBlcnJvcnMnLCAoKSA9PiB7XG4gICAgICBjb25zdCBmaWVsZEVycm9ycyA9IHsgZW1haWw6ICdJbnZhbGlkIGVtYWlsJywgdXJsOiAnSW52YWxpZCBVUkwnIH07XG4gICAgICBjb25zdCBlcnJvciA9IG5ldyBWYWxpZGF0aW9uRXJyb3IoJ1ZhbGlkYXRpb24gZmFpbGVkJywgZmllbGRFcnJvcnMpO1xuICAgICAgZXhwZWN0KGVycm9yLm1lc3NhZ2UpLnRvQmUoJ1ZhbGlkYXRpb24gZmFpbGVkJyk7XG4gICAgICBleHBlY3QoZXJyb3Iuc3RhdHVzKS50b0JlKDQwMCk7XG4gICAgICBleHBlY3QoZXJyb3IuY29kZSkudG9CZSgndmFsaWRhdGlvbl9lcnJvcicpO1xuICAgICAgZXhwZWN0KGVycm9yLmZpZWxkRXJyb3JzKS50b0VxdWFsKGZpZWxkRXJyb3JzKTtcbiAgICB9KTtcblxuICAgIGl0KCdzaG91bGQgZGVmYXVsdCB0byBlbXB0eSBmaWVsZCBlcnJvcnMnLCAoKSA9PiB7XG4gICAgICBjb25zdCBlcnJvciA9IG5ldyBWYWxpZGF0aW9uRXJyb3IoJ1ZhbGlkYXRpb24gZmFpbGVkJyk7XG4gICAgICBleHBlY3QoZXJyb3IuZmllbGRFcnJvcnMpLnRvRXF1YWwoe30pO1xuICAgIH0pO1xuICB9KTtcbn0pO1xuIl19
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|