@exabugs/dynamodb-client 1.3.39 → 1.3.41
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 +48 -0
- package/dist/server/handler.cjs +120 -27
- package/dist/server/handler.cjs.map +3 -3
- package/dist/server/operations/updateMany.d.ts.map +1 -1
- package/dist/server/operations/updateMany.js +136 -27
- package/dist/server/operations/updateMany.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,54 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.3.41] - 2026-01-11
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **updateOne filter版 upsert: true サポート**: filterで検索して存在しない場合に新規作成する機能を実装
|
|
15
|
+
- filter版で`upsert: true`オプションをサポート
|
|
16
|
+
- filterの条件を`$setOnInsert`に自動マージ
|
|
17
|
+
- 新規IDを自動生成(UUID)
|
|
18
|
+
- サーバー側ユニットテスト追加(8テスト)
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **updateOne filter版 upsert: true バグ修正**: filterで検索して存在しない場合に`No records found matching filter`エラーが発生していた問題を修正
|
|
23
|
+
- `updateMany`のfilter版でレコードが見つからない場合の処理を実装
|
|
24
|
+
- 新規レコード作成時にfilterの条件を`$setOnInsert`にマージ
|
|
25
|
+
- FCMトークン登録(`{ token: "..." }`でupsert)が正常に動作するように修正
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- **テストカバレッジ向上**: サーバー側の`updateOne`ユニットテストを追加(全707テスト成功)
|
|
30
|
+
|
|
31
|
+
## [1.3.40] - 2026-01-11
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- **updateMany upsert: true サポート**: 存在しないレコードの新規作成機能を実装
|
|
36
|
+
- `upsert: true`オプションで存在しないレコードを自動作成
|
|
37
|
+
- `$set`と`$setOnInsert`オペレーターのサポート
|
|
38
|
+
- `$set`が`$setOnInsert`より優先される仕様
|
|
39
|
+
- サーバー側ユニットテスト追加(8テスト)
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- **updateMany upsert: true バグ修正**: 存在しないレコードで`ITEM_NOT_FOUND`エラーが発生していた問題を修正
|
|
44
|
+
- 新規レコード作成処理を実装
|
|
45
|
+
- `createdAt`/`updatedAt`タイムスタンプを自動追加
|
|
46
|
+
- シャドーレコードを自動生成
|
|
47
|
+
- レスポンス形式を修正(新規作成時は`$set`と`$setOnInsert`をマージ)
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
|
|
51
|
+
- **テストカバレッジ向上**: サーバー側の`updateMany`ユニットテストを追加
|
|
52
|
+
- 基本的な更新テスト
|
|
53
|
+
- `upsert: false`のテスト
|
|
54
|
+
- `upsert: true`(insert case)のテスト
|
|
55
|
+
- `upsert: true`(update case)のテスト
|
|
56
|
+
- `upsert: true`(混在ケース)のテスト
|
|
57
|
+
|
|
10
58
|
## [1.3.39] - 2026-01-11
|
|
11
59
|
|
|
12
60
|
### Fixed
|
package/dist/server/handler.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// @exabugs/dynamodb-client v1.3.
|
|
2
|
-
// Built: 2026-01-
|
|
1
|
+
// @exabugs/dynamodb-client v1.3.41
|
|
2
|
+
// Built: 2026-01-11T01:29:54.903Z
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
@@ -31344,6 +31344,12 @@ var init_findOne = __esm({
|
|
|
31344
31344
|
});
|
|
31345
31345
|
|
|
31346
31346
|
// src/server/utils/timestamps.ts
|
|
31347
|
+
var timestamps_exports = {};
|
|
31348
|
+
__export(timestamps_exports, {
|
|
31349
|
+
addCreateTimestamps: () => addCreateTimestamps,
|
|
31350
|
+
addUpdateTimestamp: () => addUpdateTimestamp,
|
|
31351
|
+
getTimestampFields: () => getTimestampFields
|
|
31352
|
+
});
|
|
31347
31353
|
function getTimestampFields() {
|
|
31348
31354
|
const shadowConfig = getShadowConfig();
|
|
31349
31355
|
return {
|
|
@@ -31711,14 +31717,36 @@ async function handleUpdateMany(resource, params, requestId) {
|
|
|
31711
31717
|
resource,
|
|
31712
31718
|
count: ids.length
|
|
31713
31719
|
});
|
|
31714
|
-
|
|
31715
|
-
|
|
31716
|
-
|
|
31717
|
-
|
|
31718
|
-
|
|
31719
|
-
|
|
31720
|
-
|
|
31721
|
-
|
|
31720
|
+
if (ids.length === 0) {
|
|
31721
|
+
if (upsert) {
|
|
31722
|
+
const { randomUUID } = await import("crypto");
|
|
31723
|
+
const newId = randomUUID();
|
|
31724
|
+
ids = [newId];
|
|
31725
|
+
logger18.info("Creating new record with filter (upsert: true)", {
|
|
31726
|
+
requestId,
|
|
31727
|
+
resource,
|
|
31728
|
+
newId,
|
|
31729
|
+
filter: params.filter
|
|
31730
|
+
});
|
|
31731
|
+
if (patchData.$setOnInsert) {
|
|
31732
|
+
patchData.$setOnInsert = {
|
|
31733
|
+
...params.filter,
|
|
31734
|
+
...patchData.$setOnInsert
|
|
31735
|
+
};
|
|
31736
|
+
} else if (patchData.$set) {
|
|
31737
|
+
patchData.$setOnInsert = params.filter;
|
|
31738
|
+
} else {
|
|
31739
|
+
patchData.$setOnInsert = params.filter;
|
|
31740
|
+
}
|
|
31741
|
+
} else {
|
|
31742
|
+
return {
|
|
31743
|
+
count: 0,
|
|
31744
|
+
successIds: {},
|
|
31745
|
+
failedIds: {},
|
|
31746
|
+
errors: {}
|
|
31747
|
+
};
|
|
31748
|
+
}
|
|
31749
|
+
}
|
|
31722
31750
|
}
|
|
31723
31751
|
logLargeBatchWarning("updateMany", ids.length, requestId, resource);
|
|
31724
31752
|
const dbClient2 = getDBClient();
|
|
@@ -31757,6 +31785,15 @@ async function handleUpdateMany(resource, params, requestId) {
|
|
|
31757
31785
|
// 最初の10件のみログ
|
|
31758
31786
|
});
|
|
31759
31787
|
}
|
|
31788
|
+
if (upsert && notFoundIds.length > 0) {
|
|
31789
|
+
logger18.info("Creating new records (upsert: true)", {
|
|
31790
|
+
requestId,
|
|
31791
|
+
resource,
|
|
31792
|
+
notFoundCount: notFoundIds.length,
|
|
31793
|
+
notFoundIds: notFoundIds.slice(0, 10)
|
|
31794
|
+
// 最初の10件のみログ
|
|
31795
|
+
});
|
|
31796
|
+
}
|
|
31760
31797
|
const shadowConfig = getShadowConfig();
|
|
31761
31798
|
const preparedRecords = [];
|
|
31762
31799
|
const preparationFailedIds = [];
|
|
@@ -31801,6 +31838,47 @@ async function handleUpdateMany(resource, params, requestId) {
|
|
|
31801
31838
|
});
|
|
31802
31839
|
}
|
|
31803
31840
|
}
|
|
31841
|
+
if (upsert && notFoundIds.length > 0) {
|
|
31842
|
+
const { addCreateTimestamps: addCreateTimestamps2 } = await Promise.resolve().then(() => (init_timestamps(), timestamps_exports));
|
|
31843
|
+
for (const id of notFoundIds) {
|
|
31844
|
+
try {
|
|
31845
|
+
const setData = patchData.$set ? patchData.$set : patchData;
|
|
31846
|
+
const setOnInsertData2 = patchData.$setOnInsert ? patchData.$setOnInsert : {};
|
|
31847
|
+
const mergedData = {
|
|
31848
|
+
...setOnInsertData2,
|
|
31849
|
+
...setData,
|
|
31850
|
+
id
|
|
31851
|
+
};
|
|
31852
|
+
const newData = addCreateTimestamps2(mergedData);
|
|
31853
|
+
const newShadowRecords = generateShadowRecords(newData, resource, shadowConfig);
|
|
31854
|
+
const newShadowKeys = newShadowRecords.map((shadow) => shadow.SK);
|
|
31855
|
+
const mainSK = generateMainRecordSK2(id);
|
|
31856
|
+
preparedRecords.push({
|
|
31857
|
+
id,
|
|
31858
|
+
updatedData: newData,
|
|
31859
|
+
oldShadowKeys: [],
|
|
31860
|
+
// 新規作成なので空
|
|
31861
|
+
newShadowKeys,
|
|
31862
|
+
mainSK
|
|
31863
|
+
});
|
|
31864
|
+
} catch (error2) {
|
|
31865
|
+
const errorMessage = error2 instanceof Error ? error2.message : "Unknown preparation error";
|
|
31866
|
+
const errorCode = getPreparationErrorCode2(error2);
|
|
31867
|
+
logger18.error("Failed to prepare new record for upsert", {
|
|
31868
|
+
requestId,
|
|
31869
|
+
recordId: id,
|
|
31870
|
+
error: errorMessage,
|
|
31871
|
+
errorCode
|
|
31872
|
+
});
|
|
31873
|
+
preparationFailedIds.push(id);
|
|
31874
|
+
preparationErrors.push({
|
|
31875
|
+
id,
|
|
31876
|
+
code: errorCode,
|
|
31877
|
+
message: errorMessage
|
|
31878
|
+
});
|
|
31879
|
+
}
|
|
31880
|
+
}
|
|
31881
|
+
}
|
|
31804
31882
|
const getItemCount = /* @__PURE__ */ __name((record) => {
|
|
31805
31883
|
const shadowDiff = calculateShadowDiff(record.oldShadowKeys, record.newShadowKeys);
|
|
31806
31884
|
return 1 + shadowDiff.toDelete.length + shadowDiff.toAdd.length;
|
|
@@ -31875,26 +31953,37 @@ async function handleUpdateMany(resource, params, requestId) {
|
|
|
31875
31953
|
const failedIdsMap = {};
|
|
31876
31954
|
const errorsMap = {};
|
|
31877
31955
|
const actualPatchData = patchData.$set ? patchData.$set : patchData;
|
|
31956
|
+
const setOnInsertData = patchData.$setOnInsert ? patchData.$setOnInsert : {};
|
|
31878
31957
|
const successIdSet = new Set(successRecords.map((r4) => r4.id));
|
|
31879
31958
|
for (let i4 = 0; i4 < ids.length; i4++) {
|
|
31880
31959
|
const id = ids[i4];
|
|
31881
31960
|
if (successIdSet.has(id)) {
|
|
31882
31961
|
successIds[i4] = id;
|
|
31883
|
-
|
|
31884
|
-
|
|
31885
|
-
|
|
31886
|
-
|
|
31962
|
+
if (upsert && notFoundIds.includes(id)) {
|
|
31963
|
+
items.push({
|
|
31964
|
+
id,
|
|
31965
|
+
...setOnInsertData,
|
|
31966
|
+
...actualPatchData
|
|
31967
|
+
});
|
|
31968
|
+
} else {
|
|
31969
|
+
items.push({
|
|
31970
|
+
id,
|
|
31971
|
+
...actualPatchData
|
|
31972
|
+
});
|
|
31973
|
+
}
|
|
31887
31974
|
}
|
|
31888
31975
|
}
|
|
31889
|
-
|
|
31890
|
-
|
|
31891
|
-
|
|
31892
|
-
|
|
31893
|
-
|
|
31894
|
-
|
|
31895
|
-
|
|
31896
|
-
|
|
31897
|
-
|
|
31976
|
+
if (!upsert) {
|
|
31977
|
+
for (let i4 = 0; i4 < ids.length; i4++) {
|
|
31978
|
+
const id = ids[i4];
|
|
31979
|
+
if (notFoundIds.includes(id)) {
|
|
31980
|
+
failedIdsMap[i4] = id;
|
|
31981
|
+
errorsMap[i4] = {
|
|
31982
|
+
id,
|
|
31983
|
+
code: "ITEM_NOT_FOUND",
|
|
31984
|
+
message: `Record not found: ${id}`
|
|
31985
|
+
};
|
|
31986
|
+
}
|
|
31898
31987
|
}
|
|
31899
31988
|
}
|
|
31900
31989
|
for (let i4 = 0; i4 < ids.length; i4++) {
|
|
@@ -31934,9 +32023,12 @@ async function handleUpdateMany(resource, params, requestId) {
|
|
|
31934
32023
|
completionRiskInfo,
|
|
31935
32024
|
{
|
|
31936
32025
|
updated: count,
|
|
31937
|
-
notFound: notFoundIds.length,
|
|
32026
|
+
notFound: upsert ? 0 : notFoundIds.length,
|
|
32027
|
+
// upsert: true の場合は0
|
|
31938
32028
|
preparationFailed: preparationFailedIds.length,
|
|
31939
|
-
chunkExecutionFailed: chunkFailedIds.length
|
|
32029
|
+
chunkExecutionFailed: chunkFailedIds.length,
|
|
32030
|
+
upserted: upsert ? notFoundIds.length : 0
|
|
32031
|
+
// upsert: true の場合は新規作成数
|
|
31940
32032
|
}
|
|
31941
32033
|
);
|
|
31942
32034
|
if (allFailedIds.length > 0) {
|
|
@@ -31949,7 +32041,8 @@ async function handleUpdateMany(resource, params, requestId) {
|
|
|
31949
32041
|
allFailedIds.length,
|
|
31950
32042
|
Object.values(errorsMap).map((e4) => e4.code),
|
|
31951
32043
|
{
|
|
31952
|
-
notFoundCount: notFoundIds.length,
|
|
32044
|
+
notFoundCount: upsert ? 0 : notFoundIds.length,
|
|
32045
|
+
// upsert: true の場合は0
|
|
31953
32046
|
preparationFailures: preparationFailedIds.length,
|
|
31954
32047
|
chunkExecutionFailures: chunkFailedIds.length
|
|
31955
32048
|
}
|
|
@@ -33927,7 +34020,7 @@ async function handler(event) {
|
|
|
33927
34020
|
return createCorsResponse(HTTP_STATUS.OK);
|
|
33928
34021
|
}
|
|
33929
34022
|
if (event.requestContext.http.method === "GET" && event.requestContext.http.path === "/version") {
|
|
33930
|
-
const version = "1.3.
|
|
34023
|
+
const version = "1.3.41";
|
|
33931
34024
|
return createSuccessResponse({ version, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, requestId);
|
|
33932
34025
|
}
|
|
33933
34026
|
if (event.requestContext.http.method !== "POST") {
|