@primitivedotdev/sdk 0.26.1 → 0.27.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +2 -2
  2. package/dist/api/index.d.ts +5 -3
  3. package/dist/api/index.js +4 -406
  4. package/dist/{api-CnUa6o4r.js → api-CZIBnM4Q.js} +118 -92
  5. package/dist/contract/index.d.ts +2 -2
  6. package/dist/contract/index.js +1 -1
  7. package/dist/{errors-x91I_yEt.js → errors-BPJGp9I6.js} +1 -1
  8. package/dist/{errors-C53fe686.d.ts → errors-T_0JE528.d.ts} +1 -1
  9. package/dist/{index-BbEVpN5e.d.ts → index-9Rqocr-c.d.ts} +87 -75
  10. package/dist/{index-Dbx9udpX.d.ts → index-EQZK4vWT.d.ts} +2 -2
  11. package/dist/index.d.ts +4 -4
  12. package/dist/index.js +3 -3
  13. package/dist/openapi/index.d.ts +1 -52
  14. package/dist/openapi/index.js +2 -8
  15. package/dist/operations.generated-BJERV_56.d.ts +53 -0
  16. package/dist/operations.generated-T3exFpgJ.js +7632 -0
  17. package/dist/parser/index.d.ts +1 -1
  18. package/dist/parser/index.js +1 -1
  19. package/dist/webhook/index.d.ts +3 -3
  20. package/dist/webhook/index.js +2 -2
  21. package/dist/{webhook-DJkfUnFZ.js → webhook-Bra-g1q8.js} +1 -1
  22. package/package.json +9 -69
  23. package/bin/run.js +0 -20
  24. package/dist/api/generated/client/client.gen.js +0 -235
  25. package/dist/api/generated/client/index.js +0 -6
  26. package/dist/api/generated/client/types.gen.js +0 -2
  27. package/dist/api/generated/client/utils.gen.js +0 -228
  28. package/dist/api/generated/client.gen.js +0 -3
  29. package/dist/api/generated/core/auth.gen.js +0 -14
  30. package/dist/api/generated/core/bodySerializer.gen.js +0 -57
  31. package/dist/api/generated/core/params.gen.js +0 -100
  32. package/dist/api/generated/core/pathSerializer.gen.js +0 -106
  33. package/dist/api/generated/core/queryKeySerializer.gen.js +0 -92
  34. package/dist/api/generated/core/serverSentEvents.gen.js +0 -132
  35. package/dist/api/generated/core/types.gen.js +0 -2
  36. package/dist/api/generated/core/utils.gen.js +0 -87
  37. package/dist/api/generated/index.js +0 -2
  38. package/dist/api/generated/sdk.gen.js +0 -878
  39. package/dist/api/generated/types.gen.js +0 -2
  40. package/dist/api/verify-signature.js +0 -198
  41. package/dist/oclif/api-command.js +0 -755
  42. package/dist/oclif/auth.js +0 -223
  43. package/dist/oclif/commands/emails-latest.js +0 -185
  44. package/dist/oclif/commands/emails-poll.js +0 -121
  45. package/dist/oclif/commands/emails-wait.js +0 -171
  46. package/dist/oclif/commands/emails-watch.js +0 -165
  47. package/dist/oclif/commands/functions-deploy.js +0 -124
  48. package/dist/oclif/commands/functions-init.js +0 -256
  49. package/dist/oclif/commands/functions-redeploy.js +0 -113
  50. package/dist/oclif/commands/functions-set-secret.js +0 -213
  51. package/dist/oclif/commands/login.js +0 -237
  52. package/dist/oclif/commands/logout.js +0 -88
  53. package/dist/oclif/commands/send.js +0 -222
  54. package/dist/oclif/commands/whoami.js +0 -95
  55. package/dist/oclif/fish-completion.js +0 -87
  56. package/dist/oclif/index.js +0 -167
  57. package/dist/oclif/lint/raw-send-mail-fetch.js +0 -98
  58. package/dist/openapi/openapi.generated.js +0 -5754
  59. package/dist/openapi/operations.generated.js +0 -4626
  60. package/dist/parser/address-parser.js +0 -129
  61. package/dist/types.generated.js +0 -7
  62. package/dist/types.js +0 -53
  63. package/dist/webhook/errors.js +0 -224
  64. package/dist/webhook/received-email.js +0 -82
  65. package/oclif.manifest.json +0 -4380
  66. /package/dist/{address-parser-BYn8oW5r.js → address-parser-CQbFjgRC.js} +0 -0
  67. /package/dist/{types-9vXGZjPd.d.ts → types-Nslo1CU0.d.ts} +0 -0
