@exabugs/dynamodb-client 1.14.0 → 1.15.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 +34 -0
- 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 +55 -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 +1143 -3613
- package/dist/server/media-process-handler-dlq.cjs.map +4 -4
- package/dist/server/media-process-handler.cjs +1194 -3679
- package/dist/server/media-process-handler.cjs.map +4 -4
- package/docs/DYNAMODB_ACCESS.md +17 -9
- package/package.json +1 -1
- package/terraform/media/README.md +5 -2
- package/terraform/media/main.tf +35 -27
- package/terraform/media/variables.tf +36 -9
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.15.0] - 2026-10-18
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **terraform/media**: 必須の変数 `records_iam_function_url`(Records の IAM 版 Function URL。本体の出力 `iam_function_url`)と `records_iam_alias_arn`(`lambda:InvokeFunctionUrl` の許可の対象。本体の出力 `iam_alias_arn`)
|
|
15
|
+
- **`src/server/media/recordsClient.ts`**: media の Lambda が Records を呼ぶ共通部品(`DynamoClient` の生成と再利用、timeout 15 秒、`findMediaRecord` / `writeMediaRecord`)
|
|
16
|
+
- 時限つきの失敗するテスト: 版が 1.16.0 以上のとき、`terraform/media/variables.tf` に廃止した変数が残っていると失敗する(ADR 0005、issue #94)
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- **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 に乗る
|
|
21
|
+
- DynamoDB アクセスの許可リストから、media の負債 4 件を削除した。`notices` は例外ではなく原則の対象外と明記した
|
|
22
|
+
- **IAM**: media の 2 つの Lambda のロールに `lambda:InvokeFunctionUrl` と `lambda:InvokeFunction` の両方を許可する。AWS のドキュメント(Function URL のアクセス制御)に「2025 年 10 月以降、新しい Function URL は両方の権限を要求する」とある
|
|
23
|
+
- **process-handler-dlq** の Lambda の timeout を 30 秒から 60 秒に上げた(変数 `process_handler_dlq_timeout`、既定 60。Records の読み 15 秒 + 書き 15 秒のクライアント timeout が収まるように)
|
|
24
|
+
|
|
25
|
+
- **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 秒のままだとマッピングが拒否される)
|
|
26
|
+
|
|
27
|
+
### Deprecated
|
|
28
|
+
|
|
29
|
+
- **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)
|
|
30
|
+
|
|
31
|
+
### 移行の手順
|
|
32
|
+
|
|
33
|
+
1. Records は先で、変更なし(IAM 版 URL とエイリアスは既存の出力)
|
|
34
|
+
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`
|
|
35
|
+
3. `table_name` / `table_arn` / `shadow_*` は、渡さなくてよい(渡しても無視される)。1.16.0 までに消す
|
|
36
|
+
4. dev で先に確かめる(画像・非画像のアップロードで `completed`、再アップロードで上書きされないこと、Records 失敗時の再試行と DLQ)→ prd
|
|
37
|
+
|
|
38
|
+
## [1.14.1] - 2026-10-18
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- **terraform/media**: media の sharp レイヤー(`aws_lambda_layer_version.sharp`)が、ライブラリの版を上げるたびに置き換わる問題を直した。pnpm では `filename` のパスにライブラリの版が入る(`node_modules/.pnpm/@exabugs+dynamodb-client@<版>_…`)ため、版を上げるだけで `filename`(ForceNew)が変わり、zip の中身が同じでもレイヤーが置き換わっていた。`lifecycle.ignore_changes = [filename]` を足した。中身(`source_code_hash`)が変わったときだけ置き換わる
|
|
43
|
+
|
|
10
44
|
## [1.14.0] - 2026-10-18
|
|
11
45
|
|
|
12
46
|
### Added
|
package/dist/server/handler.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// @exabugs/dynamodb-client v1.
|
|
2
|
-
// Built: 2026-10-
|
|
1
|
+
// @exabugs/dynamodb-client v1.15.0
|
|
2
|
+
// Built: 2026-10-18T10:11:26.658Z
|
|
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.15.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;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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
* 失敗は握りつぶさず、そのまま例外にする(呼び出し側が再スローして、Lambda の非同期再試行と
|
|
7
|
+
* DLQ に乗せる)。
|
|
8
|
+
*/
|
|
9
|
+
import { DynamoClient } from '../../client/index.iam.js';
|
|
10
|
+
/** Records へのリクエストの timeout(ms)。Records の Lambda の timeout(30 秒)より短くする */
|
|
11
|
+
export const RECORDS_REQUEST_TIMEOUT_MS = 15_000;
|
|
12
|
+
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
|
+
}
|
|
38
|
+
/** media レコードを 1 件読む。無ければ undefined */
|
|
39
|
+
export async function findMediaRecord(fileId) {
|
|
40
|
+
const records = await (await getMediaCollection()).findMany([fileId]);
|
|
41
|
+
return records[0];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* media レコードを作成/更新する(冪等な upsert)。
|
|
45
|
+
* filter に id を入れること(入れないと別の ID が採番される)。
|
|
46
|
+
*/
|
|
47
|
+
export async function writeMediaRecord(fileId, fields) {
|
|
48
|
+
const now = new Date().toISOString();
|
|
49
|
+
await (await getMediaCollection()).updateOne({ id: fileId }, { $set: { ...fields, updatedAt: now }, $setOnInsert: { createdAt: now } }, { upsert: true });
|
|
50
|
+
}
|
|
51
|
+
/** テスト用: キャッシュしたクライアントを捨てる */
|
|
52
|
+
export function resetRecordsClientForTest() {
|
|
53
|
+
mediaCollection = undefined;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=recordsClient.js.map
|
|
@@ -0,0 +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"}
|