@exabugs/dynamodb-client 1.6.2 → 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 +25 -0
- package/dist/scripts/operations/bulkRecordRepair.js +2 -1
- package/dist/scripts/operations/bulkRecordRepair.js.map +1 -1
- package/dist/server/handler.cjs +174 -76
- package/dist/server/handler.cjs.map +4 -4
- package/dist/server/media-handler.cjs +2 -2
- package/dist/server/media-process-handler-dlq.cjs +179 -81
- package/dist/server/media-process-handler-dlq.cjs.map +4 -4
- package/dist/server/media-process-handler.cjs +179 -81
- package/dist/server/media-process-handler.cjs.map +4 -4
- 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/server/operations/find/utils.js +2 -2
- package/dist/server/operations/find/utils.js.map +1 -1
- package/dist/server/operations/updateMany.d.ts.map +1 -1
- package/dist/server/operations/updateMany.js +6 -76
- package/dist/server/operations/updateMany.js.map +1 -1
- package/dist/server/operations/updateOne.d.ts.map +1 -1
- package/dist/server/operations/updateOne.js +9 -1
- package/dist/server/operations/updateOne.js.map +1 -1
- package/dist/server/utils/mergePatch.d.ts +8 -0
- package/dist/server/utils/mergePatch.d.ts.map +1 -0
- package/dist/server/utils/mergePatch.js +127 -0
- package/dist/server/utils/mergePatch.js.map +1 -0
- package/dist/server/utils/timestamps.d.ts.map +1 -1
- package/dist/server/utils/timestamps.js +2 -1
- package/dist/server/utils/timestamps.js.map +1 -1
- package/dist/sharp-layer.zip +0 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,31 @@ 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
|
+
|
|
22
|
+
## [1.7.0] - 2026-10-12
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- 更新のドット記法パス(例: `sections.1.t`)が配列をオブジェクトに壊していた。配列は要素単位で更新し、範囲外・非数値の添字は 400 で拒否する。`__proto__` を含むパスも拒否する (#48)
|
|
27
|
+
- 更新時に `updatedAt` が変わらなかった(既存の値が優先されていた)。パッチ側に付与して毎回更新する。クライアントが値を指定した場合はそれを尊重し、`null` / `undefined` は未指定として扱う (#48)
|
|
28
|
+
- 解釈できないフィルタ指定が 500 (`CONFIG_ERROR`) になっていた。400 (`INVALID_FILTER`) を返す (#48)
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- **`updatedAt` を明示しない更新で、値が毎回変わる**ようになった。`updatedAt` 順の一覧は更新のたびに並びが変わり、`updatedAt` のシャドーの再作成により 1 レコードあたりの書き込み項目が増える
|
|
33
|
+
- ドット記法で、存在しない(またはオブジェクト以外の)親に対する `null`(削除)は何もしなくなった(従来は `{a: null}` のような残骸が残っていた)
|
|
34
|
+
|
|
10
35
|
## [1.6.2] - 2026-10-11
|
|
11
36
|
|
|
12
37
|
### Security
|
|
@@ -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;
|
|
@@ -73,7 +73,7 @@ var init_types = __esm({
|
|
|
73
73
|
function isAppError(error2) {
|
|
74
74
|
return error2 instanceof AppError;
|
|
75
75
|
}
|
|
76
|
-
var AppError, AuthError, ConfigError, InvalidTokenError, ItemNotFoundError;
|
|
76
|
+
var AppError, AuthError, ConfigError, InvalidFilterError, InvalidTokenError, ItemNotFoundError;
|
|
77
77
|
var init_errors = __esm({
|
|
78
78
|
"src/shared/errors/index.ts"() {
|
|
79
79
|
"use strict";
|
|
@@ -117,6 +117,14 @@ var init_errors = __esm({
|
|
|
117
117
|
super("CONFIG_ERROR" /* CONFIG_ERROR */, message, 500, details);
|
|
118
118
|
}
|
|
119
119
|
};
|
|
120
|
+
InvalidFilterError = class extends AppError {
|
|
121
|
+
static {
|
|
122
|
+
__name(this, "InvalidFilterError");
|
|
123
|
+
}
|
|
124
|
+
constructor(message, details) {
|
|
125
|
+
super("INVALID_FILTER" /* INVALID_FILTER */, message, 400, details);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
120
128
|
InvalidTokenError = class extends AppError {
|
|
121
129
|
static {
|
|
122
130
|
__name(this, "InvalidTokenError");
|
|
@@ -34459,7 +34467,7 @@ function parseFilters(filter) {
|
|
|
34459
34467
|
fieldKey,
|
|
34460
34468
|
error: error2 instanceof Error ? error2.message : String(error2)
|
|
34461
34469
|
});
|
|
34462
|
-
throw new
|
|
34470
|
+
throw new InvalidFilterError(`Invalid filter field syntax: ${fieldKey}`, {
|
|
34463
34471
|
field: fieldKey,
|
|
34464
34472
|
error: error2 instanceof Error ? error2.message : String(error2)
|
|
34465
34473
|
});
|
|
@@ -34651,7 +34659,14 @@ async function executeInQuery(resource, targetIds, sort, requestId) {
|
|
|
34651
34659
|
}
|
|
34652
34660
|
async function executeAllRecordsQuery(resource, sort, perPage, nextToken, parsedFilters, requestId) {
|
|
34653
34661
|
if (parsedFilters.length > 0) {
|
|
34654
|
-
return executeAllRecordsQueryFullScan(
|
|
34662
|
+
return executeAllRecordsQueryFullScan(
|
|
34663
|
+
resource,
|
|
34664
|
+
sort,
|
|
34665
|
+
perPage,
|
|
34666
|
+
nextToken,
|
|
34667
|
+
parsedFilters,
|
|
34668
|
+
requestId
|
|
34669
|
+
);
|
|
34655
34670
|
}
|
|
34656
34671
|
const dbClient2 = getDBClient();
|
|
34657
34672
|
const tableName = getTableName();
|
|
@@ -34723,19 +34738,20 @@ async function executeAllRecordsQueryFullScan(resource, sort, perPage, nextToken
|
|
|
34723
34738
|
const dbClient2 = getDBClient();
|
|
34724
34739
|
const tableName = getTableName();
|
|
34725
34740
|
const costTracker = new CostTracker();
|
|
34726
|
-
const offset = nextToken ? decodeOffsetToken(nextToken) ?? 0 : 0;
|
|
34727
|
-
logger6.debug("Executing full scan query (ID sort)", {
|
|
34728
|
-
requestId,
|
|
34729
|
-
resource,
|
|
34730
|
-
offset
|
|
34731
|
-
});
|
|
34732
|
-
const allRecords = [];
|
|
34733
34741
|
let exclusiveStartKey;
|
|
34734
|
-
|
|
34735
|
-
|
|
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;
|
|
34736
34750
|
let iterations = 0;
|
|
34737
|
-
|
|
34738
|
-
|
|
34751
|
+
let previousKeySignature;
|
|
34752
|
+
let chunkSize = SCAN_CHUNK_INITIAL;
|
|
34753
|
+
scan: while (true) {
|
|
34754
|
+
if (++iterations > SCAN_MAX_ITERATIONS) {
|
|
34739
34755
|
logger6.error("executeAllRecordsQueryFullScan: \u6700\u5927\u53CD\u5FA9\u56DE\u6570\u3092\u8D85\u904E\u3057\u305F\u305F\u3081\u4E2D\u65AD", {
|
|
34740
34756
|
requestId,
|
|
34741
34757
|
resource,
|
|
@@ -34753,8 +34769,7 @@ async function executeAllRecordsQueryFullScan(resource, sort, perPage, nextToken
|
|
|
34753
34769
|
":skPrefix": "id#"
|
|
34754
34770
|
},
|
|
34755
34771
|
ScanIndexForward: sort.order === "ASC",
|
|
34756
|
-
Limit:
|
|
34757
|
-
// 大きめの Limit でループ回数を最小化
|
|
34772
|
+
Limit: chunkSize,
|
|
34758
34773
|
ExclusiveStartKey: exclusiveStartKey,
|
|
34759
34774
|
ConsistentRead: true,
|
|
34760
34775
|
ReturnConsumedCapacity: "TOTAL"
|
|
@@ -34763,10 +34778,24 @@ async function executeAllRecordsQueryFullScan(resource, sort, perPage, nextToken
|
|
|
34763
34778
|
"Query"
|
|
34764
34779
|
);
|
|
34765
34780
|
costTracker.add(queryResult.ConsumedCapacity);
|
|
34766
|
-
|
|
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
|
+
}
|
|
34767
34793
|
const nextKey = queryResult.LastEvaluatedKey;
|
|
34768
|
-
|
|
34769
|
-
|
|
34794
|
+
if (!nextKey) {
|
|
34795
|
+
break;
|
|
34796
|
+
}
|
|
34797
|
+
const nextKeySignature = `${nextKey.PK}#${nextKey.SK}`;
|
|
34798
|
+
if (nextKeySignature === previousKeySignature) {
|
|
34770
34799
|
logger6.error("executeAllRecordsQueryFullScan: \u540C\u4E00\u306ELastEvaluatedKey\u304C\u8FD4\u5374\u3055\u308C\u305F\u305F\u3081\u4E2D\u65AD", {
|
|
34771
34800
|
requestId,
|
|
34772
34801
|
resource,
|
|
@@ -34776,28 +34805,24 @@ async function executeAllRecordsQueryFullScan(resource, sort, perPage, nextToken
|
|
|
34776
34805
|
}
|
|
34777
34806
|
previousKeySignature = nextKeySignature;
|
|
34778
34807
|
exclusiveStartKey = nextKey;
|
|
34779
|
-
|
|
34780
|
-
|
|
34781
|
-
|
|
34782
|
-
|
|
34783
|
-
const hasNextPage = offset + perPage < items.length;
|
|
34784
|
-
const nextTokenValue = hasNextPage ? encodeOffsetToken(offset + perPage) : void 0;
|
|
34785
|
-
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", {
|
|
34786
34812
|
requestId,
|
|
34787
34813
|
resource,
|
|
34788
|
-
|
|
34789
|
-
offset,
|
|
34814
|
+
scanned,
|
|
34790
34815
|
returned: page.length,
|
|
34791
34816
|
hasNextPage
|
|
34792
34817
|
});
|
|
34793
34818
|
return {
|
|
34794
34819
|
items: page,
|
|
34795
|
-
pageInfo: { hasNextPage, hasPreviousPage:
|
|
34820
|
+
pageInfo: { hasNextPage, hasPreviousPage: !!nextToken },
|
|
34796
34821
|
...nextTokenValue && { nextToken: nextTokenValue },
|
|
34797
34822
|
consumedCapacity: costTracker.getAggregated()
|
|
34798
34823
|
};
|
|
34799
34824
|
}
|
|
34800
|
-
var import_lib_dynamodb2, logger6;
|
|
34825
|
+
var import_lib_dynamodb2, logger6, SCAN_CHUNK_INITIAL, SCAN_CHUNK_MAX, SCAN_MAX_ITERATIONS;
|
|
34801
34826
|
var init_idQuery = __esm({
|
|
34802
34827
|
"src/server/operations/find/idQuery.ts"() {
|
|
34803
34828
|
"use strict";
|
|
@@ -34815,6 +34840,9 @@ var init_idQuery = __esm({
|
|
|
34815
34840
|
__name(executeSpecificIdQuery, "executeSpecificIdQuery");
|
|
34816
34841
|
__name(executeInQuery, "executeInQuery");
|
|
34817
34842
|
__name(executeAllRecordsQuery, "executeAllRecordsQuery");
|
|
34843
|
+
SCAN_CHUNK_INITIAL = 100;
|
|
34844
|
+
SCAN_CHUNK_MAX = 1e3;
|
|
34845
|
+
SCAN_MAX_ITERATIONS = 1e4;
|
|
34818
34846
|
__name(executeAllRecordsQueryFullScan, "executeAllRecordsQueryFullScan");
|
|
34819
34847
|
}
|
|
34820
34848
|
});
|
|
@@ -35918,8 +35946,8 @@ function addUpdateTimestamp(data) {
|
|
|
35918
35946
|
}
|
|
35919
35947
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
35920
35948
|
return {
|
|
35921
|
-
|
|
35922
|
-
|
|
35949
|
+
...data,
|
|
35950
|
+
[timestampFields.updatedAt]: data[timestampFields.updatedAt] ?? now
|
|
35923
35951
|
};
|
|
35924
35952
|
}
|
|
35925
35953
|
var init_timestamps = __esm({
|
|
@@ -36219,51 +36247,118 @@ var init_insertMany = __esm({
|
|
|
36219
36247
|
}
|
|
36220
36248
|
});
|
|
36221
36249
|
|
|
36222
|
-
// src/server/
|
|
36223
|
-
|
|
36224
|
-
|
|
36225
|
-
|
|
36226
|
-
|
|
36227
|
-
|
|
36228
|
-
|
|
36250
|
+
// src/server/utils/mergePatch.ts
|
|
36251
|
+
function isPlainObject(value) {
|
|
36252
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
36253
|
+
}
|
|
36254
|
+
function invalidPath(path, reason) {
|
|
36255
|
+
return new AppError("VALIDATION_ERROR" /* VALIDATION_ERROR */, `Invalid patch path "${path}": ${reason}`, 400, {
|
|
36256
|
+
path
|
|
36257
|
+
});
|
|
36258
|
+
}
|
|
36259
|
+
function mergeInto(target, patch) {
|
|
36229
36260
|
for (const [key, value] of Object.entries(patch)) {
|
|
36230
36261
|
if (key.includes(".")) {
|
|
36231
|
-
|
|
36232
|
-
let current = result;
|
|
36233
|
-
for (let i4 = 0; i4 < keys.length - 1; i4++) {
|
|
36234
|
-
const k4 = keys[i4];
|
|
36235
|
-
if (!(k4 in current)) {
|
|
36236
|
-
current[k4] = {};
|
|
36237
|
-
} else if (typeof current[k4] !== "object" || Array.isArray(current[k4])) {
|
|
36238
|
-
current[k4] = {};
|
|
36239
|
-
}
|
|
36240
|
-
current = current[k4];
|
|
36241
|
-
}
|
|
36242
|
-
const lastKey = keys[keys.length - 1];
|
|
36243
|
-
current[lastKey] = value;
|
|
36262
|
+
setByPath(target, key, value);
|
|
36244
36263
|
} else {
|
|
36245
|
-
|
|
36264
|
+
setKey(target, key, value, key);
|
|
36246
36265
|
}
|
|
36247
36266
|
}
|
|
36248
|
-
return result;
|
|
36249
36267
|
}
|
|
36250
|
-
function
|
|
36251
|
-
|
|
36252
|
-
|
|
36253
|
-
|
|
36254
|
-
|
|
36255
|
-
|
|
36256
|
-
|
|
36257
|
-
|
|
36258
|
-
|
|
36259
|
-
|
|
36260
|
-
|
|
36268
|
+
function setKey(target, key, value, path) {
|
|
36269
|
+
if (key === "__proto__") {
|
|
36270
|
+
throw invalidPath(path, "forbidden segment");
|
|
36271
|
+
}
|
|
36272
|
+
if (value === null) {
|
|
36273
|
+
delete target[key];
|
|
36274
|
+
} else if (isPlainObject(value) && isPlainObject(target[key])) {
|
|
36275
|
+
mergeInto(target[key], value);
|
|
36276
|
+
} else {
|
|
36277
|
+
target[key] = value;
|
|
36278
|
+
}
|
|
36279
|
+
}
|
|
36280
|
+
function setByPath(target, path, value) {
|
|
36281
|
+
const keys = path.split(".");
|
|
36282
|
+
if (keys.some((k4) => k4 === "")) {
|
|
36283
|
+
throw invalidPath(path, "empty segment");
|
|
36284
|
+
}
|
|
36285
|
+
let current = target;
|
|
36286
|
+
for (let i4 = 0; i4 < keys.length; i4++) {
|
|
36287
|
+
const key = keys[i4];
|
|
36288
|
+
const isLast = i4 === keys.length - 1;
|
|
36289
|
+
if (Array.isArray(current)) {
|
|
36290
|
+
if (!/^(0|[1-9]\d*)$/.test(key) || Number(key) >= current.length) {
|
|
36291
|
+
throw invalidPath(path, `"${key}" is not a valid index (length ${current.length})`);
|
|
36292
|
+
}
|
|
36293
|
+
const index = Number(key);
|
|
36294
|
+
if (isLast) {
|
|
36295
|
+
if (value === null) {
|
|
36296
|
+
throw invalidPath(path, "array elements cannot be deleted by path");
|
|
36297
|
+
}
|
|
36298
|
+
const existing = current[index];
|
|
36299
|
+
if (isPlainObject(value) && isPlainObject(existing)) {
|
|
36300
|
+
mergeInto(existing, value);
|
|
36301
|
+
} else {
|
|
36302
|
+
current[index] = value;
|
|
36303
|
+
}
|
|
36304
|
+
return;
|
|
36305
|
+
}
|
|
36306
|
+
const next2 = current[index];
|
|
36307
|
+
if (isPlainObject(next2) || Array.isArray(next2)) {
|
|
36308
|
+
current = next2;
|
|
36309
|
+
} else {
|
|
36310
|
+
if (value === null) {
|
|
36311
|
+
return;
|
|
36312
|
+
}
|
|
36313
|
+
const created = {};
|
|
36314
|
+
current[index] = created;
|
|
36315
|
+
current = created;
|
|
36316
|
+
}
|
|
36317
|
+
continue;
|
|
36318
|
+
}
|
|
36319
|
+
if (isLast) {
|
|
36320
|
+
setKey(current, key, value, path);
|
|
36321
|
+
return;
|
|
36322
|
+
}
|
|
36323
|
+
if (key === "__proto__") {
|
|
36324
|
+
throw invalidPath(path, "forbidden segment");
|
|
36325
|
+
}
|
|
36326
|
+
const next = current[key];
|
|
36327
|
+
if (isPlainObject(next) || Array.isArray(next)) {
|
|
36328
|
+
current = next;
|
|
36261
36329
|
} else {
|
|
36262
|
-
|
|
36330
|
+
if (value === null) {
|
|
36331
|
+
return;
|
|
36332
|
+
}
|
|
36333
|
+
const created = {};
|
|
36334
|
+
current[key] = created;
|
|
36335
|
+
current = created;
|
|
36263
36336
|
}
|
|
36264
36337
|
}
|
|
36338
|
+
}
|
|
36339
|
+
function applyJsonMergePatch(target, patch) {
|
|
36340
|
+
const result = structuredClone(target);
|
|
36341
|
+
mergeInto(result, structuredClone(patch));
|
|
36265
36342
|
return result;
|
|
36266
36343
|
}
|
|
36344
|
+
var init_mergePatch = __esm({
|
|
36345
|
+
"src/server/utils/mergePatch.ts"() {
|
|
36346
|
+
"use strict";
|
|
36347
|
+
init_shared();
|
|
36348
|
+
__name(isPlainObject, "isPlainObject");
|
|
36349
|
+
__name(invalidPath, "invalidPath");
|
|
36350
|
+
__name(mergeInto, "mergeInto");
|
|
36351
|
+
__name(setKey, "setKey");
|
|
36352
|
+
__name(setByPath, "setByPath");
|
|
36353
|
+
__name(applyJsonMergePatch, "applyJsonMergePatch");
|
|
36354
|
+
}
|
|
36355
|
+
});
|
|
36356
|
+
|
|
36357
|
+
// src/server/operations/updateMany.ts
|
|
36358
|
+
var updateMany_exports = {};
|
|
36359
|
+
__export(updateMany_exports, {
|
|
36360
|
+
handleUpdateMany: () => handleUpdateMany
|
|
36361
|
+
});
|
|
36267
36362
|
async function handleUpdateMany(resource, params, requestId) {
|
|
36268
36363
|
const { data: patchData, options } = params;
|
|
36269
36364
|
const upsert = options?.upsert ?? false;
|
|
@@ -36391,11 +36486,9 @@ async function handleUpdateMany(resource, params, requestId) {
|
|
|
36391
36486
|
const id = existingData.id;
|
|
36392
36487
|
const oldShadowKeys = item.__shadowKeys ?? existingData.__shadowKeys ?? [];
|
|
36393
36488
|
const actualPatchData2 = patchData.$set ? patchData.$set : patchData;
|
|
36394
|
-
const
|
|
36395
|
-
const
|
|
36396
|
-
|
|
36397
|
-
id
|
|
36398
|
-
});
|
|
36489
|
+
const timestampedPatch = addUpdateTimestamp(actualPatchData2);
|
|
36490
|
+
const mergedData = applyJsonMergePatch(removeShadowKeys(existingData), timestampedPatch);
|
|
36491
|
+
const updatedData = { ...mergedData, id };
|
|
36399
36492
|
const newShadowRecords = generateShadowRecords(updatedData, resource, shadowConfig);
|
|
36400
36493
|
const newShadowKeys = newShadowRecords.map((shadow) => shadow.SK);
|
|
36401
36494
|
const mainSK = generateMainRecordSK2(id);
|
|
@@ -36676,10 +36769,9 @@ var init_updateMany = __esm({
|
|
|
36676
36769
|
init_chunking();
|
|
36677
36770
|
init_cost_tracker();
|
|
36678
36771
|
init_dynamodb3();
|
|
36772
|
+
init_mergePatch();
|
|
36679
36773
|
init_timestamps();
|
|
36680
36774
|
logger18 = createLogger({ service: "records-lambda" });
|
|
36681
|
-
__name(expandDotNotation, "expandDotNotation");
|
|
36682
|
-
__name(applyJsonMergePatch, "applyJsonMergePatch");
|
|
36683
36775
|
__name(handleUpdateMany, "handleUpdateMany");
|
|
36684
36776
|
__name(getPreparationErrorCode2, "getPreparationErrorCode");
|
|
36685
36777
|
}
|
|
@@ -37115,6 +37207,9 @@ async function handleUpdateOne(resource, params, requestId) {
|
|
|
37115
37207
|
if (updateManyResult.count === 0) {
|
|
37116
37208
|
const error2 = Object.values(updateManyResult.errors || {})[0];
|
|
37117
37209
|
if (error2) {
|
|
37210
|
+
if (error2.code === "VALIDATION_ERROR" /* VALIDATION_ERROR */) {
|
|
37211
|
+
throw new AppError("VALIDATION_ERROR" /* VALIDATION_ERROR */, error2.message ?? "Invalid patch", 400);
|
|
37212
|
+
}
|
|
37118
37213
|
throw new Error(`Failed to update record: ${error2.message}`);
|
|
37119
37214
|
} else {
|
|
37120
37215
|
throw new Error(`Failed to update record: ${targetId}`);
|
|
@@ -37154,6 +37249,9 @@ async function handleUpdateOne(resource, params, requestId) {
|
|
|
37154
37249
|
if (updateManyResult.count === 0) {
|
|
37155
37250
|
const error2 = Object.values(updateManyResult.errors || {})[0];
|
|
37156
37251
|
if (error2) {
|
|
37252
|
+
if (error2.code === "VALIDATION_ERROR" /* VALIDATION_ERROR */) {
|
|
37253
|
+
throw new AppError("VALIDATION_ERROR" /* VALIDATION_ERROR */, error2.message ?? "Invalid patch", 400);
|
|
37254
|
+
}
|
|
37157
37255
|
throw new Error(`Failed to update record: ${error2.message}`);
|
|
37158
37256
|
} else {
|
|
37159
37257
|
throw new Error(`No records found matching filter`);
|
|
@@ -38706,7 +38804,7 @@ async function handler(event) {
|
|
|
38706
38804
|
return createCorsResponse(HTTP_STATUS.OK);
|
|
38707
38805
|
}
|
|
38708
38806
|
if (event.requestContext.http.method === "GET" && event.requestContext.http.path === "/version") {
|
|
38709
|
-
const version = "1.
|
|
38807
|
+
const version = "1.8.0";
|
|
38710
38808
|
return createSuccessResponse({ version, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, requestId);
|
|
38711
38809
|
}
|
|
38712
38810
|
if (event.requestContext.http.method !== "POST") {
|