@exabugs/dynamodb-client 1.15.0 → 1.17.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +2 -0
  3. package/dist/client/index.iam.d.ts +1 -0
  4. package/dist/client/index.iam.d.ts.map +1 -1
  5. package/dist/client/index.iam.js +1 -0
  6. package/dist/client/index.iam.js.map +1 -1
  7. package/dist/client/recordsClient.d.ts +51 -0
  8. package/dist/client/recordsClient.d.ts.map +1 -0
  9. package/dist/client/recordsClient.js +64 -0
  10. package/dist/client/recordsClient.js.map +1 -0
  11. package/dist/notify/admins.d.ts +44 -0
  12. package/dist/notify/admins.d.ts.map +1 -0
  13. package/dist/notify/admins.js +55 -0
  14. package/dist/notify/admins.js.map +1 -0
  15. package/dist/notify/channels/slack.d.ts +8 -0
  16. package/dist/notify/channels/slack.d.ts.map +1 -1
  17. package/dist/notify/channels/slack.js +27 -19
  18. package/dist/notify/channels/slack.js.map +1 -1
  19. package/dist/notify/index.d.ts +2 -0
  20. package/dist/notify/index.d.ts.map +1 -1
  21. package/dist/notify/index.js +1 -0
  22. package/dist/notify/index.js.map +1 -1
  23. package/dist/server/handler.cjs +3 -3
  24. package/dist/server/media/recordsClient.d.ts.map +1 -1
  25. package/dist/server/media/recordsClient.js +10 -30
  26. package/dist/server/media/recordsClient.js.map +1 -1
  27. package/dist/server/media-handler.cjs +2 -2
  28. package/dist/server/media-process-handler-dlq.cjs +46 -25
  29. package/dist/server/media-process-handler-dlq.cjs.map +4 -4
  30. package/dist/server/media-process-handler.cjs +46 -25
  31. package/dist/server/media-process-handler.cjs.map +4 -4
  32. package/dist/system-changes/handler.d.ts +47 -0
  33. package/dist/system-changes/handler.d.ts.map +1 -0
  34. package/dist/system-changes/handler.js +74 -0
  35. package/dist/system-changes/handler.js.map +1 -0
  36. package/dist/system-changes/index.d.ts +15 -0
  37. package/dist/system-changes/index.d.ts.map +1 -0
  38. package/dist/system-changes/index.js +12 -0
  39. package/dist/system-changes/index.js.map +1 -0
  40. package/dist/system-changes/record.d.ts +92 -0
  41. package/dist/system-changes/record.d.ts.map +1 -0
  42. package/dist/system-changes/record.js +198 -0
  43. package/dist/system-changes/record.js.map +1 -0
  44. package/dist/system-changes/recorder.d.ts +104 -0
  45. package/dist/system-changes/recorder.d.ts.map +1 -0
  46. package/dist/system-changes/recorder.js +184 -0
  47. package/dist/system-changes/recorder.js.map +1 -0
  48. package/dist/system-changes/slack.d.ts +19 -0
  49. package/dist/system-changes/slack.d.ts.map +1 -0
  50. package/dist/system-changes/slack.js +124 -0
  51. package/dist/system-changes/slack.js.map +1 -0
  52. package/docs/RECORDS_CLIENT.md +47 -0
  53. package/docs/SYSTEM_CHANGES.md +117 -0
  54. package/package.json +8 -2
  55. package/terraform/media/README.md +1 -2
  56. package/terraform/media/variables.tf +0 -37
