@upyo/core 0.6.0-dev.309 → 0.6.0-dev.312
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/attachment.cjs +155 -2
- package/dist/attachment.d.cts +44 -5
- package/dist/attachment.d.ts +44 -5
- package/dist/attachment.js +154 -2
- package/dist/index.cjs +3 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/message.cjs +1 -1
- package/dist/message.js +1 -1
- package/package.json +1 -1
package/dist/attachment.cjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const require_abort_signal = require('./abort-signal.cjs');
|
|
1
2
|
|
|
2
3
|
//#region src/attachment.ts
|
|
3
4
|
/**
|
|
@@ -6,8 +7,160 @@
|
|
|
6
7
|
* @return `true` if the value is an {@link Attachment}, otherwise `false`.
|
|
7
8
|
*/
|
|
8
9
|
function isAttachment(attachment) {
|
|
9
|
-
return typeof attachment === "object" && attachment !== null && "inline" in attachment && "filename" in attachment && "content" in attachment && "contentType" in attachment && "contentId" in attachment && typeof attachment.inline === "boolean" && typeof attachment.filename === "string" && (attachment.content instanceof Uint8Array || attachment.content instanceof Promise && typeof attachment.content.then === "function") && typeof attachment.contentType === "string" && typeof attachment.contentId === "string";
|
|
10
|
+
return typeof attachment === "object" && attachment !== null && "inline" in attachment && "filename" in attachment && "content" in attachment && "contentType" in attachment && "contentId" in attachment && typeof attachment.inline === "boolean" && typeof attachment.filename === "string" && (attachment.content instanceof Uint8Array || typeof attachment.content === "function" || typeof Blob !== "undefined" && attachment.content instanceof Blob || attachment.content instanceof Promise && typeof attachment.content.then === "function") && typeof attachment.contentType === "string" && typeof attachment.contentId === "string";
|
|
11
|
+
}
|
|
12
|
+
function waitForContent(promise, signal) {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const abort = () => reject(signal.reason);
|
|
15
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
16
|
+
promise.then(resolve, reject).finally(() => {
|
|
17
|
+
signal.removeEventListener("abort", abort);
|
|
18
|
+
});
|
|
19
|
+
if (signal.aborted) abort();
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function blobIterator(blob) {
|
|
23
|
+
const reader = blob.stream().getReader();
|
|
24
|
+
let pending;
|
|
25
|
+
let released = false;
|
|
26
|
+
const release = () => {
|
|
27
|
+
if (!released) {
|
|
28
|
+
released = true;
|
|
29
|
+
reader.releaseLock();
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
return {
|
|
33
|
+
async next() {
|
|
34
|
+
pending = reader.read();
|
|
35
|
+
const result = await pending;
|
|
36
|
+
if (result.done) release();
|
|
37
|
+
return result.done ? {
|
|
38
|
+
done: true,
|
|
39
|
+
value: void 0
|
|
40
|
+
} : {
|
|
41
|
+
done: false,
|
|
42
|
+
value: result.value
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
async return() {
|
|
46
|
+
try {
|
|
47
|
+
await reader.cancel();
|
|
48
|
+
await pending;
|
|
49
|
+
} finally {
|
|
50
|
+
release();
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
done: true,
|
|
54
|
+
value: void 0
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Reads attachment chunks without prefetching. Early exit or cancellation
|
|
61
|
+
* aborts the source and requests cleanup without waiting for an uncooperative
|
|
62
|
+
* producer. Producers remain responsible for honoring cancellation.
|
|
63
|
+
* @param content Replayable content to read.
|
|
64
|
+
* @param signal Optional cancellation signal.
|
|
65
|
+
* @returns An iterable of byte chunks, valid until the next read.
|
|
66
|
+
* @throws {TypeError} If a source or yielded chunk is invalid.
|
|
67
|
+
* @throws {Error} If reading fails or cancellation is requested.
|
|
68
|
+
* @since 0.6.0
|
|
69
|
+
*/
|
|
70
|
+
async function* iterateAttachmentContent(content, signal) {
|
|
71
|
+
const controller = new AbortController();
|
|
72
|
+
const combined = require_abort_signal.combineSignals(controller.signal, signal);
|
|
73
|
+
const ownedSignal = combined.signal;
|
|
74
|
+
let iterator;
|
|
75
|
+
let returned = false;
|
|
76
|
+
let done = false;
|
|
77
|
+
const close = () => {
|
|
78
|
+
if (iterator == null || returned || done) return;
|
|
79
|
+
returned = true;
|
|
80
|
+
try {
|
|
81
|
+
Promise.resolve(iterator.return?.()).catch(() => {});
|
|
82
|
+
} catch {}
|
|
83
|
+
};
|
|
84
|
+
const abort = () => close();
|
|
85
|
+
ownedSignal.addEventListener("abort", abort, { once: true });
|
|
86
|
+
try {
|
|
87
|
+
if (content instanceof Promise) content.catch(() => {});
|
|
88
|
+
ownedSignal.throwIfAborted();
|
|
89
|
+
if (content instanceof Uint8Array || content instanceof Promise) {
|
|
90
|
+
const bytes = await waitForContent(Promise.resolve(content), ownedSignal);
|
|
91
|
+
ownedSignal.throwIfAborted();
|
|
92
|
+
if (!(bytes instanceof Uint8Array)) throw new TypeError("Expected attachment bytes to be a Uint8Array.");
|
|
93
|
+
yield bytes;
|
|
94
|
+
done = true;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (typeof Blob !== "undefined" && content instanceof Blob) iterator = blobIterator(content);
|
|
98
|
+
else if (typeof content === "function") {
|
|
99
|
+
const opening = Promise.resolve(content(ownedSignal)).then((source) => {
|
|
100
|
+
iterator = source[Symbol.asyncIterator]();
|
|
101
|
+
if (ownedSignal.aborted) close();
|
|
102
|
+
return iterator;
|
|
103
|
+
});
|
|
104
|
+
iterator = await waitForContent(opening, ownedSignal);
|
|
105
|
+
} else throw new TypeError("Expected replayable attachment content.");
|
|
106
|
+
let iterations = 0;
|
|
107
|
+
let bytesRead = 0;
|
|
108
|
+
while (true) {
|
|
109
|
+
ownedSignal.throwIfAborted();
|
|
110
|
+
const result = await waitForContent(Promise.resolve(iterator.next()), ownedSignal);
|
|
111
|
+
ownedSignal.throwIfAborted();
|
|
112
|
+
if (result.done) {
|
|
113
|
+
done = true;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
if (!(result.value instanceof Uint8Array)) throw new TypeError("Expected an attachment chunk to be a Uint8Array.");
|
|
117
|
+
yield result.value;
|
|
118
|
+
bytesRead += result.value.length;
|
|
119
|
+
if (++iterations >= 256 || bytesRead >= 1024 * 1024) {
|
|
120
|
+
await waitForContent(new Promise((resolve) => setTimeout(resolve, 0)), ownedSignal);
|
|
121
|
+
iterations = bytesRead = 0;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
} finally {
|
|
125
|
+
if (!done) {
|
|
126
|
+
controller.abort();
|
|
127
|
+
close();
|
|
128
|
+
}
|
|
129
|
+
ownedSignal.removeEventListener("abort", abort);
|
|
130
|
+
combined.cleanup();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Collects an attachment in memory. Existing arrays retain their identity;
|
|
135
|
+
* streamed chunks are copied before the producer can reuse their storage.
|
|
136
|
+
* @param content Replayable content to collect.
|
|
137
|
+
* @param signal Optional cancellation signal.
|
|
138
|
+
* @returns The complete attachment bytes.
|
|
139
|
+
* @throws {TypeError} If a source or yielded chunk is invalid.
|
|
140
|
+
* @throws {RangeError} If the attachment cannot fit in a byte array.
|
|
141
|
+
* @throws {Error} If reading fails or cancellation is requested.
|
|
142
|
+
* @since 0.6.0
|
|
143
|
+
*/
|
|
144
|
+
async function readAttachmentContent(content, signal) {
|
|
145
|
+
const chunks = [];
|
|
146
|
+
let length = 0;
|
|
147
|
+
for await (const chunk of iterateAttachmentContent(content, signal)) {
|
|
148
|
+
if (content instanceof Uint8Array || content instanceof Promise) return chunk;
|
|
149
|
+
if (chunk.length === 0) continue;
|
|
150
|
+
chunks.push(new Uint8Array(chunk));
|
|
151
|
+
length += chunk.length;
|
|
152
|
+
if (!Number.isSafeInteger(length)) throw new RangeError("Attachment size exceeds the safe integer range.");
|
|
153
|
+
}
|
|
154
|
+
const bytes = new Uint8Array(length);
|
|
155
|
+
let offset = 0;
|
|
156
|
+
for (const chunk of chunks) {
|
|
157
|
+
bytes.set(chunk, offset);
|
|
158
|
+
offset += chunk.length;
|
|
159
|
+
}
|
|
160
|
+
return bytes;
|
|
10
161
|
}
|
|
11
162
|
|
|
12
163
|
//#endregion
|
|
13
|
-
exports.isAttachment = isAttachment;
|
|
164
|
+
exports.isAttachment = isAttachment;
|
|
165
|
+
exports.iterateAttachmentContent = iterateAttachmentContent;
|
|
166
|
+
exports.readAttachmentContent = readAttachmentContent;
|
package/dist/attachment.d.cts
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
//#region src/attachment.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Opens a fresh, independent attachment reader on every invocation. Readers
|
|
4
|
+
* must produce identical bytes for retries, concurrent sends, and DKIM passes.
|
|
5
|
+
* A yielded chunk must remain valid until the next read. Factories should honor
|
|
6
|
+
* the signal during acquisition and release resources when iteration ends.
|
|
7
|
+
* @param signal Cancellation signal owned by the reader.
|
|
8
|
+
* @returns A new iterable, optionally acquired asynchronously.
|
|
9
|
+
* @since 0.6.0
|
|
10
|
+
*/
|
|
11
|
+
type AttachmentContentFactory = (signal?: AbortSignal) => AsyncIterable<Uint8Array> | Promise<AsyncIterable<Uint8Array>>;
|
|
12
|
+
/**
|
|
13
|
+
* Replayable attachment bytes. One-shot iterables are intentionally excluded;
|
|
14
|
+
* wrap them in a factory that opens a new reader for each invocation.
|
|
15
|
+
* @since 0.6.0
|
|
16
|
+
*/
|
|
17
|
+
type AttachmentContent = Uint8Array | Promise<Uint8Array> | Blob | AttachmentContentFactory;
|
|
2
18
|
/**
|
|
3
19
|
* Represents an attachment in an email message.
|
|
4
20
|
*/
|
|
@@ -13,11 +29,10 @@ interface Attachment {
|
|
|
13
29
|
*/
|
|
14
30
|
readonly filename: string;
|
|
15
31
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* of the attachment content.
|
|
32
|
+
* Replayable attachment content. Use {@link readAttachmentContent} to collect
|
|
33
|
+
* bytes or {@link iterateAttachmentContent} to read them incrementally.
|
|
19
34
|
*/
|
|
20
|
-
readonly content:
|
|
35
|
+
readonly content: AttachmentContent;
|
|
21
36
|
/**
|
|
22
37
|
* The media type of the attachment, which indicates the type of content
|
|
23
38
|
* and how it should be handled by email clients.
|
|
@@ -35,5 +50,29 @@ interface Attachment {
|
|
|
35
50
|
* @return `true` if the value is an {@link Attachment}, otherwise `false`.
|
|
36
51
|
*/
|
|
37
52
|
declare function isAttachment(attachment: unknown): attachment is Attachment;
|
|
53
|
+
/**
|
|
54
|
+
* Reads attachment chunks without prefetching. Early exit or cancellation
|
|
55
|
+
* aborts the source and requests cleanup without waiting for an uncooperative
|
|
56
|
+
* producer. Producers remain responsible for honoring cancellation.
|
|
57
|
+
* @param content Replayable content to read.
|
|
58
|
+
* @param signal Optional cancellation signal.
|
|
59
|
+
* @returns An iterable of byte chunks, valid until the next read.
|
|
60
|
+
* @throws {TypeError} If a source or yielded chunk is invalid.
|
|
61
|
+
* @throws {Error} If reading fails or cancellation is requested.
|
|
62
|
+
* @since 0.6.0
|
|
63
|
+
*/
|
|
64
|
+
declare function iterateAttachmentContent(content: AttachmentContent, signal?: AbortSignal): AsyncIterable<Uint8Array>;
|
|
65
|
+
/**
|
|
66
|
+
* Collects an attachment in memory. Existing arrays retain their identity;
|
|
67
|
+
* streamed chunks are copied before the producer can reuse their storage.
|
|
68
|
+
* @param content Replayable content to collect.
|
|
69
|
+
* @param signal Optional cancellation signal.
|
|
70
|
+
* @returns The complete attachment bytes.
|
|
71
|
+
* @throws {TypeError} If a source or yielded chunk is invalid.
|
|
72
|
+
* @throws {RangeError} If the attachment cannot fit in a byte array.
|
|
73
|
+
* @throws {Error} If reading fails or cancellation is requested.
|
|
74
|
+
* @since 0.6.0
|
|
75
|
+
*/
|
|
76
|
+
declare function readAttachmentContent(content: AttachmentContent, signal?: AbortSignal): Promise<Uint8Array>;
|
|
38
77
|
//#endregion
|
|
39
|
-
export { Attachment, isAttachment };
|
|
78
|
+
export { Attachment, AttachmentContent, AttachmentContentFactory, isAttachment, iterateAttachmentContent, readAttachmentContent };
|
package/dist/attachment.d.ts
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
//#region src/attachment.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Opens a fresh, independent attachment reader on every invocation. Readers
|
|
4
|
+
* must produce identical bytes for retries, concurrent sends, and DKIM passes.
|
|
5
|
+
* A yielded chunk must remain valid until the next read. Factories should honor
|
|
6
|
+
* the signal during acquisition and release resources when iteration ends.
|
|
7
|
+
* @param signal Cancellation signal owned by the reader.
|
|
8
|
+
* @returns A new iterable, optionally acquired asynchronously.
|
|
9
|
+
* @since 0.6.0
|
|
10
|
+
*/
|
|
11
|
+
type AttachmentContentFactory = (signal?: AbortSignal) => AsyncIterable<Uint8Array> | Promise<AsyncIterable<Uint8Array>>;
|
|
12
|
+
/**
|
|
13
|
+
* Replayable attachment bytes. One-shot iterables are intentionally excluded;
|
|
14
|
+
* wrap them in a factory that opens a new reader for each invocation.
|
|
15
|
+
* @since 0.6.0
|
|
16
|
+
*/
|
|
17
|
+
type AttachmentContent = Uint8Array | Promise<Uint8Array> | Blob | AttachmentContentFactory;
|
|
2
18
|
/**
|
|
3
19
|
* Represents an attachment in an email message.
|
|
4
20
|
*/
|
|
@@ -13,11 +29,10 @@ interface Attachment {
|
|
|
13
29
|
*/
|
|
14
30
|
readonly filename: string;
|
|
15
31
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* of the attachment content.
|
|
32
|
+
* Replayable attachment content. Use {@link readAttachmentContent} to collect
|
|
33
|
+
* bytes or {@link iterateAttachmentContent} to read them incrementally.
|
|
19
34
|
*/
|
|
20
|
-
readonly content:
|
|
35
|
+
readonly content: AttachmentContent;
|
|
21
36
|
/**
|
|
22
37
|
* The media type of the attachment, which indicates the type of content
|
|
23
38
|
* and how it should be handled by email clients.
|
|
@@ -35,5 +50,29 @@ interface Attachment {
|
|
|
35
50
|
* @return `true` if the value is an {@link Attachment}, otherwise `false`.
|
|
36
51
|
*/
|
|
37
52
|
declare function isAttachment(attachment: unknown): attachment is Attachment;
|
|
53
|
+
/**
|
|
54
|
+
* Reads attachment chunks without prefetching. Early exit or cancellation
|
|
55
|
+
* aborts the source and requests cleanup without waiting for an uncooperative
|
|
56
|
+
* producer. Producers remain responsible for honoring cancellation.
|
|
57
|
+
* @param content Replayable content to read.
|
|
58
|
+
* @param signal Optional cancellation signal.
|
|
59
|
+
* @returns An iterable of byte chunks, valid until the next read.
|
|
60
|
+
* @throws {TypeError} If a source or yielded chunk is invalid.
|
|
61
|
+
* @throws {Error} If reading fails or cancellation is requested.
|
|
62
|
+
* @since 0.6.0
|
|
63
|
+
*/
|
|
64
|
+
declare function iterateAttachmentContent(content: AttachmentContent, signal?: AbortSignal): AsyncIterable<Uint8Array>;
|
|
65
|
+
/**
|
|
66
|
+
* Collects an attachment in memory. Existing arrays retain their identity;
|
|
67
|
+
* streamed chunks are copied before the producer can reuse their storage.
|
|
68
|
+
* @param content Replayable content to collect.
|
|
69
|
+
* @param signal Optional cancellation signal.
|
|
70
|
+
* @returns The complete attachment bytes.
|
|
71
|
+
* @throws {TypeError} If a source or yielded chunk is invalid.
|
|
72
|
+
* @throws {RangeError} If the attachment cannot fit in a byte array.
|
|
73
|
+
* @throws {Error} If reading fails or cancellation is requested.
|
|
74
|
+
* @since 0.6.0
|
|
75
|
+
*/
|
|
76
|
+
declare function readAttachmentContent(content: AttachmentContent, signal?: AbortSignal): Promise<Uint8Array>;
|
|
38
77
|
//#endregion
|
|
39
|
-
export { Attachment, isAttachment };
|
|
78
|
+
export { Attachment, AttachmentContent, AttachmentContentFactory, isAttachment, iterateAttachmentContent, readAttachmentContent };
|
package/dist/attachment.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { combineSignals } from "./abort-signal.js";
|
|
2
|
+
|
|
1
3
|
//#region src/attachment.ts
|
|
2
4
|
/**
|
|
3
5
|
* Checks if the provided value is an {@link Attachment} object.
|
|
@@ -5,8 +7,158 @@
|
|
|
5
7
|
* @return `true` if the value is an {@link Attachment}, otherwise `false`.
|
|
6
8
|
*/
|
|
7
9
|
function isAttachment(attachment) {
|
|
8
|
-
return typeof attachment === "object" && attachment !== null && "inline" in attachment && "filename" in attachment && "content" in attachment && "contentType" in attachment && "contentId" in attachment && typeof attachment.inline === "boolean" && typeof attachment.filename === "string" && (attachment.content instanceof Uint8Array || attachment.content instanceof Promise && typeof attachment.content.then === "function") && typeof attachment.contentType === "string" && typeof attachment.contentId === "string";
|
|
10
|
+
return typeof attachment === "object" && attachment !== null && "inline" in attachment && "filename" in attachment && "content" in attachment && "contentType" in attachment && "contentId" in attachment && typeof attachment.inline === "boolean" && typeof attachment.filename === "string" && (attachment.content instanceof Uint8Array || typeof attachment.content === "function" || typeof Blob !== "undefined" && attachment.content instanceof Blob || attachment.content instanceof Promise && typeof attachment.content.then === "function") && typeof attachment.contentType === "string" && typeof attachment.contentId === "string";
|
|
11
|
+
}
|
|
12
|
+
function waitForContent(promise, signal) {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const abort = () => reject(signal.reason);
|
|
15
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
16
|
+
promise.then(resolve, reject).finally(() => {
|
|
17
|
+
signal.removeEventListener("abort", abort);
|
|
18
|
+
});
|
|
19
|
+
if (signal.aborted) abort();
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function blobIterator(blob) {
|
|
23
|
+
const reader = blob.stream().getReader();
|
|
24
|
+
let pending;
|
|
25
|
+
let released = false;
|
|
26
|
+
const release = () => {
|
|
27
|
+
if (!released) {
|
|
28
|
+
released = true;
|
|
29
|
+
reader.releaseLock();
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
return {
|
|
33
|
+
async next() {
|
|
34
|
+
pending = reader.read();
|
|
35
|
+
const result = await pending;
|
|
36
|
+
if (result.done) release();
|
|
37
|
+
return result.done ? {
|
|
38
|
+
done: true,
|
|
39
|
+
value: void 0
|
|
40
|
+
} : {
|
|
41
|
+
done: false,
|
|
42
|
+
value: result.value
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
async return() {
|
|
46
|
+
try {
|
|
47
|
+
await reader.cancel();
|
|
48
|
+
await pending;
|
|
49
|
+
} finally {
|
|
50
|
+
release();
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
done: true,
|
|
54
|
+
value: void 0
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Reads attachment chunks without prefetching. Early exit or cancellation
|
|
61
|
+
* aborts the source and requests cleanup without waiting for an uncooperative
|
|
62
|
+
* producer. Producers remain responsible for honoring cancellation.
|
|
63
|
+
* @param content Replayable content to read.
|
|
64
|
+
* @param signal Optional cancellation signal.
|
|
65
|
+
* @returns An iterable of byte chunks, valid until the next read.
|
|
66
|
+
* @throws {TypeError} If a source or yielded chunk is invalid.
|
|
67
|
+
* @throws {Error} If reading fails or cancellation is requested.
|
|
68
|
+
* @since 0.6.0
|
|
69
|
+
*/
|
|
70
|
+
async function* iterateAttachmentContent(content, signal) {
|
|
71
|
+
const controller = new AbortController();
|
|
72
|
+
const combined = combineSignals(controller.signal, signal);
|
|
73
|
+
const ownedSignal = combined.signal;
|
|
74
|
+
let iterator;
|
|
75
|
+
let returned = false;
|
|
76
|
+
let done = false;
|
|
77
|
+
const close = () => {
|
|
78
|
+
if (iterator == null || returned || done) return;
|
|
79
|
+
returned = true;
|
|
80
|
+
try {
|
|
81
|
+
Promise.resolve(iterator.return?.()).catch(() => {});
|
|
82
|
+
} catch {}
|
|
83
|
+
};
|
|
84
|
+
const abort = () => close();
|
|
85
|
+
ownedSignal.addEventListener("abort", abort, { once: true });
|
|
86
|
+
try {
|
|
87
|
+
if (content instanceof Promise) content.catch(() => {});
|
|
88
|
+
ownedSignal.throwIfAborted();
|
|
89
|
+
if (content instanceof Uint8Array || content instanceof Promise) {
|
|
90
|
+
const bytes = await waitForContent(Promise.resolve(content), ownedSignal);
|
|
91
|
+
ownedSignal.throwIfAborted();
|
|
92
|
+
if (!(bytes instanceof Uint8Array)) throw new TypeError("Expected attachment bytes to be a Uint8Array.");
|
|
93
|
+
yield bytes;
|
|
94
|
+
done = true;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (typeof Blob !== "undefined" && content instanceof Blob) iterator = blobIterator(content);
|
|
98
|
+
else if (typeof content === "function") {
|
|
99
|
+
const opening = Promise.resolve(content(ownedSignal)).then((source) => {
|
|
100
|
+
iterator = source[Symbol.asyncIterator]();
|
|
101
|
+
if (ownedSignal.aborted) close();
|
|
102
|
+
return iterator;
|
|
103
|
+
});
|
|
104
|
+
iterator = await waitForContent(opening, ownedSignal);
|
|
105
|
+
} else throw new TypeError("Expected replayable attachment content.");
|
|
106
|
+
let iterations = 0;
|
|
107
|
+
let bytesRead = 0;
|
|
108
|
+
while (true) {
|
|
109
|
+
ownedSignal.throwIfAborted();
|
|
110
|
+
const result = await waitForContent(Promise.resolve(iterator.next()), ownedSignal);
|
|
111
|
+
ownedSignal.throwIfAborted();
|
|
112
|
+
if (result.done) {
|
|
113
|
+
done = true;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
if (!(result.value instanceof Uint8Array)) throw new TypeError("Expected an attachment chunk to be a Uint8Array.");
|
|
117
|
+
yield result.value;
|
|
118
|
+
bytesRead += result.value.length;
|
|
119
|
+
if (++iterations >= 256 || bytesRead >= 1024 * 1024) {
|
|
120
|
+
await waitForContent(new Promise((resolve) => setTimeout(resolve, 0)), ownedSignal);
|
|
121
|
+
iterations = bytesRead = 0;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
} finally {
|
|
125
|
+
if (!done) {
|
|
126
|
+
controller.abort();
|
|
127
|
+
close();
|
|
128
|
+
}
|
|
129
|
+
ownedSignal.removeEventListener("abort", abort);
|
|
130
|
+
combined.cleanup();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Collects an attachment in memory. Existing arrays retain their identity;
|
|
135
|
+
* streamed chunks are copied before the producer can reuse their storage.
|
|
136
|
+
* @param content Replayable content to collect.
|
|
137
|
+
* @param signal Optional cancellation signal.
|
|
138
|
+
* @returns The complete attachment bytes.
|
|
139
|
+
* @throws {TypeError} If a source or yielded chunk is invalid.
|
|
140
|
+
* @throws {RangeError} If the attachment cannot fit in a byte array.
|
|
141
|
+
* @throws {Error} If reading fails or cancellation is requested.
|
|
142
|
+
* @since 0.6.0
|
|
143
|
+
*/
|
|
144
|
+
async function readAttachmentContent(content, signal) {
|
|
145
|
+
const chunks = [];
|
|
146
|
+
let length = 0;
|
|
147
|
+
for await (const chunk of iterateAttachmentContent(content, signal)) {
|
|
148
|
+
if (content instanceof Uint8Array || content instanceof Promise) return chunk;
|
|
149
|
+
if (chunk.length === 0) continue;
|
|
150
|
+
chunks.push(new Uint8Array(chunk));
|
|
151
|
+
length += chunk.length;
|
|
152
|
+
if (!Number.isSafeInteger(length)) throw new RangeError("Attachment size exceeds the safe integer range.");
|
|
153
|
+
}
|
|
154
|
+
const bytes = new Uint8Array(length);
|
|
155
|
+
let offset = 0;
|
|
156
|
+
for (const chunk of chunks) {
|
|
157
|
+
bytes.set(chunk, offset);
|
|
158
|
+
offset += chunk.length;
|
|
159
|
+
}
|
|
160
|
+
return bytes;
|
|
9
161
|
}
|
|
10
162
|
|
|
11
163
|
//#endregion
|
|
12
|
-
export { isAttachment };
|
|
164
|
+
export { isAttachment, iterateAttachmentContent, readAttachmentContent };
|
package/dist/index.cjs
CHANGED
|
@@ -15,5 +15,7 @@ exports.createReceiptError = require_receipt.createReceiptError;
|
|
|
15
15
|
exports.formatAddress = require_address.formatAddress;
|
|
16
16
|
exports.isAttachment = require_attachment.isAttachment;
|
|
17
17
|
exports.isEmailAddress = require_address.isEmailAddress;
|
|
18
|
+
exports.iterateAttachmentContent = require_attachment.iterateAttachmentContent;
|
|
18
19
|
exports.parseAddress = require_address.parseAddress;
|
|
19
|
-
exports.parseRetryAfter = require_receipt.parseRetryAfter;
|
|
20
|
+
exports.parseRetryAfter = require_receipt.parseRetryAfter;
|
|
21
|
+
exports.readAttachmentContent = require_attachment.readAttachmentContent;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { CombinedSignal, combineSignals } from "./abort-signal.cjs";
|
|
2
2
|
import { Address, EmailAddress, formatAddress, isEmailAddress, parseAddress } from "./address.cjs";
|
|
3
|
-
import { Attachment, isAttachment } from "./attachment.cjs";
|
|
3
|
+
import { Attachment, AttachmentContent, AttachmentContentFactory, isAttachment, iterateAttachmentContent, readAttachmentContent } from "./attachment.cjs";
|
|
4
4
|
import { Priority, comparePriority } from "./priority.cjs";
|
|
5
5
|
import { ImmutableHeaders, Message, MessageConstructor, MessageContent, createMessage } from "./message.cjs";
|
|
6
6
|
import { CreateFailedReceiptOptions, CreateReceiptErrorOptions, Receipt, ReceiptError, ReceiptErrorCategory, ReceiptErrorClassification, classifyHttpStatus, classifyReceiptError, createFailedReceipt, createReceiptError, parseRetryAfter } from "./receipt.cjs";
|
|
7
7
|
import { Transport, TransportOptions } from "./transport.cjs";
|
|
8
|
-
export { Address, Attachment, CombinedSignal, CreateFailedReceiptOptions, CreateReceiptErrorOptions, EmailAddress, ImmutableHeaders, Message, MessageConstructor, MessageContent, Priority, Receipt, ReceiptError, ReceiptErrorCategory, ReceiptErrorClassification, Transport, TransportOptions, classifyHttpStatus, classifyReceiptError, combineSignals, comparePriority, createFailedReceipt, createMessage, createReceiptError, formatAddress, isAttachment, isEmailAddress, parseAddress, parseRetryAfter };
|
|
8
|
+
export { Address, Attachment, AttachmentContent, AttachmentContentFactory, CombinedSignal, CreateFailedReceiptOptions, CreateReceiptErrorOptions, EmailAddress, ImmutableHeaders, Message, MessageConstructor, MessageContent, Priority, Receipt, ReceiptError, ReceiptErrorCategory, ReceiptErrorClassification, Transport, TransportOptions, classifyHttpStatus, classifyReceiptError, combineSignals, comparePriority, createFailedReceipt, createMessage, createReceiptError, formatAddress, isAttachment, isEmailAddress, iterateAttachmentContent, parseAddress, parseRetryAfter, readAttachmentContent };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { CombinedSignal, combineSignals } from "./abort-signal.js";
|
|
2
2
|
import { Address, EmailAddress, formatAddress, isEmailAddress, parseAddress } from "./address.js";
|
|
3
|
-
import { Attachment, isAttachment } from "./attachment.js";
|
|
3
|
+
import { Attachment, AttachmentContent, AttachmentContentFactory, isAttachment, iterateAttachmentContent, readAttachmentContent } from "./attachment.js";
|
|
4
4
|
import { Priority, comparePriority } from "./priority.js";
|
|
5
5
|
import { ImmutableHeaders, Message, MessageConstructor, MessageContent, createMessage } from "./message.js";
|
|
6
6
|
import { CreateFailedReceiptOptions, CreateReceiptErrorOptions, Receipt, ReceiptError, ReceiptErrorCategory, ReceiptErrorClassification, classifyHttpStatus, classifyReceiptError, createFailedReceipt, createReceiptError, parseRetryAfter } from "./receipt.js";
|
|
7
7
|
import { Transport, TransportOptions } from "./transport.js";
|
|
8
|
-
export { Address, Attachment, CombinedSignal, CreateFailedReceiptOptions, CreateReceiptErrorOptions, EmailAddress, ImmutableHeaders, Message, MessageConstructor, MessageContent, Priority, Receipt, ReceiptError, ReceiptErrorCategory, ReceiptErrorClassification, Transport, TransportOptions, classifyHttpStatus, classifyReceiptError, combineSignals, comparePriority, createFailedReceipt, createMessage, createReceiptError, formatAddress, isAttachment, isEmailAddress, parseAddress, parseRetryAfter };
|
|
8
|
+
export { Address, Attachment, AttachmentContent, AttachmentContentFactory, CombinedSignal, CreateFailedReceiptOptions, CreateReceiptErrorOptions, EmailAddress, ImmutableHeaders, Message, MessageConstructor, MessageContent, Priority, Receipt, ReceiptError, ReceiptErrorCategory, ReceiptErrorClassification, Transport, TransportOptions, classifyHttpStatus, classifyReceiptError, combineSignals, comparePriority, createFailedReceipt, createMessage, createReceiptError, formatAddress, isAttachment, isEmailAddress, iterateAttachmentContent, parseAddress, parseRetryAfter, readAttachmentContent };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { combineSignals } from "./abort-signal.js";
|
|
2
2
|
import { formatAddress, isEmailAddress, parseAddress } from "./address.js";
|
|
3
|
-
import { isAttachment } from "./attachment.js";
|
|
3
|
+
import { isAttachment, iterateAttachmentContent, readAttachmentContent } from "./attachment.js";
|
|
4
4
|
import { createMessage } from "./message.js";
|
|
5
5
|
import { comparePriority } from "./priority.js";
|
|
6
6
|
import { classifyHttpStatus, classifyReceiptError, createFailedReceipt, createReceiptError, parseRetryAfter } from "./receipt.js";
|
|
7
7
|
|
|
8
|
-
export { classifyHttpStatus, classifyReceiptError, combineSignals, comparePriority, createFailedReceipt, createMessage, createReceiptError, formatAddress, isAttachment, isEmailAddress, parseAddress, parseRetryAfter };
|
|
8
|
+
export { classifyHttpStatus, classifyReceiptError, combineSignals, comparePriority, createFailedReceipt, createMessage, createReceiptError, formatAddress, isAttachment, isEmailAddress, iterateAttachmentContent, parseAddress, parseRetryAfter, readAttachmentContent };
|
package/dist/message.cjs
CHANGED
|
@@ -37,7 +37,7 @@ function createMessage(constructor) {
|
|
|
37
37
|
if (attachment instanceof File) return {
|
|
38
38
|
inline: false,
|
|
39
39
|
filename: attachment.name,
|
|
40
|
-
content: attachment
|
|
40
|
+
content: attachment,
|
|
41
41
|
contentType: attachment.type == null || attachment.type === "" ? "application/octet-stream" : attachment.type,
|
|
42
42
|
contentId: `${crypto.randomUUID()}@${sender.address.replace(/^[^@]*@/, "")}`
|
|
43
43
|
};
|
package/dist/message.js
CHANGED
|
@@ -37,7 +37,7 @@ function createMessage(constructor) {
|
|
|
37
37
|
if (attachment instanceof File) return {
|
|
38
38
|
inline: false,
|
|
39
39
|
filename: attachment.name,
|
|
40
|
-
content: attachment
|
|
40
|
+
content: attachment,
|
|
41
41
|
contentType: attachment.type == null || attachment.type === "" ? "application/octet-stream" : attachment.type,
|
|
42
42
|
contentId: `${crypto.randomUUID()}@${sender.address.replace(/^[^@]*@/, "")}`
|
|
43
43
|
};
|