@pgns/core 0.1.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PGNS LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,238 @@
1
+ # @pgns/core
2
+
3
+ TypeScript SDK for the pgns API. Framework-agnostic — works in Node.js, Deno, Bun, edge runtimes, and the browser.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @pgns/core
9
+ # or
10
+ pnpm add @pgns/core
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ### With an API key (server-side)
16
+
17
+ ```ts
18
+ import { PigeonsClient } from "@pgns/core";
19
+
20
+ const client = new PigeonsClient({
21
+ baseUrl: "https://api.pgns.io",
22
+ apiKey: "pk_live_...",
23
+ });
24
+
25
+ const roosts = await client.listRoosts();
26
+ ```
27
+
28
+ ### With user authentication (browser)
29
+
30
+ ```ts
31
+ import { PigeonsClient } from "@pgns/core";
32
+
33
+ const client = new PigeonsClient({
34
+ baseUrl: "https://api.pgns.io",
35
+ onTokenRefresh(tokens) {
36
+ // Persist new tokens however you like
37
+ localStorage.setItem("access_token", tokens.access_token);
38
+ localStorage.setItem("refresh_token", tokens.refresh_token);
39
+ },
40
+ });
41
+
42
+ await client.login({ email: "user@example.com", password: "..." });
43
+ const roosts = await client.listRoosts();
44
+ ```
45
+
46
+ ## Authentication
47
+
48
+ The client supports two authentication modes:
49
+
50
+ | Mode | Use case | How |
51
+ |------|----------|-----|
52
+ | **API key** | Server-side / scripts | Pass `apiKey` to the constructor |
53
+ | **JWT** | Browser / user sessions | Call `login()` / `signup()`, or pass `accessToken` to the constructor |
54
+
55
+ When using JWT auth the client automatically retries on `401` by refreshing the token. Supply `onTokenRefresh` to persist new tokens.
56
+
57
+ You can switch credentials at any time:
58
+
59
+ ```ts
60
+ client.setApiKey("pk_live_new_key");
61
+ // or
62
+ client.setAccessToken("eyJhbG...");
63
+ ```
64
+
65
+ ## Roosts
66
+
67
+ Roosts are webhook endpoints that receive incoming requests and route them to destinations.
68
+
69
+ ```ts
70
+ // Create
71
+ const roost = await client.createRoost({
72
+ name: "GitHub Webhooks",
73
+ description: "Receives push events",
74
+ secret: "whsec_...", // optional HMAC verification secret
75
+ });
76
+
77
+ // List all
78
+ const roosts = await client.listRoosts();
79
+
80
+ // Get by ID
81
+ const roost = await client.getRoost("rst_01J...");
82
+
83
+ // Update
84
+ await client.updateRoost("rst_01J...", { name: "Renamed" });
85
+
86
+ // Delete
87
+ await client.deleteRoost("rst_01J...");
88
+ ```
89
+
90
+ ## Pigeons
91
+
92
+ Pigeons are individual webhook requests captured by a roost.
93
+
94
+ ```ts
95
+ // List pigeons for a roost (default limit: 50)
96
+ const pigeons = await client.listPigeons({ roostId: "rst_01J..." });
97
+
98
+ // List all pigeons across roosts
99
+ const all = await client.listPigeons();
100
+
101
+ // With custom limit
102
+ const recent = await client.listPigeons({ roostId: "rst_01J...", limit: 10 });
103
+
104
+ // Get a single pigeon
105
+ const pigeon = await client.getPigeon("pgn_01J...");
106
+
107
+ // Get delivery attempts for a pigeon
108
+ const deliveries = await client.getPigeonDeliveries("pgn_01J...");
109
+
110
+ // Replay a pigeon (re-deliver to all destinations)
111
+ const result = await client.replayPigeon("pgn_01J...");
112
+ // => { replayed: true, pigeon_id: "pgn_01J...", delivery_attempts: 2 }
113
+ ```
114
+
115
+ ## Destinations
116
+
117
+ Destinations define where pigeons get forwarded to — URLs, Slack, Discord, or email.
118
+
119
+ ```ts
120
+ // Create a URL destination
121
+ const dest = await client.createDestination("rst_01J...", {
122
+ destination_type: "url",
123
+ config: { url: "https://example.com/webhook" },
124
+ filter_expression: "headers.x-event == 'push'", // optional CEL filter
125
+ retry_max: 3,
126
+ retry_delay_ms: 1000,
127
+ retry_multiplier: 2,
128
+ });
129
+
130
+ // List destinations for a roost
131
+ const destinations = await client.listDestinations("rst_01J...");
132
+
133
+ // Get by ID
134
+ const dest = await client.getDestination("dst_01J...");
135
+
136
+ // Pause / unpause
137
+ await client.pauseDestination("dst_01J...", true); // pause
138
+ await client.pauseDestination("dst_01J...", false); // unpause
139
+
140
+ // Delete
141
+ await client.deleteDestination("dst_01J...");
142
+ ```
143
+
144
+ ### Destination types
145
+
146
+ | Type | Config |
147
+ |------|--------|
148
+ | `url` | `{ url: string }` |
149
+ | `slack` | `{ webhook_url: string }` |
150
+ | `discord` | `{ webhook_url: string }` |
151
+ | `email` | `{ to: string }` |
152
+
153
+ ## API Keys
154
+
155
+ Manage API keys for programmatic access.
156
+
157
+ ```ts
158
+ // Create (name is optional)
159
+ const created = await client.createApiKey({ name: "CI deploy key" });
160
+ // => { id: "...", key: "pk_live_...", key_prefix: "pk_live_abc", name: "CI deploy key", created_at: "..." }
161
+ // The full key is only returned at creation time — store it securely.
162
+
163
+ // List
164
+ const keys = await client.listApiKeys();
165
+
166
+ // Get
167
+ const key = await client.getApiKey("key_01J...");
168
+
169
+ // Rename
170
+ await client.updateApiKey("key_01J...", { name: "Renamed key" });
171
+
172
+ // Revoke
173
+ await client.deleteApiKey("key_01J...");
174
+ ```
175
+
176
+ ## Real-time Events (SSE)
177
+
178
+ Subscribe to a live stream of incoming pigeons using Server-Sent Events. Works in any runtime with `fetch` and `ReadableStream`.
179
+
180
+ ```ts
181
+ import { createEventSource } from "@pgns/core";
182
+
183
+ const controller = new AbortController();
184
+
185
+ createEventSource("https://api.pgns.io", {
186
+ token: "eyJhbG...",
187
+ roostId: "rst_01J...", // optional — omit for all roosts
188
+ signal: controller.signal,
189
+ onEvent(data) {
190
+ console.log("New pigeon:", data);
191
+ },
192
+ onError(err) {
193
+ console.error("SSE error:", err);
194
+ },
195
+ });
196
+
197
+ // Stop listening
198
+ controller.abort();
199
+ ```
200
+
201
+ The connection automatically reconnects on failure with a 3-second delay.
202
+
203
+ ## Error Handling
204
+
205
+ All API errors throw a `PigeonsError` with the HTTP status code attached.
206
+
207
+ ```ts
208
+ import { PigeonsClient, PigeonsError } from "@pgns/core";
209
+
210
+ try {
211
+ await client.getRoost("rst_nonexistent");
212
+ } catch (err) {
213
+ if (err instanceof PigeonsError) {
214
+ console.error(err.message); // "Not found"
215
+ console.error(err.status); // 404
216
+ }
217
+ }
218
+ ```
219
+
220
+ ## Zod Schemas
221
+
222
+ Every type has a corresponding Zod schema exported for runtime validation. Useful for validating webhook payloads or API responses in your own code.
223
+
224
+ ```ts
225
+ import { PigeonSchema, RoostSchema } from "@pgns/core";
226
+
227
+ const parsed = PigeonSchema.parse(untrustedData);
228
+ ```
229
+
230
+ Available schemas: `AuthTokensSchema`, `UserSchema`, `RoostSchema`, `PigeonSchema`, `DestinationSchema`, `DeliveryAttemptSchema`, `ApiKeyResponseSchema`, `ApiKeyCreatedResponseSchema`, `CreateRoostSchema`, `UpdateRoostSchema`, `CreateDestinationSchema`, `CreateApiKeyRequestSchema`, `UpdateApiKeyRequestSchema`, `ReplayResponseSchema`, `ApiErrorSchema`, `DestinationTypeSchema`, `DeliveryStatusSchema`.
231
+
232
+ ## TypeScript
233
+
234
+ All types are exported and inferred from the Zod schemas:
235
+
236
+ ```ts
237
+ import type { Roost, Pigeon, Destination, DeliveryAttempt } from "@pgns/core";
238
+ ```
@@ -0,0 +1,108 @@
1
+ import type { ApiKeyCreatedResponse, ApiKeyResponse, AuthTokens, CreateApiKeyRequest, CreateDestination, CreateRoost, DeliveryAttempt, Destination, LoginRequest, MagicLinkRequest, MagicLinkVerifyRequest, Pigeon, ReplayResponse, Roost, SignupRequest, UpdateApiKeyRequest, UpdateRoost } from './types.js';
2
+ /** Configuration for {@link PigeonsClient}. */
3
+ export interface PigeonsClientConfig {
4
+ /** Base URL of the pgns API (e.g. `"https://api.pgns.io"`). */
5
+ baseUrl: string;
6
+ /** API key (`pk_live_...`) for server-side authentication. */
7
+ apiKey?: string;
8
+ /** JWT access token for browser / user-session authentication. */
9
+ accessToken?: string;
10
+ /** Called after a token refresh so you can persist the new tokens. */
11
+ onTokenRefresh?: (tokens: AuthTokens) => void;
12
+ }
13
+ /**
14
+ * Framework-agnostic client for the pgns API.
15
+ *
16
+ * Supports two authentication modes:
17
+ * - **API key** — pass `apiKey` in the constructor for server-side usage.
18
+ * - **JWT** — call {@link login} / {@link signup}, or pass `accessToken`.
19
+ * Expired tokens are refreshed automatically on `401`.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const client = new PigeonsClient({
24
+ * baseUrl: "https://api.pgns.io",
25
+ * apiKey: "pk_live_...",
26
+ * });
27
+ * const roosts = await client.listRoosts();
28
+ * ```
29
+ */
30
+ export declare class PigeonsClient {
31
+ private baseUrl;
32
+ private apiKey?;
33
+ private accessToken?;
34
+ private onTokenRefresh?;
35
+ private refreshPromise;
36
+ constructor(config: PigeonsClientConfig);
37
+ /** Replace the current JWT access token. */
38
+ setAccessToken(token: string): void;
39
+ /** Replace the current API key. */
40
+ setApiKey(key: string): void;
41
+ private authHeader;
42
+ private handleResponse;
43
+ /** Coalesce concurrent refresh calls into a single request. */
44
+ private refreshToken;
45
+ private request;
46
+ private unauthRequest;
47
+ /** Create a new account. Stores the returned tokens on the client. */
48
+ signup(data: SignupRequest): Promise<AuthTokens>;
49
+ /** Authenticate with email and password. Stores the returned tokens on the client. */
50
+ login(data: LoginRequest): Promise<AuthTokens>;
51
+ /** Send a magic-link email to the given address. */
52
+ requestMagicLink(data: MagicLinkRequest): Promise<{
53
+ message: string;
54
+ }>;
55
+ /** Exchange a magic-link token for auth tokens. Stores the returned tokens on the client. */
56
+ verifyMagicLink(data: MagicLinkVerifyRequest): Promise<AuthTokens>;
57
+ /** Refresh the access token. The refresh token is sent via httpOnly cookie. */
58
+ refresh(): Promise<AuthTokens>;
59
+ /** Revoke the refresh token (via cookie) and clear stored credentials on the client. */
60
+ logout(): Promise<void>;
61
+ /** List all roosts for the authenticated user. */
62
+ listRoosts(): Promise<Roost[]>;
63
+ /** Get a roost by ID. */
64
+ getRoost(roostId: string): Promise<Roost>;
65
+ /** Create a new roost (webhook endpoint). */
66
+ createRoost(data: CreateRoost): Promise<Roost>;
67
+ /** Update a roost's name, description, secret, or active state. */
68
+ updateRoost(roostId: string, data: UpdateRoost): Promise<Roost>;
69
+ /** Delete a roost and all its destinations. */
70
+ deleteRoost(roostId: string): Promise<void>;
71
+ /**
72
+ * List pigeons, optionally filtered by roost.
73
+ * @param opts.roostId - Restrict results to a single roost. Omit for all roosts.
74
+ * @param opts.limit - Maximum number of results (default: 50).
75
+ */
76
+ listPigeons(opts?: {
77
+ roostId?: string;
78
+ limit?: number;
79
+ }): Promise<Pigeon[]>;
80
+ /** Get a single pigeon by ID, including headers and body. */
81
+ getPigeon(pigeonId: string): Promise<Pigeon>;
82
+ /** List all delivery attempts for a pigeon. */
83
+ getPigeonDeliveries(pigeonId: string): Promise<DeliveryAttempt[]>;
84
+ /** Re-deliver a pigeon to all active destinations. */
85
+ replayPigeon(pigeonId: string): Promise<ReplayResponse>;
86
+ /** List all destinations for a roost. */
87
+ listDestinations(roostId: string): Promise<Destination[]>;
88
+ /** Get a destination by ID. */
89
+ getDestination(destinationId: string): Promise<Destination>;
90
+ /** Add a new destination (url, slack, discord, or email) to a roost. */
91
+ createDestination(roostId: string, data: CreateDestination): Promise<Destination>;
92
+ /** Pause or unpause delivery to a destination. */
93
+ pauseDestination(destinationId: string, isPaused: boolean): Promise<{
94
+ is_paused: boolean;
95
+ }>;
96
+ /** Permanently delete a destination. */
97
+ deleteDestination(destinationId: string): Promise<void>;
98
+ /** List all API keys for the authenticated user. */
99
+ listApiKeys(): Promise<ApiKeyResponse[]>;
100
+ /** Get an API key by ID. Does not return the full key value. */
101
+ getApiKey(keyId: string): Promise<ApiKeyResponse>;
102
+ /** Create a new API key. The full key is only returned in this response — store it securely. */
103
+ createApiKey(data?: CreateApiKeyRequest): Promise<ApiKeyCreatedResponse>;
104
+ /** Rename an API key. */
105
+ updateApiKey(keyId: string, data: UpdateApiKeyRequest): Promise<ApiKeyResponse>;
106
+ /** Permanently revoke and delete an API key. */
107
+ deleteApiKey(keyId: string): Promise<void>;
108
+ }
package/dist/client.js ADDED
@@ -0,0 +1,278 @@
1
+ import { PigeonsError } from './errors.js';
2
+ /**
3
+ * Framework-agnostic client for the pgns API.
4
+ *
5
+ * Supports two authentication modes:
6
+ * - **API key** — pass `apiKey` in the constructor for server-side usage.
7
+ * - **JWT** — call {@link login} / {@link signup}, or pass `accessToken`.
8
+ * Expired tokens are refreshed automatically on `401`.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const client = new PigeonsClient({
13
+ * baseUrl: "https://api.pgns.io",
14
+ * apiKey: "pk_live_...",
15
+ * });
16
+ * const roosts = await client.listRoosts();
17
+ * ```
18
+ */
19
+ export class PigeonsClient {
20
+ baseUrl;
21
+ apiKey;
22
+ accessToken;
23
+ onTokenRefresh;
24
+ refreshPromise = null;
25
+ constructor(config) {
26
+ this.baseUrl = config.baseUrl.replace(/\/+$/, '');
27
+ this.apiKey = config.apiKey;
28
+ this.accessToken = config.accessToken;
29
+ this.onTokenRefresh = config.onTokenRefresh;
30
+ }
31
+ /** Replace the current JWT access token. */
32
+ setAccessToken(token) {
33
+ this.accessToken = token;
34
+ }
35
+ /** Replace the current API key. */
36
+ setApiKey(key) {
37
+ this.apiKey = key;
38
+ }
39
+ // -- Internal helpers --
40
+ authHeader() {
41
+ if (this.apiKey)
42
+ return { Authorization: `Bearer ${this.apiKey}` };
43
+ if (this.accessToken)
44
+ return { Authorization: `Bearer ${this.accessToken}` };
45
+ return {};
46
+ }
47
+ async handleResponse(res) {
48
+ if (!res.ok) {
49
+ const body = (await res.json().catch(() => ({ error: res.statusText })));
50
+ throw new PigeonsError(body.error, res.status);
51
+ }
52
+ if (res.status === 204)
53
+ return undefined;
54
+ return res.json();
55
+ }
56
+ /** Coalesce concurrent refresh calls into a single request. */
57
+ async refreshToken() {
58
+ if (this.refreshPromise)
59
+ return this.refreshPromise;
60
+ this.refreshPromise = this.unauthRequest('/v1/auth/refresh', {
61
+ method: 'POST',
62
+ }).finally(() => {
63
+ this.refreshPromise = null;
64
+ });
65
+ const tokens = await this.refreshPromise;
66
+ this.accessToken = tokens.access_token;
67
+ this.onTokenRefresh?.(tokens);
68
+ return tokens;
69
+ }
70
+ async request(path, options = {}) {
71
+ const res = await fetch(`${this.baseUrl}${path}`, {
72
+ ...options,
73
+ credentials: 'include',
74
+ headers: {
75
+ 'Content-Type': 'application/json',
76
+ ...this.authHeader(),
77
+ ...options.headers,
78
+ },
79
+ });
80
+ if (res.status === 401 && !this.apiKey) {
81
+ try {
82
+ const tokens = await this.refreshToken();
83
+ const retryRes = await fetch(`${this.baseUrl}${path}`, {
84
+ ...options,
85
+ credentials: 'include',
86
+ headers: {
87
+ 'Content-Type': 'application/json',
88
+ Authorization: `Bearer ${tokens.access_token}`,
89
+ ...options.headers,
90
+ },
91
+ });
92
+ return this.handleResponse(retryRes);
93
+ }
94
+ catch (err) {
95
+ this.accessToken = undefined;
96
+ throw new PigeonsError('Session expired', 401, err);
97
+ }
98
+ }
99
+ return this.handleResponse(res);
100
+ }
101
+ async unauthRequest(path, options = {}) {
102
+ const res = await fetch(`${this.baseUrl}${path}`, {
103
+ ...options,
104
+ credentials: 'include',
105
+ headers: {
106
+ 'Content-Type': 'application/json',
107
+ ...options.headers,
108
+ },
109
+ });
110
+ return this.handleResponse(res);
111
+ }
112
+ // -- Auth --
113
+ /** Create a new account. Stores the returned tokens on the client. */
114
+ async signup(data) {
115
+ const tokens = await this.unauthRequest('/v1/auth/signup', {
116
+ method: 'POST',
117
+ body: JSON.stringify(data),
118
+ });
119
+ this.accessToken = tokens.access_token;
120
+ this.onTokenRefresh?.(tokens);
121
+ return tokens;
122
+ }
123
+ /** Authenticate with email and password. Stores the returned tokens on the client. */
124
+ async login(data) {
125
+ const tokens = await this.unauthRequest('/v1/auth/login', {
126
+ method: 'POST',
127
+ body: JSON.stringify(data),
128
+ });
129
+ this.accessToken = tokens.access_token;
130
+ this.onTokenRefresh?.(tokens);
131
+ return tokens;
132
+ }
133
+ /** Send a magic-link email to the given address. */
134
+ async requestMagicLink(data) {
135
+ return this.unauthRequest('/v1/auth/magic-link', {
136
+ method: 'POST',
137
+ body: JSON.stringify(data),
138
+ });
139
+ }
140
+ /** Exchange a magic-link token for auth tokens. Stores the returned tokens on the client. */
141
+ async verifyMagicLink(data) {
142
+ const tokens = await this.unauthRequest('/v1/auth/magic-link/verify', {
143
+ method: 'POST',
144
+ body: JSON.stringify(data),
145
+ });
146
+ this.accessToken = tokens.access_token;
147
+ this.onTokenRefresh?.(tokens);
148
+ return tokens;
149
+ }
150
+ /** Refresh the access token. The refresh token is sent via httpOnly cookie. */
151
+ async refresh() {
152
+ const tokens = await this.unauthRequest('/v1/auth/refresh', {
153
+ method: 'POST',
154
+ });
155
+ this.accessToken = tokens.access_token;
156
+ this.onTokenRefresh?.(tokens);
157
+ return tokens;
158
+ }
159
+ /** Revoke the refresh token (via cookie) and clear stored credentials on the client. */
160
+ async logout() {
161
+ await this.request('/v1/auth/logout', {
162
+ method: 'POST',
163
+ });
164
+ this.accessToken = undefined;
165
+ }
166
+ // -- Roosts --
167
+ /** List all roosts for the authenticated user. */
168
+ listRoosts() {
169
+ return this.request('/v1/roosts');
170
+ }
171
+ /** Get a roost by ID. */
172
+ getRoost(roostId) {
173
+ return this.request(`/v1/roosts/${encodeURIComponent(roostId)}`);
174
+ }
175
+ /** Create a new roost (webhook endpoint). */
176
+ createRoost(data) {
177
+ return this.request('/v1/roosts', {
178
+ method: 'POST',
179
+ body: JSON.stringify(data),
180
+ });
181
+ }
182
+ /** Update a roost's name, description, secret, or active state. */
183
+ updateRoost(roostId, data) {
184
+ return this.request(`/v1/roosts/${encodeURIComponent(roostId)}`, {
185
+ method: 'PATCH',
186
+ body: JSON.stringify(data),
187
+ });
188
+ }
189
+ /** Delete a roost and all its destinations. */
190
+ deleteRoost(roostId) {
191
+ return this.request(`/v1/roosts/${encodeURIComponent(roostId)}`, { method: 'DELETE' });
192
+ }
193
+ // -- Pigeons --
194
+ /**
195
+ * List pigeons, optionally filtered by roost.
196
+ * @param opts.roostId - Restrict results to a single roost. Omit for all roosts.
197
+ * @param opts.limit - Maximum number of results (default: 50).
198
+ */
199
+ listPigeons(opts) {
200
+ const params = new URLSearchParams();
201
+ if (opts?.roostId)
202
+ params.set('roost_id', opts.roostId);
203
+ params.set('limit', String(opts?.limit ?? 50));
204
+ return this.request(`/v1/pigeons?${params.toString()}`);
205
+ }
206
+ /** Get a single pigeon by ID, including headers and body. */
207
+ getPigeon(pigeonId) {
208
+ return this.request(`/v1/pigeons/${encodeURIComponent(pigeonId)}`);
209
+ }
210
+ /** List all delivery attempts for a pigeon. */
211
+ getPigeonDeliveries(pigeonId) {
212
+ return this.request(`/v1/pigeons/${encodeURIComponent(pigeonId)}/deliveries`);
213
+ }
214
+ /** Re-deliver a pigeon to all active destinations. */
215
+ replayPigeon(pigeonId) {
216
+ return this.request(`/v1/pigeons/${encodeURIComponent(pigeonId)}/replay`, {
217
+ method: 'POST',
218
+ });
219
+ }
220
+ // -- Destinations --
221
+ /** List all destinations for a roost. */
222
+ listDestinations(roostId) {
223
+ return this.request(`/v1/roosts/${encodeURIComponent(roostId)}/destinations`);
224
+ }
225
+ /** Get a destination by ID. */
226
+ getDestination(destinationId) {
227
+ return this.request(`/v1/destinations/${encodeURIComponent(destinationId)}`);
228
+ }
229
+ /** Add a new destination (url, slack, discord, or email) to a roost. */
230
+ createDestination(roostId, data) {
231
+ return this.request(`/v1/roosts/${encodeURIComponent(roostId)}/destinations`, {
232
+ method: 'POST',
233
+ body: JSON.stringify(data),
234
+ });
235
+ }
236
+ /** Pause or unpause delivery to a destination. */
237
+ pauseDestination(destinationId, isPaused) {
238
+ return this.request(`/v1/destinations/${encodeURIComponent(destinationId)}/pause`, {
239
+ method: 'PATCH',
240
+ body: JSON.stringify({ is_paused: isPaused }),
241
+ });
242
+ }
243
+ /** Permanently delete a destination. */
244
+ deleteDestination(destinationId) {
245
+ return this.request(`/v1/destinations/${encodeURIComponent(destinationId)}`, {
246
+ method: 'DELETE',
247
+ });
248
+ }
249
+ // -- API Keys --
250
+ /** List all API keys for the authenticated user. */
251
+ listApiKeys() {
252
+ return this.request('/v1/api-keys');
253
+ }
254
+ /** Get an API key by ID. Does not return the full key value. */
255
+ getApiKey(keyId) {
256
+ return this.request(`/v1/api-keys/${encodeURIComponent(keyId)}`);
257
+ }
258
+ /** Create a new API key. The full key is only returned in this response — store it securely. */
259
+ createApiKey(data = {}) {
260
+ return this.request('/v1/api-keys', {
261
+ method: 'POST',
262
+ body: JSON.stringify(data),
263
+ });
264
+ }
265
+ /** Rename an API key. */
266
+ updateApiKey(keyId, data) {
267
+ return this.request(`/v1/api-keys/${encodeURIComponent(keyId)}`, {
268
+ method: 'PATCH',
269
+ body: JSON.stringify(data),
270
+ });
271
+ }
272
+ /** Permanently revoke and delete an API key. */
273
+ deleteApiKey(keyId) {
274
+ return this.request(`/v1/api-keys/${encodeURIComponent(keyId)}`, {
275
+ method: 'DELETE',
276
+ });
277
+ }
278
+ }
@@ -0,0 +1,6 @@
1
+ /** Error thrown by {@link PigeonsClient} when the API returns a non-2xx response. */
2
+ export declare class PigeonsError extends Error {
3
+ /** HTTP status code from the API response. */
4
+ status: number;
5
+ constructor(message: string, status: number, cause?: unknown);
6
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,10 @@
1
+ /** Error thrown by {@link PigeonsClient} when the API returns a non-2xx response. */
2
+ export class PigeonsError extends Error {
3
+ /** HTTP status code from the API response. */
4
+ status;
5
+ constructor(message, status, cause) {
6
+ super(message, cause !== undefined ? { cause } : undefined);
7
+ this.name = 'PigeonsError';
8
+ this.status = status;
9
+ }
10
+ }
@@ -0,0 +1,31 @@
1
+ /** Options for {@link createEventSource}. */
2
+ export interface EventSourceOptions {
3
+ /** Bearer token for authentication. */
4
+ token?: string;
5
+ /** Restrict the stream to pigeons for a single roost. Omit for all roosts. */
6
+ roostId?: string;
7
+ /** Abort signal to stop the connection. */
8
+ signal?: AbortSignal;
9
+ /** Called for each SSE `data:` line received. */
10
+ onEvent: (data: string) => void;
11
+ /** Called when a connection error occurs (before automatic reconnect). */
12
+ onError?: (err: Error) => void;
13
+ }
14
+ /**
15
+ * Connect to the Pgns SSE event stream using `fetch` + `ReadableStream`.
16
+ *
17
+ * Automatically reconnects on failure with a 3-second delay. Cancel the
18
+ * connection by aborting the signal passed in `opts`.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * const controller = new AbortController();
23
+ * createEventSource("https://api.pgns.io", {
24
+ * token: "eyJhbG...",
25
+ * signal: controller.signal,
26
+ * onEvent(data) { console.log(data); },
27
+ * });
28
+ * // later: controller.abort();
29
+ * ```
30
+ */
31
+ export declare function createEventSource(baseUrl: string, opts: EventSourceOptions): Promise<void>;