aboutsummaryrefslogtreecommitdiff
path: root/services/docker/mail-server/aws-sendmail
diff options
context:
space:
mode:
Diffstat (limited to 'services/docker/mail-server/aws-sendmail')
-rw-r--r--services/docker/mail-server/aws-sendmail/aws/context.ts41
-rw-r--r--services/docker/mail-server/aws-sendmail/aws/deliver.ts42
-rw-r--r--services/docker/mail-server/aws-sendmail/aws/retriver.ts91
-rw-r--r--services/docker/mail-server/aws-sendmail/config.ts56
-rw-r--r--services/docker/mail-server/aws-sendmail/db.ts121
-rw-r--r--services/docker/mail-server/aws-sendmail/deno.json16
-rw-r--r--services/docker/mail-server/aws-sendmail/deno.lock1440
-rw-r--r--services/docker/mail-server/aws-sendmail/dovecot.ts40
-rw-r--r--services/docker/mail-server/aws-sendmail/logger.ts50
-rw-r--r--services/docker/mail-server/aws-sendmail/mail.ts177
-rw-r--r--services/docker/mail-server/aws-sendmail/main.ts18
-rw-r--r--services/docker/mail-server/aws-sendmail/traffic.ts21
-rw-r--r--services/docker/mail-server/aws-sendmail/util.ts49
13 files changed, 2162 insertions, 0 deletions
diff --git a/services/docker/mail-server/aws-sendmail/aws/context.ts b/services/docker/mail-server/aws-sendmail/aws/context.ts
new file mode 100644
index 0000000..65c8371
--- /dev/null
+++ b/services/docker/mail-server/aws-sendmail/aws/context.ts
@@ -0,0 +1,41 @@
+import {
+ CopyObjectCommand,
+ DeleteObjectCommand,
+ S3Client,
+} from "@aws-sdk/client-s3";
+
+import { getConfig } from "../config.ts";
+
+export class AwsContext {
+ readonly region = "ap-southeast-1";
+
+ accessKeyId = getConfig().getValue("awsAccessKeyId");
+ secretAccessKey = getConfig().getValue("awsSecretAccessKey");
+
+ getCredentials() {
+ const { accessKeyId, secretAccessKey } = this;
+ return Promise.resolve({ accessKeyId, secretAccessKey });
+ }
+
+ readonly credentials = this.getCredentials.bind(this);
+}
+
+export async function s3MoveObject(
+ client: S3Client,
+ bucket: string,
+ path: string,
+ newPath: string,
+): Promise<void> {
+ const copyCommand = new CopyObjectCommand({
+ Bucket: bucket,
+ Key: newPath,
+ CopySource: path,
+ });
+ await client.send(copyCommand);
+
+ const deleteCommand = new DeleteObjectCommand({
+ Bucket: bucket,
+ Key: path,
+ });
+ await client.send(deleteCommand);
+}
diff --git a/services/docker/mail-server/aws-sendmail/aws/deliver.ts b/services/docker/mail-server/aws-sendmail/aws/deliver.ts
new file mode 100644
index 0000000..5982d56
--- /dev/null
+++ b/services/docker/mail-server/aws-sendmail/aws/deliver.ts
@@ -0,0 +1,42 @@
+import { SendEmailCommand, SESv2Client } from "@aws-sdk/client-sesv2";
+
+import { AwsContext } from "./context.ts";
+import { Mail, MailDeliverer } from "../mail.ts";
+
+export class AwsMailDeliverer extends MailDeliverer {
+ private _ses;
+
+ constructor(readonly aws: AwsContext) {
+ super("aws");
+
+ const { region, credentials } = aws;
+
+ this._ses = new SESv2Client({ region, credentials });
+ }
+
+ protected override async doDeliver(mail: Mail): Promise<void> {
+ let awsMessageId: string | undefined;
+
+ try {
+ const sendCommand = new SendEmailCommand({
+ Content: {
+ Raw: { Data: mail.toUtf8Bytes() },
+ },
+ });
+
+ const res = await this._ses.send(sendCommand);
+ awsMessageId = res.MessageId;
+ } catch (cause) {
+ mail.throwDeliverError(
+ this,
+ "failed to call send-email api of aws.",
+ cause,
+ );
+ }
+
+ if (awsMessageId == null) {
+ mail.setDelivered(this, new Error("No message id is returned from aws."));
+ }
+ mail.awsMessageId = awsMessageId ?? null;
+ }
+}
diff --git a/services/docker/mail-server/aws-sendmail/aws/retriver.ts b/services/docker/mail-server/aws-sendmail/aws/retriver.ts
new file mode 100644
index 0000000..1544c04
--- /dev/null
+++ b/services/docker/mail-server/aws-sendmail/aws/retriver.ts
@@ -0,0 +1,91 @@
+/// <reference types="npm:@types/node" />
+
+import {
+ GetObjectCommand,
+ ListObjectsV2Command,
+ S3Client,
+} from "@aws-sdk/client-s3";
+
+import { generateTimeStringForFileName } from "../util.ts";
+import { getLogger } from "../logger.ts";
+import { AwsContext, s3MoveObject } from "./context.ts";
+import { getConfig } from "../config.ts";
+
+export class AwsMailRetriever {
+ readonly liveMailPrefix = "mail/live/";
+ readonly archiveMailPrefix = "mail/archive/";
+ readonly mailBucket = getConfig().getValue("awsMailBucket");
+
+ private readonly s3Client;
+ private readonly liveMailRecyclerAborter = new AbortController();
+
+ constructor(
+ aws: AwsContext,
+ private readonly callback: (rawMail: string) => Promise<void>,
+ ) {
+ const { region, credentials } = aws;
+ this.s3Client = new S3Client({ region, credentials });
+ }
+
+ setupLiveMailRecycler() {
+ Deno.cron("live-mail-recycler", "0 */6 * * *", {
+ signal: this.liveMailRecyclerAborter.signal,
+ }, () => {
+ });
+ }
+
+ generateArchivePrefix(instant: Date | Temporal.Instant): string {
+ return `${this.archiveMailPrefix}${
+ generateTimeStringForFileName(instant, true)
+ }/`;
+ }
+
+ async listLiveMails(): Promise<string[]> {
+ const listCommand = new ListObjectsV2Command({
+ Bucket: this.mailBucket,
+ Prefix: this.liveMailPrefix,
+ });
+ const res = await this.s3Client.send(listCommand);
+
+ if (res.Contents == null) {
+ getLogger().warn("Listing live mails in S3 returns null Content.");
+ return [];
+ }
+
+ const result: string[] = [];
+ for (const object of res.Contents) {
+ if (object.Key != null) {
+ result.push(object.Key);
+ } else {
+ getLogger().warn(
+ "Listing live mails in S3 returns an object with no Key.",
+ );
+ }
+ }
+ return result;
+ }
+
+ async deliverS3MailObject(messageId: string) {
+ const mailPath = `${this.liveMailPrefix}${messageId}`;
+ const command = new GetObjectCommand({
+ Bucket: this.mailBucket,
+ Key: mailPath,
+ });
+ const res = await this.s3Client.send(command);
+
+ if (res.Body == null) {
+ // TODO: Better error.
+ throw new Error();
+ }
+
+ const rawMail = await res.Body.transformToString();
+ await this.callback(rawMail);
+
+ // TODO: Continue here.
+ await s3MoveObject(this.s3Client, this.mailBucket, mailPath, );
+ }
+
+ async recycleLiveMails() {
+ const mails = await this.listLiveMails();
+ }
+}
diff --git a/services/docker/mail-server/aws-sendmail/config.ts b/services/docker/mail-server/aws-sendmail/config.ts
new file mode 100644
index 0000000..0212029
--- /dev/null
+++ b/services/docker/mail-server/aws-sendmail/config.ts
@@ -0,0 +1,56 @@
+import { createSingleton, transformProperties } from "./util.ts";
+
+export const APP_PREFIX = "crupest";
+export const APP_NAME = "mailserver";
+
+interface ConfigItemDef {
+ env: string;
+ description: string;
+ default?: string;
+}
+
+export const CONFIG_DEFS = {
+ awsAccessKeyId: { env: "AWS_USER", description: "aws access key id" },
+ awsSecretAccessKey: {
+ env: "AWS_PASSWORD",
+ description: "aws secret access key",
+ },
+ awsMailBucket: {
+ env: "AWS_MAIL_BUCKET",
+ description: "aws s3 bucket saving raw mails",
+ },
+} as const satisfies Record<string, ConfigItemDef>;
+
+type ConfigDefs = typeof CONFIG_DEFS;
+type ConfigKey = keyof ConfigDefs;
+type ConfigMap = {
+ [K in ConfigKey]: ConfigDefs[K] & { value: string };
+};
+
+function resolveAppConfigItem(def: ConfigItemDef): string {
+ const envKey =
+ `${APP_PREFIX.toUpperCase()}_${APP_NAME.toUpperCase()}_${def.env}`;
+ const value = Deno.env.get(envKey);
+ if (value != null) return value;
+ if (def.default != null) return def.default;
+ throw new Error(
+ `Required env ${envKey} (${def.description}) is not set.`,
+ );
+}
+
+export class Config {
+ private config = transformProperties(
+ CONFIG_DEFS,
+ (def) => ({ ...def, value: resolveAppConfigItem(def) }),
+ ) as ConfigMap;
+
+ get<K extends ConfigKey>(key: K): ConfigMap[K] {
+ return this.config[key];
+ }
+
+ getValue(key: ConfigKey): string {
+ return this.get(key).value;
+ }
+}
+
+export const [getConfig, setConfig] = createSingleton<Config>("Config");
diff --git a/services/docker/mail-server/aws-sendmail/db.ts b/services/docker/mail-server/aws-sendmail/db.ts
new file mode 100644
index 0000000..e239e72
--- /dev/null
+++ b/services/docker/mail-server/aws-sendmail/db.ts
@@ -0,0 +1,121 @@
+// spellchecker: words sqlocal kysely insertable updateable
+
+import { SQLocalKysely } from "sqlocal/kysely";
+import { Generated, Insertable, Kysely, Migration, Migrator } from "kysely";
+
+const tableNames = {
+ mail: {
+ table: "mail",
+ columns: {
+ id: "id",
+ messageId: "message_id",
+ awsMessageId: "aws_message_id",
+ raw: "raw",
+ },
+ },
+} as const;
+
+interface MailTable {
+ [tableNames.mail.columns.id]: Generated<number>;
+ [tableNames.mail.columns.messageId]: string;
+ [tableNames.mail.columns.awsMessageId]: string | null;
+ [tableNames.mail.columns.raw]: string;
+}
+
+interface Database {
+ [tableNames.mail.table]: MailTable;
+}
+
+const migrations: Record<string, Migration> = {
+ "0001-init": {
+ // deno-lint-ignore no-explicit-any
+ async up(db: Kysely<any>): Promise<void> {
+ const names = tableNames.mail;
+
+ await db.schema
+ .createTable(names.table)
+ .addColumn(
+ names.columns.id,
+ "integer",
+ (col) => col.primaryKey().autoIncrement(),
+ )
+ .addColumn(
+ names.columns.messageId,
+ "text",
+ (col) => col.notNull().unique(),
+ )
+ .addColumn(names.columns.awsMessageId, "text", (col) => col.unique())
+ .addColumn(names.columns.raw, "text", (col) => col.notNull())
+ .execute();
+
+ for (
+ const column of [names.columns.messageId, names.columns.awsMessageId]
+ ) {
+ await db.schema
+ .createIndex(`${names.table}_${column}`)
+ .on(names.table)
+ .column(column)
+ .execute();
+ }
+ },
+
+ // deno-lint-ignore no-explicit-any
+ async down(db: Kysely<any>): Promise<void> {
+ await db.schema.dropTable(tableNames.mail.table).execute();
+ },
+ },
+};
+
+export class DbService {
+ private _sqlocal;
+ private _db;
+ private _migrator;
+
+ constructor(public readonly path: string) {
+ this._sqlocal = new SQLocalKysely({
+ databasePath: path,
+ onInit: (sql) => [
+ // Though this can be executed only once when database is
+ // created, re-calls should not affect performance.
+ sql`PRAGMA journal_mode=WAL;`,
+ ],
+ });
+ const db = new Kysely<Database>({ dialect: this._sqlocal.dialect });
+ this._db = db;
+ this._migrator = new Migrator({
+ db,
+ provider: {
+ getMigrations(): Promise<Record<string, Migration>> {
+ return Promise.resolve(migrations);
+ },
+ },
+ });
+ }
+
+ async migrate(): Promise<void> {
+ await this._migrator.migrateToLatest();
+ }
+
+ async addMail(mail: Insertable<MailTable>): Promise<void> {
+ await this._db.insertInto(tableNames.mail.table).values(mail)
+ .executeTakeFirstOrThrow();
+ }
+
+ async messageIdToAws(messageId: string): Promise<string | null> {
+ const row = await this._db.selectFrom(tableNames.mail.table).where(
+ tableNames.mail.columns.messageId,
+ "=",
+ messageId,
+ ).select(tableNames.mail.columns.awsMessageId).executeTakeFirst();
+ return row?.aws_message_id ?? null;
+ }
+
+ async messageIdFromAws(awsMessageId: string): Promise<string | null> {
+ const row = await this._db.selectFrom(tableNames.mail.table).where(
+ tableNames.mail.columns.awsMessageId,
+ "=",
+ awsMessageId,
+ ).select(tableNames.mail.columns.messageId).executeTakeFirst();
+ return row?.message_id ?? null;
+ }
+}
diff --git a/services/docker/mail-server/aws-sendmail/deno.json b/services/docker/mail-server/aws-sendmail/deno.json
new file mode 100644
index 0000000..67dd7d1
--- /dev/null
+++ b/services/docker/mail-server/aws-sendmail/deno.json
@@ -0,0 +1,16 @@
+{
+ "unstable": [ "temporal", "cron" ],
+ "tasks": {
+ "dev": "deno run --watch main.ts"
+ },
+ "imports": {
+ "@aws-sdk/client-s3": "npm:@aws-sdk/client-s3@^3.797.0",
+ "@aws-sdk/client-sesv2": "npm:@aws-sdk/client-sesv2@^3.782.0",
+ "@oak/oak": "jsr:@oak/oak@^17.1.4",
+ "@std/cli": "jsr:@std/cli@^1.0.17",
+ "@std/encoding": "jsr:@std/encoding@^1.0.10",
+ "@std/path": "jsr:@std/path@^1.0.9",
+ "kysely": "npm:kysely@^0.28.2",
+ "sqlocal": "npm:sqlocal@^0.14.0"
+ }
+}
diff --git a/services/docker/mail-server/aws-sendmail/deno.lock b/services/docker/mail-server/aws-sendmail/deno.lock
new file mode 100644
index 0000000..81e20d7
--- /dev/null
+++ b/services/docker/mail-server/aws-sendmail/deno.lock
@@ -0,0 +1,1440 @@
+{
+ "version": "4",
+ "specifiers": {
+ "jsr:@oak/commons@1": "1.0.0",
+ "jsr:@oak/oak@^17.1.4": "17.1.4",
+ "jsr:@std/assert@1": "1.0.13",
+ "jsr:@std/bytes@1": "1.0.5",
+ "jsr:@std/cli@^1.0.17": "1.0.17",
+ "jsr:@std/crypto@1": "1.0.4",
+ "jsr:@std/encoding@1": "1.0.10",
+ "jsr:@std/encoding@^1.0.10": "1.0.10",
+ "jsr:@std/http@1": "1.0.15",
+ "jsr:@std/media-types@1": "1.1.0",
+ "jsr:@std/path@1": "1.0.9",
+ "jsr:@std/path@^1.0.9": "1.0.9",
+ "npm:@aws-sdk/client-s3@^3.797.0": "3.797.0",
+ "npm:@aws-sdk/client-sesv2@^3.782.0": "3.782.0",
+ "npm:@types/node@*": "22.12.0",
+ "npm:kysely@~0.28.2": "0.28.2",
+ "npm:path-to-regexp@^6.3.0": "6.3.0",
+ "npm:sqlocal@0.14": "0.14.0_kysely@0.28.2"
+ },
+ "jsr": {
+ "@oak/commons@1.0.0": {
+ "integrity": "49805b55603c3627a9d6235c0655aa2b6222d3036b3a13ff0380c16368f607ac",
+ "dependencies": [
+ "jsr:@std/assert",
+ "jsr:@std/bytes",
+ "jsr:@std/crypto",
+ "jsr:@std/encoding@1",
+ "jsr:@std/http",
+ "jsr:@std/media-types"
+ ]
+ },
+ "@oak/oak@17.1.4": {
+ "integrity": "60530b582bf276ff741e39cc664026781aa08dd5f2bc5134d756cc427bf2c13e",
+ "dependencies": [
+ "jsr:@oak/commons",
+ "jsr:@std/assert",
+ "jsr:@std/bytes",
+ "jsr:@std/http",
+ "jsr:@std/media-types",
+ "jsr:@std/path@1",
+ "npm:path-to-regexp"
+ ]
+ },
+ "@std/assert@1.0.13": {
+ "integrity": "ae0d31e41919b12c656c742b22522c32fb26ed0cba32975cb0de2a273cb68b29"
+ },
+ "@std/bytes@1.0.5": {
+ "integrity": "4465dd739d7963d964c809202ebea6d5c6b8e3829ef25c6a224290fbb8a1021e"
+ },
+ "@std/cli@1.0.17": {
+ "integrity": "e15b9abe629e17be90cc6216327f03a29eae613365f1353837fa749aad29ce7b"
+ },
+ "@std/crypto@1.0.4": {
+ "integrity": "cee245c453bd5366207f4d8aa25ea3e9c86cecad2be3fefcaa6cb17203d79340"
+ },
+ "@std/encoding@1.0.10": {
+ "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1"
+ },
+ "@std/http@1.0.15": {
+ "integrity": "435a4934b4e196e82a8233f724da525f7b7112f3566502f28815e94764c19159",
+ "dependencies": [
+ "jsr:@std/encoding@^1.0.10"
+ ]
+ },
+ "@std/media-types@1.1.0": {
+ "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4"
+ },
+ "@std/path@1.0.9": {
+ "integrity": "260a49f11edd3db93dd38350bf9cd1b4d1366afa98e81b86167b4e3dd750129e"
+ }
+ },
+ "npm": {
+ "@aws-crypto/crc32@5.2.0": {
+ "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==",
+ "dependencies": [
+ "@aws-crypto/util",
+ "@aws-sdk/types",
+ "tslib"
+ ]
+ },
+ "@aws-crypto/crc32c@5.2.0": {
+ "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==",
+ "dependencies": [
+ "@aws-crypto/util",
+ "@aws-sdk/types",
+ "tslib"
+ ]
+ },
+ "@aws-crypto/sha1-browser@5.2.0": {
+ "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==",
+ "dependencies": [
+ "@aws-crypto/supports-web-crypto",
+ "@aws-crypto/util",
+ "@aws-sdk/types",
+ "@aws-sdk/util-locate-window",
+ "@smithy/util-utf8@2.3.0",
+ "tslib"
+ ]
+ },
+ "@aws-crypto/sha256-browser@5.2.0": {
+ "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==",
+ "dependencies": [
+ "@aws-crypto/sha256-js",
+ "@aws-crypto/supports-web-crypto",
+ "@aws-crypto/util",
+ "@aws-sdk/types",
+ "@aws-sdk/util-locate-window",
+ "@smithy/util-utf8@2.3.0",
+ "tslib"
+ ]
+ },
+ "@aws-crypto/sha256-js@5.2.0": {
+ "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==",
+ "dependencies": [
+ "@aws-crypto/util",
+ "@aws-sdk/types",
+ "tslib"
+ ]
+ },
+ "@aws-crypto/supports-web-crypto@5.2.0": {
+ "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==",
+ "dependencies": [
+ "tslib"
+ ]
+ },
+ "@aws-crypto/util@5.2.0": {
+ "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==",
+ "dependencies": [
+ "@aws-sdk/types",
+ "@smithy/util-utf8@2.3.0",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/client-s3@3.797.0": {
+ "integrity": "sha512-N7pB94mXi4fCt+rYmR9TzfbbwZsWs6Mnk/jDNX9sAZyWkZQnS3AZ/nRtnUmdCimdnOPOMNVjmAoZ4mW3Ff8LDw==",
+ "dependencies": [
+ "@aws-crypto/sha1-browser",
+ "@aws-crypto/sha256-browser",
+ "@aws-crypto/sha256-js",
+ "@aws-sdk/core@3.796.0",
+ "@aws-sdk/credential-provider-node@3.797.0",
+ "@aws-sdk/middleware-bucket-endpoint",
+ "@aws-sdk/middleware-expect-continue",
+ "@aws-sdk/middleware-flexible-checksums",
+ "@aws-sdk/middleware-host-header",
+ "@aws-sdk/middleware-location-constraint",
+ "@aws-sdk/middleware-logger",
+ "@aws-sdk/middleware-recursion-detection",
+ "@aws-sdk/middleware-sdk-s3@3.796.0",
+ "@aws-sdk/middleware-ssec",
+ "@aws-sdk/middleware-user-agent@3.796.0",
+ "@aws-sdk/region-config-resolver",
+ "@aws-sdk/signature-v4-multi-region@3.796.0",
+ "@aws-sdk/types",
+ "@aws-sdk/util-endpoints@3.787.0",
+ "@aws-sdk/util-user-agent-browser",
+ "@aws-sdk/util-user-agent-node@3.796.0",
+ "@aws-sdk/xml-builder",
+ "@smithy/config-resolver",
+ "@smithy/core",
+ "@smithy/eventstream-serde-browser",
+ "@smithy/eventstream-serde-config-resolver",
+ "@smithy/eventstream-serde-node",
+ "@smithy/fetch-http-handler",
+ "@smithy/hash-blob-browser",
+ "@smithy/hash-node",
+ "@smithy/hash-stream-node",
+ "@smithy/invalid-dependency",
+ "@smithy/md5-js",
+ "@smithy/middleware-content-length",
+ "@smithy/middleware-endpoint",
+ "@smithy/middleware-retry",
+ "@smithy/middleware-serde",
+ "@smithy/middleware-stack",
+ "@smithy/node-config-provider",
+ "@smithy/node-http-handler",
+ "@smithy/protocol-http",
+ "@smithy/smithy-client",
+ "@smithy/types",
+ "@smithy/url-parser",
+ "@smithy/util-base64",
+ "@smithy/util-body-length-browser",
+ "@smithy/util-body-length-node",
+ "@smithy/util-defaults-mode-browser",
+ "@smithy/util-defaults-mode-node",
+ "@smithy/util-endpoints",
+ "@smithy/util-middleware",
+ "@smithy/util-retry",
+ "@smithy/util-stream",
+ "@smithy/util-utf8@4.0.0",
+ "@smithy/util-waiter",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/client-sesv2@3.782.0": {
+ "integrity": "sha512-h7Uhh+9BZ9UCE+SM5vld5d10M3VpF9gYbqmzW4+gWtBq9lmsLWjB9Dsk74Xrh4i7s9op7TEfVYm/o+ZB180BtQ==",
+ "dependencies": [
+ "@aws-crypto/sha256-browser",
+ "@aws-crypto/sha256-js",
+ "@aws-sdk/core@3.775.0",
+ "@aws-sdk/credential-provider-node@3.782.0",
+ "@aws-sdk/middleware-host-header",
+ "@aws-sdk/middleware-logger",
+ "@aws-sdk/middleware-recursion-detection",
+ "@aws-sdk/middleware-user-agent@3.782.0",
+ "@aws-sdk/region-config-resolver",
+ "@aws-sdk/signature-v4-multi-region@3.775.0",
+ "@aws-sdk/types",
+ "@aws-sdk/util-endpoints@3.782.0",
+ "@aws-sdk/util-user-agent-browser",
+ "@aws-sdk/util-user-agent-node@3.782.0",
+ "@smithy/config-resolver",
+ "@smithy/core",
+ "@smithy/fetch-http-handler",
+ "@smithy/hash-node",
+ "@smithy/invalid-dependency",
+ "@smithy/middleware-content-length",
+ "@smithy/middleware-endpoint",
+ "@smithy/middleware-retry",
+ "@smithy/middleware-serde",
+ "@smithy/middleware-stack",
+ "@smithy/node-config-provider",
+ "@smithy/node-http-handler",
+ "@smithy/protocol-http",
+ "@smithy/smithy-client",
+ "@smithy/types",
+ "@smithy/url-parser",
+ "@smithy/util-base64",
+ "@smithy/util-body-length-browser",
+ "@smithy/util-body-length-node",
+ "@smithy/util-defaults-mode-browser",
+ "@smithy/util-defaults-mode-node",
+ "@smithy/util-endpoints",
+ "@smithy/util-middleware",
+ "@smithy/util-retry",
+ "@smithy/util-utf8@4.0.0",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/client-sso@3.782.0": {
+ "integrity": "sha512-5GlJBejo8wqMpSSEKb45WE82YxI2k73YuebjLH/eWDNQeE6VI5Bh9lA1YQ7xNkLLH8hIsb0pSfKVuwh0VEzVrg==",
+ "dependencies": [
+ "@aws-crypto/sha256-browser",
+ "@aws-crypto/sha256-js",
+ "@aws-sdk/core@3.775.0",
+ "@aws-sdk/middleware-host-header",
+ "@aws-sdk/middleware-logger",
+ "@aws-sdk/middleware-recursion-detection",
+ "@aws-sdk/middleware-user-agent@3.782.0",
+ "@aws-sdk/region-config-resolver",
+ "@aws-sdk/types",
+ "@aws-sdk/util-endpoints@3.782.0",
+ "@aws-sdk/util-user-agent-browser",
+ "@aws-sdk/util-user-agent-node@3.782.0",
+ "@smithy/config-resolver",
+ "@smithy/core",
+ "@smithy/fetch-http-handler",
+ "@smithy/hash-node",
+ "@smithy/invalid-dependency",
+ "@smithy/middleware-content-length",
+ "@smithy/middleware-endpoint",
+ "@smithy/middleware-retry",
+ "@smithy/middleware-serde",
+ "@smithy/middleware-stack",
+ "@smithy/node-config-provider",
+ "@smithy/node-http-handler",
+ "@smithy/protocol-http",
+ "@smithy/smithy-client",
+ "@smithy/types",
+ "@smithy/url-parser",
+ "@smithy/util-base64",
+ "@smithy/util-body-length-browser",
+ "@smithy/util-body-length-node",
+ "@smithy/util-defaults-mode-browser",
+ "@smithy/util-defaults-mode-node",
+ "@smithy/util-endpoints",
+ "@smithy/util-middleware",
+ "@smithy/util-retry",
+ "@smithy/util-utf8@4.0.0",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/client-sso@3.797.0": {
+ "integrity": "sha512-9xuR918p7tShR67ZL+AOSbydpJxSHAOdXcQswxxWR/hKCF7tULX7tyL3gNo3l/ETp0CDcStvorOdH/nCbzEOjw==",
+ "dependencies": [
+ "@aws-crypto/sha256-browser",
+ "@aws-crypto/sha256-js",
+ "@aws-sdk/core@3.796.0",
+ "@aws-sdk/middleware-host-header",
+ "@aws-sdk/middleware-logger",
+ "@aws-sdk/middleware-recursion-detection",
+ "@aws-sdk/middleware-user-agent@3.796.0",
+ "@aws-sdk/region-config-resolver",
+ "@aws-sdk/types",
+ "@aws-sdk/util-endpoints@3.787.0",
+ "@aws-sdk/util-user-agent-browser",
+ "@aws-sdk/util-user-agent-node@3.796.0",
+ "@smithy/config-resolver",
+ "@smithy/core",
+ "@smithy/fetch-http-handler",
+ "@smithy/hash-node",
+ "@smithy/invalid-dependency",
+ "@smithy/middleware-content-length",
+ "@smithy/middleware-endpoint",
+ "@smithy/middleware-retry",
+ "@smithy/middleware-serde",
+ "@smithy/middleware-stack",
+ "@smithy/node-config-provider",
+ "@smithy/node-http-handler",
+ "@smithy/protocol-http",
+ "@smithy/smithy-client",
+ "@smithy/types",
+ "@smithy/url-parser",
+ "@smithy/util-base64",
+ "@smithy/util-body-length-browser",
+ "@smithy/util-body-length-node",
+ "@smithy/util-defaults-mode-browser",
+ "@smithy/util-defaults-mode-node",
+ "@smithy/util-endpoints",
+ "@smithy/util-middleware",
+ "@smithy/util-retry",
+ "@smithy/util-utf8@4.0.0",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/core@3.775.0": {
+ "integrity": "sha512-8vpW4WihVfz0DX+7WnnLGm3GuQER++b0IwQG35JlQMlgqnc44M//KbJPsIHA0aJUJVwJAEShgfr5dUbY8WUzaA==",
+ "dependencies": [
+ "@aws-sdk/types",
+ "@smithy/core",
+ "@smithy/node-config-provider",
+ "@smithy/property-provider",
+ "@smithy/protocol-http",
+ "@smithy/signature-v4@5.0.2",
+ "@smithy/smithy-client",
+ "@smithy/types",
+ "@smithy/util-middleware",
+ "fast-xml-parser",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/core@3.796.0": {
+ "integrity": "sha512-tH8Sp7lCxISVoLnkyv4AouuXs2CDlMhTuesWa0lq2NX1f+DXsMwSBtN37ttZdpFMw3F8mWdsJt27X9h2Oq868A==",
+ "dependencies": [
+ "@aws-sdk/types",
+ "@smithy/core",
+ "@smithy/node-config-provider",
+ "@smithy/property-provider",
+ "@smithy/protocol-http",
+ "@smithy/signature-v4@5.1.0",
+ "@smithy/smithy-client",
+ "@smithy/types",
+ "@smithy/util-middleware",
+ "fast-xml-parser",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/credential-provider-env@3.775.0": {
+ "integrity": "sha512-6ESVxwCbGm7WZ17kY1fjmxQud43vzJFoLd4bmlR+idQSWdqlzGDYdcfzpjDKTcivdtNrVYmFvcH1JBUwCRAZhw==",
+ "dependencies": [
+ "@aws-sdk/core@3.775.0",
+ "@aws-sdk/types",
+ "@smithy/property-provider",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/credential-provider-env@3.796.0": {
+ "integrity": "sha512-kQzGKm4IOYYO6vUrai2JocNwhJm4Aml2BsAV+tBhFhhkutE7khf9PUucoVjB78b0J48nF+kdSacqzY+gB81/Uw==",
+ "dependencies": [
+ "@aws-sdk/core@3.796.0",
+ "@aws-sdk/types",
+ "@smithy/property-provider",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/credential-provider-http@3.775.0": {
+ "integrity": "sha512-PjDQeDH/J1S0yWV32wCj2k5liRo0ssXMseCBEkCsD3SqsU8o5cU82b0hMX4sAib/RkglCSZqGO0xMiN0/7ndww==",
+ "dependencies": [
+ "@aws-sdk/core@3.775.0",
+ "@aws-sdk/types",
+ "@smithy/fetch-http-handler",
+ "@smithy/node-http-handler",
+ "@smithy/property-provider",
+ "@smithy/protocol-http",
+ "@smithy/smithy-client",
+ "@smithy/types",
+ "@smithy/util-stream",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/credential-provider-http@3.796.0": {
+ "integrity": "sha512-wWOT6VAHIKOuHdKFGm1iyKvx7f6+Kc/YTzFWJPuT+l+CPlXR6ylP1UMIDsHHLKpMzsrh3CH77QDsjkhQrnKkfg==",
+ "dependencies": [
+ "@aws-sdk/core@3.796.0",
+ "@aws-sdk/types",
+ "@smithy/fetch-http-handler",
+ "@smithy/node-http-handler",
+ "@smithy/property-provider",
+ "@smithy/protocol-http",
+ "@smithy/smithy-client",
+ "@smithy/types",
+ "@smithy/util-stream",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/credential-provider-ini@3.782.0": {
+ "integrity": "sha512-wd4KdRy2YjLsE4Y7pz00470Iip06GlRHkG4dyLW7/hFMzEO2o7ixswCWp6J2VGZVAX64acknlv2Q0z02ebjmhw==",
+ "dependencies": [
+ "@aws-sdk/core@3.775.0",
+ "@aws-sdk/credential-provider-env@3.775.0",
+ "@aws-sdk/credential-provider-http@3.775.0",
+ "@aws-sdk/credential-provider-process@3.775.0",
+ "@aws-sdk/credential-provider-sso@3.782.0",
+ "@aws-sdk/credential-provider-web-identity@3.782.0",
+ "@aws-sdk/nested-clients@3.782.0",
+ "@aws-sdk/types",
+ "@smithy/credential-provider-imds",
+ "@smithy/property-provider",
+ "@smithy/shared-ini-file-loader",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/credential-provider-ini@3.797.0": {
+ "integrity": "sha512-Zpj6pJ2hnebrhLDr+x61ArMUkjHG6mfJRfamHxeVTgZkhLcwHjC5aM4u9pWTVugIaPY+VBtgkKPbi3TRbHlt2g==",
+ "dependencies": [
+ "@aws-sdk/core@3.796.0",
+ "@aws-sdk/credential-provider-env@3.796.0",
+ "@aws-sdk/credential-provider-http@3.796.0",
+ "@aws-sdk/credential-provider-process@3.796.0",
+ "@aws-sdk/credential-provider-sso@3.797.0",
+ "@aws-sdk/credential-provider-web-identity@3.797.0",
+ "@aws-sdk/nested-clients@3.797.0",
+ "@aws-sdk/types",
+ "@smithy/credential-provider-imds",
+ "@smithy/property-provider",
+ "@smithy/shared-ini-file-loader",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/credential-provider-node@3.782.0": {
+ "integrity": "sha512-HZiAF+TCEyKjju9dgysjiPIWgt/+VerGaeEp18mvKLNfgKz1d+/82A2USEpNKTze7v3cMFASx3CvL8yYyF7mJw==",
+ "dependencies": [
+ "@aws-sdk/credential-provider-env@3.775.0",
+ "@aws-sdk/credential-provider-http@3.775.0",
+ "@aws-sdk/credential-provider-ini@3.782.0",
+ "@aws-sdk/credential-provider-process@3.775.0",
+ "@aws-sdk/credential-provider-sso@3.782.0",
+ "@aws-sdk/credential-provider-web-identity@3.782.0",
+ "@aws-sdk/types",
+ "@smithy/credential-provider-imds",
+ "@smithy/property-provider",
+ "@smithy/shared-ini-file-loader",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/credential-provider-node@3.797.0": {
+ "integrity": "sha512-xJSWvvnmzEfHbqbpN4F3E3mI9+zJ/VWLGiKOjzX1Inbspa5WqNn2GoMamolZR2TvvZS4F3Hp73TD1WoBzkIjuw==",
+ "dependencies": [
+ "@aws-sdk/credential-provider-env@3.796.0",
+ "@aws-sdk/credential-provider-http@3.796.0",
+ "@aws-sdk/credential-provider-ini@3.797.0",
+ "@aws-sdk/credential-provider-process@3.796.0",
+ "@aws-sdk/credential-provider-sso@3.797.0",
+ "@aws-sdk/credential-provider-web-identity@3.797.0",
+ "@aws-sdk/types",
+ "@smithy/credential-provider-imds",
+ "@smithy/property-provider",
+ "@smithy/shared-ini-file-loader",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/credential-provider-process@3.775.0": {
+ "integrity": "sha512-A6k68H9rQp+2+7P7SGO90Csw6nrUEm0Qfjpn9Etc4EboZhhCLs9b66umUsTsSBHus4FDIe5JQxfCUyt1wgNogg==",
+ "dependencies": [
+ "@aws-sdk/core@3.775.0",
+ "@aws-sdk/types",
+ "@smithy/property-provider",
+ "@smithy/shared-ini-file-loader",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/credential-provider-process@3.796.0": {
+ "integrity": "sha512-r4e8/4AdKn/qQbRVocW7oXkpoiuXdTv0qty8AASNLnbQnT1vjD1bvmP6kp4fbHPWgwY8I9h0Dqjp49uy9Bqyuw==",
+ "dependencies": [
+ "@aws-sdk/core@3.796.0",
+ "@aws-sdk/types",
+ "@smithy/property-provider",
+ "@smithy/shared-ini-file-loader",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/credential-provider-sso@3.782.0": {
+ "integrity": "sha512-1y1ucxTtTIGDSNSNxriQY8msinilhe9gGvQpUDYW9gboyC7WQJPDw66imy258V6osdtdi+xoHzVCbCz3WhosMQ==",
+ "dependencies": [
+ "@aws-sdk/client-sso@3.782.0",
+ "@aws-sdk/core@3.775.0",
+ "@aws-sdk/token-providers@3.782.0",
+ "@aws-sdk/types",
+ "@smithy/property-provider",
+ "@smithy/shared-ini-file-loader",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/credential-provider-sso@3.797.0": {
+ "integrity": "sha512-VlyWnjTsTnBXqXcEW0nw3S7nj00n9fYwF6uU6HPO9t860yIySG01lNPAWTvAt3DfVL5SRS0GANriCZF6ohcMcQ==",
+ "dependencies": [
+ "@aws-sdk/client-sso@3.797.0",
+ "@aws-sdk/core@3.796.0",
+ "@aws-sdk/token-providers@3.797.0",
+ "@aws-sdk/types",
+ "@smithy/property-provider",
+ "@smithy/shared-ini-file-loader",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/credential-provider-web-identity@3.782.0": {
+ "integrity": "sha512-xCna0opVPaueEbJoclj5C6OpDNi0Gynj+4d7tnuXGgQhTHPyAz8ZyClkVqpi5qvHTgxROdUEDxWqEO5jqRHZHQ==",
+ "dependencies": [
+ "@aws-sdk/core@3.775.0",
+ "@aws-sdk/nested-clients@3.782.0",
+ "@aws-sdk/types",
+ "@smithy/property-provider",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/credential-provider-web-identity@3.797.0": {
+ "integrity": "sha512-DIb05FEmdOX7bNsqSVEAB3UkaDgrYHonQ2+gcBLqZ7LoDNnovHIlvC5jii93usgEStxITZstnzw+49keNEgVWw==",
+ "dependencies": [
+ "@aws-sdk/core@3.796.0",
+ "@aws-sdk/nested-clients@3.797.0",
+ "@aws-sdk/types",
+ "@smithy/property-provider",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/middleware-bucket-endpoint@3.775.0": {
+ "integrity": "sha512-qogMIpVChDYr4xiUNC19/RDSw/sKoHkAhouS6Skxiy6s27HBhow1L3Z1qVYXuBmOZGSWPU0xiyZCvOyWrv9s+Q==",
+ "dependencies": [
+ "@aws-sdk/types",
+ "@aws-sdk/util-arn-parser",
+ "@smithy/node-config-provider",
+ "@smithy/protocol-http",
+ "@smithy/types",
+ "@smithy/util-config-provider",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/middleware-expect-continue@3.775.0": {
+ "integrity": "sha512-Apd3owkIeUW5dnk3au9np2IdW2N0zc9NjTjHiH+Mx3zqwSrc+m+ANgJVgk9mnQjMzU/vb7VuxJ0eqdEbp5gYsg==",
+ "dependencies": [
+ "@aws-sdk/types",
+ "@smithy/protocol-http",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/middleware-flexible-checksums@3.796.0": {
+ "integrity": "sha512-JTqnyzGlbvXDcEnBtd5LFNrCFKUHnGyp/V9+BkvzNP02WXABLWzYvj1TCaf5pQySwK/b4kVn5lvbpTi0rXqjZw==",
+ "dependencies": [
+ "@aws-crypto/crc32",
+ "@aws-crypto/crc32c",
+ "@aws-crypto/util",
+ "@aws-sdk/core@3.796.0",
+ "@aws-sdk/types",
+ "@smithy/is-array-buffer@4.0.0",
+ "@smithy/node-config-provider",
+ "@smithy/protocol-http",
+ "@smithy/types",
+ "@smithy/util-middleware",
+ "@smithy/util-stream",
+ "@smithy/util-utf8@4.0.0",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/middleware-host-header@3.775.0": {
+ "integrity": "sha512-tkSegM0Z6WMXpLB8oPys/d+umYIocvO298mGvcMCncpRl77L9XkvSLJIFzaHes+o7djAgIduYw8wKIMStFss2w==",
+ "dependencies": [
+ "@aws-sdk/types",
+ "@smithy/protocol-http",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/middleware-location-constraint@3.775.0": {
+ "integrity": "sha512-8TMXEHZXZTFTckQLyBT5aEI8fX11HZcwZseRifvBKKpj0RZDk4F0EEYGxeNSPpUQ7n+PRWyfAEnnZNRdAj/1NQ==",
+ "dependencies": [
+ "@aws-sdk/types",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/middleware-logger@3.775.0": {
+ "integrity": "sha512-FaxO1xom4MAoUJsldmR92nT1G6uZxTdNYOFYtdHfd6N2wcNaTuxgjIvqzg5y7QIH9kn58XX/dzf1iTjgqUStZw==",
+ "dependencies": [
+ "@aws-sdk/types",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/middleware-recursion-detection@3.775.0": {
+ "integrity": "sha512-GLCzC8D0A0YDG5u3F5U03Vb9j5tcOEFhr8oc6PDk0k0vm5VwtZOE6LvK7hcCSoAB4HXyOUM0sQuXrbaAh9OwXA==",
+ "dependencies": [
+ "@aws-sdk/types",
+ "@smithy/protocol-http",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/middleware-sdk-s3@3.775.0": {
+ "integrity": "sha512-zsvcu7cWB28JJ60gVvjxPCI7ZU7jWGcpNACPiZGyVtjYXwcxyhXbYEVDSWKsSA6ERpz9XrpLYod8INQWfW3ECg==",
+ "dependencies": [
+ "@aws-sdk/core@3.775.0",
+ "@aws-sdk/types",
+ "@aws-sdk/util-arn-parser",
+ "@smithy/core",
+ "@smithy/node-config-provider",
+ "@smithy/protocol-http",
+ "@smithy/signature-v4@5.0.2",
+ "@smithy/smithy-client",
+ "@smithy/types",
+ "@smithy/util-config-provider",
+ "@smithy/util-middleware",
+ "@smithy/util-stream",
+ "@smithy/util-utf8@4.0.0",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/middleware-sdk-s3@3.796.0": {
+ "integrity": "sha512-5o78oE79sGOtYkL7Up02h2nmr9UhGQZJgxE29EBdTw4dZ1EaA46L+C8oA+fBCmAB5xPQsjQqvhRrsr4Lcp+jZQ==",
+ "dependencies": [
+ "@aws-sdk/core@3.796.0",
+ "@aws-sdk/types",
+ "@aws-sdk/util-arn-parser",
+ "@smithy/core",
+ "@smithy/node-config-provider",
+ "@smithy/protocol-http",
+ "@smithy/signature-v4@5.1.0",
+ "@smithy/smithy-client",
+ "@smithy/types",
+ "@smithy/util-config-provider",
+ "@smithy/util-middleware",
+ "@smithy/util-stream",
+ "@smithy/util-utf8@4.0.0",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/middleware-ssec@3.775.0": {
+ "integrity": "sha512-Iw1RHD8vfAWWPzBBIKaojO4GAvQkHOYIpKdAfis/EUSUmSa79QsnXnRqsdcE0mCB0Ylj23yi+ah4/0wh9FsekA==",
+ "dependencies": [
+ "@aws-sdk/types",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/middleware-user-agent@3.782.0": {
+ "integrity": "sha512-i32H2R6IItX+bQ2p4+v2gGO2jA80jQoJO2m1xjU9rYWQW3+ErWy4I5YIuQHTBfb6hSdAHbaRfqPDgbv9J2rjEg==",
+ "dependencies": [
+ "@aws-sdk/core@3.775.0",
+ "@aws-sdk/types",
+ "@aws-sdk/util-endpoints@3.782.0",
+ "@smithy/core",
+ "@smithy/protocol-http",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/middleware-user-agent@3.796.0": {
+ "integrity": "sha512-IeNg+3jNWT37J45opi5Jx89hGF0lOnZjiNwlMp3rKq7PlOqy8kWq5J1Gxk0W3tIkPpuf68CtBs/QFrRXWOjsZw==",
+ "dependencies": [
+ "@aws-sdk/core@3.796.0",
+ "@aws-sdk/types",
+ "@aws-sdk/util-endpoints@3.787.0",
+ "@smithy/core",
+ "@smithy/protocol-http",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/nested-clients@3.782.0": {
+ "integrity": "sha512-QOYC8q7luzHFXrP0xYAqBctoPkynjfV0r9dqntFu4/IWMTyC1vlo1UTxFAjIPyclYw92XJyEkVCVg9v/nQnsUA==",
+ "dependencies": [
+ "@aws-crypto/sha256-browser",
+ "@aws-crypto/sha256-js",
+ "@aws-sdk/core@3.775.0",
+ "@aws-sdk/middleware-host-header",
+ "@aws-sdk/middleware-logger",
+ "@aws-sdk/middleware-recursion-detection",
+ "@aws-sdk/middleware-user-agent@3.782.0",
+ "@aws-sdk/region-config-resolver",
+ "@aws-sdk/types",
+ "@aws-sdk/util-endpoints@3.782.0",
+ "@aws-sdk/util-user-agent-browser",
+ "@aws-sdk/util-user-agent-node@3.782.0",
+ "@smithy/config-resolver",
+ "@smithy/core",
+ "@smithy/fetch-http-handler",
+ "@smithy/hash-node",
+ "@smithy/invalid-dependency",
+ "@smithy/middleware-content-length",
+ "@smithy/middleware-endpoint",
+ "@smithy/middleware-retry",
+ "@smithy/middleware-serde",
+ "@smithy/middleware-stack",
+ "@smithy/node-config-provider",
+ "@smithy/node-http-handler",
+ "@smithy/protocol-http",
+ "@smithy/smithy-client",
+ "@smithy/types",
+ "@smithy/url-parser",
+ "@smithy/util-base64",
+ "@smithy/util-body-length-browser",
+ "@smithy/util-body-length-node",
+ "@smithy/util-defaults-mode-browser",
+ "@smithy/util-defaults-mode-node",
+ "@smithy/util-endpoints",
+ "@smithy/util-middleware",
+ "@smithy/util-retry",
+ "@smithy/util-utf8@4.0.0",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/nested-clients@3.797.0": {
+ "integrity": "sha512-xCsRKdsv0GAg9E28fvYBdC3JR2xdtZ2o41MVknOs+pSFtMsZm3SsgxObN35p1OTMk/o/V0LORGVLnFQMlc5QiA==",
+ "dependencies": [
+ "@aws-crypto/sha256-browser",
+ "@aws-crypto/sha256-js",
+ "@aws-sdk/core@3.796.0",
+ "@aws-sdk/middleware-host-header",
+ "@aws-sdk/middleware-logger",
+ "@aws-sdk/middleware-recursion-detection",
+ "@aws-sdk/middleware-user-agent@3.796.0",
+ "@aws-sdk/region-config-resolver",
+ "@aws-sdk/types",
+ "@aws-sdk/util-endpoints@3.787.0",
+ "@aws-sdk/util-user-agent-browser",
+ "@aws-sdk/util-user-agent-node@3.796.0",
+ "@smithy/config-resolver",
+ "@smithy/core",
+ "@smithy/fetch-http-handler",
+ "@smithy/hash-node",
+ "@smithy/invalid-dependency",
+ "@smithy/middleware-content-length",
+ "@smithy/middleware-endpoint",
+ "@smithy/middleware-retry",
+ "@smithy/middleware-serde",
+ "@smithy/middleware-stack",
+ "@smithy/node-config-provider",
+ "@smithy/node-http-handler",
+ "@smithy/protocol-http",
+ "@smithy/smithy-client",
+ "@smithy/types",
+ "@smithy/url-parser",
+ "@smithy/util-base64",
+ "@smithy/util-body-length-browser",
+ "@smithy/util-body-length-node",
+ "@smithy/util-defaults-mode-browser",
+ "@smithy/util-defaults-mode-node",
+ "@smithy/util-endpoints",
+ "@smithy/util-middleware",
+ "@smithy/util-retry",
+ "@smithy/util-utf8@4.0.0",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/region-config-resolver@3.775.0": {
+ "integrity": "sha512-40iH3LJjrQS3LKUJAl7Wj0bln7RFPEvUYKFxtP8a+oKFDO0F65F52xZxIJbPn6sHkxWDAnZlGgdjZXM3p2g5wQ==",
+ "dependencies": [
+ "@aws-sdk/types",
+ "@smithy/node-config-provider",
+ "@smithy/types",
+ "@smithy/util-config-provider",
+ "@smithy/util-middleware",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/signature-v4-multi-region@3.775.0": {
+ "integrity": "sha512-cnGk8GDfTMJ8p7+qSk92QlIk2bmTmFJqhYxcXZ9PysjZtx0xmfCMxnG3Hjy1oU2mt5boPCVSOptqtWixayM17g==",
+ "dependencies": [
+ "@aws-sdk/middleware-sdk-s3@3.775.0",
+ "@aws-sdk/types",
+ "@smithy/protocol-http",
+ "@smithy/signature-v4@5.0.2",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/signature-v4-multi-region@3.796.0": {
+ "integrity": "sha512-JAOLdvazTc9HlTFslSrIOrKRMuOruuM3FeGw0hyfLP/RIbjd9bqe/xLIzDSJr3wpCpJs0sXoofwJgXtgTipvjA==",
+ "dependencies": [
+ "@aws-sdk/middleware-sdk-s3@3.796.0",
+ "@aws-sdk/types",
+ "@smithy/protocol-http",
+ "@smithy/signature-v4@5.1.0",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/token-providers@3.782.0": {
+ "integrity": "sha512-4tPuk/3+THPrzKaXW4jE2R67UyGwHLFizZ47pcjJWbhb78IIJAy94vbeqEQ+veS84KF5TXcU7g5jGTXC0D70Wg==",
+ "dependencies": [
+ "@aws-sdk/nested-clients@3.782.0",
+ "@aws-sdk/types",
+ "@smithy/property-provider",
+ "@smithy/shared-ini-file-loader",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/token-providers@3.797.0": {
+ "integrity": "sha512-TLFkP4BBdkH2zCXhG3JjaYrRft25MMZ+6/YDz1C/ikq2Zk8krUbVoSmhtYMVz10JtxAPiQ++w0vI/qbz2JSDXg==",
+ "dependencies": [
+ "@aws-sdk/nested-clients@3.797.0",
+ "@aws-sdk/types",
+ "@smithy/property-provider",
+ "@smithy/shared-ini-file-loader",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/types@3.775.0": {
+ "integrity": "sha512-ZoGKwa4C9fC9Av6bdfqcW6Ix5ot05F/S4VxWR2nHuMv7hzfmAjTOcUiWT7UR4hM/U0whf84VhDtXN/DWAk52KA==",
+ "dependencies": [
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/util-arn-parser@3.723.0": {
+ "integrity": "sha512-ZhEfvUwNliOQROcAk34WJWVYTlTa4694kSVhDSjW6lE1bMataPnIN8A0ycukEzBXmd8ZSoBcQLn6lKGl7XIJ5w==",
+ "dependencies": [
+ "tslib"
+ ]
+ },
+ "@aws-sdk/util-endpoints@3.782.0": {
+ "integrity": "sha512-/RJOAO7o7HI6lEa4ASbFFLHGU9iPK876BhsVfnl54MvApPVYWQ9sHO0anOUim2S5lQTwd/6ghuH3rFYSq/+rdw==",
+ "dependencies": [
+ "@aws-sdk/types",
+ "@smithy/types",
+ "@smithy/util-endpoints",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/util-endpoints@3.787.0": {
+ "integrity": "sha512-fd3zkiOkwnbdbN0Xp9TsP5SWrmv0SpT70YEdbb8wAj2DWQwiCmFszaSs+YCvhoCdmlR3Wl9Spu0pGpSAGKeYvQ==",
+ "dependencies": [
+ "@aws-sdk/types",
+ "@smithy/types",
+ "@smithy/util-endpoints",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/util-locate-window@3.723.0": {
+ "integrity": "sha512-Yf2CS10BqK688DRsrKI/EO6B8ff5J86NXe4C+VCysK7UOgN0l1zOTeTukZ3H8Q9tYYX3oaF1961o8vRkFm7Nmw==",
+ "dependencies": [
+ "tslib"
+ ]
+ },
+ "@aws-sdk/util-user-agent-browser@3.775.0": {
+ "integrity": "sha512-txw2wkiJmZKVdDbscK7VBK+u+TJnRtlUjRTLei+elZg2ADhpQxfVAQl436FUeIv6AhB/oRHW6/K/EAGXUSWi0A==",
+ "dependencies": [
+ "@aws-sdk/types",
+ "@smithy/types",
+ "bowser",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/util-user-agent-node@3.782.0": {
+ "integrity": "sha512-dMFkUBgh2Bxuw8fYZQoH/u3H4afQ12VSkzEi//qFiDTwbKYq+u+RYjc8GLDM6JSK1BShMu5AVR7HD4ap1TYUnA==",
+ "dependencies": [
+ "@aws-sdk/middleware-user-agent@3.782.0",
+ "@aws-sdk/types",
+ "@smithy/node-config-provider",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/util-user-agent-node@3.796.0": {
+ "integrity": "sha512-9fQpNcHgVFitf1tbTT8V1xGRoRHSmOAWjrhevo6Tc0WoINMAKz+4JNqfVGWRE5Tmtpq0oHKo1RmvxXQQtJYciA==",
+ "dependencies": [
+ "@aws-sdk/middleware-user-agent@3.796.0",
+ "@aws-sdk/types",
+ "@smithy/node-config-provider",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@aws-sdk/xml-builder@3.775.0": {
+ "integrity": "sha512-b9NGO6FKJeLGYnV7Z1yvcP1TNU4dkD5jNsLWOF1/sygZoASaQhNOlaiJ/1OH331YQ1R1oWk38nBb0frsYkDsOQ==",
+ "dependencies": [
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@smithy/abort-controller@4.0.2": {
+ "integrity": "sha512-Sl/78VDtgqKxN2+1qduaVE140XF+Xg+TafkncspwM4jFP/LHr76ZHmIY/y3V1M0mMLNk+Je6IGbzxy23RSToMw==",
+ "dependencies": [
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@smithy/chunked-blob-reader-native@4.0.0": {
+ "integrity": "sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==",
+ "dependencies": [
+ "@smithy/util-base64",
+ "tslib"
+ ]
+ },
+ "@smithy/chunked-blob-reader@5.0.0": {
+ "integrity": "sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw==",
+ "dependencies": [
+ "tslib"
+ ]
+ },
+ "@smithy/config-resolver@4.1.0": {
+ "integrity": "sha512-8smPlwhga22pwl23fM5ew4T9vfLUCeFXlcqNOCD5M5h8VmNPNUE9j6bQSuRXpDSV11L/E/SwEBQuW8hr6+nS1A==",
+ "dependencies": [
+ "@smithy/node-config-provider",
+ "@smithy/types",
+ "@smithy/util-config-provider",
+ "@smithy/util-middleware",
+ "tslib"
+ ]
+ },
+ "@smithy/core@3.2.0": {
+ "integrity": "sha512-k17bgQhVZ7YmUvA8at4af1TDpl0NDMBuBKJl8Yg0nrefwmValU+CnA5l/AriVdQNthU/33H3nK71HrLgqOPr1Q==",
+ "dependencies": [
+ "@smithy/middleware-serde",
+ "@smithy/protocol-http",
+ "@smithy/types",
+ "@smithy/util-body-length-browser",
+ "@smithy/util-middleware",
+ "@smithy/util-stream",
+ "@smithy/util-utf8@4.0.0",
+ "tslib"
+ ]
+ },
+ "@smithy/credential-provider-imds@4.0.2": {
+ "integrity": "sha512-32lVig6jCaWBHnY+OEQ6e6Vnt5vDHaLiydGrwYMW9tPqO688hPGTYRamYJ1EptxEC2rAwJrHWmPoKRBl4iTa8w==",
+ "dependencies": [
+ "@smithy/node-config-provider",
+ "@smithy/property-provider",
+ "@smithy/types",
+ "@smithy/url-parser",
+ "tslib"
+ ]
+ },
+ "@smithy/eventstream-codec@4.0.2": {
+ "integrity": "sha512-p+f2kLSK7ZrXVfskU/f5dzksKTewZk8pJLPvER3aFHPt76C2MxD9vNatSfLzzQSQB4FNO96RK4PSXfhD1TTeMQ==",
+ "dependencies": [
+ "@aws-crypto/crc32",
+ "@smithy/types",
+ "@smithy/util-hex-encoding",
+ "tslib"
+ ]
+ },
+ "@smithy/eventstream-serde-browser@4.0.2": {
+ "integrity": "sha512-CepZCDs2xgVUtH7ZZ7oDdZFH8e6Y2zOv8iiX6RhndH69nlojCALSKK+OXwZUgOtUZEUaZ5e1hULVCHYbCn7pug==",
+ "dependencies": [
+ "@smithy/eventstream-serde-universal",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@smithy/eventstream-serde-config-resolver@4.1.0": {
+ "integrity": "sha512-1PI+WPZ5TWXrfj3CIoKyUycYynYJgZjuQo8U+sphneOtjsgrttYybdqESFReQrdWJ+LKt6NEdbYzmmfDBmjX2A==",
+ "dependencies": [
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@smithy/eventstream-serde-node@4.0.2": {
+ "integrity": "sha512-C5bJ/C6x9ENPMx2cFOirspnF9ZsBVnBMtP6BdPl/qYSuUawdGQ34Lq0dMcf42QTjUZgWGbUIZnz6+zLxJlb9aw==",
+ "dependencies": [
+ "@smithy/eventstream-serde-universal",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@smithy/eventstream-serde-universal@4.0.2": {
+ "integrity": "sha512-St8h9JqzvnbB52FtckiHPN4U/cnXcarMniXRXTKn0r4b4XesZOGiAyUdj1aXbqqn1icSqBlzzUsCl6nPB018ng==",
+ "dependencies": [
+ "@smithy/eventstream-codec",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@smithy/fetch-http-handler@5.0.2": {
+ "integrity": "sha512-+9Dz8sakS9pe7f2cBocpJXdeVjMopUDLgZs1yWeu7h++WqSbjUYv/JAJwKwXw1HV6gq1jyWjxuyn24E2GhoEcQ==",
+ "dependencies": [
+ "@smithy/protocol-http",
+ "@smithy/querystring-builder",
+ "@smithy/types",
+ "@smithy/util-base64",
+ "tslib"
+ ]
+ },
+ "@smithy/hash-blob-browser@4.0.2": {
+ "integrity": "sha512-3g188Z3DyhtzfBRxpZjU8R9PpOQuYsbNnyStc/ZVS+9nVX1f6XeNOa9IrAh35HwwIZg+XWk8bFVtNINVscBP+g==",
+ "dependencies": [
+ "@smithy/chunked-blob-reader",
+ "@smithy/chunked-blob-reader-native",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@smithy/hash-node@4.0.2": {
+ "integrity": "sha512-VnTpYPnRUE7yVhWozFdlxcYknv9UN7CeOqSrMH+V877v4oqtVYuoqhIhtSjmGPvYrYnAkaM61sLMKHvxL138yg==",
+ "dependencies": [
+ "@smithy/types",
+ "@smithy/util-buffer-from@4.0.0",
+ "@smithy/util-utf8@4.0.0",
+ "tslib"
+ ]
+ },
+ "@smithy/hash-stream-node@4.0.2": {
+ "integrity": "sha512-POWDuTznzbIwlEXEvvXoPMS10y0WKXK790soe57tFRfvf4zBHyzE529HpZMqmDdwG9MfFflnyzndUQ8j78ZdSg==",
+ "dependencies": [
+ "@smithy/types",
+ "@smithy/util-utf8@4.0.0",
+ "tslib"
+ ]
+ },
+ "@smithy/invalid-dependency@4.0.2": {
+ "integrity": "sha512-GatB4+2DTpgWPday+mnUkoumP54u/MDM/5u44KF9hIu8jF0uafZtQLcdfIKkIcUNuF/fBojpLEHZS/56JqPeXQ==",
+ "dependencies": [
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@smithy/is-array-buffer@2.2.0": {
+ "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==",
+ "dependencies": [
+ "tslib"
+ ]
+ },
+ "@smithy/is-array-buffer@4.0.0": {
+ "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==",
+ "dependencies": [
+ "tslib"
+ ]
+ },
+ "@smithy/md5-js@4.0.2": {
+ "integrity": "sha512-Hc0R8EiuVunUewCse2syVgA2AfSRco3LyAv07B/zCOMa+jpXI9ll+Q21Nc6FAlYPcpNcAXqBzMhNs1CD/pP2bA==",
+ "dependencies": [
+ "@smithy/types",
+ "@smithy/util-utf8@4.0.0",
+ "tslib"
+ ]
+ },
+ "@smithy/middleware-content-length@4.0.2": {
+ "integrity": "sha512-hAfEXm1zU+ELvucxqQ7I8SszwQ4znWMbNv6PLMndN83JJN41EPuS93AIyh2N+gJ6x8QFhzSO6b7q2e6oClDI8A==",
+ "dependencies": [
+ "@smithy/protocol-http",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@smithy/middleware-endpoint@4.1.0": {
+ "integrity": "sha512-xhLimgNCbCzsUppRTGXWkZywksuTThxaIB0HwbpsVLY5sceac4e1TZ/WKYqufQLaUy+gUSJGNdwD2jo3cXL0iA==",
+ "dependencies": [
+ "@smithy/core",
+ "@smithy/middleware-serde",
+ "@smithy/node-config-provider",
+ "@smithy/shared-ini-file-loader",
+ "@smithy/types",
+ "@smithy/url-parser",
+ "@smithy/util-middleware",
+ "tslib"
+ ]
+ },
+ "@smithy/middleware-retry@4.1.0": {
+ "integrity": "sha512-2zAagd1s6hAaI/ap6SXi5T3dDwBOczOMCSkkYzktqN1+tzbk1GAsHNAdo/1uzxz3Ky02jvZQwbi/vmDA6z4Oyg==",
+ "dependencies": [
+ "@smithy/node-config-provider",
+ "@smithy/protocol-http",
+ "@smithy/service-error-classification",
+ "@smithy/smithy-client",
+ "@smithy/types",
+ "@smithy/util-middleware",
+ "@smithy/util-retry",
+ "tslib",
+ "uuid"
+ ]
+ },
+ "@smithy/middleware-serde@4.0.3": {
+ "integrity": "sha512-rfgDVrgLEVMmMn0BI8O+8OVr6vXzjV7HZj57l0QxslhzbvVfikZbVfBVthjLHqib4BW44QhcIgJpvebHlRaC9A==",
+ "dependencies": [
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@smithy/middleware-stack@4.0.2": {
+ "integrity": "sha512-eSPVcuJJGVYrFYu2hEq8g8WWdJav3sdrI4o2c6z/rjnYDd3xH9j9E7deZQCzFn4QvGPouLngH3dQ+QVTxv5bOQ==",
+ "dependencies": [
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@smithy/node-config-provider@4.0.2": {
+ "integrity": "sha512-WgCkILRZfJwJ4Da92a6t3ozN/zcvYyJGUTmfGbgS/FkCcoCjl7G4FJaCDN1ySdvLvemnQeo25FdkyMSTSwulsw==",
+ "dependencies": [
+ "@smithy/property-provider",
+ "@smithy/shared-ini-file-loader",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@smithy/node-http-handler@4.0.4": {
+ "integrity": "sha512-/mdqabuAT3o/ihBGjL94PUbTSPSRJ0eeVTdgADzow0wRJ0rN4A27EOrtlK56MYiO1fDvlO3jVTCxQtQmK9dZ1g==",
+ "dependencies": [
+ "@smithy/abort-controller",
+ "@smithy/protocol-http",
+ "@smithy/querystring-builder",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@smithy/property-provider@4.0.2": {
+ "integrity": "sha512-wNRoQC1uISOuNc2s4hkOYwYllmiyrvVXWMtq+TysNRVQaHm4yoafYQyjN/goYZS+QbYlPIbb/QRjaUZMuzwQ7A==",
+ "dependencies": [
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@smithy/protocol-http@5.1.0": {
+ "integrity": "sha512-KxAOL1nUNw2JTYrtviRRjEnykIDhxc84qMBzxvu1MUfQfHTuBlCG7PA6EdVwqpJjH7glw7FqQoFxUJSyBQgu7g==",
+ "dependencies": [
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@smithy/querystring-builder@4.0.2": {
+ "integrity": "sha512-NTOs0FwHw1vimmQM4ebh+wFQvOwkEf/kQL6bSM1Lock+Bv4I89B3hGYoUEPkmvYPkDKyp5UdXJYu+PoTQ3T31Q==",
+ "dependencies": [
+ "@smithy/types",
+ "@smithy/util-uri-escape",
+ "tslib"
+ ]
+ },
+ "@smithy/querystring-parser@4.0.2": {
+ "integrity": "sha512-v6w8wnmZcVXjfVLjxw8qF7OwESD9wnpjp0Dqry/Pod0/5vcEA3qxCr+BhbOHlxS8O+29eLpT3aagxXGwIoEk7Q==",
+ "dependencies": [
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@smithy/service-error-classification@4.0.2": {
+ "integrity": "sha512-LA86xeFpTKn270Hbkixqs5n73S+LVM0/VZco8dqd+JT75Dyx3Lcw/MraL7ybjmz786+160K8rPOmhsq0SocoJQ==",
+ "dependencies": [
+ "@smithy/types"
+ ]
+ },
+ "@smithy/shared-ini-file-loader@4.0.2": {
+ "integrity": "sha512-J9/gTWBGVuFZ01oVA6vdb4DAjf1XbDhK6sLsu3OS9qmLrS6KB5ygpeHiM3miIbj1qgSJ96GYszXFWv6ErJ8QEw==",
+ "dependencies": [
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@smithy/signature-v4@5.0.2": {
+ "integrity": "sha512-Mz+mc7okA73Lyz8zQKJNyr7lIcHLiPYp0+oiqiMNc/t7/Kf2BENs5d63pEj7oPqdjaum6g0Fc8wC78dY1TgtXw==",
+ "dependencies": [
+ "@smithy/is-array-buffer@4.0.0",
+ "@smithy/protocol-http",
+ "@smithy/types",
+ "@smithy/util-hex-encoding",
+ "@smithy/util-middleware",
+ "@smithy/util-uri-escape",
+ "@smithy/util-utf8@4.0.0",
+ "tslib"
+ ]
+ },
+ "@smithy/signature-v4@5.1.0": {
+ "integrity": "sha512-4t5WX60sL3zGJF/CtZsUQTs3UrZEDO2P7pEaElrekbLqkWPYkgqNW1oeiNYC6xXifBnT9dVBOnNQRvOE9riU9w==",
+ "dependencies": [
+ "@smithy/is-array-buffer@4.0.0",
+ "@smithy/protocol-http",
+ "@smithy/types",
+ "@smithy/util-hex-encoding",
+ "@smithy/util-middleware",
+ "@smithy/util-uri-escape",
+ "@smithy/util-utf8@4.0.0",
+ "tslib"
+ ]
+ },
+ "@smithy/smithy-client@4.2.0": {
+ "integrity": "sha512-Qs65/w30pWV7LSFAez9DKy0Koaoh3iHhpcpCCJ4waj/iqwsuSzJna2+vYwq46yBaqO5ZbP9TjUsATUNxrKeBdw==",
+ "dependencies": [
+ "@smithy/core",
+ "@smithy/middleware-endpoint",
+ "@smithy/middleware-stack",
+ "@smithy/protocol-http",
+ "@smithy/types",
+ "@smithy/util-stream",
+ "tslib"
+ ]
+ },
+ "@smithy/types@4.2.0": {
+ "integrity": "sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==",
+ "dependencies": [
+ "tslib"
+ ]
+ },
+ "@smithy/url-parser@4.0.2": {
+ "integrity": "sha512-Bm8n3j2ScqnT+kJaClSVCMeiSenK6jVAzZCNewsYWuZtnBehEz4r2qP0riZySZVfzB+03XZHJeqfmJDkeeSLiQ==",
+ "dependencies": [
+ "@smithy/querystring-parser",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@smithy/util-base64@4.0.0": {
+ "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==",
+ "dependencies": [
+ "@smithy/util-buffer-from@4.0.0",
+ "@smithy/util-utf8@4.0.0",
+ "tslib"
+ ]
+ },
+ "@smithy/util-body-length-browser@4.0.0": {
+ "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==",
+ "dependencies": [
+ "tslib"
+ ]
+ },
+ "@smithy/util-body-length-node@4.0.0": {
+ "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==",
+ "dependencies": [
+ "tslib"
+ ]
+ },
+ "@smithy/util-buffer-from@2.2.0": {
+ "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==",
+ "dependencies": [
+ "@smithy/is-array-buffer@2.2.0",
+ "tslib"
+ ]
+ },
+ "@smithy/util-buffer-from@4.0.0": {
+ "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==",
+ "dependencies": [
+ "@smithy/is-array-buffer@4.0.0",
+ "tslib"
+ ]
+ },
+ "@smithy/util-config-provider@4.0.0": {
+ "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==",
+ "dependencies": [
+ "tslib"
+ ]
+ },
+ "@smithy/util-defaults-mode-browser@4.0.8": {
+ "integrity": "sha512-ZTypzBra+lI/LfTYZeop9UjoJhhGRTg3pxrNpfSTQLd3AJ37r2z4AXTKpq1rFXiiUIJsYyFgNJdjWRGP/cbBaQ==",
+ "dependencies": [
+ "@smithy/property-provider",
+ "@smithy/smithy-client",
+ "@smithy/types",
+ "bowser",
+ "tslib"
+ ]
+ },
+ "@smithy/util-defaults-mode-node@4.0.8": {
+ "integrity": "sha512-Rgk0Jc/UDfRTzVthye/k2dDsz5Xxs9LZaKCNPgJTRyoyBoeiNCnHsYGOyu1PKN+sDyPnJzMOz22JbwxzBp9NNA==",
+ "dependencies": [
+ "@smithy/config-resolver",
+ "@smithy/credential-provider-imds",
+ "@smithy/node-config-provider",
+ "@smithy/property-provider",
+ "@smithy/smithy-client",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@smithy/util-endpoints@3.0.2": {
+ "integrity": "sha512-6QSutU5ZyrpNbnd51zRTL7goojlcnuOB55+F9VBD+j8JpRY50IGamsjlycrmpn8PQkmJucFW8A0LSfXj7jjtLQ==",
+ "dependencies": [
+ "@smithy/node-config-provider",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@smithy/util-hex-encoding@4.0.0": {
+ "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==",
+ "dependencies": [
+ "tslib"
+ ]
+ },
+ "@smithy/util-middleware@4.0.2": {
+ "integrity": "sha512-6GDamTGLuBQVAEuQ4yDQ+ti/YINf/MEmIegrEeg7DdB/sld8BX1lqt9RRuIcABOhAGTA50bRbPzErez7SlDtDQ==",
+ "dependencies": [
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@smithy/util-retry@4.0.2": {
+ "integrity": "sha512-Qryc+QG+7BCpvjloFLQrmlSd0RsVRHejRXd78jNO3+oREueCjwG1CCEH1vduw/ZkM1U9TztwIKVIi3+8MJScGg==",
+ "dependencies": [
+ "@smithy/service-error-classification",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@smithy/util-stream@4.2.0": {
+ "integrity": "sha512-Vj1TtwWnuWqdgQI6YTUF5hQ/0jmFiOYsc51CSMgj7QfyO+RF4EnT2HNjoviNlOOmgzgvf3f5yno+EiC4vrnaWQ==",
+ "dependencies": [
+ "@smithy/fetch-http-handler",
+ "@smithy/node-http-handler",
+ "@smithy/types",
+ "@smithy/util-base64",
+ "@smithy/util-buffer-from@4.0.0",
+ "@smithy/util-hex-encoding",
+ "@smithy/util-utf8@4.0.0",
+ "tslib"
+ ]
+ },
+ "@smithy/util-uri-escape@4.0.0": {
+ "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==",
+ "dependencies": [
+ "tslib"
+ ]
+ },
+ "@smithy/util-utf8@2.3.0": {
+ "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==",
+ "dependencies": [
+ "@smithy/util-buffer-from@2.2.0",
+ "tslib"
+ ]
+ },
+ "@smithy/util-utf8@4.0.0": {
+ "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==",
+ "dependencies": [
+ "@smithy/util-buffer-from@4.0.0",
+ "tslib"
+ ]
+ },
+ "@smithy/util-waiter@4.0.3": {
+ "integrity": "sha512-JtaY3FxmD+te+KSI2FJuEcfNC9T/DGGVf551babM7fAaXhjJUt7oSYurH1Devxd2+BOSUACCgt3buinx4UnmEA==",
+ "dependencies": [
+ "@smithy/abort-controller",
+ "@smithy/types",
+ "tslib"
+ ]
+ },
+ "@sqlite.org/sqlite-wasm@3.48.0-build4": {
+ "integrity": "sha512-hI6twvUkzOmyGZhQMza1gpfqErZxXRw6JEsiVjUbo7tFanVD+8Oil0Ih3l2nGzHdxPI41zFmfUQG7GHqhciKZQ=="
+ },
+ "@types/node@22.12.0": {
+ "integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==",
+ "dependencies": [
+ "undici-types"
+ ]
+ },
+ "@ungap/structured-clone@1.3.0": {
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="
+ },
+ "@ungap/with-resolvers@0.1.0": {
+ "integrity": "sha512-g7f0IkJdPW2xhY7H4iE72DAsIyfuwEFc6JWc2tYFwKDMWWAF699vGjrM348cwQuOXgHpe1gWFe+Eiyjx/ewvvw=="
+ },
+ "bowser@2.11.0": {
+ "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA=="
+ },
+ "coincident@1.2.3": {
+ "integrity": "sha512-Uxz3BMTWIslzeWjuQnizGWVg0j6khbvHUQ8+5BdM7WuJEm4ALXwq3wluYoB+uF68uPBz/oUOeJnYURKyfjexlA==",
+ "dependencies": [
+ "@ungap/structured-clone",
+ "@ungap/with-resolvers",
+ "gc-hook",
+ "proxy-target",
+ "ws"
+ ]
+ },
+ "fast-xml-parser@4.4.1": {
+ "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==",
+ "dependencies": [
+ "strnum"
+ ]
+ },
+ "gc-hook@0.3.1": {
+ "integrity": "sha512-E5M+O/h2o7eZzGhzRZGex6hbB3k4NWqO0eA+OzLRLXxhdbYPajZnynPwAtphnh+cRHPwsj5Z80dqZlfI4eK55A=="
+ },
+ "kysely@0.28.2": {
+ "integrity": "sha512-4YAVLoF0Sf0UTqlhgQMFU9iQECdah7n+13ANkiuVfRvlK+uI0Etbgd7bVP36dKlG+NXWbhGua8vnGt+sdhvT7A=="
+ },
+ "path-to-regexp@6.3.0": {
+ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="
+ },
+ "proxy-target@3.0.2": {
+ "integrity": "sha512-FFE1XNwXX/FNC3/P8HiKaJSy/Qk68RitG/QEcLy/bVnTAPlgTAWPZKh0pARLAnpfXQPKyalBhk009NRTgsk8vQ=="
+ },
+ "sqlocal@0.14.0_kysely@0.28.2": {
+ "integrity": "sha512-qhkjWXrvh0aG0IwMeuTznCozNTJvlp2hr+JlSGOx2c1vpjgWoXYvZEf4jcynJ5n/pm05mdKNcDFzb/9KJL/IHQ==",
+ "dependencies": [
+ "@sqlite.org/sqlite-wasm",
+ "coincident",
+ "kysely"
+ ]
+ },
+ "strnum@1.1.2": {
+ "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA=="
+ },
+ "tslib@2.8.1": {
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
+ },
+ "undici-types@6.20.0": {
+ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="
+ },
+ "uuid@9.0.1": {
+ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="
+ },
+ "ws@8.18.1": {
+ "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w=="
+ }
+ },
+ "workspace": {
+ "dependencies": [
+ "jsr:@oak/oak@^17.1.4",
+ "jsr:@std/cli@^1.0.17",
+ "jsr:@std/encoding@^1.0.10",
+ "jsr:@std/path@^1.0.9",
+ "npm:@aws-sdk/client-s3@^3.797.0",
+ "npm:@aws-sdk/client-sesv2@^3.782.0",
+ "npm:kysely@~0.28.2",
+ "npm:sqlocal@0.14"
+ ]
+ }
+}
diff --git a/services/docker/mail-server/aws-sendmail/dovecot.ts b/services/docker/mail-server/aws-sendmail/dovecot.ts
new file mode 100644
index 0000000..9097cd0
--- /dev/null
+++ b/services/docker/mail-server/aws-sendmail/dovecot.ts
@@ -0,0 +1,40 @@
+import { getLogger } from "./logger.ts";
+import { Mail, MailDeliverer } from "./mail.ts";
+
+export class DovecotMailDeliverer extends MailDeliverer {
+ constructor() {
+ super("dovecot");
+ }
+
+ readonly ldaBin = "dovecot-lda";
+
+ protected override async doDeliver(mail: Mail): Promise<void> {
+ const { ldaBin } = this;
+ let status;
+
+ try {
+ const utf8Stream = mail.toUtf8Bytes();
+
+ const ldaCommand = new Deno.Command(ldaBin, {
+ stdin: "piped",
+ stdout: "piped",
+ stderr: "piped",
+ });
+ const ldaProcess = ldaCommand.spawn();
+ getLogger().logProgramOutput(ldaProcess, ldaBin);
+
+ const stdinWriter = ldaProcess.stdin.getWriter();
+ await stdinWriter.ready;
+ await stdinWriter.write(utf8Stream);
+ await stdinWriter.ready;
+
+ status = await ldaProcess.status;
+ } catch (cause) {
+ mail.throwDeliverError(this, "external error.", cause);
+ }
+
+ if (!status.success) {
+ mail.throwDeliverError(this, `${ldaBin} exited with non-zero.`);
+ }
+ }
+}
diff --git a/services/docker/mail-server/aws-sendmail/logger.ts b/services/docker/mail-server/aws-sendmail/logger.ts
new file mode 100644
index 0000000..160d70a
--- /dev/null
+++ b/services/docker/mail-server/aws-sendmail/logger.ts
@@ -0,0 +1,50 @@
+import * as path from "@std/path";
+
+import { createSingleton, generateTimeStringForFileName } from "./util.ts";
+
+export class Logger {
+ constructor(public readonly path: string) {}
+
+ warn(message: string) {
+ console.log(message);
+ }
+
+ generateLogFilePath(
+ prefix: string = "",
+ suffix: string = "",
+ instant?: Temporal.Instant | Date,
+ ): string {
+ return path.join(
+ this.path,
+ `${prefix}-${generateTimeStringForFileName(instant)}-${suffix}`,
+ );
+ }
+
+ openLogFile(
+ prefix: string = "",
+ suffix: string = "",
+ instant?: Temporal.Instant | Date,
+ ): Promise<Deno.FsFile> {
+ const logPath = this.generateLogFilePath(prefix, suffix, instant);
+ return Deno.open(logPath, {
+ read: false,
+ write: true,
+ append: true,
+ create: true,
+ });
+ }
+
+ async logProgramOutput(
+ process: Deno.ChildProcess,
+ program: string,
+ instant?: Temporal.Instant | Date,
+ ): Promise<void> {
+ const stdoutFile = await this.openLogFile(program, "stdout", instant);
+ const stderrFile = await this.openLogFile(program, "stderr", instant);
+
+ process.stdout.pipeTo(stdoutFile.writable);
+ process.stderr.pipeTo(stderrFile.writable);
+ }
+}
+
+export const [getLogger, setLogger] = createSingleton<Logger>("Logger");
diff --git a/services/docker/mail-server/aws-sendmail/mail.ts b/services/docker/mail-server/aws-sendmail/mail.ts
new file mode 100644
index 0000000..d4bcf75
--- /dev/null
+++ b/services/docker/mail-server/aws-sendmail/mail.ts
@@ -0,0 +1,177 @@
+import { encodeBase64 } from "@std/encoding/base64";
+import { getLogger } from "./logger.ts";
+
+export type MailDeliverStateNotSent = { kind: "not-sent" };
+export type MailDeliverStateDelivered = {
+ kind: "delivered";
+ deliverer: MailDeliverer;
+ error?: Error;
+};
+export type MailDeliverStateError = {
+ kind: "error";
+ deliverer: MailDeliverer;
+ error: Error;
+};
+export type MailDeliverState =
+ | MailDeliverStateNotSent
+ | MailDeliverStateDelivered
+ | MailDeliverStateError;
+
+class MailParseError extends Error {
+ constructor(
+ message: string,
+ public readonly mail: Mail,
+ options?: ErrorOptions,
+ ) {
+ super(message, options);
+ }
+}
+
+class MailDeliverError extends Error {
+ constructor(
+ message: string,
+ public readonly mail: Mail,
+ options?: ErrorOptions,
+ ) {
+ super(message, options);
+ }
+}
+
+const eolNames = new Map([
+ ["\n", "\\n"],
+ ["\r", "\\r"],
+ ["\n\r", "\\n\\r"],
+]);
+
+interface ParsedMail {
+ headerStr: string;
+ bodyStr: string;
+ sepStr: string;
+ eol: string;
+}
+
+export class Mail {
+ messageId: string | null = null;
+ awsMessageId: string | null = null;
+ deliverState: MailDeliverState = { kind: "not-sent" };
+
+ constructor(public raw: string) {}
+
+ setDelivered(deliverer: MailDeliverer, error?: Error) {
+ this.deliverState = { "kind": "delivered", deliverer, error };
+ }
+
+ throwDeliverError(
+ deliverer: MailDeliverer,
+ reason: string,
+ cause?: unknown,
+ ): never {
+ const error = new MailDeliverError(
+ `Failed to deliver mail to ${deliverer.destination}: ${reason}`,
+ this,
+ { cause },
+ );
+ this.deliverState = { "kind": "error", deliverer, error };
+ throw error;
+ }
+
+ toUtf8Bytes(): Uint8Array {
+ const utf8Encoder = new TextEncoder();
+ return utf8Encoder.encode(this.raw);
+ }
+
+ toBase64(): string {
+ return encodeBase64(this.raw);
+ }
+
+ simpleParse(): ParsedMail {
+ const twoEolMatch = this.raw.match(/(\r\n|\n|\r)(\r\n|\n|\r)/);
+ // "\r\n" is a false positive.
+ if (twoEolMatch == null || twoEolMatch[0] === "\r\n") {
+ throw new MailParseError(
+ "No header/body separator (2 successive EOLs) found. Cannot append headers.",
+ this,
+ );
+ }
+
+ const [eol, sepStr] = [twoEolMatch[1], twoEolMatch[2]];
+
+ if (eol !== sepStr) {
+ getLogger().warn(
+ `Different EOLs (${eolNames.get(eol)} \
+ and ${eolNames.get(sepStr)}) found in mail.`,
+ );
+ }
+
+ return {
+ headerStr: this.raw.slice(0, twoEolMatch.index! + eol.length),
+ bodyStr: this.raw.slice(twoEolMatch.index! + eol.length + sepStr.length),
+ sepStr,
+ eol,
+ };
+ }
+
+ simpleParseHeaders(): [key: string, value: string][] {
+ const { headerStr } = this.simpleParse();
+ const lines: string[] = [];
+ for (const line of headerStr.split(/\r?\n|\r/)) {
+ if (line.match(/^\s/)) {
+ if (lines.length === 0) {
+ throw new MailParseError("Header part starts with a space.", this);
+ }
+ lines[lines.length - 1] += line;
+ }
+ }
+
+ // TODO: Continue here.
+ }
+
+ appendHeaders(headers: [key: string, value: string][]) {
+ const { headerStr, bodyStr, sepStr, eol } = this.simpleParse();
+ const newHeaderStr = headerStr +
+ headers.map(([k, v]) => `${k}: ${v}${eol}`).join("");
+ this.raw = newHeaderStr + sepStr + bodyStr;
+ }
+}
+
+type MailDeliverHook = (mail: Mail) => Promise<void>;
+
+export abstract class MailDeliverer {
+ preHooks: MailDeliverHook[] = [];
+ postHooks: MailDeliverHook[] = [];
+
+ constructor(public readonly destination: string) {}
+
+ protected doPrepare(_mail: Mail): Promise<void> {
+ return Promise.resolve();
+ }
+ protected abstract doDeliver(mail: Mail): Promise<void>;
+ protected doFinalize(_mail: Mail): Promise<void> {
+ return Promise.resolve();
+ }
+
+ async deliverRaw(raw: string): Promise<void> {
+ const mail = new Mail(raw);
+ await this.deliver(mail);
+ }
+
+ async deliver(mail: Mail): Promise<void> {
+ this.doPrepare(mail);
+
+ for (const hook of this.preHooks) {
+ await hook(mail);
+ }
+
+ await this.doDeliver(mail);
+
+ if (mail.deliverState.kind === "not-sent") {
+ mail.setDelivered(this);
+ }
+
+ for (const hook of this.postHooks) {
+ await hook(mail);
+ }
+
+ await this.doFinalize(mail);
+ }
+}
diff --git a/services/docker/mail-server/aws-sendmail/main.ts b/services/docker/mail-server/aws-sendmail/main.ts
new file mode 100644
index 0000000..dcc2af1
--- /dev/null
+++ b/services/docker/mail-server/aws-sendmail/main.ts
@@ -0,0 +1,18 @@
+import { AwsContext } from "./aws/context.ts";
+import { MailTrafficHandler } from "./traffic.ts";
+import { Logger, setLogger } from "./logger.ts";
+import { Config, setConfig } from "./config.ts";
+
+class App {
+ readonly aws = new AwsContext();
+ readonly mailTrafficDeliverer = new MailTrafficHandler(this.aws);
+
+ constructor() {
+ setLogger(new Logger("log"));
+ setConfig(new Config());
+ }
+}
+
+if (import.meta.main) {
+ const app = new App();
+}
diff --git a/services/docker/mail-server/aws-sendmail/traffic.ts b/services/docker/mail-server/aws-sendmail/traffic.ts
new file mode 100644
index 0000000..7ecc405
--- /dev/null
+++ b/services/docker/mail-server/aws-sendmail/traffic.ts
@@ -0,0 +1,21 @@
+import { MailDeliverer } from "./mail.ts";
+import { DbService } from "./db.ts";
+import { DovecotMailDeliverer } from "./dovecot.ts";
+import { AwsContext } from "./aws/context.ts";
+import { AwsMailDeliverer } from "./aws/deliver.ts";
+
+export abstract class MailTrafficHandler {
+ constructor(
+ public readonly receiver: MailDeliverer,
+ public readonly sender: MailDeliverer,
+ ) {}
+}
+
+export class AwsRelayTrafficHandler extends MailTrafficHandler {
+ constructor(
+ private readonly db: DbService,
+ private readonly aws: AwsContext,
+ ) {
+ super(new DovecotMailDeliverer(), new AwsMailDeliverer(aws));
+ }
+}
diff --git a/services/docker/mail-server/aws-sendmail/util.ts b/services/docker/mail-server/aws-sendmail/util.ts
new file mode 100644
index 0000000..1411840
--- /dev/null
+++ b/services/docker/mail-server/aws-sendmail/util.ts
@@ -0,0 +1,49 @@
+function getZonedDateTime(instant?: Temporal.Instant | Date) {
+ if (instant == null) {
+ instant = Temporal.Now.instant();
+ } else if (instant instanceof Date) {
+ instant = instant.toTemporalInstant();
+ }
+
+ return instant.toZonedDateTimeISO("UTC");
+}
+
+export function generateTimeStringForFileName(
+ instant?: Temporal.Instant | Date,
+ dateOnly: boolean = false,
+): string {
+ const time = getZonedDateTime(instant);
+
+ if (dateOnly) {
+ return time.toPlainDate().toString();
+ } else {
+ return time.toPlainDateTime().toString().replaceAll(/:|\./g, "-");
+ }
+}
+
+export function transformProperties<T extends object, N>(
+ object: T,
+ transformer: (value: T[keyof T], key: keyof T) => N,
+): { [k in keyof T]: N } {
+ return Object.fromEntries(
+ Object.entries(object).map((
+ [k, v],
+ ) => [k, transformer(v, k as keyof T)]),
+ ) as { [k in keyof T]: N };
+}
+
+export function createSingleton<T>(
+ name: string,
+): [() => T, (v: T | null) => void] {
+ let singleton: T | null = null;
+
+ return [
+ () => {
+ if (singleton == null) {
+ throw new Error(`Singleton ${name} is not set now.`);
+ }
+ return singleton;
+ },
+ (newValue: T | null) => singleton = newValue,
+ ];
+}