@@ -1,129 +0,0 @@
1
- import addressparser from "nodemailer/lib/addressparser/index.js";
2
- import isEmail from "validator/lib/isEmail.js";
3
- // Per RFC 5322 §2.1.1, header lines are bounded at 998 octets. We measure
4
- // in UTF-8 bytes, not JS code units, so SMTPUTF8 (RFC 6531) headers with
5
- // multi-byte characters cannot bypass the cap by being short on chars but
6
- // long on bytes. Reject anything beyond as malformed without parsing: a
7
- // longer From field is either a header-injection probe or a corrupt feed.
8
- const MAX_HEADER_LENGTH = 998;
9
- // Options for validator's isEmail. The per-part length limits (64-octet
10
- // local-part, 255-octet domain), dot-atom rules, hostname-label rules,
11
- // and TLD requirement are all enforced inside isEmail. We choose:
12
- // allow_ip_domain: true -- accept user@[192.168.1.1] address-literals
13
- // require_tld: true -- reject user@localhost
14
- // allow_display_name: false -- we already extracted the address with
15
- // addressparser, so isEmail only sees the
16
- // bare addr-spec
17
- // allow_utf8_local_part: true -- accept SMTPUTF8 / EAI local-parts
18
- const IS_EMAIL_OPTIONS = {
19
- allow_ip_domain: true,
20
- require_tld: true,
21
- allow_display_name: false,
22
- allow_utf8_local_part: true,
23
- };
24
- /**
25
- * Strict parser for RFC 5322 From-style headers in security-bearing
26
- * contexts (allowlist gates, permission grants).
27
- *
28
- * Rejects, without falling back to a "best guess":
29
- * - empty / whitespace-only input
30
- * - inputs longer than RFC 5322's 998-octet line limit
31
- * - multi-address From (RFC 5322 allows it but it is vanishingly
32
- * rare and ambiguous as an identity)
33
- * - group syntax ("Friends: a@b.com, c@d.com;")
34
- * - any address that fails validator's isEmail check with our chosen
35
- * options. That covers per-part length limits, dot-atom rules,
36
- * hostname-label rules, TLD requirement, and other RFC 5321/5322
37
- * conformance checks.
38
- *
39
- * Returns ONLY the validated address, with no display name. Strict
40
- * exists for gating decisions, where the address is the security-
41
- * bearing field. Display names from addressparser are not trustworthy
42
- * here: weird inputs like `Name <user@x.com> <attacker@y.com>` get
43
- * parsed as a single entry whose `name` silently includes the second
44
- * address. Surfacing that as a "parsed name" would invite downstream
45
- * misuse, so we drop it. If you need the name, call
46
- * {@link parseFromHeaderLoose} alongside (it returns null on failure
47
- * anyway, so you can still gate on strict's Result).
48
- *
49
- * Returns a typed Result so callers can map the failure reason to
50
- * stable error codes without inspecting message text.
51
- */
52
- export function parseFromHeader(header) {
53
- if (header === null || header === undefined) {
54
- return { ok: false, reason: "empty" };
55
- }
56
- const trimmed = header.trim();
57
- if (trimmed.length === 0) {
58
- return { ok: false, reason: "empty" };
59
- }
60
- if (Buffer.byteLength(trimmed, "utf8") > MAX_HEADER_LENGTH) {
61
- return { ok: false, reason: "too_long" };
62
- }
63
- // Default (no flatten) so group entries surface as { name, group: [] }
64
- // rather than being silently merged into the address list.
65
- const parsed = addressparser(trimmed);
66
- if (parsed.length > 1) {
67
- return { ok: false, reason: "multiple_addresses" };
68
- }
69
- const entry = parsed[0];
70
- // addressparser returns a single entry with empty `address` for raw
71
- // garbage rather than an empty array, so an empty result is only
72
- // possible for inputs that already failed our trim/empty check above.
73
- // The defensive fall-through maps any future regression to
74
- // invalid_address rather than crashing on parsed[0].
75
- if (entry === undefined) {
76
- return { ok: false, reason: "invalid_address" };
77
- }
78
- if ("group" in entry) {
79
- return { ok: false, reason: "group_syntax" };
80
- }
81
- const address = entry.address;
82
- if (address === undefined || !isEmail(address, IS_EMAIL_OPTIONS)) {
83
- return { ok: false, reason: "invalid_address" };
84
- }
85
- return {
86
- ok: true,
87
- value: { address: address.toLowerCase() },
88
- };
89
- }
90
- /**
91
- * Lenient parser for display-only call sites (inbox card "from",
92
- * log lines, debugging). Returns the first parseable address with its
93
- * display name, or null.
94
- *
95
- * Differences from {@link parseFromHeader}:
96
- * - Multi-address From returns the first address instead of rejecting
97
- * - Group syntax is flattened into its member addresses
98
- * - Returns null instead of a typed reason on failure
99
- * - Includes the parsed display name in the result
100
- *
101
- * Do not use for permission gates or any decision that grants access.
102
- * That is what {@link parseFromHeader} is for. Names returned here can
103
- * include addressparser's recovery output (trailing tokens, garbage
104
- * before the address); treat as opaque text for display.
105
- */
106
- export function parseFromHeaderLoose(header) {
107
- if (header === null || header === undefined) {
108
- return null;
109
- }
110
- const trimmed = header.trim();
111
- if (trimmed.length === 0 ||
112
- Buffer.byteLength(trimmed, "utf8") > MAX_HEADER_LENGTH) {
113
- return null;
114
- }
115
- const parsed = addressparser(trimmed);
116
- for (const entry of parsed) {
117
- const candidates = "group" in entry && Array.isArray(entry.group) ? entry.group : [entry];
118
- for (const candidate of candidates) {
119
- const address = candidate.address;
120
- if (address !== undefined && isEmail(address, IS_EMAIL_OPTIONS)) {
121
- return {
122
- address: address.toLowerCase(),
123
- name: candidate.name && candidate.name.length > 0 ? candidate.name : null,
124
- };
125
- }
126
- }
127
- }
128
- return null;
129
- }
@@ -1,7 +0,0 @@
1
- /**
2
- * Types for Primitive webhook payloads.
3
- *
4
- * AUTO-GENERATED - DO NOT EDIT
5
- * Run `pnpm generate:types` to regenerate.
6
- */
7
- export {};
package/dist/types.js DELETED
@@ -1,53 +0,0 @@
1
- /**
2
- * Primitive webhook event types derived from the canonical JSON schema.
3
- *
4
- * @packageDocumentation
5
- */
6
- export const EventType = {
7
- EmailReceived: "email.received",
8
- };
9
- export const ParsedStatus = {
10
- Complete: "complete",
11
- Failed: "failed",
12
- };
13
- export const ForwardVerdict = {
14
- Legit: "legit",
15
- Unknown: "unknown",
16
- };
17
- export const SpfResult = {
18
- Pass: "pass",
19
- Fail: "fail",
20
- Softfail: "softfail",
21
- Neutral: "neutral",
22
- None: "none",
23
- Temperror: "temperror",
24
- Permerror: "permerror",
25
- };
26
- export const DmarcResult = {
27
- Pass: "pass",
28
- Fail: "fail",
29
- None: "none",
30
- Temperror: "temperror",
31
- Permerror: "permerror",
32
- };
33
- export const DmarcPolicy = {
34
- Reject: "reject",
35
- Quarantine: "quarantine",
36
- None: "none",
37
- };
38
- export const DkimResult = {
39
- Pass: "pass",
40
- Fail: "fail",
41
- Temperror: "temperror",
42
- Permerror: "permerror",
43
- };
44
- export const AuthConfidence = {
45
- High: "high",
46
- Medium: "medium",
47
- Low: "low",
48
- };
49
- export const AuthVerdict = {
50
- Legit: "legit",
51
- Suspicious: "suspicious",
52
- Unknown: "unknown",
53
- };
@@ -1,224 +0,0 @@
1
- // -----------------------------------------------------------------------------
2
- // Error Definitions (Single Source of Truth)
3
- // -----------------------------------------------------------------------------
4
- /**
5
- * Verification error definitions.
6
- * Use these for documentation, dashboards, and i18n.
7
- */
8
- export const VERIFICATION_ERRORS = {
9
- INVALID_SIGNATURE_HEADER: {
10
- message: "Missing or malformed Primitive-Signature header",
11
- suggestion: "Check that you're reading the correct header (Primitive-Signature) and it's being passed correctly from your web framework.",
12
- },
13
- TIMESTAMP_OUT_OF_RANGE: {
14
- message: "Timestamp is too old (possible replay attack)",
15
- suggestion: "This could indicate a replay attack, network delay, or server clock drift. Check your server's time is synced.",
16
- },
17
- SIGNATURE_MISMATCH: {
18
- message: "Signature doesn't match expected value",
19
- suggestion: "Verify the webhook secret matches and you're using the raw request body (not re-serialized JSON).",
20
- },
21
- MISSING_SECRET: {
22
- message: "No webhook secret was provided",
23
- suggestion: "Pass your webhook secret from the Primitive dashboard. Check that the environment variable is set.",
24
- },
25
- };
26
- /**
27
- * Payload parsing error definitions.
28
- * Use these for documentation, dashboards, and i18n.
29
- */
30
- export const PAYLOAD_ERRORS = {
31
- PAYLOAD_NULL: {
32
- message: "Webhook payload is null",
33
- suggestion: "Ensure you're passing the parsed JSON body, not null. Check your framework's body parsing middleware.",
34
- },
35
- PAYLOAD_UNDEFINED: {
36
- message: "Webhook payload is undefined",
37
- suggestion: "The payload was not provided. Make sure you're passing the request body to the handler.",
38
- },
39
- PAYLOAD_WRONG_TYPE: {
40
- message: "Webhook payload must be an object",
41
- suggestion: "The payload should be a parsed JSON object. Check that you're not passing a string or other primitive.",
42
- },
43
- PAYLOAD_IS_ARRAY: {
44
- message: "Webhook payload is an array, expected object",
45
- suggestion: "Primitive webhooks are single event objects, not arrays. Check the payload structure.",
46
- },
47
- PAYLOAD_MISSING_EVENT: {
48
- message: "Webhook payload missing 'event' field",
49
- suggestion: "All webhook payloads must have an 'event' field. This may not be a valid Primitive webhook.",
50
- },
51
- PAYLOAD_UNKNOWN_EVENT: {
52
- message: "Unknown webhook event type",
53
- suggestion: "This event type is not recognized. You may need to update your SDK or handle unknown events gracefully.",
54
- },
55
- PAYLOAD_EMPTY_BODY: {
56
- message: "Request body is empty",
57
- suggestion: "The request body was empty. Ensure the webhook is sending data and your framework is parsing it correctly.",
58
- },
59
- JSON_PARSE_FAILED: {
60
- message: "Failed to parse JSON body",
61
- suggestion: "The request body is not valid JSON. Check the raw body content and Content-Type header.",
62
- },
63
- INVALID_ENCODING: {
64
- message: "Invalid body encoding",
65
- suggestion: "The request body encoding is not supported. Primitive webhooks use UTF-8 encoded JSON.",
66
- },
67
- };
68
- /**
69
- * Raw email decode error definitions.
70
- * Use these for documentation, dashboards, and i18n.
71
- */
72
- export const RAW_EMAIL_ERRORS = {
73
- NOT_INCLUDED: {
74
- message: "Raw email content not included inline",
75
- suggestion: "Use the download URL at event.email.content.download.url to fetch the raw email.",
76
- },
77
- INVALID_BASE64: {
78
- message: "Raw email content is not valid base64",
79
- suggestion: "The raw email data is malformed. Fetch the raw email from the download URL or regenerate the webhook payload.",
80
- },
81
- HASH_MISMATCH: {
82
- message: "SHA-256 hash verification failed",
83
- suggestion: "The raw email data may be corrupted. Try downloading from the URL instead.",
84
- },
85
- };
86
- /**
87
- * Base class for all Primitive webhook errors.
88
- *
89
- * Catch this to handle any error from the SDK in a single catch block.
90
- *
91
- * @example
92
- * ```typescript
93
- * import { handleWebhook, PrimitiveWebhookError } from '@primitivedotdev/sdk';
94
- *
95
- * try {
96
- * const event = handleWebhook({ body, headers, secret });
97
- * } catch (err) {
98
- * if (err instanceof PrimitiveWebhookError) {
99
- * console.error(`[${err.code}] ${err.message}`);
100
- * return res.status(400).json({ error: err.code });
101
- * }
102
- * throw err;
103
- * }
104
- * ```
105
- */
106
- export class PrimitiveWebhookError extends Error {
107
- /**
108
- * Formats the error for logging/display.
109
- */
110
- toString() {
111
- return `${this.name} [${this.code}]: ${this.message}\n\nSuggestion: ${this.suggestion}`;
112
- }
113
- /**
114
- * Serializes cleanly for structured logging (Datadog, CloudWatch, etc.)
115
- */
116
- toJSON() {
117
- return {
118
- name: this.name,
119
- code: this.code,
120
- message: this.message,
121
- suggestion: this.suggestion,
122
- };
123
- }
124
- }
125
- /**
126
- * Error thrown when webhook signature verification fails.
127
- *
128
- * Use the `code` property to programmatically handle specific error cases.
129
- */
130
- export class WebhookVerificationError extends PrimitiveWebhookError {
131
- code;
132
- suggestion;
133
- constructor(code, message, suggestion) {
134
- super(message ?? VERIFICATION_ERRORS[code].message);
135
- this.name = "WebhookVerificationError";
136
- this.code = code;
137
- this.suggestion = suggestion ?? VERIFICATION_ERRORS[code].suggestion;
138
- }
139
- }
140
- /**
141
- * Error thrown when webhook payload parsing fails (lightweight parser).
142
- *
143
- * Use the `code` property for programmatic handling and monitoring.
144
- * The `suggestion` property contains actionable guidance for fixing the issue.
145
- */
146
- export class WebhookPayloadError extends PrimitiveWebhookError {
147
- code;
148
- suggestion;
149
- /** Original error if this wraps another error (e.g., JSON.parse failure) */
150
- cause;
151
- constructor(code, message, suggestion, cause) {
152
- super(message ?? PAYLOAD_ERRORS[code].message);
153
- this.name = "WebhookPayloadError";
154
- this.code = code;
155
- this.suggestion = suggestion ?? PAYLOAD_ERRORS[code].suggestion;
156
- this.cause = cause;
157
- }
158
- }
159
- /**
160
- * Error thrown when schema validation fails.
161
- */
162
- export class WebhookValidationError extends PrimitiveWebhookError {
163
- code = "SCHEMA_VALIDATION_FAILED";
164
- suggestion;
165
- /** The specific field path that failed (e.g., "email.headers.from") */
166
- field;
167
- /** Original schema validation errors for advanced debugging */
168
- validationErrors;
169
- /** Number of additional validation errors beyond the first */
170
- additionalErrorCount;
171
- constructor(field, message, suggestion, validationErrors) {
172
- super(message);
173
- this.name = "WebhookValidationError";
174
- this.field = field;
175
- this.suggestion = suggestion;
176
- this.validationErrors = validationErrors;
177
- this.additionalErrorCount = Math.max(0, validationErrors.length - 1);
178
- }
179
- /**
180
- * Formats the error for logging/display.
181
- * Includes error count and suggestion.
182
- */
183
- toString() {
184
- let output = `${this.name} [${this.code}]: ${this.message}`;
185
- if (this.additionalErrorCount > 0) {
186
- output += ` (and ${this.additionalErrorCount} more validation error${this.additionalErrorCount > 1 ? "s" : ""})`;
187
- }
188
- output += `\n\nSuggestion: ${this.suggestion}`;
189
- return output;
190
- }
191
- /**
192
- * Serializes cleanly for structured logging (Datadog, CloudWatch, etc.)
193
- */
194
- toJSON() {
195
- return {
196
- name: this.name,
197
- code: this.code,
198
- field: this.field,
199
- message: this.message,
200
- suggestion: this.suggestion,
201
- additionalErrorCount: this.additionalErrorCount,
202
- };
203
- }
204
- }
205
- // -----------------------------------------------------------------------------
206
- // Raw Email Decode Errors
207
- // -----------------------------------------------------------------------------
208
- /**
209
- * Error thrown when raw email decoding or verification fails.
210
- *
211
- * Use the `code` property to determine the failure reason:
212
- * - `NOT_INCLUDED`: Raw email not inline, must download from URL
213
- * - `HASH_MISMATCH`: SHA-256 verification failed, content may be corrupted
214
- */
215
- export class RawEmailDecodeError extends PrimitiveWebhookError {
216
- code;
217
- suggestion;
218
- constructor(code, message) {
219
- super(message ?? RAW_EMAIL_ERRORS[code].message);
220
- this.name = "RawEmailDecodeError";
221
- this.code = code;
222
- this.suggestion = RAW_EMAIL_ERRORS[code].suggestion;
223
- }
224
- }
@@ -1,82 +0,0 @@
1
- import { parseFromHeaderLoose } from "../parser/address-parser.js";
2
- const REPLY_PREFIX_RE = /^re\s*:/i;
3
- const FORWARD_PREFIX_RE = /^(fwd?|fw)\s*:/i;
4
- export function normalizeReceivedEmail(event) {
5
- const receivedBy = event.email.smtp.rcpt_to[0];
6
- if (!receivedBy) {
7
- throw new Error("email.smtp.rcpt_to must contain at least one recipient");
8
- }
9
- const sender = parseHeaderAddress(event.email.headers.from) ?? {
10
- address: event.email.smtp.mail_from.trim().toLowerCase(),
11
- name: null,
12
- };
13
- const replyTarget = firstStructuredAddress(event.email.parsed.reply_to) ?? sender;
14
- const subject = event.email.headers.subject ?? null;
15
- const references = event.email.parsed.references ?? [];
16
- const messageId = event.email.headers.message_id ?? null;
17
- return {
18
- id: event.email.id,
19
- eventId: event.id,
20
- receivedAt: event.email.received_at,
21
- sender,
22
- replyTarget,
23
- receivedBy,
24
- receivedByAll: [...event.email.smtp.rcpt_to],
25
- subject,
26
- replySubject: buildReplySubject(subject),
27
- forwardSubject: buildForwardSubject(subject),
28
- text: event.email.parsed.body_text ?? null,
29
- thread: {
30
- messageId,
31
- inReplyTo: event.email.parsed.in_reply_to ?? [],
32
- references,
33
- },
34
- attachments: event.email.parsed.attachments ?? [],
35
- auth: event.email.auth,
36
- analysis: event.email.analysis,
37
- raw: event,
38
- };
39
- }
40
- export function buildReplySubject(subject) {
41
- const trimmed = subject?.trim() ?? "";
42
- if (trimmed.length === 0) {
43
- return "Re:";
44
- }
45
- return REPLY_PREFIX_RE.test(trimmed) ? trimmed : `Re: ${trimmed}`;
46
- }
47
- export function buildForwardSubject(subject) {
48
- const trimmed = subject?.trim() ?? "";
49
- if (trimmed.length === 0) {
50
- return "Fwd:";
51
- }
52
- return FORWARD_PREFIX_RE.test(trimmed) ? trimmed : `Fwd: ${trimmed}`;
53
- }
54
- export function formatAddress(address) {
55
- return address.name
56
- ? `${address.name} <${address.address}>`
57
- : address.address;
58
- }
59
- function firstStructuredAddress(addresses) {
60
- const address = addresses?.[0];
61
- if (!address) {
62
- return null;
63
- }
64
- return {
65
- address: address.address.trim().toLowerCase(),
66
- name: address.name ?? null,
67
- };
68
- }
69
- // Lenient about quirky headers (unquoted commas in display names, missing
70
- // closing angle brackets) but strict about the resulting address: it must
71
- // validate as an email, otherwise the normalizer falls back to the SMTP
72
- // envelope sender. Exported so cross-SDK fixtures can exercise it directly.
73
- export function parseHeaderAddress(value) {
74
- const parsed = parseFromHeaderLoose(value);
75
- if (!parsed) {
76
- return null;
77
- }
78
- return {
79
- address: parsed.address,
80
- name: parsed.name?.trim() || null,
81
- };
82
- }