@hogsend/client 0.6.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 ADDED
@@ -0,0 +1,93 @@
1
+ Elastic License 2.0 (ELv2)
2
+
3
+ Copyright 2025 Doug Silkstone and Hogsend contributors
4
+
5
+ ## Acceptance
6
+
7
+ By using the software, you agree to all of the terms and conditions below.
8
+
9
+ ## Copyright License
10
+
11
+ The licensor grants you a non-exclusive, royalty-free, worldwide,
12
+ non-sublicensable, non-transferable license to use, copy, distribute, make
13
+ available, and prepare derivative works of the software, in each case subject to
14
+ the limitations and conditions below.
15
+
16
+ ## Limitations
17
+
18
+ You may not provide the software to third parties as a hosted or managed
19
+ service, where the service provides users with access to any substantial set of
20
+ the features or functionality of the software.
21
+
22
+ You may not move, change, disable, or circumvent the license key functionality
23
+ in the software, and you may not remove or obscure any functionality in the
24
+ software that is protected by the license key.
25
+
26
+ You may not alter, remove, or obscure any licensing, copyright, or other notices
27
+ of the licensor in the software. Any use of the licensor's trademarks is subject
28
+ to applicable law.
29
+
30
+ ## Patents
31
+
32
+ The licensor grants you a license, under any patent claims the licensor can
33
+ license, or becomes able to license, to make, have made, use, sell, offer for
34
+ sale, import and have imported the software, in each case subject to the
35
+ limitations and conditions in this license. This license does not cover any
36
+ patent claims that you cause to be infringed by modifications or additions to
37
+ the software. If you or your company make any written claim that the software
38
+ infringes or contributes to infringement of any patent, your patent license for
39
+ the software granted under these terms ends immediately. If your company makes
40
+ such a claim, your patent license ends immediately for work on behalf of your
41
+ company.
42
+
43
+ ## Notices
44
+
45
+ You must ensure that anyone who gets a copy of any part of the software from you
46
+ also gets a copy of these terms.
47
+
48
+ If you modify the software, you must include in any modified copies of the
49
+ software prominent notices stating that you have modified the software.
50
+
51
+ ## No Other Rights
52
+
53
+ These terms do not imply any licenses other than those expressly granted in
54
+ these terms.
55
+
56
+ ## Termination
57
+
58
+ If you use the software in violation of these terms, such use is not licensed,
59
+ and your licenses will automatically terminate. If the licensor provides you
60
+ with a notice of your violation, and you cease all violation of this license no
61
+ later than 30 days after you receive that notice, your licenses will be
62
+ reinstated retroactively. However, if you violate these terms after such
63
+ reinstatement, any additional violation of these terms will cause your licenses
64
+ to terminate automatically and permanently.
65
+
66
+ ## No Liability
67
+
68
+ *As far as the law allows, the software comes as is, without any warranty or
69
+ condition, and the licensor will not be liable to you for any damages arising out
70
+ of these terms or the use or nature of the software, under any kind of legal
71
+ claim.*
72
+
73
+ ## Definitions
74
+
75
+ The **licensor** is the entity offering these terms, and the **software** is the
76
+ software the licensor makes available under these terms, including any portion
77
+ of it.
78
+
79
+ **you** refers to the individual or entity agreeing to these terms.
80
+
81
+ **your company** is any legal entity, sole proprietorship, or other kind of
82
+ organization that you work for, plus all organizations that have control over,
83
+ are under the control of, or are under common control with that organization.
84
+ **control** means ownership of substantially all the assets of an entity, or the
85
+ power to direct its management and policies by vote, contract, or otherwise.
86
+ Control can be direct or indirect.
87
+
88
+ **your licenses** are all the licenses granted to you for the software under
89
+ these terms.
90
+
91
+ **use** means anything you do with the software requiring one of your licenses.
92
+
93
+ **trademark** means trademarks, service marks, and similar rights.
package/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # @hogsend/client
2
+
3
+ Typed HTTP client for the [Hogsend](https://hogsend.com) data plane — contacts,
4
+ events, transactional emails, and lists. Thin wrapper over native `fetch`, no
5
+ heavy dependencies, ships compiled ESM + CJS + `.d.ts`.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pnpm add @hogsend/client
11
+ ```
12
+
13
+ For fully type-checked `emails.send`, also install `@hogsend/email` (a
14
+ **type-only optional peer**) and augment its template registry in your app.
15
+ Without it, the `emails.send` _shape_ degrades to `{ template: string; props? }`.
16
+
17
+ > Note: the shipped `.d.ts` references `@hogsend/email` by module name. If you
18
+ > do NOT install the optional peer, keep `skipLibCheck: true` in your tsconfig
19
+ > (the default for most app scaffolds) — otherwise `tsc` emits `TS2307: Cannot
20
+ > find module '@hogsend/email'` from this package's declarations. Installing the
21
+ > peer (even for types only) removes the caveat entirely. The runtime JS has no
22
+ > dependency on `@hogsend/email`.
23
+
24
+ ## Usage
25
+
26
+ ```ts
27
+ import { Hogsend } from "@hogsend/client";
28
+
29
+ const hs = new Hogsend({
30
+ baseUrl: "https://api.example.com",
31
+ apiKey: process.env.HOGSEND_DATA_KEY!, // hsk_… key with the `ingest` scope
32
+ });
33
+
34
+ // Contacts ----------------------------------------------------------------
35
+ await hs.contacts.upsert({
36
+ email: "ada@example.com",
37
+ userId: "u_1",
38
+ properties: { plan: "pro" },
39
+ lists: { newsletter: true },
40
+ }); // -> { id, created, linked }
41
+
42
+ const found = await hs.contacts.find({ email: "ada@example.com" }); // Contact[]
43
+ await hs.contacts.delete({ userId: "u_1" }); // -> { deleted }
44
+
45
+ // Events ------------------------------------------------------------------
46
+ await hs.events.send({
47
+ userId: "u_1",
48
+ name: "signup",
49
+ eventProperties: { source: "landing" }, // → trigger.where / exitOn
50
+ contactProperties: { country: "GB" }, // → contact record
51
+ idempotencyKey: "evt_abc",
52
+ }); // -> { stored, exits: [{ journeyId, stateId, exited }] }
53
+
54
+ hs.events.track(/* … */); // alias of events.send
55
+
56
+ // Emails ------------------------------------------------------------------
57
+ await hs.emails.send({
58
+ to: "ada@example.com",
59
+ template: "welcome",
60
+ props: { name: "Ada" },
61
+ }); // -> { emailSendId, status }
62
+
63
+ // Lists -------------------------------------------------------------------
64
+ await hs.lists.list(); // -> ListSummary[]
65
+ await hs.lists.subscribe({ list: "newsletter", email: "ada@example.com" });
66
+ await hs.lists.unsubscribe({ list: "newsletter", userId: "u_1" });
67
+ ```
68
+
69
+ ### Identity
70
+
71
+ Every write takes an **identity** — at least one of `email` or `userId`
72
+ (your external id). Both may be supplied; the type union and a runtime guard
73
+ enforce that at least one is present.
74
+
75
+ ## Options
76
+
77
+ | Option | Type | Default | Notes |
78
+ | ----------- | -------------------------- | --------- | ----------------------------------------- |
79
+ | `baseUrl` | `string` | — | API base, e.g. `https://api.example.com`. |
80
+ | `apiKey` | `string` | — | Data-plane `hsk_…` key (`ingest` scope). |
81
+ | `fetch` | `typeof fetch` | global | Override for tests / custom agents. |
82
+ | `timeoutMs` | `number` | `30000` | Per-request timeout (aborts the request). |
83
+ | `headers` | `Record<string, string>` | `{}` | Extra headers on every request. |
84
+
85
+ ## Errors
86
+
87
+ All non-2xx responses (and transport failures) throw typed errors:
88
+
89
+ ```ts
90
+ import { HogsendAPIError, RateLimitError } from "@hogsend/client";
91
+
92
+ try {
93
+ await hs.emails.send({ to: "x@y.com", template: "welcome", props: {} });
94
+ } catch (err) {
95
+ if (err instanceof RateLimitError) {
96
+ // 429 — back off for err.retryAfter seconds
97
+ } else if (err instanceof HogsendAPIError) {
98
+ // err.status (0 = transport failure), err.body (parsed JSON or raw text)
99
+ }
100
+ }
101
+ ```
102
+
103
+ - `HogsendAPIError` — `{ status, body }`. `status === 0` means the request never
104
+ reached the server (DNS/connect/timeout).
105
+ - `RateLimitError extends HogsendAPIError` — `status === 429`, with `retryAfter`
106
+ (seconds, from the `Retry-After` header) when present.
107
+
108
+ ## License
109
+
110
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,372 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Hogsend: () => Hogsend,
24
+ HogsendAPIError: () => HogsendAPIError,
25
+ RateLimitError: () => RateLimitError
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/errors.ts
30
+ var HogsendAPIError = class extends Error {
31
+ status;
32
+ body;
33
+ constructor(message, status, body) {
34
+ super(message);
35
+ this.name = "HogsendAPIError";
36
+ this.status = status;
37
+ this.body = body;
38
+ Object.setPrototypeOf(this, new.target.prototype);
39
+ }
40
+ };
41
+ var RateLimitError = class extends HogsendAPIError {
42
+ retryAfter;
43
+ constructor(message, body, retryAfter) {
44
+ super(message, 429, body);
45
+ this.name = "RateLimitError";
46
+ this.retryAfter = retryAfter;
47
+ Object.setPrototypeOf(this, new.target.prototype);
48
+ }
49
+ };
50
+
51
+ // src/internal/http.ts
52
+ var DEFAULT_TIMEOUT_MS = 3e4;
53
+ function buildUrl(baseUrl, path, query) {
54
+ const url = new URL(path.startsWith("/") ? path : `/${path}`, `${baseUrl}/`);
55
+ if (query) {
56
+ for (const [key, value] of Object.entries(query)) {
57
+ if (value === void 0) continue;
58
+ url.searchParams.set(key, String(value));
59
+ }
60
+ }
61
+ return url.toString();
62
+ }
63
+ function bodyMessage(status, body) {
64
+ if (body && typeof body === "object") {
65
+ const errField = body.error;
66
+ if (typeof errField === "string") {
67
+ return `${status}: ${errField}`;
68
+ }
69
+ if (body.success === false && errField && typeof errField === "object") {
70
+ const summary = JSON.stringify(errField).slice(0, 200);
71
+ return `${status}: validation failed ${summary}`;
72
+ }
73
+ }
74
+ return `request failed with status ${status}`;
75
+ }
76
+ function parseRetryAfter(value) {
77
+ if (!value) return void 0;
78
+ const seconds = Number.parseInt(value, 10);
79
+ return Number.isFinite(seconds) ? seconds : void 0;
80
+ }
81
+ function createHttpClient(config) {
82
+ const doFetch = config.fetch ?? fetch;
83
+ const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
84
+ const extraHeaders = config.headers ?? {};
85
+ async function request(method, path, opts) {
86
+ const headers = {
87
+ Accept: "application/json",
88
+ Authorization: `Bearer ${config.apiKey}`,
89
+ ...extraHeaders
90
+ };
91
+ if (opts.body !== void 0) {
92
+ headers["Content-Type"] = "application/json";
93
+ }
94
+ if (opts.extras?.idempotencyKey) {
95
+ headers["Idempotency-Key"] = opts.extras.idempotencyKey;
96
+ }
97
+ const url = buildUrl(config.baseUrl, path, opts.query);
98
+ const controller = new AbortController();
99
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
100
+ let res;
101
+ try {
102
+ res = await doFetch(url, {
103
+ method,
104
+ headers,
105
+ body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0,
106
+ signal: controller.signal
107
+ });
108
+ } catch (cause) {
109
+ const msg = cause instanceof Error ? cause.message : String(cause);
110
+ throw new HogsendAPIError(
111
+ `cannot reach ${config.baseUrl} (${msg})`,
112
+ 0,
113
+ void 0
114
+ );
115
+ } finally {
116
+ clearTimeout(timer);
117
+ }
118
+ const text = await res.text();
119
+ let parsed;
120
+ if (text.length > 0) {
121
+ try {
122
+ parsed = JSON.parse(text);
123
+ } catch {
124
+ parsed = text;
125
+ }
126
+ }
127
+ if (!res.ok) {
128
+ if (res.status === 429) {
129
+ throw new RateLimitError(
130
+ bodyMessage(res.status, parsed),
131
+ parsed,
132
+ parseRetryAfter(res.headers.get("Retry-After"))
133
+ );
134
+ }
135
+ throw new HogsendAPIError(
136
+ bodyMessage(res.status, parsed),
137
+ res.status,
138
+ parsed
139
+ );
140
+ }
141
+ return parsed;
142
+ }
143
+ return {
144
+ get: (path, query) => request("GET", path, { query }),
145
+ post: (path, body, extras) => request("POST", path, { body, extras }),
146
+ put: (path, body, extras) => request("PUT", path, { body, extras }),
147
+ del: (path, body) => request("DELETE", path, { body })
148
+ };
149
+ }
150
+
151
+ // src/resources/campaigns.ts
152
+ var CampaignsResource = class {
153
+ constructor(http) {
154
+ this.http = http;
155
+ }
156
+ http;
157
+ /**
158
+ * Queue a broadcast: durably send one template to every subscribed member of
159
+ * a `list` (or every active member of a `bucket`). Exactly one of `list` /
160
+ * `bucket` must be set; `template`/`props` are type-checked against the
161
+ * augmented `TemplateRegistryMap` when `@hogsend/email` is installed, else
162
+ * degrade to `{ template: string; props? }`.
163
+ *
164
+ * Returns the 202 enqueue ack (`{ campaignId, status }`); the actual sends run
165
+ * asynchronously in the worker. Poll {@link CampaignsResource.get} for counts.
166
+ */
167
+ send(input) {
168
+ const body = input;
169
+ return this.http.post("/v1/campaigns", {
170
+ name: body.name,
171
+ list: body.list,
172
+ bucket: body.bucket,
173
+ template: body.template,
174
+ props: body.props,
175
+ from: body.from,
176
+ subject: body.subject
177
+ });
178
+ }
179
+ /** Fetch a campaign's current status + send counts. */
180
+ get(id) {
181
+ return this.http.get(`/v1/campaigns/${encodeURIComponent(id)}`);
182
+ }
183
+ };
184
+
185
+ // src/internal/identity.ts
186
+ function assertIdentity(input) {
187
+ const hasEmail = typeof input.email === "string" && input.email.length > 0;
188
+ const hasUserId = typeof input.userId === "string" && input.userId.length > 0;
189
+ if (!hasEmail && !hasUserId) {
190
+ throw new TypeError(
191
+ "Hogsend: an identity is required \u2014 pass `email`, `userId`, or both."
192
+ );
193
+ }
194
+ }
195
+
196
+ // src/resources/contacts.ts
197
+ var ContactsResource = class {
198
+ constructor(http) {
199
+ this.http = http;
200
+ }
201
+ http;
202
+ /**
203
+ * Upsert a contact by identity. Resolves/merges server-side and optionally
204
+ * applies list membership. Returns `{ id, created, linked }`.
205
+ */
206
+ async upsert(input) {
207
+ assertIdentity(input);
208
+ return this.http.put("/v1/contacts", {
209
+ email: input.email,
210
+ userId: input.userId,
211
+ properties: input.properties,
212
+ lists: input.lists
213
+ });
214
+ }
215
+ /** Find non-deleted contacts by `email` or `userId`. */
216
+ async find(input) {
217
+ const query = {
218
+ email: "email" in input ? input.email : void 0,
219
+ userId: "userId" in input ? input.userId : void 0
220
+ };
221
+ const res = await this.http.get(
222
+ "/v1/contacts/find",
223
+ query
224
+ );
225
+ return res.contacts;
226
+ }
227
+ /** Soft-delete a contact by identity. */
228
+ async delete(input) {
229
+ assertIdentity(input);
230
+ return this.http.del("/v1/contacts", {
231
+ email: input.email,
232
+ userId: input.userId
233
+ });
234
+ }
235
+ };
236
+
237
+ // src/resources/emails.ts
238
+ var EmailsResource = class {
239
+ constructor(http) {
240
+ this.http = http;
241
+ }
242
+ http;
243
+ /**
244
+ * Send a transactional email by template. Recipient is `to` (raw address) or
245
+ * `userId` (resolved server-side). `template`/`props` are type-checked against
246
+ * the augmented `TemplateRegistryMap` when `@hogsend/email` is installed,
247
+ * else degrade to `{ template: string; props? }`.
248
+ */
249
+ send(input) {
250
+ const body = input;
251
+ return this.http.post(
252
+ "/v1/emails",
253
+ {
254
+ to: body.to,
255
+ userId: body.userId,
256
+ template: body.template,
257
+ props: body.props,
258
+ from: body.from,
259
+ subject: body.subject,
260
+ replyTo: body.replyTo,
261
+ category: body.category,
262
+ skipPreferenceCheck: body.skipPreferenceCheck,
263
+ idempotencyKey: body.idempotencyKey
264
+ },
265
+ body.idempotencyKey ? { idempotencyKey: body.idempotencyKey } : void 0
266
+ );
267
+ }
268
+ };
269
+
270
+ // src/resources/events.ts
271
+ var EventsResource = class {
272
+ constructor(http) {
273
+ this.http = http;
274
+ }
275
+ http;
276
+ /**
277
+ * Send an event through the ingestion pipeline. The two property bags are
278
+ * kept distinct: `eventProperties` feed `trigger.where`/`exitOn`,
279
+ * `contactProperties` merge onto the contact. Optionally apply list
280
+ * membership. Returns the ingest result (stored + exit evaluations).
281
+ *
282
+ * `idempotencyKey` is sent both as the `Idempotency-Key` header (which wins
283
+ * server-side) and in the body, matching `POST /v1/events`.
284
+ */
285
+ send(input) {
286
+ assertIdentity(input);
287
+ return this.http.post(
288
+ "/v1/events",
289
+ {
290
+ name: input.name,
291
+ email: input.email,
292
+ userId: input.userId,
293
+ eventProperties: input.eventProperties,
294
+ contactProperties: input.contactProperties,
295
+ lists: input.lists,
296
+ idempotencyKey: input.idempotencyKey
297
+ },
298
+ input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : void 0
299
+ );
300
+ }
301
+ /** Alias of {@link EventsResource.send}. */
302
+ track(input) {
303
+ return this.send(input);
304
+ }
305
+ };
306
+
307
+ // src/resources/lists.ts
308
+ var ListsResource = class {
309
+ constructor(http) {
310
+ this.http = http;
311
+ }
312
+ http;
313
+ /** List all code-defined lists. */
314
+ async list() {
315
+ const res = await this.http.get("/v1/lists");
316
+ return res.lists;
317
+ }
318
+ /** Subscribe an identity to a list. */
319
+ async subscribe(input) {
320
+ assertIdentity(input);
321
+ const res = await this.http.post(
322
+ `/v1/lists/${encodeURIComponent(input.list)}/subscribe`,
323
+ { email: input.email, userId: input.userId }
324
+ );
325
+ return { subscribed: res.subscribed };
326
+ }
327
+ /** Unsubscribe an identity from a list. */
328
+ async unsubscribe(input) {
329
+ assertIdentity(input);
330
+ const res = await this.http.post(
331
+ `/v1/lists/${encodeURIComponent(input.list)}/unsubscribe`,
332
+ { email: input.email, userId: input.userId }
333
+ );
334
+ return { unsubscribed: res.subscribed === false };
335
+ }
336
+ };
337
+
338
+ // src/hogsend.ts
339
+ var Hogsend = class {
340
+ contacts;
341
+ events;
342
+ emails;
343
+ lists;
344
+ campaigns;
345
+ constructor(opts) {
346
+ if (!opts.baseUrl) {
347
+ throw new TypeError("Hogsend: `baseUrl` is required.");
348
+ }
349
+ if (!opts.apiKey) {
350
+ throw new TypeError("Hogsend: `apiKey` is required.");
351
+ }
352
+ const http = createHttpClient({
353
+ baseUrl: opts.baseUrl.replace(/\/+$/, ""),
354
+ apiKey: opts.apiKey,
355
+ fetch: opts.fetch,
356
+ timeoutMs: opts.timeoutMs,
357
+ headers: opts.headers
358
+ });
359
+ this.contacts = new ContactsResource(http);
360
+ this.events = new EventsResource(http);
361
+ this.emails = new EmailsResource(http);
362
+ this.lists = new ListsResource(http);
363
+ this.campaigns = new CampaignsResource(http);
364
+ }
365
+ };
366
+ // Annotate the CommonJS export names for ESM import in node:
367
+ 0 && (module.exports = {
368
+ Hogsend,
369
+ HogsendAPIError,
370
+ RateLimitError
371
+ });
372
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/internal/http.ts","../src/resources/campaigns.ts","../src/internal/identity.ts","../src/resources/contacts.ts","../src/resources/emails.ts","../src/resources/events.ts","../src/resources/lists.ts","../src/hogsend.ts"],"sourcesContent":["export { HogsendAPIError, RateLimitError } from \"./errors.js\";\nexport { Hogsend } from \"./hogsend.js\";\nexport type {\n Campaign,\n CampaignAudienceKind,\n CampaignStatus,\n Contact,\n DeleteContactInput,\n DeleteContactResult,\n ExitResult,\n FindContactsInput,\n HogsendOptions,\n Identity,\n IngestResult,\n ListSummary,\n SendCampaignInput,\n SendCampaignResult,\n SendEmailInput,\n SendEmailResult,\n SendEventInput,\n SubscribeInput,\n SubscribeResult,\n UnsubscribeResult,\n UpsertContactInput,\n UpsertContactResult,\n} from \"./types.js\";\n","/**\n * A non-2xx response — or a transport-level failure — from the Hogsend data\n * plane. `status` is the HTTP status code, or `0` when the request never\n * reached the server (DNS/connect/timeout). `body` is the parsed JSON body when\n * available, else the raw text, else `undefined`.\n */\nexport class HogsendAPIError extends Error {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.name = \"HogsendAPIError\";\n this.status = status;\n this.body = body;\n // Restore prototype chain for instanceof across transpile targets.\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/**\n * A `429 Too Many Requests` response. `retryAfter` is the parsed `Retry-After`\n * header in seconds when present (the server sends it on 429 for `/v1/emails`).\n */\nexport class RateLimitError extends HogsendAPIError {\n readonly retryAfter?: number;\n\n constructor(message: string, body: unknown, retryAfter?: number) {\n super(message, 429, body);\n this.name = \"RateLimitError\";\n this.retryAfter = retryAfter;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import { HogsendAPIError, RateLimitError } from \"../errors.js\";\n\n/** Query params accepted by `get` — undefined values are dropped. */\nexport type Query = Record<string, string | number | undefined>;\n\n/** Per-request extras (currently just an idempotency header passthrough). */\nexport interface RequestExtras {\n /** Sent as the `Idempotency-Key` header when set. */\n idempotencyKey?: string;\n}\n\nexport interface HttpClientConfig {\n baseUrl: string;\n apiKey: string;\n fetch?: typeof fetch;\n timeoutMs?: number;\n headers?: Record<string, string>;\n}\n\n/** A minimal, self-contained data-plane HTTP client over native fetch. */\nexport interface HttpClient {\n get<T = unknown>(path: string, query?: Query): Promise<T>;\n post<T = unknown>(\n path: string,\n body: unknown,\n extras?: RequestExtras,\n ): Promise<T>;\n put<T = unknown>(\n path: string,\n body: unknown,\n extras?: RequestExtras,\n ): Promise<T>;\n del<T = unknown>(path: string, body?: unknown): Promise<T>;\n}\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nfunction buildUrl(baseUrl: string, path: string, query?: Query): string {\n const url = new URL(path.startsWith(\"/\") ? path : `/${path}`, `${baseUrl}/`);\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined) continue;\n url.searchParams.set(key, String(value));\n }\n }\n return url.toString();\n}\n\nfunction bodyMessage(status: number, body: unknown): string {\n if (body && typeof body === \"object\") {\n // Application-handler envelope: `{ error: \"human message\" }`.\n const errField = (body as { error?: unknown }).error;\n if (typeof errField === \"string\") {\n return `${status}: ${errField}`;\n }\n // @hono/zod-openapi default-hook validation envelope:\n // `{ success: false, error: <ZodError> }` (no defaultHook configured). The\n // structured ZodError is preserved on `err.body`; surface a short, readable\n // summary for `err.message` instead of the generic fallback.\n if (\n (body as { success?: unknown }).success === false &&\n errField &&\n typeof errField === \"object\"\n ) {\n const summary = JSON.stringify(errField).slice(0, 200);\n return `${status}: validation failed ${summary}`;\n }\n }\n return `request failed with status ${status}`;\n}\n\n/** Parse a `Retry-After` header (seconds form) into a number, else undefined. */\nfunction parseRetryAfter(value: string | null): number | undefined {\n if (!value) return undefined;\n const seconds = Number.parseInt(value, 10);\n return Number.isFinite(seconds) ? seconds : undefined;\n}\n\n/**\n * Builds an {@link HttpClient} bound to a config. Native `fetch`, JSON in/out,\n * `Authorization: Bearer <apiKey>`. Throws typed errors:\n * - {@link RateLimitError} on 429 (with parsed `Retry-After`),\n * - {@link HogsendAPIError} on any other non-2xx,\n * - {@link HogsendAPIError} with `status === 0` on a transport failure\n * (DNS/connect/abort/timeout).\n */\nexport function createHttpClient(config: HttpClientConfig): HttpClient {\n const doFetch = config.fetch ?? fetch;\n const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const extraHeaders = config.headers ?? {};\n\n async function request<T>(\n method: string,\n path: string,\n opts: { query?: Query; body?: unknown; extras?: RequestExtras },\n ): Promise<T> {\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n Authorization: `Bearer ${config.apiKey}`,\n ...extraHeaders,\n };\n if (opts.body !== undefined) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n if (opts.extras?.idempotencyKey) {\n headers[\"Idempotency-Key\"] = opts.extras.idempotencyKey;\n }\n\n const url = buildUrl(config.baseUrl, path, opts.query);\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n let res: Response;\n try {\n res = await doFetch(url, {\n method,\n headers,\n body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,\n signal: controller.signal,\n });\n } catch (cause) {\n const msg = cause instanceof Error ? cause.message : String(cause);\n throw new HogsendAPIError(\n `cannot reach ${config.baseUrl} (${msg})`,\n 0,\n undefined,\n );\n } finally {\n clearTimeout(timer);\n }\n\n const text = await res.text();\n let parsed: unknown;\n if (text.length > 0) {\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = text;\n }\n }\n\n if (!res.ok) {\n if (res.status === 429) {\n throw new RateLimitError(\n bodyMessage(res.status, parsed),\n parsed,\n parseRetryAfter(res.headers.get(\"Retry-After\")),\n );\n }\n throw new HogsendAPIError(\n bodyMessage(res.status, parsed),\n res.status,\n parsed,\n );\n }\n\n return parsed as T;\n }\n\n return {\n get: <T>(path: string, query?: Query) => request<T>(\"GET\", path, { query }),\n post: <T>(path: string, body: unknown, extras?: RequestExtras) =>\n request<T>(\"POST\", path, { body, extras }),\n put: <T>(path: string, body: unknown, extras?: RequestExtras) =>\n request<T>(\"PUT\", path, { body, extras }),\n del: <T>(path: string, body?: unknown) =>\n request<T>(\"DELETE\", path, { body }),\n };\n}\n","import type { HttpClient } from \"../internal/http.js\";\nimport type {\n Campaign,\n SendCampaignInput,\n SendCampaignResult,\n} from \"../types.js\";\n\n/** The `campaigns.*` resource bound to an {@link HttpClient}. */\nexport class CampaignsResource {\n constructor(private readonly http: HttpClient) {}\n\n /**\n * Queue a broadcast: durably send one template to every subscribed member of\n * a `list` (or every active member of a `bucket`). Exactly one of `list` /\n * `bucket` must be set; `template`/`props` are type-checked against the\n * augmented `TemplateRegistryMap` when `@hogsend/email` is installed, else\n * degrade to `{ template: string; props? }`.\n *\n * Returns the 202 enqueue ack (`{ campaignId, status }`); the actual sends run\n * asynchronously in the worker. Poll {@link CampaignsResource.get} for counts.\n */\n send(input: SendCampaignInput): Promise<SendCampaignResult> {\n // The discriminated union narrows `template`/`props` and the audience; index\n // into the input via a permissive view to build the wire body without\n // re-discriminating.\n const body = input as SendCampaignInput & {\n list?: string;\n bucket?: string;\n props?: Record<string, unknown>;\n };\n return this.http.post<SendCampaignResult>(\"/v1/campaigns\", {\n name: body.name,\n list: body.list,\n bucket: body.bucket,\n template: body.template,\n props: body.props,\n from: body.from,\n subject: body.subject,\n });\n }\n\n /** Fetch a campaign's current status + send counts. */\n get(id: string): Promise<Campaign> {\n return this.http.get<Campaign>(`/v1/campaigns/${encodeURIComponent(id)}`);\n }\n}\n","/**\n * Runtime guard mirroring the `Identity` union: at least one of `email` /\n * `userId` must be a non-empty string. The type system enforces this at the\n * call site, but runtime callers (plain JS, untyped data) can still violate it,\n * so we fail fast with a clear message before issuing the request.\n */\nexport function assertIdentity(input: {\n email?: string;\n userId?: string;\n}): void {\n const hasEmail = typeof input.email === \"string\" && input.email.length > 0;\n const hasUserId = typeof input.userId === \"string\" && input.userId.length > 0;\n if (!hasEmail && !hasUserId) {\n throw new TypeError(\n \"Hogsend: an identity is required — pass `email`, `userId`, or both.\",\n );\n }\n}\n","import type { HttpClient } from \"../internal/http.js\";\nimport { assertIdentity } from \"../internal/identity.js\";\nimport type {\n Contact,\n DeleteContactInput,\n DeleteContactResult,\n FindContactsInput,\n UpsertContactInput,\n UpsertContactResult,\n} from \"../types.js\";\n\n/** The `contacts.*` resource bound to an {@link HttpClient}. */\nexport class ContactsResource {\n constructor(private readonly http: HttpClient) {}\n\n /**\n * Upsert a contact by identity. Resolves/merges server-side and optionally\n * applies list membership. Returns `{ id, created, linked }`.\n */\n async upsert(input: UpsertContactInput): Promise<UpsertContactResult> {\n assertIdentity(input);\n return this.http.put<UpsertContactResult>(\"/v1/contacts\", {\n email: input.email,\n userId: input.userId,\n properties: input.properties,\n lists: input.lists,\n });\n }\n\n /** Find non-deleted contacts by `email` or `userId`. */\n async find(input: FindContactsInput): Promise<Contact[]> {\n const query: Record<string, string | undefined> = {\n email: \"email\" in input ? input.email : undefined,\n userId: \"userId\" in input ? input.userId : undefined,\n };\n const res = await this.http.get<{ contacts: Contact[] }>(\n \"/v1/contacts/find\",\n query,\n );\n return res.contacts;\n }\n\n /** Soft-delete a contact by identity. */\n async delete(input: DeleteContactInput): Promise<DeleteContactResult> {\n assertIdentity(input);\n return this.http.del<DeleteContactResult>(\"/v1/contacts\", {\n email: input.email,\n userId: input.userId,\n });\n }\n}\n","import type { HttpClient } from \"../internal/http.js\";\nimport type { SendEmailInput, SendEmailResult } from \"../types.js\";\n\n/** The `emails.*` resource bound to an {@link HttpClient}. */\nexport class EmailsResource {\n constructor(private readonly http: HttpClient) {}\n\n /**\n * Send a transactional email by template. Recipient is `to` (raw address) or\n * `userId` (resolved server-side). `template`/`props` are type-checked against\n * the augmented `TemplateRegistryMap` when `@hogsend/email` is installed,\n * else degrade to `{ template: string; props? }`.\n */\n send(input: SendEmailInput): Promise<SendEmailResult> {\n // The discriminated union narrows `template`/`props`; index into the input\n // via a permissive view to build the wire body without re-discriminating.\n const body = input as SendEmailInput & {\n props?: Record<string, unknown>;\n };\n return this.http.post<SendEmailResult>(\n \"/v1/emails\",\n {\n to: body.to,\n userId: body.userId,\n template: body.template,\n props: body.props,\n from: body.from,\n subject: body.subject,\n replyTo: body.replyTo,\n category: body.category,\n skipPreferenceCheck: body.skipPreferenceCheck,\n idempotencyKey: body.idempotencyKey,\n },\n body.idempotencyKey ? { idempotencyKey: body.idempotencyKey } : undefined,\n );\n }\n}\n","import type { HttpClient } from \"../internal/http.js\";\nimport { assertIdentity } from \"../internal/identity.js\";\nimport type { IngestResult, SendEventInput } from \"../types.js\";\n\n/** The `events.*` resource bound to an {@link HttpClient}. */\nexport class EventsResource {\n constructor(private readonly http: HttpClient) {}\n\n /**\n * Send an event through the ingestion pipeline. The two property bags are\n * kept distinct: `eventProperties` feed `trigger.where`/`exitOn`,\n * `contactProperties` merge onto the contact. Optionally apply list\n * membership. Returns the ingest result (stored + exit evaluations).\n *\n * `idempotencyKey` is sent both as the `Idempotency-Key` header (which wins\n * server-side) and in the body, matching `POST /v1/events`.\n */\n send(input: SendEventInput): Promise<IngestResult> {\n assertIdentity(input);\n return this.http.post<IngestResult>(\n \"/v1/events\",\n {\n name: input.name,\n email: input.email,\n userId: input.userId,\n eventProperties: input.eventProperties,\n contactProperties: input.contactProperties,\n lists: input.lists,\n idempotencyKey: input.idempotencyKey,\n },\n input.idempotencyKey\n ? { idempotencyKey: input.idempotencyKey }\n : undefined,\n );\n }\n\n /** Alias of {@link EventsResource.send}. */\n track(input: SendEventInput): Promise<IngestResult> {\n return this.send(input);\n }\n}\n","import type { HttpClient } from \"../internal/http.js\";\nimport { assertIdentity } from \"../internal/identity.js\";\nimport type {\n ListSummary,\n SubscribeInput,\n SubscribeResult,\n UnsubscribeResult,\n} from \"../types.js\";\n\n/** The `lists.*` resource bound to an {@link HttpClient}. */\nexport class ListsResource {\n constructor(private readonly http: HttpClient) {}\n\n /** List all code-defined lists. */\n async list(): Promise<ListSummary[]> {\n const res = await this.http.get<{ lists: ListSummary[] }>(\"/v1/lists\");\n return res.lists;\n }\n\n /** Subscribe an identity to a list. */\n async subscribe(input: SubscribeInput): Promise<SubscribeResult> {\n assertIdentity(input);\n const res = await this.http.post<{ list: string; subscribed: boolean }>(\n `/v1/lists/${encodeURIComponent(input.list)}/subscribe`,\n { email: input.email, userId: input.userId },\n );\n return { subscribed: res.subscribed };\n }\n\n /** Unsubscribe an identity from a list. */\n async unsubscribe(input: SubscribeInput): Promise<UnsubscribeResult> {\n assertIdentity(input);\n const res = await this.http.post<{ list: string; subscribed: boolean }>(\n `/v1/lists/${encodeURIComponent(input.list)}/unsubscribe`,\n { email: input.email, userId: input.userId },\n );\n return { unsubscribed: res.subscribed === false };\n }\n}\n","import { createHttpClient } from \"./internal/http.js\";\nimport { CampaignsResource } from \"./resources/campaigns.js\";\nimport { ContactsResource } from \"./resources/contacts.js\";\nimport { EmailsResource } from \"./resources/emails.js\";\nimport { EventsResource } from \"./resources/events.js\";\nimport { ListsResource } from \"./resources/lists.js\";\nimport type { HogsendOptions } from \"./types.js\";\n\n/**\n * Typed HTTP client for the Hogsend data plane.\n *\n * ```ts\n * const hs = new Hogsend({ baseUrl: \"https://api.example.com\", apiKey: \"hsk_…\" });\n * await hs.contacts.upsert({ email: \"a@b.com\", properties: { plan: \"pro\" } });\n * await hs.events.send({ userId: \"u_1\", name: \"signup\" });\n * ```\n */\nexport class Hogsend {\n readonly contacts: ContactsResource;\n readonly events: EventsResource;\n readonly emails: EmailsResource;\n readonly lists: ListsResource;\n readonly campaigns: CampaignsResource;\n\n constructor(opts: HogsendOptions) {\n if (!opts.baseUrl) {\n throw new TypeError(\"Hogsend: `baseUrl` is required.\");\n }\n if (!opts.apiKey) {\n throw new TypeError(\"Hogsend: `apiKey` is required.\");\n }\n\n const http = createHttpClient({\n baseUrl: opts.baseUrl.replace(/\\/+$/, \"\"),\n apiKey: opts.apiKey,\n fetch: opts.fetch,\n timeoutMs: opts.timeoutMs,\n headers: opts.headers,\n });\n\n this.contacts = new ContactsResource(http);\n this.events = new EventsResource(http);\n this.emails = new EmailsResource(http);\n this.lists = new ListsResource(http);\n this.campaigns = new CampaignsResource(http);\n }\n}\n"],"mappings":"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","names":[]}