stageflow 0.31.0 → 0.33.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/README.md +1 -0
- package/dist/agent/fakeAgent.js +0 -6
- package/dist/agent/piAdapter.js +21 -0
- package/dist/agent/port.d.ts +2 -0
- package/dist/agent/providerAuth.d.ts +6 -0
- package/dist/agent/providerAuth.js +50 -14
- package/dist/agent/resolveAgentPort.d.ts +2 -0
- package/dist/agent/resolveAgentPort.js +5 -1
- package/dist/config/listConfig.d.ts +0 -16
- package/dist/config/listConfig.js +0 -3
- package/dist/config/loadStage.js +10 -0
- package/dist/config/pipelineStageKeys.js +1 -0
- package/dist/email/accounts.d.ts +109 -0
- package/dist/email/accounts.js +285 -0
- package/dist/email/adapter.d.ts +69 -0
- package/dist/email/adapter.js +720 -0
- package/dist/email/attachments.d.ts +21 -0
- package/dist/email/attachments.js +146 -0
- package/dist/email/events.d.ts +95 -0
- package/dist/email/events.js +292 -0
- package/dist/email/host.d.ts +15 -0
- package/dist/email/host.js +119 -0
- package/dist/email/messages.d.ts +44 -0
- package/dist/email/messages.js +182 -0
- package/dist/email/port.d.ts +161 -0
- package/dist/email/port.js +12 -0
- package/dist/email/replies.d.ts +5 -0
- package/dist/email/replies.js +56 -0
- package/dist/email/submissions.d.ts +31 -0
- package/dist/email/submissions.js +123 -0
- package/dist/email/triggers.d.ts +140 -0
- package/dist/email/triggers.js +563 -0
- package/dist/hosted/containment.d.ts +3 -0
- package/dist/hosted/containment.js +36 -0
- package/dist/hosted/errors.d.ts +21 -0
- package/dist/hosted/errors.js +30 -0
- package/dist/hosted/packageSkills.d.ts +3 -0
- package/dist/hosted/packageSkills.js +80 -0
- package/dist/hosted/runtime.d.ts +12 -0
- package/dist/hosted/runtime.js +596 -0
- package/dist/hosted/sequencer.d.ts +28 -0
- package/dist/hosted/sequencer.js +129 -0
- package/dist/hosted/translate.d.ts +3 -0
- package/dist/hosted/translate.js +43 -0
- package/dist/hosted/types.d.ts +108 -0
- package/dist/hosted/types.js +10 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.js +5 -0
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/projection/projectRun.d.ts +0 -1
- package/dist/projection/projectRun.js +0 -1
- package/dist/runstore/port.d.ts +4 -0
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +1 -0
- package/dist/runstore/sqlite/SqliteRunStore.js +24 -5
- package/dist/runstore/sqlite/migrations/011-email-dispatch-key.d.ts +7 -0
- package/dist/runstore/sqlite/migrations/011-email-dispatch-key.js +12 -0
- package/dist/runstore/sqlite/migrations/index.js +2 -0
- package/dist/runtime/containerLimits.d.ts +0 -3
- package/dist/runtime/containerLimits.js +0 -5
- package/dist/runtime/credentialBinding.d.ts +3 -0
- package/dist/runtime/credentialBinding.js +9 -0
- package/dist/runtime/pipelineRunner.d.ts +3 -0
- package/dist/runtime/pipelineRunner.js +11 -0
- package/dist/runtime/pipelineScheduler.js +3 -0
- package/dist/runtime/runManager.d.ts +1 -0
- package/dist/runtime/runManager.js +24 -3
- package/dist/runtime/runRetentionSweep.d.ts +0 -2
- package/dist/runtime/runRetentionSweep.js +0 -8
- package/dist/runtime/stageAttemptBootstrap.js +14 -0
- package/dist/runtime/stageProcessLauncher.d.ts +4 -0
- package/dist/runtime/stageProcessLauncher.js +45 -1
- package/dist/runtime/stageRoots.js +5 -1
- package/dist/server/bootstrap.d.ts +7 -0
- package/dist/server/bootstrap.js +17 -0
- package/dist/server/emailRoutes.d.ts +5 -0
- package/dist/server/emailRoutes.js +136 -0
- package/dist/server/http.d.ts +2 -0
- package/dist/server/http.js +16 -0
- package/dist/tools/readEmail.d.ts +77 -0
- package/dist/tools/readEmail.js +31 -0
- package/dist/tools/replyEmail.d.ts +31 -0
- package/dist/tools/replyEmail.js +25 -0
- package/dist/tools/sendEmail.d.ts +43 -0
- package/dist/tools/sendEmail.js +26 -0
- package/dist/types/stage.d.ts +4 -0
- package/dist/ui/assets/index-59cjF0OA.js +133 -0
- package/dist/ui/index.html +1 -1
- package/examples/hello-email/README.md +23 -0
- package/examples/hello-email/hello-email-reply.pipeline.yaml +6 -0
- package/examples/hello-email/hello-email.pipeline.yaml +6 -0
- package/examples/hello-email/hello-email.task.yaml +6 -0
- package/examples/hello-email/reply-hello-email.yaml +63 -0
- package/examples/hello-email/send-hello-email.yaml +37 -0
- package/package.json +14 -2
- package/dist/ui/assets/index-p8Iou3zk.js +0 -133
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { closeSync, constants, existsSync, fstatSync, mkdirSync, openSync, readFileSync, readSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { parseDocument } from "yaml";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { storeRootFor } from "../runstore/paths.js";
|
|
7
|
+
import { EmailError } from "./port.js";
|
|
8
|
+
import { attachmentLimitsSchema } from "./attachments.js";
|
|
9
|
+
const localSecrets = new Map();
|
|
10
|
+
const authSchema = z.object({
|
|
11
|
+
type: z.enum(["password", "oauth2"]),
|
|
12
|
+
secretRef: z.string().regex(/^(env:[A-Za-z_][A-Za-z0-9_]*|local:[a-f0-9]{64})$/),
|
|
13
|
+
expiresAt: z.iso.datetime().optional(),
|
|
14
|
+
}).strict().refine(auth => auth.type !== "oauth2" || auth.expiresAt !== undefined);
|
|
15
|
+
const connectionSchema = z.object({
|
|
16
|
+
host: z.string().min(1).max(253).regex(/^[A-Za-z0-9.:-]+$/),
|
|
17
|
+
port: z.number().int().min(1).max(65535),
|
|
18
|
+
username: z.string().min(1).max(320),
|
|
19
|
+
tls: z.enum(["implicit", "starttls", "none"]).default("implicit"),
|
|
20
|
+
auth: authSchema,
|
|
21
|
+
}).strict();
|
|
22
|
+
const accountSchema = z.object({
|
|
23
|
+
displayName: z.string().min(1).max(200), address: z.email(), enabled: z.boolean().default(true),
|
|
24
|
+
adapter: z.literal("local").default("local"),
|
|
25
|
+
folders: z.array(z.string().min(1).max(200)).min(1).max(20).default(["INBOX"]),
|
|
26
|
+
senderAliases: z.array(z.email()).max(20).default([]),
|
|
27
|
+
imap: connectionSchema, smtp: connectionSchema,
|
|
28
|
+
allowInsecureLocalDevelopment: z.boolean().default(false),
|
|
29
|
+
connectionTimeoutMs: z.number().int().min(100).max(60000).default(10000),
|
|
30
|
+
pollingIntervalMs: z.number().int().min(1000).max(3600000).default(60000),
|
|
31
|
+
reconnectMaxDelayMs: z.number().int().min(1000).max(3600000).default(60000),
|
|
32
|
+
searchWorkLimit: z.number().int().min(1).max(10000).default(1000),
|
|
33
|
+
attachmentLimits: attachmentLimitsSchema.default({ count: 10, perFileBytes: 2097152, totalBytes: 5242880, downloadBytes: 8388608 }),
|
|
34
|
+
sentFolder: z.string().min(1).max(200).optional(),
|
|
35
|
+
sentCopyPolicy: z.enum(["provider-managed", "imap-append"]).default("provider-managed"),
|
|
36
|
+
sentCopyMaxBytes: z.number().int().min(1).max(50331648).default(8388608),
|
|
37
|
+
}).strict();
|
|
38
|
+
const yamlFileLimit = 262144;
|
|
39
|
+
const yamlFileSchema = z.object({
|
|
40
|
+
version: z.literal(1),
|
|
41
|
+
accounts: z.array(z.object({ accountId: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/) }).passthrough()).max(100),
|
|
42
|
+
}).strict();
|
|
43
|
+
function parseAccount(input) {
|
|
44
|
+
const parsed = accountSchema.safeParse(input);
|
|
45
|
+
if (!parsed.success)
|
|
46
|
+
throw new EmailError("EMAIL_INVALID_INPUT");
|
|
47
|
+
if (parsed.data.sentCopyPolicy === "imap-append" && !parsed.data.sentFolder)
|
|
48
|
+
throw new EmailError("EMAIL_INVALID_INPUT");
|
|
49
|
+
for (const connection of [parsed.data.imap, parsed.data.smtp]) {
|
|
50
|
+
if (connection.tls === "none" && (!parsed.data.allowInsecureLocalDevelopment ||
|
|
51
|
+
!["127.0.0.1", "::1", "localhost"].includes(connection.host.toLowerCase()))) {
|
|
52
|
+
throw new EmailError("EMAIL_INVALID_INPUT");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return parsed.data;
|
|
56
|
+
}
|
|
57
|
+
function startupAccounts(file, scope) {
|
|
58
|
+
let descriptor;
|
|
59
|
+
try {
|
|
60
|
+
descriptor = openSync(file, constants.O_RDONLY | constants.O_NONBLOCK);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (error.code === "ENOENT")
|
|
64
|
+
return [];
|
|
65
|
+
throw new EmailError("EMAIL_STORAGE_FAILED");
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const stat = fstatSync(descriptor);
|
|
69
|
+
if (!stat.isFile())
|
|
70
|
+
throw new EmailError("EMAIL_INVALID_INPUT");
|
|
71
|
+
if (stat.size > yamlFileLimit)
|
|
72
|
+
throw new EmailError("EMAIL_RESOURCE_LIMIT");
|
|
73
|
+
const bytes = Buffer.alloc(yamlFileLimit + 1);
|
|
74
|
+
let length = 0;
|
|
75
|
+
while (length < bytes.length) {
|
|
76
|
+
const count = readSync(descriptor, bytes, length, bytes.length - length, null);
|
|
77
|
+
if (!count)
|
|
78
|
+
break;
|
|
79
|
+
length += count;
|
|
80
|
+
}
|
|
81
|
+
if (length > yamlFileLimit)
|
|
82
|
+
throw new EmailError("EMAIL_RESOURCE_LIMIT");
|
|
83
|
+
const document = parseDocument(bytes.toString("utf8", 0, length), { uniqueKeys: true, strict: true });
|
|
84
|
+
if (document.errors.length || document.warnings.length)
|
|
85
|
+
throw new EmailError("EMAIL_INVALID_INPUT");
|
|
86
|
+
const parsed = yamlFileSchema.safeParse(document.toJS({ maxAliasCount: 0 }));
|
|
87
|
+
if (!parsed.success)
|
|
88
|
+
throw new EmailError("EMAIL_INVALID_INPUT");
|
|
89
|
+
const ids = new Set();
|
|
90
|
+
const pendingSecrets = new Map();
|
|
91
|
+
const configured = parsed.data.accounts.map(({ accountId, ...input }) => {
|
|
92
|
+
if (ids.has(accountId))
|
|
93
|
+
throw new EmailError("EMAIL_INVALID_INPUT");
|
|
94
|
+
ids.add(accountId);
|
|
95
|
+
const normalized = structuredClone(input);
|
|
96
|
+
for (const protocol of ["imap", "smtp"]) {
|
|
97
|
+
const connection = normalized[protocol];
|
|
98
|
+
const auth = connection?.auth;
|
|
99
|
+
if (auth?.type !== "password")
|
|
100
|
+
continue;
|
|
101
|
+
let password = auth.password;
|
|
102
|
+
if (password === undefined && typeof auth.secretRef === "string"
|
|
103
|
+
&& !auth.secretRef.startsWith("env:") && !auth.secretRef.startsWith("local:")) {
|
|
104
|
+
password = auth.secretRef;
|
|
105
|
+
}
|
|
106
|
+
if (password === undefined)
|
|
107
|
+
continue;
|
|
108
|
+
if (typeof password !== "string" || !password.length || (auth.password !== undefined && auth.secretRef !== undefined))
|
|
109
|
+
throw new EmailError("EMAIL_INVALID_INPUT");
|
|
110
|
+
const reference = `local:${createHash("sha256").update(JSON.stringify([scope, accountId, protocol])).digest("hex")}`;
|
|
111
|
+
const gmailAppPassword = connection?.host?.endsWith(".gmail.com") && /^[a-z]{4}( [a-z]{4}){3}$/.test(password);
|
|
112
|
+
pendingSecrets.set(reference, gmailAppPassword ? password.replaceAll(" ", "") : password);
|
|
113
|
+
delete auth.password;
|
|
114
|
+
auth.secretRef = reference;
|
|
115
|
+
}
|
|
116
|
+
return { ...parseAccount(normalized), accountId, scope };
|
|
117
|
+
});
|
|
118
|
+
for (const [reference, secret] of pendingSecrets)
|
|
119
|
+
localSecrets.set(reference, secret);
|
|
120
|
+
return configured;
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
if (error instanceof EmailError)
|
|
124
|
+
throw error;
|
|
125
|
+
throw new EmailError("EMAIL_INVALID_INPUT");
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
closeSync(descriptor);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/** Host-owned configuration. Reads return copies; mutations replace the file atomically. */
|
|
132
|
+
export class EmailAccounts {
|
|
133
|
+
scope;
|
|
134
|
+
file;
|
|
135
|
+
state;
|
|
136
|
+
listeners = new Set();
|
|
137
|
+
constructor(cwd) {
|
|
138
|
+
this.scope = path.resolve(cwd);
|
|
139
|
+
this.file = path.join(storeRootFor(cwd), "email-accounts.json");
|
|
140
|
+
this.state = { version: 1, scope: this.scope, accounts: [], health: {} };
|
|
141
|
+
if (existsSync(this.file)) {
|
|
142
|
+
try {
|
|
143
|
+
const saved = JSON.parse(readFileSync(this.file, "utf8"));
|
|
144
|
+
if (saved.version !== 1 || saved.scope !== this.scope || !Array.isArray(saved.accounts)) {
|
|
145
|
+
throw new Error();
|
|
146
|
+
}
|
|
147
|
+
this.state = { ...saved, accounts: saved.accounts.map(({ accountId, scope, ...input }) => {
|
|
148
|
+
if (typeof accountId !== "string" || scope !== this.scope)
|
|
149
|
+
throw new Error();
|
|
150
|
+
return { ...parseAccount(input), accountId, scope };
|
|
151
|
+
}) };
|
|
152
|
+
const ids = this.state.accounts.map(account => account.accountId);
|
|
153
|
+
const managed = saved.yamlManagedAccountIds ?? [];
|
|
154
|
+
if (new Set(ids).size !== ids.length || !Array.isArray(managed) || new Set(managed).size !== managed.length
|
|
155
|
+
|| managed.some(id => typeof id !== "string" || !ids.includes(id)))
|
|
156
|
+
throw new Error();
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
throw new EmailError("EMAIL_INVALID_INPUT");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
this.syncStartupAccounts(startupAccounts(path.join(this.scope, "email.yaml"), this.scope));
|
|
163
|
+
}
|
|
164
|
+
list() {
|
|
165
|
+
return structuredClone(this.state.accounts);
|
|
166
|
+
}
|
|
167
|
+
get(accountId, requireEnabled = true) {
|
|
168
|
+
const account = this.state.accounts.find(a => a.accountId === accountId && a.scope === this.scope);
|
|
169
|
+
if (!account)
|
|
170
|
+
throw new EmailError("EMAIL_ACCOUNT_NOT_FOUND");
|
|
171
|
+
if (requireEnabled && !account.enabled)
|
|
172
|
+
throw new EmailError("EMAIL_ACCOUNT_DISABLED");
|
|
173
|
+
return structuredClone(account);
|
|
174
|
+
}
|
|
175
|
+
create(input) {
|
|
176
|
+
const account = { ...parseAccount(input), accountId: randomUUID(), scope: this.scope };
|
|
177
|
+
this.state.accounts.push(account);
|
|
178
|
+
this.persist();
|
|
179
|
+
this.notify(account.accountId);
|
|
180
|
+
return structuredClone(account);
|
|
181
|
+
}
|
|
182
|
+
update(accountId, patch) {
|
|
183
|
+
this.requireHttpAccount(accountId);
|
|
184
|
+
const { accountId: _id, scope: _scope, ...current } = this.get(accountId, false);
|
|
185
|
+
if (!patch || typeof patch !== "object" || Array.isArray(patch))
|
|
186
|
+
throw new EmailError("EMAIL_INVALID_INPUT");
|
|
187
|
+
const account = { ...parseAccount({ ...current, ...patch }), accountId, scope: this.scope };
|
|
188
|
+
this.state.accounts = this.state.accounts.map(a => a.accountId === accountId ? account : a);
|
|
189
|
+
delete this.state.health[accountId];
|
|
190
|
+
this.persist();
|
|
191
|
+
this.notify(accountId);
|
|
192
|
+
return structuredClone(account);
|
|
193
|
+
}
|
|
194
|
+
remove(accountId) {
|
|
195
|
+
this.requireHttpAccount(accountId);
|
|
196
|
+
this.get(accountId, false);
|
|
197
|
+
this.state.accounts = this.state.accounts.filter(a => a.accountId !== accountId);
|
|
198
|
+
delete this.state.health[accountId];
|
|
199
|
+
this.persist();
|
|
200
|
+
this.notify(accountId);
|
|
201
|
+
}
|
|
202
|
+
health(accountId) {
|
|
203
|
+
this.get(accountId, false);
|
|
204
|
+
return structuredClone(this.state.health[accountId]);
|
|
205
|
+
}
|
|
206
|
+
recordHealth(status) {
|
|
207
|
+
this.get(status.accountId);
|
|
208
|
+
this.state.health[status.accountId] = structuredClone(status);
|
|
209
|
+
this.persist();
|
|
210
|
+
}
|
|
211
|
+
onChange(listener) {
|
|
212
|
+
this.listeners.add(listener);
|
|
213
|
+
return () => this.listeners.delete(listener);
|
|
214
|
+
}
|
|
215
|
+
notify(accountId) {
|
|
216
|
+
for (const listener of this.listeners)
|
|
217
|
+
listener(accountId);
|
|
218
|
+
}
|
|
219
|
+
requireHttpAccount(accountId) {
|
|
220
|
+
if (this.state.yamlManagedAccountIds?.includes(accountId))
|
|
221
|
+
throw new EmailError("EMAIL_OPERATION_CONFLICT");
|
|
222
|
+
}
|
|
223
|
+
syncStartupAccounts(configured) {
|
|
224
|
+
const managed = new Set(this.state.yamlManagedAccountIds ?? []);
|
|
225
|
+
for (const account of configured) {
|
|
226
|
+
if (!managed.has(account.accountId) && this.state.accounts.some(saved => saved.accountId === account.accountId)) {
|
|
227
|
+
throw new EmailError("EMAIL_OPERATION_CONFLICT");
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const declared = new Map(configured.map(account => [account.accountId, account]));
|
|
231
|
+
const changed = new Set();
|
|
232
|
+
const accounts = this.state.accounts.map(saved => {
|
|
233
|
+
const replacement = declared.get(saved.accountId);
|
|
234
|
+
declared.delete(saved.accountId);
|
|
235
|
+
const account = replacement ?? (managed.has(saved.accountId) ? { ...saved, enabled: false } : saved);
|
|
236
|
+
if (JSON.stringify(account) !== JSON.stringify(saved))
|
|
237
|
+
changed.add(saved.accountId);
|
|
238
|
+
return account;
|
|
239
|
+
});
|
|
240
|
+
for (const account of declared.values()) {
|
|
241
|
+
accounts.push(account);
|
|
242
|
+
changed.add(account.accountId);
|
|
243
|
+
}
|
|
244
|
+
for (const account of configured)
|
|
245
|
+
managed.add(account.accountId);
|
|
246
|
+
if (!changed.size)
|
|
247
|
+
return;
|
|
248
|
+
const next = { ...this.state, accounts, health: { ...this.state.health }, yamlManagedAccountIds: [...managed] };
|
|
249
|
+
for (const accountId of changed)
|
|
250
|
+
delete next.health[accountId];
|
|
251
|
+
try {
|
|
252
|
+
this.persist(next);
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
throw new EmailError("EMAIL_STORAGE_FAILED");
|
|
256
|
+
}
|
|
257
|
+
this.state = next;
|
|
258
|
+
}
|
|
259
|
+
persist(state = this.state) {
|
|
260
|
+
mkdirSync(path.dirname(this.file), { recursive: true, mode: 0o700 });
|
|
261
|
+
const temporary = `${this.file}.${randomUUID()}.tmp`;
|
|
262
|
+
try {
|
|
263
|
+
writeFileSync(temporary, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600 });
|
|
264
|
+
renameSync(temporary, this.file);
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
try {
|
|
268
|
+
unlinkSync(temporary);
|
|
269
|
+
}
|
|
270
|
+
catch { /* A failed write might not create a temporary file. */ }
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
export function resolveEmailSecret(connection, env = process.env) {
|
|
276
|
+
if (connection.auth.type === "oauth2" && connection.auth.expiresAt &&
|
|
277
|
+
Date.parse(connection.auth.expiresAt) <= Date.now())
|
|
278
|
+
throw new EmailError("EMAIL_TOKEN_EXPIRED");
|
|
279
|
+
const secret = connection.auth.secretRef.startsWith("local:")
|
|
280
|
+
? localSecrets.get(connection.auth.secretRef)
|
|
281
|
+
: env[connection.auth.secretRef.slice(4)];
|
|
282
|
+
if (!secret)
|
|
283
|
+
throw new EmailError("EMAIL_AUTH_FAILED");
|
|
284
|
+
return secret;
|
|
285
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { EmailEvents, type ReceiveMailbox } from "./events.js";
|
|
2
|
+
import { EmailSubmissions } from "./submissions.js";
|
|
3
|
+
import { type MailRecord, type MailQuery } from "./messages.js";
|
|
4
|
+
import { EmailAccounts, type EmailAccount } from "./accounts.js";
|
|
5
|
+
import { type EmailMailbox, type EmailEventSource, type EmailAccountStatus, type EmailMessageRef, type EmailMessage, type SendEmailInput, type SendEmailResult, type ReplyToEmailInput, type SearchEmailsInput, type SearchEmailsResult, type EmailReceivedEvent, type EmailArtifactContext, type PreparedEmailAttachment, type DownloadEmailAttachmentInput, type DownloadEmailAttachmentResult } from "./port.js";
|
|
6
|
+
/** Both adapters validate account scope before any operation, including unsupported operations. */
|
|
7
|
+
export declare abstract class AccountEmailAdapter implements EmailMailbox, EmailEventSource {
|
|
8
|
+
protected readonly accounts: EmailAccounts;
|
|
9
|
+
readonly submissions: EmailSubmissions;
|
|
10
|
+
readonly events: EmailEvents;
|
|
11
|
+
protected readonly cursorKey: NonSharedBuffer;
|
|
12
|
+
private stopped;
|
|
13
|
+
constructor(accounts: EmailAccounts);
|
|
14
|
+
abstract testAccount(accountId: string, protocol?: "imap" | "smtp" | "both"): Promise<EmailAccountStatus>;
|
|
15
|
+
getReceivedEvent(ref: EmailMessageRef): Promise<EmailReceivedEvent>;
|
|
16
|
+
protected account(accountId: string): EmailAccount;
|
|
17
|
+
send(input: SendEmailInput, context?: EmailArtifactContext): Promise<SendEmailResult>;
|
|
18
|
+
private sendValidated;
|
|
19
|
+
protected abstract submit(account: EmailAccount, input: SendEmailInput, operationId: string, attachments: PreparedEmailAttachment[], mime?: Buffer): Promise<SendEmailResult>;
|
|
20
|
+
protected abstract appendSent(account: EmailAccount, mime: Buffer): Promise<void>;
|
|
21
|
+
reply(input: ReplyToEmailInput, context?: EmailArtifactContext): Promise<SendEmailResult>;
|
|
22
|
+
downloadAttachment(input: DownloadEmailAttachmentInput, context?: EmailArtifactContext): Promise<DownloadEmailAttachmentResult>;
|
|
23
|
+
protected abstract attachmentSource(account: EmailAccount, ref: EmailMessageRef): Promise<Buffer>;
|
|
24
|
+
abstract getMessage(ref: EmailMessageRef): Promise<EmailMessage>;
|
|
25
|
+
abstract search(input: SearchEmailsInput): Promise<SearchEmailsResult>;
|
|
26
|
+
protected abstract openReceiver(account: EmailAccount, folder: string, signal: () => void): Promise<ReceiveMailbox>;
|
|
27
|
+
start(emit: (event: EmailReceivedEvent) => Promise<void>): Promise<void>;
|
|
28
|
+
stop(): Promise<void>;
|
|
29
|
+
protected searchPosition(uid: number, high: number, last: number | undefined, query: MailQuery): boolean;
|
|
30
|
+
protected status(accountId: string): EmailAccountStatus;
|
|
31
|
+
}
|
|
32
|
+
export declare class InMemoryEmailAdapter extends AccountEmailAdapter {
|
|
33
|
+
private readonly signals;
|
|
34
|
+
private readonly mailboxes;
|
|
35
|
+
/** Provider fixture data. Content is held only by this test adapter. */
|
|
36
|
+
seedMailbox(accountId: string, messages: MailRecord[], mailbox?: string, generation?: string): void;
|
|
37
|
+
protected openReceiver(account: EmailAccount, folder: string, signal: () => void): Promise<ReceiveMailbox>;
|
|
38
|
+
search(input: SearchEmailsInput): Promise<SearchEmailsResult>;
|
|
39
|
+
getMessage(ref: EmailMessageRef): Promise<EmailMessage>;
|
|
40
|
+
readonly sent: SendEmailInput[];
|
|
41
|
+
readonly sentAttachments: PreparedEmailAttachment[][];
|
|
42
|
+
protected attachmentSource(account: EmailAccount, ref: EmailMessageRef): Promise<Buffer>;
|
|
43
|
+
protected appendSent(account: EmailAccount, mime: Buffer): Promise<void>;
|
|
44
|
+
protected submit(_account: EmailAccount, input: SendEmailInput, operationId: string, attachments: PreparedEmailAttachment[], _mime?: Buffer): Promise<SendEmailResult>;
|
|
45
|
+
testAccount(accountId: string, protocol?: "imap" | "smtp" | "both"): Promise<EmailAccountStatus>;
|
|
46
|
+
}
|
|
47
|
+
export declare class LocalEmailAdapter extends AccountEmailAdapter {
|
|
48
|
+
private readonly env;
|
|
49
|
+
private readonly active;
|
|
50
|
+
private unsubscribe?;
|
|
51
|
+
constructor(accounts: EmailAccounts, env?: NodeJS.ProcessEnv);
|
|
52
|
+
start(emit: (event: EmailReceivedEvent) => Promise<void>): Promise<void>;
|
|
53
|
+
protected openReceiver(account: EmailAccount, folder: string, signal: () => void): Promise<ReceiveMailbox>;
|
|
54
|
+
private readMailbox;
|
|
55
|
+
private readMessage;
|
|
56
|
+
private nextSearchUid;
|
|
57
|
+
search(input: SearchEmailsInput): Promise<SearchEmailsResult>;
|
|
58
|
+
getMessage(ref: EmailMessageRef): Promise<EmailMessage>;
|
|
59
|
+
protected attachmentSource(account: EmailAccount, ref: EmailMessageRef): Promise<Buffer>;
|
|
60
|
+
protected appendSent(account: EmailAccount, mime: Buffer): Promise<void>;
|
|
61
|
+
protected submit(account: EmailAccount, input: SendEmailInput, operationId: string, attachments: PreparedEmailAttachment[], mime?: Buffer): Promise<SendEmailResult>;
|
|
62
|
+
testAccount(accountId: string, protocol?: "imap" | "smtp" | "both"): Promise<EmailAccountStatus>;
|
|
63
|
+
stop(): Promise<void>;
|
|
64
|
+
private cancel;
|
|
65
|
+
private receiveWork;
|
|
66
|
+
private bounded;
|
|
67
|
+
private testImap;
|
|
68
|
+
private testSmtp;
|
|
69
|
+
}
|