@upyo/jmap 0.4.0-dev.72 → 0.4.0-dev.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +127 -5
- package/dist/index.d.cts +18 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.js +127 -5
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -516,14 +516,84 @@ var JmapTransport = class {
|
|
|
516
516
|
}
|
|
517
517
|
}
|
|
518
518
|
/**
|
|
519
|
-
* Sends multiple messages
|
|
519
|
+
* Sends multiple messages in a single batched JMAP request.
|
|
520
520
|
* @param messages The messages to send.
|
|
521
521
|
* @param options Optional transport options.
|
|
522
522
|
* @yields Receipts for each message.
|
|
523
523
|
* @since 0.4.0
|
|
524
524
|
*/
|
|
525
525
|
async *sendMany(messages, options) {
|
|
526
|
-
|
|
526
|
+
const signal = options?.signal;
|
|
527
|
+
const messageArray = [];
|
|
528
|
+
for await (const message of messages) messageArray.push(message);
|
|
529
|
+
if (messageArray.length === 0) return;
|
|
530
|
+
try {
|
|
531
|
+
signal?.throwIfAborted();
|
|
532
|
+
const session = await this.getSession(signal);
|
|
533
|
+
signal?.throwIfAborted();
|
|
534
|
+
const accountId = this.config.accountId ?? findMailAccount(session);
|
|
535
|
+
if (!accountId) {
|
|
536
|
+
for (let i = 0; i < messageArray.length; i++) yield {
|
|
537
|
+
successful: false,
|
|
538
|
+
errorMessages: ["No mail-capable account found in JMAP session"]
|
|
539
|
+
};
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
const draftsMailboxId = await this.getDraftsMailboxId(session, accountId, signal);
|
|
543
|
+
signal?.throwIfAborted();
|
|
544
|
+
const identityMap = await this.getIdentityMap(session, accountId, signal);
|
|
545
|
+
signal?.throwIfAborted();
|
|
546
|
+
const allUploadedBlobs = /* @__PURE__ */ new Map();
|
|
547
|
+
for (let i = 0; i < messageArray.length; i++) {
|
|
548
|
+
const message = messageArray[i];
|
|
549
|
+
const uploadedBlobs = await this.uploadAttachments(session, accountId, message.attachments, signal);
|
|
550
|
+
allUploadedBlobs.set(i, uploadedBlobs);
|
|
551
|
+
signal?.throwIfAborted();
|
|
552
|
+
}
|
|
553
|
+
const emailCreates = {};
|
|
554
|
+
const submissionCreates = {};
|
|
555
|
+
for (let i = 0; i < messageArray.length; i++) {
|
|
556
|
+
const message = messageArray[i];
|
|
557
|
+
const uploadedBlobs = allUploadedBlobs.get(i);
|
|
558
|
+
const emailCreate = convertMessage(message, draftsMailboxId, uploadedBlobs);
|
|
559
|
+
const senderEmail = message.sender.address.toLowerCase();
|
|
560
|
+
const identityId = identityMap.get(senderEmail) ?? identityMap.values().next().value;
|
|
561
|
+
emailCreates[`draft${i}`] = emailCreate;
|
|
562
|
+
submissionCreates[`sub${i}`] = {
|
|
563
|
+
identityId,
|
|
564
|
+
emailId: `#draft${i}`
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
const response = await this.httpClient.executeRequest(session.apiUrl, {
|
|
568
|
+
using: [
|
|
569
|
+
JMAP_CAPABILITIES.core,
|
|
570
|
+
JMAP_CAPABILITIES.mail,
|
|
571
|
+
JMAP_CAPABILITIES.submission
|
|
572
|
+
],
|
|
573
|
+
methodCalls: [[
|
|
574
|
+
"Email/set",
|
|
575
|
+
{
|
|
576
|
+
accountId,
|
|
577
|
+
create: emailCreates
|
|
578
|
+
},
|
|
579
|
+
"c0"
|
|
580
|
+
], [
|
|
581
|
+
"EmailSubmission/set",
|
|
582
|
+
{
|
|
583
|
+
accountId,
|
|
584
|
+
create: submissionCreates
|
|
585
|
+
},
|
|
586
|
+
"c1"
|
|
587
|
+
]]
|
|
588
|
+
}, signal);
|
|
589
|
+
for (let i = 0; i < messageArray.length; i++) yield this.parseBatchResponseForIndex(response, i);
|
|
590
|
+
} catch (error) {
|
|
591
|
+
const errorMessage = error instanceof Error && error.name === "AbortError" ? `Request aborted: ${error.message}` : error instanceof JmapApiError ? error.message : error instanceof Error ? error.message : String(error);
|
|
592
|
+
for (let i = 0; i < messageArray.length; i++) yield {
|
|
593
|
+
successful: false,
|
|
594
|
+
errorMessages: [errorMessage]
|
|
595
|
+
};
|
|
596
|
+
}
|
|
527
597
|
}
|
|
528
598
|
/**
|
|
529
599
|
* Gets or refreshes the JMAP session.
|
|
@@ -609,6 +679,21 @@ var JmapTransport = class {
|
|
|
609
679
|
*/
|
|
610
680
|
async getIdentityId(session, accountId, senderEmail, signal) {
|
|
611
681
|
if (this.config.identityId) return this.config.identityId;
|
|
682
|
+
const identityMap = await this.getIdentityMap(session, accountId, signal);
|
|
683
|
+
const matching = identityMap.get(senderEmail.toLowerCase());
|
|
684
|
+
if (matching) return matching;
|
|
685
|
+
return identityMap.values().next().value;
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Gets all identities and builds a map of email to identity ID.
|
|
689
|
+
* @param session The JMAP session.
|
|
690
|
+
* @param accountId The account ID.
|
|
691
|
+
* @param signal Optional abort signal.
|
|
692
|
+
* @returns Map of lowercase email to identity ID.
|
|
693
|
+
* @since 0.4.0
|
|
694
|
+
*/
|
|
695
|
+
async getIdentityMap(session, accountId, signal) {
|
|
696
|
+
if (this.config.identityId) return new Map([["*", this.config.identityId]]);
|
|
612
697
|
const response = await this.httpClient.executeRequest(session.apiUrl, {
|
|
613
698
|
using: [JMAP_CAPABILITIES.core, JMAP_CAPABILITIES.submission],
|
|
614
699
|
methodCalls: [[
|
|
@@ -621,9 +706,9 @@ var JmapTransport = class {
|
|
|
621
706
|
if (!identityResponse) throw new JmapApiError("No Identity/get response received");
|
|
622
707
|
const identities = identityResponse[1].list;
|
|
623
708
|
if (!identities || identities.length === 0) throw new JmapApiError("No identities found");
|
|
624
|
-
const
|
|
625
|
-
|
|
626
|
-
return
|
|
709
|
+
const identityMap = /* @__PURE__ */ new Map();
|
|
710
|
+
for (const identity of identities) identityMap.set(identity.email.toLowerCase(), identity.id);
|
|
711
|
+
return identityMap;
|
|
627
712
|
}
|
|
628
713
|
/**
|
|
629
714
|
* Uploads all attachments and returns a map of contentId to blobId.
|
|
@@ -674,6 +759,43 @@ var JmapTransport = class {
|
|
|
674
759
|
errorMessages: errors
|
|
675
760
|
};
|
|
676
761
|
}
|
|
762
|
+
/**
|
|
763
|
+
* Parses the JMAP batch response to extract receipt for a specific index.
|
|
764
|
+
* @param response The JMAP response.
|
|
765
|
+
* @param index The message index in the batch.
|
|
766
|
+
* @returns A receipt indicating success or failure for that message.
|
|
767
|
+
* @since 0.4.0
|
|
768
|
+
*/
|
|
769
|
+
parseBatchResponseForIndex(response, index) {
|
|
770
|
+
const errors = [];
|
|
771
|
+
const draftKey = `draft${index}`;
|
|
772
|
+
const subKey = `sub${index}`;
|
|
773
|
+
const emailResponse = response.methodResponses.find((r) => r[0] === "Email/set");
|
|
774
|
+
if (emailResponse) {
|
|
775
|
+
const emailResult = emailResponse[1];
|
|
776
|
+
if (emailResult.notCreated?.[draftKey]) {
|
|
777
|
+
const error = emailResult.notCreated[draftKey];
|
|
778
|
+
errors.push(`Email creation failed: ${error.type}${error.description ? ` - ${error.description}` : ""}`);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
const submissionResponse = response.methodResponses.find((r) => r[0] === "EmailSubmission/set");
|
|
782
|
+
if (submissionResponse) {
|
|
783
|
+
const submissionResult = submissionResponse[1];
|
|
784
|
+
if (submissionResult.notCreated?.[subKey]) {
|
|
785
|
+
const error = submissionResult.notCreated[subKey];
|
|
786
|
+
errors.push(`Email submission failed: ${error.type}${error.description ? ` - ${error.description}` : ""}`);
|
|
787
|
+
}
|
|
788
|
+
if (submissionResult.created?.[subKey]) return {
|
|
789
|
+
successful: true,
|
|
790
|
+
messageId: submissionResult.created[subKey].id
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
if (errors.length === 0) errors.push("Unknown error: No submission result received");
|
|
794
|
+
return {
|
|
795
|
+
successful: false,
|
|
796
|
+
errorMessages: errors
|
|
797
|
+
};
|
|
798
|
+
}
|
|
677
799
|
};
|
|
678
800
|
|
|
679
801
|
//#endregion
|
package/dist/index.d.cts
CHANGED
|
@@ -122,7 +122,7 @@ declare class JmapTransport implements Transport {
|
|
|
122
122
|
*/
|
|
123
123
|
send(message: Message, options?: TransportOptions): Promise<Receipt>;
|
|
124
124
|
/**
|
|
125
|
-
* Sends multiple messages
|
|
125
|
+
* Sends multiple messages in a single batched JMAP request.
|
|
126
126
|
* @param messages The messages to send.
|
|
127
127
|
* @param options Optional transport options.
|
|
128
128
|
* @yields Receipts for each message.
|
|
@@ -162,6 +162,15 @@ declare class JmapTransport implements Transport {
|
|
|
162
162
|
* @since 0.4.0
|
|
163
163
|
*/
|
|
164
164
|
private getIdentityId;
|
|
165
|
+
/**
|
|
166
|
+
* Gets all identities and builds a map of email to identity ID.
|
|
167
|
+
* @param session The JMAP session.
|
|
168
|
+
* @param accountId The account ID.
|
|
169
|
+
* @param signal Optional abort signal.
|
|
170
|
+
* @returns Map of lowercase email to identity ID.
|
|
171
|
+
* @since 0.4.0
|
|
172
|
+
*/
|
|
173
|
+
private getIdentityMap;
|
|
165
174
|
/**
|
|
166
175
|
* Uploads all attachments and returns a map of contentId to blobId.
|
|
167
176
|
* @param session The JMAP session.
|
|
@@ -179,6 +188,14 @@ declare class JmapTransport implements Transport {
|
|
|
179
188
|
* @since 0.4.0
|
|
180
189
|
*/
|
|
181
190
|
private parseResponse;
|
|
191
|
+
/**
|
|
192
|
+
* Parses the JMAP batch response to extract receipt for a specific index.
|
|
193
|
+
* @param response The JMAP response.
|
|
194
|
+
* @param index The message index in the batch.
|
|
195
|
+
* @returns A receipt indicating success or failure for that message.
|
|
196
|
+
* @since 0.4.0
|
|
197
|
+
*/
|
|
198
|
+
private parseBatchResponseForIndex;
|
|
182
199
|
}
|
|
183
200
|
//#endregion
|
|
184
201
|
//#region src/errors.d.ts
|
package/dist/index.d.ts
CHANGED
|
@@ -122,7 +122,7 @@ declare class JmapTransport implements Transport {
|
|
|
122
122
|
*/
|
|
123
123
|
send(message: Message, options?: TransportOptions): Promise<Receipt>;
|
|
124
124
|
/**
|
|
125
|
-
* Sends multiple messages
|
|
125
|
+
* Sends multiple messages in a single batched JMAP request.
|
|
126
126
|
* @param messages The messages to send.
|
|
127
127
|
* @param options Optional transport options.
|
|
128
128
|
* @yields Receipts for each message.
|
|
@@ -162,6 +162,15 @@ declare class JmapTransport implements Transport {
|
|
|
162
162
|
* @since 0.4.0
|
|
163
163
|
*/
|
|
164
164
|
private getIdentityId;
|
|
165
|
+
/**
|
|
166
|
+
* Gets all identities and builds a map of email to identity ID.
|
|
167
|
+
* @param session The JMAP session.
|
|
168
|
+
* @param accountId The account ID.
|
|
169
|
+
* @param signal Optional abort signal.
|
|
170
|
+
* @returns Map of lowercase email to identity ID.
|
|
171
|
+
* @since 0.4.0
|
|
172
|
+
*/
|
|
173
|
+
private getIdentityMap;
|
|
165
174
|
/**
|
|
166
175
|
* Uploads all attachments and returns a map of contentId to blobId.
|
|
167
176
|
* @param session The JMAP session.
|
|
@@ -179,6 +188,14 @@ declare class JmapTransport implements Transport {
|
|
|
179
188
|
* @since 0.4.0
|
|
180
189
|
*/
|
|
181
190
|
private parseResponse;
|
|
191
|
+
/**
|
|
192
|
+
* Parses the JMAP batch response to extract receipt for a specific index.
|
|
193
|
+
* @param response The JMAP response.
|
|
194
|
+
* @param index The message index in the batch.
|
|
195
|
+
* @returns A receipt indicating success or failure for that message.
|
|
196
|
+
* @since 0.4.0
|
|
197
|
+
*/
|
|
198
|
+
private parseBatchResponseForIndex;
|
|
182
199
|
}
|
|
183
200
|
//#endregion
|
|
184
201
|
//#region src/errors.d.ts
|
package/dist/index.js
CHANGED
|
@@ -515,14 +515,84 @@ var JmapTransport = class {
|
|
|
515
515
|
}
|
|
516
516
|
}
|
|
517
517
|
/**
|
|
518
|
-
* Sends multiple messages
|
|
518
|
+
* Sends multiple messages in a single batched JMAP request.
|
|
519
519
|
* @param messages The messages to send.
|
|
520
520
|
* @param options Optional transport options.
|
|
521
521
|
* @yields Receipts for each message.
|
|
522
522
|
* @since 0.4.0
|
|
523
523
|
*/
|
|
524
524
|
async *sendMany(messages, options) {
|
|
525
|
-
|
|
525
|
+
const signal = options?.signal;
|
|
526
|
+
const messageArray = [];
|
|
527
|
+
for await (const message of messages) messageArray.push(message);
|
|
528
|
+
if (messageArray.length === 0) return;
|
|
529
|
+
try {
|
|
530
|
+
signal?.throwIfAborted();
|
|
531
|
+
const session = await this.getSession(signal);
|
|
532
|
+
signal?.throwIfAborted();
|
|
533
|
+
const accountId = this.config.accountId ?? findMailAccount(session);
|
|
534
|
+
if (!accountId) {
|
|
535
|
+
for (let i = 0; i < messageArray.length; i++) yield {
|
|
536
|
+
successful: false,
|
|
537
|
+
errorMessages: ["No mail-capable account found in JMAP session"]
|
|
538
|
+
};
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
const draftsMailboxId = await this.getDraftsMailboxId(session, accountId, signal);
|
|
542
|
+
signal?.throwIfAborted();
|
|
543
|
+
const identityMap = await this.getIdentityMap(session, accountId, signal);
|
|
544
|
+
signal?.throwIfAborted();
|
|
545
|
+
const allUploadedBlobs = /* @__PURE__ */ new Map();
|
|
546
|
+
for (let i = 0; i < messageArray.length; i++) {
|
|
547
|
+
const message = messageArray[i];
|
|
548
|
+
const uploadedBlobs = await this.uploadAttachments(session, accountId, message.attachments, signal);
|
|
549
|
+
allUploadedBlobs.set(i, uploadedBlobs);
|
|
550
|
+
signal?.throwIfAborted();
|
|
551
|
+
}
|
|
552
|
+
const emailCreates = {};
|
|
553
|
+
const submissionCreates = {};
|
|
554
|
+
for (let i = 0; i < messageArray.length; i++) {
|
|
555
|
+
const message = messageArray[i];
|
|
556
|
+
const uploadedBlobs = allUploadedBlobs.get(i);
|
|
557
|
+
const emailCreate = convertMessage(message, draftsMailboxId, uploadedBlobs);
|
|
558
|
+
const senderEmail = message.sender.address.toLowerCase();
|
|
559
|
+
const identityId = identityMap.get(senderEmail) ?? identityMap.values().next().value;
|
|
560
|
+
emailCreates[`draft${i}`] = emailCreate;
|
|
561
|
+
submissionCreates[`sub${i}`] = {
|
|
562
|
+
identityId,
|
|
563
|
+
emailId: `#draft${i}`
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
const response = await this.httpClient.executeRequest(session.apiUrl, {
|
|
567
|
+
using: [
|
|
568
|
+
JMAP_CAPABILITIES.core,
|
|
569
|
+
JMAP_CAPABILITIES.mail,
|
|
570
|
+
JMAP_CAPABILITIES.submission
|
|
571
|
+
],
|
|
572
|
+
methodCalls: [[
|
|
573
|
+
"Email/set",
|
|
574
|
+
{
|
|
575
|
+
accountId,
|
|
576
|
+
create: emailCreates
|
|
577
|
+
},
|
|
578
|
+
"c0"
|
|
579
|
+
], [
|
|
580
|
+
"EmailSubmission/set",
|
|
581
|
+
{
|
|
582
|
+
accountId,
|
|
583
|
+
create: submissionCreates
|
|
584
|
+
},
|
|
585
|
+
"c1"
|
|
586
|
+
]]
|
|
587
|
+
}, signal);
|
|
588
|
+
for (let i = 0; i < messageArray.length; i++) yield this.parseBatchResponseForIndex(response, i);
|
|
589
|
+
} catch (error) {
|
|
590
|
+
const errorMessage = error instanceof Error && error.name === "AbortError" ? `Request aborted: ${error.message}` : error instanceof JmapApiError ? error.message : error instanceof Error ? error.message : String(error);
|
|
591
|
+
for (let i = 0; i < messageArray.length; i++) yield {
|
|
592
|
+
successful: false,
|
|
593
|
+
errorMessages: [errorMessage]
|
|
594
|
+
};
|
|
595
|
+
}
|
|
526
596
|
}
|
|
527
597
|
/**
|
|
528
598
|
* Gets or refreshes the JMAP session.
|
|
@@ -608,6 +678,21 @@ var JmapTransport = class {
|
|
|
608
678
|
*/
|
|
609
679
|
async getIdentityId(session, accountId, senderEmail, signal) {
|
|
610
680
|
if (this.config.identityId) return this.config.identityId;
|
|
681
|
+
const identityMap = await this.getIdentityMap(session, accountId, signal);
|
|
682
|
+
const matching = identityMap.get(senderEmail.toLowerCase());
|
|
683
|
+
if (matching) return matching;
|
|
684
|
+
return identityMap.values().next().value;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Gets all identities and builds a map of email to identity ID.
|
|
688
|
+
* @param session The JMAP session.
|
|
689
|
+
* @param accountId The account ID.
|
|
690
|
+
* @param signal Optional abort signal.
|
|
691
|
+
* @returns Map of lowercase email to identity ID.
|
|
692
|
+
* @since 0.4.0
|
|
693
|
+
*/
|
|
694
|
+
async getIdentityMap(session, accountId, signal) {
|
|
695
|
+
if (this.config.identityId) return new Map([["*", this.config.identityId]]);
|
|
611
696
|
const response = await this.httpClient.executeRequest(session.apiUrl, {
|
|
612
697
|
using: [JMAP_CAPABILITIES.core, JMAP_CAPABILITIES.submission],
|
|
613
698
|
methodCalls: [[
|
|
@@ -620,9 +705,9 @@ var JmapTransport = class {
|
|
|
620
705
|
if (!identityResponse) throw new JmapApiError("No Identity/get response received");
|
|
621
706
|
const identities = identityResponse[1].list;
|
|
622
707
|
if (!identities || identities.length === 0) throw new JmapApiError("No identities found");
|
|
623
|
-
const
|
|
624
|
-
|
|
625
|
-
return
|
|
708
|
+
const identityMap = /* @__PURE__ */ new Map();
|
|
709
|
+
for (const identity of identities) identityMap.set(identity.email.toLowerCase(), identity.id);
|
|
710
|
+
return identityMap;
|
|
626
711
|
}
|
|
627
712
|
/**
|
|
628
713
|
* Uploads all attachments and returns a map of contentId to blobId.
|
|
@@ -673,6 +758,43 @@ var JmapTransport = class {
|
|
|
673
758
|
errorMessages: errors
|
|
674
759
|
};
|
|
675
760
|
}
|
|
761
|
+
/**
|
|
762
|
+
* Parses the JMAP batch response to extract receipt for a specific index.
|
|
763
|
+
* @param response The JMAP response.
|
|
764
|
+
* @param index The message index in the batch.
|
|
765
|
+
* @returns A receipt indicating success or failure for that message.
|
|
766
|
+
* @since 0.4.0
|
|
767
|
+
*/
|
|
768
|
+
parseBatchResponseForIndex(response, index) {
|
|
769
|
+
const errors = [];
|
|
770
|
+
const draftKey = `draft${index}`;
|
|
771
|
+
const subKey = `sub${index}`;
|
|
772
|
+
const emailResponse = response.methodResponses.find((r) => r[0] === "Email/set");
|
|
773
|
+
if (emailResponse) {
|
|
774
|
+
const emailResult = emailResponse[1];
|
|
775
|
+
if (emailResult.notCreated?.[draftKey]) {
|
|
776
|
+
const error = emailResult.notCreated[draftKey];
|
|
777
|
+
errors.push(`Email creation failed: ${error.type}${error.description ? ` - ${error.description}` : ""}`);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
const submissionResponse = response.methodResponses.find((r) => r[0] === "EmailSubmission/set");
|
|
781
|
+
if (submissionResponse) {
|
|
782
|
+
const submissionResult = submissionResponse[1];
|
|
783
|
+
if (submissionResult.notCreated?.[subKey]) {
|
|
784
|
+
const error = submissionResult.notCreated[subKey];
|
|
785
|
+
errors.push(`Email submission failed: ${error.type}${error.description ? ` - ${error.description}` : ""}`);
|
|
786
|
+
}
|
|
787
|
+
if (submissionResult.created?.[subKey]) return {
|
|
788
|
+
successful: true,
|
|
789
|
+
messageId: submissionResult.created[subKey].id
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
if (errors.length === 0) errors.push("Unknown error: No submission result received");
|
|
793
|
+
return {
|
|
794
|
+
successful: false,
|
|
795
|
+
errorMessages: errors
|
|
796
|
+
};
|
|
797
|
+
}
|
|
676
798
|
};
|
|
677
799
|
|
|
678
800
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@upyo/jmap",
|
|
3
|
-
"version": "0.4.0-dev.
|
|
3
|
+
"version": "0.4.0-dev.75+4fff8d51",
|
|
4
4
|
"description": "JMAP transport for Upyo email library",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"email",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
"sideEffects": false,
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@upyo/core": "0.4.0-dev.
|
|
56
|
+
"@upyo/core": "0.4.0-dev.75+4fff8d51"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@dotenvx/dotenvx": "^1.47.3",
|