@pattern-js/mod-email 0.3.0-main.4a77893

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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +59 -0
  3. package/dist/app.d.ts +13 -0
  4. package/dist/app.d.ts.map +1 -0
  5. package/dist/app.js +172 -0
  6. package/dist/app.js.map +1 -0
  7. package/dist/config.d.ts +52 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +85 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/delivery.d.ts +26 -0
  12. package/dist/delivery.d.ts.map +1 -0
  13. package/dist/delivery.js +83 -0
  14. package/dist/delivery.js.map +1 -0
  15. package/dist/index.d.ts +16 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +15 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/markdown.d.ts +17 -0
  20. package/dist/markdown.d.ts.map +1 -0
  21. package/dist/markdown.js +173 -0
  22. package/dist/markdown.js.map +1 -0
  23. package/dist/mod.d.ts +20 -0
  24. package/dist/mod.d.ts.map +1 -0
  25. package/dist/mod.js +57 -0
  26. package/dist/mod.js.map +1 -0
  27. package/dist/ops/account.d.ts +4 -0
  28. package/dist/ops/account.d.ts.map +1 -0
  29. package/dist/ops/account.js +33 -0
  30. package/dist/ops/account.js.map +1 -0
  31. package/dist/ops/send.d.ts +4 -0
  32. package/dist/ops/send.d.ts.map +1 -0
  33. package/dist/ops/send.js +57 -0
  34. package/dist/ops/send.js.map +1 -0
  35. package/dist/ops/shared.d.ts +8 -0
  36. package/dist/ops/shared.d.ts.map +1 -0
  37. package/dist/ops/shared.js +18 -0
  38. package/dist/ops/shared.js.map +1 -0
  39. package/dist/service.d.ts +51 -0
  40. package/dist/service.d.ts.map +1 -0
  41. package/dist/service.js +156 -0
  42. package/dist/service.js.map +1 -0
  43. package/dist/settings.d.ts +18 -0
  44. package/dist/settings.d.ts.map +1 -0
  45. package/dist/settings.js +122 -0
  46. package/dist/settings.js.map +1 -0
  47. package/dist/types.d.ts +133 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +63 -0
  50. package/dist/types.js.map +1 -0
  51. package/dist/well-known.d.ts +34 -0
  52. package/dist/well-known.d.ts.map +1 -0
  53. package/dist/well-known.js +28 -0
  54. package/dist/well-known.js.map +1 -0
  55. package/docs/guides/delivery.md +57 -0
  56. package/docs/index.md +95 -0
  57. package/docs/ops/email.account.md +1 -0
  58. package/docs/ops/email.send.md +1 -0
  59. package/package.json +54 -0
