mailchannels-sdk 0.8.0-0 → 0.8.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Yizack Rangel
3
+ Copyright (c) 2026 MailChannels
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -4,7 +4,6 @@
4
4
 
5
5
  [![npm version][npm-version-src]][npm-version-href]
6
6
  [![npm downloads][npm-downloads-src]][npm-downloads-href]
7
- [![codecov][codecov-coverage-src]][codecov-coverage-href]
8
7
 
9
8
  > Built and tested against Email API `0.21.1`
10
9
 
@@ -109,7 +108,7 @@ This package includes a local MailChannels simulator you can run via the CLI. It
109
108
 
110
109
  | API | Source |
111
110
  | ----------- | -------------------------------------------------------------------------------------------------------------- |
112
- | Email API | [`src/simulator/email-api.mjs`](https://github.com/Yizack/mailchannels/blob/main/src/simulator/email-api.mjs) |
111
+ | Email API | [`src/simulator/email-api.mjs`](https://bitbucket.org/mailchannels/mailchannels-email-api-sdk-js/src/main/src/simulator/email-api.mjs) |
113
112
 
114
113
  > [!IMPORTANT]
115
114
  > The simulator approximates the MailChannels service for local development and testing. It is not a production implementation and may differ from the live service.
@@ -212,7 +211,7 @@ pnpm parity:fixtures
212
211
  pnpm simulate
213
212
 
214
213
  # Run a playground script
215
- npx jiti playground/emails/send.ts
214
+ pnpx jiti playground/emails/send.ts
216
215
 
217
216
  # Release new version
218
217
  pnpm release
@@ -226,6 +225,3 @@ pnpm release
226
225
 
227
226
  [npm-downloads-src]: https://img.shields.io/npm/dm/mailchannels-sdk.svg?style=flat&colorA=070a30&colorB=35a047
228
227
  [npm-downloads-href]: https://npmjs.com/package/mailchannels-sdk
229
-
230
- [codecov-coverage-src]: https://img.shields.io/codecov/c/github/yizack/mailchannels?style=flat&colorA=070a30&token=HTSBRHSJ5M
231
- [codecov-coverage-href]: https://codecov.io/gh/Yizack/mailchannels
@@ -20,13 +20,24 @@ interface MailChannelsClientOptions {
20
20
  * @default false
21
21
  */
22
22
  retry?: number | false;
23
+ /**
24
+ * Request timeout in milliseconds.
25
+ * Set to `false` or `0` to disable timeout handling.
26
+ * @default 120000
27
+ */
28
+ timeout?: number | false;
29
+ /**
30
+ * Abort signal applied to requests made by the client.
31
+ */
32
+ signal?: AbortSignal;
23
33
  }
24
34
  declare class MailChannelsClient {
25
35
  #private;
26
36
  private static readonly DEFAULT_BASE_URL;
37
+ private static readonly DEFAULT_TIMEOUT;
27
38
  private readonly options;
28
39
  constructor(key: string, options?: MailChannelsClientOptions);
29
- protected _fetch<T>(path: string, options?: FetchOptions<"json">): Promise<T>;
40
+ protected _fetch<T>(path: string, options: FetchOptions<"json">): Promise<T>;
30
41
  post<T>(path: string, options?: Omit<FetchOptions<"json">, "method">): Promise<T>;
31
42
  get<T>(path: string, options?: Omit<FetchOptions<"json">, "method">): Promise<T>;
32
43
  delete<T>(path: string, options?: Omit<FetchOptions<"json">, "method">): Promise<T>;
@@ -96,7 +107,15 @@ interface EmailsSendAttachment {
96
107
  /**
97
108
  * The MIME type of the attachment.
98
109
  */
99
- type: string;
110
+ type?: string;
111
+ /**
112
+ * The `Content-ID` header value for inline attachments, referenced from HTML with `cid:`.
113
+ */
114
+ contentId?: string;
115
+ /**
116
+ * The `Content-Disposition` header value for the attachment.
117
+ */
118
+ disposition?: "attachment" | "inline";
100
119
  }
101
120
  interface EmailsSendTracking {
102
121
  /**
@@ -211,7 +230,7 @@ interface EmailsSendOptionsBase {
211
230
  /**
212
231
  * An array of attachments to be sent with the email.
213
232
  */
214
- attachments?: EmailsSendAttachment[];
233
+ attachments?: (EmailsSendAttachment | Promise<EmailsSendAttachment>)[];
215
234
  /**
216
235
  * The campaign identifier. If specified, this ID will be included in all relevant webhooks. It can be up to 48 UTF-8 characters long and must not contain spaces.
217
236
  */
@@ -438,7 +457,7 @@ type EmailsSendResponse = DataResponse<{
438
457
  status: "sent" | "failed";
439
458
  }[];
440
459
  }>;
441
- type EmailsSendAsyncResponse = DataResponse<{
460
+ type EmailsQueueResponse = DataResponse<{
442
461
  /**
443
462
  * ISO 8601 timestamp when the request was queued for processing.
444
463
  */
@@ -448,6 +467,7 @@ type EmailsSendAsyncResponse = DataResponse<{
448
467
  */
449
468
  requestId: string;
450
469
  }>;
470
+ type EmailsSendAsyncResponse = EmailsQueueResponse;
451
471
  declare class Emails {
452
472
  protected mailchannels: MailChannelsClient;
453
473
  constructor(mailchannels: MailChannelsClient);
@@ -478,7 +498,7 @@ declare class Emails {
478
498
  * @example
479
499
  * ```ts
480
500
  * const mailchannels = new MailChannels('your-api-key')
481
- * const { data, error } = await mailchannels.emails.sendAsync({
501
+ * const { data, error } = await mailchannels.emails.queue({
482
502
  * to: 'to@example.com',
483
503
  * from: 'from@example.com',
484
504
  * subject: 'Test',
@@ -486,7 +506,11 @@ declare class Emails {
486
506
  * })
487
507
  * ```
488
508
  */
489
- sendAsync(options: EmailsSendOptions): Promise<EmailsSendAsyncResponse>;
509
+ queue(options: EmailsSendOptions): Promise<EmailsQueueResponse>;
510
+ /**
511
+ * @deprecated Use `queue` instead.
512
+ */
513
+ sendAsync(options: EmailsSendOptions): Promise<EmailsQueueResponse>;
490
514
  }
491
515
  interface DomainsDkimCreateOptions {
492
516
  /**
@@ -578,10 +602,6 @@ interface DomainsCheckOptions {
578
602
  * 6. If `selector` is present and `domain` is not, the domain will be taken from the domain field of the request.
579
603
  */
580
604
  dkim?: DomainsCheck[] | DomainsCheck;
581
- /**
582
- * Domain used for sending emails. If `dkim` settings are not provided, or `dkim` settings are provided with no `domain`, the stored dkim settings for this domain will be used.
583
- */
584
- domain: string;
585
605
  /**
586
606
  * Used exclusively for [Domain Lockdown](https://support.mailchannels.com/hc/en-us/articles/16918954360845-Secure-your-domain-name-against-spoofing-with-Domain-Lockdown) verification. If you're not using senderid to associate your domain with your account, you can disregard this field. The corresponding value is included in the `X-MailChannels-SenderId` header of emails sent via MailChannels.
587
607
  */
@@ -676,7 +696,7 @@ interface DomainsDkimListOptions {
676
696
  }
677
697
  type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
678
698
  type DomainsDkimListResponse = DataResponse<Optional<DomainsDkimKey, "dnsRecords">[]>;
679
- interface DomainsDkimUpdateOptions {
699
+ interface DomainsDkimUpdateStatusOptions {
680
700
  /**
681
701
  * Selector of the DKIM key pair to update. Must be a maximum of 63 characters.
682
702
  */
@@ -707,22 +727,22 @@ declare class Domains {
707
727
  constructor(mailchannels: MailChannelsClient);
708
728
  /**
709
729
  * Validates a domain's email authentication setup by retrieving its DKIM, SPF, and Domain Lockdown status. This endpoint checks whether the domain is properly configured for secure email delivery.
730
+ * @param domain - Domain used for sending emails. If `dkim` settings are not provided, or `dkim` settings are provided with no `domain`, the stored dkim settings for this domain will be used.
710
731
  * @param options - The domain options to check.
711
732
  * @example
712
733
  * ```ts
713
734
  * const mailchannels = new MailChannels('your-api-key')
714
- * const { data, error } = await mailchannels.domains.check({
735
+ * const { data, error } = await mailchannels.domains.check('example.com', {
715
736
  * dkim: [{
716
737
  * domain: 'example.com',
717
738
  * privateKey: 'your-private-key',
718
739
  * selector: 'mailchannels'
719
740
  * }],
720
- * domain: 'example.com',
721
741
  * senderId: 'sender-id'
722
742
  * })
723
743
  * ```
724
744
  */
725
- check(options: DomainsCheckOptions): Promise<DomainsCheckResponse>;
745
+ check(domain: string, options?: DomainsCheckOptions): Promise<DomainsCheckResponse>;
726
746
  }
727
747
  declare class DomainsDkim {
728
748
  private mailchannels;
@@ -760,12 +780,12 @@ declare class DomainsDkim {
760
780
  * @example
761
781
  * ```ts
762
782
  * const mailchannels = new MailChannels('your-api-key')
763
- * const { success, error } = await mailchannels.domains.dkim.update('example.com', {
783
+ * const { success, error } = await mailchannels.domains.dkim.updateStatus('example.com', {
764
784
  * selector: 'mailchannels',
765
785
  * status: 'retired'
766
786
  * })
767
787
  */
768
- update(domain: string, options: DomainsDkimUpdateOptions): Promise<SuccessResponse>;
788
+ updateStatus(domain: string, options: DomainsDkimUpdateStatusOptions): Promise<SuccessResponse>;
769
789
  /**
770
790
  * Rotate an active DKIM key pair. Mark the original key as `rotated`, and create a new key pair with the required new key selector, reusing the same algorithm and key length. The rotated key remains valid for signing for a 3-day grace period, and is automatically changed to `retired` 2 weeks after rotation. Publish the new key to its DNS TXT record before rotated key expires for signing as emails sent with an unpublished key will fail DKIM validation by receiving providers. After the grace period, only the new key is valid for signing if published.
771
791
  * @param domain - The domain the DKIM key belongs to.
@@ -832,6 +852,78 @@ type WebhooksValidateResponse = DataResponse<{
832
852
  } | null;
833
853
  }[];
834
854
  }>;
855
+ type WebhookEventType = "processed" | "delivered" | "open" | "click" | "hard-bounced" | "soft-bounced" | "dropped" | "complained" | "unsubscribed" | "test";
856
+ interface WebhookEventBase<T extends WebhookEventType> {
857
+ /**
858
+ * The sender's email address
859
+ */
860
+ email?: string;
861
+ /**
862
+ * The MailChannels account ID that generated the webhook.
863
+ * If the message was sent by a sub-account, this field contains the sub-account handle.
864
+ */
865
+ customerHandle: string;
866
+ /**
867
+ * The Unix timestamp (in seconds) when the event occurred; the timezone is always UTC
868
+ */
869
+ timestamp: number;
870
+ /**
871
+ * The Message-Id of the message that generated the event
872
+ */
873
+ smtpId?: string;
874
+ /**
875
+ * The type of event that occurred
876
+ */
877
+ event: T;
878
+ /**
879
+ * A unique identifier generated to track the original HTTP request
880
+ */
881
+ requestId?: string;
882
+ /**
883
+ * The campaign identifier for the message that generated the event
884
+ */
885
+ campaignId?: string;
886
+ /**
887
+ * The recipients of the message
888
+ */
889
+ recipients?: string[];
890
+ }
891
+ interface WebhookEventProcessed extends WebhookEventBase<"processed"> {}
892
+ interface WebhookEventDelivered extends WebhookEventBase<"delivered"> {}
893
+ interface WebhookEventWithTracking {
894
+ /**
895
+ * The User-Agent header given when the recipient opened the message
896
+ */
897
+ userAgent?: string;
898
+ /**
899
+ * The IP address of the host that made the HTTP request
900
+ */
901
+ ip?: string;
902
+ }
903
+ interface WebhookEventOpen extends WebhookEventBase<"open">, WebhookEventWithTracking {}
904
+ interface WebhookEventClick extends WebhookEventBase<"click">, WebhookEventWithTracking {
905
+ /**
906
+ * The URL that was clicked by the recipient
907
+ */
908
+ url?: string;
909
+ }
910
+ interface WebhookEventWithStatus {
911
+ /**
912
+ * The SMTP status code that caused the bounce
913
+ */
914
+ status?: string;
915
+ /**
916
+ * A human-readable explanation of why the message hard-bounced
917
+ */
918
+ reason?: string;
919
+ }
920
+ interface WebhookEventHardBounced extends WebhookEventBase<"hard-bounced">, WebhookEventWithStatus {}
921
+ interface WebhookEventSoftBounced extends WebhookEventBase<"soft-bounced">, WebhookEventWithStatus {}
922
+ interface WebhookEventDropped extends WebhookEventBase<"dropped">, WebhookEventWithStatus {}
923
+ interface WebhookEventComplained extends WebhookEventBase<"complained"> {}
924
+ interface WebhookEventUnsubscribed extends WebhookEventBase<"unsubscribed"> {}
925
+ interface WebhookEventTest extends Omit<WebhookEventBase<"test">, "recipients" | "campaignId"> {}
926
+ type WebhookEvent = WebhookEventProcessed | WebhookEventDelivered | WebhookEventOpen | WebhookEventClick | WebhookEventHardBounced | WebhookEventSoftBounced | WebhookEventDropped | WebhookEventComplained | WebhookEventUnsubscribed | WebhookEventTest;
835
927
  interface WebhooksVerifyOptions {
836
928
  /**
837
929
  * The raw body of the incoming webhook request as a string. This should be the exact payload received from the webhook, without any modifications or parsing, to ensure accurate signature verification.
@@ -855,6 +947,7 @@ interface WebhooksVerifyOptions {
855
947
  */
856
948
  cache?: boolean;
857
949
  }
950
+ type WebhooksVerifyResponse = DataResponse<WebhookEvent[]>;
858
951
  type WebhooksBatchStatus = "1xx" | "2xx" | "3xx" | "4xx" | "5xx" | "no_response";
859
952
  type WebhooksBatchResponseStatus = "1xx_response" | "2xx_response" | "3xx_response" | "4xx_response" | "5xx_response" | "no_response";
860
953
  interface WebhooksBatchesOptions {
@@ -963,10 +1056,10 @@ declare class Webhooks {
963
1056
  * @example
964
1057
  * ```ts
965
1058
  * const mailchannels = new MailChannels('your-api-key')
966
- * const { success, error } = mailchannels.webhooks.enroll('https://example.com/api/webhooks/mailchannels')
1059
+ * const { success, error } = await mailchannels.webhooks.create('https://example.com/api/webhooks/mailchannels')
967
1060
  * ```
968
1061
  */
969
- enroll(endpoint: string): Promise<SuccessResponse>;
1062
+ create(endpoint: string): Promise<SuccessResponse>;
970
1063
  /**
971
1064
  * Retrieves all registered webhook endpoints associated with the customer.
972
1065
  * @example
@@ -981,10 +1074,10 @@ declare class Webhooks {
981
1074
  * @example
982
1075
  * ```ts
983
1076
  * const mailchannels = new MailChannels('your-api-key')
984
- * const { success, error } = await mailchannels.webhooks.delete()
1077
+ * const { success, error } = await mailchannels.webhooks.deleteAll()
985
1078
  * ```
986
1079
  */
987
- delete(): Promise<SuccessResponse>;
1080
+ deleteAll(): Promise<SuccessResponse>;
988
1081
  /**
989
1082
  * Retrieves the public key used to verify signatures on incoming webhook payloads.
990
1083
  * @param id - The ID of the key.
@@ -1010,20 +1103,20 @@ declare class Webhooks {
1010
1103
  * @param options - The options for verifying the webhook.
1011
1104
  * @example
1012
1105
  * ```ts
1013
- * const isValid = await Webhooks.verify({ payload: rawBody, headers })
1106
+ * const { data, error } = await Webhooks.verify({ payload: rawBody, headers })
1014
1107
  * ```
1015
1108
  */
1016
- static verify(options: WebhooksVerifyOptions): Promise<boolean>;
1109
+ static verify(options: WebhooksVerifyOptions): Promise<WebhooksVerifyResponse>;
1017
1110
  /**
1018
1111
  * Verifies the authenticity of incoming webhook requests by validating their signatures using the provided options.
1019
1112
  * @param options - The options for verifying the webhook.
1020
1113
  * @example
1021
1114
  * ```ts
1022
1115
  * const mailchannels = new MailChannels('your-api-key')
1023
- * const isValid = await mailchannels.webhooks.verify({ payload: rawBody, headers })
1116
+ * const { data, error } = await mailchannels.webhooks.verify({ payload: rawBody, headers })
1024
1117
  * ```
1025
1118
  */
1026
- verify(options: WebhooksVerifyOptions): Promise<boolean>;
1119
+ verify(options: WebhooksVerifyOptions): Promise<WebhooksVerifyResponse>;
1027
1120
  /**
1028
1121
  * Retrieves paged webhook batches associated with the customer. The time range specified by `createdAfter` and `createdBefore` must not exceed 31 days. If neither is specified, the default time range is the last 3 days.
1029
1122
  * @param options - The options for listing webhook batches.
@@ -1664,79 +1757,13 @@ declare class Suppressions {
1664
1757
  */
1665
1758
  list(options?: SuppressionsListOptions): Promise<SuppressionsListResponse>;
1666
1759
  }
1667
- type WebhookEventType = "processed" | "delivered" | "open" | "click" | "hard-bounced" | "soft-bounced" | "dropped" | "complained" | "unsubscribed" | "test";
1668
- interface WebhookEventBase<T extends WebhookEventType> {
1669
- /**
1670
- * The sender's email address
1671
- */
1672
- email?: string;
1673
- /**
1674
- * The MailChannels account ID that generated the webhook.
1675
- * If the message was sent by a sub-account, this field contains the sub-account handle.
1676
- */
1677
- customer_handle: string;
1678
- /**
1679
- * The Unix timestamp (in seconds) when the event occurred; the timezone is always UTC
1680
- */
1681
- timestamp: number;
1682
- /**
1683
- * The Message-Id of the message that generated the event
1684
- */
1685
- smtp_id?: string;
1686
- /**
1687
- * The type of event that occurred
1688
- */
1689
- event: T;
1690
- /**
1691
- * A unique identifier generated to track the original HTTP request
1692
- */
1693
- request_id?: string;
1694
- /**
1695
- * The campaign identifier for the message that generated the event
1696
- */
1697
- campaign_id?: string;
1698
- /**
1699
- * The recipients of the message
1700
- */
1701
- recipients?: string[];
1702
- }
1703
- interface WebhookEventProcessed extends WebhookEventBase<"processed"> {}
1704
- interface WebhookEventDelivered extends WebhookEventBase<"delivered"> {}
1705
- interface WebhookEventWithTracking {
1706
- /**
1707
- * The User-Agent header given when the recipient opened the message
1708
- */
1709
- user_agent?: string;
1710
- /**
1711
- * The IP address of the host that made the HTTP request
1712
- */
1713
- ip?: string;
1760
+ type AttachmentOptions = Omit<EmailsSendAttachment, "content">;
1761
+ declare class Attachment {
1762
+ static fromBytes(data: ArrayBuffer | Uint8Array, options: AttachmentOptions): EmailsSendAttachment;
1763
+ static fromFile(path: string | URL, options?: Partial<AttachmentOptions>): Promise<EmailsSendAttachment>;
1764
+ static fromUrl(url: string, options?: Partial<AttachmentOptions>): Promise<EmailsSendAttachment>;
1765
+ static inlineFile(path: string | URL, options?: Partial<Omit<AttachmentOptions, "disposition">>): Promise<EmailsSendAttachment>;
1714
1766
  }
1715
- interface WebhookEventOpen extends WebhookEventBase<"open">, WebhookEventWithTracking {}
1716
- interface WebhookEventClick extends WebhookEventBase<"click">, WebhookEventWithTracking {
1717
- /**
1718
- * The URL that was clicked by the recipient
1719
- */
1720
- url?: string;
1721
- }
1722
- interface WebhookEventWithStatus {
1723
- /**
1724
- * The SMTP status code that caused the bounce
1725
- */
1726
- status?: string;
1727
- /**
1728
- * A human-readable explanation of why the message hard-bounced
1729
- */
1730
- reason?: string;
1731
- }
1732
- interface WebhookEventHardBounced extends WebhookEventBase<"hard-bounced">, WebhookEventWithStatus {}
1733
- interface WebhookEventSoftBounced extends WebhookEventBase<"soft-bounced">, WebhookEventWithStatus {}
1734
- interface WebhookEventDropped extends WebhookEventBase<"dropped">, WebhookEventWithStatus {}
1735
- interface WebhookEventComplained extends WebhookEventBase<"complained"> {}
1736
- interface WebhookEventUnsubscribed extends WebhookEventBase<"unsubscribed"> {}
1737
- interface WebhookEventTest extends Omit<WebhookEventBase<"test">, "recipients" | "campaign_id"> {}
1738
- type WebhookEvent = WebhookEventProcessed | WebhookEventDelivered | WebhookEventOpen | WebhookEventClick | WebhookEventHardBounced | WebhookEventSoftBounced | WebhookEventDropped | WebhookEventComplained | WebhookEventUnsubscribed | WebhookEventTest;
1739
- type WebhookEvents = WebhookEvent[];
1740
1767
  declare class MailChannels extends MailChannelsClient {
1741
1768
  readonly emails: Emails;
1742
1769
  readonly domains: Domains;
@@ -1746,4 +1773,4 @@ declare class MailChannels extends MailChannelsClient {
1746
1773
  readonly suppressions: Suppressions;
1747
1774
  constructor(key: string, options?: MailChannelsClientOptions);
1748
1775
  }
1749
- export { DataResponse, Domains, DomainsCheckOptions, DomainsCheckResponse, DomainsCheckVerdict, DomainsDkimCreateOptions, DomainsDkimCreateResponse, DomainsDkimKey, DomainsDkimKeyStatus, DomainsDkimListOptions, DomainsDkimListResponse, DomainsDkimRotateOptions, DomainsDkimRotateResponse, DomainsDkimUpdateOptions, Emails, EmailsSendAsyncResponse, EmailsSendAttachment, EmailsSendContent, EmailsSendDkim, EmailsSendOptions, EmailsSendPersonalization, EmailsSendRecipient, EmailsSendRecipientInput, EmailsSendResponse, EmailsSendTemplate, EmailsSendTemplateType, EmailsSendTemplateValue, EmailsSendTracking, ErrorResponse, MailChannels, MailChannelsClient, MailChannelsClientOptions, Metrics, MetricsBucket, MetricsEngagement, MetricsEngagementResponse, MetricsOptions, MetricsPerformance, MetricsPerformanceResponse, MetricsRecipientBehaviour, MetricsRecipientBehaviourResponse, MetricsSenders, MetricsSendersOptions, MetricsSendersResponse, MetricsSendersType, MetricsUsageResponse, MetricsVolume, MetricsVolumeResponse, SubAccounts, SubAccountsAccount, SubAccountsApiKey, SubAccountsCreateApiKeyResponse, SubAccountsCreateResponse, SubAccountsCreateSmtpPasswordResponse, SubAccountsLimit, SubAccountsLimitResponse, SubAccountsListApiKeyOptions, SubAccountsListApiKeyResponse, SubAccountsListOptions, SubAccountsListResponse, SubAccountsListSmtpPasswordResponse, SubAccountsSmtpPassword, SubAccountsUsage, SubAccountsUsageResponse, SuccessResponse, Suppressions, SuppressionsCreateOptions, SuppressionsListEntry, SuppressionsListOptions, SuppressionsListResponse, SuppressionsSource, SuppressionsTypes, WebhookEvent, WebhookEventClick, WebhookEventComplained, WebhookEventDelivered, WebhookEventDropped, WebhookEventHardBounced, WebhookEventOpen, WebhookEventProcessed, WebhookEventSoftBounced, WebhookEventTest, WebhookEventType, WebhookEventUnsubscribed, WebhookEvents, Webhooks, WebhooksBatch, WebhooksBatchResponseStatus, WebhooksBatchStatus, WebhooksBatchesOptions, WebhooksBatchesResponse, WebhooksListResponse, WebhooksResendBatch, WebhooksResendBatchResponse, WebhooksSigningKeyResponse, WebhooksValidateResponse, WebhooksVerifyOptions };
1776
+ export { Attachment, type DataResponse, Domains, type DomainsCheckOptions, type DomainsCheckResponse, type DomainsCheckVerdict, type DomainsDkimCreateOptions, type DomainsDkimCreateResponse, type DomainsDkimKey, type DomainsDkimKeyStatus, type DomainsDkimListOptions, type DomainsDkimListResponse, type DomainsDkimRotateOptions, type DomainsDkimRotateResponse, type DomainsDkimUpdateStatusOptions, Emails, type EmailsQueueResponse, type EmailsSendAsyncResponse, type EmailsSendAttachment, type EmailsSendContent, type EmailsSendDkim, type EmailsSendOptions, type EmailsSendPersonalization, type EmailsSendRecipient, type EmailsSendRecipientInput, type EmailsSendResponse, type EmailsSendTemplate, type EmailsSendTemplateType, type EmailsSendTemplateValue, type EmailsSendTracking, type ErrorResponse, MailChannels, MailChannelsClient, type MailChannelsClientOptions, Metrics, type MetricsBucket, type MetricsEngagement, type MetricsEngagementResponse, type MetricsOptions, type MetricsPerformance, type MetricsPerformanceResponse, type MetricsRecipientBehaviour, type MetricsRecipientBehaviourResponse, type MetricsSenders, type MetricsSendersOptions, type MetricsSendersResponse, type MetricsSendersType, type MetricsUsageResponse, type MetricsVolume, type MetricsVolumeResponse, SubAccounts, type SubAccountsAccount, type SubAccountsApiKey, type SubAccountsCreateApiKeyResponse, type SubAccountsCreateResponse, type SubAccountsCreateSmtpPasswordResponse, type SubAccountsLimit, type SubAccountsLimitResponse, type SubAccountsListApiKeyOptions, type SubAccountsListApiKeyResponse, type SubAccountsListOptions, type SubAccountsListResponse, type SubAccountsListSmtpPasswordResponse, type SubAccountsSmtpPassword, type SubAccountsUsage, type SubAccountsUsageResponse, type SuccessResponse, Suppressions, type SuppressionsCreateOptions, type SuppressionsListEntry, type SuppressionsListOptions, type SuppressionsListResponse, type SuppressionsSource, type SuppressionsTypes, type WebhookEvent, type WebhookEventClick, type WebhookEventComplained, type WebhookEventDelivered, type WebhookEventDropped, type WebhookEventHardBounced, type WebhookEventOpen, type WebhookEventProcessed, type WebhookEventSoftBounced, type WebhookEventTest, type WebhookEventType, type WebhookEventUnsubscribed, Webhooks, type WebhooksBatch, type WebhooksBatchResponseStatus, type WebhooksBatchStatus, type WebhooksBatchesOptions, type WebhooksBatchesResponse, type WebhooksListResponse, type WebhooksResendBatch, type WebhooksResendBatchResponse, type WebhooksSigningKeyResponse, type WebhooksValidateResponse, type WebhooksVerifyOptions, type WebhooksVerifyResponse };
@@ -1,16 +1,22 @@
1
1
  import { $fetch } from "ofetch";
2
2
  import { subtle } from "node:crypto";
3
3
  import { Buffer } from "node:buffer";
4
- var version = "0.8.0-0";
4
+ import { basename } from "node:path";
5
+ import { readFile } from "node:fs/promises";
6
+ import mime from "mime";
7
+ var version = "0.8.0";
5
8
  var MailChannelsClient = class MailChannelsClient {
6
9
  static DEFAULT_BASE_URL = "https://api.mailchannels.net";
10
+ static DEFAULT_TIMEOUT = 12e4;
7
11
  options;
8
12
  #headers;
9
13
  constructor(key, options = {}) {
10
14
  if (!key) throw new Error("Missing MailChannels API key.");
11
15
  this.options = {
12
16
  baseUrl: options.baseUrl || MailChannelsClient.DEFAULT_BASE_URL,
13
- retry: options.retry ?? false
17
+ retry: options.retry ?? false,
18
+ signal: options.signal,
19
+ timeout: options.timeout ?? MailChannelsClient.DEFAULT_TIMEOUT
14
20
  };
15
21
  this.#headers = {
16
22
  "X-API-Key": key,
@@ -20,13 +26,16 @@ var MailChannelsClient = class MailChannelsClient {
20
26
  };
21
27
  }
22
28
  async _fetch(path, options) {
29
+ const { headers, signal = this.options.signal, ...fetchOptions } = options;
23
30
  return $fetch(path, {
24
31
  baseURL: this.options.baseUrl,
25
32
  retry: this.options.retry,
26
- ...options,
33
+ signal,
34
+ timeout: this.options.timeout === false ? void 0 : this.options.timeout,
35
+ ...fetchOptions,
27
36
  headers: {
28
37
  ...this.#headers,
29
- ...options?.headers
38
+ ...headers
30
39
  }
31
40
  });
32
41
  }
@@ -85,7 +94,6 @@ const validatePagination = (pagination = {}) => {
85
94
  if (typeof offset === "number" && offset < 0) return createError("Offset must be greater than or equal to 0.");
86
95
  return null;
87
96
  };
88
- const stripPemHeaders = (pem) => pem.replace(/-----[^-]+-----|\s|#.*$/gm, "");
89
97
  const clean = (data) => {
90
98
  if (Array.isArray(data)) {
91
99
  const result = [];
@@ -108,25 +116,6 @@ const clean = (data) => {
108
116
  }
109
117
  return data;
110
118
  };
111
- const mapBuckets = (arr) => {
112
- return arr.map(({ count, period_start }) => ({
113
- count,
114
- periodStart: period_start
115
- }));
116
- };
117
- const mapDkimKey = (key) => ({
118
- algorithm: key.algorithm,
119
- createdAt: key.created_at,
120
- dnsRecords: key.dkim_dns_records,
121
- domain: key.domain,
122
- gracePeriodExpiresAt: key.gracePeriodExpiresAt,
123
- length: key.key_length,
124
- publicKey: key.public_key,
125
- retiresAt: key.retiresAt,
126
- selector: key.selector,
127
- status: key.status,
128
- statusModifiedAt: key.status_modified_at
129
- });
130
119
  const isValidEmail = (email) => {
131
120
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
132
121
  };
@@ -157,6 +146,7 @@ const parseArrayRecipients = (recipients) => {
157
146
  const filtered = (typeof recipients === "string" ? [parseRecipientString(recipients)] : Array.isArray(recipients) ? recipients.map(parseRecipient) : [recipients]).filter((recipient) => Boolean(recipient));
158
147
  return filtered.length > 0 ? filtered : void 0;
159
148
  };
149
+ const stripPemHeaders = (pem) => pem.replace(/-----[^-]+-----|\s|#.*$/gm, "");
160
150
  const RESERVED_HEADER_NAMES = new Set([
161
151
  "authentication-results",
162
152
  "bcc",
@@ -207,6 +197,13 @@ const mapDkim = (dkim) => ({
207
197
  dkim_private_key: dkim?.privateKey ? stripPemHeaders(dkim.privateKey) : void 0,
208
198
  dkim_selector: dkim?.selector
209
199
  });
200
+ const mapAttachment = (attachment) => ({
201
+ content: attachment.content,
202
+ filename: attachment.filename,
203
+ type: attachment.type,
204
+ content_id: attachment.contentId,
205
+ disposition: attachment.disposition
206
+ });
210
207
  const mapPersonalization = (personalization, index, rootTemplateData) => {
211
208
  const to = parseArrayRecipients(personalization.to);
212
209
  if (!to || !to.length) return `Personalization at index ${index} must include at least one recipient in the 'to' field.`;
@@ -238,7 +235,11 @@ const mapPersonalization = (personalization, index, rootTemplateData) => {
238
235
  to
239
236
  };
240
237
  };
241
- const buildSendPayload = (options) => {
238
+ const resolveAttachments = async (attachments) => {
239
+ if (!attachments) return;
240
+ return Promise.all(attachments.map((a) => Promise.resolve(a))).catch((e) => e.message);
241
+ };
242
+ const buildSendPayload = async (options) => {
242
243
  const { from, html, text } = options;
243
244
  const contentTypes = options.content ? new Set(options.content.map((item) => item.type.toLowerCase())) : void 0;
244
245
  const parsedFrom = parseRecipient(from);
@@ -307,8 +308,10 @@ const buildSendPayload = (options) => {
307
308
  value: item.value,
308
309
  template_type
309
310
  });
311
+ const resolvedAttachments = await resolveAttachments(options.attachments);
312
+ if (typeof resolvedAttachments === "string") return resolvedAttachments;
310
313
  return {
311
- attachments: options.attachments,
314
+ attachments: resolvedAttachments?.map((a) => mapAttachment(a)),
312
315
  campaign_id: options.campaignId,
313
316
  ...mapDkim(options.dkim),
314
317
  envelope_from: parseRecipient(options.envelopeFrom),
@@ -332,7 +335,7 @@ var Emails = class {
332
335
  }
333
336
  async _sendEmail(options, flags) {
334
337
  let error = null;
335
- const payload = buildSendPayload(options);
338
+ const payload = await buildSendPayload(options);
336
339
  if (typeof payload === "string") {
337
340
  error = createError(payload);
338
341
  return {
@@ -387,10 +390,26 @@ var Emails = class {
387
390
  async send(options, dryRun = false) {
388
391
  return this._sendEmail(options, { dryRun });
389
392
  }
390
- async sendAsync(options) {
393
+ async queue(options) {
391
394
  return this._sendEmail(options, { async: true });
392
395
  }
396
+ async sendAsync(options) {
397
+ return this.queue(options);
398
+ }
393
399
  };
400
+ const mapDkimKey = (key) => ({
401
+ algorithm: key.algorithm,
402
+ createdAt: key.created_at,
403
+ dnsRecords: key.dkim_dns_records,
404
+ domain: key.domain,
405
+ gracePeriodExpiresAt: key.gracePeriodExpiresAt,
406
+ length: key.key_length,
407
+ publicKey: key.public_key,
408
+ retiresAt: key.retiresAt,
409
+ selector: key.selector,
410
+ status: key.status,
411
+ statusModifiedAt: key.status_modified_at
412
+ });
394
413
  var Domains = class {
395
414
  mailchannels;
396
415
  dkim;
@@ -398,10 +417,16 @@ var Domains = class {
398
417
  this.mailchannels = mailchannels;
399
418
  this.dkim = new DomainsDkim(mailchannels);
400
419
  }
401
- async check(options) {
420
+ async check(domain, options) {
402
421
  let error = null;
403
- const { dkim, domain, senderId } = options;
404
- const dkimOptions = dkim ? Array.isArray(dkim) ? dkim : [dkim] : void 0;
422
+ if (!domain) {
423
+ error = createError("No domain provided.");
424
+ return {
425
+ data: null,
426
+ error
427
+ };
428
+ }
429
+ const dkimOptions = options?.dkim ? Array.isArray(options.dkim) ? options.dkim : [options.dkim] : void 0;
405
430
  if (dkimOptions && dkimOptions.length > 10) {
406
431
  error = createError("A maximum of 10 DKIM settings can be provided.");
407
432
  return {
@@ -417,13 +442,13 @@ var Domains = class {
417
442
  };
418
443
  }
419
444
  const payload = {
420
- dkim_settings: dkimOptions?.map(({ domain, privateKey, selector }) => ({
421
- dkim_domain: domain,
422
- dkim_private_key: privateKey ? stripPemHeaders(privateKey) : void 0,
423
- dkim_selector: selector
445
+ dkim_settings: dkimOptions?.map((dkim) => ({
446
+ dkim_domain: dkim.domain,
447
+ dkim_private_key: dkim.privateKey ? stripPemHeaders(dkim.privateKey) : void 0,
448
+ dkim_selector: dkim.selector
424
449
  })),
425
450
  domain,
426
- sender_id: senderId
451
+ sender_id: options?.senderId
427
452
  };
428
453
  const response = await this.mailchannels.post("/tx/v1/check-domain", {
429
454
  body: payload,
@@ -466,6 +491,13 @@ var DomainsDkim = class {
466
491
  }
467
492
  async create(domain, options) {
468
493
  let error = null;
494
+ if (!domain) {
495
+ error = createError("No domain provided.");
496
+ return {
497
+ data: null,
498
+ error
499
+ };
500
+ }
469
501
  if (!options.selector || options.selector.length > 63) {
470
502
  error = createError("Selector must be between 1 and 63 characters.");
471
503
  return {
@@ -501,6 +533,13 @@ var DomainsDkim = class {
501
533
  }
502
534
  async list(domain, options) {
503
535
  let error = null;
536
+ if (!domain) {
537
+ error = createError("No domain provided.");
538
+ return {
539
+ data: null,
540
+ error
541
+ };
542
+ }
504
543
  if (options?.selector && options.selector.length > 63) {
505
544
  error = createError("Selector must be between 1 and 63 characters.");
506
545
  return {
@@ -541,8 +580,15 @@ var DomainsDkim = class {
541
580
  error: null
542
581
  };
543
582
  }
544
- async update(domain, options) {
583
+ async updateStatus(domain, options) {
545
584
  let error = null;
585
+ if (!domain) {
586
+ error = createError("No domain provided.");
587
+ return {
588
+ success: false,
589
+ error
590
+ };
591
+ }
546
592
  if (!options.selector || options.selector.length > 63) {
547
593
  error = createError("Selector must be between 1 and 63 characters.");
548
594
  return {
@@ -560,7 +606,7 @@ var DomainsDkim = class {
560
606
  });
561
607
  }
562
608
  }).catch((e) => {
563
- error ||= getResultError(e, "Failed to update DKIM key.");
609
+ error ||= getResultError(e, "Failed to update status of DKIM key.");
564
610
  });
565
611
  return {
566
612
  success: !error,
@@ -569,6 +615,13 @@ var DomainsDkim = class {
569
615
  }
570
616
  async rotate(domain, selector, options) {
571
617
  let error = null;
618
+ if (!domain) {
619
+ error = createError("No domain provided.");
620
+ return {
621
+ data: null,
622
+ error
623
+ };
624
+ }
572
625
  if (!selector || selector.length > 63) {
573
626
  error = createError("Selector must be between 1 and 63 characters.");
574
627
  return {
@@ -687,7 +740,7 @@ var Webhooks = class Webhooks {
687
740
  constructor(mailchannels) {
688
741
  this.mailchannels = mailchannels;
689
742
  }
690
- async enroll(endpoint) {
743
+ async create(endpoint) {
691
744
  let error = null;
692
745
  if (!endpoint) {
693
746
  error = createError("No endpoint provided.");
@@ -709,7 +762,7 @@ var Webhooks = class Webhooks {
709
762
  error = getStatusError(response, { [409]: `Endpoint '${endpoint}' is already enrolled to receive notifications.` });
710
763
  }
711
764
  }).catch((e) => {
712
- error ||= getResultError(e, "Failed to enroll webhook.");
765
+ error ||= getResultError(e, "Failed to create webhook.");
713
766
  });
714
767
  return {
715
768
  success: !error,
@@ -733,7 +786,7 @@ var Webhooks = class Webhooks {
733
786
  error: null
734
787
  };
735
788
  }
736
- async delete() {
789
+ async deleteAll() {
737
790
  let error = null;
738
791
  await this.mailchannels.delete("/tx/v1/webhook", { onResponseError: async ({ response }) => {
739
792
  error = getStatusError(response);
@@ -805,7 +858,48 @@ var Webhooks = class Webhooks {
805
858
  };
806
859
  }
807
860
  static async verify(options) {
808
- return isValidWebhook(options).catch(() => false);
861
+ let error = null;
862
+ if (!await isValidWebhook(options).catch(() => false)) {
863
+ error = createError("Invalid webhook signature.");
864
+ return {
865
+ data: null,
866
+ error
867
+ };
868
+ }
869
+ try {
870
+ const payload = JSON.parse(options.payload);
871
+ if (!Array.isArray(payload)) {
872
+ error = createError("Invalid webhook payload.");
873
+ return {
874
+ data: null,
875
+ error
876
+ };
877
+ }
878
+ return {
879
+ data: clean(payload.map((event) => ({
880
+ email: event.email,
881
+ customerHandle: event.customer_handle,
882
+ timestamp: event.timestamp,
883
+ smtpId: event.smtp_id,
884
+ event: event.event,
885
+ requestId: event.request_id,
886
+ campaignId: "campaign_id" in event ? event.campaign_id : void 0,
887
+ recipients: "recipients" in event ? event.recipients : void 0,
888
+ userAgent: "user_agent" in event ? event.user_agent : void 0,
889
+ ip: "ip" in event ? event.ip : void 0,
890
+ url: "url" in event ? event.url : void 0,
891
+ status: "status" in event ? event.status : void 0,
892
+ reason: "reason" in event ? event.reason : void 0
893
+ }))),
894
+ error: null
895
+ };
896
+ } catch {
897
+ error = createError("Invalid webhook payload.");
898
+ return {
899
+ data: null,
900
+ error
901
+ };
902
+ }
809
903
  }
810
904
  async verify(options) {
811
905
  return Webhooks.verify(options);
@@ -1322,6 +1416,12 @@ var SubAccounts = class SubAccounts {
1322
1416
  };
1323
1417
  }
1324
1418
  };
1419
+ const mapBucket = (bucket) => {
1420
+ return {
1421
+ count: bucket.count,
1422
+ periodStart: bucket.period_start
1423
+ };
1424
+ };
1325
1425
  var Metrics = class {
1326
1426
  mailchannels;
1327
1427
  constructor(mailchannels) {
@@ -1350,10 +1450,10 @@ var Metrics = class {
1350
1450
  return {
1351
1451
  data: clean({
1352
1452
  buckets: {
1353
- click: mapBuckets(response.buckets.click),
1354
- clickTrackingDelivered: mapBuckets(response.buckets.click_tracking_delivered),
1355
- open: mapBuckets(response.buckets.open),
1356
- openTrackingDelivered: mapBuckets(response.buckets.open_tracking_delivered)
1453
+ click: response.buckets.click.map(mapBucket),
1454
+ clickTrackingDelivered: response.buckets.click_tracking_delivered.map(mapBucket),
1455
+ open: response.buckets.open.map(mapBucket),
1456
+ openTrackingDelivered: response.buckets.open_tracking_delivered.map(mapBucket)
1357
1457
  },
1358
1458
  click: response.click,
1359
1459
  clickTrackingDelivered: response.click_tracking_delivered,
@@ -1389,9 +1489,9 @@ var Metrics = class {
1389
1489
  data: clean({
1390
1490
  bounced: response.bounced,
1391
1491
  buckets: {
1392
- bounced: mapBuckets(response.buckets.bounced),
1393
- delivered: mapBuckets(response.buckets.delivered),
1394
- processed: mapBuckets(response.buckets.processed)
1492
+ bounced: response.buckets.bounced.map(mapBucket),
1493
+ delivered: response.buckets.delivered.map(mapBucket),
1494
+ processed: response.buckets.processed.map(mapBucket)
1395
1495
  },
1396
1496
  delivered: response.delivered,
1397
1497
  endTime: response.end_time,
@@ -1424,8 +1524,8 @@ var Metrics = class {
1424
1524
  return {
1425
1525
  data: clean({
1426
1526
  buckets: {
1427
- unsubscribeDelivered: mapBuckets(response.buckets.unsubscribe_delivered),
1428
- unsubscribed: mapBuckets(response.buckets.unsubscribed)
1527
+ unsubscribeDelivered: response.buckets.unsubscribe_delivered.map(mapBucket),
1528
+ unsubscribed: response.buckets.unsubscribed.map(mapBucket)
1429
1529
  },
1430
1530
  endTime: response.end_time,
1431
1531
  startTime: response.start_time,
@@ -1458,9 +1558,9 @@ var Metrics = class {
1458
1558
  return {
1459
1559
  data: clean({
1460
1560
  buckets: {
1461
- delivered: mapBuckets(response.buckets.delivered),
1462
- dropped: mapBuckets(response.buckets.dropped),
1463
- processed: mapBuckets(response.buckets.processed)
1561
+ delivered: response.buckets.delivered.map(mapBucket),
1562
+ dropped: response.buckets.dropped.map(mapBucket),
1563
+ processed: response.buckets.processed.map(mapBucket)
1464
1564
  },
1465
1565
  delivered: response.delivered,
1466
1566
  dropped: response.dropped,
@@ -1640,6 +1740,62 @@ var Suppressions = class {
1640
1740
  };
1641
1741
  }
1642
1742
  };
1743
+ const base64Content = (data) => {
1744
+ if (data instanceof ArrayBuffer) return Buffer.from(data).toString("base64");
1745
+ return Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString("base64");
1746
+ };
1747
+ const guessContentType = (filename) => {
1748
+ return mime.getType(filename) || void 0;
1749
+ };
1750
+ var Attachment = class Attachment {
1751
+ static fromBytes(data, options) {
1752
+ const { filename, type, contentId, disposition = "attachment" } = options;
1753
+ return {
1754
+ content: base64Content(data),
1755
+ filename: decodeURIComponent(filename) || "attachment",
1756
+ type: type || guessContentType(filename),
1757
+ contentId,
1758
+ disposition
1759
+ };
1760
+ }
1761
+ static async fromFile(path, options) {
1762
+ try {
1763
+ const data = await readFile(path);
1764
+ return Attachment.fromBytes(data, {
1765
+ filename: basename(path.toString()),
1766
+ ...options
1767
+ });
1768
+ } catch (error) {
1769
+ throw new Error(`Unable to read attachment file: ${path}`, { cause: error });
1770
+ }
1771
+ }
1772
+ static async fromUrl(url, options) {
1773
+ try {
1774
+ let contentType;
1775
+ const data = await $fetch(url, {
1776
+ responseType: "arrayBuffer",
1777
+ timeout: 12e4,
1778
+ onResponse: ({ response }) => {
1779
+ const contentTypeHeader = response.headers.get("content-type");
1780
+ if (contentTypeHeader) contentType = contentTypeHeader.split(";", 1)[0]?.trim();
1781
+ }
1782
+ });
1783
+ return Attachment.fromBytes(data, {
1784
+ type: contentType,
1785
+ filename: basename(new URL(url).pathname),
1786
+ ...options
1787
+ });
1788
+ } catch (error) {
1789
+ throw new Error(`Unable to fetch attachment from URL: ${url}`, { cause: error });
1790
+ }
1791
+ }
1792
+ static inlineFile(path, options) {
1793
+ return Attachment.fromFile(path, {
1794
+ ...options,
1795
+ disposition: "inline"
1796
+ });
1797
+ }
1798
+ };
1643
1799
  var MailChannels = class extends MailChannelsClient {
1644
1800
  emails = new Emails(this);
1645
1801
  domains = new Domains(this);
@@ -1651,4 +1807,4 @@ var MailChannels = class extends MailChannelsClient {
1651
1807
  super(key, options);
1652
1808
  }
1653
1809
  };
1654
- export { Domains, Emails, MailChannels, MailChannelsClient, Metrics, SubAccounts, Suppressions, Webhooks };
1810
+ export { Attachment, Domains, Emails, MailChannels, MailChannelsClient, Metrics, SubAccounts, Suppressions, Webhooks };
package/package.json CHANGED
@@ -1,26 +1,24 @@
1
1
  {
2
2
  "name": "mailchannels-sdk",
3
- "version": "0.8.0-0",
3
+ "version": "0.8.0",
4
4
  "description": "Node.js SDK to integrate MailChannels Email API into your JavaScript or TypeScript server-side applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "keywords": [
8
8
  "mailchannels",
9
- "javascript",
10
9
  "typescript",
11
- "node",
12
- "sdk",
13
- "email"
10
+ "email",
11
+ "transactional-email",
12
+ "sdk"
14
13
  ],
15
14
  "repository": {
16
15
  "type": "git",
17
- "url": "git+https://github.com/Yizack/mailchannels.git"
16
+ "url": "git+https://bitbucket.org/mailchannels/mailchannels-email-api-sdk-js.git"
18
17
  },
19
18
  "homepage": "https://mailchannels.yizack.com",
20
19
  "author": {
21
- "name": "Yizack Rangel",
22
- "email": "yizackr@gmail.com",
23
- "url": "https://yizack.com"
20
+ "name": "MailChannels",
21
+ "email": "dev@mailchannels.com"
24
22
  },
25
23
  "main": "./dist/mailchannels.mjs",
26
24
  "exports": {
@@ -38,22 +36,26 @@
38
36
  "dist"
39
37
  ],
40
38
  "dependencies": {
39
+ "mime": "^4.1.0",
41
40
  "ofetch": "^2.0.0-alpha.3"
42
41
  },
43
42
  "devDependencies": {
44
43
  "@stylistic/eslint-plugin": "^5.10.0",
45
44
  "@types/markdown-it": "^14.1.2",
46
- "@types/node": "^25.8.0",
47
- "@vitest/coverage-v8": "^4.1.6",
45
+ "@types/node": "^25.9.1",
46
+ "@vitest/coverage-v8": "^4.1.7",
48
47
  "changelogen": "^0.6.2",
49
- "obuild": "^0.4.35",
50
- "oxlint": "^1.64.0",
48
+ "obuild": "^0.4.36",
49
+ "oxlint": "^1.67.0",
51
50
  "scule": "^1.3.0",
52
51
  "typescript": "^6.0.3",
53
52
  "vitepress": "^2.0.0-alpha.17",
54
53
  "vitepress-plugin-group-icons": "^1.7.5",
55
- "vitepress-plugin-llms": "^1.12.2",
56
- "vitest": "^4.1.6"
54
+ "vitepress-plugin-llms": "^1.13.0",
55
+ "vitest": "^4.1.7"
56
+ },
57
+ "engines": {
58
+ "node": ">=20"
57
59
  },
58
60
  "scripts": {
59
61
  "build": "obuild",