@exabugs/dynamodb-client 1.4.8 → 1.4.10

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.
@@ -1,5 +1,5 @@
1
- // @exabugs/dynamodb-client v1.4.8
2
- // Built: 2026-04-05T11:02:55.391Z
1
+ // @exabugs/dynamodb-client v1.4.10
2
+ // Built: 2026-04-05T13:26:07.424Z
3
3
  "use strict";
4
4
  var __create = Object.create;
5
5
  var __defProp = Object.defineProperty;
@@ -30324,6 +30324,25 @@ function encodeNextToken(pk, sk) {
30324
30324
  const base64url = base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
30325
30325
  return base64url;
30326
30326
  }
30327
+ function encodeOffsetToken(offset) {
30328
+ const payload2 = { offset };
30329
+ const json = JSON.stringify(payload2);
30330
+ const base64 = Buffer.from(json, "utf-8").toString("base64");
30331
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
30332
+ }
30333
+ function decodeOffsetToken(token) {
30334
+ try {
30335
+ let base64 = token.replace(/-/g, "+").replace(/_/g, "/");
30336
+ while (base64.length % 4 !== 0) base64 += "=";
30337
+ const payload2 = JSON.parse(Buffer.from(base64, "base64").toString("utf-8"));
30338
+ if (typeof payload2.offset === "number" && !payload2.PK) {
30339
+ return payload2.offset;
30340
+ }
30341
+ return null;
30342
+ } catch {
30343
+ return null;
30344
+ }
30345
+ }
30327
30346
  function decodeNextToken(token) {
30328
30347
  try {
30329
30348
  let base64 = token.replace(/-/g, "+").replace(/_/g, "/");
@@ -30349,6 +30368,8 @@ var init_pagination = __esm({
30349
30368
  init_index2();
30350
30369
  init_validation3();
30351
30370
  __name(encodeNextToken, "encodeNextToken");
30371
+ __name(encodeOffsetToken, "encodeOffsetToken");
30372
+ __name(decodeOffsetToken, "decodeOffsetToken");
30352
30373
  __name(decodeNextToken, "decodeNextToken");
30353
30374
  }
30354
30375
  });
@@ -30819,20 +30840,26 @@ async function executeShadowQuery(resource, normalizedParams, requestId) {
30819
30840
  const costTracker = new CostTracker();
30820
30841
  const filterFirstCandidate = selectFilterFirstCandidate(parsedFilters, sort.field, normalizedParams.schema, perPage);
30821
30842
  const isFilterFirst = filterFirstCandidate !== void 0;
30822
- const querySort = isFilterFirst ? { field: filterFirstCandidate.parsed.field, order: "ASC" } : sort;
30823
- const optimizableFilter = isFilterFirst ? filterFirstCandidate : findOptimizableFilter(sort.field, parsedFilters);
30824
- const remainingFilters = isFilterFirst ? parsedFilters.filter((f4) => f4 !== filterFirstCandidate) : parsedFilters;
30843
+ if (isFilterFirst) {
30844
+ return executeFilterFirstQuery(
30845
+ resource,
30846
+ normalizedParams,
30847
+ filterFirstCandidate,
30848
+ costTracker,
30849
+ requestId
30850
+ );
30851
+ }
30852
+ const optimizableFilter = findOptimizableFilter(sort.field, parsedFilters);
30825
30853
  logger8.debug("Executing shadow query", {
30826
30854
  requestId,
30827
30855
  resource,
30828
30856
  sortField: sort.field,
30829
- isFilterFirst,
30830
- queryField: querySort.field,
30857
+ isFilterFirst: false,
30831
30858
  hasFilters: parsedFilters.length > 0
30832
30859
  });
30833
30860
  const shadowRecords = await executeShadowRecordQuery(
30834
30861
  resource,
30835
- querySort,
30862
+ sort,
30836
30863
  perPage,
30837
30864
  nextToken,
30838
30865
  optimizableFilter,
@@ -30863,11 +30890,8 @@ async function executeShadowQuery(resource, normalizedParams, requestId) {
30863
30890
  seenIds.add(id);
30864
30891
  return true;
30865
30892
  }).map((id) => recordMap.get(id)).filter((record) => record !== void 0);
30866
- if (remainingFilters.length > 0) {
30867
- items = items.filter((record) => matchesAllFilters(record, remainingFilters));
30868
- }
30869
- if (isFilterFirst) {
30870
- items = sortInMemory(items, sort);
30893
+ if (parsedFilters.length > 0) {
30894
+ items = items.filter((record) => matchesAllFilters(record, parsedFilters));
30871
30895
  }
30872
30896
  const hasNextPage = (shadowRecords.Items?.length || 0) < perPage ? false : shadowRecords.LastEvaluatedKey !== void 0;
30873
30897
  const nextTokenValue = hasNextPage && shadowRecords.LastEvaluatedKey ? encodeNextToken(
@@ -30878,7 +30902,7 @@ async function executeShadowQuery(resource, normalizedParams, requestId) {
30878
30902
  requestId,
30879
30903
  resource,
30880
30904
  sortField: sort.field,
30881
- isFilterFirst,
30905
+ isFilterFirst: false,
30882
30906
  shadowCount: shadowRecords.Items?.length || 0,
30883
30907
  mainCount: items.length,
30884
30908
  hasNextPage
@@ -30893,6 +30917,75 @@ async function executeShadowQuery(resource, normalizedParams, requestId) {
30893
30917
  consumedCapacity: costTracker.getAggregated()
30894
30918
  };
30895
30919
  }
30920
+ async function executeFilterFirstQuery(resource, normalizedParams, filterCandidate, costTracker, requestId) {
30921
+ const { sort, pagination, parsedFilters } = normalizedParams;
30922
+ const { perPage, nextToken } = pagination;
30923
+ const offset = nextToken ? decodeOffsetToken(nextToken) ?? 0 : 0;
30924
+ const filterSort = { field: filterCandidate.parsed.field, order: "ASC" };
30925
+ const remainingFilters = parsedFilters.filter((f4) => f4 !== filterCandidate);
30926
+ logger8.debug("Executing filter-first query", {
30927
+ requestId,
30928
+ resource,
30929
+ filterField: filterCandidate.parsed.field,
30930
+ sortField: sort.field,
30931
+ offset
30932
+ });
30933
+ const allShadowItems = [];
30934
+ let lastKey;
30935
+ do {
30936
+ const result = await executeShadowRecordQuery(
30937
+ resource,
30938
+ filterSort,
30939
+ 1e3,
30940
+ // 大きめの Limit でループ回数を最小化
30941
+ lastKey ? encodeNextToken(lastKey.PK, lastKey.SK) : void 0,
30942
+ filterCandidate,
30943
+ costTracker,
30944
+ requestId
30945
+ );
30946
+ allShadowItems.push(...result.Items || []);
30947
+ lastKey = result.LastEvaluatedKey;
30948
+ } while (lastKey);
30949
+ const allIds = Array.from(new Set(extractRecordIds(allShadowItems)));
30950
+ if (allIds.length === 0) {
30951
+ return {
30952
+ items: [],
30953
+ pageInfo: { hasNextPage: false, hasPreviousPage: offset > 0 },
30954
+ consumedCapacity: costTracker.getAggregated()
30955
+ };
30956
+ }
30957
+ const mainRecords = await fetchMainRecords(resource, allIds, costTracker, requestId);
30958
+ const recordMap = new Map(
30959
+ mainRecords.map((item) => {
30960
+ const data2 = item.data;
30961
+ return [data2.id, extractCleanRecord(item)];
30962
+ })
30963
+ );
30964
+ let items = allIds.map((id) => recordMap.get(id)).filter((r4) => r4 !== void 0);
30965
+ if (remainingFilters.length > 0) {
30966
+ items = items.filter((r4) => matchesAllFilters(r4, remainingFilters));
30967
+ }
30968
+ items = sortInMemory(items, sort);
30969
+ const page = items.slice(offset, offset + perPage);
30970
+ const hasNextPage = offset + perPage < items.length;
30971
+ const nextTokenValue = hasNextPage ? encodeOffsetToken(offset + perPage) : void 0;
30972
+ logger8.info("Filter-first query succeeded", {
30973
+ requestId,
30974
+ resource,
30975
+ filterField: filterCandidate.parsed.field,
30976
+ sortField: sort.field,
30977
+ totalMatched: items.length,
30978
+ offset,
30979
+ returned: page.length,
30980
+ hasNextPage
30981
+ });
30982
+ return {
30983
+ items: page,
30984
+ pageInfo: { hasNextPage, hasPreviousPage: offset > 0 },
30985
+ ...nextTokenValue && { nextToken: nextTokenValue },
30986
+ consumedCapacity: costTracker.getAggregated()
30987
+ };
30988
+ }
30896
30989
  function selectFilterFirstCandidate(parsedFilters, sortField, schema, perPage) {
30897
30990
  const eqFilters = parsedFilters.filter(
30898
30991
  (f4) => f4.parsed.operator === "$eq" && f4.parsed.field !== sortField
@@ -30900,7 +30993,7 @@ function selectFilterFirstCandidate(parsedFilters, sortField, schema, perPage) {
30900
30993
  if (eqFilters.length === 0) return void 0;
30901
30994
  const cardinality = schema?.cardinality;
30902
30995
  if (!cardinality) {
30903
- return eqFilters[0];
30996
+ return void 0;
30904
30997
  }
30905
30998
  const threshold = perPage / 2;
30906
30999
  const scored = eqFilters.map((f4) => ({ f: f4, score: cardinality[f4.parsed.field] ?? 0 })).filter(({ score }) => score > threshold).sort((a4, b4) => b4.score - a4.score);
@@ -31053,36 +31146,41 @@ async function fetchMainRecords(resource, recordIds, costTracker, requestId) {
31053
31146
  const dbClient2 = getDBClient();
31054
31147
  const tableName = getTableName();
31055
31148
  const uniqueRecordIds = Array.from(new Set(recordIds));
31056
- const batchGetResult = await executeDynamoDBOperation(
31057
- () => dbClient2.send(
31058
- new import_lib_dynamodb4.BatchGetCommand({
31059
- RequestItems: {
31060
- [tableName]: {
31061
- Keys: uniqueRecordIds.map((id) => ({
31062
- PK: resource,
31063
- SK: `id#${id}`
31064
- })),
31065
- ConsistentRead: true
31066
- }
31067
- },
31068
- ReturnConsumedCapacity: "TOTAL"
31069
- })
31070
- ),
31071
- "BatchGetItem"
31072
- );
31073
- if (batchGetResult.ConsumedCapacity) {
31074
- for (const capacity of batchGetResult.ConsumedCapacity) {
31075
- costTracker.add(capacity);
31149
+ const BATCH_SIZE = 100;
31150
+ const allRecords = [];
31151
+ for (let i4 = 0; i4 < uniqueRecordIds.length; i4 += BATCH_SIZE) {
31152
+ const chunk = uniqueRecordIds.slice(i4, i4 + BATCH_SIZE);
31153
+ const batchGetResult = await executeDynamoDBOperation(
31154
+ () => dbClient2.send(
31155
+ new import_lib_dynamodb4.BatchGetCommand({
31156
+ RequestItems: {
31157
+ [tableName]: {
31158
+ Keys: chunk.map((id) => ({
31159
+ PK: resource,
31160
+ SK: `id#${id}`
31161
+ })),
31162
+ ConsistentRead: true
31163
+ }
31164
+ },
31165
+ ReturnConsumedCapacity: "TOTAL"
31166
+ })
31167
+ ),
31168
+ "BatchGetItem"
31169
+ );
31170
+ if (batchGetResult.ConsumedCapacity) {
31171
+ for (const capacity of batchGetResult.ConsumedCapacity) {
31172
+ costTracker.add(capacity);
31173
+ }
31076
31174
  }
31175
+ allRecords.push(...batchGetResult.Responses?.[tableName] || []);
31077
31176
  }
31078
- const mainRecords = batchGetResult.Responses?.[tableName] || [];
31079
31177
  logger8.debug("Main records fetched", {
31080
31178
  requestId,
31081
31179
  resource,
31082
31180
  requestedCount: uniqueRecordIds.length,
31083
- fetchedCount: mainRecords.length
31181
+ fetchedCount: allRecords.length
31084
31182
  });
31085
- return mainRecords;
31183
+ return allRecords;
31086
31184
  }
31087
31185
  var import_lib_dynamodb4, logger8;
31088
31186
  var init_shadowQuery = __esm({
@@ -31100,6 +31198,7 @@ var init_shadowQuery = __esm({
31100
31198
  level: process.env.LOG_LEVEL || "info"
31101
31199
  });
31102
31200
  __name(executeShadowQuery, "executeShadowQuery");
31201
+ __name(executeFilterFirstQuery, "executeFilterFirstQuery");
31103
31202
  __name(selectFilterFirstCandidate, "selectFilterFirstCandidate");
31104
31203
  __name(sortInMemory, "sortInMemory");
31105
31204
  __name(executeShadowRecordQuery, "executeShadowRecordQuery");
@@ -34401,7 +34500,7 @@ async function handler(event) {
34401
34500
  return createCorsResponse(HTTP_STATUS.OK);
34402
34501
  }
34403
34502
  if (event.requestContext.http.method === "GET" && event.requestContext.http.path === "/version") {
34404
- const version = "1.4.8";
34503
+ const version = "1.4.10";
34405
34504
  return createSuccessResponse({ version, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, requestId);
34406
34505
  }
34407
34506
  if (event.requestContext.http.method !== "POST") {