package/CHANGELOG.md CHANGED
@@ -7,6 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.17.0] - 2026-10-24
11
+
12
+ ### Added
13
+
14
+ - **`@exabugs/dynamodb-client/system-changes`**(ADR 0006 の段階 2 = (a)): システム変更履歴(`systemChanges`)の記録と Slack 通知。asanowa・astro に複製されていたロジックを 1 つにした。使い方は `docs/SYSTEM_CHANGES.md`
15
+ - `createSystemChangeHandler(options?)`: 記録 Lambda のハンドラのファクトリ。呼び出しごとに `withCollection` で Records に接続する。アプリ名・Records の URL・Slack の設定(SSM の名前、環境名、管理画面の URL、開始も送るか)は、オプションか環境変数(`PROJECT_NAME` / `RECORDS_IAM_URL` / `SLACK_WEBHOOK_PARAM` / `ENV_NAME` / `ADMIN_BASE_URL` / `NOTIFY_STARTED`)。書き込み先の包み方(`collection`。権限チェック層を通すアプリ用)、ログ(`log`)、SSM のクライアントを注入できる
16
+ - `recordSystemChange` / `recordAndNotify` / `systemChangesCollectionOf`、純粋な関数 `parseSystemChangeInput` / `toSystemChangeRecord` / `decideRunWrite` / `formatSystemChangeMessage` / `formatDuration` / `lambdaReleaseNames` / `shouldNotify`、定数・型
17
+ - Slack の文言は asanowa の版に揃えた(ADR 0006 の決定)。`details.lambdaReleases` の取り出しは公開関数 `lambdaReleaseNames` の 1 つ
18
+ - 既定の書き込み先は、`changeId` の読み取りを `find({ id }, { sort: { id: 'asc' }, limit: 1 })`(Records の ID の経路。PK/SK の Query 1 回) で行う(`findOne` の 404 で Records が ITEM_NOT_FOUND の warn を出さない)
19
+ - **`postSlackText(url, text, options?)`**(`src/notify/channels/slack.ts`。内部用、notify の index からは出さない): `createSlackChannel` の送信処理を切り出したもの。`system-changes` が使う
20
+
21
+ ### Changed
22
+
23
+ - `createSlackChannel` の送信は、切り出した `postSlackText` を呼ぶ。挙動は同じ(URL の接頭辞の確認、timeout 既定 5 秒、URL を出さないエラー)。違いは、`fetchImpl` を省略したときの `fetch` を、チャネルの作成時ではなく送信のたびに読むことだけ
24
+
25
+ ## [1.16.0] - 2026-10-20
26
+
27
+ ### Added
28
+
29
+ - **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`
30
+ - **`createAdminDirectory`**(`@exabugs/dynamodb-client/notify`): `admins` の全件から通知の宛先を返す `AdminDirectory`。宛先のフィールドは `recipientFields`(既定 `['id']`。`['email','id']` で `email ?? id`)。あわせて `normalizeEmail` / `emailsOf` を公開
31
+
32
+ ### Changed
33
+
34
+ - media の Records クライアント(`src/server/media/recordsClient.ts`)は、公開ヘルパー `lazyCollection` を使う。挙動は同じ(`connect()`、timeout 15 秒、`RECORDS_IAM_URL` の扱い、コールドスタートで 1 回の接続と再利用)。並行した初回の呼び出しが 1 つの接続を共有するようになった
35
+
36
+ ### Removed
37
+
38
+ - **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 以降、渡しても無視されていた)
39
+
10
40
  ## [1.15.0] - 2026-10-18
11
41
 
12
42
  ### Added
package/README.md CHANGED
@@ -196,7 +196,9 @@ 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`)
201
+ - **[システム変更履歴](docs/SYSTEM_CHANGES.md)** - インフラの変更・リリースを `systemChanges` に記録し、Slack に送る部品と記録 Lambda のハンドラ(`@exabugs/dynamodb-client/system-changes`)
200
202
  - **[Deployment](docs/DEPLOYMENT.md)** - Production deployment guide
201
203
  - **[Terraform Modules](terraform/README.md)** - Infrastructure as Code
202
204
 
@@ -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"}
@@ -25,4 +25,5 @@ export class DynamoClient extends BaseDynamoClient {
25
25
  });
26
26
  }
27
27
  }
28
+ export { createRecordsClient, lazyCollection, withCollection, } from './recordsClient.js';
28
29
  //# sourceMappingURL=index.iam.js.map
@@ -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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"}
@@ -8,5 +8,13 @@ export interface SlackOptions {
8
8
  fetchImpl?: typeof fetch;
9
9
  timeoutMs?: number;
10
10
  }
