@nateq/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,341 @@
1
+ //#region src/errors.ts
2
+ var NateqError = class extends Error {
3
+ /** HTTP status, when the failure came from a response. */
4
+ status;
5
+ /** API error code, when the response carried one. */
6
+ code;
7
+ /** Extra context from the API (validation details, scope lists, ...). */
8
+ details;
9
+ /** Value of the `X-Request-Id` response header, useful for support tickets. */
10
+ requestId;
11
+ constructor(message, opts = {}) {
12
+ super(message, opts.cause !== void 0 ? { cause: opts.cause } : void 0);
13
+ this.name = new.target.name;
14
+ this.status = opts.status;
15
+ this.code = opts.code;
16
+ this.details = opts.details;
17
+ this.requestId = opts.requestId;
18
+ Error.captureStackTrace?.(this, new.target);
19
+ }
20
+ };
21
+ /** The key is missing, malformed, revoked, or unknown (401). */
22
+ var NateqAuthenticationError = class extends NateqError {};
23
+ /**
24
+ * The key is valid but not allowed to do this (403).
25
+ *
26
+ * Common causes: the key lacks the `emails:send` scope, the endpoint is not
27
+ * exposed to API keys, or the caller's IP is outside the key's allow-list.
28
+ */
29
+ var NateqPermissionError = class extends NateqError {};
30
+ /** The request body failed validation (400/422). */
31
+ var NateqValidationError = class extends NateqError {};
32
+ /** The requested resource does not exist, or is not visible to this key (404). */
33
+ var NateqNotFoundError = class extends NateqError {};
34
+ /** A rate limit was hit (429). Nothing was sent — this is safe to retry. */
35
+ var NateqRateLimitError = class extends NateqError {
36
+ /** Seconds to wait before retrying, when the API supplies `Retry-After`. */
37
+ retryAfter;
38
+ constructor(message, opts = {}) {
39
+ super(message, opts);
40
+ this.retryAfter = opts.retryAfter;
41
+ }
42
+ };
43
+ /** The API failed to handle the request (5xx). */
44
+ var NateqServerError = class extends NateqError {};
45
+ /** The request never produced a response: DNS, TLS, socket, or offline. */
46
+ var NateqConnectionError = class extends NateqError {};
47
+ /** The request exceeded the configured timeout, or was aborted by the caller. */
48
+ var NateqTimeoutError = class extends NateqError {};
49
+ /** Bad SDK usage — thrown before any network call (never carries a status). */
50
+ var NateqConfigurationError = class extends NateqError {};
51
+ //#endregion
52
+ //#region src/emails.ts
53
+ /** Outbound email operations. Reachable at `client.emails`. */
54
+ var Emails = class {
55
+ transport;
56
+ constructor(transport) {
57
+ this.transport = transport;
58
+ }
59
+ /**
60
+ * Sends an email, returning once the API has accepted it. Delivery itself is
61
+ * asynchronous — poll {@link get} or subscribe to a webhook for the outcome.
62
+ *
63
+ * Requires an API key with the `emails:send` scope.
64
+ *
65
+ * A failed send is **not** replayed automatically unless the API rejected it
66
+ * outright (429), because a request that fails after reaching the server may
67
+ * already have sent the mail. On a timeout or 5xx, look the email up with
68
+ * {@link list} before retrying, or you risk sending twice.
69
+ *
70
+ * @throws {NateqValidationError} if the params are unusable, before any call.
71
+ * @throws {NateqPermissionError} if the key lacks the `emails:send` scope.
72
+ */
73
+ async send(params, opts = {}) {
74
+ if (!params.toEmails?.length) throw new NateqValidationError("`toEmails` must contain at least one recipient.");
75
+ if (!params.subject?.trim()) throw new NateqValidationError("`subject` is required.");
76
+ if (!params.htmlBody?.trim() && !params.plainTextBody?.trim()) throw new NateqValidationError("Provide `htmlBody`, `plainTextBody`, or both.");
77
+ return this.transport.request({
78
+ method: "POST",
79
+ path: "/outbound-emails",
80
+ body: params,
81
+ signal: opts.signal,
82
+ retryOnAmbiguousFailure: false
83
+ });
84
+ }
85
+ /**
86
+ * Fetches a single sent email by id, including delivery status.
87
+ *
88
+ * Requires an API key with the `emails:read` scope.
89
+ */
90
+ async get(id, opts = {}) {
91
+ if (!id?.trim()) throw new NateqValidationError("`id` is required.");
92
+ return this.transport.request({
93
+ method: "GET",
94
+ path: `/outbound-emails/${encodeURIComponent(id)}`,
95
+ signal: opts.signal,
96
+ retryOnAmbiguousFailure: true
97
+ });
98
+ }
99
+ /**
100
+ * Lists sent emails, newest first. `limit` defaults to 50 and is capped at 100.
101
+ *
102
+ * Requires an API key with the `emails:read` scope.
103
+ */
104
+ async list(params = {}, opts = {}) {
105
+ return this.transport.request({
106
+ method: "GET",
107
+ path: "/outbound-emails",
108
+ query: params,
109
+ signal: opts.signal,
110
+ retryOnAmbiguousFailure: true
111
+ });
112
+ }
113
+ };
114
+ //#endregion
115
+ //#region src/http.ts
116
+ /**
117
+ * Removes the API key from a string. Applied to every message the SDK raises so
118
+ * a leaked stack trace or log line can never reveal the credential.
119
+ */
120
+ function redact(text, apiKey) {
121
+ return apiKey ? text.split(apiKey).join("[REDACTED]") : text;
122
+ }
123
+ function isAbortError(err) {
124
+ return err instanceof Error && err.name === "AbortError";
125
+ }
126
+ function parseRetryAfter(headers) {
127
+ const raw = headers.get("retry-after");
128
+ if (!raw) return void 0;
129
+ const seconds = Number(raw);
130
+ if (Number.isFinite(seconds) && seconds >= 0) return seconds;
131
+ const date = Date.parse(raw);
132
+ if (Number.isNaN(date)) return void 0;
133
+ return Math.max(0, (date - Date.now()) / 1e3);
134
+ }
135
+ function buildUrl(baseUrl, path, query) {
136
+ const url = new URL(baseUrl.replace(/\/+$/, "") + path);
137
+ for (const [key, value] of Object.entries(query ?? {})) {
138
+ if (value === void 0 || value === null) continue;
139
+ url.searchParams.set(key, String(value));
140
+ }
141
+ return url.toString();
142
+ }
143
+ /** Maps a failed response onto the matching error class. */
144
+ function toError(status, payload, headers, apiKey) {
145
+ let code;
146
+ let message;
147
+ let details;
148
+ const err = payload?.error;
149
+ if (typeof err === "string") {
150
+ message = err;
151
+ details = payload?.details;
152
+ } else if (err && typeof err === "object") {
153
+ code = err.code;
154
+ message = err.message;
155
+ details = err.details;
156
+ }
157
+ const requestId = headers.get("x-request-id") ?? void 0;
158
+ const text = redact(message ?? `Request failed with status ${status}`, apiKey);
159
+ const opts = {
160
+ status,
161
+ code,
162
+ details,
163
+ requestId
164
+ };
165
+ if (status === 401) return new NateqAuthenticationError(text, opts);
166
+ if (status === 403) return new NateqPermissionError(text, opts);
167
+ if (status === 404) return new NateqNotFoundError(text, opts);
168
+ if (status === 400 || status === 422) return new NateqValidationError(text, opts);
169
+ if (status === 429) return new NateqRateLimitError(text, {
170
+ ...opts,
171
+ retryAfter: parseRetryAfter(headers)
172
+ });
173
+ if (status >= 500) return new NateqServerError(text, opts);
174
+ return new NateqError(text, opts);
175
+ }
176
+ /** Full-jitter exponential backoff, capped at 8s, honouring `Retry-After`. */
177
+ function backoffDelay(attempt, retryAfter) {
178
+ if (retryAfter !== void 0) return Math.min(retryAfter * 1e3, 6e4);
179
+ const ceiling = Math.min(500 * 2 ** attempt, 8e3);
180
+ return Math.random() * ceiling;
181
+ }
182
+ function sleep(ms, signal) {
183
+ return new Promise((resolve, reject) => {
184
+ if (signal?.aborted) {
185
+ reject(new NateqTimeoutError("Request aborted by caller"));
186
+ return;
187
+ }
188
+ const timer = setTimeout(() => {
189
+ signal?.removeEventListener("abort", onAbort);
190
+ resolve();
191
+ }, ms);
192
+ const onAbort = () => {
193
+ clearTimeout(timer);
194
+ reject(new NateqTimeoutError("Request aborted by caller"));
195
+ };
196
+ signal?.addEventListener("abort", onAbort, { once: true });
197
+ });
198
+ }
199
+ var Transport = class {
200
+ options;
201
+ constructor(options) {
202
+ this.options = options;
203
+ }
204
+ async request(req) {
205
+ const { apiKey, baseUrl, timeout, maxRetries, fetch: doFetch, userAgent } = this.options;
206
+ const url = buildUrl(baseUrl, req.path, req.query);
207
+ let lastError;
208
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
209
+ if (attempt > 0) {
210
+ const retryAfter = lastError instanceof NateqRateLimitError ? lastError.retryAfter : void 0;
211
+ await sleep(backoffDelay(attempt - 1, retryAfter), req.signal);
212
+ }
213
+ const timeoutSignal = AbortSignal.timeout(timeout);
214
+ const signal = req.signal ? AbortSignal.any([req.signal, timeoutSignal]) : timeoutSignal;
215
+ let response;
216
+ try {
217
+ response = await doFetch(url, {
218
+ method: req.method,
219
+ signal,
220
+ headers: {
221
+ Authorization: `Bearer ${apiKey}`,
222
+ Accept: "application/json",
223
+ "User-Agent": userAgent,
224
+ ...req.body !== void 0 ? { "Content-Type": "application/json" } : {}
225
+ },
226
+ ...req.body !== void 0 ? { body: JSON.stringify(req.body) } : {}
227
+ });
228
+ } catch (err) {
229
+ const caller = req.signal?.aborted === true;
230
+ lastError = caller ? new NateqTimeoutError("Request aborted by caller", { cause: err }) : isAbortError(err) || err?.name === "TimeoutError" ? new NateqTimeoutError(`Request timed out after ${timeout}ms`, { cause: err }) : new NateqConnectionError(redact(`Could not reach the Nateq API: ${err?.message ?? err}`, apiKey), { cause: err });
231
+ if (caller) throw lastError;
232
+ if (req.retryOnAmbiguousFailure && attempt < maxRetries) continue;
233
+ throw lastError;
234
+ }
235
+ if (response.ok) {
236
+ if (response.status === 204) return void 0;
237
+ return await response.json();
238
+ }
239
+ let payload;
240
+ try {
241
+ payload = await response.json();
242
+ } catch {
243
+ payload = void 0;
244
+ }
245
+ lastError = toError(response.status, payload, response.headers, apiKey);
246
+ if ((lastError instanceof NateqRateLimitError || req.retryOnAmbiguousFailure && (lastError instanceof NateqServerError || response.status === 408)) && attempt < maxRetries) continue;
247
+ throw lastError;
248
+ }
249
+ /* istanbul ignore next — the loop always returns or throws. */
250
+ throw lastError ?? new NateqError("Request failed");
251
+ }
252
+ };
253
+ //#endregion
254
+ //#region src/client.ts
255
+ const VERSION = "0.1.0";
256
+ /** Recognised API-key prefixes, mirroring the API's own validation. */
257
+ const KEY_PREFIXES = ["tg_live_", "tg_test_"];
258
+ const MIN_KEY_LENGTH = 50;
259
+ function resolveApiKey(explicit) {
260
+ const key = explicit ?? process.env["NATEQ_API_KEY"];
261
+ if (!key) throw new NateqConfigurationError("No API key provided. Pass `new Nateq({ apiKey })` or set the NATEQ_API_KEY environment variable.");
262
+ if (key !== key.trim()) throw new NateqConfigurationError("The API key has leading or trailing whitespace, which usually means it was copied incorrectly.");
263
+ if (!KEY_PREFIXES.some((p) => key.startsWith(p))) throw new NateqConfigurationError(`Invalid API key format: expected it to start with ${KEY_PREFIXES.join(" or ")}.`);
264
+ if (key.length < MIN_KEY_LENGTH) throw new NateqConfigurationError("Invalid API key format: the key is too short.");
265
+ return key;
266
+ }
267
+ function resolveBaseUrl(explicit) {
268
+ const raw = explicit ?? process.env["NATEQ_BASE_URL"] ?? "https://api.nateq.io/api/v1";
269
+ let url;
270
+ try {
271
+ url = new URL(raw);
272
+ } catch {
273
+ throw new NateqConfigurationError(`Invalid baseUrl: ${raw}`);
274
+ }
275
+ const isLoopback = [
276
+ "localhost",
277
+ "127.0.0.1",
278
+ "[::1]",
279
+ "::1"
280
+ ].includes(url.hostname);
281
+ if (url.protocol !== "https:" && !isLoopback) throw new NateqConfigurationError(`Refusing to send an API key over ${url.protocol}// to ${url.hostname}. Use https, or a localhost address for local development.`);
282
+ return url.toString().replace(/\/+$/, "");
283
+ }
284
+ /**
285
+ * The Nateq API client.
286
+ *
287
+ * ```ts
288
+ * import { Nateq } from "@nateq/sdk";
289
+ *
290
+ * const nateq = new Nateq(); // reads NATEQ_API_KEY
291
+ *
292
+ * await nateq.emails.send({
293
+ * toEmails: ["customer@example.com"],
294
+ * subject: "Welcome aboard",
295
+ * htmlBody: "<p>Thanks for signing up.</p>",
296
+ * });
297
+ * ```
298
+ *
299
+ * The instance holds your API key, so it is never safe to serialise: `console.log`,
300
+ * `JSON.stringify`, and util.inspect all render it with the key redacted.
301
+ */
302
+ var Nateq = class {
303
+ /** Outbound email: send, get, list. */
304
+ emails;
305
+ baseUrl;
306
+ /** Stored non-enumerably so the key cannot leak via property enumeration. */
307
+ #apiKey;
308
+ constructor(options = {}) {
309
+ this.#apiKey = resolveApiKey(options.apiKey);
310
+ this.baseUrl = resolveBaseUrl(options.baseUrl);
311
+ const fetchImpl = options.fetch ?? globalThis.fetch;
312
+ if (typeof fetchImpl !== "function") throw new NateqConfigurationError("No global fetch available. Use Node 18+, or pass a fetch implementation via `new Nateq({ fetch })`.");
313
+ const transport = new Transport({
314
+ apiKey: this.#apiKey,
315
+ baseUrl: this.baseUrl,
316
+ timeout: options.timeout ?? 3e4,
317
+ maxRetries: options.maxRetries ?? 2,
318
+ fetch: fetchImpl.bind(globalThis),
319
+ userAgent: `nateq-node/${VERSION} (node ${process.versions.node})`
320
+ });
321
+ this.emails = new Emails(transport);
322
+ }
323
+ /** Last four characters of the key, for logging which key is in use. */
324
+ get apiKeyLast4() {
325
+ return this.#apiKey.slice(-4);
326
+ }
327
+ toJSON() {
328
+ return {
329
+ baseUrl: this.baseUrl,
330
+ apiKey: `[REDACTED:...${this.apiKeyLast4}]`
331
+ };
332
+ }
333
+ /** Keeps the key out of `console.log(client)` and util.inspect output. */
334
+ [Symbol.for("nodejs.util.inspect.custom")]() {
335
+ return `Nateq { baseUrl: '${this.baseUrl}', apiKey: '[REDACTED:...${this.apiKeyLast4}]' }`;
336
+ }
337
+ };
338
+ //#endregion
339
+ export { Emails, Nateq, NateqAuthenticationError, NateqConfigurationError, NateqConnectionError, NateqError, NateqNotFoundError, NateqPermissionError, NateqRateLimitError, NateqServerError, NateqTimeoutError, NateqValidationError };
340
+
341
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["#apiKey"],"sources":["../src/errors.ts","../src/emails.ts","../src/http.ts","../src/client.ts"],"sourcesContent":["/**\n * Error types raised by the Nateq SDK.\n *\n * Every error extends {@link NateqError}, so a single `catch (err) { if (err\n * instanceof NateqError) ... }` covers the whole surface. No error ever carries\n * the API key: see `redact` in `http.ts`.\n */\n\n/** Machine-readable code returned by the API, when it sends one. */\nexport type NateqErrorCode =\n | \"MISSING_API_KEY\"\n | \"INVALID_AUTH_FORMAT\"\n | \"INVALID_API_KEY_FORMAT\"\n | \"INVALID_API_KEY\"\n | \"AUTHENTICATION_FAILED\"\n | \"ENDPOINT_NOT_PUBLIC\"\n | \"INSUFFICIENT_SCOPE\"\n | \"INSUFFICIENT_PERMISSION\"\n | \"IP_NOT_ALLOWED\"\n | \"RATE_LIMIT_EXCEEDED\"\n | (string & {});\n\nexport class NateqError extends Error {\n /** HTTP status, when the failure came from a response. */\n readonly status?: number | undefined;\n /** API error code, when the response carried one. */\n readonly code?: NateqErrorCode | undefined;\n /** Extra context from the API (validation details, scope lists, ...). */\n readonly details?: unknown;\n /** Value of the `X-Request-Id` response header, useful for support tickets. */\n readonly requestId?: string | undefined;\n\n constructor(\n message: string,\n opts: {\n status?: number | undefined;\n code?: NateqErrorCode | undefined;\n details?: unknown;\n requestId?: string | undefined;\n cause?: unknown;\n } = {},\n ) {\n super(message, opts.cause !== undefined ? { cause: opts.cause } : undefined);\n this.name = new.target.name;\n this.status = opts.status;\n this.code = opts.code;\n this.details = opts.details;\n this.requestId = opts.requestId;\n Error.captureStackTrace?.(this, new.target);\n }\n}\n\n/** The key is missing, malformed, revoked, or unknown (401). */\nexport class NateqAuthenticationError extends NateqError {}\n\n/**\n * The key is valid but not allowed to do this (403).\n *\n * Common causes: the key lacks the `emails:send` scope, the endpoint is not\n * exposed to API keys, or the caller's IP is outside the key's allow-list.\n */\nexport class NateqPermissionError extends NateqError {}\n\n/** The request body failed validation (400/422). */\nexport class NateqValidationError extends NateqError {}\n\n/** The requested resource does not exist, or is not visible to this key (404). */\nexport class NateqNotFoundError extends NateqError {}\n\n/** A rate limit was hit (429). Nothing was sent — this is safe to retry. */\nexport class NateqRateLimitError extends NateqError {\n /** Seconds to wait before retrying, when the API supplies `Retry-After`. */\n readonly retryAfter?: number | undefined;\n\n constructor(\n message: string,\n opts: ConstructorParameters<typeof NateqError>[1] & { retryAfter?: number | undefined } = {},\n ) {\n super(message, opts);\n this.retryAfter = opts.retryAfter;\n }\n}\n\n/** The API failed to handle the request (5xx). */\nexport class NateqServerError extends NateqError {}\n\n/** The request never produced a response: DNS, TLS, socket, or offline. */\nexport class NateqConnectionError extends NateqError {}\n\n/** The request exceeded the configured timeout, or was aborted by the caller. */\nexport class NateqTimeoutError extends NateqError {}\n\n/** Bad SDK usage — thrown before any network call (never carries a status). */\nexport class NateqConfigurationError extends NateqError {}\n","import { NateqValidationError } from \"./errors.js\";\nimport type { Transport } from \"./http.js\";\nimport type {\n ListEmailsParams,\n ListEmailsResult,\n OutboundEmail,\n SendEmailParams,\n SendEmailResult,\n} from \"./types.js\";\n\n/** Outbound email operations. Reachable at `client.emails`. */\nexport class Emails {\n constructor(private readonly transport: Transport) {}\n\n /**\n * Sends an email, returning once the API has accepted it. Delivery itself is\n * asynchronous — poll {@link get} or subscribe to a webhook for the outcome.\n *\n * Requires an API key with the `emails:send` scope.\n *\n * A failed send is **not** replayed automatically unless the API rejected it\n * outright (429), because a request that fails after reaching the server may\n * already have sent the mail. On a timeout or 5xx, look the email up with\n * {@link list} before retrying, or you risk sending twice.\n *\n * @throws {NateqValidationError} if the params are unusable, before any call.\n * @throws {NateqPermissionError} if the key lacks the `emails:send` scope.\n */\n async send(params: SendEmailParams, opts: { signal?: AbortSignal } = {}): Promise<SendEmailResult> {\n // Validate locally what the API would reject anyway, so the common mistakes\n // surface as a typed error instead of an opaque 400.\n if (!params.toEmails?.length) {\n throw new NateqValidationError(\"`toEmails` must contain at least one recipient.\");\n }\n if (!params.subject?.trim()) {\n throw new NateqValidationError(\"`subject` is required.\");\n }\n if (!params.htmlBody?.trim() && !params.plainTextBody?.trim()) {\n throw new NateqValidationError(\"Provide `htmlBody`, `plainTextBody`, or both.\");\n }\n\n return this.transport.request<SendEmailResult>({\n method: \"POST\",\n path: \"/outbound-emails\",\n body: params,\n signal: opts.signal,\n // Sending is not idempotent and the API has no idempotency key, so an\n // ambiguous failure must surface rather than silently double-send.\n retryOnAmbiguousFailure: false,\n });\n }\n\n /**\n * Fetches a single sent email by id, including delivery status.\n *\n * Requires an API key with the `emails:read` scope.\n */\n async get(id: string, opts: { signal?: AbortSignal } = {}): Promise<OutboundEmail> {\n if (!id?.trim()) throw new NateqValidationError(\"`id` is required.\");\n\n return this.transport.request<OutboundEmail>({\n method: \"GET\",\n path: `/outbound-emails/${encodeURIComponent(id)}`,\n signal: opts.signal,\n retryOnAmbiguousFailure: true,\n });\n }\n\n /**\n * Lists sent emails, newest first. `limit` defaults to 50 and is capped at 100.\n *\n * Requires an API key with the `emails:read` scope.\n */\n async list(\n params: ListEmailsParams = {},\n opts: { signal?: AbortSignal } = {},\n ): Promise<ListEmailsResult> {\n return this.transport.request<ListEmailsResult>({\n method: \"GET\",\n path: \"/outbound-emails\",\n query: params as Record<string, unknown>,\n signal: opts.signal,\n retryOnAmbiguousFailure: true,\n });\n }\n}\n","import {\n NateqAuthenticationError,\n NateqConnectionError,\n NateqError,\n NateqNotFoundError,\n NateqPermissionError,\n NateqRateLimitError,\n NateqServerError,\n NateqTimeoutError,\n NateqValidationError,\n type NateqErrorCode,\n} from \"./errors.js\";\n\n/** Subset of `fetch` the SDK relies on, so callers can inject their own. */\nexport type FetchLike = (input: string, init: RequestInit) => Promise<Response>;\n\nexport interface TransportOptions {\n apiKey: string;\n baseUrl: string;\n timeout: number;\n maxRetries: number;\n fetch: FetchLike;\n userAgent: string;\n}\n\nexport interface RequestOptions {\n method: \"GET\" | \"POST\";\n path: string;\n query?: Record<string, unknown> | undefined;\n body?: unknown;\n signal?: AbortSignal | undefined;\n /**\n * Whether this request may be replayed after an ambiguous failure (network\n * error, timeout, 5xx) — i.e. a failure where the server may already have\n * acted. Only safe for requests with no side effects.\n *\n * A 429 is retried regardless: the API rejects rate-limited sends during\n * validation, before any mail leaves, so no duplicate can result.\n */\n retryOnAmbiguousFailure: boolean;\n}\n\n/**\n * The two error envelopes the API emits. Auth middleware returns the standard\n * `{success,error:{code,message,details}}` envelope; the outbound-email handler\n * returns a flat `{error,details}`. Both are parsed into one error type.\n */\ninterface EnvelopeError {\n success?: boolean;\n error?: { code?: string; message?: string; details?: unknown } | string;\n details?: unknown;\n}\n\n/**\n * Removes the API key from a string. Applied to every message the SDK raises so\n * a leaked stack trace or log line can never reveal the credential.\n */\nfunction redact(text: string, apiKey: string): string {\n return apiKey ? text.split(apiKey).join(\"[REDACTED]\") : text;\n}\n\nfunction isAbortError(err: unknown): boolean {\n return err instanceof Error && err.name === \"AbortError\";\n}\n\nfunction parseRetryAfter(headers: Headers): number | undefined {\n const raw = headers.get(\"retry-after\");\n if (!raw) return undefined;\n const seconds = Number(raw);\n if (Number.isFinite(seconds) && seconds >= 0) return seconds;\n const date = Date.parse(raw);\n if (Number.isNaN(date)) return undefined;\n return Math.max(0, (date - Date.now()) / 1000);\n}\n\nfunction buildUrl(baseUrl: string, path: string, query?: Record<string, unknown>): string {\n const url = new URL(baseUrl.replace(/\\/+$/, \"\") + path);\n for (const [key, value] of Object.entries(query ?? {})) {\n if (value === undefined || value === null) continue;\n url.searchParams.set(key, String(value));\n }\n return url.toString();\n}\n\n/** Maps a failed response onto the matching error class. */\nfunction toError(\n status: number,\n payload: EnvelopeError | undefined,\n headers: Headers,\n apiKey: string,\n): NateqError {\n let code: string | undefined;\n let message: string | undefined;\n let details: unknown;\n\n const err = payload?.error;\n if (typeof err === \"string\") {\n // Flat shape: { error: \"Failed to send email\", details: \"...\" }\n message = err;\n details = payload?.details;\n } else if (err && typeof err === \"object\") {\n // Envelope shape: { success: false, error: { code, message, details } }\n code = err.code;\n message = err.message;\n details = err.details;\n }\n\n const requestId = headers.get(\"x-request-id\") ?? undefined;\n const text = redact(message ?? `Request failed with status ${status}`, apiKey);\n const opts = {\n status,\n code: code as NateqErrorCode | undefined,\n details,\n requestId,\n };\n\n if (status === 401) return new NateqAuthenticationError(text, opts);\n if (status === 403) return new NateqPermissionError(text, opts);\n if (status === 404) return new NateqNotFoundError(text, opts);\n if (status === 400 || status === 422) return new NateqValidationError(text, opts);\n if (status === 429) {\n return new NateqRateLimitError(text, { ...opts, retryAfter: parseRetryAfter(headers) });\n }\n if (status >= 500) return new NateqServerError(text, opts);\n return new NateqError(text, opts);\n}\n\n/** Full-jitter exponential backoff, capped at 8s, honouring `Retry-After`. */\nfunction backoffDelay(attempt: number, retryAfter?: number): number {\n if (retryAfter !== undefined) return Math.min(retryAfter * 1000, 60_000);\n const ceiling = Math.min(500 * 2 ** attempt, 8_000);\n return Math.random() * ceiling;\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new NateqTimeoutError(\"Request aborted by caller\"));\n return;\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(timer);\n reject(new NateqTimeoutError(\"Request aborted by caller\"));\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\nexport class Transport {\n constructor(private readonly options: TransportOptions) {}\n\n async request<T>(req: RequestOptions): Promise<T> {\n const { apiKey, baseUrl, timeout, maxRetries, fetch: doFetch, userAgent } = this.options;\n const url = buildUrl(baseUrl, req.path, req.query);\n\n let lastError: NateqError | undefined;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n if (attempt > 0) {\n const retryAfter = lastError instanceof NateqRateLimitError ? lastError.retryAfter : undefined;\n await sleep(backoffDelay(attempt - 1, retryAfter), req.signal);\n }\n\n // Per-attempt timeout, combined with any caller-supplied signal.\n const timeoutSignal = AbortSignal.timeout(timeout);\n const signal = req.signal\n ? AbortSignal.any([req.signal, timeoutSignal])\n : timeoutSignal;\n\n let response: Response;\n try {\n response = await doFetch(url, {\n method: req.method,\n signal,\n headers: {\n // The key travels in the Authorization header only — never in the\n // URL, where it would land in server logs and browser history.\n Authorization: `Bearer ${apiKey}`,\n Accept: \"application/json\",\n \"User-Agent\": userAgent,\n ...(req.body !== undefined ? { \"Content-Type\": \"application/json\" } : {}),\n },\n ...(req.body !== undefined ? { body: JSON.stringify(req.body) } : {}),\n });\n } catch (err) {\n // The request produced no response. Whether the server acted is unknown.\n const caller = req.signal?.aborted === true;\n lastError = caller\n ? new NateqTimeoutError(\"Request aborted by caller\", { cause: err })\n : isAbortError(err) || (err as Error)?.name === \"TimeoutError\"\n ? new NateqTimeoutError(`Request timed out after ${timeout}ms`, { cause: err })\n : new NateqConnectionError(\n redact(`Could not reach the Nateq API: ${(err as Error)?.message ?? err}`, apiKey),\n { cause: err },\n );\n\n if (caller) throw lastError;\n if (req.retryOnAmbiguousFailure && attempt < maxRetries) continue;\n throw lastError;\n }\n\n if (response.ok) {\n if (response.status === 204) return undefined as T;\n return (await response.json()) as T;\n }\n\n let payload: EnvelopeError | undefined;\n try {\n payload = (await response.json()) as EnvelopeError;\n } catch {\n payload = undefined;\n }\n\n lastError = toError(response.status, payload, response.headers, apiKey);\n\n const retriable =\n lastError instanceof NateqRateLimitError ||\n (req.retryOnAmbiguousFailure &&\n (lastError instanceof NateqServerError || response.status === 408));\n\n if (retriable && attempt < maxRetries) continue;\n throw lastError;\n }\n\n /* istanbul ignore next — the loop always returns or throws. */\n throw lastError ?? new NateqError(\"Request failed\");\n }\n}\n","import { Emails } from \"./emails.js\";\nimport { NateqConfigurationError } from \"./errors.js\";\nimport { Transport, type FetchLike } from \"./http.js\";\n\nconst VERSION = \"0.1.0\";\n\n/** Recognised API-key prefixes, mirroring the API's own validation. */\nconst KEY_PREFIXES = [\"tg_live_\", \"tg_test_\"] as const;\nconst MIN_KEY_LENGTH = 50;\n\nexport interface NateqOptions {\n /**\n * Your Nateq API key. Defaults to `process.env.NATEQ_API_KEY`.\n *\n * Load this from the environment or a secret manager — never commit it, and\n * never ship it to a browser: the key carries your organization's full scope\n * grant and this SDK is server-side only.\n */\n apiKey?: string;\n /** API base URL. Defaults to `process.env.NATEQ_BASE_URL` or production. */\n baseUrl?: string;\n /** Per-request timeout in milliseconds. Defaults to 30000. */\n timeout?: number;\n /** Retries after a retriable failure. Defaults to 2. Set 0 to disable. */\n maxRetries?: number;\n /** Custom fetch, for tests or proxies. Defaults to the global `fetch`. */\n fetch?: FetchLike;\n}\n\nfunction resolveApiKey(explicit?: string): string {\n const key = explicit ?? process.env[\"NATEQ_API_KEY\"];\n\n if (!key) {\n throw new NateqConfigurationError(\n \"No API key provided. Pass `new Nateq({ apiKey })` or set the NATEQ_API_KEY environment variable.\",\n );\n }\n if (key !== key.trim()) {\n throw new NateqConfigurationError(\n \"The API key has leading or trailing whitespace, which usually means it was copied incorrectly.\",\n );\n }\n // Validate locally so an obviously bad key fails fast, without a round-trip\n // that would put the credential on the wire.\n if (!KEY_PREFIXES.some((p) => key.startsWith(p))) {\n throw new NateqConfigurationError(\n `Invalid API key format: expected it to start with ${KEY_PREFIXES.join(\" or \")}.`,\n );\n }\n if (key.length < MIN_KEY_LENGTH) {\n throw new NateqConfigurationError(\"Invalid API key format: the key is too short.\");\n }\n return key;\n}\n\nfunction resolveBaseUrl(explicit?: string): string {\n const raw = explicit ?? process.env[\"NATEQ_BASE_URL\"] ?? \"https://api.nateq.io/api/v1\";\n\n let url: URL;\n try {\n url = new URL(raw);\n } catch {\n throw new NateqConfigurationError(`Invalid baseUrl: ${raw}`);\n }\n\n // Refuse to send the key over cleartext. Loopback is exempt so local\n // development against a plain-HTTP server still works.\n const isLoopback = [\"localhost\", \"127.0.0.1\", \"[::1]\", \"::1\"].includes(url.hostname);\n if (url.protocol !== \"https:\" && !isLoopback) {\n throw new NateqConfigurationError(\n `Refusing to send an API key over ${url.protocol}// to ${url.hostname}. Use https, or a localhost address for local development.`,\n );\n }\n return url.toString().replace(/\\/+$/, \"\");\n}\n\n/**\n * The Nateq API client.\n *\n * ```ts\n * import { Nateq } from \"@nateq/sdk\";\n *\n * const nateq = new Nateq(); // reads NATEQ_API_KEY\n *\n * await nateq.emails.send({\n * toEmails: [\"customer@example.com\"],\n * subject: \"Welcome aboard\",\n * htmlBody: \"<p>Thanks for signing up.</p>\",\n * });\n * ```\n *\n * The instance holds your API key, so it is never safe to serialise: `console.log`,\n * `JSON.stringify`, and util.inspect all render it with the key redacted.\n */\nexport class Nateq {\n /** Outbound email: send, get, list. */\n readonly emails: Emails;\n\n readonly baseUrl: string;\n\n /** Stored non-enumerably so the key cannot leak via property enumeration. */\n readonly #apiKey: string;\n\n constructor(options: NateqOptions = {}) {\n this.#apiKey = resolveApiKey(options.apiKey);\n this.baseUrl = resolveBaseUrl(options.baseUrl);\n\n const fetchImpl = options.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== \"function\") {\n throw new NateqConfigurationError(\n \"No global fetch available. Use Node 18+, or pass a fetch implementation via `new Nateq({ fetch })`.\",\n );\n }\n\n const transport = new Transport({\n apiKey: this.#apiKey,\n baseUrl: this.baseUrl,\n timeout: options.timeout ?? 30_000,\n maxRetries: options.maxRetries ?? 2,\n fetch: fetchImpl.bind(globalThis) as FetchLike,\n userAgent: `nateq-node/${VERSION} (node ${process.versions.node})`,\n });\n\n this.emails = new Emails(transport);\n }\n\n /** Last four characters of the key, for logging which key is in use. */\n get apiKeyLast4(): string {\n return this.#apiKey.slice(-4);\n }\n\n toJSON(): Record<string, unknown> {\n return { baseUrl: this.baseUrl, apiKey: `[REDACTED:...${this.apiKeyLast4}]` };\n }\n\n /** Keeps the key out of `console.log(client)` and util.inspect output. */\n [Symbol.for(\"nodejs.util.inspect.custom\")](): string {\n return `Nateq { baseUrl: '${this.baseUrl}', apiKey: '[REDACTED:...${this.apiKeyLast4}]' }`;\n }\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@nateq/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Official Node.js SDK for the Nateq API",
5
+ "keywords": [
6
+ "nateq",
7
+ "email",
8
+ "sdk",
9
+ "api"
10
+ ],
11
+ "license": "MIT",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/nateq-ai/sdk-node.git"
15
+ },
16
+ "homepage": "https://github.com/nateq-ai/sdk-node#readme",
17
+ "bugs": {
18
+ "url": "https://github.com/nateq-ai/sdk-node/issues"
19
+ },
20
+ "type": "module",
21
+ "main": "./dist/index.cjs",
22
+ "module": "./dist/index.js",
23
+ "types": "./dist/index.d.ts",
24
+ "exports": {
25
+ ".": {
26
+ "import": {
27
+ "types": "./dist/index.d.ts",
28
+ "default": "./dist/index.js"
29
+ },
30
+ "require": {
31
+ "types": "./dist/index.d.cts",
32
+ "default": "./dist/index.cjs"
33
+ }
34
+ }
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "README.md",
39
+ "LICENSE"
40
+ ],
41
+ "engines": {
42
+ "node": ">=18"
43
+ },
44
+ "sideEffects": false,
45
+ "scripts": {
46
+ "build": "tsdown",
47
+ "test": "vitest run",
48
+ "typecheck": "tsc --noEmit",
49
+ "check-exports": "attw --pack .",
50
+ "prepublishOnly": "npm run typecheck && npm run test && npm run build && npm run check-exports"
51
+ },
52
+ "devDependencies": {
53
+ "@arethetypeswrong/cli": "^0.18.5",
54
+ "@types/node": "^26.1.1",
55
+ "tsdown": "^0.22.9",
56
+ "typescript": "^7.0.2",
57
+ "vitest": "^4.1.10"
58
+ },
59
+ "publishConfig": {
60
+ "access": "public"
61
+ }
62
+ }