@open-mercato/channel-ses 0.7.1-develop.7151.1.00d0391847

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 (46) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/build.mjs +7 -0
  3. package/dist/index.js +5 -0
  4. package/dist/index.js.map +7 -0
  5. package/dist/modules/channel_ses/acl.js +10 -0
  6. package/dist/modules/channel_ses/acl.js.map +7 -0
  7. package/dist/modules/channel_ses/capabilities.js +10 -0
  8. package/dist/modules/channel_ses/capabilities.js.map +7 -0
  9. package/dist/modules/channel_ses/di.js +22 -0
  10. package/dist/modules/channel_ses/di.js.map +7 -0
  11. package/dist/modules/channel_ses/index.js +13 -0
  12. package/dist/modules/channel_ses/index.js.map +7 -0
  13. package/dist/modules/channel_ses/integration.js +69 -0
  14. package/dist/modules/channel_ses/integration.js.map +7 -0
  15. package/dist/modules/channel_ses/lib/adapter.js +112 -0
  16. package/dist/modules/channel_ses/lib/adapter.js.map +7 -0
  17. package/dist/modules/channel_ses/lib/credentials.js +10 -0
  18. package/dist/modules/channel_ses/lib/credentials.js.map +7 -0
  19. package/dist/modules/channel_ses/lib/health.js +45 -0
  20. package/dist/modules/channel_ses/lib/health.js.map +7 -0
  21. package/dist/modules/channel_ses/lib/preset.js +59 -0
  22. package/dist/modules/channel_ses/lib/preset.js.map +7 -0
  23. package/dist/modules/channel_ses/lib/system-email-config.js +13 -0
  24. package/dist/modules/channel_ses/lib/system-email-config.js.map +7 -0
  25. package/dist/modules/channel_ses/setup.js +30 -0
  26. package/dist/modules/channel_ses/setup.js.map +7 -0
  27. package/jest.config.cjs +33 -0
  28. package/package.json +89 -0
  29. package/src/index.ts +1 -0
  30. package/src/modules/channel_ses/__tests__/contracts.test.ts +27 -0
  31. package/src/modules/channel_ses/acl.ts +6 -0
  32. package/src/modules/channel_ses/capabilities.ts +8 -0
  33. package/src/modules/channel_ses/di.ts +20 -0
  34. package/src/modules/channel_ses/index.ts +9 -0
  35. package/src/modules/channel_ses/integration.ts +64 -0
  36. package/src/modules/channel_ses/lib/__tests__/adapter.test.ts +94 -0
  37. package/src/modules/channel_ses/lib/__tests__/health.test.ts +51 -0
  38. package/src/modules/channel_ses/lib/__tests__/preset.test.ts +184 -0
  39. package/src/modules/channel_ses/lib/adapter.ts +154 -0
  40. package/src/modules/channel_ses/lib/credentials.ts +9 -0
  41. package/src/modules/channel_ses/lib/health.ts +53 -0
  42. package/src/modules/channel_ses/lib/preset.ts +91 -0
  43. package/src/modules/channel_ses/lib/system-email-config.ts +10 -0
  44. package/src/modules/channel_ses/setup.ts +30 -0
  45. package/tsconfig.json +9 -0
  46. package/watch.mjs +7 -0