11
+ /** `postSlackText` のオプション(`createSlackChannel` と共通) */
12
+ export type SlackPostOptions = Pick<SlackOptions, 'urlPrefix' | 'fetchImpl' | 'timeoutMs'>;
13
+ /**
14
+ * 文字列(mrkdwn)を 1 通、Webhook に送る。失敗は例外(メッセージに URL を出さない)。
15
+ * `createSlackChannel` の送信と同じ処理(URL の接頭辞の確認、timeout 既定 5 秒)。
16
+ * Notice を経由しない直接の投稿(システム変更履歴など)に使う
17
+ */
18
+ export declare function postSlackText(url: string, text: string, options?: SlackPostOptions): Promise<void>;
11
19
  export declare function createSlackChannel(options: SlackOptions): Channel;
12
20
  //# sourceMappingURL=slack.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../../src/notify/channels/slack.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAQnD,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAKnD;AAED,MAAM,WAAW,YAAY;IAC3B,0CAA0C;IAC1C,aAAa,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACrC,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CA0BjE"}
1
+ {"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../../src/notify/channels/slack.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAQnD,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAKnD;AAED,MAAM,WAAW,YAAY;IAC3B,0CAA0C;IAC1C,aAAa,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACrC,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,uDAAuD;AACvD,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,YAAY,EAAE,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC,CAAC;AAE3F;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAkBf;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAUjE"}
@@ -13,8 +13,33 @@ export function slackText(notices) {
13
13
  parts.push(`…ほか ${notices.length - shown.length} 件`);
14
14
  return parts.join('\n\n');
15
15
  }
