@exabugs/dynamodb-client 1.7.0 → 1.8.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 +12 -0
- package/dist/scripts/operations/bulkRecordRepair.js +2 -1
- package/dist/scripts/operations/bulkRecordRepair.js.map +1 -1
- package/dist/server/handler.cjs +51 -31
- package/dist/server/handler.cjs.map +2 -2
- package/dist/server/media-handler.cjs +2 -2
- package/dist/server/media-process-handler-dlq.cjs +50 -30
- package/dist/server/media-process-handler-dlq.cjs.map +2 -2
- package/dist/server/media-process-handler.cjs +50 -30
- package/dist/server/media-process-handler.cjs.map +2 -2
- package/dist/server/operations/find/idQuery.d.ts.map +1 -1
- package/dist/server/operations/find/idQuery.js +57 -39
- package/dist/server/operations/find/idQuery.js.map +1 -1
- package/dist/sharp-layer.zip +0 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.8.0] - 2026-10-12
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- `sort: id` にフィルタを付けた一覧が、ページごとに全レコードを読み直していた(全ページで O(ページ数 × 件数)、更新中は取りこぼしの原因)。ID 順のカーソルで読み進める方式に変更し、全ページを通した読み取りは O(件数)、走査中の更新でも取りこぼし・重複が出ない (#50)
|
|
15
|
+
- `bulkRecordRepair` が既定ソート(updatedAt)で列挙していたため、修復中の更新で取りこぼし・重複が出うる問題を、id 順の列挙に変更して解消 (#51)
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- `sort: id` + フィルタの `nextToken` は、オフセットではなく最後に読んだレコードのキーになった。デプロイをまたいで途中のページネーションを続けると、古いトークンは 400 になる
|
|
20
|
+
- `sort: id` + フィルタは、ページが埋まる(perPage 件)か末尾に達するまで読んでから返す。一致が疎だと 1 リクエストで多くのレコードを読むので、頻繁に使う条件はシャドー(インデックス)の対象フィールドで絞ること。`perPage` 未満なら最終ページ、という前提は従来どおり保たれる
|
|
21
|
+
|
|
10
22
|
## [1.7.0] - 2026-10-12
|
|
11
23
|
|
|
12
24
|
### Fixed
|
|
@@ -32,7 +32,8 @@ export async function repairAllRecords(collection, dryRun) {
|
|
|
32
32
|
* @returns 全レコードの配列
|
|
33
33
|
*/
|
|
34
34
|
async function fetchAllRecords(collection) {
|
|
35
|
-
|
|
35
|
+
// 修復中にレコードが更新されても取りこぼし・重複が出ないよう、不変の id 順で列挙する
|
|
36
|
+
return await collection.find({}).sort({ id: 'asc' }).toArrayAll();
|
|
36
37
|
}
|
|
37
38
|
/**
|
|
38
39
|
* 単一レコードを処理
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bulkRecordRepair.js","sourceRoot":"","sources":["../../../src/scripts/operations/bulkRecordRepair.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,oBAAoB,EACpB,oBAAoB,EACpB,2BAA2B,EAC3B,gBAAgB,GACjB,MAAM,yBAAyB,CAAC;AAEjC;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,UAA4E,EAC5E,MAAe;IAEf,MAAM,KAAK,GAAgB;QACzB,OAAO,EAAE,CAAC;QACV,WAAW,EAAE,CAAC;QACd,QAAQ,EAAE,CAAC;QACX,MAAM,EAAE,CAAC;QACT,MAAM,EAAE,EAAE;KACX,CAAC;IAEF,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;IAC9C,OAAO,CAAC,GAAG,CAAC,SAAS,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;IAEtD,WAAW;IACX,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,UAAU,CAAC,CAAC;IAClD,OAAO,CAAC,GAAG,CAAC,SAAS,OAAO,CAAC,MAAM,UAAU,CAAC,CAAC;IAE/C,WAAW;IACX,KAAK,MAAM,UAAU,IAAI,OAAO,EAAE,CAAC;QACjC,MAAM,aAAa,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;IAC7D,CAAC;IAED,UAAU;IACV,oBAAoB,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,eAAe,CAC5B,UAA4E;IAE5E,OAAO,MAAM,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"bulkRecordRepair.js","sourceRoot":"","sources":["../../../src/scripts/operations/bulkRecordRepair.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,oBAAoB,EACpB,oBAAoB,EACpB,2BAA2B,EAC3B,gBAAgB,GACjB,MAAM,yBAAyB,CAAC;AAEjC;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,UAA4E,EAC5E,MAAe;IAEf,MAAM,KAAK,GAAgB;QACzB,OAAO,EAAE,CAAC;QACV,WAAW,EAAE,CAAC;QACd,QAAQ,EAAE,CAAC;QACX,MAAM,EAAE,CAAC;QACT,MAAM,EAAE,EAAE;KACX,CAAC;IAEF,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;IAC9C,OAAO,CAAC,GAAG,CAAC,SAAS,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;IAEtD,WAAW;IACX,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,UAAU,CAAC,CAAC;IAClD,OAAO,CAAC,GAAG,CAAC,SAAS,OAAO,CAAC,MAAM,UAAU,CAAC,CAAC;IAE/C,WAAW;IACX,KAAK,MAAM,UAAU,IAAI,OAAO,EAAE,CAAC;QACjC,MAAM,aAAa,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;IAC7D,CAAC;IAED,UAAU;IACV,oBAAoB,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,eAAe,CAC5B,UAA4E;IAE5E,8CAA8C;IAC9C,OAAO,MAAM,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC;AACpE,CAAC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,aAAa,CAC1B,UAAkD,EAClD,KAAkB,EAClB,MAAe,EACf,UAA4E;IAE5E,KAAK,CAAC,OAAO,EAAE,CAAC;IAChB,MAAM,QAAQ,GAAG,UAAU,CAAC,EAAE,CAAC;IAE/B,IAAI,CAAC;QACH,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,GAAG,MAAM,oBAAoB,EAAE,CAAC;QAEnF,IAAI,WAAW,EAAE,CAAC;YAChB,KAAK,CAAC,WAAW,EAAE,CAAC;YACpB,OAAO,CAAC,GAAG,CACT,MAAM,KAAK,CAAC,OAAO,YAAY,QAAQ,aAAa,cAAc,WAAW,YAAY,EAAE,CAC5F,CAAC;YAEF,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,2BAA2B,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;gBACxD,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACjB,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;gBAE5B,kBAAkB;gBAClB,MAAM,gBAAgB,EAAE,CAAC;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB;;;;;WAKG;QACH,KAAK,CAAC,MAAM,EAAE,CAAC;QACf,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5E,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;QACzD,OAAO,CAAC,KAAK,CAAC,eAAe,YAAY,EAAE,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC"}
|
package/dist/server/handler.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// @exabugs/dynamodb-client v1.
|
|
2
|
-
// Built: 2026-10-
|
|
1
|
+
// @exabugs/dynamodb-client v1.8.0
|
|
2
|
+
// Built: 2026-10-11T23:03:45.636Z
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
@@ -34659,7 +34659,14 @@ async function executeInQuery(resource, targetIds, sort, requestId) {
|
|
|
34659
34659
|
}
|
|
34660
34660
|
async function executeAllRecordsQuery(resource, sort, perPage, nextToken, parsedFilters, requestId) {
|
|
34661
34661
|
if (parsedFilters.length > 0) {
|
|
34662
|
-
return executeAllRecordsQueryFullScan(
|
|
34662
|
+
return executeAllRecordsQueryFullScan(
|
|
34663
|
+
resource,
|
|
34664
|
+
sort,
|
|
34665
|
+
perPage,
|
|
34666
|
+
nextToken,
|
|
34667
|
+
parsedFilters,
|
|
34668
|
+
requestId
|
|
34669
|
+
);
|
|
34663
34670
|
}
|
|
34664
34671
|
const dbClient2 = getDBClient();
|
|
34665
34672
|
const tableName = getTableName();
|
|
@@ -34731,19 +34738,20 @@ async function executeAllRecordsQueryFullScan(resource, sort, perPage, nextToken
|
|
|
34731
34738
|
const dbClient2 = getDBClient();
|
|
34732
34739
|
const tableName = getTableName();
|
|
34733
34740
|
const costTracker = new CostTracker();
|
|
34734
|
-
const offset = nextToken ? decodeOffsetToken(nextToken) ?? 0 : 0;
|
|
34735
|
-
logger6.debug("Executing full scan query (ID sort)", {
|
|
34736
|
-
requestId,
|
|
34737
|
-
resource,
|
|
34738
|
-
offset
|
|
34739
|
-
});
|
|
34740
|
-
const allRecords = [];
|
|
34741
34741
|
let exclusiveStartKey;
|
|
34742
|
-
|
|
34743
|
-
|
|
34742
|
+
if (nextToken) {
|
|
34743
|
+
const decoded = decodeNextToken(nextToken);
|
|
34744
|
+
exclusiveStartKey = { PK: decoded.PK, SK: decoded.SK };
|
|
34745
|
+
}
|
|
34746
|
+
const page = [];
|
|
34747
|
+
let lastReturned;
|
|
34748
|
+
let hasNextPage = false;
|
|
34749
|
+
let scanned = 0;
|
|
34744
34750
|
let iterations = 0;
|
|
34745
|
-
|
|
34746
|
-
|
|
34751
|
+
let previousKeySignature;
|
|
34752
|
+
let chunkSize = SCAN_CHUNK_INITIAL;
|
|
34753
|
+
scan: while (true) {
|
|
34754
|
+
if (++iterations > SCAN_MAX_ITERATIONS) {
|
|
34747
34755
|
logger6.error("executeAllRecordsQueryFullScan: \u6700\u5927\u53CD\u5FA9\u56DE\u6570\u3092\u8D85\u904E\u3057\u305F\u305F\u3081\u4E2D\u65AD", {
|
|
34748
34756
|
requestId,
|
|
34749
34757
|
resource,
|
|
@@ -34761,8 +34769,7 @@ async function executeAllRecordsQueryFullScan(resource, sort, perPage, nextToken
|
|
|
34761
34769
|
":skPrefix": "id#"
|
|
34762
34770
|
},
|
|
34763
34771
|
ScanIndexForward: sort.order === "ASC",
|
|
34764
|
-
Limit:
|
|
34765
|
-
// 大きめの Limit でループ回数を最小化
|
|
34772
|
+
Limit: chunkSize,
|
|
34766
34773
|
ExclusiveStartKey: exclusiveStartKey,
|
|
34767
34774
|
ConsistentRead: true,
|
|
34768
34775
|
ReturnConsumedCapacity: "TOTAL"
|
|
@@ -34771,10 +34778,24 @@ async function executeAllRecordsQueryFullScan(resource, sort, perPage, nextToken
|
|
|
34771
34778
|
"Query"
|
|
34772
34779
|
);
|
|
34773
34780
|
costTracker.add(queryResult.ConsumedCapacity);
|
|
34774
|
-
|
|
34781
|
+
for (const row of queryResult.Items || []) {
|
|
34782
|
+
scanned++;
|
|
34783
|
+
if (!matchesAllFilters(extractCleanRecord(row), parsedFilters)) {
|
|
34784
|
+
continue;
|
|
34785
|
+
}
|
|
34786
|
+
if (page.length >= perPage) {
|
|
34787
|
+
hasNextPage = true;
|
|
34788
|
+
break scan;
|
|
34789
|
+
}
|
|
34790
|
+
page.push(extractCleanRecord(row));
|
|
34791
|
+
lastReturned = { PK: row.PK, SK: row.SK };
|
|
34792
|
+
}
|
|
34775
34793
|
const nextKey = queryResult.LastEvaluatedKey;
|
|
34776
|
-
|
|
34777
|
-
|
|
34794
|
+
if (!nextKey) {
|
|
34795
|
+
break;
|
|
34796
|
+
}
|
|
34797
|
+
const nextKeySignature = `${nextKey.PK}#${nextKey.SK}`;
|
|
34798
|
+
if (nextKeySignature === previousKeySignature) {
|
|
34778
34799
|
logger6.error("executeAllRecordsQueryFullScan: \u540C\u4E00\u306ELastEvaluatedKey\u304C\u8FD4\u5374\u3055\u308C\u305F\u305F\u3081\u4E2D\u65AD", {
|
|
34779
34800
|
requestId,
|
|
34780
34801
|
resource,
|
|
@@ -34784,28 +34805,24 @@ async function executeAllRecordsQueryFullScan(resource, sort, perPage, nextToken
|
|
|
34784
34805
|
}
|
|
34785
34806
|
previousKeySignature = nextKeySignature;
|
|
34786
34807
|
exclusiveStartKey = nextKey;
|
|
34787
|
-
|
|
34788
|
-
|
|
34789
|
-
|
|
34790
|
-
|
|
34791
|
-
const hasNextPage = offset + perPage < items.length;
|
|
34792
|
-
const nextTokenValue = hasNextPage ? encodeOffsetToken(offset + perPage) : void 0;
|
|
34793
|
-
logger6.info("ID full scan query succeeded", {
|
|
34808
|
+
chunkSize = Math.min(chunkSize * 2, SCAN_CHUNK_MAX);
|
|
34809
|
+
}
|
|
34810
|
+
const nextTokenValue = hasNextPage && lastReturned ? encodeNextToken(lastReturned.PK, lastReturned.SK) : void 0;
|
|
34811
|
+
logger6.info("ID filtered scan query succeeded", {
|
|
34794
34812
|
requestId,
|
|
34795
34813
|
resource,
|
|
34796
|
-
|
|
34797
|
-
offset,
|
|
34814
|
+
scanned,
|
|
34798
34815
|
returned: page.length,
|
|
34799
34816
|
hasNextPage
|
|
34800
34817
|
});
|
|
34801
34818
|
return {
|
|
34802
34819
|
items: page,
|
|
34803
|
-
pageInfo: { hasNextPage, hasPreviousPage:
|
|
34820
|
+
pageInfo: { hasNextPage, hasPreviousPage: !!nextToken },
|
|
34804
34821
|
...nextTokenValue && { nextToken: nextTokenValue },
|
|
34805
34822
|
consumedCapacity: costTracker.getAggregated()
|
|
34806
34823
|
};
|
|
34807
34824
|
}
|
|
34808
|
-
var import_lib_dynamodb2, logger6;
|
|
34825
|
+
var import_lib_dynamodb2, logger6, SCAN_CHUNK_INITIAL, SCAN_CHUNK_MAX, SCAN_MAX_ITERATIONS;
|
|
34809
34826
|
var init_idQuery = __esm({
|
|
34810
34827
|
"src/server/operations/find/idQuery.ts"() {
|
|
34811
34828
|
"use strict";
|
|
@@ -34823,6 +34840,9 @@ var init_idQuery = __esm({
|
|
|
34823
34840
|
__name(executeSpecificIdQuery, "executeSpecificIdQuery");
|
|
34824
34841
|
__name(executeInQuery, "executeInQuery");
|
|
34825
34842
|
__name(executeAllRecordsQuery, "executeAllRecordsQuery");
|
|
34843
|
+
SCAN_CHUNK_INITIAL = 100;
|
|
34844
|
+
SCAN_CHUNK_MAX = 1e3;
|
|
34845
|
+
SCAN_MAX_ITERATIONS = 1e4;
|
|
34826
34846
|
__name(executeAllRecordsQueryFullScan, "executeAllRecordsQueryFullScan");
|
|
34827
34847
|
}
|
|
34828
34848
|
});
|
|
@@ -38784,7 +38804,7 @@ async function handler(event) {
|
|
|
38784
38804
|
return createCorsResponse(HTTP_STATUS.OK);
|
|
38785
38805
|
}
|
|
38786
38806
|
if (event.requestContext.http.method === "GET" && event.requestContext.http.path === "/version") {
|
|
38787
|
-
const version = "1.
|
|
38807
|
+
const version = "1.8.0";
|
|
38788
38808
|
return createSuccessResponse({ version, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, requestId);
|
|
38789
38809
|
}
|
|
38790
38810
|
if (event.requestContext.http.method !== "POST") {
|