@exabugs/dynamodb-client 1.3.38 → 1.3.40

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 CHANGED
@@ -7,6 +7,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.3.40] - 2026-01-11
11
+
12
+ ### Added
13
+
14
+ - **updateMany upsert: true サポート**: 存在しないレコードの新規作成機能を実装
15
+ - `upsert: true`オプションで存在しないレコードを自動作成
16
+ - `$set`と`$setOnInsert`オペレーターのサポート
17
+ - `$set`が`$setOnInsert`より優先される仕様
18
+ - サーバー側ユニットテスト追加(8テスト)
19
+
20
+ ### Fixed
21
+
22
+ - **updateMany upsert: true バグ修正**: 存在しないレコードで`ITEM_NOT_FOUND`エラーが発生していた問題を修正
23
+ - 新規レコード作成処理を実装
24
+ - `createdAt`/`updatedAt`タイムスタンプを自動追加
25
+ - シャドーレコードを自動生成
26
+ - レスポンス形式を修正(新規作成時は`$set`と`$setOnInsert`をマージ)
27
+
28
+ ### Changed
29
+
30
+ - **テストカバレッジ向上**: サーバー側の`updateMany`ユニットテストを追加
31
+ - 基本的な更新テスト
32
+ - `upsert: false`のテスト
33
+ - `upsert: true`(insert case)のテスト
34
+ - `upsert: true`(update case)のテスト
35
+ - `upsert: true`(混在ケース)のテスト
36
+
37
+ ## [1.3.39] - 2026-01-11
38
+
39
+ ### Fixed
40
+
41
+ - **package-lock.json同期**: CI失敗を修正
42
+ - `yaml@2.8.2`の依存関係を追加
43
+ - `npm ci`が正常に動作するように修正
44
+
10
45
  ## [1.3.38] - 2026-01-11
11
46
 
12
47
  ### Changed
@@ -1,5 +1,5 @@
1
- // @exabugs/dynamodb-client v1.3.38
2
- // Built: 2026-01-10T23:33:04.543Z
1
+ // @exabugs/dynamodb-client v1.3.40
2
+ // Built: 2026-01-11T00:55:44.277Z
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 {
@@ -31757,6 +31763,15 @@ async function handleUpdateMany(resource, params, requestId) {
31757
31763
  // 最初の10件のみログ
31758
31764
  });
31759
31765
  }
31766
+ if (upsert && notFoundIds.length > 0) {
31767
+ logger18.info("Creating new records (upsert: true)", {
31768
+ requestId,
31769
+ resource,
31770
+ notFoundCount: notFoundIds.length,
31771
+ notFoundIds: notFoundIds.slice(0, 10)
31772
+ // 最初の10件のみログ
31773
+ });
31774
+ }
31760
31775
  const shadowConfig = getShadowConfig();
31761
31776
  const preparedRecords = [];
31762
31777
  const preparationFailedIds = [];
@@ -31801,6 +31816,47 @@ async function handleUpdateMany(resource, params, requestId) {
31801
31816
  });
31802
31817
  }
31803
31818
  }