16
- export function createSlackChannel(options) {
16
+ /**
17
+ * 文字列(mrkdwn)を 1 通、Webhook に送る。失敗は例外(メッセージに URL を出さない)。
18
+ * `createSlackChannel` の送信と同じ処理(URL の接頭辞の確認、timeout 既定 5 秒)。
19
+ * Notice を経由しない直接の投稿(システム変更履歴など)に使う
20
+ */
21
+ export async function postSlackText(url, text, options = {}) {
22
+ if (!url.startsWith(options.urlPrefix ?? 'https://hooks.slack.com/')) {
23
+ throw new Error('Slack の Webhook の URL が不正です(許可した接頭辞で始まっていません)');
24
+ }
17
25
  const doFetch = options.fetchImpl ?? fetch;
26
+ let res;
27
+ try {
28
+ res = await doFetch(url, {
29
+ method: 'POST',
30
+ headers: { 'Content-Type': 'application/json' },
31
+ body: JSON.stringify({ text }),
32
+ signal: AbortSignal.timeout(options.timeoutMs ?? 5000),
33
+ });
34
+ }
35
+ catch (e) {
36
+ // fetch のエラーには URL(秘密)が入ることがあるので、原因の種類だけを残す
37
+ throw new Error(`Slack への送信に失敗しました (${e instanceof Error ? e.name : 'error'})`);
38
+ }
39
+ if (!res.ok)
40
+ throw new Error(`Slack への送信に失敗しました (HTTP ${res.status})`);
41
+ }
42
+ export function createSlackChannel(options) {
18
43
  return {
19
44
  name: 'slack',
20
45
  needsRecipients: false,
@@ -22,24 +47,7 @@ export function createSlackChannel(options) {
22
47
  if (notices.length === 0)
23
48
  return;
24
49
  const url = await options.getWebhookUrl();
25
- if (!url.startsWith(options.urlPrefix ?? 'https://hooks.slack.com/')) {
26
- throw new Error('Slack の Webhook の URL が不正です(許可した接頭辞で始まっていません)');
27
- }
28
- let res;
29
- try {
30
- res = await doFetch(url, {
31
- method: 'POST',
32
- headers: { 'Content-Type': 'application/json' },
33
- body: JSON.stringify({ text: slackText(notices) }),
34
- signal: AbortSignal.timeout(options.timeoutMs ?? 5000),
35
- });
36
- }
37
- catch (e) {
38
- // fetch のエラーには URL(秘密)が入ることがあるので、原因の種類だけを残す
39
- throw new Error(`Slack への送信に失敗しました (${e instanceof Error ? e.name : 'error'})`);
40
- }
41
- if (!res.ok)
42
- throw new Error(`Slack への送信に失敗しました (HTTP ${res.status})`);
50
+ await postSlackText(url, slackText(notices), options);
43
51
  },
44
52
  };
45
53
  }
@@ -1 +1 @@
1
- {"version":3,"file":"slack.js","sourceRoot":"","sources":["../../../src/notify/channels/slack.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGvD,MAAM,eAAe,GAAG,EAAE,CAAC;AAE3B,kCAAkC;AAClC,MAAM,GAAG,GAAG,CAAC,CAAS,EAAU,EAAE,CAChC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAEvE,MAAM,UAAU,SAAS,CAAC,OAAiB;IACzC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC;IAChD,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACvF,IAAI,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,OAAO,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;IACxF,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC;AAWD,MAAM,UAAU,kBAAkB,CAAC,OAAqB;IACtD,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,IAAI,KAAK,CAAC;IAC3C,OAAO;QACL,IAAI,EAAE,OAAO;QACb,eAAe,EAAE,KAAK;QACtB,KAAK,CAAC,IAAI,CAAC,OAAO;YAChB,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO;YACjC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,aAAa,EAAE,CAAC;YAC1C,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,IAAI,0BAA0B,CAAC,EAAE,CAAC;gBACrE,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;YACnE,CAAC;YACD,IAAI,GAAa,CAAC;YAClB,IAAI,CAAC;gBACH,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE;oBACvB,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;oBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;oBAClD,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC;iBACvD,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,4CAA4C;gBAC5C,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC;YAClF,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;QACzE,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"slack.js","sourceRoot":"","sources":["../../../src/notify/channels/slack.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGvD,MAAM,eAAe,GAAG,EAAE,CAAC;AAE3B,kCAAkC;AAClC,MAAM,GAAG,GAAG,CAAC,CAAS,EAAU,EAAE,CAChC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAEvE,MAAM,UAAU,SAAS,CAAC,OAAiB;IACzC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC;IAChD,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACvF,IAAI,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,OAAO,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;IACxF,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC;AAcD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,GAAW,EACX,IAAY,EACZ,UAA4B,EAAE;IAE9B,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,IAAI,0BAA0B,CAAC,EAAE,CAAC;QACrE,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,CAAC;IACD,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,IAAI,KAAK,CAAC;IAC3C,IAAI,GAAa,CAAC;IAClB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE;YACvB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC;YAC9B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC;SACvD,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,4CAA4C;QAC5C,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC;IAClF,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,OAAqB;IACtD,OAAO;QACL,IAAI,EAAE,OAAO;QACb,eAAe,EAAE,KAAK;QACtB,KAAK,CAAC,IAAI,CAAC,OAAO;YAChB,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO;YACjC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,aAAa,EAAE,CAAC;YAC1C,MAAM,aAAa,CAAC,GAAG,EAAE,SAAS,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;QACxD,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -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"}
@@ -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';
@@ -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"}
@@ -1,5 +1,5 @@
1
- // @exabugs/dynamodb-client v1.15.0
2
- // Built: 2026-10-18T10:11:26.658Z
1
+ // @exabugs/dynamodb-client v1.17.0
2
+ // Built: 2026-10-24T03:15:07.224Z
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.15.0";
38983
+ const version = "1.17.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":"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;AAiCjD,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"}
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"}
@@ -2,42 +2,22 @@
2
2
  * media の Lambda(process-handler / process-handler-dlq)が Records を呼ぶための共通部品
3
3
  *
4
4
  * DynamoDB には直接触れず、Records の IAM 版 Function URL を `DynamoClient`(SigV4 署名)で
5
- * 呼ぶ(詳細: docs/adr/0005-writes-through-records.md)。
5
+ * 呼ぶ(詳細: docs/adr/0005-writes-through-records.md)。接続の定型は公開ヘルパー
6
+ * `lazyCollection`(src/client/recordsClient.ts。ADR 0006)に任せる。
6
7
  * 失敗は握りつぶさず、そのまま例外にする(呼び出し側が再スローして、Lambda の非同期再試行と
7
8
  * DLQ に乗せる)。
8
9
  */
9
- import { DynamoClient } from '../../client/index.iam.js';
10
+ import { lazyCollection } from '../../client/recordsClient.js';
10
11
  /** Records へのリクエストの timeout(ms)。Records の Lambda の timeout(30 秒)より短くする */
11
12
  export const RECORDS_REQUEST_TIMEOUT_MS = 15_000;
12
13
  const MEDIA_COLLECTION = 'media';
13
- let mediaCollection;
14
- /**
15
- * コールドスタートで 1 回だけ生成・接続して再利用する。
16
- * `db()` は `connect()` の前に呼ぶと例外になるため、接続を待つ(IAM では接続は状態の更新だけ)。
17
- * 失敗したらキャッシュせず、次の呼び出しでやり直す。
18
- */
19
- async function getMediaCollection() {
20
- if (!mediaCollection) {
21
- const endpoint = process.env.RECORDS_IAM_URL;
22
- if (!endpoint) {
23
- throw new Error('RECORDS_IAM_URL environment variable is not set');
24
- }
25
- const region = process.env.AWS_REGION ?? process.env.REGION;
26
- if (!region) {
27
- throw new Error('AWS_REGION environment variable is not set');
28
- }
29
- const client = new DynamoClient(endpoint, {
30
- auth: { region },
31
- timeout: RECORDS_REQUEST_TIMEOUT_MS,
32
- });
33
- await client.connect();
34
- mediaCollection = client.db().collection(MEDIA_COLLECTION);
35
- }
36
- return mediaCollection;
37
- }
14
+ /** コールドスタートで 1 回だけ接続して再利用する。環境変数は初回の使用時に読む */
15
+ const mediaCollection = lazyCollection(MEDIA_COLLECTION, {
16
+ timeout: RECORDS_REQUEST_TIMEOUT_MS,
17
+ });
38
18
  /** media レコードを 1 件読む。無ければ undefined */
39
19
  export async function findMediaRecord(fileId) {
40
- const records = await (await getMediaCollection()).findMany([fileId]);
20
+ const records = await (await mediaCollection()).findMany([fileId]);
41
21
  return records[0];
42
22
  }
43
23
  /**
@@ -46,10 +26,10 @@ export async function findMediaRecord(fileId) {
46
26
  */
47
27
  export async function writeMediaRecord(fileId, fields) {
48
28
  const now = new Date().toISOString();
49
- await (await getMediaCollection()).updateOne({ id: fileId }, { $set: { ...fields, updatedAt: now }, $setOnInsert: { createdAt: now } }, { upsert: true });
29
+ await (await mediaCollection()).updateOne({ id: fileId }, { $set: { ...fields, updatedAt: now }, $setOnInsert: { createdAt: now } }, { upsert: true });
50
30
  }
51
31
  /** テスト用: キャッシュしたクライアントを捨てる */
52
32
  export function resetRecordsClientForTest() {
53
- mediaCollection = undefined;
33
+ mediaCollection.reset();
54
34
  }
55
35
  //# sourceMappingURL=recordsClient.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"recordsClient.js","sourceRoot":"","sources":["../../../src/server/media/recordsClient.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAIzD,0EAA0E;AAC1E,MAAM,CAAC,MAAM,0BAA0B,GAAG,MAAM,CAAC;AAEjD,MAAM,gBAAgB,GAAG,OAAO,CAAC;AAIjC,IAAI,eAA4C,CAAC;AAEjD;;;;GAIG;AACH,KAAK,UAAU,kBAAkB;IAC/B,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;QAC7C,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;QACrE,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;QAC5D,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;QAChE,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,QAAQ,EAAE;YACxC,IAAI,EAAE,EAAE,MAAM,EAAE;YAChB,OAAO,EAAE,0BAA0B;SACpC,CAAC,CAAC;QACH,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;QACvB,eAAe,GAAG,MAAM,CAAC,EAAE,EAAE,CAAC,UAAU,CAA+B,gBAAgB,CAAC,CAAC;IAC3F,CAAC;IACD,OAAO,eAAe,CAAC;AACzB,CAAC;AAED,uCAAuC;AACvC,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAc;IAClD,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,kBAAkB,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IACtE,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,kBAAkB,EAAE,CAC3B,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,GAAG,SAAS,CAAC;AAC9B,CAAC"}
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"}
@@ -1,5 +1,5 @@
1
- // @exabugs/dynamodb-client v1.15.0
2
- // Built: 2026-10-18T10:11:26.658Z
1
+ // @exabugs/dynamodb-client v1.17.0
2
+ // Built: 2026-10-24T03:15:07.224Z
3
3
  "use strict";
4
4
  var __create = Object.create;
5
5
  var __defProp = Object.defineProperty;