@exabugs/dynamodb-client 1.14.1 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/README.md +1 -0
- package/dist/client/index.iam.d.ts +1 -0
- package/dist/client/index.iam.d.ts.map +1 -1
- package/dist/client/index.iam.js +1 -0
- package/dist/client/index.iam.js.map +1 -1
- package/dist/client/recordsClient.d.ts +51 -0
- package/dist/client/recordsClient.d.ts.map +1 -0
- package/dist/client/recordsClient.js +64 -0
- package/dist/client/recordsClient.js.map +1 -0
- package/dist/notify/admins.d.ts +44 -0
- package/dist/notify/admins.d.ts.map +1 -0
- package/dist/notify/admins.js +55 -0
- package/dist/notify/admins.js.map +1 -0
- package/dist/notify/index.d.ts +2 -0
- package/dist/notify/index.d.ts.map +1 -1
- package/dist/notify/index.js +1 -0
- package/dist/notify/index.js.map +1 -1
- package/dist/server/handler.cjs +3 -3
- package/dist/server/media/process-handler-dlq.d.ts.map +1 -1
- package/dist/server/media/process-handler-dlq.js +8 -19
- package/dist/server/media/process-handler-dlq.js.map +1 -1
- package/dist/server/media/process-handler.d.ts.map +1 -1
- package/dist/server/media/process-handler.js +5 -18
- package/dist/server/media/process-handler.js.map +1 -1
- package/dist/server/media/recordsClient.d.ts +13 -0
- package/dist/server/media/recordsClient.d.ts.map +1 -0
- package/dist/server/media/recordsClient.js +35 -0
- package/dist/server/media/recordsClient.js.map +1 -0
- package/dist/server/media-handler.cjs +2 -2
- package/dist/server/media-process-handler-dlq.cjs +1157 -3606
- package/dist/server/media-process-handler-dlq.cjs.map +4 -4
- package/dist/server/media-process-handler.cjs +1211 -3675
- package/dist/server/media-process-handler.cjs.map +4 -4
- package/docs/DYNAMODB_ACCESS.md +17 -9
- package/docs/RECORDS_CLIENT.md +45 -0
- package/package.json +3 -2
- package/terraform/media/README.md +4 -2
- package/terraform/media/main.tf +26 -27
- package/terraform/media/variables.tf +21 -31
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,49 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.16.0] - 2026-10-20
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Records クライアントのヘルパー**(`@exabugs/dynamodb-client/client/iam`。ADR 0006 の段階 1): `createRecordsClient`(`DynamoClient` の生成と `connect()`)、`withCollection`(作る → 使う → 確実に close)、`lazyCollection`(初回の使用時に 1 回だけ接続して再利用)。`url` / `region` は省略時に環境変数 `RECORDS_IAM_URL` / `AWS_REGION` から読む。使い方は `docs/RECORDS_CLIENT.md`
|
|
15
|
+
- **`createAdminDirectory`**(`@exabugs/dynamodb-client/notify`): `admins` の全件から通知の宛先を返す `AdminDirectory`。宛先のフィールドは `recipientFields`(既定 `['id']`。`['email','id']` で `email ?? id`)。あわせて `normalizeEmail` / `emailsOf` を公開
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- media の Records クライアント(`src/server/media/recordsClient.ts`)は、公開ヘルパー `lazyCollection` を使う。挙動は同じ(`connect()`、timeout 15 秒、`RECORDS_IAM_URL` の扱い、コールドスタートで 1 回の接続と再利用)。並行した初回の呼び出しが 1 つの接続を共有するようになった
|
|
20
|
+
|
|
21
|
+
### Removed
|
|
22
|
+
|
|
23
|
+
- **terraform/media**: 変数 `table_name`、`table_arn`、`shadow_created_at_field`、`shadow_updated_at_field`、`shadow_string_max_bytes`、`shadow_number_padding`(ADR 0005、issue #94。1.15.0 で deprecated)。利用側が渡していると、`terraform plan` が「宣言されていない変数」で失敗する。移行: 渡すのをやめる(1.15.0 以降、渡しても無視されていた)
|
|
24
|
+
|
|
25
|
+
## [1.15.0] - 2026-10-18
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- **terraform/media**: 必須の変数 `records_iam_function_url`(Records の IAM 版 Function URL。本体の出力 `iam_function_url`)と `records_iam_alias_arn`(`lambda:InvokeFunctionUrl` の許可の対象。本体の出力 `iam_alias_arn`)
|
|
30
|
+
- **`src/server/media/recordsClient.ts`**: media の Lambda が Records を呼ぶ共通部品(`DynamoClient` の生成と再利用、timeout 15 秒、`findMediaRecord` / `writeMediaRecord`)
|
|
31
|
+
- 時限つきの失敗するテスト: 版が 1.16.0 以上のとき、`terraform/media/variables.tf` に廃止した変数が残っていると失敗する(ADR 0005、issue #94)
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- **media の Lambda は Records 経由になった(ADR 0005)**: `process-handler` と `process-handler-dlq` は、`handleUpdateOne` / `handleFindMany` の直接呼び出しをやめ、`DynamoClient`(IAM)で Records を呼ぶ。DynamoDB に直接触れない(`process_handler_dynamodb` / `process_handler_dlq_dynamodb` の IAM、環境変数 `TABLE_NAME` と `SHADOW_*` を削除。環境変数 `RECORDS_IAM_URL` を追加)。書き込みの意味(upsert、`$set` / `$setOnInsert`)は変わらない。Records が失敗したら例外を再スローし、Lambda の非同期再試行と DLQ に乗る
|
|
36
|
+
- DynamoDB アクセスの許可リストから、media の負債 4 件を削除した。`notices` は例外ではなく原則の対象外と明記した
|
|
37
|
+
- **IAM**: media の 2 つの Lambda のロールに `lambda:InvokeFunctionUrl` と `lambda:InvokeFunction` の両方を許可する。AWS のドキュメント(Function URL のアクセス制御)に「2025 年 10 月以降、新しい Function URL は両方の権限を要求する」とある
|
|
38
|
+
- **process-handler-dlq** の Lambda の timeout を 30 秒から 60 秒に上げた(変数 `process_handler_dlq_timeout`、既定 60。Records の読み 15 秒 + 書き 15 秒のクライアント timeout が収まるように)
|
|
39
|
+
|
|
40
|
+
- **SQS の可視性タイムアウト**: process-handler の DLQ キューの `visibility_timeout_seconds` を `process_handler_dlq_timeout * 6`(既定 360 秒)にした。SQS をイベントソースにする Lambda では、可視性タイムアウトは関数の timeout 以上が必須で、AWS の推奨は 6 倍(timeout を 60 秒に上げたため。DLQ の再配信の間隔は 30 秒から 360 秒になり、Records の一時的な失敗のあと `failed` が書かれるまでの遅れが最大 6 分ほど延びる。既定の 30 秒のままだとマッピングが拒否される)
|
|
41
|
+
|
|
42
|
+
### Deprecated
|
|
43
|
+
|
|
44
|
+
- **terraform/media**: 変数 `table_name`、`table_arn`、`shadow_created_at_field`、`shadow_updated_at_field`、`shadow_string_max_bytes`、`shadow_number_padding`。渡しても無視される。**1.16.0 で削除する**(issue #94)
|
|
45
|
+
|
|
46
|
+
### 移行の手順
|
|
47
|
+
|
|
48
|
+
1. Records は先で、変更なし(IAM 版 URL とエイリアスは既存の出力)
|
|
49
|
+
2. 利用アプリの `module "media"` に `records_iam_function_url`(Records の `iam_function_url`)と `records_iam_alias_arn`(`iam_alias_arn`)を渡す。asanowa は `module.app_base.dynamodb_client_api_iam_function_url` / `..._iam_alias_arn`
|
|
50
|
+
3. `table_name` / `table_arn` / `shadow_*` は、渡さなくてよい(渡しても無視される)。1.16.0 までに消す
|
|
51
|
+
4. dev で先に確かめる(画像・非画像のアップロードで `completed`、再アップロードで上書きされないこと、Records 失敗時の再試行と DLQ)→ prd
|
|
52
|
+
|
|
10
53
|
## [1.14.1] - 2026-10-18
|
|
11
54
|
|
|
12
55
|
### Fixed
|
package/README.md
CHANGED
|
@@ -196,6 +196,7 @@ const article = await articles.findOne({ title: 'Hello DynamoDB' });
|
|
|
196
196
|
- **[React Admin Integration](docs/react-admin-integration.md)** - Admin UI setup and many-to-many relationships
|
|
197
197
|
- **[Authorization & Secure Setup](docs/AUTHORIZATION.md)** - 認可モデルと安全な構成(必読。JWT の有効性しか検証しない/管理者専用ユーザープール/`aud` 検証)
|
|
198
198
|
- **[疎な検索の警告と対応](docs/SCAN_WARNING.md)** - 検索を打ち切らず、警告(SPARSE_SCAN)で気づいてインデックス・スキーマ・ロジックを見直す
|
|
199
|
+
- **[Records クライアントのヘルパー](docs/RECORDS_CLIENT.md)** - Lambda から Records を呼ぶ定型(`createRecordsClient` / `withCollection` / `lazyCollection`)
|
|
199
200
|
- **[運用通知](docs/NOTIFY.md)** - ログ・アラームを、Slack・メールで管理者へ通知する部品(`@exabugs/dynamodb-client/notify`)
|
|
200
201
|
- **[Deployment](docs/DEPLOYMENT.md)** - Production deployment guide
|
|
201
202
|
- **[Terraform Modules](terraform/README.md)** - Infrastructure as Code
|
|
@@ -31,4 +31,5 @@ export interface ClientOptions extends Omit<BaseClientOptions<IAMAuthOptions>, '
|
|
|
31
31
|
export declare class DynamoClient extends BaseDynamoClient<IAMAuthOptions> {
|
|
32
32
|
constructor(endpoint: string, options: ClientOptions);
|
|
33
33
|
}
|
|
34
|
+
export { createRecordsClient, lazyCollection, withCollection, type LazyCollection, type RecordsClientOptions, type RecordsCollection, } from './recordsClient.js';
|
|
34
35
|
//# sourceMappingURL=index.iam.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.iam.d.ts","sourceRoot":"","sources":["../../src/client/index.iam.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,KAAK,aAAa,IAAI,iBAAiB,EACvC,YAAY,IAAI,gBAAgB,EACjC,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,mBAAmB;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,aAAc,SAAQ,IAAI,CAAC,iBAAiB,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACpF,kBAAkB;IAClB,IAAI,EAAE,cAAc,CAAC;CACtB;AAED;;;;;GAKG;AACH,qBAAa,YAAa,SAAQ,gBAAgB,CAAC,cAAc,CAAC;gBACpD,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa;CAQrD"}
|
|
1
|
+
{"version":3,"file":"index.iam.d.ts","sourceRoot":"","sources":["../../src/client/index.iam.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,KAAK,aAAa,IAAI,iBAAiB,EACvC,YAAY,IAAI,gBAAgB,EACjC,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,mBAAmB;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,aAAc,SAAQ,IAAI,CAAC,iBAAiB,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACpF,kBAAkB;IAClB,IAAI,EAAE,cAAc,CAAC;CACtB;AAED;;;;;GAKG;AACH,qBAAa,YAAa,SAAQ,gBAAgB,CAAC,cAAc,CAAC;gBACpD,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa;CAQrD;AAED,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,GACvB,MAAM,oBAAoB,CAAC"}
|
package/dist/client/index.iam.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.iam.js","sourceRoot":"","sources":["../../src/client/index.iam.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAEL,YAAY,IAAI,gBAAgB,GACjC,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,UAAU,EAAmC,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAkB7C;;;;;GAKG;AACH,MAAM,OAAO,YAAa,SAAQ,gBAAgC;IAChE,YAAY,QAAgB,EAAE,OAAsB;QAClD,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,GAAW,EAAE,IAAY,EAAE,IAAqB,EAAE,EAAE;YAClF,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;YACnD,CAAC;YACD,OAAO,MAAM,WAAW,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
1
|
+
{"version":3,"file":"index.iam.js","sourceRoot":"","sources":["../../src/client/index.iam.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAEL,YAAY,IAAI,gBAAgB,GACjC,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,UAAU,EAAmC,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAkB7C;;;;;GAKG;AACH,MAAM,OAAO,YAAa,SAAQ,gBAAgC;IAChE,YAAY,QAAgB,EAAE,OAAsB;QAClD,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,GAAW,EAAE,IAAY,EAAE,IAAqB,EAAE,EAAE;YAClF,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;YACnD,CAAC;YACD,OAAO,MAAM,WAAW,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAED,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,cAAc,GAIf,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Records(IAM 版 Function URL)を呼ぶクライアントのヘルパー(ADR 0006)
|
|
3
|
+
*
|
|
4
|
+
* Lambda などのサーバー側が Records のコレクションを使うときの定型を持つ。
|
|
5
|
+
* - `createRecordsClient`: `DynamoClient` の生成と `connect()`(`connect()` 忘れの防止)
|
|
6
|
+
* - `withCollection`: 作る → 使う → 確実に close(Lambda の 1 回の呼び出しで使い切る用途)
|
|
7
|
+
* - `lazyCollection`: 初回の使用時に 1 回だけ接続して再利用(コールドスタートで共有する用途)
|
|
8
|
+
*
|
|
9
|
+
* `url` と `region` は、省略時に環境変数 `RECORDS_IAM_URL`、`AWS_REGION`(無ければ `REGION`)から読む。
|
|
10
|
+
*/
|
|
11
|
+
import { DynamoClient } from './index.iam.js';
|
|
12
|
+
import type { Collection } from './index.iam.js';
|
|
13
|
+
/** Records 用のコレクションの型(IAM 認証) */
|
|
14
|
+
export type RecordsCollection<T extends {
|
|
15
|
+
id: string;
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}> = Collection<T, {
|
|
18
|
+
region: string;
|
|
19
|
+
}>;
|
|
20
|
+
export interface RecordsClientOptions {
|
|
21
|
+
/** Records の IAM 版 Function URL。省略時は環境変数 RECORDS_IAM_URL */
|
|
22
|
+
url?: string;
|
|
23
|
+
/** AWS リージョン。省略時は環境変数 AWS_REGION(無ければ REGION) */
|
|
24
|
+
region?: string;
|
|
25
|
+
/** リクエストの timeout(ms)。省略時は DynamoClient の既定。Records の Lambda の timeout より短くする */
|
|
26
|
+
timeout?: number;
|
|
27
|
+
}
|
|
28
|
+
type RecordShape = {
|
|
29
|
+
id: string;
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
};
|
|
32
|
+
/** `DynamoClient` を生成して接続する。IAM では接続は状態の更新だけ(`db()` の前に必須) */
|
|
33
|
+
export declare function createRecordsClient(options?: RecordsClientOptions): Promise<DynamoClient>;
|
|
34
|
+
/**
|
|
35
|
+
* 接続して、コレクションで `fn` を実行し、成功でも失敗でも close する。
|
|
36
|
+
* `fn` の例外はそのまま投げる(close の失敗で `fn` の例外を隠さない)。
|
|
37
|
+
*/
|
|
38
|
+
export declare function withCollection<T extends RecordShape = RecordShape, R = unknown>(name: string, fn: (collection: RecordsCollection<T>) => Promise<R>, options?: RecordsClientOptions): Promise<R>;
|
|
39
|
+
/** `lazyCollection` の戻り値。呼ぶとコレクションを返す(初回に接続) */
|
|
40
|
+
export interface LazyCollection<T extends RecordShape> {
|
|
41
|
+
(): Promise<RecordsCollection<T>>;
|
|
42
|
+
/** キャッシュを捨てる(テスト用。次の呼び出しで接続し直す) */
|
|
43
|
+
reset(): void;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 最初の使用時に接続し、以降は同じコレクションを再利用する(コールドスタートで 1 回だけ接続)。
|
|
47
|
+
* 並行した初回の呼び出しは 1 つの接続を共有する。失敗したらキャッシュせず、次の呼び出しでやり直す。
|
|
48
|
+
*/
|
|
49
|
+
export declare function lazyCollection<T extends RecordShape>(name: string, options?: RecordsClientOptions): LazyCollection<T>;
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=recordsClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recordsClient.d.ts","sourceRoot":"","sources":["../../src/client/recordsClient.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,iCAAiC;AACjC,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,IAAI,UAAU,CAC1F,CAAC,EACD;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CACnB,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACnC,4DAA4D;IAC5D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,KAAK,WAAW,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAE1D,8DAA8D;AAC9D,wBAAsB,mBAAmB,CACvC,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,YAAY,CAAC,CAevB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAAE,CAAC,GAAG,OAAO,EACnF,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,EACpD,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,CAAC,CAAC,CAOZ;AAED,gDAAgD;AAChD,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,WAAW;IACnD,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;IAClC,mCAAmC;IACnC,KAAK,IAAI,IAAI,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,WAAW,EAClD,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,oBAAyB,GACjC,cAAc,CAAC,CAAC,CAAC,CAkBnB"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Records(IAM 版 Function URL)を呼ぶクライアントのヘルパー(ADR 0006)
|
|
3
|
+
*
|
|
4
|
+
* Lambda などのサーバー側が Records のコレクションを使うときの定型を持つ。
|
|
5
|
+
* - `createRecordsClient`: `DynamoClient` の生成と `connect()`(`connect()` 忘れの防止)
|
|
6
|
+
* - `withCollection`: 作る → 使う → 確実に close(Lambda の 1 回の呼び出しで使い切る用途)
|
|
7
|
+
* - `lazyCollection`: 初回の使用時に 1 回だけ接続して再利用(コールドスタートで共有する用途)
|
|
8
|
+
*
|
|
9
|
+
* `url` と `region` は、省略時に環境変数 `RECORDS_IAM_URL`、`AWS_REGION`(無ければ `REGION`)から読む。
|
|
10
|
+
*/
|
|
11
|
+
import { DynamoClient } from './index.iam.js';
|
|
12
|
+
/** `DynamoClient` を生成して接続する。IAM では接続は状態の更新だけ(`db()` の前に必須) */
|
|
13
|
+
export async function createRecordsClient(options = {}) {
|
|
14
|
+
const url = options.url ?? process.env.RECORDS_IAM_URL;
|
|
15
|
+
if (!url) {
|
|
16
|
+
throw new Error('RECORDS_IAM_URL environment variable is not set');
|
|
17
|
+
}
|
|
18
|
+
const region = options.region ?? process.env.AWS_REGION ?? process.env.REGION;
|
|
19
|
+
if (!region) {
|
|
20
|
+
throw new Error('AWS_REGION environment variable is not set');
|
|
21
|
+
}
|
|
22
|
+
const client = new DynamoClient(url, {
|
|
23
|
+
auth: { region },
|
|
24
|
+
...(options.timeout !== undefined ? { timeout: options.timeout } : {}),
|
|
25
|
+
});
|
|
26
|
+
await client.connect();
|
|
27
|
+
return client;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 接続して、コレクションで `fn` を実行し、成功でも失敗でも close する。
|
|
31
|
+
* `fn` の例外はそのまま投げる(close の失敗で `fn` の例外を隠さない)。
|
|
32
|
+
*/
|
|
33
|
+
export async function withCollection(name, fn, options = {}) {
|
|
34
|
+
const client = await createRecordsClient(options);
|
|
35
|
+
try {
|
|
36
|
+
return await fn(client.db().collection(name));
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
await client.close();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 最初の使用時に接続し、以降は同じコレクションを再利用する(コールドスタートで 1 回だけ接続)。
|
|
44
|
+
* 並行した初回の呼び出しは 1 つの接続を共有する。失敗したらキャッシュせず、次の呼び出しでやり直す。
|
|
45
|
+
*/
|
|
46
|
+
export function lazyCollection(name, options = {}) {
|
|
47
|
+
let cached;
|
|
48
|
+
const get = (() => {
|
|
49
|
+
if (!cached) {
|
|
50
|
+
const attempt = createRecordsClient(options).then((client) => client.db().collection(name));
|
|
51
|
+
cached = attempt;
|
|
52
|
+
attempt.catch(() => {
|
|
53
|
+
if (cached === attempt)
|
|
54
|
+
cached = undefined;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return cached;
|
|
58
|
+
});
|
|
59
|
+
get.reset = () => {
|
|
60
|
+
cached = undefined;
|
|
61
|
+
};
|
|
62
|
+
return get;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=recordsClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recordsClient.js","sourceRoot":"","sources":["../../src/client/recordsClient.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAoB9C,8DAA8D;AAC9D,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,UAAgC,EAAE;IAElC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IACvD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;IACrE,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;IAC9E,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,GAAG,EAAE;QACnC,IAAI,EAAE,EAAE,MAAM,EAAE;QAChB,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACvE,CAAC,CAAC;IACH,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;IACvB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAY,EACZ,EAAoD,EACpD,UAAgC,EAAE;IAElC,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAClD,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,UAAU,CAAI,IAAI,CAAC,CAAC,CAAC;IACnD,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;AACH,CAAC;AASD;;;GAGG;AACH,MAAM,UAAU,cAAc,CAC5B,IAAY,EACZ,UAAgC,EAAE;IAElC,IAAI,MAAiD,CAAC;IACtD,MAAM,GAAG,GAAG,CAAC,GAAG,EAAE;QAChB,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,OAAO,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAC3D,MAAM,CAAC,EAAE,EAAE,CAAC,UAAU,CAAI,IAAI,CAAC,CAChC,CAAC;YACF,MAAM,GAAG,OAAO,CAAC;YACjB,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE;gBACjB,IAAI,MAAM,KAAK,OAAO;oBAAE,MAAM,GAAG,SAAS,CAAC;YAC7C,CAAC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAsB,CAAC;IACxB,GAAG,CAAC,KAAK,GAAG,GAAG,EAAE;QACf,MAAM,GAAG,SAAS,CAAC;IACrB,CAAC,CAAC;IACF,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { RecordsClientOptions } from '../client/recordsClient.js';
|
|
2
|
+
import type { AdminDirectory } from './types.js';
|
|
3
|
+
export interface AdminRecord {
|
|
4
|
+
id: string;
|
|
5
|
+
[key: string]: unknown;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* 管理者の一覧を読む側(`authorizedCollection` の `list` と同じ形)。
|
|
9
|
+
* 自前で接続せず、呼び出し側が権限チェック層などを通した読み取りを渡すときに使う。
|
|
10
|
+
* `all.maxItems` を超える件数のときは、呼び出し側が例外にする(黙って切らない)
|
|
11
|
+
*/
|
|
12
|
+
export interface AdminsLister {
|
|
13
|
+
list(filter: Record<string, unknown>, options?: {
|
|
14
|
+
sort?: Record<string, 'asc' | 'desc'>;
|
|
15
|
+
all?: {
|
|
16
|
+
maxItems: number;
|
|
17
|
+
};
|
|
18
|
+
}): Promise<readonly AdminRecord[]>;
|
|
19
|
+
}
|
|
20
|
+
export interface AdminDirectoryOptions extends RecordsClientOptions {
|
|
21
|
+
/** 管理者のコレクション名(自前で接続するとき)。既定 `admins` */
|
|
22
|
+
collection?: string;
|
|
23
|
+
/**
|
|
24
|
+
* 管理者を読む側。指定すると自前で接続せず(url / region / timeout / collection は使わない)、
|
|
25
|
+
* これを通して読む。astro は `authorizedCollection` を渡す
|
|
26
|
+
*/
|
|
27
|
+
source?: AdminsLister;
|
|
28
|
+
/** 管理者の数の上限。件数が上限を超えたら例外(ちょうど上限は成功)。通知先の設計を見直す。既定 200 */
|
|
29
|
+
maxAdmins?: number;
|
|
30
|
+
/**
|
|
31
|
+
* 宛先を取り出すフィールド。先頭から見て、最初に null / undefined でない値を使う。
|
|
32
|
+
* 既定 `['id']`(asanowa)。astro の現行は `['email', 'id']`
|
|
33
|
+
*/
|
|
34
|
+
recipientFields?: readonly string[];
|
|
35
|
+
/** true なら id の昇順で読み、宛先の順序を固定する(astro の現行)。既定 false(順序は不定) */
|
|
36
|
+
sortById?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/** メールアドレスの形式なら小文字・前後空白除去で返す。そうでなければ null */
|
|
39
|
+
export declare function normalizeEmail(value: unknown): string | null;
|
|
40
|
+
/** 管理者のレコード → 宛先(小文字・重複なし・不正な形式は除く)。id がメールアドレス */
|
|
41
|
+
export declare function emailsOf(admins: readonly AdminRecord[], fields?: readonly string[]): string[];
|
|
42
|
+
/** 管理者の宛先を、呼び出しごとに Records から読む(`createNotifyHandler` の `admins` に渡す) */
|
|
43
|
+
export declare function createAdminDirectory(options?: AdminDirectoryOptions): AdminDirectory;
|
|
44
|
+
//# sourceMappingURL=admins.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admins.d.ts","sourceRoot":"","sources":["../../src/notify/admins.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAKjD,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,CACF,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC,CAAC;QAAC,GAAG,CAAC,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,GAC9E,OAAO,CAAC,SAAS,WAAW,EAAE,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,qBAAsB,SAAQ,oBAAoB;IACjE,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,6CAA6C;AAC7C,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAI5D;AAED,mDAAmD;AACnD,wBAAgB,QAAQ,CACtB,MAAM,EAAE,SAAS,WAAW,EAAE,EAC9B,MAAM,GAAE,SAAS,MAAM,EAAW,GACjC,MAAM,EAAE,CAQV;AAED,yEAAyE;AACzE,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,qBAA0B,GAAG,cAAc,CAsCxF"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 通知の宛先(管理者全員)の取得(ADR 0006)
|
|
3
|
+
*
|
|
4
|
+
* 管理者は `admins` レコード(管理画面で増減)が正本。レコードの id が小文字のメールアドレス。
|
|
5
|
+
* Records の IAM 版 Function URL 経由で全件を読み、正規化したメールアドレスを返す。
|
|
6
|
+
*/
|
|
7
|
+
import { withCollection } from '../client/recordsClient.js';
|
|
8
|
+
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
9
|
+
const PAGE_SIZE = 50;
|
|
10
|
+
/** メールアドレスの形式なら小文字・前後空白除去で返す。そうでなければ null */
|
|
11
|
+
export function normalizeEmail(value) {
|
|
12
|
+
if (typeof value !== 'string')
|
|
13
|
+
return null;
|
|
14
|
+
const email = value.trim().toLowerCase();
|
|
15
|
+
return EMAIL_PATTERN.test(email) ? email : null;
|
|
16
|
+
}
|
|
17
|
+
/** 管理者のレコード → 宛先(小文字・重複なし・不正な形式は除く)。id がメールアドレス */
|
|
18
|
+
export function emailsOf(admins, fields = ['id']) {
|
|
19
|
+
const set = new Set();
|
|
20
|
+
for (const a of admins) {
|
|
21
|
+
const key = fields.find((f) => a[f] !== undefined && a[f] !== null);
|
|
22
|
+
const email = normalizeEmail(key === undefined ? undefined : a[key]);
|
|
23
|
+
if (email)
|
|
24
|
+
set.add(email);
|
|
25
|
+
}
|
|
26
|
+
return [...set];
|
|
27
|
+
}
|
|
28
|
+
/** 管理者の宛先を、呼び出しごとに Records から読む(`createNotifyHandler` の `admins` に渡す) */
|
|
29
|
+
export function createAdminDirectory(options = {}) {
|
|
30
|
+
const { collection = 'admins', source, maxAdmins = 200, recipientFields = ['id'], sortById = false, ...clientOptions } = options;
|
|
31
|
+
const sort = sortById ? { id: 'asc' } : undefined;
|
|
32
|
+
return {
|
|
33
|
+
async listEmails() {
|
|
34
|
+
if (source) {
|
|
35
|
+
const all = await source.list({}, { ...(sort ? { sort } : {}), all: { maxItems: maxAdmins } });
|
|
36
|
+
return emailsOf(all, recipientFields);
|
|
37
|
+
}
|
|
38
|
+
return await withCollection(collection, async (admins) => {
|
|
39
|
+
const cursor = admins.find({});
|
|
40
|
+
if (sort)
|
|
41
|
+
cursor.sort(sort);
|
|
42
|
+
// 上限 + 1 件まで読めれば、超過を件数で判定できる(ちょうど上限は成功)
|
|
43
|
+
const all = await cursor.toArrayAll({
|
|
44
|
+
pageSize: PAGE_SIZE,
|
|
45
|
+
maxPages: Math.ceil((maxAdmins + 1) / PAGE_SIZE),
|
|
46
|
+
});
|
|
47
|
+
if (all.length > maxAdmins) {
|
|
48
|
+
throw new Error(`Too many admins: more than ${maxAdmins} in "${collection}"`);
|
|
49
|
+
}
|
|
50
|
+
return emailsOf(all, recipientFields);
|
|
51
|
+
}, clientOptions);
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=admins.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admins.js","sourceRoot":"","sources":["../../src/notify/admins.ts"],"names":[],"mappings":"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"}
|
package/dist/notify/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* ログ(warn 以上)・CloudWatch アラーム・直接呼び出しを Notice にそろえ、重複抑制のうえ、
|
|
5
5
|
* Slack・メールなどのチャネルへ配信する部品。配線(購読フィルタ・Lambda・Terraform)は各アプリが行う。
|
|
6
6
|
*/
|
|
7
|
+
export { createAdminDirectory, emailsOf, normalizeEmail } from './admins.js';
|
|
8
|
+
export type { AdminDirectoryOptions, AdminRecord, AdminsLister } from './admins.js';
|
|
7
9
|
export { ALERTS_RESOURCE, ALERT_RETENTION_DAYS, alertKeyOf, cleanupAlerts, createAlertRecorder, } from './alerts.js';
|
|
8
10
|
export type { AlertInput, AlertOccurrence, AlertRecord, AlertRecorder, AlertRecorderOptions, AlertsCleanupCollection, AlertsCollection, CleanupAlertsOptions, CleanupAlertsResult, } from './alerts.js';
|
|
9
11
|
export { createEmailChannel, sesSender } from './channels/email.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/notify/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,UAAU,EACV,aAAa,EACb,mBAAmB,GACpB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,UAAU,EACV,eAAe,EACf,WAAW,EACX,aAAa,EACb,oBAAoB,EACpB,uBAAuB,EACvB,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACpE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACjG,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,YAAY,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,qBAAqB,EACrB,QAAQ,EACR,OAAO,GACR,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACjF,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAClE,YAAY,EACV,cAAc,EACd,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,OAAO,EACP,MAAM,EACN,WAAW,EACX,MAAM,EACN,WAAW,EACX,WAAW,EACX,YAAY,GACb,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/notify/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,oBAAoB,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7E,YAAY,EAAE,qBAAqB,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACpF,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,UAAU,EACV,aAAa,EACb,mBAAmB,GACpB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,UAAU,EACV,eAAe,EACf,WAAW,EACX,aAAa,EACb,oBAAoB,EACpB,uBAAuB,EACvB,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACpE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACjG,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,YAAY,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,qBAAqB,EACrB,QAAQ,EACR,OAAO,GACR,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACjF,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAClE,YAAY,EACV,cAAc,EACd,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,OAAO,EACP,MAAM,EACN,WAAW,EACX,MAAM,EACN,WAAW,EACX,WAAW,EACX,YAAY,GACb,MAAM,YAAY,CAAC"}
|
package/dist/notify/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* ログ(warn 以上)・CloudWatch アラーム・直接呼び出しを Notice にそろえ、重複抑制のうえ、
|
|
5
5
|
* Slack・メールなどのチャネルへ配信する部品。配線(購読フィルタ・Lambda・Terraform)は各アプリが行う。
|
|
6
6
|
*/
|
|
7
|
+
export { createAdminDirectory, emailsOf, normalizeEmail } from './admins.js';
|
|
7
8
|
export { ALERTS_RESOURCE, ALERT_RETENTION_DAYS, alertKeyOf, cleanupAlerts, createAlertRecorder, } from './alerts.js';
|
|
8
9
|
export { createEmailChannel, sesSender } from './channels/email.js';
|
|
9
10
|
export { createSlackChannel } from './channels/slack.js';
|
package/dist/notify/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/notify/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,UAAU,EACV,aAAa,EACb,mBAAmB,GACpB,MAAM,aAAa,CAAC;AAYrB,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,qBAAqB,EACrB,QAAQ,EACR,OAAO,GACR,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEjF,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/notify/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,oBAAoB,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7E,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,UAAU,EACV,aAAa,EACb,mBAAmB,GACpB,MAAM,aAAa,CAAC;AAYrB,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,qBAAqB,EACrB,QAAQ,EACR,OAAO,GACR,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEjF,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/server/handler.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// @exabugs/dynamodb-client v1.
|
|
2
|
-
// Built: 2026-10-
|
|
1
|
+
// @exabugs/dynamodb-client v1.16.0
|
|
2
|
+
// Built: 2026-10-20T00:50:17.412Z
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
@@ -38980,7 +38980,7 @@ async function handler(event) {
|
|
|
38980
38980
|
return createCorsResponse(HTTP_STATUS.OK);
|
|
38981
38981
|
}
|
|
38982
38982
|
if (event.requestContext.http.method === "GET" && event.requestContext.http.path === "/version") {
|
|
38983
|
-
const version = "1.
|
|
38983
|
+
const version = "1.16.0";
|
|
38984
38984
|
return createSuccessResponse({ version, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, requestId);
|
|
38985
38985
|
}
|
|
38986
38986
|
if (event.requestContext.http.method !== "POST") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-handler-dlq.d.ts","sourceRoot":"","sources":["../../../src/server/media/process-handler-dlq.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAY,UAAU,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"process-handler-dlq.d.ts","sourceRoot":"","sources":["../../../src/server/media/process-handler-dlq.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAY,UAAU,EAAE,MAAM,YAAY,CAAC;AA6FvD,eAAO,MAAM,OAAO,EAAE,UAIrB,CAAC"}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { HeadObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
|
2
2
|
import { createLogger } from '../../shared/index.js';
|
|
3
|
-
import { handleFindMany } from '../operations/findMany.js';
|
|
4
|
-
import { handleUpdateOne } from '../operations/updateOne.js';
|
|
5
3
|
import { decodeOriginalFilename, extractAppMetadata } from './metadata.js';
|
|
4
|
+
import { findMediaRecord, writeMediaRecord } from './recordsClient.js';
|
|
6
5
|
const logger = createLogger({ service: 'media-process-handler-dlq' });
|
|
7
6
|
const s3Client = new S3Client({ region: process.env.AWS_REGION });
|
|
8
7
|
function extractS3Ref(envelope) {
|
|
@@ -30,8 +29,7 @@ async function processMessage(body) {
|
|
|
30
29
|
}
|
|
31
30
|
const { bucket, key, fileId } = ref;
|
|
32
31
|
const requestId = `dlq-${fileId}`;
|
|
33
|
-
const
|
|
34
|
-
const existingRecord = existing[0];
|
|
32
|
+
const existingRecord = await findMediaRecord(fileId);
|
|
35
33
|
if (existingRecord?.status === 'completed') {
|
|
36
34
|
logger.info('Media already completed, skipping DLQ failure write', { requestId, fileId });
|
|
37
35
|
return;
|
|
@@ -56,21 +54,12 @@ async function processMessage(body) {
|
|
|
56
54
|
error: error instanceof Error ? error.message : String(error),
|
|
57
55
|
});
|
|
58
56
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
error: 'Processing failed after exhausting retries',
|
|
66
|
-
updatedAt: now,
|
|
67
|
-
...(originalFilename ? { originalFilename } : {}),
|
|
68
|
-
...appMetadata,
|
|
69
|
-
},
|
|
70
|
-
$setOnInsert: { createdAt: now },
|
|
71
|
-
},
|
|
72
|
-
options: { upsert: true },
|
|
73
|
-
}, requestId);
|
|
57
|
+
await writeMediaRecord(fileId, {
|
|
58
|
+
status: 'failed',
|
|
59
|
+
error: 'Processing failed after exhausting retries',
|
|
60
|
+
...(originalFilename ? { originalFilename } : {}),
|
|
61
|
+
...appMetadata,
|
|
62
|
+
});
|
|
74
63
|
logger.warn('Media marked as failed via DLQ', { requestId, fileId });
|
|
75
64
|
}
|
|
76
65
|
export const handler = async (event) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-handler-dlq.js","sourceRoot":"","sources":["../../../src/server/media/process-handler-dlq.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEjE,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"process-handler-dlq.js","sourceRoot":"","sources":["../../../src/server/media/process-handler-dlq.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-handler.d.ts","sourceRoot":"","sources":["../../../src/server/media/process-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAW,SAAS,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"process-handler.d.ts","sourceRoot":"","sources":["../../../src/server/media/process-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAW,SAAS,EAAE,MAAM,YAAY,CAAC;AA8JrD,eAAO,MAAM,OAAO,EAAE,SAgBrB,CAAC"}
|
|
@@ -2,10 +2,9 @@ import sharp from 'sharp';
|
|
|
2
2
|
import { CopyObjectCommand, GetObjectCommand, HeadObjectCommand, PutObjectCommand, S3Client, } from '@aws-sdk/client-s3';
|
|
3
3
|
import { SSMClient } from '@aws-sdk/client-ssm';
|
|
4
4
|
import { createLogger } from '../../shared/index.js';
|
|
5
|
-
import { handleFindMany } from '../operations/findMany.js';
|
|
6
|
-
import { handleUpdateOne } from '../operations/updateOne.js';
|
|
7
5
|
import { isProcessableImage, verifyMagicBytes } from './magicBytes.js';
|
|
8
6
|
import { buildContentDisposition, decodeOriginalFilename, extractAppMetadata } from './metadata.js';
|
|
7
|
+
import { findMediaRecord, writeMediaRecord } from './recordsClient.js';
|
|
9
8
|
import { getImagePolicy } from './ssmParams.js';
|
|
10
9
|
const logger = createLogger({ service: 'media-process-handler' });
|
|
11
10
|
const s3Client = new S3Client({ region: process.env.AWS_REGION });
|
|
@@ -32,17 +31,6 @@ async function getHeadBytes(bucket, key) {
|
|
|
32
31
|
const result = await s3Client.send(new GetObjectCommand({ Bucket: bucket, Key: key, Range: `bytes=0-${MAGIC_BYTES_CHECK_RANGE}` }));
|
|
33
32
|
return streamToBuffer(result.Body);
|
|
34
33
|
}
|
|
35
|
-
async function writeMediaRecord(fileId, fields, requestId) {
|
|
36
|
-
const now = new Date().toISOString();
|
|
37
|
-
await handleUpdateOne('media', {
|
|
38
|
-
id: fileId,
|
|
39
|
-
data: {
|
|
40
|
-
$set: { ...fields, updatedAt: now },
|
|
41
|
-
$setOnInsert: { createdAt: now },
|
|
42
|
-
},
|
|
43
|
-
options: { upsert: true },
|
|
44
|
-
}, requestId);
|
|
45
|
-
}
|
|
46
34
|
async function processRecord(bucket, rawKey) {
|
|
47
35
|
const key = decodeURIComponent(rawKey.replace(/\+/g, ' '));
|
|
48
36
|
const fileId = key.replace(/^raw\//, '');
|
|
@@ -52,8 +40,7 @@ async function processRecord(bucket, rawKey) {
|
|
|
52
40
|
// fileIdがcompletedとして処理済みなら、再処理してmasterを上書きしない
|
|
53
41
|
// (process-handler-dlq.tsの重複配信対策と同じガード。詳細: docs/media-design.md
|
|
54
42
|
// 「presigned POSTの再利用・master不変性について」)
|
|
55
|
-
const
|
|
56
|
-
const existingRecord = existing[0];
|
|
43
|
+
const existingRecord = await findMediaRecord(fileId);
|
|
57
44
|
if (existingRecord?.status === 'completed') {
|
|
58
45
|
logger.warn('Media already completed, ignoring re-upload to the same fileId', {
|
|
59
46
|
requestId,
|
|
@@ -74,7 +61,7 @@ async function processRecord(bucket, rawKey) {
|
|
|
74
61
|
status: 'failed',
|
|
75
62
|
contentType,
|
|
76
63
|
error: 'Content type does not match file signature',
|
|
77
|
-
}
|
|
64
|
+
});
|
|
78
65
|
return;
|
|
79
66
|
}
|
|
80
67
|
const masterKey = `master/${fileId}`;
|
|
@@ -110,7 +97,7 @@ async function processRecord(bucket, rawKey) {
|
|
|
110
97
|
size: outputBuffer.length,
|
|
111
98
|
...(originalFilename ? { originalFilename } : {}),
|
|
112
99
|
...appMetadata,
|
|
113
|
-
}
|
|
100
|
+
});
|
|
114
101
|
}
|
|
115
102
|
else {
|
|
116
103
|
await s3Client.send(new CopyObjectCommand({
|
|
@@ -128,7 +115,7 @@ async function processRecord(bucket, rawKey) {
|
|
|
128
115
|
size: head.ContentLength ?? 0,
|
|
129
116
|
...(originalFilename ? { originalFilename } : {}),
|
|
130
117
|
...appMetadata,
|
|
131
|
-
}
|
|
118
|
+
});
|
|
132
119
|
}
|
|
133
120
|
logger.info('Media processed', { requestId, fileId, contentType });
|
|
134
121
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-handler.js","sourceRoot":"","sources":["../../../src/server/media/process-handler.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,QAAQ,GACT,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"process-handler.js","sourceRoot":"","sources":["../../../src/server/media/process-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { MediaRecord } from './types.js';
|
|
2
|
+
/** Records へのリクエストの timeout(ms)。Records の Lambda の timeout(30 秒)より短くする */
|
|
3
|
+
export declare const RECORDS_REQUEST_TIMEOUT_MS = 15000;
|
|
4
|
+
/** media レコードを 1 件読む。無ければ undefined */
|
|
5
|
+
export declare function findMediaRecord(fileId: string): Promise<MediaRecord | undefined>;
|
|
6
|
+
/**
|
|
7
|
+
* media レコードを作成/更新する(冪等な upsert)。
|
|
8
|
+
* filter に id を入れること(入れないと別の ID が採番される)。
|
|
9
|
+
*/
|
|
10
|
+
export declare function writeMediaRecord(fileId: string, fields: Record<string, unknown>): Promise<void>;
|
|
11
|
+
/** テスト用: キャッシュしたクライアントを捨てる */
|
|
12
|
+
export declare function resetRecordsClientForTest(): void;
|
|
13
|
+
//# sourceMappingURL=recordsClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recordsClient.d.ts","sourceRoot":"","sources":["../../../src/server/media/recordsClient.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,0EAA0E;AAC1E,eAAO,MAAM,0BAA0B,QAAS,CAAC;AASjD,uCAAuC;AACvC,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAGtF;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC,IAAI,CAAC,CASf;AAED,8BAA8B;AAC9B,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* media の Lambda(process-handler / process-handler-dlq)が Records を呼ぶための共通部品
|
|
3
|
+
*
|
|
4
|
+
* DynamoDB には直接触れず、Records の IAM 版 Function URL を `DynamoClient`(SigV4 署名)で
|
|
5
|
+
* 呼ぶ(詳細: docs/adr/0005-writes-through-records.md)。接続の定型は公開ヘルパー
|
|
6
|
+
* `lazyCollection`(src/client/recordsClient.ts。ADR 0006)に任せる。
|
|
7
|
+
* 失敗は握りつぶさず、そのまま例外にする(呼び出し側が再スローして、Lambda の非同期再試行と
|
|
8
|
+
* DLQ に乗せる)。
|
|
9
|
+
*/
|
|
10
|
+
import { lazyCollection } from '../../client/recordsClient.js';
|
|
11
|
+
/** Records へのリクエストの timeout(ms)。Records の Lambda の timeout(30 秒)より短くする */
|
|
12
|
+
export const RECORDS_REQUEST_TIMEOUT_MS = 15_000;
|
|
13
|
+
const MEDIA_COLLECTION = 'media';
|
|
14
|
+
/** コールドスタートで 1 回だけ接続して再利用する。環境変数は初回の使用時に読む */
|
|
15
|
+
const mediaCollection = lazyCollection(MEDIA_COLLECTION, {
|
|
16
|
+
timeout: RECORDS_REQUEST_TIMEOUT_MS,
|
|
17
|
+
});
|
|
18
|
+
/** media レコードを 1 件読む。無ければ undefined */
|
|
19
|
+
export async function findMediaRecord(fileId) {
|
|
20
|
+
const records = await (await mediaCollection()).findMany([fileId]);
|
|
21
|
+
return records[0];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* media レコードを作成/更新する(冪等な upsert)。
|
|
25
|
+
* filter に id を入れること(入れないと別の ID が採番される)。
|
|
26
|
+
*/
|
|
27
|
+
export async function writeMediaRecord(fileId, fields) {
|
|
28
|
+
const now = new Date().toISOString();
|
|
29
|
+
await (await mediaCollection()).updateOne({ id: fileId }, { $set: { ...fields, updatedAt: now }, $setOnInsert: { createdAt: now } }, { upsert: true });
|
|
30
|
+
}
|
|
31
|
+
/** テスト用: キャッシュしたクライアントを捨てる */
|
|
32
|
+
export function resetRecordsClientForTest() {
|
|
33
|
+
mediaCollection.reset();
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=recordsClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recordsClient.js","sourceRoot":"","sources":["../../../src/server/media/recordsClient.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAG/D,0EAA0E;AAC1E,MAAM,CAAC,MAAM,0BAA0B,GAAG,MAAM,CAAC;AAEjD,MAAM,gBAAgB,GAAG,OAAO,CAAC;AAEjC,8CAA8C;AAC9C,MAAM,eAAe,GAAG,cAAc,CAA+B,gBAAgB,EAAE;IACrF,OAAO,EAAE,0BAA0B;CACpC,CAAC,CAAC;AAEH,uCAAuC;AACvC,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAc;IAClD,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,eAAe,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IACnE,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,MAAc,EACd,MAA+B;IAE/B,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrC,MAAM,CACJ,MAAM,eAAe,EAAE,CACxB,CAAC,SAAS,CACT,EAAE,EAAE,EAAE,MAAM,EAAE,EACd,EAAE,IAAI,EAAE,EAAE,GAAG,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,EAAE,YAAY,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,EAAE,EACzE,EAAE,MAAM,EAAE,IAAI,EAAE,CACjB,CAAC;AACJ,CAAC;AAED,8BAA8B;AAC9B,MAAM,UAAU,yBAAyB;IACvC,eAAe,CAAC,KAAK,EAAE,CAAC;AAC1B,CAAC"}
|