31819
+ if (upsert && notFoundIds.length > 0) {
31820
+ const { addCreateTimestamps: addCreateTimestamps2 } = await Promise.resolve().then(() => (init_timestamps(), timestamps_exports));
31821
+ for (const id of notFoundIds) {
31822
+ try {
31823
+ const setData = patchData.$set ? patchData.$set : patchData;
31824
+ const setOnInsertData2 = patchData.$setOnInsert ? patchData.$setOnInsert : {};
31825
+ const mergedData = {
31826
+ ...setOnInsertData2,
31827
+ ...setData,
31828
+ id
31829
+ };
31830
+ const newData = addCreateTimestamps2(mergedData);
31831
+ const newShadowRecords = generateShadowRecords(newData, resource, shadowConfig);
31832
+ const newShadowKeys = newShadowRecords.map((shadow) => shadow.SK);
31833
+ const mainSK = generateMainRecordSK2(id);
31834
+ preparedRecords.push({
31835
+ id,
31836
+ updatedData: newData,
31837
+ oldShadowKeys: [],
31838
+ // 新規作成なので空
31839
+ newShadowKeys,
31840
+ mainSK
31841
+ });
31842
+ } catch (error2) {
31843
+ const errorMessage = error2 instanceof Error ? error2.message : "Unknown preparation error";
31844
+ const errorCode = getPreparationErrorCode2(error2);
31845
+ logger18.error("Failed to prepare new record for upsert", {
31846
+ requestId,
31847
+ recordId: id,
31848
+ error: errorMessage,
31849
+ errorCode
31850
+ });
31851
+ preparationFailedIds.push(id);
31852
+ preparationErrors.push({
31853
+ id,
31854
+ code: errorCode,
31855
+ message: errorMessage
31856
+ });
31857
+ }
31858
+ }
31859
+ }
31804
31860
  const getItemCount = /* @__PURE__ */ __name((record) => {
31805
31861
  const shadowDiff = calculateShadowDiff(record.oldShadowKeys, record.newShadowKeys);
31806
31862
  return 1 + shadowDiff.toDelete.length + shadowDiff.toAdd.length;
@@ -31875,26 +31931,37 @@ async function handleUpdateMany(resource, params, requestId) {
31875
31931
  const failedIdsMap = {};
31876
31932
  const errorsMap = {};
31877
31933
  const actualPatchData = patchData.$set ? patchData.$set : patchData;
31934
+ const setOnInsertData = patchData.$setOnInsert ? patchData.$setOnInsert : {};
31878
31935
  const successIdSet = new Set(successRecords.map((r4) => r4.id));
31879
31936
  for (let i4 = 0; i4 < ids.length; i4++) {
31880
31937
  const id = ids[i4];
31881
31938
  if (successIdSet.has(id)) {
31882
31939
  successIds[i4] = id;
31883
- items.push({
31884
- id,
31885
- ...actualPatchData
31886
- });
31940
+ if (upsert && notFoundIds.includes(id)) {
31941
+ items.push({
31942
+ id,
31943
+ ...setOnInsertData,
31944
+ ...actualPatchData
31945
+ });
31946
+ } else {
31947
+ items.push({
31948
+ id,
31949
+ ...actualPatchData
31950
+ });
31951
+ }
31887
31952
  }
31888
31953
  }
31889
- for (let i4 = 0; i4 < ids.length; i4++) {
31890
- const id = ids[i4];
31891
- if (notFoundIds.includes(id)) {
31892
- failedIdsMap[i4] = id;
31893
- errorsMap[i4] = {
31894
- id,
31895
- code: "ITEM_NOT_FOUND",
31896
- message: `Record not found: ${id}`
31897
- };
31954
+ if (!upsert) {
31955
+ for (let i4 = 0; i4 < ids.length; i4++) {
31956
+ const id = ids[i4];
31957
+ if (notFoundIds.includes(id)) {
31958
+ failedIdsMap[i4] = id;
31959
+ errorsMap[i4] = {
31960
+ id,
31961
+ code: "ITEM_NOT_FOUND",
31962
+ message: `Record not found: ${id}`
31963
+ };
31964
+ }
31898
31965
  }
31899
31966
  }
31900
31967
  for (let i4 = 0; i4 < ids.length; i4++) {
@@ -31934,9 +32001,12 @@ async function handleUpdateMany(resource, params, requestId) {
31934
32001
  completionRiskInfo,
31935
32002
  {
31936
32003
  updated: count,
31937
- notFound: notFoundIds.length,
32004
+ notFound: upsert ? 0 : notFoundIds.length,
32005
+ // upsert: true の場合は0
31938
32006
  preparationFailed: preparationFailedIds.length,
31939
- chunkExecutionFailed: chunkFailedIds.length
32007
+ chunkExecutionFailed: chunkFailedIds.length,
32008
+ upserted: upsert ? notFoundIds.length : 0
32009
+ // upsert: true の場合は新規作成数
31940
32010
  }
31941
32011
  );
31942
32012
  if (allFailedIds.length > 0) {
@@ -31949,7 +32019,8 @@ async function handleUpdateMany(resource, params, requestId) {
31949
32019
  allFailedIds.length,
31950
32020
  Object.values(errorsMap).map((e4) => e4.code),
31951
32021
  {
31952
- notFoundCount: notFoundIds.length,
32022
+ notFoundCount: upsert ? 0 : notFoundIds.length,
32023
+ // upsert: true の場合は0
31953
32024
  preparationFailures: preparationFailedIds.length,
31954
32025
  chunkExecutionFailures: chunkFailedIds.length
31955
32026
  }
@@ -33927,7 +33998,7 @@ async function handler(event) {
33927
33998
  return createCorsResponse(HTTP_STATUS.OK);
33928
33999
  }
33929
34000
  if (event.requestContext.http.method === "GET" && event.requestContext.http.path === "/version") {
33930
- const version = "1.3.38";
34001
+ const version = "1.3.40";
33931
34002
  return createSuccessResponse({ version, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, requestId);
33932
34003
  }
33933
34004
  if (event.requestContext.http.method !== "POST") {