@exabugs/dynamodb-client 1.3.41 → 1.3.43

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,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.3.43] - 2026-01-17
11
+
12
+ ### Changed
13
+
14
+ - **$near検索の最適化**: maxDistanceによる早期終了条件を追加
15
+ - 候補数による早期終了: maxDistance内の候補がlimit件以上見つかったら終了
16
+ - カバー範囲による早期終了: 現在の精度での9ブロック検索範囲がmaxDistanceを完全にカバーしたら終了
17
+ - GEOHASH_COVERAGE定義を拡張: Precision 2-8のカバー範囲を定義(従来は6-8のみ)
18
+ - 不要な精度緩和を削減し、検索パフォーマンスを向上
19
+
20
+ ## [1.3.42] - 2026-01-15
21
+
22
+ ### Added
23
+
24
+ - **ドット記法サポート**: `$set`オペレーターでドット記法(例: `"settings.locationEnabled"`)をサポート
25
+ - ネストされたオブジェクトとして正しく保存される(例: `settings: { locationEnabled: true }`)
26
+ - `expandDotNotation`ヘルパー関数を追加
27
+ - `applyJsonMergePatch`関数を更新してドット記法を展開
28
+ - ユニットテスト追加(10テスト)
29
+
30
+ ### Fixed
31
+
32
+ - **ドット記法の文字列キー問題**: `$set: { "settings.locationEnabled": true }`が文字列キーとして保存されていた問題を修正
33
+ - 以前: `{ "settings.locationEnabled": true }` (文字列キー)
34
+ - 修正後: `{ settings: { locationEnabled: true } }` (ネストされたオブジェクト)
35
+
10
36
  ## [1.3.41] - 2026-01-11
11
37
 
12
38
  ### Added
