mail-time 3.0.0 → 4.0.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 +325 -609
- package/adapters/mongo.d.ts +118 -0
- package/adapters/mongo.js +199 -78
- package/adapters/postgres.d.ts +100 -0
- package/adapters/postgres.js +520 -0
- package/adapters/redis.d.ts +105 -0
- package/adapters/redis.js +342 -79
- package/helpers.js +209 -3
- package/index.cjs +2219 -446
- package/index.d.cts +300 -0
- package/index.d.ts +300 -0
- package/index.js +817 -305
- package/package.json +66 -17
- package/presets.d.ts +87 -0
- package/presets.js +193 -0
- package/adapters/blank-example.js +0 -230
package/helpers.js
CHANGED
|
@@ -1,11 +1,217 @@
|
|
|
1
|
+
const hasOwn = Object.prototype.hasOwnProperty;
|
|
2
|
+
|
|
1
3
|
const debug = (isDebug, ...args) => {
|
|
2
4
|
if (isDebug) {
|
|
3
|
-
console.info
|
|
5
|
+
console.info('[DEBUG] [mail-time]', `${new Date()}`, ...args);
|
|
4
6
|
}
|
|
5
7
|
};
|
|
6
8
|
|
|
7
9
|
const logError = (...args) => {
|
|
8
|
-
console.error
|
|
10
|
+
console.error('[ERROR] [mail-time]', `${new Date()}`, ...args);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const isPlainObject = (value) => {
|
|
14
|
+
if (value === null || typeof value !== 'object') {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const proto = Object.getPrototypeOf(value);
|
|
18
|
+
return proto === null || proto === Object.prototype;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Minimal deep-merge sufficient for nodemailer-shaped mail options:
|
|
23
|
+
* - plain objects merge key-by-key
|
|
24
|
+
* - arrays concatenate
|
|
25
|
+
* - other values (strings, numbers, Date, Buffer, streams, classes) replace
|
|
26
|
+
* Source values override target values.
|
|
27
|
+
*/
|
|
28
|
+
const deepMerge = (target, source) => {
|
|
29
|
+
if (!isPlainObject(source)) {
|
|
30
|
+
return target;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const out = isPlainObject(target) ? { ...target } : {};
|
|
34
|
+
|
|
35
|
+
for (const key of Object.keys(source)) {
|
|
36
|
+
const sVal = source[key];
|
|
37
|
+
const tVal = out[key];
|
|
38
|
+
|
|
39
|
+
if (Array.isArray(sVal)) {
|
|
40
|
+
out[key] = Array.isArray(tVal) ? tVal.concat(sVal) : sVal.slice();
|
|
41
|
+
} else if (isPlainObject(sVal)) {
|
|
42
|
+
out[key] = isPlainObject(tVal) ? deepMerge(tVal, sVal) : deepMerge({}, sVal);
|
|
43
|
+
} else {
|
|
44
|
+
out[key] = sVal;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return out;
|
|
9
49
|
};
|
|
10
50
|
|
|
11
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Order-insensitive deep equality. Treats arrays as multisets and
|
|
53
|
+
* objects as unordered maps. Designed for the small `mailOptions`
|
|
54
|
+
* shape used by MailTime's email concatenation dedup.
|
|
55
|
+
*/
|
|
56
|
+
const equals = (a, b) => {
|
|
57
|
+
if (a === b) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!a || !b || typeof a !== 'object' || typeof b !== 'object') {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (a instanceof Date && b instanceof Date) {
|
|
66
|
+
return a.valueOf() === b.valueOf();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (Array.isArray(a)) {
|
|
70
|
+
if (!Array.isArray(b) || a.length !== b.length) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const matched = new Array(b.length).fill(false);
|
|
75
|
+
for (let i = 0; i < a.length; i++) {
|
|
76
|
+
let found = false;
|
|
77
|
+
for (let j = 0; j < b.length; j++) {
|
|
78
|
+
if (!matched[j] && equals(a[i], b[j])) {
|
|
79
|
+
matched[j] = true;
|
|
80
|
+
found = true;
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (!found) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (Array.isArray(b)) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const aKeys = Object.keys(a);
|
|
96
|
+
const bKeys = Object.keys(b);
|
|
97
|
+
|
|
98
|
+
if (aKeys.length !== bKeys.length) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
for (let i = 0; i < aKeys.length; i++) {
|
|
103
|
+
const key = aKeys[i];
|
|
104
|
+
if (!hasOwn.call(b, key) || !equals(a[key], b[key])) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return true;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Extract the email part of a nodemailer-shaped recipient entry.
|
|
114
|
+
* Accepts `'a@x.com'`, `'Name <a@x.com>'`, or `{ name, address }`.
|
|
115
|
+
* Returns the address lowercased, or `null` when none can be parsed.
|
|
116
|
+
*/
|
|
117
|
+
const extractEmail = (entry) => {
|
|
118
|
+
if (!entry) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
if (typeof entry === 'object' && typeof entry.address === 'string') {
|
|
122
|
+
return entry.address.trim().toLowerCase();
|
|
123
|
+
}
|
|
124
|
+
if (typeof entry !== 'string') {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
const angled = entry.match(/<([^>]+)>/);
|
|
128
|
+
return (angled ? angled[1] : entry).trim().toLowerCase();
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Normalize a `to`/`cc`/`bcc` field into a flat list of lowercase addresses.
|
|
133
|
+
*/
|
|
134
|
+
const toAddressList = (field) => {
|
|
135
|
+
if (!field) {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
if (Array.isArray(field)) {
|
|
139
|
+
const out = [];
|
|
140
|
+
for (const entry of field) {
|
|
141
|
+
const addr = extractEmail(entry);
|
|
142
|
+
if (addr) {
|
|
143
|
+
out.push(addr);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
const single = extractEmail(field);
|
|
149
|
+
return single ? [single] : [];
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Remove entries whose extracted address is in `acceptedSet` from a
|
|
154
|
+
* nodemailer `to`/`cc`/`bcc` field. Returns `void 0` when the filtered
|
|
155
|
+
* array would be empty or the single string is dropped.
|
|
156
|
+
*/
|
|
157
|
+
const filterAddressField = (field, acceptedSet) => {
|
|
158
|
+
if (!field || !(acceptedSet instanceof Set) || acceptedSet.size === 0) {
|
|
159
|
+
return field;
|
|
160
|
+
}
|
|
161
|
+
if (Array.isArray(field)) {
|
|
162
|
+
const filtered = [];
|
|
163
|
+
for (const entry of field) {
|
|
164
|
+
const addr = extractEmail(entry);
|
|
165
|
+
if (!addr || !acceptedSet.has(addr)) {
|
|
166
|
+
filtered.push(entry);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return filtered.length ? filtered : void 0;
|
|
170
|
+
}
|
|
171
|
+
const addr = extractEmail(field);
|
|
172
|
+
if (addr && acceptedSet.has(addr)) {
|
|
173
|
+
return void 0;
|
|
174
|
+
}
|
|
175
|
+
return field;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const isSendClaimUpdate = (updateObj) => {
|
|
179
|
+
return updateObj && updateObj.isSending === true && typeof updateObj.tries === 'number';
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const isSendLeaseGuardedUpdate = (updateObj) => {
|
|
183
|
+
return updateObj && typeof updateObj.leaseTries === 'number' && typeof updateObj.leaseSendingAt === 'number';
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const isAppendMailOptionUpdate = (updateObj) => {
|
|
187
|
+
return updateObj && updateObj.appendMailOption !== void 0;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/** Strip MailTime-internal update keys before persisting to storage. */
|
|
191
|
+
const stripInternalUpdateMeta = (updateObj) => {
|
|
192
|
+
const out = { ...updateObj };
|
|
193
|
+
delete out.leaseTries;
|
|
194
|
+
delete out.leaseSendingAt;
|
|
195
|
+
delete out.appendMailOption;
|
|
196
|
+
return out;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const isSendLeaseRemove = (opts) => {
|
|
200
|
+
return opts && typeof opts.leaseTries === 'number' && typeof opts.leaseSendingAt === 'number';
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
export {
|
|
204
|
+
debug,
|
|
205
|
+
logError,
|
|
206
|
+
isPlainObject,
|
|
207
|
+
deepMerge,
|
|
208
|
+
equals,
|
|
209
|
+
extractEmail,
|
|
210
|
+
toAddressList,
|
|
211
|
+
filterAddressField,
|
|
212
|
+
isSendClaimUpdate,
|
|
213
|
+
isSendLeaseGuardedUpdate,
|
|
214
|
+
isAppendMailOptionUpdate,
|
|
215
|
+
stripInternalUpdateMeta,
|
|
216
|
+
isSendLeaseRemove,
|
|
217
|
+
};
|