@@ -0,0 +1,2 @@
1
+ [build:channel-ses] found 12 entry points
2
+ [build:channel-ses] built successfully
package/build.mjs ADDED
@@ -0,0 +1,7 @@
1
+ import { dirname } from 'node:path'
2
+ import { fileURLToPath } from 'node:url'
3
+ import { buildPackage } from '../../scripts/build-package.mjs'
4
+
5
+ const packageDir = dirname(fileURLToPath(import.meta.url))
6
+
7
+ await buildPackage(packageDir, { name: 'channel-ses' })
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import { metadata } from "./modules/channel_ses/index.js";
2
+ export {
3
+ metadata
4
+ };
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts"],
4
+ "sourcesContent": ["export { metadata } from './modules/channel_ses/index'\n"],
5
+ "mappings": "AAAA,SAAS,gBAAgB;",
6
+ "names": []
7
+ }
@@ -0,0 +1,10 @@
1
+ const features = [
2
+ { id: "channel_ses.view", title: "View Amazon SES channel configuration", module: "channel_ses" },
3
+ { id: "channel_ses.configure", title: "Configure Amazon SES channel defaults", module: "channel_ses" }
4
+ ];
5
+ var acl_default = features;
6
+ export {
7
+ acl_default as default,
8
+ features
9
+ };
10
+ //# sourceMappingURL=acl.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/channel_ses/acl.ts"],
4
+ "sourcesContent": ["export const features = [\n { id: 'channel_ses.view', title: 'View Amazon SES channel configuration', module: 'channel_ses' },\n { id: 'channel_ses.configure', title: 'Configure Amazon SES channel defaults', module: 'channel_ses' },\n]\n\nexport default features\n"],
5
+ "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,oBAAoB,OAAO,yCAAyC,QAAQ,cAAc;AAAA,EAChG,EAAE,IAAI,yBAAyB,OAAO,yCAAyC,QAAQ,cAAc;AACvG;AAEA,IAAO,cAAQ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,10 @@
1
+ import { baseEmailCapabilities } from "@open-mercato/core/modules/communication_channels/lib/email-capabilities";
2
+ const sesCapabilities = {
3
+ ...baseEmailCapabilities,
4
+ fileSharing: false,
5
+ conversationHistory: false
6
+ };
7
+ export {
8
+ sesCapabilities
9
+ };
10
+ //# sourceMappingURL=capabilities.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/channel_ses/capabilities.ts"],
4
+ "sourcesContent": ["import { baseEmailCapabilities } from '@open-mercato/core/modules/communication_channels/lib/email-capabilities'\nimport type { ChannelCapabilities } from '@open-mercato/core/modules/communication_channels/lib/adapter'\n\nexport const sesCapabilities: ChannelCapabilities = {\n ...baseEmailCapabilities,\n fileSharing: false,\n conversationHistory: false,\n}\n"],
5
+ "mappings": "AAAA,SAAS,6BAA6B;AAG/B,MAAM,kBAAuC;AAAA,EAClD,GAAG;AAAA,EACH,aAAa;AAAA,EACb,qBAAqB;AACvB;",
6
+ "names": []
7
+ }
@@ -0,0 +1,22 @@
1
+ import { asValue } from "awilix";
2
+ import {
3
+ hasChannelAdapter,
4
+ registerChannelAdapter
5
+ } from "@open-mercato/core/modules/communication_channels/lib/adapter-registry-singleton";
6
+ import { getSesChannelAdapter } from "./lib/adapter.js";
7
+ import { registerSesSystemEmailConfigResolver } from "./lib/system-email-config.js";
8
+ import { channelSesHealthCheck } from "./lib/health.js";
9
+ function register(container) {
10
+ registerSesSystemEmailConfigResolver();
11
+ if (!hasChannelAdapter("ses")) {
12
+ registerChannelAdapter(getSesChannelAdapter());
13
+ }
14
+ container.register({
15
+ channelSesAdapter: asValue(getSesChannelAdapter()),
16
+ channelSesHealthCheck: asValue(channelSesHealthCheck)
17
+ });
18
+ }
19
+ export {
20
+ register
21
+ };
22
+ //# sourceMappingURL=di.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/channel_ses/di.ts"],
4
+ "sourcesContent": ["import { asValue } from 'awilix'\nimport type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport {\n hasChannelAdapter,\n registerChannelAdapter,\n} from '@open-mercato/core/modules/communication_channels/lib/adapter-registry-singleton'\nimport { getSesChannelAdapter } from './lib/adapter'\nimport { registerSesSystemEmailConfigResolver } from './lib/system-email-config'\nimport { channelSesHealthCheck } from './lib/health'\n\nexport function register(container: AppContainer): void {\n registerSesSystemEmailConfigResolver()\n if (!hasChannelAdapter('ses')) {\n registerChannelAdapter(getSesChannelAdapter())\n }\n container.register({\n channelSesAdapter: asValue(getSesChannelAdapter()),\n channelSesHealthCheck: asValue(channelSesHealthCheck),\n })\n}\n"],
5
+ "mappings": "AAAA,SAAS,eAAe;AAExB;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,4BAA4B;AACrC,SAAS,4CAA4C;AACrD,SAAS,6BAA6B;AAE/B,SAAS,SAAS,WAA+B;AACtD,uCAAqC;AACrC,MAAI,CAAC,kBAAkB,KAAK,GAAG;AAC7B,2BAAuB,qBAAqB,CAAC;AAAA,EAC/C;AACA,YAAU,SAAS;AAAA,IACjB,mBAAmB,QAAQ,qBAAqB,CAAC;AAAA,IACjD,uBAAuB,QAAQ,qBAAqB;AAAA,EACtD,CAAC;AACH;",
6
+ "names": []
7
+ }
@@ -0,0 +1,13 @@
1
+ const metadata = {
2
+ id: "channel_ses",
3
+ name: "Amazon SES Email Channel",
4
+ description: "Outbound Amazon SES email channel provider for the Communications Hub.",
5
+ version: "0.1.0",
6
+ requires: ["communication_channels", "integrations"]
7
+ };
8
+ var channel_ses_default = metadata;
9
+ export {
10
+ channel_ses_default as default,
11
+ metadata
12
+ };
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/channel_ses/index.ts"],
4
+ "sourcesContent": ["export const metadata = {\n id: 'channel_ses',\n name: 'Amazon SES Email Channel',\n description: 'Outbound Amazon SES email channel provider for the Communications Hub.',\n version: '0.1.0',\n requires: ['communication_channels', 'integrations'],\n}\n\nexport default metadata\n"],
5
+ "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,aAAa;AAAA,EACb,SAAS;AAAA,EACT,UAAU,CAAC,0BAA0B,cAAc;AACrD;AAEA,IAAO,sBAAQ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,69 @@
1
+ import { buildIntegrationDetailWidgetSpotId } from "@open-mercato/shared/modules/integrations/types";
2
+ const channelSesDetailWidgetSpotId = buildIntegrationDetailWidgetSpotId("channel_ses");
3
+ const integration = {
4
+ id: "channel_ses",
5
+ title: "Amazon SES",
6
+ description: "Send transactional email through Amazon SES using the Communications Hub.",
7
+ category: "communication",
8
+ hub: "communication_channels",
9
+ providerKey: "ses",
10
+ icon: "mail",
11
+ docsUrl: "https://docs.aws.amazon.com/ses/",
12
+ package: "@open-mercato/channel-ses",
13
+ version: "0.1.0",
14
+ author: "Open Mercato Team",
15
+ company: "Open Mercato",
16
+ license: "MIT",
17
+ tags: ["email", "aws", "ses", "transactional", "communication"],
18
+ detailPage: {
19
+ widgetSpotId: channelSesDetailWidgetSpotId
20
+ },
21
+ apiVersions: [
22
+ {
23
+ id: "sesv2",
24
+ label: "Amazon SES v2",
25
+ status: "stable",
26
+ default: true,
27
+ changelog: "Initial outbound transactional email adapter."
28
+ }
29
+ ],
30
+ healthCheck: { service: "channelSesHealthCheck" },
31
+ credentials: {
32
+ fields: [
33
+ {
34
+ key: "region",
35
+ label: "AWS region",
36
+ type: "text",
37
+ required: true,
38
+ placeholder: "eu-west-2",
39
+ helpText: "SES region used for this tenant connection."
40
+ },
41
+ {
42
+ key: "fromAddress",
43
+ label: "From address",
44
+ type: "text",
45
+ required: true,
46
+ placeholder: "no-reply@example.com",
47
+ helpText: "Verified SES sender address or domain identity."
48
+ },
49
+ {
50
+ key: "configurationSetName",
51
+ label: "Configuration set",
52
+ type: "text",
53
+ required: false,
54
+ helpText: "Optional SES configuration set name."
55
+ }
56
+ ]
57
+ }
58
+ };
59
+ const integrations = [integration];
60
+ const bundles = [];
61
+ const bundle = void 0;
62
+ export {
63
+ bundle,
64
+ bundles,
65
+ channelSesDetailWidgetSpotId,
66
+ integration,
67
+ integrations
68
+ };
69
+ //# sourceMappingURL=integration.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/channel_ses/integration.ts"],
4
+ "sourcesContent": ["import { buildIntegrationDetailWidgetSpotId, type IntegrationBundle, type IntegrationDefinition } from '@open-mercato/shared/modules/integrations/types'\n\nexport const channelSesDetailWidgetSpotId = buildIntegrationDetailWidgetSpotId('channel_ses')\n\nexport const integration: IntegrationDefinition = {\n id: 'channel_ses',\n title: 'Amazon SES',\n description: 'Send transactional email through Amazon SES using the Communications Hub.',\n category: 'communication',\n hub: 'communication_channels',\n providerKey: 'ses',\n icon: 'mail',\n docsUrl: 'https://docs.aws.amazon.com/ses/',\n package: '@open-mercato/channel-ses',\n version: '0.1.0',\n author: 'Open Mercato Team',\n company: 'Open Mercato',\n license: 'MIT',\n tags: ['email', 'aws', 'ses', 'transactional', 'communication'],\n detailPage: {\n widgetSpotId: channelSesDetailWidgetSpotId,\n },\n apiVersions: [\n {\n id: 'sesv2',\n label: 'Amazon SES v2',\n status: 'stable',\n default: true,\n changelog: 'Initial outbound transactional email adapter.',\n },\n ],\n healthCheck: { service: 'channelSesHealthCheck' },\n credentials: {\n fields: [\n {\n key: 'region',\n label: 'AWS region',\n type: 'text',\n required: true,\n placeholder: 'eu-west-2',\n helpText: 'SES region used for this tenant connection.',\n },\n {\n key: 'fromAddress',\n label: 'From address',\n type: 'text',\n required: true,\n placeholder: 'no-reply@example.com',\n helpText: 'Verified SES sender address or domain identity.',\n },\n {\n key: 'configurationSetName',\n label: 'Configuration set',\n type: 'text',\n required: false,\n helpText: 'Optional SES configuration set name.',\n },\n ],\n },\n}\n\nexport const integrations: IntegrationDefinition[] = [integration]\nexport const bundles: IntegrationBundle[] = []\nexport const bundle: IntegrationBundle | undefined = undefined\n"],
5
+ "mappings": "AAAA,SAAS,0CAA8F;AAEhG,MAAM,+BAA+B,mCAAmC,aAAa;AAErF,MAAM,cAAqC;AAAA,EAChD,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,aAAa;AAAA,EACb,UAAU;AAAA,EACV,KAAK;AAAA,EACL,aAAa;AAAA,EACb,MAAM;AAAA,EACN,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AAAA,EACT,MAAM,CAAC,SAAS,OAAO,OAAO,iBAAiB,eAAe;AAAA,EAC9D,YAAY;AAAA,IACV,cAAc;AAAA,EAChB;AAAA,EACA,aAAa;AAAA,IACX;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,WAAW;AAAA,IACb;AAAA,EACF;AAAA,EACA,aAAa,EAAE,SAAS,wBAAwB;AAAA,EAChD,aAAa;AAAA,IACX,QAAQ;AAAA,MACN;AAAA,QACE,KAAK;AAAA,QACL,OAAO;AAAA,QACP,MAAM;AAAA,QACN,UAAU;AAAA,QACV,aAAa;AAAA,QACb,UAAU;AAAA,MACZ;AAAA,MACA;AAAA,QACE,KAAK;AAAA,QACL,OAAO;AAAA,QACP,MAAM;AAAA,QACN,UAAU;AAAA,QACV,aAAa;AAAA,QACb,UAAU;AAAA,MACZ;AAAA,MACA;AAAA,QACE,KAAK;AAAA,QACL,OAAO;AAAA,QACP,MAAM;AAAA,QACN,UAAU;AAAA,QACV,UAAU;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AACF;AAEO,MAAM,eAAwC,CAAC,WAAW;AAC1D,MAAM,UAA+B,CAAC;AACtC,MAAM,SAAwC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,112 @@
1
+ import { SESv2Client, SendEmailCommand } from "@aws-sdk/client-sesv2";
2
+ import nodemailer from "nodemailer";
3
+ import {
4
+ htmlToText,
5
+ sanitizeHeaderValue,
6
+ stringOrUndefined,
7
+ toAddressList
8
+ } from "@open-mercato/core/modules/communication_channels/lib/email-mime";
9
+ import { sesCapabilities } from "../capabilities.js";
10
+ import { sesCredentialsSchema } from "./credentials.js";
11
+ function attachmentsFromMeta(value) {
12
+ if (!Array.isArray(value)) return void 0;
13
+ const attachments = value.flatMap((item) => {
14
+ if (!item || typeof item !== "object") return [];
15
+ const record = item;
16
+ const filename = stringOrUndefined(record.filename);
17
+ const content = stringOrUndefined(record.content);
18
+ if (!filename || !content) return [];
19
+ const contentType = stringOrUndefined(record.contentType);
20
+ return [{
21
+ filename,
22
+ content,
23
+ encoding: "base64",
24
+ ...contentType ? { contentType } : {}
25
+ }];
26
+ });
27
+ return attachments.length ? attachments : void 0;
28
+ }
29
+ class SesChannelAdapter {
30
+ constructor() {
31
+ this.providerKey = "ses";
32
+ this.channelType = "email";
33
+ this.capabilities = sesCapabilities;
34
+ }
35
+ async sendMessage(input) {
36
+ const credentials = sesCredentialsSchema.parse(input.credentials);
37
+ const meta = input.metadata ?? {};
38
+ const to = Array.isArray(meta.to) ? meta.to : [];
39
+ if (to.length === 0) {
40
+ return { externalMessageId: "", status: "failed", error: "[internal] Email send requires at least one recipient" };
41
+ }
42
+ const subject = stringOrUndefined(meta.subject);
43
+ if (!subject) {
44
+ return { externalMessageId: "", status: "failed", error: "[internal] Email send requires a subject" };
45
+ }
46
+ const sesClient = new SESv2Client({ region: credentials.region });
47
+ const transporter = nodemailer.createTransport({
48
+ SES: { sesClient, SendEmailCommand }
49
+ });
50
+ const configurationSetName = stringOrUndefined(credentials.configurationSetName);
51
+ const options = {
52
+ from: stringOrUndefined(meta.from) ?? credentials.fromAddress,
53
+ to,
54
+ subject,
55
+ ...input.content.text ? { text: input.content.text } : {},
56
+ ...input.content.html ? { html: input.content.html } : {},
57
+ ...stringOrUndefined(meta.replyTo) ? { replyTo: stringOrUndefined(meta.replyTo) } : {},
58
+ ...attachmentsFromMeta(meta.attachments)?.length ? { attachments: attachmentsFromMeta(meta.attachments) } : {},
59
+ ...configurationSetName ? { ses: { ConfigurationSetName: configurationSetName } } : {}
60
+ };
61
+ try {
62
+ const info = await transporter.sendMail(options);
63
+ return {
64
+ externalMessageId: info.messageId || `ses:${Date.now()}`,
65
+ conversationId: input.conversationId,
66
+ status: "sent",
67
+ metadata: info.response ? { response: info.response } : void 0
68
+ };
69
+ } catch (error) {
70
+ const message = error instanceof Error ? error.message : "Unknown error";
71
+ return { externalMessageId: "", status: "failed", error: `SES_SEND_FAILED: ${message}` };
72
+ }
73
+ }
74
+ async verifyWebhook(_input) {
75
+ return { raw: {}, eventType: "other", metadata: { reason: "ses-system-email-outbound-only" } };
76
+ }
77
+ async getStatus(_input) {
78
+ return { status: "sent" };
79
+ }
80
+ async normalizeInbound(_raw) {
81
+ throw new Error("[internal] Amazon SES system email adapter is outbound-only");
82
+ }
83
+ async convertOutbound(input) {
84
+ const meta = input.channelMetadata ?? {};
85
+ const to = toAddressList(meta.to).map(sanitizeHeaderValue);
86
+ const subject = stringOrUndefined(meta.subject);
87
+ const html = input.bodyFormat === "html" ? input.body : void 0;
88
+ const text = input.bodyFormat === "html" ? htmlToText(input.body) : input.body;
89
+ return {
90
+ content: {
91
+ text,
92
+ html,
93
+ bodyFormat: input.bodyFormat
94
+ },
95
+ metadata: {
96
+ to,
97
+ subject,
98
+ from: stringOrUndefined(meta.from),
99
+ replyTo: stringOrUndefined(meta.replyTo),
100
+ attachments: attachmentsFromMeta(meta.attachments)
101
+ }
102
+ };
103
+ }
104
+ }
105
+ const adapter = new SesChannelAdapter();
106
+ function getSesChannelAdapter() {
107
+ return adapter;
108
+ }
109
+ export {
110
+ getSesChannelAdapter
111
+ };
112
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/channel_ses/lib/adapter.ts"],
4
+ "sourcesContent": ["import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2'\nimport nodemailer from 'nodemailer'\nimport type {\n ChannelAdapter,\n ChannelNativeContent,\n ConvertOutboundInput,\n GetMessageStatusInput,\n InboundMessage,\n MessageStatus,\n NormalizedInboundMessage,\n SendMessageInput,\n SendMessageResult,\n VerifyWebhookInput,\n} from '@open-mercato/core/modules/communication_channels/lib/adapter'\nimport {\n htmlToText,\n sanitizeHeaderValue,\n stringOrUndefined,\n toAddressList,\n} from '@open-mercato/core/modules/communication_channels/lib/email-mime'\nimport { sesCapabilities } from '../capabilities'\nimport { sesCredentialsSchema } from './credentials'\n\ntype SesAttachment = {\n filename: string\n content: string\n encoding: 'base64'\n contentType?: string\n}\n\ntype SesSendInfo = {\n messageId?: string\n response?: string\n}\n\ntype SesTransportOptions = {\n SES: {\n sesClient: SESv2Client\n SendEmailCommand: typeof SendEmailCommand\n }\n}\n\ntype SesMailOptions = Parameters<ReturnType<typeof nodemailer.createTransport>['sendMail']>[0] & {\n ses?: {\n ConfigurationSetName?: string\n }\n}\n\nfunction attachmentsFromMeta(value: unknown): SesAttachment[] | undefined {\n if (!Array.isArray(value)) return undefined\n const attachments = value.flatMap((item): SesAttachment[] => {\n if (!item || typeof item !== 'object') return []\n const record = item as Record<string, unknown>\n const filename = stringOrUndefined(record.filename)\n const content = stringOrUndefined(record.content)\n if (!filename || !content) return []\n const contentType = stringOrUndefined(record.contentType)\n return [{\n filename,\n content,\n encoding: 'base64',\n ...(contentType ? { contentType } : {}),\n }]\n })\n return attachments.length ? attachments : undefined\n}\n\nclass SesChannelAdapter implements ChannelAdapter {\n readonly providerKey = 'ses'\n readonly channelType = 'email'\n readonly capabilities = sesCapabilities\n\n async sendMessage(input: SendMessageInput): Promise<SendMessageResult> {\n const credentials = sesCredentialsSchema.parse(input.credentials)\n const meta = (input.metadata ?? {}) as Record<string, unknown>\n const to = Array.isArray(meta.to) ? (meta.to as string[]) : []\n if (to.length === 0) {\n return { externalMessageId: '', status: 'failed', error: '[internal] Email send requires at least one recipient' }\n }\n const subject = stringOrUndefined(meta.subject)\n if (!subject) {\n return { externalMessageId: '', status: 'failed', error: '[internal] Email send requires a subject' }\n }\n\n const sesClient = new SESv2Client({ region: credentials.region })\n const transporter = nodemailer.createTransport({\n SES: { sesClient, SendEmailCommand },\n } as Parameters<typeof nodemailer.createTransport>[0] & SesTransportOptions)\n const configurationSetName = stringOrUndefined(credentials.configurationSetName)\n const options: SesMailOptions = {\n from: stringOrUndefined(meta.from) ?? credentials.fromAddress,\n to,\n subject,\n ...(input.content.text ? { text: input.content.text } : {}),\n ...(input.content.html ? { html: input.content.html } : {}),\n ...(stringOrUndefined(meta.replyTo) ? { replyTo: stringOrUndefined(meta.replyTo) } : {}),\n ...(attachmentsFromMeta(meta.attachments)?.length ? { attachments: attachmentsFromMeta(meta.attachments) } : {}),\n ...(configurationSetName ? { ses: { ConfigurationSetName: configurationSetName } } : {}),\n }\n\n try {\n const info = await transporter.sendMail(options) as SesSendInfo\n return {\n externalMessageId: info.messageId || `ses:${Date.now()}`,\n conversationId: input.conversationId,\n status: 'sent',\n metadata: info.response ? { response: info.response } : undefined,\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error'\n return { externalMessageId: '', status: 'failed', error: `SES_SEND_FAILED: ${message}` }\n }\n }\n\n async verifyWebhook(_input: VerifyWebhookInput): Promise<InboundMessage> {\n return { raw: {}, eventType: 'other', metadata: { reason: 'ses-system-email-outbound-only' } }\n }\n\n async getStatus(_input: GetMessageStatusInput): Promise<MessageStatus> {\n return { status: 'sent' }\n }\n\n async normalizeInbound(_raw: InboundMessage): Promise<NormalizedInboundMessage> {\n throw new Error('[internal] Amazon SES system email adapter is outbound-only')\n }\n\n async convertOutbound(input: ConvertOutboundInput): Promise<ChannelNativeContent> {\n const meta = (input.channelMetadata ?? {}) as Record<string, unknown>\n const to = toAddressList(meta.to).map(sanitizeHeaderValue)\n const subject = stringOrUndefined(meta.subject)\n const html = input.bodyFormat === 'html' ? input.body : undefined\n const text = input.bodyFormat === 'html' ? htmlToText(input.body) : input.body\n return {\n content: {\n text,\n html,\n bodyFormat: input.bodyFormat,\n },\n metadata: {\n to,\n subject,\n from: stringOrUndefined(meta.from),\n replyTo: stringOrUndefined(meta.replyTo),\n attachments: attachmentsFromMeta(meta.attachments),\n },\n }\n }\n}\n\nconst adapter = new SesChannelAdapter()\n\nexport function getSesChannelAdapter(): ChannelAdapter {\n return adapter\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,10 @@
1
+ import { z } from "zod";
2
+ const sesCredentialsSchema = z.object({
3
+ region: z.string().min(1),
4
+ fromAddress: z.string().email(),
5
+ configurationSetName: z.string().min(1).optional()
6
+ });
7
+ export {
8
+ sesCredentialsSchema
9
+ };
10
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/channel_ses/lib/credentials.ts"],
4
+ "sourcesContent": ["import { z } from 'zod'\n\nexport const sesCredentialsSchema = z.object({\n region: z.string().min(1),\n fromAddress: z.string().email(),\n configurationSetName: z.string().min(1).optional(),\n})\n\nexport type SesCredentials = z.infer<typeof sesCredentialsSchema>\n"],
5
+ "mappings": "AAAA,SAAS,SAAS;AAEX,MAAM,uBAAuB,EAAE,OAAO;AAAA,EAC3C,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACxB,aAAa,EAAE,OAAO,EAAE,MAAM;AAAA,EAC9B,sBAAsB,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AACnD,CAAC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,45 @@
1
+ import { GetAccountCommand, SESv2Client } from "@aws-sdk/client-sesv2";
2
+ import { withTimeout } from "@open-mercato/shared/lib/http/fetchWithTimeout";
3
+ import { sesCredentialsSchema } from "./credentials.js";
4
+ const channelSesHealthCheck = {
5
+ async check(credentials, _scope) {
6
+ const parsed = sesCredentialsSchema.safeParse(credentials ?? {});
7
+ if (!parsed.success) {
8
+ return {
9
+ status: "unhealthy",
10
+ message: `Amazon SES credentials invalid: ${parsed.error.issues[0]?.message ?? "unknown validation error"}`,
11
+ details: { reason: "invalid_credentials" }
12
+ };
13
+ }
14
+ const client = new SESv2Client({ region: parsed.data.region });
15
+ try {
16
+ const account = await withTimeout(
17
+ (signal) => client.send(new GetAccountCommand({}), { abortSignal: signal }),
18
+ 8e3,
19
+ "Amazon SES GetAccount"
20
+ );
21
+ return {
22
+ status: "healthy",
23
+ message: "Amazon SES account is reachable",
24
+ details: {
25
+ region: parsed.data.region,
26
+ productionAccessEnabled: account.ProductionAccessEnabled ?? false,
27
+ sendingEnabled: account.SendingEnabled ?? false
28
+ }
29
+ };
30
+ } catch (error) {
31
+ const message = error instanceof Error ? error.message : "Amazon SES request failed";
32
+ return {
33
+ status: "unhealthy",
34
+ message: `Amazon SES health check failed: ${message}`,
35
+ details: { reason: "request_failed", region: parsed.data.region }
36
+ };
37
+ } finally {
38
+ client.destroy();
39
+ }
40
+ }
41
+ };
42
+ export {
43
+ channelSesHealthCheck
44
+ };
45
+ //# sourceMappingURL=health.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/channel_ses/lib/health.ts"],
4
+ "sourcesContent": ["import { GetAccountCommand, SESv2Client } from '@aws-sdk/client-sesv2'\nimport type { IntegrationScope } from '@open-mercato/shared/modules/integrations/types'\nimport { withTimeout } from '@open-mercato/shared/lib/http/fetchWithTimeout'\nimport { sesCredentialsSchema } from './credentials'\n\ntype HealthCheckResult = {\n status: 'healthy' | 'unhealthy'\n message: string\n details: Record<string, unknown>\n}\n\nexport const channelSesHealthCheck = {\n async check(\n credentials: Record<string, unknown> | null,\n _scope: IntegrationScope,\n ): Promise<HealthCheckResult> {\n const parsed = sesCredentialsSchema.safeParse(credentials ?? {})\n if (!parsed.success) {\n return {\n status: 'unhealthy',\n message: `Amazon SES credentials invalid: ${parsed.error.issues[0]?.message ?? 'unknown validation error'}`,\n details: { reason: 'invalid_credentials' },\n }\n }\n\n const client = new SESv2Client({ region: parsed.data.region })\n try {\n const account = await withTimeout(\n (signal) => client.send(new GetAccountCommand({}), { abortSignal: signal }),\n 8_000,\n 'Amazon SES GetAccount',\n )\n return {\n status: 'healthy',\n message: 'Amazon SES account is reachable',\n details: {\n region: parsed.data.region,\n productionAccessEnabled: account.ProductionAccessEnabled ?? false,\n sendingEnabled: account.SendingEnabled ?? false,\n },\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Amazon SES request failed'\n return {\n status: 'unhealthy',\n message: `Amazon SES health check failed: ${message}`,\n details: { reason: 'request_failed', region: parsed.data.region },\n }\n } finally {\n client.destroy()\n }\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,mBAAmB,mBAAmB;AAE/C,SAAS,mBAAmB;AAC5B,SAAS,4BAA4B;AAQ9B,MAAM,wBAAwB;AAAA,EACnC,MAAM,MACJ,aACA,QAC4B;AAC5B,UAAM,SAAS,qBAAqB,UAAU,eAAe,CAAC,CAAC;AAC/D,QAAI,CAAC,OAAO,SAAS;AACnB,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,SAAS,mCAAmC,OAAO,MAAM,OAAO,CAAC,GAAG,WAAW,0BAA0B;AAAA,QACzG,SAAS,EAAE,QAAQ,sBAAsB;AAAA,MAC3C;AAAA,IACF;AAEA,UAAM,SAAS,IAAI,YAAY,EAAE,QAAQ,OAAO,KAAK,OAAO,CAAC;AAC7D,QAAI;AACF,YAAM,UAAU,MAAM;AAAA,QACpB,CAAC,WAAW,OAAO,KAAK,IAAI,kBAAkB,CAAC,CAAC,GAAG,EAAE,aAAa,OAAO,CAAC;AAAA,QAC1E;AAAA,QACA;AAAA,MACF;AACA,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,SAAS;AAAA,UACP,QAAQ,OAAO,KAAK;AAAA,UACpB,yBAAyB,QAAQ,2BAA2B;AAAA,UAC5D,gBAAgB,QAAQ,kBAAkB;AAAA,QAC5C;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,SAAS,mCAAmC,OAAO;AAAA,QACnD,SAAS,EAAE,QAAQ,kBAAkB,QAAQ,OAAO,KAAK,OAAO;AAAA,MAClE;AAAA,IACF,UAAE;AACA,aAAO,QAAQ;AAAA,IACjB;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,59 @@
1
+ import { normalizeEnvString, resolveDefaultEmailFromAddress } from "@open-mercato/shared/lib/email/config";
2
+ import { createLogger } from "@open-mercato/shared/lib/logger";
3
+ import { ensureSystemEmailChannel } from "@open-mercato/core/modules/communication_channels/lib/ensure-system-email-channel";
4
+ import { isSelectedSystemEmailProvider } from "@open-mercato/core/modules/communication_channels/lib/system-email-provider-config";
5
+ import { sesCapabilities } from "../capabilities.js";
6
+ const logger = createLogger("channel_ses");
7
+ function readSesEnvPreset() {
8
+ const fromAddress = resolveDefaultEmailFromAddress();
9
+ const region = normalizeEnvString(process.env.AWS_SES_REGION) || normalizeEnvString(process.env.AWS_REGION);
10
+ if (!fromAddress || !region) return null;
11
+ const configurationSetName = normalizeEnvString(process.env.AWS_SES_CONFIGURATION_SET);
12
+ return {
13
+ fromAddress,
14
+ region,
15
+ ...configurationSetName ? { configurationSetName } : {}
16
+ };
17
+ }
18
+ async function applySesEnvPreset(ctx) {
19
+ if (!isSelectedSystemEmailProvider("ses")) return;
20
+ const preset = readSesEnvPreset();
21
+ if (!preset) return;
22
+ let credentialsService;
23
+ try {
24
+ credentialsService = ctx.container.resolve("integrationCredentialsService");
25
+ } catch {
26
+ return;
27
+ }
28
+ await credentialsService.save("channel_ses", preset, {
29
+ tenantId: ctx.tenantId,
30
+ organizationId: ctx.organizationId,
31
+ userId: null
32
+ });
33
+ try {
34
+ const integrationStateService = ctx.container.resolve("integrationStateService");
35
+ await integrationStateService.upsert("channel_ses", { isEnabled: true }, {
36
+ tenantId: ctx.tenantId,
37
+ organizationId: ctx.organizationId
38
+ });
39
+ } catch (err) {
40
+ logger.warn("Failed to enable the SES integration state; Integrations will read Disabled while email is live", {
41
+ err,
42
+ tenantId: ctx.tenantId,
43
+ organizationId: ctx.organizationId
44
+ });
45
+ }
46
+ await ensureSystemEmailChannel(ctx.em, {
47
+ tenantId: ctx.tenantId,
48
+ organizationId: ctx.organizationId,
49
+ providerKey: "ses",
50
+ externalIdentifier: preset.fromAddress,
51
+ displayName: "Amazon SES system email",
52
+ capabilities: { ...sesCapabilities }
53
+ });
54
+ }
55
+ export {
56
+ applySesEnvPreset,
57
+ readSesEnvPreset
58
+ };
59
+ //# sourceMappingURL=preset.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/channel_ses/lib/preset.ts"],
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { normalizeEnvString, resolveDefaultEmailFromAddress } from '@open-mercato/shared/lib/email/config'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { ensureSystemEmailChannel } from '@open-mercato/core/modules/communication_channels/lib/ensure-system-email-channel'\nimport { isSelectedSystemEmailProvider } from '@open-mercato/core/modules/communication_channels/lib/system-email-provider-config'\nimport { sesCapabilities } from '../capabilities'\n\nconst logger = createLogger('channel_ses')\n\ntype PresetScope = {\n em: EntityManager\n container: AppContainer\n tenantId: string\n organizationId: string\n}\n\ntype CredentialsServiceLike = {\n save: (\n integrationId: string,\n credentials: Record<string, unknown>,\n scope: { organizationId: string; tenantId: string; userId?: string | null },\n ) => Promise<void>\n}\n\ntype IntegrationStateServiceLike = {\n upsert: (\n integrationId: string,\n input: { isEnabled: boolean },\n scope: { organizationId: string; tenantId: string },\n ) => Promise<unknown>\n}\n\nexport function readSesEnvPreset(): { region: string; fromAddress: string; configurationSetName?: string } | null {\n const fromAddress = resolveDefaultEmailFromAddress()\n const region = normalizeEnvString(process.env.AWS_SES_REGION) || normalizeEnvString(process.env.AWS_REGION)\n if (!fromAddress || !region) return null\n const configurationSetName = normalizeEnvString(process.env.AWS_SES_CONFIGURATION_SET)\n return {\n fromAddress,\n region,\n ...(configurationSetName ? { configurationSetName } : {}),\n }\n}\n\nexport async function applySesEnvPreset(ctx: PresetScope): Promise<void> {\n // Only the provider this instance actually selected seeds anything. `AWS_REGION` is not an email\n // variable here \u2014 `.env.example` ships it uncommented for vector search, and every AWS runtime\n // injects it \u2014 so without this gate a Resend instance would advertise an Enabled SES integration\n // and a `status: 'connected'` SES channel nobody configured. That is the same misleading admin\n // state #5531 was filed for, pointed the other way.\n if (!isSelectedSystemEmailProvider('ses')) return\n const preset = readSesEnvPreset()\n if (!preset) return\n\n let credentialsService: CredentialsServiceLike\n try {\n credentialsService = ctx.container.resolve('integrationCredentialsService') as CredentialsServiceLike\n } catch {\n return\n }\n\n await credentialsService.save('channel_ses', preset, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: null,\n })\n\n try {\n const integrationStateService = ctx.container.resolve('integrationStateService') as IntegrationStateServiceLike\n await integrationStateService.upsert('channel_ses', { isEnabled: true }, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n } catch (err) {\n logger.warn('Failed to enable the SES integration state; Integrations will read Disabled while email is live', {\n err,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n }\n\n await ensureSystemEmailChannel(ctx.em, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n providerKey: 'ses',\n externalIdentifier: preset.fromAddress,\n displayName: 'Amazon SES system email',\n capabilities: { ...sesCapabilities },\n })\n}\n"],
5
+ "mappings": "AAEA,SAAS,oBAAoB,sCAAsC;AACnE,SAAS,oBAAoB;AAC7B,SAAS,gCAAgC;AACzC,SAAS,qCAAqC;AAC9C,SAAS,uBAAuB;AAEhC,MAAM,SAAS,aAAa,aAAa;AAyBlC,SAAS,mBAAkG;AAChH,QAAM,cAAc,+BAA+B;AACnD,QAAM,SAAS,mBAAmB,QAAQ,IAAI,cAAc,KAAK,mBAAmB,QAAQ,IAAI,UAAU;AAC1G,MAAI,CAAC,eAAe,CAAC,OAAQ,QAAO;AACpC,QAAM,uBAAuB,mBAAmB,QAAQ,IAAI,yBAAyB;AACrF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,GAAI,uBAAuB,EAAE,qBAAqB,IAAI,CAAC;AAAA,EACzD;AACF;AAEA,eAAsB,kBAAkB,KAAiC;AAMvE,MAAI,CAAC,8BAA8B,KAAK,EAAG;AAC3C,QAAM,SAAS,iBAAiB;AAChC,MAAI,CAAC,OAAQ;AAEb,MAAI;AACJ,MAAI;AACF,yBAAqB,IAAI,UAAU,QAAQ,+BAA+B;AAAA,EAC5E,QAAQ;AACN;AAAA,EACF;AAEA,QAAM,mBAAmB,KAAK,eAAe,QAAQ;AAAA,IACnD,UAAU,IAAI;AAAA,IACd,gBAAgB,IAAI;AAAA,IACpB,QAAQ;AAAA,EACV,CAAC;AAED,MAAI;AACF,UAAM,0BAA0B,IAAI,UAAU,QAAQ,yBAAyB;AAC/E,UAAM,wBAAwB,OAAO,eAAe,EAAE,WAAW,KAAK,GAAG;AAAA,MACvE,UAAU,IAAI;AAAA,MACd,gBAAgB,IAAI;AAAA,IACtB,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,WAAO,KAAK,mGAAmG;AAAA,MAC7G;AAAA,MACA,UAAU,IAAI;AAAA,MACd,gBAAgB,IAAI;AAAA,IACtB,CAAC;AAAA,EACH;AAEA,QAAM,yBAAyB,IAAI,IAAI;AAAA,IACrC,UAAU,IAAI;AAAA,IACd,gBAAgB,IAAI;AAAA,IACpB,aAAa;AAAA,IACb,oBAAoB,OAAO;AAAA,IAC3B,aAAa;AAAA,IACb,cAAc,EAAE,GAAG,gBAAgB;AAAA,EACrC,CAAC;AACH;",
6
+ "names": []
7
+ }
@@ -0,0 +1,13 @@
1
+ import { registerSystemEmailProviderConfigResolver } from "@open-mercato/core/modules/communication_channels/lib/system-email-provider-config";
2
+ import { readSesEnvPreset } from "./preset.js";
3
+ function registerSesSystemEmailConfigResolver() {
4
+ registerSystemEmailProviderConfigResolver({
5
+ providerKey: "ses",
6
+ isConfigured: () => Boolean(readSesEnvPreset()),
7
+ resolveCredentials: ({ fromAddress }) => readSesEnvPreset() ?? { fromAddress }
8
+ });
9
+ }
10
+ export {
11
+ registerSesSystemEmailConfigResolver
12
+ };
13
+ //# sourceMappingURL=system-email-config.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/channel_ses/lib/system-email-config.ts"],
4
+ "sourcesContent": ["import { registerSystemEmailProviderConfigResolver } from '@open-mercato/core/modules/communication_channels/lib/system-email-provider-config'\nimport { readSesEnvPreset } from './preset'\n\nexport function registerSesSystemEmailConfigResolver(): void {\n registerSystemEmailProviderConfigResolver({\n providerKey: 'ses',\n isConfigured: () => Boolean(readSesEnvPreset()),\n resolveCredentials: ({ fromAddress }) => readSesEnvPreset() ?? { fromAddress },\n })\n}\n"],
5
+ "mappings": "AAAA,SAAS,iDAAiD;AAC1D,SAAS,wBAAwB;AAE1B,SAAS,uCAA6C;AAC3D,4CAA0C;AAAA,IACxC,aAAa;AAAA,IACb,cAAc,MAAM,QAAQ,iBAAiB,CAAC;AAAA,IAC9C,oBAAoB,CAAC,EAAE,YAAY,MAAM,iBAAiB,KAAK,EAAE,YAAY;AAAA,EAC/E,CAAC;AACH;",
6
+ "names": []
7
+ }
@@ -0,0 +1,30 @@
1
+ import {
2
+ hasChannelAdapter,
3
+ registerChannelAdapter
4
+ } from "@open-mercato/core/modules/communication_channels/lib/adapter-registry-singleton";
5
+ import { getSesChannelAdapter } from "./lib/adapter.js";
6
+ import { applySesEnvPreset } from "./lib/preset.js";
7
+ import { registerSesSystemEmailConfigResolver } from "./lib/system-email-config.js";
8
+ function ensureSesAdapterRegistered() {
9
+ if (hasChannelAdapter("ses")) return;
10
+ registerChannelAdapter(getSesChannelAdapter());
11
+ }
12
+ ensureSesAdapterRegistered();
13
+ registerSesSystemEmailConfigResolver();
14
+ const setup = {
15
+ defaultRoleFeatures: {
16
+ superadmin: ["channel_ses.view", "channel_ses.configure"],
17
+ admin: ["channel_ses.view", "channel_ses.configure"]
18
+ },
19
+ async seedDefaults(ctx) {
20
+ ensureSesAdapterRegistered();
21
+ registerSesSystemEmailConfigResolver();
22
+ await applySesEnvPreset(ctx);
23
+ }
24
+ };
25
+ var setup_default = setup;
26
+ export {
27
+ setup_default as default,
28
+ setup
29
+ };
30
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/channel_ses/setup.ts"],
4
+ "sourcesContent": ["import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'\nimport {\n hasChannelAdapter,\n registerChannelAdapter,\n} from '@open-mercato/core/modules/communication_channels/lib/adapter-registry-singleton'\nimport { getSesChannelAdapter } from './lib/adapter'\nimport { applySesEnvPreset } from './lib/preset'\nimport { registerSesSystemEmailConfigResolver } from './lib/system-email-config'\n\nfunction ensureSesAdapterRegistered(): void {\n if (hasChannelAdapter('ses')) return\n registerChannelAdapter(getSesChannelAdapter())\n}\n\nensureSesAdapterRegistered()\nregisterSesSystemEmailConfigResolver()\n\nexport const setup: ModuleSetupConfig = {\n defaultRoleFeatures: {\n superadmin: ['channel_ses.view', 'channel_ses.configure'],\n admin: ['channel_ses.view', 'channel_ses.configure'],\n },\n async seedDefaults(ctx) {\n ensureSesAdapterRegistered()\n registerSesSystemEmailConfigResolver()\n await applySesEnvPreset(ctx)\n },\n}\n\nexport default setup\n"],
5
+ "mappings": "AACA;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,4BAA4B;AACrC,SAAS,yBAAyB;AAClC,SAAS,4CAA4C;AAErD,SAAS,6BAAmC;AAC1C,MAAI,kBAAkB,KAAK,EAAG;AAC9B,yBAAuB,qBAAqB,CAAC;AAC/C;AAEA,2BAA2B;AAC3B,qCAAqC;AAE9B,MAAM,QAA2B;AAAA,EACtC,qBAAqB;AAAA,IACnB,YAAY,CAAC,oBAAoB,uBAAuB;AAAA,IACxD,OAAO,CAAC,oBAAoB,uBAAuB;AAAA,EACrD;AAAA,EACA,MAAM,aAAa,KAAK;AACtB,+BAA2B;AAC3B,yCAAqC;AACrC,UAAM,kBAAkB,GAAG;AAAA,EAC7B;AACF;AAEA,IAAO,gBAAQ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,33 @@
1
+ /** @type {import('jest').Config} */
2
+ const base = require('../../jest.config.base.cjs')
3
+
4
+ module.exports = {
5
+ ...base,
6
+ testEnvironment: 'node',
7
+ watchman: false,
8
+ rootDir: '.',
9
+ moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
10
+ moduleNameMapper: {
11
+ '^@open-mercato/channel-ses/(.*)$': '<rootDir>/src/$1',
12
+ '^@open-mercato/core/(.*)$': '<rootDir>/../core/src/$1',
13
+ '^@open-mercato/shared/(.*)$': '<rootDir>/../shared/src/$1',
14
+ '^@open-mercato/ui/(.*)$': '<rootDir>/../ui/src/$1',
15
+ },
16
+ transform: {
17
+ '^.+\\.(t|j)sx?$': [
18
+ '<rootDir>/../../scripts/jest-mikroorm-transformer.cjs',
19
+ {
20
+ tsconfig: {
21
+ jsx: 'react-jsx',
22
+ rootDir: '.',
23
+ ignoreDeprecations: '6.0',
24
+ },
25
+ },
26
+ ],
27
+ },
28
+ transformIgnorePatterns: [
29
+ 'node_modules/(?!(@mikro-orm|kysely)/)',
30
+ ],
31
+ testMatch: ['<rootDir>/src/**/__tests__/**/*.test.(ts|tsx)'],
32
+ passWithNoTests: true,
33
+ }