@primitivedotdev/sdk 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -210
- package/dist/address-parser-BYn8oW5r.js +111 -0
- package/dist/api/generated/index.js +1 -1
- package/dist/api/generated/sdk.gen.js +17 -0
- package/dist/api/index.d.ts +2 -1877
- package/dist/api/index.js +255 -0
- package/dist/api-COSr-Fqm.js +1311 -0
- package/dist/chunk-pbuEa-1d.js +13 -0
- package/dist/contract/index.d.ts +6 -8
- package/dist/contract/index.js +28 -15
- package/dist/{index-DLmAI4UQ.d.ts → index-CbEivn3S.d.ts} +13 -30
- package/dist/index-DVow4Fjd.d.ts +2140 -0
- package/dist/index.d.ts +12 -3
- package/dist/index.js +10 -3
- package/dist/oclif/api-command.js +89 -1
- package/dist/openapi/index.d.ts +8 -3
- package/dist/openapi/openapi.generated.js +412 -1
- package/dist/openapi/operations.generated.js +255 -0
- package/dist/parser/address-parser.js +129 -0
- package/dist/parser/index.d.ts +4 -19
- package/dist/parser/index.js +7 -122
- package/dist/received-email-D6tKtWwW.js +69 -0
- package/dist/received-email-DNjpq_Wt.d.ts +37 -0
- package/dist/{types-CKFmgitP.d.ts → types-9vXGZjPd.d.ts} +3 -19
- package/dist/types.generated.js +7 -0
- package/dist/types.js +53 -0
- package/dist/webhook/index.d.ts +4 -3
- package/dist/webhook/index.js +3 -3
- package/dist/webhook/received-email.js +82 -0
- package/dist/{webhook-COe5N_Uj.js → webhook-zkN4wUTs.js} +119 -81
- package/oclif.manifest.json +54 -8
- package/package.json +5 -2
- package/dist/chunk-Cl8Af3a2.js +0 -11
package/dist/api/index.js
CHANGED
|
@@ -4,9 +4,15 @@
|
|
|
4
4
|
* Generated operations are exported directly, and `PrimitiveApiClient`
|
|
5
5
|
* provides a configured fetch client for those operations.
|
|
6
6
|
*/
|
|
7
|
+
import { formatAddress } from "../webhook/received-email.js";
|
|
7
8
|
import { createClient, createConfig, } from "./generated/client/index.js";
|
|
8
9
|
import * as generatedOperations from "./generated/sdk.gen.js";
|
|
9
10
|
export const DEFAULT_BASE_URL = "https://www.primitive.dev/api/v1";
|
|
11
|
+
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
12
|
+
const MAX_THREAD_REFERENCES = 100;
|
|
13
|
+
const MAX_THREAD_HEADER_BYTES = 8 * 1024;
|
|
14
|
+
const MAX_FROM_HEADER_LENGTH = 998;
|
|
15
|
+
const MAX_TO_HEADER_LENGTH = 320;
|
|
10
16
|
function createDefaultAuth(apiKey) {
|
|
11
17
|
return (security) => {
|
|
12
18
|
if (security.type === "http" && security.scheme === "bearer") {
|
|
@@ -15,6 +21,134 @@ function createDefaultAuth(apiKey) {
|
|
|
15
21
|
return undefined;
|
|
16
22
|
};
|
|
17
23
|
}
|
|
24
|
+
function validateAddressHeader(field, value) {
|
|
25
|
+
const trimmed = value.trim();
|
|
26
|
+
const maxLength = field === "from" ? MAX_FROM_HEADER_LENGTH : MAX_TO_HEADER_LENGTH;
|
|
27
|
+
if (trimmed.length < 3) {
|
|
28
|
+
throw new TypeError(`${field} must be at least 3 characters`);
|
|
29
|
+
}
|
|
30
|
+
if (trimmed.length > maxLength) {
|
|
31
|
+
throw new TypeError(`${field} must be at most ${maxLength} characters`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function validateEmailAddress(field, value) {
|
|
35
|
+
if (!EMAIL_REGEX.test(value) &&
|
|
36
|
+
!/^.+<[^\s@]+@[^\s@]+\.[^\s@]+>$/.test(value)) {
|
|
37
|
+
throw new TypeError(`${field} must be a valid email address`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function validateThreadHeaderValue(field, value) {
|
|
41
|
+
if (value.trim().length === 0) {
|
|
42
|
+
throw new TypeError(`${field} must be a non-empty string`);
|
|
43
|
+
}
|
|
44
|
+
if ([...value].some((char) => {
|
|
45
|
+
const code = char.charCodeAt(0);
|
|
46
|
+
return code <= 0x1f || code === 0x7f;
|
|
47
|
+
})) {
|
|
48
|
+
throw new TypeError(`${field} must not contain control characters`);
|
|
49
|
+
}
|
|
50
|
+
if (value.length > 998) {
|
|
51
|
+
throw new TypeError(`${field} must be at most 998 characters`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function validateSendInput(input) {
|
|
55
|
+
validateAddressHeader("from", input.from);
|
|
56
|
+
validateAddressHeader("to", input.to);
|
|
57
|
+
validateEmailAddress("to", input.to);
|
|
58
|
+
if (input.subject.trim().length === 0) {
|
|
59
|
+
throw new TypeError("subject must be a non-empty string");
|
|
60
|
+
}
|
|
61
|
+
if (!input.bodyText && !input.bodyHtml) {
|
|
62
|
+
throw new TypeError("one of bodyText or bodyHtml is required");
|
|
63
|
+
}
|
|
64
|
+
if (input.thread?.inReplyTo) {
|
|
65
|
+
validateThreadHeaderValue("thread.inReplyTo", input.thread.inReplyTo);
|
|
66
|
+
}
|
|
67
|
+
if (input.thread?.references) {
|
|
68
|
+
if (input.thread.references.length > MAX_THREAD_REFERENCES) {
|
|
69
|
+
throw new TypeError(`thread.references must contain at most ${MAX_THREAD_REFERENCES} values`);
|
|
70
|
+
}
|
|
71
|
+
for (const [index, reference] of input.thread.references.entries()) {
|
|
72
|
+
validateThreadHeaderValue(`thread.references[${index}]`, reference);
|
|
73
|
+
}
|
|
74
|
+
if (input.thread.references.join(" ").length > MAX_THREAD_HEADER_BYTES) {
|
|
75
|
+
throw new TypeError(`thread.references header must be at most ${MAX_THREAD_HEADER_BYTES} characters`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (input.waitTimeoutMs !== undefined) {
|
|
79
|
+
if (!Number.isInteger(input.waitTimeoutMs)) {
|
|
80
|
+
throw new TypeError("waitTimeoutMs must be an integer");
|
|
81
|
+
}
|
|
82
|
+
if (input.waitTimeoutMs < 1000 || input.waitTimeoutMs > 30000) {
|
|
83
|
+
throw new TypeError("waitTimeoutMs must be between 1000 and 30000");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function validateForwardInput(input) {
|
|
88
|
+
validateEmailAddress("to", input.to);
|
|
89
|
+
if (input.subject !== undefined && input.subject.trim().length === 0) {
|
|
90
|
+
throw new TypeError("subject must be a non-empty string");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function parseApiErrorPayload(payload) {
|
|
94
|
+
const fallback = {
|
|
95
|
+
message: "Primitive API request failed",
|
|
96
|
+
code: undefined,
|
|
97
|
+
gates: undefined,
|
|
98
|
+
requestId: undefined,
|
|
99
|
+
details: undefined,
|
|
100
|
+
};
|
|
101
|
+
if (!payload || typeof payload !== "object") {
|
|
102
|
+
return fallback;
|
|
103
|
+
}
|
|
104
|
+
if ("error" in payload &&
|
|
105
|
+
payload.error &&
|
|
106
|
+
typeof payload.error === "object") {
|
|
107
|
+
const err = payload.error;
|
|
108
|
+
return {
|
|
109
|
+
message: typeof err.message === "string" ? err.message : fallback.message,
|
|
110
|
+
code: typeof err.code === "string" ? err.code : undefined,
|
|
111
|
+
gates: Array.isArray(err.gates) ? err.gates : undefined,
|
|
112
|
+
requestId: typeof err.request_id === "string" ? err.request_id : undefined,
|
|
113
|
+
details: err.details && typeof err.details === "object"
|
|
114
|
+
? err.details
|
|
115
|
+
: undefined,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if ("message" in payload && typeof payload.message === "string") {
|
|
119
|
+
return { ...fallback, message: payload.message };
|
|
120
|
+
}
|
|
121
|
+
return fallback;
|
|
122
|
+
}
|
|
123
|
+
export class PrimitiveApiError extends Error {
|
|
124
|
+
status;
|
|
125
|
+
code;
|
|
126
|
+
gates;
|
|
127
|
+
requestId;
|
|
128
|
+
retryAfter;
|
|
129
|
+
details;
|
|
130
|
+
payload;
|
|
131
|
+
constructor(message, options) {
|
|
132
|
+
super(message);
|
|
133
|
+
this.name = "PrimitiveApiError";
|
|
134
|
+
this.payload = options.payload;
|
|
135
|
+
this.status = options.status;
|
|
136
|
+
this.code = options.code;
|
|
137
|
+
this.gates = options.gates;
|
|
138
|
+
this.requestId = options.requestId;
|
|
139
|
+
this.retryAfter = options.retryAfter;
|
|
140
|
+
this.details = options.details;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function parseRetryAfterHeader(response) {
|
|
144
|
+
if (!response)
|
|
145
|
+
return undefined;
|
|
146
|
+
const raw = response.headers.get("retry-after");
|
|
147
|
+
if (!raw)
|
|
148
|
+
return undefined;
|
|
149
|
+
const seconds = Number.parseInt(raw, 10);
|
|
150
|
+
return Number.isFinite(seconds) ? seconds : undefined;
|
|
151
|
+
}
|
|
18
152
|
export class PrimitiveApiClient {
|
|
19
153
|
client;
|
|
20
154
|
constructor(options = {}) {
|
|
@@ -32,8 +166,129 @@ export class PrimitiveApiClient {
|
|
|
32
166
|
return this.client.setConfig(config);
|
|
33
167
|
}
|
|
34
168
|
}
|
|
169
|
+
export class PrimitiveClient extends PrimitiveApiClient {
|
|
170
|
+
async send(input) {
|
|
171
|
+
validateSendInput(input);
|
|
172
|
+
const body = {
|
|
173
|
+
from: input.from,
|
|
174
|
+
to: input.to,
|
|
175
|
+
subject: input.subject,
|
|
176
|
+
...(input.bodyText !== undefined ? { body_text: input.bodyText } : {}),
|
|
177
|
+
...(input.bodyHtml !== undefined ? { body_html: input.bodyHtml } : {}),
|
|
178
|
+
...(input.thread?.inReplyTo
|
|
179
|
+
? { in_reply_to: input.thread.inReplyTo }
|
|
180
|
+
: {}),
|
|
181
|
+
...(input.thread?.references?.length
|
|
182
|
+
? { references: input.thread.references }
|
|
183
|
+
: {}),
|
|
184
|
+
...(input.wait !== undefined ? { wait: input.wait } : {}),
|
|
185
|
+
...(input.waitTimeoutMs !== undefined
|
|
186
|
+
? { wait_timeout_ms: input.waitTimeoutMs }
|
|
187
|
+
: {}),
|
|
188
|
+
};
|
|
189
|
+
const result = await generatedOperations.sendEmail({
|
|
190
|
+
body,
|
|
191
|
+
...(input.idempotencyKey
|
|
192
|
+
? { headers: { "Idempotency-Key": input.idempotencyKey } }
|
|
193
|
+
: {}),
|
|
194
|
+
client: this.client,
|
|
195
|
+
responseStyle: "fields",
|
|
196
|
+
});
|
|
197
|
+
const response = result.response;
|
|
198
|
+
if (result.error) {
|
|
199
|
+
const parsed = parseApiErrorPayload(result.error);
|
|
200
|
+
throw new PrimitiveApiError(parsed.message, {
|
|
201
|
+
payload: result.error,
|
|
202
|
+
status: response?.status,
|
|
203
|
+
code: parsed.code,
|
|
204
|
+
gates: parsed.gates,
|
|
205
|
+
requestId: parsed.requestId,
|
|
206
|
+
retryAfter: parseRetryAfterHeader(response),
|
|
207
|
+
details: parsed.details,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
if (!result.data?.data) {
|
|
211
|
+
throw new PrimitiveApiError("Primitive API returned no send result", {
|
|
212
|
+
payload: result,
|
|
213
|
+
status: response?.status,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
return mapSendResult(result.data.data);
|
|
217
|
+
}
|
|
218
|
+
async reply(email, input) {
|
|
219
|
+
const resolved = typeof input === "string" ? { text: input } : input;
|
|
220
|
+
return this.send({
|
|
221
|
+
from: resolved.from ?? email.receivedBy,
|
|
222
|
+
to: email.replyTarget.address,
|
|
223
|
+
subject: resolved.subject ?? email.replySubject,
|
|
224
|
+
bodyText: resolved.text,
|
|
225
|
+
thread: {
|
|
226
|
+
...(email.thread.messageId
|
|
227
|
+
? { inReplyTo: email.thread.messageId }
|
|
228
|
+
: {}),
|
|
229
|
+
references: email.thread.messageId
|
|
230
|
+
? [...email.thread.references, email.thread.messageId]
|
|
231
|
+
: email.thread.references,
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
async forward(email, input) {
|
|
236
|
+
validateForwardInput(input);
|
|
237
|
+
return this.send({
|
|
238
|
+
from: input.from ?? email.receivedBy,
|
|
239
|
+
to: input.to,
|
|
240
|
+
subject: input.subject ?? email.forwardSubject,
|
|
241
|
+
bodyText: buildForwardText(email, input.bodyText),
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
function buildForwardText(email, intro) {
|
|
246
|
+
const lines = [
|
|
247
|
+
...(intro ? [intro.trim(), ""] : []),
|
|
248
|
+
"---------- Forwarded message ----------",
|
|
249
|
+
`From: ${formatAddress(email.sender)}`,
|
|
250
|
+
`To: ${email.raw.email.headers.to}`,
|
|
251
|
+
`Subject: ${email.subject ?? ""}`,
|
|
252
|
+
...(email.raw.email.headers.date
|
|
253
|
+
? [`Date: ${email.raw.email.headers.date}`]
|
|
254
|
+
: []),
|
|
255
|
+
...(email.thread.messageId
|
|
256
|
+
? [`Message-ID: ${email.thread.messageId}`]
|
|
257
|
+
: []),
|
|
258
|
+
"",
|
|
259
|
+
email.text ?? "",
|
|
260
|
+
];
|
|
261
|
+
return lines.join("\n").trimEnd();
|
|
262
|
+
}
|
|
263
|
+
function mapSendResult(result) {
|
|
264
|
+
return {
|
|
265
|
+
id: result.id,
|
|
266
|
+
status: result.status,
|
|
267
|
+
queueId: result.queue_id,
|
|
268
|
+
accepted: result.accepted,
|
|
269
|
+
rejected: result.rejected,
|
|
270
|
+
clientIdempotencyKey: result.client_idempotency_key,
|
|
271
|
+
requestId: result.request_id,
|
|
272
|
+
contentHash: result.content_hash,
|
|
273
|
+
...(result.delivery_status !== undefined
|
|
274
|
+
? { deliveryStatus: result.delivery_status }
|
|
275
|
+
: {}),
|
|
276
|
+
...(result.smtp_response_code !== undefined
|
|
277
|
+
? { smtpResponseCode: result.smtp_response_code }
|
|
278
|
+
: {}),
|
|
279
|
+
...(result.smtp_response_text !== undefined
|
|
280
|
+
? { smtpResponseText: result.smtp_response_text }
|
|
281
|
+
: {}),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
35
284
|
export function createPrimitiveApiClient(options = {}) {
|
|
36
285
|
return new PrimitiveApiClient(options);
|
|
37
286
|
}
|
|
287
|
+
export function createPrimitiveClient(options = {}) {
|
|
288
|
+
return new PrimitiveClient(options);
|
|
289
|
+
}
|
|
290
|
+
export function client(options = {}) {
|
|
291
|
+
return new PrimitiveClient(options);
|
|
292
|
+
}
|
|
38
293
|
export const operations = generatedOperations;
|
|
39
294
|
export * from "./generated/index.js";
|