mail-time 1.4.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 +367 -301
- package/adapters/mongo.d.ts +118 -0
- package/adapters/mongo.js +431 -0
- package/adapters/postgres.d.ts +100 -0
- package/adapters/postgres.js +520 -0
- package/adapters/redis.d.ts +105 -0
- package/adapters/redis.js +565 -0
- package/helpers.js +217 -0
- package/index.cjs +2862 -462
- package/index.d.cts +300 -0
- package/index.d.ts +300 -0
- package/index.js +1002 -475
- package/package.json +66 -16
- package/presets.d.ts +87 -0
- package/presets.js +193 -0
package/package.json
CHANGED
|
@@ -1,22 +1,64 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mail-time",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
|
+
"description": "📮 Email queue extending nodemailer with multi-SMTP transports and horizontally scaled apps support",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"types": "./index.d.ts",
|
|
6
7
|
"type": "module",
|
|
7
8
|
"exports": {
|
|
8
|
-
"
|
|
9
|
-
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./index.d.ts",
|
|
12
|
+
"default": "./index.js"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./index.d.cts",
|
|
16
|
+
"default": "./index.cjs"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"./presets": {
|
|
20
|
+
"import": {
|
|
21
|
+
"types": "./presets.d.ts",
|
|
22
|
+
"default": "./presets.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"./adapters/mongo": {
|
|
26
|
+
"import": {
|
|
27
|
+
"types": "./adapters/mongo.d.ts",
|
|
28
|
+
"default": "./adapters/mongo.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"./adapters/redis": {
|
|
32
|
+
"import": {
|
|
33
|
+
"types": "./adapters/redis.d.ts",
|
|
34
|
+
"default": "./adapters/redis.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"./adapters/postgres": {
|
|
38
|
+
"import": {
|
|
39
|
+
"types": "./adapters/postgres.d.ts",
|
|
40
|
+
"default": "./adapters/postgres.js"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"./package.json": "./package.json"
|
|
10
44
|
},
|
|
11
45
|
"scripts": {
|
|
12
|
-
"prepublishOnly": "rollup --config rollup.config.js",
|
|
13
|
-
"test": "mocha
|
|
46
|
+
"prepublishOnly": "rollup --config rollup.config.js && rm -rf ./.types-tmp && tsc --emitDeclarationOnly --declaration --outDir ./.types-tmp --rootDir ./ && node ./scripts/strip-internal-dts.mjs ./.types-tmp/index.d.ts ./.types-tmp/presets.d.ts ./.types-tmp/adapters/mongo.d.ts ./.types-tmp/adapters/redis.d.ts ./.types-tmp/adapters/postgres.d.ts && cp ./.types-tmp/index.d.ts ./index.d.ts && cp ./.types-tmp/index.d.ts ./index.d.cts && cp ./.types-tmp/presets.d.ts ./presets.d.ts && cp ./.types-tmp/adapters/*.d.ts ./adapters/ && rm -rf ./.types-tmp",
|
|
47
|
+
"test": "npm run test:jest && npm run test:mocha && npm run test:types",
|
|
48
|
+
"test:jest": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --config jest.config.mjs",
|
|
49
|
+
"test:mocha": "mocha ./test/npm.js",
|
|
50
|
+
"test:types": "tsc -p ./tsconfig.types.json",
|
|
51
|
+
"test:redis": "mocha ./test/npm-redis-redis.js",
|
|
52
|
+
"test:mongo": "mocha ./test/npm-mongo-mongo.js",
|
|
53
|
+
"test:postgres": "mocha ./test/npm-postgres.js",
|
|
54
|
+
"test:bun": "bun test ./test/jest"
|
|
14
55
|
},
|
|
15
56
|
"keywords": [
|
|
16
57
|
"backup",
|
|
17
58
|
"balanced",
|
|
18
59
|
"balancer",
|
|
19
60
|
"bulletproof",
|
|
61
|
+
"bun",
|
|
20
62
|
"cluster",
|
|
21
63
|
"email",
|
|
22
64
|
"highload",
|
|
@@ -29,7 +71,9 @@
|
|
|
29
71
|
"microservice",
|
|
30
72
|
"micro-service",
|
|
31
73
|
"nodemailer",
|
|
74
|
+
"postgres",
|
|
32
75
|
"queue",
|
|
76
|
+
"scheduler",
|
|
33
77
|
"template"
|
|
34
78
|
],
|
|
35
79
|
"tags": [
|
|
@@ -37,6 +81,7 @@
|
|
|
37
81
|
"balanced",
|
|
38
82
|
"balancer",
|
|
39
83
|
"bulletproof",
|
|
84
|
+
"bun",
|
|
40
85
|
"cluster",
|
|
41
86
|
"email",
|
|
42
87
|
"highload",
|
|
@@ -49,7 +94,9 @@
|
|
|
49
94
|
"microservice",
|
|
50
95
|
"micro-service",
|
|
51
96
|
"nodemailer",
|
|
97
|
+
"postgres",
|
|
52
98
|
"queue",
|
|
99
|
+
"scheduler",
|
|
53
100
|
"template"
|
|
54
101
|
],
|
|
55
102
|
"repository": {
|
|
@@ -63,19 +110,22 @@
|
|
|
63
110
|
"author": "dr.dimitru (https://veliovgroup.com)",
|
|
64
111
|
"license": "BSD-3-Clause",
|
|
65
112
|
"engines": {
|
|
66
|
-
"node": ">=
|
|
113
|
+
"node": ">=20.9.0"
|
|
67
114
|
},
|
|
68
115
|
"dependencies": {
|
|
69
|
-
"
|
|
70
|
-
"josk": "^3.1.1"
|
|
116
|
+
"josk": "^6.1.0"
|
|
71
117
|
},
|
|
72
118
|
"devDependencies": {
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"chai": "^
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"nodemailer
|
|
119
|
+
"@jest/globals": "^30.4.1",
|
|
120
|
+
"@types/node": "^25.9.0",
|
|
121
|
+
"chai": "^6.2.2",
|
|
122
|
+
"jest": "^30.4.2",
|
|
123
|
+
"mocha": "^11.7.5",
|
|
124
|
+
"mongodb": "^7.2.0",
|
|
125
|
+
"nodemailer": "^8.0.7",
|
|
126
|
+
"pg": "^8.21.0",
|
|
127
|
+
"redis": "^5.12.1",
|
|
128
|
+
"rollup": "^4.60.4",
|
|
129
|
+
"typescript": "^6.0.3"
|
|
80
130
|
}
|
|
81
131
|
}
|
package/presets.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export type MailTimePresetConfig = {
|
|
2
|
+
concatEmails?: boolean | undefined;
|
|
3
|
+
concatDelay?: number | undefined;
|
|
4
|
+
concatSubject?: string | undefined;
|
|
5
|
+
retries?: number | undefined;
|
|
6
|
+
retryDelay?: number | undefined;
|
|
7
|
+
revolvingInterval?: number | undefined;
|
|
8
|
+
sendingTimeout?: number | undefined;
|
|
9
|
+
mode?: "one" | "batch" | undefined;
|
|
10
|
+
concurrency?: number | undefined;
|
|
11
|
+
onError?: ((error: unknown, email: object, details?: object) => void) | undefined;
|
|
12
|
+
josk?: object | undefined;
|
|
13
|
+
};
|
|
14
|
+
export type MailTimePresetName = keyof typeof PRESETS;
|
|
15
|
+
/**
|
|
16
|
+
* Materialize a MailTime constructor config from a built-in preset.
|
|
17
|
+
* The named preset is deep-cloned (so the result is freely mutable) and
|
|
18
|
+
* `overrides` is deep-merged on top — overrides win for scalar keys, and
|
|
19
|
+
* nested objects like `josk` are merged so the preset's defaults compose
|
|
20
|
+
* with the caller's `adapter`, `lockOwnerId`, `onError`, etc.
|
|
21
|
+
*
|
|
22
|
+
* ```js
|
|
23
|
+
* import { MailTime, RedisQueue, mailTimePreset } from 'mail-time';
|
|
24
|
+
* const mailTime = new MailTime(mailTimePreset('otp', {
|
|
25
|
+
* prefix: 'otp',
|
|
26
|
+
* queue: new RedisQueue({ client }),
|
|
27
|
+
* transports: [otpTransport],
|
|
28
|
+
* josk: { adapter: { type: 'redis', client } },
|
|
29
|
+
* }));
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @param {MailTimePresetName} name - one of `presetNames`
|
|
33
|
+
* @param {object} [overrides] - additional MailTime constructor options
|
|
34
|
+
* @returns {object} fresh, mutable MailTime constructor options
|
|
35
|
+
* @throws {Error} when `name` is unknown
|
|
36
|
+
* @throws {TypeError} when `overrides` is provided but not a plain object
|
|
37
|
+
*/
|
|
38
|
+
export function mailTimePreset(name: MailTimePresetName, overrides?: object): object;
|
|
39
|
+
/**
|
|
40
|
+
* Read-only map of preset name → partial MailTime config. Use
|
|
41
|
+
* `mailTimePreset(name, overrides)` to materialize a merged copy; use
|
|
42
|
+
* this object directly only when you want to introspect or compose
|
|
43
|
+
* presets manually.
|
|
44
|
+
*/
|
|
45
|
+
export const presets: Readonly<Record<"transactional" | "otp" | "newsletter" | "marketing" | "notifications" | "alerts", Readonly<MailTimePresetConfig>>>;
|
|
46
|
+
/**
|
|
47
|
+
* @typedef {keyof typeof PRESETS} MailTimePresetName
|
|
48
|
+
*/
|
|
49
|
+
/**
|
|
50
|
+
* Names of every built-in preset.
|
|
51
|
+
* @type {ReadonlyArray<MailTimePresetName>}
|
|
52
|
+
*/
|
|
53
|
+
export const presetNames: ReadonlyArray<MailTimePresetName>;
|
|
54
|
+
/**
|
|
55
|
+
* @typedef {object} MailTimePresetConfig
|
|
56
|
+
* @property {boolean} [concatEmails]
|
|
57
|
+
* @property {number} [concatDelay]
|
|
58
|
+
* @property {string} [concatSubject]
|
|
59
|
+
* @property {number} [retries]
|
|
60
|
+
* @property {number} [retryDelay]
|
|
61
|
+
* @property {number} [revolvingInterval]
|
|
62
|
+
* @property {number} [sendingTimeout]
|
|
63
|
+
* @property {'one' | 'batch'} [mode]
|
|
64
|
+
* @property {number} [concurrency]
|
|
65
|
+
* @property {(error: unknown, email: object, details?: object) => void} [onError]
|
|
66
|
+
* @property {object} [josk]
|
|
67
|
+
*/
|
|
68
|
+
/**
|
|
69
|
+
* Built-in MailTime presets keyed by use-case. Each value is a partial
|
|
70
|
+
* MailTime constructor options object — pass it through `mailTimePreset`
|
|
71
|
+
* (or spread directly) and supply your own `queue` / `transports` /
|
|
72
|
+
* `josk.adapter` / `prefix`. Presets only set the knobs that differ from
|
|
73
|
+
* MailTime defaults so the rest of the constructor stays in your hands.
|
|
74
|
+
*
|
|
75
|
+
* | Preset | Shape | Best for |
|
|
76
|
+
* |-----------------|-------|----------|
|
|
77
|
+
* | `transactional` | High retries, single SMTP per instance, no concat | Receipts, password resets, account changes, welcome emails. |
|
|
78
|
+
* | `otp` | Few retries, fast retryDelay, snappy polling, parallel SMTPs | Sign-in codes, 2FA, verification codes — stale OTPs aren't worth resending forever. |
|
|
79
|
+
* | `newsletter` | `concatEmails: true` with 5-min fold window | Scheduled digests / weekly updates / "what's new" emails. |
|
|
80
|
+
* | `marketing` | High concurrency, moderate retries, no concat | Promotional / campaign blasts where each letter is unique. |
|
|
81
|
+
* | `notifications` | `concatEmails: true` with 60-s fold window | App / social activity (likes, mentions) where bursts collapse into one letter. |
|
|
82
|
+
* | `alerts` | Many retries, fast retryDelay, modest concurrency | Ops / admin alerts: monitoring, error reports, escalations. |
|
|
83
|
+
*
|
|
84
|
+
* @type {Readonly<Record<'transactional' | 'otp' | 'newsletter' | 'marketing' | 'notifications' | 'alerts', Readonly<MailTimePresetConfig>>>}
|
|
85
|
+
*/
|
|
86
|
+
declare const PRESETS: Readonly<Record<"transactional" | "otp" | "newsletter" | "marketing" | "notifications" | "alerts", Readonly<MailTimePresetConfig>>>;
|
|
87
|
+
export {};
|
package/presets.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { deepMerge, isPlainObject, logError } from './helpers.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Default `onError` hook used by every built-in preset. Logs via the
|
|
5
|
+
* shared `logError` helper and tags the line with the MailTime instance
|
|
6
|
+
* `prefix` (or `'default'` when unset) so multi-queue deployments can
|
|
7
|
+
* tell their streams apart. Defined as a regular function so `this`
|
|
8
|
+
* resolves to the MailTime instance at call time — `this.onError(...)`
|
|
9
|
+
* in `index.js` binds the receiver. Users override by passing their own
|
|
10
|
+
* `onError` through `mailTimePreset(name, { onError })` or the
|
|
11
|
+
* constructor.
|
|
12
|
+
*/
|
|
13
|
+
function defaultPresetOnError(error, email, info) {
|
|
14
|
+
logError(`[${this?.prefix || 'default'}] [onError]`, { error, email, info });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {object} MailTimePresetConfig
|
|
19
|
+
* @property {boolean} [concatEmails]
|
|
20
|
+
* @property {number} [concatDelay]
|
|
21
|
+
* @property {string} [concatSubject]
|
|
22
|
+
* @property {number} [retries]
|
|
23
|
+
* @property {number} [retryDelay]
|
|
24
|
+
* @property {number} [revolvingInterval]
|
|
25
|
+
* @property {number} [sendingTimeout]
|
|
26
|
+
* @property {'one' | 'batch'} [mode]
|
|
27
|
+
* @property {number} [concurrency]
|
|
28
|
+
* @property {(error: unknown, email: object, details?: object) => void} [onError]
|
|
29
|
+
* @property {object} [josk]
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Built-in MailTime presets keyed by use-case. Each value is a partial
|
|
34
|
+
* MailTime constructor options object — pass it through `mailTimePreset`
|
|
35
|
+
* (or spread directly) and supply your own `queue` / `transports` /
|
|
36
|
+
* `josk.adapter` / `prefix`. Presets only set the knobs that differ from
|
|
37
|
+
* MailTime defaults so the rest of the constructor stays in your hands.
|
|
38
|
+
*
|
|
39
|
+
* | Preset | Shape | Best for |
|
|
40
|
+
* |-----------------|-------|----------|
|
|
41
|
+
* | `transactional` | High retries, single SMTP per instance, no concat | Receipts, password resets, account changes, welcome emails. |
|
|
42
|
+
* | `otp` | Few retries, fast retryDelay, snappy polling, parallel SMTPs | Sign-in codes, 2FA, verification codes — stale OTPs aren't worth resending forever. |
|
|
43
|
+
* | `newsletter` | `concatEmails: true` with 5-min fold window | Scheduled digests / weekly updates / "what's new" emails. |
|
|
44
|
+
* | `marketing` | High concurrency, moderate retries, no concat | Promotional / campaign blasts where each letter is unique. |
|
|
45
|
+
* | `notifications` | `concatEmails: true` with 60-s fold window | App / social activity (likes, mentions) where bursts collapse into one letter. |
|
|
46
|
+
* | `alerts` | Many retries, fast retryDelay, modest concurrency | Ops / admin alerts: monitoring, error reports, escalations. |
|
|
47
|
+
*
|
|
48
|
+
* @type {Readonly<Record<'transactional' | 'otp' | 'newsletter' | 'marketing' | 'notifications' | 'alerts', Readonly<MailTimePresetConfig>>>}
|
|
49
|
+
*/
|
|
50
|
+
// Every preset pins `mode: 'batch'` explicitly. `'one'` would trade per-tick
|
|
51
|
+
// throughput for cluster-wide fairness across pods on the same `prefix`, but:
|
|
52
|
+
// - urgent classes (`otp`, `alerts`, `transactional`) want all due rows claimed
|
|
53
|
+
// immediately, not spread one-per-tick;
|
|
54
|
+
// - bulk classes (`newsletter`, `marketing`, `notifications`) are bursty and
|
|
55
|
+
// need fast drains during the send window;
|
|
56
|
+
// - multiple `server` pods on the same `prefix` is already an anti-pattern in
|
|
57
|
+
// this library (one JoSk lease per prefix), so the fairness payoff is moot.
|
|
58
|
+
// If you do want `'one'`, pass it via `mailTimePreset(name, { mode: 'one' })`.
|
|
59
|
+
const PRESETS = Object.freeze({
|
|
60
|
+
transactional: Object.freeze({
|
|
61
|
+
concatEmails: false,
|
|
62
|
+
retries: 30,
|
|
63
|
+
retryDelay: 10_000,
|
|
64
|
+
mode: 'batch',
|
|
65
|
+
concurrency: 1,
|
|
66
|
+
onError: defaultPresetOnError,
|
|
67
|
+
josk: Object.freeze({
|
|
68
|
+
zombieTime: 120_000,
|
|
69
|
+
}),
|
|
70
|
+
}),
|
|
71
|
+
otp: Object.freeze({
|
|
72
|
+
concatEmails: false,
|
|
73
|
+
retries: 5,
|
|
74
|
+
retryDelay: 2000,
|
|
75
|
+
revolvingInterval: 1024,
|
|
76
|
+
sendingTimeout: 60_000,
|
|
77
|
+
mode: 'batch',
|
|
78
|
+
concurrency: 4,
|
|
79
|
+
onError: defaultPresetOnError,
|
|
80
|
+
josk: Object.freeze({
|
|
81
|
+
minRevolvingDelay: 256,
|
|
82
|
+
maxRevolvingDelay: 1024,
|
|
83
|
+
zombieTime: 60_000,
|
|
84
|
+
}),
|
|
85
|
+
}),
|
|
86
|
+
newsletter: Object.freeze({
|
|
87
|
+
concatEmails: true,
|
|
88
|
+
concatDelay: 5 * 60_000,
|
|
89
|
+
concatSubject: 'Your updates',
|
|
90
|
+
retries: 5,
|
|
91
|
+
retryDelay: 60_000,
|
|
92
|
+
sendingTimeout: 600_000,
|
|
93
|
+
mode: 'batch',
|
|
94
|
+
concurrency: 2,
|
|
95
|
+
onError: defaultPresetOnError,
|
|
96
|
+
josk: Object.freeze({
|
|
97
|
+
zombieTime: 300_000,
|
|
98
|
+
}),
|
|
99
|
+
}),
|
|
100
|
+
marketing: Object.freeze({
|
|
101
|
+
concatEmails: false,
|
|
102
|
+
retries: 10,
|
|
103
|
+
retryDelay: 30_000,
|
|
104
|
+
mode: 'batch',
|
|
105
|
+
concurrency: 5,
|
|
106
|
+
onError: defaultPresetOnError,
|
|
107
|
+
josk: Object.freeze({
|
|
108
|
+
zombieTime: 180_000,
|
|
109
|
+
}),
|
|
110
|
+
}),
|
|
111
|
+
notifications: Object.freeze({
|
|
112
|
+
concatEmails: true,
|
|
113
|
+
concatDelay: 60_000,
|
|
114
|
+
concatSubject: 'New activity',
|
|
115
|
+
retries: 8,
|
|
116
|
+
retryDelay: 30_000,
|
|
117
|
+
mode: 'batch',
|
|
118
|
+
concurrency: 3,
|
|
119
|
+
onError: defaultPresetOnError,
|
|
120
|
+
josk: Object.freeze({
|
|
121
|
+
zombieTime: 180_000,
|
|
122
|
+
}),
|
|
123
|
+
}),
|
|
124
|
+
alerts: Object.freeze({
|
|
125
|
+
concatEmails: false,
|
|
126
|
+
retries: 20,
|
|
127
|
+
retryDelay: 5000,
|
|
128
|
+
revolvingInterval: 1024,
|
|
129
|
+
sendingTimeout: 60_000,
|
|
130
|
+
mode: 'batch',
|
|
131
|
+
concurrency: 2,
|
|
132
|
+
onError: defaultPresetOnError,
|
|
133
|
+
josk: Object.freeze({
|
|
134
|
+
minRevolvingDelay: 256,
|
|
135
|
+
maxRevolvingDelay: 1024,
|
|
136
|
+
zombieTime: 60_000,
|
|
137
|
+
}),
|
|
138
|
+
}),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Read-only map of preset name → partial MailTime config. Use
|
|
143
|
+
* `mailTimePreset(name, overrides)` to materialize a merged copy; use
|
|
144
|
+
* this object directly only when you want to introspect or compose
|
|
145
|
+
* presets manually.
|
|
146
|
+
*/
|
|
147
|
+
const presets = PRESETS;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @typedef {keyof typeof PRESETS} MailTimePresetName
|
|
151
|
+
*/
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Names of every built-in preset.
|
|
155
|
+
* @type {ReadonlyArray<MailTimePresetName>}
|
|
156
|
+
*/
|
|
157
|
+
const presetNames = Object.freeze(/** @type {MailTimePresetName[]} */ (Object.keys(PRESETS)));
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Materialize a MailTime constructor config from a built-in preset.
|
|
161
|
+
* The named preset is deep-cloned (so the result is freely mutable) and
|
|
162
|
+
* `overrides` is deep-merged on top — overrides win for scalar keys, and
|
|
163
|
+
* nested objects like `josk` are merged so the preset's defaults compose
|
|
164
|
+
* with the caller's `adapter`, `lockOwnerId`, `onError`, etc.
|
|
165
|
+
*
|
|
166
|
+
* ```js
|
|
167
|
+
* import { MailTime, RedisQueue, mailTimePreset } from 'mail-time';
|
|
168
|
+
* const mailTime = new MailTime(mailTimePreset('otp', {
|
|
169
|
+
* prefix: 'otp',
|
|
170
|
+
* queue: new RedisQueue({ client }),
|
|
171
|
+
* transports: [otpTransport],
|
|
172
|
+
* josk: { adapter: { type: 'redis', client } },
|
|
173
|
+
* }));
|
|
174
|
+
* ```
|
|
175
|
+
*
|
|
176
|
+
* @param {MailTimePresetName} name - one of `presetNames`
|
|
177
|
+
* @param {object} [overrides] - additional MailTime constructor options
|
|
178
|
+
* @returns {object} fresh, mutable MailTime constructor options
|
|
179
|
+
* @throws {Error} when `name` is unknown
|
|
180
|
+
* @throws {TypeError} when `overrides` is provided but not a plain object
|
|
181
|
+
*/
|
|
182
|
+
const mailTimePreset = (name, overrides) => {
|
|
183
|
+
if (typeof name !== 'string' || !Object.hasOwn(PRESETS, name)) {
|
|
184
|
+
throw new Error(`[mail-time] [mailTimePreset] unknown preset "${name}". Available: ${presetNames.join(', ')}`);
|
|
185
|
+
}
|
|
186
|
+
if (overrides !== void 0 && !isPlainObject(overrides)) {
|
|
187
|
+
throw new TypeError('[mail-time] [mailTimePreset] {overrides} must be a plain object when provided');
|
|
188
|
+
}
|
|
189
|
+
const cloned = deepMerge({}, PRESETS[name]);
|
|
190
|
+
return overrides ? deepMerge(cloned, overrides) : cloned;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
export { mailTimePreset, presets, presetNames };
|