@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 +93 -0
- package/README.md +110 -0
- package/dist/index.cjs +372 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +335 -0
- package/dist/index.d.ts +335 -0
- package/dist/index.js +343 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var HogsendAPIError = class extends Error {
|
|
3
|
+
status;
|
|
4
|
+
body;
|
|
5
|
+
constructor(message, status, body) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "HogsendAPIError";
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.body = body;
|
|
10
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var RateLimitError = class extends HogsendAPIError {
|
|
14
|
+
retryAfter;
|
|
15
|
+
constructor(message, body, retryAfter) {
|
|
16
|
+
super(message, 429, body);
|
|
17
|
+
this.name = "RateLimitError";
|
|
18
|
+
this.retryAfter = retryAfter;
|
|
19
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// src/internal/http.ts
|
|
24
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
25
|
+
function buildUrl(baseUrl, path, query) {
|
|
26
|
+
const url = new URL(path.startsWith("/") ? path : `/${path}`, `${baseUrl}/`);
|
|
27
|
+
if (query) {
|
|
28
|
+
for (const [key, value] of Object.entries(query)) {
|
|
29
|
+
if (value === void 0) continue;
|
|
30
|
+
url.searchParams.set(key, String(value));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return url.toString();
|
|
34
|
+
}
|
|
35
|
+
function bodyMessage(status, body) {
|
|
36
|
+
if (body && typeof body === "object") {
|
|
37
|
+
const errField = body.error;
|
|
38
|
+
if (typeof errField === "string") {
|
|
39
|
+
return `${status}: ${errField}`;
|
|
40
|
+
}
|
|
41
|
+
if (body.success === false && errField && typeof errField === "object") {
|
|
42
|
+
const summary = JSON.stringify(errField).slice(0, 200);
|
|
43
|
+
return `${status}: validation failed ${summary}`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return `request failed with status ${status}`;
|
|
47
|
+
}
|
|
48
|
+
function parseRetryAfter(value) {
|
|
49
|
+
if (!value) return void 0;
|
|
50
|
+
const seconds = Number.parseInt(value, 10);
|
|
51
|
+
return Number.isFinite(seconds) ? seconds : void 0;
|
|
52
|
+
}
|
|
53
|
+
function createHttpClient(config) {
|
|
54
|
+
const doFetch = config.fetch ?? fetch;
|
|
55
|
+
const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
56
|
+
const extraHeaders = config.headers ?? {};
|
|
57
|
+
async function request(method, path, opts) {
|
|
58
|
+
const headers = {
|
|
59
|
+
Accept: "application/json",
|
|
60
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
61
|
+
...extraHeaders
|
|
62
|
+
};
|
|
63
|
+
if (opts.body !== void 0) {
|
|
64
|
+
headers["Content-Type"] = "application/json";
|
|
65
|
+
}
|
|
66
|
+
if (opts.extras?.idempotencyKey) {
|
|
67
|
+
headers["Idempotency-Key"] = opts.extras.idempotencyKey;
|
|
68
|
+
}
|
|
69
|
+
const url = buildUrl(config.baseUrl, path, opts.query);
|
|
70
|
+
const controller = new AbortController();
|
|
71
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
72
|
+
let res;
|
|
73
|
+
try {
|
|
74
|
+
res = await doFetch(url, {
|
|
75
|
+
method,
|
|
76
|
+
headers,
|
|
77
|
+
body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0,
|
|
78
|
+
signal: controller.signal
|
|
79
|
+
});
|
|
80
|
+
} catch (cause) {
|
|
81
|
+
const msg = cause instanceof Error ? cause.message : String(cause);
|
|
82
|
+
throw new HogsendAPIError(
|
|
83
|
+
`cannot reach ${config.baseUrl} (${msg})`,
|
|
84
|
+
0,
|
|
85
|
+
void 0
|
|
86
|
+
);
|
|
87
|
+
} finally {
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
}
|
|
90
|
+
const text = await res.text();
|
|
91
|
+
let parsed;
|
|
92
|
+
if (text.length > 0) {
|
|
93
|
+
try {
|
|
94
|
+
parsed = JSON.parse(text);
|
|
95
|
+
} catch {
|
|
96
|
+
parsed = text;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (!res.ok) {
|
|
100
|
+
if (res.status === 429) {
|
|
101
|
+
throw new RateLimitError(
|
|
102
|
+
bodyMessage(res.status, parsed),
|
|
103
|
+
parsed,
|
|
104
|
+
parseRetryAfter(res.headers.get("Retry-After"))
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
throw new HogsendAPIError(
|
|
108
|
+
bodyMessage(res.status, parsed),
|
|
109
|
+
res.status,
|
|
110
|
+
parsed
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return parsed;
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
get: (path, query) => request("GET", path, { query }),
|
|
117
|
+
post: (path, body, extras) => request("POST", path, { body, extras }),
|
|
118
|
+
put: (path, body, extras) => request("PUT", path, { body, extras }),
|
|
119
|
+
del: (path, body) => request("DELETE", path, { body })
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/resources/campaigns.ts
|
|
124
|
+
var CampaignsResource = class {
|
|
125
|
+
constructor(http) {
|
|
126
|
+
this.http = http;
|
|
127
|
+
}
|
|
128
|
+
http;
|
|
129
|
+
/**
|
|
130
|
+
* Queue a broadcast: durably send one template to every subscribed member of
|
|
131
|
+
* a `list` (or every active member of a `bucket`). Exactly one of `list` /
|
|
132
|
+
* `bucket` must be set; `template`/`props` are type-checked against the
|
|
133
|
+
* augmented `TemplateRegistryMap` when `@hogsend/email` is installed, else
|
|
134
|
+
* degrade to `{ template: string; props? }`.
|
|
135
|
+
*
|
|
136
|
+
* Returns the 202 enqueue ack (`{ campaignId, status }`); the actual sends run
|
|
137
|
+
* asynchronously in the worker. Poll {@link CampaignsResource.get} for counts.
|
|
138
|
+
*/
|
|
139
|
+
send(input) {
|
|
140
|
+
const body = input;
|
|
141
|
+
return this.http.post("/v1/campaigns", {
|
|
142
|
+
name: body.name,
|
|
143
|
+
list: body.list,
|
|
144
|
+
bucket: body.bucket,
|
|
145
|
+
template: body.template,
|
|
146
|
+
props: body.props,
|
|
147
|
+
from: body.from,
|
|
148
|
+
subject: body.subject
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/** Fetch a campaign's current status + send counts. */
|
|
152
|
+
get(id) {
|
|
153
|
+
return this.http.get(`/v1/campaigns/${encodeURIComponent(id)}`);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// src/internal/identity.ts
|
|
158
|
+
function assertIdentity(input) {
|
|
159
|
+
const hasEmail = typeof input.email === "string" && input.email.length > 0;
|
|
160
|
+
const hasUserId = typeof input.userId === "string" && input.userId.length > 0;
|
|
161
|
+
if (!hasEmail && !hasUserId) {
|
|
162
|
+
throw new TypeError(
|
|
163
|
+
"Hogsend: an identity is required \u2014 pass `email`, `userId`, or both."
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/resources/contacts.ts
|
|
169
|
+
var ContactsResource = class {
|
|
170
|
+
constructor(http) {
|
|
171
|
+
this.http = http;
|
|
172
|
+
}
|
|
173
|
+
http;
|
|
174
|
+
/**
|
|
175
|
+
* Upsert a contact by identity. Resolves/merges server-side and optionally
|
|
176
|
+
* applies list membership. Returns `{ id, created, linked }`.
|
|
177
|
+
*/
|
|
178
|
+
async upsert(input) {
|
|
179
|
+
assertIdentity(input);
|
|
180
|
+
return this.http.put("/v1/contacts", {
|
|
181
|
+
email: input.email,
|
|
182
|
+
userId: input.userId,
|
|
183
|
+
properties: input.properties,
|
|
184
|
+
lists: input.lists
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/** Find non-deleted contacts by `email` or `userId`. */
|
|
188
|
+
async find(input) {
|
|
189
|
+
const query = {
|
|
190
|
+
email: "email" in input ? input.email : void 0,
|
|
191
|
+
userId: "userId" in input ? input.userId : void 0
|
|
192
|
+
};
|
|
193
|
+
const res = await this.http.get(
|
|
194
|
+
"/v1/contacts/find",
|
|
195
|
+
query
|
|
196
|
+
);
|
|
197
|
+
return res.contacts;
|
|
198
|
+
}
|
|
199
|
+
/** Soft-delete a contact by identity. */
|
|
200
|
+
async delete(input) {
|
|
201
|
+
assertIdentity(input);
|
|
202
|
+
return this.http.del("/v1/contacts", {
|
|
203
|
+
email: input.email,
|
|
204
|
+
userId: input.userId
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// src/resources/emails.ts
|
|
210
|
+
var EmailsResource = class {
|
|
211
|
+
constructor(http) {
|
|
212
|
+
this.http = http;
|
|
213
|
+
}
|
|
214
|
+
http;
|
|
215
|
+
/**
|
|
216
|
+
* Send a transactional email by template. Recipient is `to` (raw address) or
|
|
217
|
+
* `userId` (resolved server-side). `template`/`props` are type-checked against
|
|
218
|
+
* the augmented `TemplateRegistryMap` when `@hogsend/email` is installed,
|
|
219
|
+
* else degrade to `{ template: string; props? }`.
|
|
220
|
+
*/
|
|
221
|
+
send(input) {
|
|
222
|
+
const body = input;
|
|
223
|
+
return this.http.post(
|
|
224
|
+
"/v1/emails",
|
|
225
|
+
{
|
|
226
|
+
to: body.to,
|
|
227
|
+
userId: body.userId,
|
|
228
|
+
template: body.template,
|
|
229
|
+
props: body.props,
|
|
230
|
+
from: body.from,
|
|
231
|
+
subject: body.subject,
|
|
232
|
+
replyTo: body.replyTo,
|
|
233
|
+
category: body.category,
|
|
234
|
+
skipPreferenceCheck: body.skipPreferenceCheck,
|
|
235
|
+
idempotencyKey: body.idempotencyKey
|
|
236
|
+
},
|
|
237
|
+
body.idempotencyKey ? { idempotencyKey: body.idempotencyKey } : void 0
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// src/resources/events.ts
|
|
243
|
+
var EventsResource = class {
|
|
244
|
+
constructor(http) {
|
|
245
|
+
this.http = http;
|
|
246
|
+
}
|
|
247
|
+
http;
|
|
248
|
+
/**
|
|
249
|
+
* Send an event through the ingestion pipeline. The two property bags are
|
|
250
|
+
* kept distinct: `eventProperties` feed `trigger.where`/`exitOn`,
|
|
251
|
+
* `contactProperties` merge onto the contact. Optionally apply list
|
|
252
|
+
* membership. Returns the ingest result (stored + exit evaluations).
|
|
253
|
+
*
|
|
254
|
+
* `idempotencyKey` is sent both as the `Idempotency-Key` header (which wins
|
|
255
|
+
* server-side) and in the body, matching `POST /v1/events`.
|
|
256
|
+
*/
|
|
257
|
+
send(input) {
|
|
258
|
+
assertIdentity(input);
|
|
259
|
+
return this.http.post(
|
|
260
|
+
"/v1/events",
|
|
261
|
+
{
|
|
262
|
+
name: input.name,
|
|
263
|
+
email: input.email,
|
|
264
|
+
userId: input.userId,
|
|
265
|
+
eventProperties: input.eventProperties,
|
|
266
|
+
contactProperties: input.contactProperties,
|
|
267
|
+
lists: input.lists,
|
|
268
|
+
idempotencyKey: input.idempotencyKey
|
|
269
|
+
},
|
|
270
|
+
input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : void 0
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
/** Alias of {@link EventsResource.send}. */
|
|
274
|
+
track(input) {
|
|
275
|
+
return this.send(input);
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// src/resources/lists.ts
|
|
280
|
+
var ListsResource = class {
|
|
281
|
+
constructor(http) {
|
|
282
|
+
this.http = http;
|
|
283
|
+
}
|
|
284
|
+
http;
|
|
285
|
+
/** List all code-defined lists. */
|
|
286
|
+
async list() {
|
|
287
|
+
const res = await this.http.get("/v1/lists");
|
|
288
|
+
return res.lists;
|
|
289
|
+
}
|
|
290
|
+
/** Subscribe an identity to a list. */
|
|
291
|
+
async subscribe(input) {
|
|
292
|
+
assertIdentity(input);
|
|
293
|
+
const res = await this.http.post(
|
|
294
|
+
`/v1/lists/${encodeURIComponent(input.list)}/subscribe`,
|
|
295
|
+
{ email: input.email, userId: input.userId }
|
|
296
|
+
);
|
|
297
|
+
return { subscribed: res.subscribed };
|
|
298
|
+
}
|
|
299
|
+
/** Unsubscribe an identity from a list. */
|
|
300
|
+
async unsubscribe(input) {
|
|
301
|
+
assertIdentity(input);
|
|
302
|
+
const res = await this.http.post(
|
|
303
|
+
`/v1/lists/${encodeURIComponent(input.list)}/unsubscribe`,
|
|
304
|
+
{ email: input.email, userId: input.userId }
|
|
305
|
+
);
|
|
306
|
+
return { unsubscribed: res.subscribed === false };
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
// src/hogsend.ts
|
|
311
|
+
var Hogsend = class {
|
|
312
|
+
contacts;
|
|
313
|
+
events;
|
|
314
|
+
emails;
|
|
315
|
+
lists;
|
|
316
|
+
campaigns;
|
|
317
|
+
constructor(opts) {
|
|
318
|
+
if (!opts.baseUrl) {
|
|
319
|
+
throw new TypeError("Hogsend: `baseUrl` is required.");
|
|
320
|
+
}
|
|
321
|
+
if (!opts.apiKey) {
|
|
322
|
+
throw new TypeError("Hogsend: `apiKey` is required.");
|
|
323
|
+
}
|
|
324
|
+
const http = createHttpClient({
|
|
325
|
+
baseUrl: opts.baseUrl.replace(/\/+$/, ""),
|
|
326
|
+
apiKey: opts.apiKey,
|
|
327
|
+
fetch: opts.fetch,
|
|
328
|
+
timeoutMs: opts.timeoutMs,
|
|
329
|
+
headers: opts.headers
|
|
330
|
+
});
|
|
331
|
+
this.contacts = new ContactsResource(http);
|
|
332
|
+
this.events = new EventsResource(http);
|
|
333
|
+
this.emails = new EmailsResource(http);
|
|
334
|
+
this.lists = new ListsResource(http);
|
|
335
|
+
this.campaigns = new CampaignsResource(http);
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
export {
|
|
339
|
+
Hogsend,
|
|
340
|
+
HogsendAPIError,
|
|
341
|
+
RateLimitError
|
|
342
|
+
};
|
|
343
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../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":["/**\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":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hogsend/client",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"description": "Typed HTTP client for the Hogsend data plane (contacts, events, emails, lists).",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/dougwithseismic/hogsend.git",
|
|
10
|
+
"directory": "packages/client"
|
|
11
|
+
},
|
|
12
|
+
"main": "./dist/index.cjs",
|
|
13
|
+
"module": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js",
|
|
19
|
+
"require": "./dist/index.cjs"
|
|
20
|
+
},
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"@hogsend/email": "^0.6.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependenciesMeta": {
|
|
34
|
+
"@hogsend/email": {
|
|
35
|
+
"optional": true
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^22.15.3",
|
|
40
|
+
"tsup": "^8.5.1",
|
|
41
|
+
"vitest": "^4.1.7",
|
|
42
|
+
"@repo/typescript-config": "0.0.0",
|
|
43
|
+
"@hogsend/email": "^0.6.0"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=22"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsup",
|
|
50
|
+
"check-types": "tsc --noEmit",
|
|
51
|
+
"lint": "biome check .",
|
|
52
|
+
"clean": "rm -rf node_modules dist .turbo",
|
|
53
|
+
"test": "vitest run",
|
|
54
|
+
"test:watch": "vitest"
|
|
55
|
+
}
|
|
56
|
+
}
|