@@ -0,0 +1,156 @@
1
+ /**
2
+ * @pattern-js/mod-email — the contract surface.
3
+ *
4
+ * Driver mods (mod-email-resend, mod-email-smtp, …) register an
5
+ * `EmailDriverSpec` here in their `ready()` (all setups run before any ready,
6
+ * so the service always exists by then — mirroring how login methods register
7
+ * on mod-identity). `send` resolves the account by NAME at send time: sourced
8
+ * secrets (vault/env), markdown → html+text, attachments → bytes, then the
9
+ * driver. Secret VALUES never sit in workflow values or persisted config.
10
+ */
11
+ import { renderEmailMarkdown } from "./markdown.js";
12
+ import { DEFAULT_ACCOUNT } from "./config.js";
13
+ import { blobStore, vaultLike } from "./well-known.js";
14
+ export class DefaultEmailService {
15
+ config;
16
+ registry = new Map();
17
+ constructor(config) {
18
+ this.config = config;
19
+ }
20
+ registerDriver(spec) {
21
+ this.registry.set(spec.id, spec);
22
+ }
23
+ drivers() {
24
+ return [...this.registry.values()].map(({ send: _send, ...info }) => info);
25
+ }
26
+ driver(id) {
27
+ return this.registry.get(id);
28
+ }
29
+ async send(input, ctx) {
30
+ const name = typeof input.account === "string" ? input.account : (input.account?.account ?? DEFAULT_ACCOUNT);
31
+ const account = this.config.account(name);
32
+ if (!account) {
33
+ throw new Error(`mod-email: no account "${name}" is configured — add it in admin → System → Email.`);
34
+ }
35
+ return this.sendVia(account, input, ctx);
36
+ }
37
+ async testAccount(draft, to, ctx) {
38
+ try {
39
+ const { messageId } = await this.sendVia(draft, {
40
+ to,
41
+ subject: "Pattern test email",
42
+ markdown: `# It works\n\nThis is a test email from your Pattern admin — the ` +
43
+ `"${draft.name}" account (${draft.provider}) delivered it.`,
44
+ }, ctx);
45
+ return { ok: true, messageId };
46
+ }
47
+ catch (err) {
48
+ return { ok: false, detail: err instanceof Error ? err.message : String(err) };
49
+ }
50
+ }
51
+ /* ── internals ─────────────────────────────────────────────────────── */
52
+ async sendVia(account, input, ctx) {
53
+ const driver = this.registry.get(account.provider);
54
+ if (!driver) {
55
+ throw new Error(`mod-email: account "${account.name}" uses provider "${account.provider}" but no such driver is registered — ` +
56
+ `install its mod (e.g. @pattern-js/mod-email-${account.provider}) and list it in pattern.config.json.`);
57
+ }
58
+ const creds = await this.resolveSecrets(driver, account, ctx);
59
+ const message = await this.normalize(input, account, ctx);
60
+ const { messageId } = await driver.send(message, creds, account.options, ctx);
61
+ return { messageId, provider: account.provider, account: account.name };
62
+ }
63
+ /** Resolve every sourced secret the account carries; enforce the driver's required fields. */
64
+ async resolveSecrets(driver, account, ctx) {
65
+ for (const field of driver.secrets.filter((s) => s.required !== false)) {
66
+ if (!account.secrets[field.field]) {
67
+ throw new Error(`mod-email: account "${account.name}" is missing the "${field.field}" secret its ${driver.label} driver requires.`);
68
+ }
69
+ }
70
+ const creds = {};
71
+ for (const [field, ref] of Object.entries(account.secrets)) {
72
+ creds[field] = await this.resolveSourced(ctx, ref);
73
+ }
74
+ return creds;
75
+ }
76
+ async resolveSourced(ctx, ref) {
77
+ if (ref.source === "env") {
78
+ const v = ctx.env[ref.key];
79
+ if (v)
80
+ return v;
81
+ throw new Error(`mod-email: env var "${ref.key}" is not set.`);
82
+ }
83
+ const vault = vaultLike(ctx);
84
+ if (vault?.unlocked() && (await vault.has(ref.key).catch(() => false)))
85
+ return vault.read(ref.key);
86
+ throw new Error(`mod-email: no vault secret "${ref.key}" — add it in admin → System → Secrets (vault must be unlocked).`);
87
+ }
88
+ /** to/cc/bcc listed, bodies final (explicit html/text win over markdown per part), attachments as bytes. */
89
+ async normalize(input, account, ctx) {
90
+ const list = (v) => v === undefined ? undefined : (Array.isArray(v) ? v : [v]).filter((s) => s.trim().length > 0);
91
+ const to = list(input.to) ?? [];
92
+ if (!to.length)
93
+ throw new Error("mod-email: `to` must name at least one recipient.");
94
+ let html = input.html;
95
+ let text = input.text;
96
+ if (input.markdown !== undefined) {
97
+ const rendered = renderEmailMarkdown(input.markdown);
98
+ html ??= rendered.html;
99
+ text ??= rendered.text;
100
+ }
101
+ if (html === undefined && text === undefined) {
102
+ throw new Error("mod-email: provide a body — `markdown`, `html`, or `text`.");
103
+ }
104
+ const attachments = input.attachments?.length
105
+ ? await Promise.all(input.attachments.map((a, i) => this.resolveAttachment(a, i, ctx)))
106
+ : undefined;
107
+ return {
108
+ from: input.from ?? account.from,
109
+ to,
110
+ cc: list(input.cc),
111
+ bcc: list(input.bcc),
112
+ replyTo: input.replyTo,
113
+ subject: input.subject,
114
+ html,
115
+ text,
116
+ attachments,
117
+ };
118
+ }
119
+ async resolveAttachment(a, index, ctx) {
120
+ if ("blobId" in a) {
121
+ const hit = await blobStore(ctx).blobs.get(a.blobId);
122
+ if (!hit)
123
+ throw new Error(`mod-email: no blob "${a.blobId}" behind the attachment reference.`);
124
+ const mime = a.mime ?? hit.meta.mime ?? "application/octet-stream";
125
+ return {
126
+ filename: a.filename ?? defaultFilename(index, mime),
127
+ content: new Uint8Array(await new Response(hit.stream).arrayBuffer()),
128
+ mime,
129
+ };
130
+ }
131
+ if ("bytes" in a) {
132
+ return { filename: a.filename ?? defaultFilename(index, a.mime), content: a.bytes, mime: a.mime };
133
+ }
134
+ const isText = typeof a.content === "string";
135
+ return {
136
+ filename: a.filename,
137
+ content: isText ? new TextEncoder().encode(a.content) : a.content,
138
+ mime: a.mime ?? (isText ? "text/plain" : "application/octet-stream"),
139
+ };
140
+ }
141
+ }
142
+ const EXT = {
143
+ "image/png": ".png",
144
+ "image/jpeg": ".jpg",
145
+ "image/webp": ".webp",
146
+ "image/gif": ".gif",
147
+ "audio/mpeg": ".mp3",
148
+ "audio/wav": ".wav",
149
+ "video/mp4": ".mp4",
150
+ "application/pdf": ".pdf",
151
+ "text/plain": ".txt",
152
+ };
153
+ function defaultFilename(index, mime) {
154
+ return `attachment-${index + 1}${EXT[mime.split(";")[0].trim()] ?? ""}`;
155
+ }
156
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,eAAe,EAA2B,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAmCvD,MAAM,OAAO,mBAAmB;IAGD;IAFZ,QAAQ,GAAG,IAAI,GAAG,EAA2B,CAAC;IAE/D,YAA6B,MAA0B;QAA1B,WAAM,GAAN,MAAM,CAAoB;IAAG,CAAC;IAE3D,cAAc,CAAC,IAAqB;QAClC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,OAAO;QACL,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;IAC7E,CAAC;IAED,MAAM,CAAC,EAAU;QACf,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,KAAgB,EAAE,GAAc;QACzC,MAAM,IAAI,GAAG,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,IAAI,eAAe,CAAC,CAAC;QAC7G,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,0BAA0B,IAAI,qDAAqD,CAAC,CAAC;QACvG,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,KAAmB,EAAE,EAAU,EAAE,GAAc;QAC/D,IAAI,CAAC;YACH,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,IAAI,CAAC,OAAO,CACtC,KAAK,EACL;gBACE,EAAE;gBACF,OAAO,EAAE,oBAAoB;gBAC7B,QAAQ,EACN,mEAAmE;oBACnE,IAAI,KAAK,CAAC,IAAI,cAAc,KAAK,CAAC,QAAQ,iBAAiB;aAC9D,EACD,GAAG,CACJ,CAAC;YACF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;QACjC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACjF,CAAC;IACH,CAAC;IAED,0EAA0E;IAElE,KAAK,CAAC,OAAO,CAAC,OAAqB,EAAE,KAAgB,EAAE,GAAc;QAC3E,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACnD,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CACb,uBAAuB,OAAO,CAAC,IAAI,oBAAoB,OAAO,CAAC,QAAQ,uCAAuC;gBAC5G,+CAA+C,OAAO,CAAC,QAAQ,uCAAuC,CACzG,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;QAC9D,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;QAC1D,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAC9E,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;IAC1E,CAAC;IAED,8FAA8F;IACtF,KAAK,CAAC,cAAc,CAC1B,MAAuB,EACvB,OAAqB,EACrB,GAAc;QAEd,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,KAAK,CAAC,EAAE,CAAC;YACvE,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;gBAClC,MAAM,IAAI,KAAK,CACb,uBAAuB,OAAO,CAAC,IAAI,qBAAqB,KAAK,CAAC,KAAK,gBAAgB,MAAM,CAAC,KAAK,mBAAmB,CACnH,CAAC;YACJ,CAAC;QACH,CAAC;QACD,MAAM,KAAK,GAA2B,EAAE,CAAC;QACzC,KAAK,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3D,KAAK,CAAC,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACrD,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAEO,KAAK,CAAC,cAAc,CAAC,GAAc,EAAE,GAAc;QACzD,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;YACzB,MAAM,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC3B,IAAI,CAAC;gBAAE,OAAO,CAAC,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,uBAAuB,GAAG,CAAC,GAAG,eAAe,CAAC,CAAC;QACjE,CAAC;QACD,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnG,MAAM,IAAI,KAAK,CAAC,+BAA+B,GAAG,CAAC,GAAG,kEAAkE,CAAC,CAAC;IAC5H,CAAC;IAED,4GAA4G;IACpG,KAAK,CAAC,SAAS,CAAC,KAAgB,EAAE,OAAqB,EAAE,GAAc;QAC7E,MAAM,IAAI,GAAG,CAAC,CAAgC,EAAwB,EAAE,CACtE,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAEhG,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;QAChC,IAAI,CAAC,EAAE,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QAErF,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;QACtB,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;QACtB,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YACjC,MAAM,QAAQ,GAAG,mBAAmB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YACrD,IAAI,KAAK,QAAQ,CAAC,IAAI,CAAC;YACvB,IAAI,KAAK,QAAQ,CAAC,IAAI,CAAC;QACzB,CAAC;QACD,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YAC7C,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;QAChF,CAAC;QAED,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,EAAE,MAAM;YAC3C,CAAC,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;YACvF,CAAC,CAAC,SAAS,CAAC;QAEd,OAAO;YACL,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI;YAChC,EAAE;YACF,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAClB,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC;YACpB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,IAAI;YACJ,IAAI;YACJ,WAAW;SACZ,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,iBAAiB,CAAC,CAAkB,EAAE,KAAa,EAAE,GAAc;QAC/E,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;YAClB,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YACrD,IAAI,CAAC,GAAG;gBAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC,MAAM,oCAAoC,CAAC,CAAC;YAC/F,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,0BAA0B,CAAC;YACnE,OAAO;gBACL,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC;gBACpD,OAAO,EAAE,IAAI,UAAU,CAAC,MAAM,IAAI,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;gBACrE,IAAI;aACL,CAAC;QACJ,CAAC;QACD,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;YACjB,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,eAAe,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACpG,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC;QAC7C,OAAO;YACL,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,OAAiB,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,OAAsB;YAC3F,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,0BAA0B,CAAC;SACrE,CAAC;IACJ,CAAC;CACF;AAED,MAAM,GAAG,GAA2B;IAClC,WAAW,EAAE,MAAM;IACnB,YAAY,EAAE,MAAM;IACpB,YAAY,EAAE,OAAO;IACrB,WAAW,EAAE,MAAM;IACnB,YAAY,EAAE,MAAM;IACpB,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,MAAM;IACnB,iBAAiB,EAAE,MAAM;IACzB,YAAY,EAAE,MAAM;CACrB,CAAC;AAEF,SAAS,eAAe,CAAC,KAAa,EAAE,IAAY;IAClD,OAAO,cAAc,KAAK,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @pattern-js/mod-email — the control-plane ops + admin routes behind the
3
+ * Email page.
4
+ *
5
+ * One persisted concept (see config.ts): ACCOUNTS — memorable names, each a
6
+ * fully self-contained sender (driver + from + sourced secrets + options).
7
+ * Credentials themselves live in mod-vault's Secrets screen (or env vars);
8
+ * here we list the REGISTERED drivers + their field specs, manage accounts,
9
+ * and test one. All admin-scoped. One deliberate divergence from mod-ai's
10
+ * alias test: `email.account.test` performs a REAL send to an operator-supplied
11
+ * address — for email, end-to-end delivery is the only check that means
12
+ * anything.
13
+ */
14
+ import { type FrontendContribution, type OpDefinition, type Workflow } from "@pattern-js/core";
15
+ export declare const settingsOps: OpDefinition[];
16
+ export declare function emailAdminRoutes(): Workflow[];
17
+ export declare function emailFrontend(): FrontendContribution;
18
+ //# sourceMappingURL=settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAOL,KAAK,oBAAoB,EAEzB,KAAK,YAAY,EACjB,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAC;AAmG1B,eAAO,MAAM,WAAW,EAAE,YAAY,EAA8E,CAAC;AAIrH,wBAAgB,gBAAgB,IAAI,QAAQ,EAAE,CAU7C;AAED,wBAAgB,aAAa,IAAI,oBAAoB,CAMpD"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * @pattern-js/mod-email — the control-plane ops + admin routes behind the
3
+ * Email page.
4
+ *
5
+ * One persisted concept (see config.ts): ACCOUNTS — memorable names, each a
6
+ * fully self-contained sender (driver + from + sourced secrets + options).
7
+ * Credentials themselves live in mod-vault's Secrets screen (or env vars);
8
+ * here we list the REGISTERED drivers + their field specs, manage accounts,
9
+ * and test one. All admin-scoped. One deliberate divergence from mod-ai's
10
+ * alias test: `email.account.test` performs a REAL send to an operator-supplied
11
+ * address — for email, end-to-end delivery is the only check that means
12
+ * anything.
13
+ */
14
+ import { fromBody, fromParams, httpEndpoint, required, value, z, } from "@pattern-js/core";
15
+ import { accountSchema } from "./types.js";
16
+ import { emailConfig, emailService, maybe } from "./ops/shared.js";
17
+ import { REMOTE } from "./app.js";
18
+ // ───────────────────────────── Providers ──────────────────────────────
19
+ const providersList = {
20
+ type: "email.providers.list",
21
+ title: "email.providers.list",
22
+ description: "List the registered email drivers + their secret/option field specs (drives the account form).",
23
+ reusable: false,
24
+ config: z.object({}),
25
+ inputs: {},
26
+ outputs: { providers: value() },
27
+ execute: (ctx) => ({ providers: emailService(ctx).drivers() }),
28
+ };
29
+ // ────────────────────────────── Accounts ──────────────────────────────
30
+ const accountFields = {
31
+ name: required(z.string()),
32
+ provider: required(z.string()),
33
+ from: required(z.string()),
34
+ secrets: value(z.record(z.string(), z.record(z.string(), z.string()))),
35
+ options: value(z.record(z.string(), z.string())),
36
+ };
37
+ async function readAccountFields(ctx) {
38
+ const [name, provider, from, secrets, options] = await Promise.all([
39
+ ctx.input.value("name"),
40
+ ctx.input.value("provider"),
41
+ ctx.input.value("from"),
42
+ maybe(ctx, "secrets"),
43
+ maybe(ctx, "options"),
44
+ ]);
45
+ return accountSchema.parse({ name, provider, from, secrets: secrets ?? {}, options: options ?? {} });
46
+ }
47
+ const accountsRead = {
48
+ type: "email.accounts.read",
49
+ title: "email.accounts.read",
50
+ description: "List the configured email accounts (secret NAMES/sources only, never values).",
51
+ reusable: false,
52
+ sensitivity: "privileged",
53
+ config: z.object({}),
54
+ inputs: {},
55
+ outputs: { accounts: value() },
56
+ execute: (ctx) => ({ accounts: emailConfig(ctx).accounts() }),
57
+ };
58
+ const accountsWrite = {
59
+ type: "email.accounts.write",
60
+ title: "email.accounts.write",
61
+ description: "Create or update an email account (upsert by name).",
62
+ reusable: false,
63
+ sensitivity: "privileged",
64
+ config: z.object({}),
65
+ inputs: accountFields,
66
+ outputs: { result: value() },
67
+ execute: async (ctx) => {
68
+ const account = await readAccountFields(ctx);
69
+ await emailConfig(ctx).upsertAccount(account);
70
+ return { result: { ok: true, name: account.name } };
71
+ },
72
+ };
73
+ const accountsDelete = {
74
+ type: "email.accounts.delete",
75
+ title: "email.accounts.delete",
76
+ description: "Delete an email account by name.",
77
+ reusable: false,
78
+ sensitivity: "privileged",
79
+ config: z.object({}),
80
+ inputs: { name: required(z.string()) },
81
+ outputs: { result: value() },
82
+ execute: async (ctx) => {
83
+ await emailConfig(ctx).deleteAccount(await ctx.input.value("name"));
84
+ return { result: { ok: true } };
85
+ },
86
+ };
87
+ const accountTest = {
88
+ type: "email.account.test",
89
+ title: "email.account.test",
90
+ description: "Test an account draft by sending a REAL canned email to `to` — the only check that proves " +
91
+ "end-to-end delivery. Reports { ok, detail?, messageId? }.",
92
+ reusable: false,
93
+ sensitivity: "privileged",
94
+ config: z.object({}),
95
+ inputs: { ...accountFields, to: required(z.string()) },
96
+ outputs: { result: value() },
97
+ execute: async (ctx) => {
98
+ const [draft, to] = await Promise.all([readAccountFields(ctx), ctx.input.value("to")]);
99
+ return { result: await emailService(ctx).testAccount(draft, to, ctx) };
100
+ },
101
+ };
102
+ export const settingsOps = [providersList, accountsRead, accountsWrite, accountsDelete, accountTest];
103
+ const API = "/admin/api";
104
+ export function emailAdminRoutes() {
105
+ const auth = { scopes: ["admin"] };
106
+ const accountIn = { name: fromBody(), provider: fromBody(), from: fromBody(), secrets: fromBody(), options: fromBody() };
107
+ return [
108
+ httpEndpoint({ id: "email.route.providers", name: `Email · GET ${API}/email/providers`, method: "GET", path: `${API}/email/providers`, op: "email.providers.list", io: { out: "providers" }, auth }),
109
+ httpEndpoint({ id: "email.route.accounts.read", name: `Email · GET ${API}/email/accounts`, method: "GET", path: `${API}/email/accounts`, op: "email.accounts.read", io: { out: "accounts" }, auth }),
110
+ httpEndpoint({ id: "email.route.accounts.write", name: `Email · POST ${API}/email/accounts`, method: "POST", path: `${API}/email/accounts`, op: "email.accounts.write", io: { in: accountIn, out: "result" }, auth }),
111
+ httpEndpoint({ id: "email.route.accounts.delete", name: `Email · DELETE ${API}/email/accounts/:name`, method: "DELETE", path: `${API}/email/accounts/:name`, op: "email.accounts.delete", io: { in: { name: fromParams() }, out: "result" }, auth }),
112
+ httpEndpoint({ id: "email.route.test", name: `Email · POST ${API}/email/test`, method: "POST", path: `${API}/email/test`, op: "email.account.test", io: { in: { ...accountIn, to: fromBody() }, out: "result" }, auth }),
113
+ ];
114
+ }
115
+ export function emailFrontend() {
116
+ return {
117
+ menu: [{ category: "System", label: "Email", icon: "mail", path: "/x/email/accounts", order: 21 }],
118
+ // The Tier-2 page is just its source; the admin serves + imports it (no workflow).
119
+ pages: [{ path: "/x/email/accounts", title: "Email", module: REMOTE }],
120
+ };
121
+ }
122
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * @pattern-js/mod-email — value shapes shared across the mod and its drivers.
3
+ *
4
+ * The persisted unit is an **account** — a memorable name ("default") bound to
5
+ * a driver id, a From address, sourced secrets, and driver options. Two
6
+ * accounts of the same provider with different credentials are just two
7
+ * records. What flows on edges is an `EmailAccountRef` (the NAME, never the
8
+ * secrets) — re-pointing an account in admin re-targets every workflow.
9
+ */
10
+ import { z } from "@pattern-js/core";
11
+ import type { OpContext } from "@pattern-js/core";
12
+ export declare const secretRefSchema: z.ZodObject<{
13
+ source: z.ZodDefault<z.ZodEnum<{
14
+ vault: "vault";
15
+ env: "env";
16
+ }>>;
17
+ key: z.ZodString;
18
+ }, z.core.$strip>;
19
+ export type SecretRef = z.infer<typeof secretRefSchema>;
20
+ export declare const accountSchema: z.ZodObject<{
21
+ name: z.ZodString;
22
+ provider: z.ZodString;
23
+ from: z.ZodString;
24
+ secrets: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
25
+ source: z.ZodDefault<z.ZodEnum<{
26
+ vault: "vault";
27
+ env: "env";
28
+ }>>;
29
+ key: z.ZodString;
30
+ }, z.core.$strip>>>;
31
+ options: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
32
+ }, z.core.$strip>;
33
+ export type EmailAccount = z.infer<typeof accountSchema>;
34
+ /** What flows on edges: the account NAME; secrets resolve at send time. */
35
+ export declare const accountRefSchema: z.ZodObject<{
36
+ kind: z.ZodLiteral<"emailAccount">;
37
+ account: z.ZodString;
38
+ provider: z.ZodString;
39
+ }, z.core.$strip>;
40
+ export type EmailAccountRef = z.infer<typeof accountRefSchema>;
41
+ /** In-memory media, the shape mod-ai's generation ops output. */
42
+ export declare const mediaAttachmentSchema: z.ZodObject<{
43
+ bytes: z.ZodCustom<Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>>;
44
+ mime: z.ZodString;
45
+ kind: z.ZodOptional<z.ZodString>;
46
+ filename: z.ZodOptional<z.ZodString>;
47
+ }, z.core.$strip>;
48
+ /** A pointer into the blob store (mod-store), the shape `store.blob.put` returns. */
49
+ export declare const mediaRefAttachmentSchema: z.ZodObject<{
50
+ blobId: z.ZodString;
51
+ mime: z.ZodOptional<z.ZodString>;
52
+ filename: z.ZodOptional<z.ZodString>;
53
+ }, z.core.$strip>;
54
+ /** A literal file: string content is UTF-8 text. */
55
+ export declare const literalAttachmentSchema: z.ZodObject<{
56
+ filename: z.ZodString;
57
+ content: z.ZodUnion<readonly [z.ZodString, z.ZodCustom<Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>>]>;
58
+ mime: z.ZodOptional<z.ZodString>;
59
+ }, z.core.$strip>;
60
+ export declare const attachmentInputSchema: z.ZodUnion<readonly [z.ZodObject<{
61
+ blobId: z.ZodString;
62
+ mime: z.ZodOptional<z.ZodString>;
63
+ filename: z.ZodOptional<z.ZodString>;
64
+ }, z.core.$strip>, z.ZodObject<{
65
+ bytes: z.ZodCustom<Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>>;
66
+ mime: z.ZodString;
67
+ kind: z.ZodOptional<z.ZodString>;
68
+ filename: z.ZodOptional<z.ZodString>;
69
+ }, z.core.$strip>, z.ZodObject<{
70
+ filename: z.ZodString;
71
+ content: z.ZodUnion<readonly [z.ZodString, z.ZodCustom<Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>>]>;
72
+ mime: z.ZodOptional<z.ZodString>;
73
+ }, z.core.$strip>]>;
74
+ export type AttachmentInput = z.infer<typeof attachmentInputSchema>;
75
+ /** What drivers receive: every attachment resolved to named bytes. */
76
+ export interface EmailAttachment {
77
+ filename: string;
78
+ content: Uint8Array;
79
+ mime: string;
80
+ }
81
+ /** The message a driver's `send` receives — recipients listed, bodies final. */
82
+ export interface EmailMessage {
83
+ from: string;
84
+ to: string[];
85
+ cc?: string[];
86
+ bcc?: string[];
87
+ replyTo?: string;
88
+ subject: string;
89
+ html?: string;
90
+ text?: string;
91
+ attachments?: EmailAttachment[];
92
+ }
93
+ /**
94
+ * A driver, registered by a provider mod (`mod-email-resend`, `mod-email-smtp`,
95
+ * …) via `EmailService.registerDriver` in its `ready()`. The `secrets`/`options`
96
+ * field lists drive the auto-generated account form in admin → System → Email.
97
+ * (Field descriptors use `field` for the key — this mod's admin page is new
98
+ * code, so it doesn't inherit mod-ai's `name` naming for the same idea.)
99
+ */
100
+ export interface EmailDriverSpec {
101
+ id: string;
102
+ label: string;
103
+ secrets: Array<{
104
+ field: string;
105
+ label: string;
106
+ required?: boolean;
107
+ }>;
108
+ options: Array<{
109
+ field: string;
110
+ label: string;
111
+ required?: boolean;
112
+ placeholder?: string;
113
+ }>;
114
+ send(message: EmailMessage, creds: Record<string, string>, options: Record<string, string>, ctx: OpContext): Promise<{
115
+ messageId?: string;
116
+ }>;
117
+ }
118
+ /** The serializable driver catalog (what `email.providers.list` returns). */
119
+ export type EmailDriverInfo = Omit<EmailDriverSpec, "send">;
120
+ export interface SendInput {
121
+ account?: EmailAccountRef | string;
122
+ to: string | string[];
123
+ cc?: string | string[];
124
+ bcc?: string | string[];
125
+ replyTo?: string;
126
+ from?: string;
127
+ subject: string;
128
+ markdown?: string;
129
+ html?: string;
130
+ text?: string;
131
+ attachments?: AttachmentInput[];
132
+ }
133
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @pattern-js/mod-email — value shapes shared across the mod and its drivers.
3
+ *
4
+ * The persisted unit is an **account** — a memorable name ("default") bound to
5
+ * a driver id, a From address, sourced secrets, and driver options. Two
6
+ * accounts of the same provider with different credentials are just two
7
+ * records. What flows on edges is an `EmailAccountRef` (the NAME, never the
8
+ * secrets) — re-pointing an account in admin re-targets every workflow.
9
+ */
10
+ import { z } from "@pattern-js/core";
11
+ /* ── sourced secrets (same scheme as mod-ai; candidate for a core hoist) ── */
12
+ export const secretRefSchema = z.object({
13
+ source: z.enum(["vault", "env"]).default("vault"),
14
+ /** The vault secret name or the env-var name (never the value). */
15
+ key: z.string(),
16
+ });
17
+ /* ── accounts ─────────────────────────────────────────────────────────── */
18
+ export const accountSchema = z.object({
19
+ /** "default" is the convention the delivery workflow and agents fall back to. */
20
+ name: z.string().min(1),
21
+ /** Driver id: "resend", "smtp", … (registered by a driver mod). */
22
+ provider: z.string().min(1),
23
+ /** Default From — `App <hello@example.com>` (the email analog of modelId). */
24
+ from: z.string().min(1),
25
+ /** field → where its value lives (vault secret or env var), never the value. */
26
+ secrets: z.record(z.string(), secretRefSchema).default({}),
27
+ /** Driver options: host/port/baseUrl/… — see each driver's declared fields. */
28
+ options: z.record(z.string(), z.string()).default({}),
29
+ });
30
+ /** What flows on edges: the account NAME; secrets resolve at send time. */
31
+ export const accountRefSchema = z.object({
32
+ kind: z.literal("emailAccount"),
33
+ account: z.string(),
34
+ /** Display/validation only — the persisted record is the source of truth. */
35
+ provider: z.string(),
36
+ });
37
+ /* ── attachments (the three accepted input shapes) ────────────────────── */
38
+ const bytesSchema = z.custom((v) => v instanceof Uint8Array);
39
+ /** In-memory media, the shape mod-ai's generation ops output. */
40
+ export const mediaAttachmentSchema = z.object({
41
+ bytes: bytesSchema,
42
+ mime: z.string(),
43
+ kind: z.string().optional(),
44
+ filename: z.string().optional(),
45
+ });
46
+ /** A pointer into the blob store (mod-store), the shape `store.blob.put` returns. */
47
+ export const mediaRefAttachmentSchema = z.object({
48
+ blobId: z.string(),
49
+ mime: z.string().optional(),
50
+ filename: z.string().optional(),
51
+ });
52
+ /** A literal file: string content is UTF-8 text. */
53
+ export const literalAttachmentSchema = z.object({
54
+ filename: z.string(),
55
+ content: z.union([z.string(), bytesSchema]),
56
+ mime: z.string().optional(),
57
+ });
58
+ export const attachmentInputSchema = z.union([
59
+ mediaRefAttachmentSchema,
60
+ mediaAttachmentSchema,
61
+ literalAttachmentSchema,
62
+ ]);
63
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,kBAAkB,CAAC;AAGrC,+EAA+E;AAE/E,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;IACjD,mEAAmE;IACnE,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;CAChB,CAAC,CAAC;AAGH,6EAA6E;AAE7E,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC;IACpC,iFAAiF;IACjF,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,mEAAmE;IACnE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3B,8EAA8E;IAC9E,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,gFAAgF;IAChF,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,eAAe,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IAC1D,+EAA+E;IAC/E,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;CACtD,CAAC,CAAC;AAGH,2EAA2E;AAC3E,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC;IAC/B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,6EAA6E;IAC7E,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;CACrB,CAAC,CAAC;AAGH,6EAA6E;AAE7E,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAa,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,YAAY,UAAU,CAAC,CAAC;AAEzE,iEAAiE;AACjE,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,KAAK,EAAE,WAAW;IAClB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAChC,CAAC,CAAC;AAEH,qFAAqF;AACrF,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/C,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAChC,CAAC,CAAC;AAEH,oDAAoD;AACpD,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,WAAW,CAAC,CAAC;IAC3C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC5B,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC;IAC3C,wBAAwB;IACxB,qBAAqB;IACrB,uBAAuB;CACxB,CAAC,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @pattern-js/mod-email — service keys + duck-typed views of sibling mods.
3
+ *
4
+ * mod-vault (secrets) and mod-store (blob attachments) are looked up by their
5
+ * well-known service keys, never imported — mod-email has no hard dependency
6
+ * on either. (The VaultLike/BlobStoreLike shapes mirror mod-ai's copies; a
7
+ * future hoist to core would collapse the three.)
8
+ */
9
+ import type { OpContext } from "@pattern-js/core";
10
+ /** Driver registry + account-resolving send/test (the contract surface). */
11
+ export declare const EMAIL_SERVICE = "emailService";
12
+ /** Persisted email accounts (.pattern-data/email-config.json). */
13
+ export declare const EMAIL_CONFIG_SERVICE = "emailConfig";
14
+ export declare const VAULT_SERVICE_KEY = "vaultService";
15
+ export interface VaultLike {
16
+ unlocked(): boolean;
17
+ has(name: string): Promise<boolean>;
18
+ read(name: string): Promise<string>;
19
+ }
20
+ export declare function vaultLike(ctx: OpContext): VaultLike | undefined;
21
+ export declare const STORE_SERVICE_KEY = "storeService";
22
+ export interface BlobStoreLike {
23
+ blobs: {
24
+ get(id: string): Promise<{
25
+ meta: {
26
+ mime: string;
27
+ };
28
+ stream: ReadableStream<Uint8Array>;
29
+ } | null>;
30
+ };
31
+ }
32
+ /** Only reached when a MediaRef attachment is wired — hence the lazy throw. */
33
+ export declare function blobStore(ctx: OpContext): BlobStoreLike;
34
+ //# sourceMappingURL=well-known.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"well-known.d.ts","sourceRoot":"","sources":["../src/well-known.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD,4EAA4E;AAC5E,eAAO,MAAM,aAAa,iBAAiB,CAAC;AAC5C,kEAAkE;AAClE,eAAO,MAAM,oBAAoB,gBAAgB,CAAC;AAIlD,eAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAEhD,MAAM,WAAW,SAAS;IACxB,QAAQ,IAAI,OAAO,CAAC;IACpB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACrC;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAE/D;AAID,eAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAEhD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE;QACL,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;YAAE,IAAI,EAAE;gBAAE,IAAI,EAAE,MAAM,CAAA;aAAE,CAAC;YAAC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,CAAA;SAAE,GAAG,IAAI,CAAC,CAAC;KACjG,CAAC;CACH;AAED,+EAA+E;AAC/E,wBAAgB,SAAS,CAAC,GAAG,EAAE,SAAS,GAAG,aAAa,CAQvD"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @pattern-js/mod-email — service keys + duck-typed views of sibling mods.
3
+ *
4
+ * mod-vault (secrets) and mod-store (blob attachments) are looked up by their
5
+ * well-known service keys, never imported — mod-email has no hard dependency
6
+ * on either. (The VaultLike/BlobStoreLike shapes mirror mod-ai's copies; a
7
+ * future hoist to core would collapse the three.)
8
+ */
9
+ /** Driver registry + account-resolving send/test (the contract surface). */
10
+ export const EMAIL_SERVICE = "emailService";
11
+ /** Persisted email accounts (.pattern-data/email-config.json). */
12
+ export const EMAIL_CONFIG_SERVICE = "emailConfig";
13
+ /* ── vault (secrets) ───────────────────────────────────────────────────── */
14
+ export const VAULT_SERVICE_KEY = "vaultService";
15
+ export function vaultLike(ctx) {
16
+ return ctx.services[VAULT_SERVICE_KEY];
17
+ }
18
+ /* ── store (blob attachments) ──────────────────────────────────────────── */
19
+ export const STORE_SERVICE_KEY = "storeService";
20
+ /** Only reached when a MediaRef attachment is wired — hence the lazy throw. */
21
+ export function blobStore(ctx) {
22
+ const svc = ctx.services[STORE_SERVICE_KEY];
23
+ if (!svc) {
24
+ throw new Error("mod-email: attaching a blob reference ({blobId}) needs @pattern-js/mod-store installed — its blob store holds the bytes.");
25
+ }
26
+ return svc;
27
+ }
28
+ //# sourceMappingURL=well-known.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"well-known.js","sourceRoot":"","sources":["../src/well-known.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,4EAA4E;AAC5E,MAAM,CAAC,MAAM,aAAa,GAAG,cAAc,CAAC;AAC5C,kEAAkE;AAClE,MAAM,CAAC,MAAM,oBAAoB,GAAG,aAAa,CAAC;AAElD,8EAA8E;AAE9E,MAAM,CAAC,MAAM,iBAAiB,GAAG,cAAc,CAAC;AAQhD,MAAM,UAAU,SAAS,CAAC,GAAc;IACtC,OAAO,GAAG,CAAC,QAAQ,CAAC,iBAAiB,CAA0B,CAAC;AAClE,CAAC;AAED,8EAA8E;AAE9E,MAAM,CAAC,MAAM,iBAAiB,GAAG,cAAc,CAAC;AAQhD,+EAA+E;AAC/E,MAAM,UAAU,SAAS,CAAC,GAAc;IACtC,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,iBAAiB,CAA8B,CAAC;IACzE,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,0HAA0H,CAC3H,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}