mailchannels-sdk 0.8.0-1 → 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 +1 -1
- package/README.md +2 -6
- package/dist/mailchannels.d.mts +53 -32
- package/dist/mailchannels.mjs +183 -54
- package/package.json +17 -15
package/LICENSE
CHANGED
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://
|
|
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
|
-
|
|
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
|
package/dist/mailchannels.d.mts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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(
|
|
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.
|
|
783
|
+
* const { success, error } = await mailchannels.domains.dkim.updateStatus('example.com', {
|
|
764
784
|
* selector: 'mailchannels',
|
|
765
785
|
* status: 'retired'
|
|
766
786
|
* })
|
|
767
787
|
*/
|
|
768
|
-
|
|
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.
|
|
@@ -842,7 +862,7 @@ interface WebhookEventBase<T extends WebhookEventType> {
|
|
|
842
862
|
* The MailChannels account ID that generated the webhook.
|
|
843
863
|
* If the message was sent by a sub-account, this field contains the sub-account handle.
|
|
844
864
|
*/
|
|
845
|
-
|
|
865
|
+
customerHandle: string;
|
|
846
866
|
/**
|
|
847
867
|
* The Unix timestamp (in seconds) when the event occurred; the timezone is always UTC
|
|
848
868
|
*/
|
|
@@ -850,7 +870,7 @@ interface WebhookEventBase<T extends WebhookEventType> {
|
|
|
850
870
|
/**
|
|
851
871
|
* The Message-Id of the message that generated the event
|
|
852
872
|
*/
|
|
853
|
-
|
|
873
|
+
smtpId?: string;
|
|
854
874
|
/**
|
|
855
875
|
* The type of event that occurred
|
|
856
876
|
*/
|
|
@@ -858,11 +878,11 @@ interface WebhookEventBase<T extends WebhookEventType> {
|
|
|
858
878
|
/**
|
|
859
879
|
* A unique identifier generated to track the original HTTP request
|
|
860
880
|
*/
|
|
861
|
-
|
|
881
|
+
requestId?: string;
|
|
862
882
|
/**
|
|
863
883
|
* The campaign identifier for the message that generated the event
|
|
864
884
|
*/
|
|
865
|
-
|
|
885
|
+
campaignId?: string;
|
|
866
886
|
/**
|
|
867
887
|
* The recipients of the message
|
|
868
888
|
*/
|
|
@@ -874,7 +894,7 @@ interface WebhookEventWithTracking {
|
|
|
874
894
|
/**
|
|
875
895
|
* The User-Agent header given when the recipient opened the message
|
|
876
896
|
*/
|
|
877
|
-
|
|
897
|
+
userAgent?: string;
|
|
878
898
|
/**
|
|
879
899
|
* The IP address of the host that made the HTTP request
|
|
880
900
|
*/
|
|
@@ -902,9 +922,8 @@ interface WebhookEventSoftBounced extends WebhookEventBase<"soft-bounced">, Webh
|
|
|
902
922
|
interface WebhookEventDropped extends WebhookEventBase<"dropped">, WebhookEventWithStatus {}
|
|
903
923
|
interface WebhookEventComplained extends WebhookEventBase<"complained"> {}
|
|
904
924
|
interface WebhookEventUnsubscribed extends WebhookEventBase<"unsubscribed"> {}
|
|
905
|
-
interface WebhookEventTest extends Omit<WebhookEventBase<"test">, "recipients" | "
|
|
925
|
+
interface WebhookEventTest extends Omit<WebhookEventBase<"test">, "recipients" | "campaignId"> {}
|
|
906
926
|
type WebhookEvent = WebhookEventProcessed | WebhookEventDelivered | WebhookEventOpen | WebhookEventClick | WebhookEventHardBounced | WebhookEventSoftBounced | WebhookEventDropped | WebhookEventComplained | WebhookEventUnsubscribed | WebhookEventTest;
|
|
907
|
-
type WebhookEvents = WebhookEvent[];
|
|
908
927
|
interface WebhooksVerifyOptions {
|
|
909
928
|
/**
|
|
910
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.
|
|
@@ -928,12 +947,7 @@ interface WebhooksVerifyOptions {
|
|
|
928
947
|
*/
|
|
929
948
|
cache?: boolean;
|
|
930
949
|
}
|
|
931
|
-
type WebhooksVerifyResponse = DataResponse<
|
|
932
|
-
/**
|
|
933
|
-
* The type of event that occurred.
|
|
934
|
-
*/
|
|
935
|
-
event: WebhookEventType;
|
|
936
|
-
}[]>;
|
|
950
|
+
type WebhooksVerifyResponse = DataResponse<WebhookEvent[]>;
|
|
937
951
|
type WebhooksBatchStatus = "1xx" | "2xx" | "3xx" | "4xx" | "5xx" | "no_response";
|
|
938
952
|
type WebhooksBatchResponseStatus = "1xx_response" | "2xx_response" | "3xx_response" | "4xx_response" | "5xx_response" | "no_response";
|
|
939
953
|
interface WebhooksBatchesOptions {
|
|
@@ -1042,10 +1056,10 @@ declare class Webhooks {
|
|
|
1042
1056
|
* @example
|
|
1043
1057
|
* ```ts
|
|
1044
1058
|
* const mailchannels = new MailChannels('your-api-key')
|
|
1045
|
-
* const { success, error } = mailchannels.webhooks.
|
|
1059
|
+
* const { success, error } = await mailchannels.webhooks.create('https://example.com/api/webhooks/mailchannels')
|
|
1046
1060
|
* ```
|
|
1047
1061
|
*/
|
|
1048
|
-
|
|
1062
|
+
create(endpoint: string): Promise<SuccessResponse>;
|
|
1049
1063
|
/**
|
|
1050
1064
|
* Retrieves all registered webhook endpoints associated with the customer.
|
|
1051
1065
|
* @example
|
|
@@ -1743,6 +1757,13 @@ declare class Suppressions {
|
|
|
1743
1757
|
*/
|
|
1744
1758
|
list(options?: SuppressionsListOptions): Promise<SuppressionsListResponse>;
|
|
1745
1759
|
}
|
|
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>;
|
|
1766
|
+
}
|
|
1746
1767
|
declare class MailChannels extends MailChannelsClient {
|
|
1747
1768
|
readonly emails: Emails;
|
|
1748
1769
|
readonly domains: Domains;
|
|
@@ -1752,4 +1773,4 @@ declare class MailChannels extends MailChannelsClient {
|
|
|
1752
1773
|
readonly suppressions: Suppressions;
|
|
1753
1774
|
constructor(key: string, options?: MailChannelsClientOptions);
|
|
1754
1775
|
}
|
|
1755
|
-
export { DataResponse, Domains, DomainsCheckOptions, DomainsCheckResponse, DomainsCheckVerdict, DomainsDkimCreateOptions, DomainsDkimCreateResponse, DomainsDkimKey, DomainsDkimKeyStatus, DomainsDkimListOptions, DomainsDkimListResponse, DomainsDkimRotateOptions, DomainsDkimRotateResponse,
|
|
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 };
|
package/dist/mailchannels.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
33
|
+
signal,
|
|
34
|
+
timeout: this.options.timeout === false ? void 0 : this.options.timeout,
|
|
35
|
+
...fetchOptions,
|
|
27
36
|
headers: {
|
|
28
37
|
...this.#headers,
|
|
29
|
-
...
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
404
|
-
|
|
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((
|
|
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
|
|
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
|
|
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
|
|
765
|
+
error ||= getResultError(e, "Failed to create webhook.");
|
|
713
766
|
});
|
|
714
767
|
return {
|
|
715
768
|
success: !error,
|
|
@@ -823,7 +876,21 @@ var Webhooks = class Webhooks {
|
|
|
823
876
|
};
|
|
824
877
|
}
|
|
825
878
|
return {
|
|
826
|
-
data: clean(payload.map((event) => ({
|
|
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
|
+
}))),
|
|
827
894
|
error: null
|
|
828
895
|
};
|
|
829
896
|
} catch {
|
|
@@ -1349,6 +1416,12 @@ var SubAccounts = class SubAccounts {
|
|
|
1349
1416
|
};
|
|
1350
1417
|
}
|
|
1351
1418
|
};
|
|
1419
|
+
const mapBucket = (bucket) => {
|
|
1420
|
+
return {
|
|
1421
|
+
count: bucket.count,
|
|
1422
|
+
periodStart: bucket.period_start
|
|
1423
|
+
};
|
|
1424
|
+
};
|
|
1352
1425
|
var Metrics = class {
|
|
1353
1426
|
mailchannels;
|
|
1354
1427
|
constructor(mailchannels) {
|
|
@@ -1377,10 +1450,10 @@ var Metrics = class {
|
|
|
1377
1450
|
return {
|
|
1378
1451
|
data: clean({
|
|
1379
1452
|
buckets: {
|
|
1380
|
-
click:
|
|
1381
|
-
clickTrackingDelivered:
|
|
1382
|
-
open:
|
|
1383
|
-
openTrackingDelivered:
|
|
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)
|
|
1384
1457
|
},
|
|
1385
1458
|
click: response.click,
|
|
1386
1459
|
clickTrackingDelivered: response.click_tracking_delivered,
|
|
@@ -1416,9 +1489,9 @@ var Metrics = class {
|
|
|
1416
1489
|
data: clean({
|
|
1417
1490
|
bounced: response.bounced,
|
|
1418
1491
|
buckets: {
|
|
1419
|
-
bounced:
|
|
1420
|
-
delivered:
|
|
1421
|
-
processed:
|
|
1492
|
+
bounced: response.buckets.bounced.map(mapBucket),
|
|
1493
|
+
delivered: response.buckets.delivered.map(mapBucket),
|
|
1494
|
+
processed: response.buckets.processed.map(mapBucket)
|
|
1422
1495
|
},
|
|
1423
1496
|
delivered: response.delivered,
|
|
1424
1497
|
endTime: response.end_time,
|
|
@@ -1451,8 +1524,8 @@ var Metrics = class {
|
|
|
1451
1524
|
return {
|
|
1452
1525
|
data: clean({
|
|
1453
1526
|
buckets: {
|
|
1454
|
-
unsubscribeDelivered:
|
|
1455
|
-
unsubscribed:
|
|
1527
|
+
unsubscribeDelivered: response.buckets.unsubscribe_delivered.map(mapBucket),
|
|
1528
|
+
unsubscribed: response.buckets.unsubscribed.map(mapBucket)
|
|
1456
1529
|
},
|
|
1457
1530
|
endTime: response.end_time,
|
|
1458
1531
|
startTime: response.start_time,
|
|
@@ -1485,9 +1558,9 @@ var Metrics = class {
|
|
|
1485
1558
|
return {
|
|
1486
1559
|
data: clean({
|
|
1487
1560
|
buckets: {
|
|
1488
|
-
delivered:
|
|
1489
|
-
dropped:
|
|
1490
|
-
processed:
|
|
1561
|
+
delivered: response.buckets.delivered.map(mapBucket),
|
|
1562
|
+
dropped: response.buckets.dropped.map(mapBucket),
|
|
1563
|
+
processed: response.buckets.processed.map(mapBucket)
|
|
1491
1564
|
},
|
|
1492
1565
|
delivered: response.delivered,
|
|
1493
1566
|
dropped: response.dropped,
|
|
@@ -1667,6 +1740,62 @@ var Suppressions = class {
|
|
|
1667
1740
|
};
|
|
1668
1741
|
}
|
|
1669
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
|
+
};
|
|
1670
1799
|
var MailChannels = class extends MailChannelsClient {
|
|
1671
1800
|
emails = new Emails(this);
|
|
1672
1801
|
domains = new Domains(this);
|
|
@@ -1678,4 +1807,4 @@ var MailChannels = class extends MailChannelsClient {
|
|
|
1678
1807
|
super(key, options);
|
|
1679
1808
|
}
|
|
1680
1809
|
};
|
|
1681
|
-
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
|
|
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
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
10
|
+
"email",
|
|
11
|
+
"transactional-email",
|
|
12
|
+
"sdk"
|
|
14
13
|
],
|
|
15
14
|
"repository": {
|
|
16
15
|
"type": "git",
|
|
17
|
-
"url": "git+https://
|
|
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": "
|
|
22
|
-
"email": "
|
|
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.
|
|
47
|
-
"@vitest/coverage-v8": "^4.1.
|
|
45
|
+
"@types/node": "^25.9.1",
|
|
46
|
+
"@vitest/coverage-v8": "^4.1.7",
|
|
48
47
|
"changelogen": "^0.6.2",
|
|
49
|
-
"obuild": "^0.4.
|
|
50
|
-
"oxlint": "^1.
|
|
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.
|
|
56
|
-
"vitest": "^4.1.
|
|
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",
|