package/README.md CHANGED
@@ -253,12 +253,7 @@ The easiest way to deploy is using the [dynamodb-client-example](https://github.
253
253
  git clone https://github.com/exabugs/dynamodb-client-example.git
254
254
  cd dynamodb-client-example
255
255
 
256
- # Deploy to dev environment
257
- make deploy-dev
258
-
259
- # Deploy to other environments
260
- make deploy-stg # Staging
261
- make deploy-prd # Production
256
+ make infra-apply
262
257
  ```
263
258
 
264
259
  See the [example project's documentation](https://github.com/exabugs/dynamodb-client-example) for detailed deployment instructions.
@@ -1,5 +1,5 @@
1
- // @exabugs/dynamodb-client v1.3.41
2
- // Built: 2026-01-11T01:29:54.903Z
1
+ // @exabugs/dynamodb-client v1.3.43
2
+ // Built: 2026-01-17T06:12:46.802Z
3
3
  "use strict";
4
4
  var __create = Object.create;
5
5
  var __defProp = Object.defineProperty;
@@ -30053,6 +30053,13 @@ var init_converter = __esm({
30053
30053
  });
30054
30054
 
30055
30055
  // src/server/query/nearSearch.ts
30056
+ function coversMaxDistance(precision, maxDistance) {
30057
+ const coverage = GEOHASH_COVERAGE[precision];
30058
+ if (!coverage) {
30059
+ return false;
30060
+ }
30061
+ return coverage >= maxDistance;
30062
+ }
30056
30063
  async function executeNearSearch(nearQuery, fieldName, limit, searchFunction, config = DEFAULT_GEOHASH_CONFIG) {
30057
30064
  const { latitude, longitude } = extractCoordinatesFromNearQuery(nearQuery);
30058
30065
  const maxDistance = extractMaxDistanceFromNearQuery(nearQuery);
@@ -30072,6 +30079,43 @@ async function executeNearSearch(nearQuery, fieldName, limit, searchFunction, co
30072
30079
  allCandidates.push(candidate);
30073
30080
  }
30074
30081
  }
30082
+ if (maxDistance !== void 0) {
30083
+ let candidatesWithinDistance = 0;
30084
+ for (const candidate of allCandidates) {
30085
+ const location = candidate[fieldName];
30086
+ if (location && typeof location.latitude === "number" && typeof location.longitude === "number") {
30087
+ const distance = calculateDistance(
30088
+ latitude,
30089
+ longitude,
30090
+ location.latitude,
30091
+ location.longitude
30092
+ );
30093
+ if (distance <= maxDistance) {
30094
+ candidatesWithinDistance++;
30095
+ }
30096
+ }
30097
+ }
30098
+ if (candidatesWithinDistance >= limit) {
30099
+ console.log("[nearSearch] Early termination: found enough candidates within maxDistance", {
30100
+ candidatesWithinDistance,
30101
+ limit,
30102
+ maxDistance,
30103
+ precision,
30104
+ iterations
30105
+ });
30106
+ break;
30107
+ }
30108
+ if (coversMaxDistance(precision, maxDistance)) {
30109
+ console.log("[nearSearch] Early termination: current precision covers maxDistance", {
30110
+ precision,
30111
+ maxDistance,
30112
+ coverage: GEOHASH_COVERAGE[precision],
30113
+ candidatesWithinDistance,
30114
+ iterations
30115
+ });
30116
+ break;
30117
+ }
30118
+ }
30075
30119
  precision--;
30076
30120
  }
30077
30121
  const documentsWithDistance = allCandidates.map((doc) => {
@@ -30120,10 +30164,28 @@ async function executeNearSearch(nearQuery, fieldName, limit, searchFunction, co
30120
30164
  }
30121
30165
  };
30122
30166
  }
30167
+ var GEOHASH_COVERAGE;
30123
30168
  var init_nearSearch = __esm({
30124
30169
  "src/server/query/nearSearch.ts"() {
30125
30170
  "use strict";
30126
30171
  init_geohash();
30172
+ GEOHASH_COVERAGE = {
30173
+ 8: 19 * 3,
30174
+ // ±19m × 3 = 約57m
30175
+ 7: 76 * 3,
30176
+ // ±76m × 3 = 約228m
30177
+ 6: 610 * 3,
30178
+ // ±610m × 3 = 約1,830m (1.8km)
30179
+ 5: 2400 * 3,
30180
+ // ±2.4km × 3 = 約7,200m (7.2km)
30181
+ 4: 2e4 * 3,
30182
+ // ±20km × 3 = 約60,000m (60km)
30183
+ 3: 78e3 * 3,
30184
+ // ±78km × 3 = 約234,000m (234km)
30185
+ 2: 63e4 * 3
30186
+ // ±630km × 3 = 約1,890,000m (1,890km)
30187
+ };
30188
+ __name(coversMaxDistance, "coversMaxDistance");
30127
30189
  __name(executeNearSearch, "executeNearSearch");
30128
30190
  }
30129
30191
  });
@@ -31670,9 +31732,33 @@ var updateMany_exports = {};
31670
31732
  __export(updateMany_exports, {
31671
31733
  handleUpdateMany: () => handleUpdateMany
31672
31734
  });
31735
+ function expandDotNotation(patch) {
31736
+ const result = {};
31737
+ for (const [key, value] of Object.entries(patch)) {
31738
+ if (key.includes(".")) {
31739
+ const keys = key.split(".");
31740
+ let current = result;
31741
+ for (let i4 = 0; i4 < keys.length - 1; i4++) {
31742
+ const k4 = keys[i4];
31743
+ if (!(k4 in current)) {
31744
+ current[k4] = {};
31745
+ } else if (typeof current[k4] !== "object" || Array.isArray(current[k4])) {
31746
+ current[k4] = {};
31747
+ }
31748
+ current = current[k4];
31749
+ }
31750
+ const lastKey = keys[keys.length - 1];
31751
+ current[lastKey] = value;
31752
+ } else {
31753
+ result[key] = value;
31754
+ }
31755
+ }
31756
+ return result;
31757
+ }
31673
31758
  function applyJsonMergePatch(target, patch) {
31759
+ const expandedPatch = expandDotNotation(patch);
31674
31760
  const result = { ...target };
31675
- for (const [key, value] of Object.entries(patch)) {
31761
+ for (const [key, value] of Object.entries(expandedPatch)) {
31676
31762
  if (value === null) {
31677
31763
  delete result[key];
31678
31764
  } else if (typeof value === "object" && !Array.isArray(value) && value !== null && typeof result[key] === "object" && !Array.isArray(result[key]) && result[key] !== null) {
@@ -32081,6 +32167,7 @@ var init_updateMany = __esm({
32081
32167
  init_dynamodb3();
32082
32168
  init_timestamps();
32083
32169
  logger18 = createLogger({ service: "records-lambda" });
32170
+ __name(expandDotNotation, "expandDotNotation");
32084
32171
  __name(applyJsonMergePatch, "applyJsonMergePatch");
32085
32172
  __name(handleUpdateMany, "handleUpdateMany");
32086
32173
  __name(getPreparationErrorCode2, "getPreparationErrorCode");
@@ -34020,7 +34107,7 @@ async function handler(event) {
34020
34107
  return createCorsResponse(HTTP_STATUS.OK);
34021
34108
  }
34022
34109
  if (event.requestContext.http.method === "GET" && event.requestContext.http.path === "/version") {
34023
- const version = "1.3.41";
34110
+ const version = "1.3.43";
34024
34111
  return createSuccessResponse({ version, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, requestId);
34025
34112
  }
34026
34113
  if (event.requestContext.http.method !== "POST") {