@shaferllc/keel 0.59.0 → 0.68.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/AGENTS.md +167 -0
- package/README.md +30 -1
- package/bin/keel-mcp.mjs +9 -0
- package/dist/core/application.d.ts +5 -5
- package/dist/core/application.js +14 -2
- package/dist/core/auth.d.ts +47 -0
- package/dist/core/auth.js +77 -0
- package/dist/core/authorization.d.ts +9 -1
- package/dist/core/authorization.js +22 -2
- package/dist/core/cache.d.ts +82 -5
- package/dist/core/cache.js +181 -23
- package/dist/core/cli/stubs.d.ts +12 -0
- package/dist/core/cli/stubs.js +120 -0
- package/dist/core/container.d.ts +20 -0
- package/dist/core/container.js +52 -0
- package/dist/core/cors.d.ts +29 -0
- package/dist/core/cors.js +72 -0
- package/dist/core/crypto.d.ts +40 -4
- package/dist/core/crypto.js +66 -6
- package/dist/core/csrf.d.ts +25 -0
- package/dist/core/csrf.js +78 -0
- package/dist/core/database.d.ts +49 -4
- package/dist/core/database.js +89 -21
- package/dist/core/events.d.ts +129 -5
- package/dist/core/events.js +165 -7
- package/dist/core/health.d.ts +141 -0
- package/dist/core/health.js +226 -0
- package/dist/core/helpers.d.ts +15 -3
- package/dist/core/helpers.js +23 -3
- package/dist/core/index.d.ts +33 -18
- package/dist/core/index.js +16 -8
- package/dist/core/lock.d.ts +139 -0
- package/dist/core/lock.js +215 -0
- package/dist/core/logger.d.ts +82 -4
- package/dist/core/logger.js +141 -23
- package/dist/core/mail.d.ts +128 -7
- package/dist/core/mail.js +264 -16
- package/dist/core/model.d.ts +2 -0
- package/dist/core/model.js +16 -14
- package/dist/core/provider.d.ts +7 -0
- package/dist/core/provider.js +7 -0
- package/dist/core/queue.d.ts +134 -9
- package/dist/core/queue.js +304 -14
- package/dist/core/rate-limit.js +3 -0
- package/dist/core/relations.js +13 -13
- package/dist/core/request.d.ts +26 -0
- package/dist/core/request.js +77 -0
- package/dist/core/shield.d.ts +39 -0
- package/dist/core/shield.js +60 -0
- package/dist/core/social.d.ts +173 -0
- package/dist/core/social.js +337 -0
- package/dist/core/storage.d.ts +159 -6
- package/dist/core/storage.js +287 -7
- package/dist/core/tokens.d.ts +74 -0
- package/dist/core/tokens.js +155 -0
- package/dist/db/d1.d.ts +32 -0
- package/dist/db/d1.js +26 -0
- package/dist/db/libsql.d.ts +29 -0
- package/dist/db/libsql.js +32 -0
- package/dist/db/pg.d.ts +29 -0
- package/dist/db/pg.js +33 -0
- package/dist/mcp/server.d.ts +19 -0
- package/dist/mcp/server.js +355 -0
- package/docs/ai-manifest.json +2472 -0
- package/docs/ai.md +128 -0
- package/docs/architecture.md +331 -0
- package/docs/authentication.md +453 -0
- package/docs/authorization.md +167 -0
- package/docs/broadcasting.md +137 -0
- package/docs/broker.md +500 -0
- package/docs/cache.md +558 -0
- package/docs/configuration.md +311 -0
- package/docs/console.md +356 -0
- package/docs/container.md +467 -0
- package/docs/controllers.md +265 -0
- package/docs/cors.md +51 -0
- package/docs/database.md +530 -0
- package/docs/debugging.md +129 -0
- package/docs/decorators.md +127 -0
- package/docs/errors.md +395 -0
- package/docs/events.md +496 -0
- package/docs/examples/architecture-app.ts +27 -0
- package/docs/examples/authentication.ts +61 -0
- package/docs/examples/authorization.ts +79 -0
- package/docs/examples/broadcasting.ts +60 -0
- package/docs/examples/broker-cache-validate.ts +34 -0
- package/docs/examples/broker-fault-tolerance.ts +29 -0
- package/docs/examples/broker-middleware.ts +27 -0
- package/docs/examples/broker.ts +203 -0
- package/docs/examples/cache.ts +222 -0
- package/docs/examples/configuration.ts +81 -0
- package/docs/examples/container.ts +134 -0
- package/docs/examples/controllers.ts +86 -0
- package/docs/examples/database.ts +118 -0
- package/docs/examples/debugging.ts +41 -0
- package/docs/examples/decorators.ts +40 -0
- package/docs/examples/errors.ts +121 -0
- package/docs/examples/events.ts +204 -0
- package/docs/examples/factories.ts +84 -0
- package/docs/examples/hashing.ts +71 -0
- package/docs/examples/health.ts +94 -0
- package/docs/examples/helpers.ts +171 -0
- package/docs/examples/hooks.ts +54 -0
- package/docs/examples/inertia.ts +81 -0
- package/docs/examples/locks.ts +120 -0
- package/docs/examples/logger.ts +92 -0
- package/docs/examples/mail.ts +160 -0
- package/docs/examples/middleware.ts +119 -0
- package/docs/examples/migrations.ts +126 -0
- package/docs/examples/models.ts +239 -0
- package/docs/examples/notification.ts +124 -0
- package/docs/examples/providers.ts +123 -0
- package/docs/examples/queues.ts +254 -0
- package/docs/examples/rate-limiting.ts +42 -0
- package/docs/examples/redis.ts +99 -0
- package/docs/examples/request-response.ts +197 -0
- package/docs/examples/routing.ts +186 -0
- package/docs/examples/scheduling.ts +62 -0
- package/docs/examples/sessions.ts +102 -0
- package/docs/examples/static-files.ts +63 -0
- package/docs/examples/storage.ts +132 -0
- package/docs/examples/templates.ts +58 -0
- package/docs/examples/testing.ts +66 -0
- package/docs/examples/transformer.ts +141 -0
- package/docs/examples/transformers.ts +49 -0
- package/docs/examples/url-builder.ts +86 -0
- package/docs/examples/validation.ts +102 -0
- package/docs/examples/views.tsx +62 -0
- package/docs/examples/vite.ts +106 -0
- package/docs/factories.md +166 -0
- package/docs/getting-started.md +290 -0
- package/docs/hashing.md +259 -0
- package/docs/health.md +225 -0
- package/docs/helpers.md +347 -0
- package/docs/hono.md +186 -0
- package/docs/hooks.md +118 -0
- package/docs/inertia.md +241 -0
- package/docs/locks.md +323 -0
- package/docs/logger.md +290 -0
- package/docs/mail.md +678 -0
- package/docs/middleware.md +425 -0
- package/docs/migrations.md +476 -0
- package/docs/models.md +810 -0
- package/docs/notifications.md +474 -0
- package/docs/providers.md +363 -0
- package/docs/queues.md +679 -0
- package/docs/rate-limiting.md +155 -0
- package/docs/redis.md +178 -0
- package/docs/request-response.md +953 -0
- package/docs/routing.md +804 -0
- package/docs/scheduling.md +110 -0
- package/docs/security.md +85 -0
- package/docs/sessions.md +354 -0
- package/docs/social-auth.md +174 -0
- package/docs/static-files.md +211 -0
- package/docs/storage.md +450 -0
- package/docs/templates.md +315 -0
- package/docs/testing.md +125 -0
- package/docs/transformers.md +381 -0
- package/docs/url-builder.md +295 -0
- package/docs/validation.md +288 -0
- package/docs/views.md +267 -0
- package/docs/vite.md +434 -0
- package/llms-full.txt +17694 -0
- package/llms.txt +116 -0
- package/package.json +38 -7
package/dist/core/logger.js
CHANGED
|
@@ -5,49 +5,135 @@
|
|
|
5
5
|
*
|
|
6
6
|
* logger().info("user registered", { userId: user.id });
|
|
7
7
|
* logger().error("payment failed", { orderId, error });
|
|
8
|
+
*
|
|
9
|
+
* Output goes through a `Sink` — `console` by default, but any function, so logs
|
|
10
|
+
* can go to a file, an HTTP collector, or a buffer in tests. Register extra
|
|
11
|
+
* loggers by name (`setLogger(audit, "audit")`) when a subsystem needs its own
|
|
12
|
+
* level or destination.
|
|
8
13
|
*/
|
|
9
|
-
const LEVELS = {
|
|
14
|
+
const LEVELS = {
|
|
15
|
+
trace: 10,
|
|
16
|
+
debug: 20,
|
|
17
|
+
info: 30,
|
|
18
|
+
warn: 40,
|
|
19
|
+
error: 50,
|
|
20
|
+
fatal: 60,
|
|
21
|
+
};
|
|
10
22
|
const REDACTED = "[redacted]";
|
|
11
|
-
|
|
12
|
-
|
|
23
|
+
function normalizeRedact(redact) {
|
|
24
|
+
if (!redact)
|
|
25
|
+
return undefined;
|
|
26
|
+
const options = Array.isArray(redact) ? { paths: redact } : redact;
|
|
27
|
+
if (!options.paths.length)
|
|
28
|
+
return undefined;
|
|
29
|
+
return { paths: options.paths, censor: options.censor ?? REDACTED, remove: options.remove ?? false };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Return a copy of `obj` with `keys` (a split dot path) redacted. Clones only
|
|
33
|
+
* along the path, so untouched branches keep their identity. A `*` segment
|
|
34
|
+
* matches every key at that level.
|
|
35
|
+
*/
|
|
36
|
+
function redactPath(obj, keys, options) {
|
|
13
37
|
const [head, ...rest] = keys;
|
|
14
|
-
if (head === undefined
|
|
38
|
+
if (head === undefined)
|
|
39
|
+
return obj;
|
|
40
|
+
const targets = head === "*" ? Object.keys(obj) : head in obj ? [head] : [];
|
|
41
|
+
if (!targets.length)
|
|
15
42
|
return obj;
|
|
16
43
|
const clone = { ...obj };
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
44
|
+
for (const key of targets) {
|
|
45
|
+
if (rest.length === 0) {
|
|
46
|
+
if (options.remove)
|
|
47
|
+
delete clone[key];
|
|
48
|
+
else
|
|
49
|
+
clone[key] = options.censor;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
const child = clone[key];
|
|
53
|
+
if (child && typeof child === "object" && !Array.isArray(child)) {
|
|
54
|
+
clone[key] = redactPath(child, rest, options);
|
|
55
|
+
}
|
|
24
56
|
}
|
|
25
57
|
}
|
|
26
58
|
return clone;
|
|
27
59
|
}
|
|
60
|
+
/** The default sink: JSON to stdout, or a pretty single line when `pretty`. */
|
|
61
|
+
export function consoleSink(pretty = false) {
|
|
62
|
+
return ({ level, time, msg, fields }) => {
|
|
63
|
+
const write = level === "warn"
|
|
64
|
+
? console.warn
|
|
65
|
+
: level === "error" || level === "fatal"
|
|
66
|
+
? console.error
|
|
67
|
+
: console.log;
|
|
68
|
+
if (pretty) {
|
|
69
|
+
const extra = Object.keys(fields).length ? " " + JSON.stringify(fields) : "";
|
|
70
|
+
write(`[${time}] ${level.toUpperCase().padEnd(5)} ${msg}${extra}`);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
write(JSON.stringify({ level, time, msg, ...fields }));
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** A sink that collects records in memory — for tests. Assert on `.records`. */
|
|
78
|
+
export class MemorySink {
|
|
79
|
+
records = [];
|
|
80
|
+
/** The sink function to hand to `LoggerOptions.sink`. */
|
|
81
|
+
sink = (record) => {
|
|
82
|
+
this.records.push(record);
|
|
83
|
+
};
|
|
84
|
+
/** Records at one level. */
|
|
85
|
+
at(level) {
|
|
86
|
+
return this.records.filter((r) => r.level === level);
|
|
87
|
+
}
|
|
88
|
+
/** The messages logged, in order. */
|
|
89
|
+
messages() {
|
|
90
|
+
return this.records.map((r) => r.msg);
|
|
91
|
+
}
|
|
92
|
+
clear() {
|
|
93
|
+
this.records.length = 0;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
28
96
|
export class Logger {
|
|
29
97
|
options;
|
|
30
98
|
threshold;
|
|
99
|
+
redact;
|
|
100
|
+
sink;
|
|
31
101
|
constructor(options = {}) {
|
|
32
102
|
this.options = options;
|
|
33
103
|
this.threshold = LEVELS[options.level ?? "info"];
|
|
104
|
+
this.redact = normalizeRedact(options.redact);
|
|
105
|
+
this.sink = options.sink ?? consoleSink(options.pretty);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Whether a level would be emitted. Check it before building an expensive
|
|
109
|
+
* context object — the work of assembling it happens whether or not the line
|
|
110
|
+
* is ever written.
|
|
111
|
+
*
|
|
112
|
+
* if (logger().isLevelEnabled("debug")) {
|
|
113
|
+
* logger().debug("state", { snapshot: expensiveSnapshot() });
|
|
114
|
+
* }
|
|
115
|
+
*/
|
|
116
|
+
isLevelEnabled(level) {
|
|
117
|
+
return this.options.enabled !== false && LEVELS[level] >= this.threshold;
|
|
118
|
+
}
|
|
119
|
+
/** Run `fn` only if `level` would be emitted — the callback form of the above. */
|
|
120
|
+
ifLevelEnabled(level, fn) {
|
|
121
|
+
if (this.isLevelEnabled(level))
|
|
122
|
+
fn(this);
|
|
34
123
|
}
|
|
35
124
|
write(level, message, context) {
|
|
36
|
-
if (
|
|
125
|
+
if (!this.isLevelEnabled(level))
|
|
37
126
|
return;
|
|
38
|
-
const time = new Date().toISOString();
|
|
39
127
|
let fields = { ...this.options.bindings, ...context };
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const extra = Object.keys(fields).length ? " " + JSON.stringify(fields) : "";
|
|
45
|
-
const fn = level === "warn" ? console.warn : level === "error" ? console.error : console.log;
|
|
46
|
-
fn(`[${time}] ${level.toUpperCase().padEnd(5)} ${message}${extra}`);
|
|
47
|
-
}
|
|
48
|
-
else {
|
|
49
|
-
console.log(JSON.stringify({ level, time, msg: message, ...fields }));
|
|
128
|
+
if (this.redact) {
|
|
129
|
+
for (const path of this.redact.paths) {
|
|
130
|
+
fields = redactPath(fields, path.split("."), this.redact);
|
|
131
|
+
}
|
|
50
132
|
}
|
|
133
|
+
this.sink({ level, time: new Date().toISOString(), msg: message, fields });
|
|
134
|
+
}
|
|
135
|
+
trace(message, context) {
|
|
136
|
+
this.write("trace", message, context);
|
|
51
137
|
}
|
|
52
138
|
debug(message, context) {
|
|
53
139
|
this.write("debug", message, context);
|
|
@@ -61,6 +147,14 @@ export class Logger {
|
|
|
61
147
|
error(message, context) {
|
|
62
148
|
this.write("error", message, context);
|
|
63
149
|
}
|
|
150
|
+
/** An unrecoverable failure — the loudest level. */
|
|
151
|
+
fatal(message, context) {
|
|
152
|
+
this.write("fatal", message, context);
|
|
153
|
+
}
|
|
154
|
+
/** Log at a level chosen at runtime. */
|
|
155
|
+
log(level, message, context) {
|
|
156
|
+
this.write(level, message, context);
|
|
157
|
+
}
|
|
64
158
|
/** A child logger with additional bound fields (e.g. a request id). */
|
|
65
159
|
child(bindings) {
|
|
66
160
|
return new Logger({
|
|
@@ -69,3 +163,27 @@ export class Logger {
|
|
|
69
163
|
});
|
|
70
164
|
}
|
|
71
165
|
}
|
|
166
|
+
/* -------------------------------- registry -------------------------------- */
|
|
167
|
+
const loggers = new Map();
|
|
168
|
+
/**
|
|
169
|
+
* Register a logger under a name, so a subsystem can have its own level or
|
|
170
|
+
* destination:
|
|
171
|
+
*
|
|
172
|
+
* setLogger(new Logger({ level: "trace", sink: auditSink }), "audit");
|
|
173
|
+
* namedLogger("audit").trace("permission granted", { userId });
|
|
174
|
+
*
|
|
175
|
+
* The default logger is the application's `Logger` singleton — resolve that with
|
|
176
|
+
* the global `logger()` helper, not this.
|
|
177
|
+
*/
|
|
178
|
+
export function setLogger(instance, name) {
|
|
179
|
+
loggers.set(name, instance);
|
|
180
|
+
return instance;
|
|
181
|
+
}
|
|
182
|
+
/** A logger registered with `setLogger`. Throws for an unknown name. */
|
|
183
|
+
export function namedLogger(name) {
|
|
184
|
+
const instance = loggers.get(name);
|
|
185
|
+
if (!instance) {
|
|
186
|
+
throw new Error(`No logger named "${name}". Register it with setLogger(logger, name).`);
|
|
187
|
+
}
|
|
188
|
+
return instance;
|
|
189
|
+
}
|
package/dist/core/mail.d.ts
CHANGED
|
@@ -13,8 +13,28 @@
|
|
|
13
13
|
* .html("<h1>Hi</h1>")
|
|
14
14
|
* .send();
|
|
15
15
|
*
|
|
16
|
-
*
|
|
16
|
+
* Sending is slow and it fails — which is exactly what a queue is for. `sendLater()`
|
|
17
|
+
* puts the message on the queue instead of holding the request open for an SMTP
|
|
18
|
+
* round trip:
|
|
19
|
+
*
|
|
20
|
+
* await mail().to(user.email).subject("Welcome").html(body).sendLater();
|
|
21
|
+
*
|
|
22
|
+
* In tests, `fakeMail()` swaps the transport for one that records and asserts.
|
|
17
23
|
*/
|
|
24
|
+
import { Job } from "./queue.js";
|
|
25
|
+
/** A file travelling with the message. */
|
|
26
|
+
export interface Attachment {
|
|
27
|
+
filename: string;
|
|
28
|
+
/** Raw bytes, or a string (UTF-8 encoded). */
|
|
29
|
+
content: string | Uint8Array;
|
|
30
|
+
/** MIME type. Inferred from the filename's extension when omitted. */
|
|
31
|
+
contentType?: string;
|
|
32
|
+
/**
|
|
33
|
+
* A content id, for embedding in the HTML body: an attachment with `cid: "logo"`
|
|
34
|
+
* is referenced as `<img src="cid:logo">`. Implies an inline disposition.
|
|
35
|
+
*/
|
|
36
|
+
cid?: string;
|
|
37
|
+
}
|
|
18
38
|
/** A normalized, ready-to-send message. */
|
|
19
39
|
export interface Message {
|
|
20
40
|
to: string[];
|
|
@@ -26,6 +46,7 @@ export interface Message {
|
|
|
26
46
|
text?: string;
|
|
27
47
|
html?: string;
|
|
28
48
|
headers?: Record<string, string>;
|
|
49
|
+
attachments?: Attachment[];
|
|
29
50
|
}
|
|
30
51
|
/** The bridge to your email provider. */
|
|
31
52
|
export interface Transport {
|
|
@@ -34,8 +55,10 @@ export interface Transport {
|
|
|
34
55
|
export interface MailerOptions {
|
|
35
56
|
/** Default `from` address for messages that don't set one. */
|
|
36
57
|
from?: string;
|
|
58
|
+
/** Default `replyTo` for messages that don't set one. */
|
|
59
|
+
replyTo?: string;
|
|
37
60
|
}
|
|
38
|
-
/** A fluent, immutable-ish builder — chain setters, then `send()`. */
|
|
61
|
+
/** A fluent, immutable-ish builder — chain setters, then `send()` or `sendLater()`. */
|
|
39
62
|
export declare class PendingMail {
|
|
40
63
|
private mailer;
|
|
41
64
|
private message;
|
|
@@ -49,23 +72,76 @@ export declare class PendingMail {
|
|
|
49
72
|
text(text: string): this;
|
|
50
73
|
html(html: string): this;
|
|
51
74
|
header(name: string, value: string): this;
|
|
75
|
+
/** Attach a file. The content type is inferred from the extension if omitted. */
|
|
76
|
+
attach(filename: string, content: string | Uint8Array, contentType?: string): this;
|
|
77
|
+
/**
|
|
78
|
+
* Attach an image and give it a content id, so the HTML body can show it inline:
|
|
79
|
+
*
|
|
80
|
+
* .embed("logo", bytes, "logo.png").html(`<img src="cid:logo">`)
|
|
81
|
+
*/
|
|
82
|
+
embed(cid: string, content: string | Uint8Array, filename?: string, contentType?: string): this;
|
|
52
83
|
/** Seed several fields at once (merges into what's been chained). */
|
|
53
84
|
fill(partial: Partial<{
|
|
54
85
|
to: string | string[];
|
|
55
86
|
cc: string | string[];
|
|
56
87
|
bcc: string | string[];
|
|
57
88
|
} & Omit<Message, "to" | "cc" | "bcc">>): this;
|
|
89
|
+
/** The message as composed so far — before the mailer applies its defaults. */
|
|
90
|
+
toMessage(): Message;
|
|
58
91
|
/** Freeze and hand the message to the mailer. */
|
|
59
92
|
send(): Promise<Message>;
|
|
93
|
+
/**
|
|
94
|
+
* Queue the message instead of sending it now — the request returns without
|
|
95
|
+
* waiting on the provider, and a failed send retries on the queue rather than
|
|
96
|
+
* failing the user's action.
|
|
97
|
+
*
|
|
98
|
+
* With the default `SyncDriver` this still sends inline; register a real queue
|
|
99
|
+
* driver to actually defer it.
|
|
100
|
+
*/
|
|
101
|
+
sendLater(): Promise<void>;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* A reusable, testable email as a class — the mail equivalent of a `Job`.
|
|
105
|
+
* Implement `build()` to compose the message; the mailer calls it for you.
|
|
106
|
+
*
|
|
107
|
+
* export class WelcomeEmail extends BaseMail {
|
|
108
|
+
* constructor(private user: User) { super(); }
|
|
109
|
+
*
|
|
110
|
+
* build(message: PendingMail) {
|
|
111
|
+
* message.to(this.user.email).subject("Welcome").html(`<h1>Hi ${this.user.name}</h1>`);
|
|
112
|
+
* }
|
|
113
|
+
* }
|
|
114
|
+
*
|
|
115
|
+
* await send(new WelcomeEmail(user)); // or sendLater(new WelcomeEmail(user))
|
|
116
|
+
*/
|
|
117
|
+
export declare abstract class BaseMail {
|
|
118
|
+
abstract build(message: PendingMail): void | Promise<void>;
|
|
119
|
+
}
|
|
120
|
+
/** The job that carries a queued message. Exported so a driver can recognize it. */
|
|
121
|
+
export declare class SendMailJob extends Job {
|
|
122
|
+
readonly message: Message;
|
|
123
|
+
readonly mailerName: string;
|
|
124
|
+
constructor(message: Message, mailerName: string);
|
|
125
|
+
handle(): Promise<void>;
|
|
60
126
|
}
|
|
61
127
|
export declare class Mailer {
|
|
62
128
|
private transport;
|
|
63
129
|
private options;
|
|
64
|
-
|
|
130
|
+
/** The name this mailer is registered under — carried onto queued jobs. */
|
|
131
|
+
readonly name: string;
|
|
132
|
+
constructor(transport: Transport, options?: MailerOptions,
|
|
133
|
+
/** The name this mailer is registered under — carried onto queued jobs. */
|
|
134
|
+
name?: string);
|
|
65
135
|
/** Start composing a message. */
|
|
66
136
|
message(): PendingMail;
|
|
137
|
+
/** Apply this mailer's defaults and check the message is sendable. */
|
|
138
|
+
protected prepare(message: Message): Message;
|
|
67
139
|
/** Validate, apply defaults, and dispatch through the transport. */
|
|
68
140
|
send(message: Message): Promise<Message>;
|
|
141
|
+
/** Validate, apply defaults, and put the message on the queue. */
|
|
142
|
+
sendLater(message: Message): Promise<Message>;
|
|
143
|
+
/** The underlying transport, for provider-specific operations. */
|
|
144
|
+
get driver(): Transport;
|
|
69
145
|
}
|
|
70
146
|
/** Collects sent messages in memory — the default, and ideal for tests. */
|
|
71
147
|
export declare class ArrayTransport implements Transport {
|
|
@@ -86,9 +162,54 @@ export interface FetchTransportOptions {
|
|
|
86
162
|
}
|
|
87
163
|
/** A generic HTTP transport for provider APIs (Resend, Postmark, …), via `fetch`. */
|
|
88
164
|
export declare function fetchTransport(options: FetchTransportOptions): Transport;
|
|
89
|
-
/**
|
|
90
|
-
export
|
|
165
|
+
/** A message recorded by the fake, and whether it was sent now or queued. */
|
|
166
|
+
export interface RecordedMail {
|
|
167
|
+
message: Message;
|
|
168
|
+
queued: boolean;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* A `Mailer` that records instead of delivering, with assertions — what
|
|
172
|
+
* `fakeMail()` installs so tests never talk to a provider.
|
|
173
|
+
*/
|
|
174
|
+
export declare class FakeMailer extends Mailer {
|
|
175
|
+
readonly mails: RecordedMail[];
|
|
176
|
+
constructor(name?: string, options?: MailerOptions);
|
|
177
|
+
send(message: Message): Promise<Message>;
|
|
178
|
+
sendLater(message: Message): Promise<Message>;
|
|
179
|
+
/** Everything sent immediately. */
|
|
180
|
+
sent(): Message[];
|
|
181
|
+
/** Everything queued with `sendLater()`. */
|
|
182
|
+
queued(): Message[];
|
|
183
|
+
assertSent(where?: (message: Message) => boolean): void;
|
|
184
|
+
assertNotSent(where?: (message: Message) => boolean): void;
|
|
185
|
+
assertSentCount(expected: number): void;
|
|
186
|
+
assertQueued(where?: (message: Message) => boolean): void;
|
|
187
|
+
assertNotQueued(where?: (message: Message) => boolean): void;
|
|
188
|
+
assertQueuedCount(expected: number): void;
|
|
189
|
+
/** Nothing sent and nothing queued. */
|
|
190
|
+
assertNothingSent(): void;
|
|
191
|
+
private summary;
|
|
192
|
+
}
|
|
193
|
+
/** Register a mailer, optionally under a name (default: `"default"`). */
|
|
194
|
+
export declare function setMailer(transport: Transport, options?: MailerOptions, name?: string): Mailer;
|
|
195
|
+
/** The default mailer, or a named one registered with `setMailer(…, …, name)`. */
|
|
196
|
+
export declare function mailer(name?: string): Mailer;
|
|
91
197
|
/** The default mailer instance. */
|
|
92
198
|
export declare function getMailer(): Mailer;
|
|
93
|
-
/** Start a message on
|
|
94
|
-
export declare function mail(): PendingMail;
|
|
199
|
+
/** Start a message on a mailer: `mail().to(…).subject(…).send()`. */
|
|
200
|
+
export declare function mail(name?: string): PendingMail;
|
|
201
|
+
/** Send a class-based mail now. */
|
|
202
|
+
export declare function send(email: BaseMail, name?: string): Promise<Message>;
|
|
203
|
+
/** Queue a class-based mail. */
|
|
204
|
+
export declare function sendLater(email: BaseMail, name?: string): Promise<Message>;
|
|
205
|
+
/**
|
|
206
|
+
* Swap a mailer for a `FakeMailer` so tests never talk to a provider, and assert
|
|
207
|
+
* on what was sent or queued. Undo with `restoreMail()`.
|
|
208
|
+
*
|
|
209
|
+
* const mail = fakeMail();
|
|
210
|
+
* await registerUser();
|
|
211
|
+
* mail.assertQueued((m) => m.subject === "Welcome");
|
|
212
|
+
*/
|
|
213
|
+
export declare function fakeMail(name?: string): FakeMailer;
|
|
214
|
+
/** Restore the real mailer after `fakeMail()`. With no name, restores them all. */
|
|
215
|
+
export declare function restoreMail(name?: string): void;
|