@exabugs/dynamodb-client 1.21.0 → 1.22.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 +8 -0
- package/README.md +1 -1
- package/dist/notify/ops/index.d.ts +10 -0
- package/dist/notify/ops/index.d.ts.map +1 -0
- package/dist/notify/ops/index.js +9 -0
- package/dist/notify/ops/index.js.map +1 -0
- package/dist/notify/ops-notify.d.ts +106 -0
- package/dist/notify/ops-notify.d.ts.map +1 -0
- package/dist/notify/ops-notify.js +205 -0
- package/dist/notify/ops-notify.js.map +1 -0
- package/dist/notify/store.d.ts +5 -0
- package/dist/notify/store.d.ts.map +1 -1
- package/dist/notify/store.js +9 -2
- package/dist/notify/store.js.map +1 -1
- package/dist/server/handler.cjs +3 -3
- package/dist/server/media-handler.cjs +2 -2
- package/dist/server/media-process-handler-dlq.cjs +2 -2
- package/dist/server/media-process-handler.cjs +2 -2
- package/docs/NOTIFY.md +51 -0
- package/package.json +6 -1
- package/terraform/ops-notify/README.md +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.22.0] - 2026-10-25
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **運用通知 Lambda のエントリ `createOpsNotifyHandler`**(新しいサブパス `@exabugs/dynamodb-client/notify/ops`。共通化の段階 1 = A5、ADR 0006 の延長): asanowa・astro に複製されていた通知のエントリ(環境変数の読み取り、Slack〔SSM の Webhook。キャッシュ〕・メール〔SES v2〕のチャネル、通知ストア、管理者の一覧、alerts の記録の配線、`SKIP_RECORD_LOG_GROUPS` が空の警告)を 1 つにした。アプリのエントリは `export const handler = createOpsNotifyHandler();` だけになる。環境変数は `terraform/ops-notify`(1.21.0)の契約どおり(`PROJECT_NAME` も必須。`app` を渡せば不要)。両アプリの差は注入: `app`、`admins`(`recipientFields`・`sortById`・`maxAdmins`・`lister` = 読み取りを権限チェック層で包む)、`alertsCollection`(書き込みを包む)、`log`、`notify`(`createNotifyHandler` に渡す設定)、`overrides`(テスト用)。既定は両アプリの現行と同じ(`failOnAnyChannelError: true`、記録の上限・遮断、alerts の HTTP の時間切れ 2500 ms)。使い方は `docs/NOTIFY.md` の「通知 Lambda のエントリ」
|
|
15
|
+
- `readOpsNotifyConfig`、定数 `OPS_NOTIFY_RECORD_LIMITS`・`OPS_NOTIFY_RECORD_BREAKER`・`OPS_NOTIFY_RECORD_HTTP_TIMEOUT_MS`、型
|
|
16
|
+
- 依存に `@aws-sdk/client-sesv2`(3.946.0。他の AWS SDK と同じ版)を加えた。SSM・SES の SDK は `notify/ops` だけが静的に import する(`notify` 本体は読まない。`--external:@aws-sdk/*` の CJS バンドルでは `require` のまま残り、Lambda の実行環境の SDK で解決される)
|
|
17
|
+
|
|
10
18
|
## [1.21.0] - 2026-10-24
|
|
11
19
|
|
|
12
20
|
### Added
|
package/README.md
CHANGED
|
@@ -197,7 +197,7 @@ const article = await articles.findOne({ title: 'Hello DynamoDB' });
|
|
|
197
197
|
- **[Authorization & Secure Setup](docs/AUTHORIZATION.md)** - 認可モデルと安全な構成(必読。JWT の有効性しか検証しない/管理者専用ユーザープール/`aud` 検証)
|
|
198
198
|
- **[疎な検索の警告と対応](docs/SCAN_WARNING.md)** - 検索を打ち切らず、警告(SPARSE_SCAN)で気づいてインデックス・スキーマ・ロジックを見直す
|
|
199
199
|
- **[Records クライアントのヘルパー](docs/RECORDS_CLIENT.md)** - Lambda から Records を呼ぶ定型(`createRecordsClient` / `withCollection` / `lazyCollection`)
|
|
200
|
-
- **[運用通知](docs/NOTIFY.md)** - ログ・アラームを、Slack・メールで管理者へ通知する部品(`@exabugs/dynamodb-client/notify`)
|
|
200
|
+
- **[運用通知](docs/NOTIFY.md)** - ログ・アラームを、Slack・メールで管理者へ通知する部品(`@exabugs/dynamodb-client/notify`。通知 Lambda のエントリは `@exabugs/dynamodb-client/notify/ops`)
|
|
201
201
|
- **[システム変更履歴](docs/SYSTEM_CHANGES.md)** - インフラの変更・リリースを `systemChanges` に記録し、Slack に送る部品と記録 Lambda のハンドラ(`@exabugs/dynamodb-client/system-changes`)、terraform のラッパーから記録する CLI(`dynamodb-system-change`)
|
|
202
202
|
- **[Deployment](docs/DEPLOYMENT.md)** - Production deployment guide
|
|
203
203
|
- **[Terraform Modules](terraform/README.md)** - Infrastructure as Code
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 運用通知 Lambda のエントリ(`@exabugs/dynamodb-client/notify/ops`)
|
|
3
|
+
*
|
|
4
|
+
* `terraform/ops-notify` の Lambda のエントリは `export const handler = createOpsNotifyHandler();` だけでよい。
|
|
5
|
+
* SSM・SES の SDK を静的に import するので、`notify` 本体とはサブパスを分ける(SDK を使わない Lambda に
|
|
6
|
+
* 読み込ませない)。docs/NOTIFY.md の「通知 Lambda のエントリ」
|
|
7
|
+
*/
|
|
8
|
+
export { OPS_NOTIFY_RECORD_BREAKER, OPS_NOTIFY_RECORD_HTTP_TIMEOUT_MS, OPS_NOTIFY_RECORD_LIMITS, createOpsNotifyHandler, readOpsNotifyConfig, } from '../ops-notify.js';
|
|
9
|
+
export type { OpsNotifyAdminsOptions, OpsNotifyAlertRow, OpsNotifyConfig, OpsNotifyHandlerOptions, OpsNotifyLog, OpsNotifyOverrides, OpsNotifyPassthrough, } from '../ops-notify.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/notify/ops/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EACL,yBAAyB,EACzB,iCAAiC,EACjC,wBAAwB,EACxB,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,sBAAsB,EACtB,iBAAiB,EACjB,eAAe,EACf,uBAAuB,EACvB,YAAY,EACZ,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 運用通知 Lambda のエントリ(`@exabugs/dynamodb-client/notify/ops`)
|
|
3
|
+
*
|
|
4
|
+
* `terraform/ops-notify` の Lambda のエントリは `export const handler = createOpsNotifyHandler();` だけでよい。
|
|
5
|
+
* SSM・SES の SDK を静的に import するので、`notify` 本体とはサブパスを分ける(SDK を使わない Lambda に
|
|
6
|
+
* 読み込ませない)。docs/NOTIFY.md の「通知 Lambda のエントリ」
|
|
7
|
+
*/
|
|
8
|
+
export { OPS_NOTIFY_RECORD_BREAKER, OPS_NOTIFY_RECORD_HTTP_TIMEOUT_MS, OPS_NOTIFY_RECORD_LIMITS, createOpsNotifyHandler, readOpsNotifyConfig, } from '../ops-notify.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/notify/ops/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EACL,yBAAyB,EACzB,iCAAiC,EACjC,wBAAwB,EACxB,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { SSMClient } from '@aws-sdk/client-ssm';
|
|
2
|
+
import { type RecordsCollection } from '../client/recordsClient.js';
|
|
3
|
+
import { type AdminDirectoryOptions, type AdminRecord, type AdminsLister } from './admins.js';
|
|
4
|
+
import { type AlertsCollection } from './alerts.js';
|
|
5
|
+
import { type EmailSender } from './channels/email.js';
|
|
6
|
+
import { type NotifyHandlerOptions, type NotifyResult } from './handler.js';
|
|
7
|
+
import type { AdminDirectory, AlertIndex, Channel, NoticeStore } from './types.js';
|
|
8
|
+
/** 環境変数から読む設定(`readOpsNotifyConfig`) */
|
|
9
|
+
export interface OpsNotifyConfig {
|
|
10
|
+
projectName: string;
|
|
11
|
+
envName: string;
|
|
12
|
+
recordsIamUrl: string;
|
|
13
|
+
noticeTable: string;
|
|
14
|
+
slackWebhookParam?: string;
|
|
15
|
+
mailFrom?: string;
|
|
16
|
+
sesRegion: string;
|
|
17
|
+
dryRun: boolean;
|
|
18
|
+
/** あるときだけ alerts に記録する(無ければ記録しない) */
|
|
19
|
+
adminBaseUrl?: string;
|
|
20
|
+
/** 記録しないロググループ(Records のロググループ) */
|
|
21
|
+
skipRecordLogGroups: string[];
|
|
22
|
+
}
|
|
23
|
+
/** 環境変数(terraform/ops-notify の契約)を読む。必須が無ければ、名前を示して例外 */
|
|
24
|
+
export declare function readOpsNotifyConfig(env?: Record<string, string | undefined>): OpsNotifyConfig;
|
|
25
|
+
/** 記録の上限(`createNotifyHandler` の既定値と同じ。明示して、変えるときに見えるようにする) */
|
|
26
|
+
export declare const OPS_NOTIFY_RECORD_LIMITS: {
|
|
27
|
+
readonly timeoutMs: 3000;
|
|
28
|
+
readonly maxAlerts: 50;
|
|
29
|
+
readonly concurrency: 5;
|
|
30
|
+
};
|
|
31
|
+
/** 記録の遮断(同上) */
|
|
32
|
+
export declare const OPS_NOTIFY_RECORD_BREAKER: {
|
|
33
|
+
readonly failures: 3;
|
|
34
|
+
readonly cooldownMs: 60000;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* alerts の書き込みの HTTP の時間切れ(ミリ秒)。記録の上限(3 秒)より短くする
|
|
38
|
+
* (打ち切った後に HTTP が残らないように)
|
|
39
|
+
*/
|
|
40
|
+
export declare const OPS_NOTIFY_RECORD_HTTP_TIMEOUT_MS = 2500;
|
|
41
|
+
/** alerts の書き込み先が受け取る、生のコレクションの行 */
|
|
42
|
+
export type OpsNotifyAlertRow = {
|
|
43
|
+
id: string;
|
|
44
|
+
[key: string]: unknown;
|
|
45
|
+
};
|
|
46
|
+
/** 管理者の一覧(メールの宛先)の設定。Records への接続(url・region)は、ハンドラが渡す */
|
|
47
|
+
export interface OpsNotifyAdminsOptions extends Omit<AdminDirectoryOptions, 'url' | 'region' | 'source'> {
|
|
48
|
+
/**
|
|
49
|
+
* 読み取りを包む(既定は生のコレクションを、ライブラリの既定の読み方で読む)。権限チェック層(astro の
|
|
50
|
+
* authorizedCollection など)を通すアプリは、受け取った生のコレクションを自分の層で包んで返す。
|
|
51
|
+
* 呼び出しごとに接続し、終わったら閉じる(`withCollection`)
|
|
52
|
+
*/
|
|
53
|
+
lister?: (raw: RecordsCollection<AdminRecord>) => AdminsLister;
|
|
54
|
+
}
|
|
55
|
+
/** `createNotifyHandler` にそのまま渡すもの(既定はライブラリの既定) */
|
|
56
|
+
export type OpsNotifyPassthrough = Pick<NotifyHandlerOptions, 'windows' | 'rate' | 'adminCacheSec' | 'failOnAnyChannelError' | 'now' | 'logger' | 'skipRecordEvents' | 'recordLimits' | 'recordBreaker'>;
|
|
57
|
+
/** テスト用の差し替え(本番は使わない。外部の AWS に触れずに、分岐を確かめる) */
|
|
58
|
+
export interface OpsNotifyOverrides {
|
|
59
|
+
store?: NoticeStore & AlertIndex;
|
|
60
|
+
alertsCollection?: AlertsCollection;
|
|
61
|
+
channels?: Channel[];
|
|
62
|
+
admins?: AdminDirectory;
|
|
63
|
+
/** Slack の Webhook の URL を読む SSM のクライアント */
|
|
64
|
+
ssmClient?: Pick<SSMClient, 'send'>;
|
|
65
|
+
/** メールの送信(既定は SES v2。SES_REGION のクライアント) */
|
|
66
|
+
emailSender?: EmailSender;
|
|
67
|
+
}
|
|
68
|
+
/** エントリ自身のログの出力先(今は設定の誤りの warn だけ。alerts-cleanup の log と同じ形) */
|
|
69
|
+
export type OpsNotifyLog = (level: 'info' | 'warn' | 'error', fields: Record<string, unknown>) => void;
|
|
70
|
+
export interface OpsNotifyHandlerOptions {
|
|
71
|
+
/** アプリ名(通知の app)。省略時は環境変数 PROJECT_NAME(必須) */
|
|
72
|
+
app?: string;
|
|
73
|
+
/** AWS のリージョン(通知ストア・SSM・Records・ログ参照)。省略時は AWS_REGION(無ければ REGION) */
|
|
74
|
+
region?: string;
|
|
75
|
+
/** 管理者の一覧(メールの宛先) */
|
|
76
|
+
admins?: OpsNotifyAdminsOptions;
|
|
77
|
+
/**
|
|
78
|
+
* alerts の書き込み先を作る(既定は生のコレクションをそのまま使う)。権限チェック層を通すアプリは、
|
|
79
|
+
* 受け取った生のコレクションを自分の層で包んで返す。生のコレクションは最初の書き込みで接続し、
|
|
80
|
+
* コンテナの中で使い回す(`lazyCollection`。接続に失敗したら、次の書き込みで作り直す)
|
|
81
|
+
*/
|
|
82
|
+
alertsCollection?: (raw: RecordsCollection<OpsNotifyAlertRow>) => AlertsCollection;
|
|
83
|
+
/** alerts の書き込みの HTTP の時間切れ。既定 OPS_NOTIFY_RECORD_HTTP_TIMEOUT_MS(2500) */
|
|
84
|
+
recordHttpTimeoutMs?: number;
|
|
85
|
+
/** 設定の誤り(SKIP_RECORD_LOG_GROUPS が空)の警告の出力先。既定 logJson */
|
|
86
|
+
log?: OpsNotifyLog;
|
|
87
|
+
/** `createNotifyHandler` に渡す設定。既定: failOnAnyChannelError は true、記録の上限・遮断は上の定数 */
|
|
88
|
+
notify?: OpsNotifyPassthrough;
|
|
89
|
+
/** テスト用 */
|
|
90
|
+
overrides?: OpsNotifyOverrides;
|
|
91
|
+
}
|
|
92
|
+
type Handler = (event: unknown) => Promise<NotifyResult>;
|
|
93
|
+
/**
|
|
94
|
+
* 運用通知 Lambda のハンドラを作る。アプリのエントリは、これを export するだけ:
|
|
95
|
+
* `export const handler = createOpsNotifyHandler();`
|
|
96
|
+
*/
|
|
97
|
+
export declare function createOpsNotifyHandler(options?: OpsNotifyHandlerOptions): Handler;
|
|
98
|
+
/** 設定からハンドラを組み立てる(`createOpsNotifyHandler` の中身。設定を自前で用意するとき・テスト用) */
|
|
99
|
+
export declare function buildOpsNotifyHandler(config: OpsNotifyConfig, options?: OpsNotifyHandlerOptions): Handler;
|
|
100
|
+
/**
|
|
101
|
+
* alerts の記録(ADMIN_BASE_URL があるときだけ)。集計(ID・件数)は通知ストアの行(UpdateItem だけ)、
|
|
102
|
+
* レコードは Records の IAM URL(管理者の一覧と同じ経路)に書く。無ければ何も足さない(記録しない)
|
|
103
|
+
*/
|
|
104
|
+
export declare function opsNotifyAlertOptions(config: OpsNotifyConfig, index: AlertIndex, region: string, options?: OpsNotifyHandlerOptions): Pick<NotifyHandlerOptions, 'recorder' | 'adminBaseUrl' | 'region' | 'skipRecordLogGroups' | 'recordLimits' | 'recordBreaker'>;
|
|
105
|
+
export {};
|
|
106
|
+
//# sourceMappingURL=ops-notify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ops-notify.d.ts","sourceRoot":"","sources":["../../src/notify/ops-notify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 運用通知 Lambda のハンドラのファクトリ(ADR 0006 の延長。asanowa・astro の通知のエントリを 1 つにしたもの)。
|
|
3
|
+
*
|
|
4
|
+
* 入力: CloudWatch Logs の購読(warn 以上のログ)、EventBridge(CloudWatch アラームの状態変化)、
|
|
5
|
+
* 直接呼び出し(週 1 回の生存確認など)。重複抑制・流量制限のうえ、Slack とメール(管理者全員)へ送る。
|
|
6
|
+
* 処理そのものは `createNotifyHandler`。ここは、その周りの定型(環境変数の読み取り、チャネル・通知ストア・
|
|
7
|
+
* 管理者の一覧・alerts の書き込み先の組み立て)を持つ。docs/NOTIFY.md
|
|
8
|
+
*
|
|
9
|
+
* 環境変数(terraform/ops-notify が設定する):
|
|
10
|
+
* PROJECT_NAME / ENV_NAME / RECORDS_IAM_URL / NOTICE_TABLE(必須。PROJECT_NAME は `app` を渡せば不要)
|
|
11
|
+
* SLACK_WEBHOOK_PARAM(SSM の SecureString の名前。Webhook の URL は秘密)
|
|
12
|
+
* MAIL_FROM(送信元。未設定ならメールは送らない)/ SES_REGION(既定 us-east-1)
|
|
13
|
+
* DRY_RUN=true(送らず、計画だけを返す)
|
|
14
|
+
* ADMIN_BASE_URL(任意。あるときだけ alerts に記録し、通知に詳細の URL を載せる)
|
|
15
|
+
* SKIP_RECORD_LOG_GROUPS(カンマ区切り。記録しないロググループ。Records のロググループを必ず入れる。
|
|
16
|
+
* 記録の失敗 → Records のエラーログ → 通知 → 記録、のループを防ぐ。通知は送る)
|
|
17
|
+
*
|
|
18
|
+
* ハンドラは最初の呼び出しで組み立て、以降はコンテナの中で使い回す(重複抑制の遮断・管理者の一覧の
|
|
19
|
+
* キャッシュを保つため)。組み立てに失敗したら、次の呼び出しでやり直す。
|
|
20
|
+
* このハンドラ自身のロググループは購読しない(自分のログで無限ループになる)。
|
|
21
|
+
*
|
|
22
|
+
* 公開はサブパス `@exabugs/dynamodb-client/notify/ops`(`./ops/index.ts`)だけ。`notify` 本体からは読まない
|
|
23
|
+
* (SSM・SES の SDK を静的に import するので、それを使わない Lambda に読み込ませない)。静的 import にするのは、
|
|
24
|
+
* `--external:@aws-sdk/*` の CJS バンドルで `require()` になり、Lambda の同梱 SDK(NODE_PATH)で解決されるため
|
|
25
|
+
* (動的 `import()` は NODE_PATH を使わない)。ops/__tests__/bundle.test.ts で固定する。
|
|
26
|
+
*/
|
|
27
|
+
import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2';
|
|
28
|
+
import { GetParameterCommand, SSMClient } from '@aws-sdk/client-ssm';
|
|
29
|
+
import { lazyCollection, withCollection, } from '../client/recordsClient.js';
|
|
30
|
+
import { createAdminDirectory, } from './admins.js';
|
|
31
|
+
import { ALERTS_RESOURCE, createAlertRecorder } from './alerts.js';
|
|
32
|
+
import { createEmailChannel, sesSender } from './channels/email.js';
|
|
33
|
+
import { createSlackChannel } from './channels/slack.js';
|
|
34
|
+
import { createNotifyHandler } from './handler.js';
|
|
35
|
+
import { logJson } from './logger.js';
|
|
36
|
+
import { createDynamoNoticeStore } from './store.js';
|
|
37
|
+
/** 環境変数(terraform/ops-notify の契約)を読む。必須が無ければ、名前を示して例外 */
|
|
38
|
+
export function readOpsNotifyConfig(env = process.env) {
|
|
39
|
+
const need = (name) => {
|
|
40
|
+
const v = env[name];
|
|
41
|
+
if (!v)
|
|
42
|
+
throw new Error(`${name} environment variable is required`);
|
|
43
|
+
return v;
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
projectName: need('PROJECT_NAME'),
|
|
47
|
+
envName: need('ENV_NAME'),
|
|
48
|
+
recordsIamUrl: need('RECORDS_IAM_URL'),
|
|
49
|
+
noticeTable: need('NOTICE_TABLE'),
|
|
50
|
+
slackWebhookParam: env.SLACK_WEBHOOK_PARAM || undefined,
|
|
51
|
+
mailFrom: env.MAIL_FROM || undefined,
|
|
52
|
+
sesRegion: env.SES_REGION || 'us-east-1',
|
|
53
|
+
dryRun: env.DRY_RUN === 'true',
|
|
54
|
+
adminBaseUrl: env.ADMIN_BASE_URL || undefined,
|
|
55
|
+
skipRecordLogGroups: (env.SKIP_RECORD_LOG_GROUPS ?? '')
|
|
56
|
+
.split(',')
|
|
57
|
+
.map((g) => g.trim())
|
|
58
|
+
.filter(Boolean),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** 記録の上限(`createNotifyHandler` の既定値と同じ。明示して、変えるときに見えるようにする) */
|
|
62
|
+
export const OPS_NOTIFY_RECORD_LIMITS = { timeoutMs: 3000, maxAlerts: 50, concurrency: 5 };
|
|
63
|
+
/** 記録の遮断(同上) */
|
|
64
|
+
export const OPS_NOTIFY_RECORD_BREAKER = { failures: 3, cooldownMs: 60_000 };
|
|
65
|
+
/**
|
|
66
|
+
* alerts の書き込みの HTTP の時間切れ(ミリ秒)。記録の上限(3 秒)より短くする
|
|
67
|
+
* (打ち切った後に HTTP が残らないように)
|
|
68
|
+
*/
|
|
69
|
+
export const OPS_NOTIFY_RECORD_HTTP_TIMEOUT_MS = 2500;
|
|
70
|
+
/**
|
|
71
|
+
* 運用通知 Lambda のハンドラを作る。アプリのエントリは、これを export するだけ:
|
|
72
|
+
* `export const handler = createOpsNotifyHandler();`
|
|
73
|
+
*/
|
|
74
|
+
export function createOpsNotifyHandler(options = {}) {
|
|
75
|
+
let built;
|
|
76
|
+
return async (event) => {
|
|
77
|
+
if (!built) {
|
|
78
|
+
const env = options.app ? { ...process.env, PROJECT_NAME: options.app } : process.env;
|
|
79
|
+
built = buildOpsNotifyHandler(readOpsNotifyConfig(env), options);
|
|
80
|
+
}
|
|
81
|
+
return built(event);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function regionOf(options) {
|
|
85
|
+
const region = options.region ?? (process.env.AWS_REGION || process.env.REGION || undefined);
|
|
86
|
+
if (!region)
|
|
87
|
+
throw new Error('AWS_REGION environment variable is not set');
|
|
88
|
+
return region;
|
|
89
|
+
}
|
|
90
|
+
/** 設定からハンドラを組み立てる(`createOpsNotifyHandler` の中身。設定を自前で用意するとき・テスト用) */
|
|
91
|
+
export function buildOpsNotifyHandler(config, options = {}) {
|
|
92
|
+
const region = regionOf(options);
|
|
93
|
+
const o = options.overrides ?? {};
|
|
94
|
+
const channels = o.channels ?? buildChannels(config, region, o);
|
|
95
|
+
const store = o.store ?? createDynamoNoticeStore(config.noticeTable, region);
|
|
96
|
+
return createNotifyHandler({
|
|
97
|
+
// 一部のチャネルだけの失敗(Webhook の失効など)も、Lambda の失敗先(運用者のメール)へ届ける
|
|
98
|
+
failOnAnyChannelError: true,
|
|
99
|
+
// 記録の上限・遮断は、記録するときだけ opsNotifyAlertOptions が渡す(現行の両アプリと同じ)
|
|
100
|
+
...options.notify,
|
|
101
|
+
app: options.app ?? config.projectName,
|
|
102
|
+
env: config.envName,
|
|
103
|
+
channels,
|
|
104
|
+
store,
|
|
105
|
+
admins: o.admins ?? adminDirectoryOf(config, region, options.admins),
|
|
106
|
+
dryRun: config.dryRun,
|
|
107
|
+
region,
|
|
108
|
+
...opsNotifyAlertOptions(config, store, region, options),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function adminDirectoryOf(config, region, admins = {}) {
|
|
112
|
+
const { lister, ...directory } = admins;
|
|
113
|
+
const records = {
|
|
114
|
+
url: config.recordsIamUrl,
|
|
115
|
+
region,
|
|
116
|
+
...(directory.timeout !== undefined ? { timeout: directory.timeout } : {}),
|
|
117
|
+
};
|
|
118
|
+
if (!lister)
|
|
119
|
+
return createAdminDirectory({ ...directory, ...records });
|
|
120
|
+
const name = directory.collection ?? 'admins';
|
|
121
|
+
return createAdminDirectory({
|
|
122
|
+
...directory,
|
|
123
|
+
source: {
|
|
124
|
+
list: (filter, listOptions) => withCollection(name, (raw) => lister(raw).list(filter, listOptions), records),
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/** Slack(SSM の Webhook。実行環境の間はキャッシュ)とメール(SES v2)のチャネル */
|
|
129
|
+
function buildChannels(config, region, o) {
|
|
130
|
+
const channels = [];
|
|
131
|
+
if (config.slackWebhookParam) {
|
|
132
|
+
const name = config.slackWebhookParam;
|
|
133
|
+
let ssm = o.ssmClient;
|
|
134
|
+
let cached;
|
|
135
|
+
channels.push(createSlackChannel({
|
|
136
|
+
getWebhookUrl: async () => {
|
|
137
|
+
if (cached)
|
|
138
|
+
return cached;
|
|
139
|
+
ssm ??= new SSMClient({ region });
|
|
140
|
+
const r = await ssm.send(new GetParameterCommand({ Name: name, WithDecryption: true }));
|
|
141
|
+
const value = r.Parameter?.Value;
|
|
142
|
+
if (!value)
|
|
143
|
+
throw new Error('Slack の Webhook の URL が取得できません');
|
|
144
|
+
cached = value;
|
|
145
|
+
return value;
|
|
146
|
+
},
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
if (config.mailFrom) {
|
|
150
|
+
channels.push(createEmailChannel({
|
|
151
|
+
from: config.mailFrom,
|
|
152
|
+
send: o.emailSender ?? lazySesSender(config.sesRegion),
|
|
153
|
+
}));
|
|
154
|
+
}
|
|
155
|
+
return channels;
|
|
156
|
+
}
|
|
157
|
+
/** SES v2 の送信。クライアントは最初の送信のときに作る */
|
|
158
|
+
function lazySesSender(sesRegion) {
|
|
159
|
+
let send;
|
|
160
|
+
return async (message) => {
|
|
161
|
+
send ??= sesSender({ client: new SESv2Client({ region: sesRegion }), SendEmailCommand });
|
|
162
|
+
return send(message);
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* alerts の記録(ADMIN_BASE_URL があるときだけ)。集計(ID・件数)は通知ストアの行(UpdateItem だけ)、
|
|
167
|
+
* レコードは Records の IAM URL(管理者の一覧と同じ経路)に書く。無ければ何も足さない(記録しない)
|
|
168
|
+
*/
|
|
169
|
+
export function opsNotifyAlertOptions(config, index, region, options = {}) {
|
|
170
|
+
if (!config.adminBaseUrl)
|
|
171
|
+
return {};
|
|
172
|
+
if (config.skipRecordLogGroups.length === 0) {
|
|
173
|
+
// 自分のログは購読されないので、Lambda の設定の誤りとして残すだけ(遮断が最後の防壁)
|
|
174
|
+
(options.log ?? logJson)('warn', {
|
|
175
|
+
event: 'ALERTS_SKIP_LOG_GROUPS_EMPTY',
|
|
176
|
+
message: 'SKIP_RECORD_LOG_GROUPS が空です(Records のロググループを入れないと、記録の失敗がループしうる)',
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
recorder: createAlertRecorder({
|
|
181
|
+
collection: options.overrides?.alertsCollection ?? alertsCollectionOf(config, region, options),
|
|
182
|
+
index,
|
|
183
|
+
}),
|
|
184
|
+
adminBaseUrl: config.adminBaseUrl,
|
|
185
|
+
region,
|
|
186
|
+
skipRecordLogGroups: config.skipRecordLogGroups,
|
|
187
|
+
recordLimits: options.notify?.recordLimits ?? OPS_NOTIFY_RECORD_LIMITS,
|
|
188
|
+
recordBreaker: options.notify?.recordBreaker ?? OPS_NOTIFY_RECORD_BREAKER,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/** alerts の書き込み先。生のコレクションは最初の書き込みで接続し、使い回す */
|
|
192
|
+
function alertsCollectionOf(config, region, options) {
|
|
193
|
+
const get = lazyCollection(ALERTS_RESOURCE, {
|
|
194
|
+
url: config.recordsIamUrl,
|
|
195
|
+
region,
|
|
196
|
+
timeout: options.recordHttpTimeoutMs ?? OPS_NOTIFY_RECORD_HTTP_TIMEOUT_MS,
|
|
197
|
+
});
|
|
198
|
+
const wrap = options.alertsCollection ?? ((raw) => raw);
|
|
199
|
+
return {
|
|
200
|
+
async updateOne(filter, update, updateOptions) {
|
|
201
|
+
return wrap(await get()).updateOne(filter, update, updateOptions);
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=ops-notify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ops-notify.js","sourceRoot":"","sources":["../../src/notify/ops-notify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErE,OAAO,EAGL,cAAc,EACd,cAAc,GACf,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAIL,oBAAoB,GACrB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,eAAe,EAAyB,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC1F,OAAO,EAAoB,kBAAkB,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAgD,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACjG,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAmBrD,yDAAyD;AACzD,MAAM,UAAU,mBAAmB,CACjC,MAA0C,OAAO,CAAC,GAAG;IAErD,MAAM,IAAI,GAAG,CAAC,IAAY,EAAU,EAAE;QACpC,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;QACpB,IAAI,CAAC,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,mCAAmC,CAAC,CAAC;QACpE,OAAO,CAAC,CAAC;IACX,CAAC,CAAC;IACF,OAAO;QACL,WAAW,EAAE,IAAI,CAAC,cAAc,CAAC;QACjC,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC;QACzB,aAAa,EAAE,IAAI,CAAC,iBAAiB,CAAC;QACtC,WAAW,EAAE,IAAI,CAAC,cAAc,CAAC;QACjC,iBAAiB,EAAE,GAAG,CAAC,mBAAmB,IAAI,SAAS;QACvD,QAAQ,EAAE,GAAG,CAAC,SAAS,IAAI,SAAS;QACpC,SAAS,EAAE,GAAG,CAAC,UAAU,IAAI,WAAW;QACxC,MAAM,EAAE,GAAG,CAAC,OAAO,KAAK,MAAM;QAC9B,YAAY,EAAE,GAAG,CAAC,cAAc,IAAI,SAAS;QAC7C,mBAAmB,EAAE,CAAC,GAAG,CAAC,sBAAsB,IAAI,EAAE,CAAC;aACpD,KAAK,CAAC,GAAG,CAAC;aACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;aACpB,MAAM,CAAC,OAAO,CAAC;KACnB,CAAC;AACJ,CAAC;AAED,+DAA+D;AAC/D,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC,EAAW,CAAC;AACpG,gBAAgB;AAChB,MAAM,CAAC,MAAM,yBAAyB,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,MAAM,EAAW,CAAC;AACtF;;;GAGG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAAG,IAAI,CAAC;AA2EtD;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,UAAmC,EAAE;IAC1E,IAAI,KAA0B,CAAC;IAC/B,OAAO,KAAK,EAAE,KAAK,EAAE,EAAE;QACrB,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC;YACtF,KAAK,GAAG,qBAAqB,CAAC,mBAAmB,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;QACnE,CAAC;QACD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,OAAgC;IAChD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,SAAS,CAAC,CAAC;IAC7F,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAC3E,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,qBAAqB,CACnC,MAAuB,EACvB,UAAmC,EAAE;IAErC,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;IACjC,MAAM,CAAC,GAAG,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC;IAClC,MAAM,QAAQ,GAAG,CAAC,CAAC,QAAQ,IAAI,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAChE,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI,uBAAuB,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAC7E,OAAO,mBAAmB,CAAC;QACzB,wDAAwD;QACxD,qBAAqB,EAAE,IAAI;QAC3B,2DAA2D;QAC3D,GAAG,OAAO,CAAC,MAAM;QACjB,GAAG,EAAE,OAAO,CAAC,GAAG,IAAI,MAAM,CAAC,WAAW;QACtC,GAAG,EAAE,MAAM,CAAC,OAAO;QACnB,QAAQ;QACR,KAAK;QACL,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC;QACpE,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,MAAM;QACN,GAAG,qBAAqB,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC;KACzD,CAAC,CAAC;AACL,CAAC;AAED,SAAS,gBAAgB,CACvB,MAAuB,EACvB,MAAc,EACd,SAAiC,EAAE;IAEnC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,GAAG,MAAM,CAAC;IACxC,MAAM,OAAO,GAAyB;QACpC,GAAG,EAAE,MAAM,CAAC,aAAa;QACzB,MAAM;QACN,GAAG,CAAC,SAAS,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC3E,CAAC;IACF,IAAI,CAAC,MAAM;QAAE,OAAO,oBAAoB,CAAC,EAAE,GAAG,SAAS,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IACvE,MAAM,IAAI,GAAG,SAAS,CAAC,UAAU,IAAI,QAAQ,CAAC;IAC9C,OAAO,oBAAoB,CAAC;QAC1B,GAAG,SAAS;QACZ,MAAM,EAAE;YACN,IAAI,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,CAC5B,cAAc,CACZ,IAAI,EACJ,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,EAC9C,OAAO,CACR;SACJ;KACF,CAAC,CAAC;AACL,CAAC;AAED,yDAAyD;AACzD,SAAS,aAAa,CAAC,MAAuB,EAAE,MAAc,EAAE,CAAqB;IACnF,MAAM,QAAQ,GAAc,EAAE,CAAC;IAE/B,IAAI,MAAM,CAAC,iBAAiB,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,iBAAiB,CAAC;QACtC,IAAI,GAAG,GAAG,CAAC,CAAC,SAAS,CAAC;QACtB,IAAI,MAA0B,CAAC;QAC/B,QAAQ,CAAC,IAAI,CACX,kBAAkB,CAAC;YACjB,aAAa,EAAE,KAAK,IAAI,EAAE;gBACxB,IAAI,MAAM;oBAAE,OAAO,MAAM,CAAC;gBAC1B,GAAG,KAAK,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;gBAClC,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,mBAAmB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;gBACxF,MAAM,KAAK,GAAG,CAAC,CAAC,SAAS,EAAE,KAAK,CAAC;gBACjC,IAAI,CAAC,KAAK;oBAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;gBAC9D,MAAM,GAAG,KAAK,CAAC;gBACf,OAAO,KAAK,CAAC;YACf,CAAC;SACF,CAAC,CACH,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,QAAQ,CAAC,IAAI,CACX,kBAAkB,CAAC;YACjB,IAAI,EAAE,MAAM,CAAC,QAAQ;YACrB,IAAI,EAAE,CAAC,CAAC,WAAW,IAAI,aAAa,CAAC,MAAM,CAAC,SAAS,CAAC;SACvD,CAAC,CACH,CAAC;IACJ,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,oCAAoC;AACpC,SAAS,aAAa,CAAC,SAAiB;IACtC,IAAI,IAA6B,CAAC;IAClC,OAAO,KAAK,EAAE,OAAO,EAAE,EAAE;QACvB,IAAI,KAAK,SAAS,CAAC,EAAE,MAAM,EAAE,IAAI,WAAW,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAC;QACzF,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CACnC,MAAuB,EACvB,KAAiB,EACjB,MAAc,EACd,UAAmC,EAAE;IAKrC,IAAI,CAAC,MAAM,CAAC,YAAY;QAAE,OAAO,EAAE,CAAC;IACpC,IAAI,MAAM,CAAC,mBAAmB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5C,gDAAgD;QAChD,CAAC,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,CAAC,MAAM,EAAE;YAC/B,KAAK,EAAE,8BAA8B;YACrC,OAAO,EACL,iEAAiE;SACpE,CAAC,CAAC;IACL,CAAC;IACD,OAAO;QACL,QAAQ,EAAE,mBAAmB,CAAC;YAC5B,UAAU,EACR,OAAO,CAAC,SAAS,EAAE,gBAAgB,IAAI,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC;YACpF,KAAK;SACN,CAAC;QACF,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,MAAM;QACN,mBAAmB,EAAE,MAAM,CAAC,mBAAmB;QAC/C,YAAY,EAAE,OAAO,CAAC,MAAM,EAAE,YAAY,IAAI,wBAAwB;QACtE,aAAa,EAAE,OAAO,CAAC,MAAM,EAAE,aAAa,IAAI,yBAAyB;KAC1E,CAAC;AACJ,CAAC;AAED,8CAA8C;AAC9C,SAAS,kBAAkB,CACzB,MAAuB,EACvB,MAAc,EACd,OAAgC;IAEhC,MAAM,GAAG,GAAG,cAAc,CAAoB,eAAe,EAAE;QAC7D,GAAG,EAAE,MAAM,CAAC,aAAa;QACzB,MAAM;QACN,OAAO,EAAE,OAAO,CAAC,mBAAmB,IAAI,iCAAiC;KAC1E,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,OAAO,CAAC,gBAAgB,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;IACxD,OAAO;QACL,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa;YAC3C,OAAO,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;QACpE,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/notify/store.d.ts
CHANGED
|
@@ -14,6 +14,11 @@ export declare class DynamoNoticeStore implements NoticeStore, AlertIndex {
|
|
|
14
14
|
*/
|
|
15
15
|
touchAlert(alertKey: string, input: AlertTouchInput): Promise<AlertTouchResult>;
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* 通知テーブル(notices)の `DynamoNoticeStore` を、リージョンの DynamoDB クライアントで作る
|
|
19
|
+
* (`createOpsNotifyHandler` の既定。SDK のクライアントの生成を、許可リストに載ったこのファイルに閉じる)
|
|
20
|
+
*/
|
|
21
|
+
export declare function createDynamoNoticeStore(tableName: string, region: string): DynamoNoticeStore;
|
|
17
22
|
/** テスト・ローカル用(1 プロセス内だけ) */
|
|
18
23
|
export declare class MemoryNoticeStore implements NoticeStore, AlertIndex {
|
|
19
24
|
readonly alerts: Map<string, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/notify/store.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,sBAAsB,EAAiB,MAAM,uBAAuB,CAAC;AAE9E,OAAO,KAAK,EACV,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,WAAW,EACZ,MAAM,YAAY,CAAC;AAIpB,qBAAa,iBAAkB,YAAW,WAAW,EAAE,UAAU;IAE7D,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBADT,GAAG,EAAE,IAAI,CAAC,sBAAsB,EAAE,MAAM,CAAC,EACzC,SAAS,EAAE,MAAM;IAG9B,KAAK,CACT,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,WAAW,CAAC;IAiDjB,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB3E,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAe5F;;;;OAIG;IACG,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAwDtF;AAcD,2BAA2B;AAC3B,qBAAa,iBAAkB,YAAW,WAAW,EAAE,UAAU;IAC/D,QAAQ,CAAC,MAAM;iBAEF,MAAM;aAAO,MAAM;qBAAe,MAAM;oBAAc,MAAM;aAAO,MAAM;OAClF;IACJ,QAAQ,CAAC,GAAG;mBAAgC,MAAM;oBAAc,MAAM;OAAM;IAC5E,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;IAE9C,KAAK,CACT,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,WAAW,CAAC;IAWjB,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM3E,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAOtF,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAmBtF"}
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/notify/store.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,sBAAsB,EAAiB,MAAM,uBAAuB,CAAC;AAE9E,OAAO,KAAK,EACV,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,WAAW,EACZ,MAAM,YAAY,CAAC;AAIpB,qBAAa,iBAAkB,YAAW,WAAW,EAAE,UAAU;IAE7D,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBADT,GAAG,EAAE,IAAI,CAAC,sBAAsB,EAAE,MAAM,CAAC,EACzC,SAAS,EAAE,MAAM;IAG9B,KAAK,CACT,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,WAAW,CAAC;IAiDjB,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB3E,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAe5F;;;;OAIG;IACG,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAwDtF;AAcD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,iBAAiB,CAK5F;AAED,2BAA2B;AAC3B,qBAAa,iBAAkB,YAAW,WAAW,EAAE,UAAU;IAC/D,QAAQ,CAAC,MAAM;iBAEF,MAAM;aAAO,MAAM;qBAAe,MAAM;oBAAc,MAAM;aAAO,MAAM;OAClF;IACJ,QAAQ,CAAC,GAAG;mBAAgC,MAAM;oBAAc,MAAM;OAAM;IAC5E,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;IAE9C,KAAK,CACT,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,WAAW,CAAC;IAWjB,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM3E,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAOtF,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAmBtF"}
|
package/dist/notify/store.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* 重複抑制・流量制限の記録(DynamoDB。専用のテーブル: パーティションキー `pk`(文字列)、TTL 属性 `ttl`)
|
|
3
3
|
* 同時実行でも二重に確保しないよう、条件付き更新を使う。窓の判定は `expiresAt`(TTL の削除の遅れに影響されない)。
|
|
4
4
|
*/
|
|
5
|
-
import { ConditionalCheckFailedException } from '@aws-sdk/client-dynamodb';
|
|
6
|
-
import { UpdateCommand } from '@aws-sdk/lib-dynamodb';
|
|
5
|
+
import { ConditionalCheckFailedException, DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
6
|
+
import { DynamoDBDocumentClient, UpdateCommand } from '@aws-sdk/lib-dynamodb';
|
|
7
7
|
const KEEP_SEC = 86400;
|
|
8
8
|
export class DynamoNoticeStore {
|
|
9
9
|
doc;
|
|
@@ -156,6 +156,13 @@ function touched(attrs, input) {
|
|
|
156
156
|
const firstSeenAt = typeof attrs?.firstSeenAt === 'string' ? attrs.firstSeenAt : (input.firstAt ?? input.at);
|
|
157
157
|
return { alertId, count, firstSeenAt, isNew: alertId === input.newId };
|
|
158
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* 通知テーブル(notices)の `DynamoNoticeStore` を、リージョンの DynamoDB クライアントで作る
|
|
161
|
+
* (`createOpsNotifyHandler` の既定。SDK のクライアントの生成を、許可リストに載ったこのファイルに閉じる)
|
|
162
|
+
*/
|
|
163
|
+
export function createDynamoNoticeStore(tableName, region) {
|
|
164
|
+
return new DynamoNoticeStore(DynamoDBDocumentClient.from(new DynamoDBClient({ region })), tableName);
|
|
165
|
+
}
|
|
159
166
|
/** テスト・ローカル用(1 プロセス内だけ) */
|
|
160
167
|
export class MemoryNoticeStore {
|
|
161
168
|
alerts = new Map();
|
package/dist/notify/store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/notify/store.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,+BAA+B,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/notify/store.ts"],"names":[],"mappings":"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"}
|
package/dist/server/handler.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// @exabugs/dynamodb-client v1.
|
|
2
|
-
// Built: 2026-10-
|
|
1
|
+
// @exabugs/dynamodb-client v1.22.0
|
|
2
|
+
// Built: 2026-10-25T05:38:01.970Z
|
|
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.22.0";
|
|
38984
38984
|
return createSuccessResponse({ version, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, requestId);
|
|
38985
38985
|
}
|
|
38986
38986
|
if (event.requestContext.http.method !== "POST") {
|
package/docs/NOTIFY.md
CHANGED
|
@@ -119,6 +119,57 @@ export const handler = createNotifyHandler({
|
|
|
119
119
|
});
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
+
### 通知 Lambda のエントリ(`createOpsNotifyHandler`。1.22.0)
|
|
123
|
+
|
|
124
|
+
`terraform/ops-notify` の Lambda のエントリは、ハンドラのファクトリを export するだけでよい。上の組み立て(環境変数の読み取り、Slack〔SSM の Webhook〕・メール〔SES v2〕のチャネル、通知ストア、管理者の一覧、alerts の記録)をライブラリが行う。asanowa・astro のエントリ(`functions/ops-notify/src/index.ts`・`apps/server/src/notify.ts`)を 1 つにしたもの:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { createOpsNotifyHandler } from '@exabugs/dynamodb-client/notify/ops';
|
|
128
|
+
|
|
129
|
+
export const handler = createOpsNotifyHandler();
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
- 環境変数(`terraform/ops-notify` が設定する。`readOpsNotifyConfig` が読む): `PROJECT_NAME`・`ENV_NAME`・`RECORDS_IAM_URL`・`NOTICE_TABLE`(必須。無ければ `<名前> environment variable is required`)、`SLACK_WEBHOOK_PARAM`・`MAIL_FROM`(空なら、そのチャネルを作らない)、`SES_REGION`(既定 `us-east-1`)、`DRY_RUN=true`、`ADMIN_BASE_URL`(あるときだけ alerts に記録する)、`SKIP_RECORD_LOG_GROUPS`(カンマ区切り。`ADMIN_BASE_URL` があって空なら warn `ALERTS_SKIP_LOG_GROUPS_EMPTY`)。リージョンは `AWS_REGION`(無ければ `REGION`。どちらも無ければ例外)
|
|
133
|
+
- ハンドラは最初の呼び出しで組み立て、コンテナの中で使い回す(遮断・管理者の一覧のキャッシュを保つ)。組み立てに失敗したら、次の呼び出しでやり直す
|
|
134
|
+
- 既定: `failOnAnyChannelError: true`(一部のチャネルの失敗も Lambda の失敗先へ)、記録の上限 `OPS_NOTIFY_RECORD_LIMITS`(3000 ms・50 件・並列 5)、遮断 `OPS_NOTIFY_RECORD_BREAKER`(3 回・60 秒)、alerts の書き込みの HTTP の時間切れ `OPS_NOTIFY_RECORD_HTTP_TIMEOUT_MS`(2500 ms。記録の上限より短く)
|
|
135
|
+
- Webhook の URL は SSM(SecureString)から最初の送信で読み、実行環境の間はキャッシュする
|
|
136
|
+
- **サブパスは `notify/ops`**(`notify` 本体と分ける)。SSM・SES の SDK(`@aws-sdk/client-ssm`・`@aws-sdk/client-sesv2`。どちらもライブラリの依存)を静的に import するので、`notify` 本体(alerts-cleanup など)は SDK を読まないまま。メールの送信は、ライブラリが既定で SES v2 を使う(`sesSender` に SDK を渡す形は、`createNotifyHandler` を直接使うときのもの)
|
|
137
|
+
- バンドル(どちらも CJS。`src/notify/ops/__tests__/bundle.test.ts` で固定):
|
|
138
|
+
- SDK を外部にする(asanowa: `esbuild --bundle --platform=node --format=cjs --external:@aws-sdk/*`): SDK は `require("@aws-sdk/…")` のまま残り、Lambda の実行環境の SDK(NODE_PATH)で解決される。動的 `import()` は残らない
|
|
139
|
+
- SDK も同梱する(astro: `--external` なし): ライブラリの依存の版の SDK がバンドルに入る
|
|
140
|
+
- alerts の書き込み先は Records のコレクション(`lazyCollection`。最初の書き込みで接続し、使い回す)。管理者の一覧は呼び出しごとに接続して閉じる(`withCollection`)
|
|
141
|
+
|
|
142
|
+
オプション(どれも任意):
|
|
143
|
+
|
|
144
|
+
| オプション | 既定 | 内容 |
|
|
145
|
+
| --------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
146
|
+
| `app` | `PROJECT_NAME` | 通知の `app`。渡せば `PROJECT_NAME` は不要 |
|
|
147
|
+
| `region` | `AWS_REGION` | 通知ストア・SSM・Records・ログ参照のリージョン |
|
|
148
|
+
| `admins` | 宛先は `id`、生のコレクション | `createAdminDirectory` の設定(`recipientFields`・`sortById`・`maxAdmins`・`collection`・`timeout`)と、`lister: (raw) => AdminsLister`(読み取りを権限チェック層で包む)。url・region はハンドラが渡す |
|
|
149
|
+
| `alertsCollection` | 生のコレクション | `(raw) => AlertsCollection`。alerts の書き込みを自分の層で包むアプリが使う |
|
|
150
|
+
| `recordHttpTimeoutMs` | 2500 | alerts の書き込みの HTTP の時間切れ |
|
|
151
|
+
| `log` | `logJson` | 設定の誤りの警告(`ALERTS_SKIP_LOG_GROUPS_EMPTY`)の出力先 |
|
|
152
|
+
| `notify` | 上の既定 | `createNotifyHandler` に渡す設定(`windows`・`rate`・`adminCacheSec`・`failOnAnyChannelError`・`now`・`logger`・`skipRecordEvents`・`recordLimits`・`recordBreaker`) |
|
|
153
|
+
| `overrides` | なし | テスト用の差し替え(`store`・`channels`・`admins`・`alertsCollection`・`ssmClient`・`emailSender`) |
|
|
154
|
+
|
|
155
|
+
アプリごとの書き方(現行の挙動を再現する):
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
// asanowa: 既定のまま(管理者は id、生のコレクション)
|
|
159
|
+
export const handler = createOpsNotifyHandler();
|
|
160
|
+
|
|
161
|
+
// astro: 管理者は email ?? id・id の昇順で、読み取りを権限チェック層に通す
|
|
162
|
+
export const handler = createOpsNotifyHandler({
|
|
163
|
+
admins: {
|
|
164
|
+
recipientFields: ['email', 'id'],
|
|
165
|
+
sortById: true,
|
|
166
|
+
lister: (raw) => authorizedCollection(ADMINS, raw, PERMISSION_MATRIX, { role: 'system' }),
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
アプリに残るもの: エントリの数行(と、権限チェック層を通すアプリの包み方)、バンドルの作成、`terraform/ops-notify` の module ブロック。IAM は `terraform/ops-notify` が付けるもの(notices の UpdateItem、Records の IAM URL、SSM の GetParameter、SES の送信)で足りる。
|
|
172
|
+
|
|
122
173
|
## 配線の注意(各アプリ)
|
|
123
174
|
|
|
124
175
|
- **通知ハンドラのロググループは購読しない**(自分のログで無限ループになる)。失敗は Lambda の失敗先(SNS など)→ 固定の運用者のアドレスへ届ける
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exabugs/dynamodb-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.0",
|
|
4
4
|
"description": "DynamoDB Single-Table Client SDK with MongoDB-like API, Shadow Records, and Lambda implementation for serverless applications",
|
|
5
5
|
"author": "exabugs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -127,6 +127,10 @@
|
|
|
127
127
|
"import": "./dist/notify/index.js",
|
|
128
128
|
"types": "./dist/notify/index.d.ts"
|
|
129
129
|
},
|
|
130
|
+
"./notify/ops": {
|
|
131
|
+
"import": "./dist/notify/ops/index.js",
|
|
132
|
+
"types": "./dist/notify/ops/index.d.ts"
|
|
133
|
+
},
|
|
130
134
|
"./system-changes": {
|
|
131
135
|
"import": "./dist/system-changes/index.js",
|
|
132
136
|
"types": "./dist/system-changes/index.d.ts"
|
|
@@ -173,6 +177,7 @@
|
|
|
173
177
|
"@aws-crypto/sha256-js": "5.2.0",
|
|
174
178
|
"@aws-sdk/client-dynamodb": "3.946.0",
|
|
175
179
|
"@aws-sdk/client-s3": "3.946.0",
|
|
180
|
+
"@aws-sdk/client-sesv2": "3.946.0",
|
|
176
181
|
"@aws-sdk/client-ssm": "3.946.0",
|
|
177
182
|
"@aws-sdk/cloudfront-signer": "3.946.0",
|
|
178
183
|
"@aws-sdk/credential-provider-node": "3.946.0",
|
|
@@ -16,7 +16,7 @@ ADR 0006 の (b)(1.21.0)。asanowa・astro の `infra/modules/ops-notify`
|
|
|
16
16
|
## 前提
|
|
17
17
|
|
|
18
18
|
- 参照は `node_modules` のパス(版は npm の版で固定される。git の `ref` ではない)。例: アプリの infra が `infra/` なら `source = "../node_modules/@exabugs/dynamodb-client/terraform/ops-notify"`、`infra/apps/<app>/` なら `"../../../node_modules/..."`
|
|
19
|
-
- **Lambda の成果物はアプリが作り、`source_file`(バンドル済みの 1 ファイル)と `handler`
|
|
19
|
+
- **Lambda の成果物はアプリが作り、`source_file`(バンドル済みの 1 ファイル)と `handler` で渡す**。エントリはアプリの数行(1.22.0 からは `@exabugs/dynamodb-client/notify/ops` の `createOpsNotifyHandler()`。権限チェック層を通すアプリは包み方を渡す。[docs/NOTIFY.md](../../docs/NOTIFY.md) の「通知 Lambda のエントリ」)なので、ライブラリの dist は使わない
|
|
20
20
|
- zip は `${path.root}/.terraform/archives/notify.zip` に作る(`path.module` を使わない。pnpm の版つきのパスが state に入ると、版を上げるたびに差分が出るため)
|
|
21
21
|
- pnpm では、版を上げた後に `.terraform/modules` を消してから `terraform init` する([../README.md](../README.md) の注意)
|
|
22
22
|
- モジュール全体の `depends_on` は使わない(依存先に変更があると、中のデータソースが apply 時の読み取りになり、既存のリソースが作り直しになる)。依存は、出力の値(`log_group_name` など)で渡す
|
|
@@ -95,7 +95,7 @@ provider "aws" {
|
|
|
95
95
|
## アプリに残るもの
|
|
96
96
|
|
|
97
97
|
- 上の module ブロック(`source`・`providers`・`source_file`・`handler` と設定の値)
|
|
98
|
-
- Lambda のエントリ(`
|
|
98
|
+
- Lambda のエントリ(`createOpsNotifyHandler` を export する数行)とバンドルの作成(esbuild など)
|
|
99
99
|
- 秘密(webhook の URL など)の SSM パラメータの作成
|
|
100
100
|
|
|
101
101
|
## アプリのモジュールからの移行(置き換えを起こさない)
|