@exabugs/dynamodb-client 0.1.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 +41 -0
- package/LICENSE +21 -0
- package/README.md +283 -0
- package/dist/client/Collection.d.ts +57 -0
- package/dist/client/Collection.d.ts.map +1 -0
- package/dist/client/Collection.js +174 -0
- package/dist/client/Collection.js.map +1 -0
- package/dist/client/Database.d.ts +35 -0
- package/dist/client/Database.d.ts.map +1 -0
- package/dist/client/Database.js +48 -0
- package/dist/client/Database.js.map +1 -0
- package/dist/client/DynamoClient.d.ts +43 -0
- package/dist/client/DynamoClient.d.ts.map +1 -0
- package/dist/client/DynamoClient.js +62 -0
- package/dist/client/DynamoClient.js.map +1 -0
- package/dist/client/FindCursor.d.ts +174 -0
- package/dist/client/FindCursor.d.ts.map +1 -0
- package/dist/client/FindCursor.js +256 -0
- package/dist/client/FindCursor.js.map +1 -0
- package/dist/client/aws-sigv4.d.ts +10 -0
- package/dist/client/aws-sigv4.d.ts.map +1 -0
- package/dist/client/aws-sigv4.js +54 -0
- package/dist/client/aws-sigv4.js.map +1 -0
- package/dist/client/index.cognito.d.ts +34 -0
- package/dist/client/index.cognito.d.ts.map +1 -0
- package/dist/client/index.cognito.js +30 -0
- package/dist/client/index.cognito.js.map +1 -0
- package/dist/client/index.d.ts +12 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.iam.d.ts +34 -0
- package/dist/client/index.iam.d.ts.map +1 -0
- package/dist/client/index.iam.js +28 -0
- package/dist/client/index.iam.js.map +1 -0
- package/dist/client/index.js +12 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/index.token.d.ts +33 -0
- package/dist/client/index.token.d.ts.map +1 -0
- package/dist/client/index.token.js +28 -0
- package/dist/client/index.token.js.map +1 -0
- package/dist/dynamodb.d.ts +20 -0
- package/dist/dynamodb.d.ts.map +1 -0
- package/dist/dynamodb.js +31 -0
- package/dist/dynamodb.js.map +1 -0
- package/dist/errors.d.ts +100 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +146 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/react-admin/dataProvider.d.ts +59 -0
- package/dist/integrations/react-admin/dataProvider.d.ts.map +1 -0
- package/dist/integrations/react-admin/dataProvider.js +364 -0
- package/dist/integrations/react-admin/dataProvider.js.map +1 -0
- package/dist/integrations/react-admin/index.d.ts +24 -0
- package/dist/integrations/react-admin/index.d.ts.map +1 -0
- package/dist/integrations/react-admin/index.js +23 -0
- package/dist/integrations/react-admin/index.js.map +1 -0
- package/dist/integrations/react-admin/types.d.ts +47 -0
- package/dist/integrations/react-admin/types.d.ts.map +1 -0
- package/dist/integrations/react-admin/types.js +5 -0
- package/dist/integrations/react-admin/types.js.map +1 -0
- package/dist/logger.d.ts +61 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +87 -0
- package/dist/logger.js.map +1 -0
- package/dist/scripts/repair-shadows.d.ts +3 -0
- package/dist/scripts/repair-shadows.d.ts.map +1 -0
- package/dist/scripts/repair-shadows.js +190 -0
- package/dist/scripts/repair-shadows.js.map +1 -0
- package/dist/server/handler.cjs +31378 -0
- package/dist/server/handler.cjs.map +7 -0
- package/dist/server/handler.d.ts +18 -0
- package/dist/server/handler.d.ts.map +1 -0
- package/dist/server/handler.js +435 -0
- package/dist/server/handler.js.map +1 -0
- package/dist/server/handler.zip +0 -0
- package/dist/server/index.d.ts +8 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +8 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/operations/deleteMany.d.ts +18 -0
- package/dist/server/operations/deleteMany.d.ts.map +1 -0
- package/dist/server/operations/deleteMany.js +222 -0
- package/dist/server/operations/deleteMany.js.map +1 -0
- package/dist/server/operations/deleteOne.d.ts +17 -0
- package/dist/server/operations/deleteOne.d.ts.map +1 -0
- package/dist/server/operations/deleteOne.js +87 -0
- package/dist/server/operations/deleteOne.js.map +1 -0
- package/dist/server/operations/find.d.ts +18 -0
- package/dist/server/operations/find.d.ts.map +1 -0
- package/dist/server/operations/find.js +382 -0
- package/dist/server/operations/find.js.map +1 -0
- package/dist/server/operations/findMany.d.ts +13 -0
- package/dist/server/operations/findMany.d.ts.map +1 -0
- package/dist/server/operations/findMany.js +61 -0
- package/dist/server/operations/findMany.js.map +1 -0
- package/dist/server/operations/findManyReference.d.ts +18 -0
- package/dist/server/operations/findManyReference.d.ts.map +1 -0
- package/dist/server/operations/findManyReference.js +150 -0
- package/dist/server/operations/findManyReference.js.map +1 -0
- package/dist/server/operations/findOne.d.ts +14 -0
- package/dist/server/operations/findOne.d.ts.map +1 -0
- package/dist/server/operations/findOne.js +56 -0
- package/dist/server/operations/findOne.js.map +1 -0
- package/dist/server/operations/insertMany.d.ts +19 -0
- package/dist/server/operations/insertMany.d.ts.map +1 -0
- package/dist/server/operations/insertMany.js +243 -0
- package/dist/server/operations/insertMany.js.map +1 -0
- package/dist/server/operations/insertOne.d.ts +18 -0
- package/dist/server/operations/insertOne.d.ts.map +1 -0
- package/dist/server/operations/insertOne.js +85 -0
- package/dist/server/operations/insertOne.js.map +1 -0
- package/dist/server/operations/updateMany.d.ts +20 -0
- package/dist/server/operations/updateMany.d.ts.map +1 -0
- package/dist/server/operations/updateMany.js +316 -0
- package/dist/server/operations/updateMany.js.map +1 -0
- package/dist/server/operations/updateOne.d.ts +20 -0
- package/dist/server/operations/updateOne.d.ts.map +1 -0
- package/dist/server/operations/updateOne.js +159 -0
- package/dist/server/operations/updateOne.js.map +1 -0
- package/dist/server/query/converter.d.ts +85 -0
- package/dist/server/query/converter.d.ts.map +1 -0
- package/dist/server/query/converter.js +161 -0
- package/dist/server/query/converter.js.map +1 -0
- package/dist/server/query/index.d.ts +5 -0
- package/dist/server/query/index.d.ts.map +1 -0
- package/dist/server/query/index.js +5 -0
- package/dist/server/query/index.js.map +1 -0
- package/dist/server/shadow/config.d.ts +147 -0
- package/dist/server/shadow/config.d.ts.map +1 -0
- package/dist/server/shadow/config.js +162 -0
- package/dist/server/shadow/config.js.map +1 -0
- package/dist/server/shadow/differ.d.ts +42 -0
- package/dist/server/shadow/differ.d.ts.map +1 -0
- package/dist/server/shadow/differ.js +66 -0
- package/dist/server/shadow/differ.js.map +1 -0
- package/dist/server/shadow/generator.d.ts +104 -0
- package/dist/server/shadow/generator.d.ts.map +1 -0
- package/dist/server/shadow/generator.js +148 -0
- package/dist/server/shadow/generator.js.map +1 -0
- package/dist/server/shadow/index.d.ts +11 -0
- package/dist/server/shadow/index.d.ts.map +1 -0
- package/dist/server/shadow/index.js +11 -0
- package/dist/server/shadow/index.js.map +1 -0
- package/dist/server/shadow/types.d.ts +44 -0
- package/dist/server/shadow/types.d.ts.map +1 -0
- package/dist/server/shadow/types.js +2 -0
- package/dist/server/shadow/types.js.map +1 -0
- package/dist/server/types.d.ts +295 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server/types.js +7 -0
- package/dist/server/types.js.map +1 -0
- package/dist/server/utils/auth.d.ts +43 -0
- package/dist/server/utils/auth.d.ts.map +1 -0
- package/dist/server/utils/auth.js +123 -0
- package/dist/server/utils/auth.js.map +1 -0
- package/dist/server/utils/bulkOperations.d.ts +81 -0
- package/dist/server/utils/bulkOperations.d.ts.map +1 -0
- package/dist/server/utils/bulkOperations.js +147 -0
- package/dist/server/utils/bulkOperations.js.map +1 -0
- package/dist/server/utils/chunking.d.ts +96 -0
- package/dist/server/utils/chunking.d.ts.map +1 -0
- package/dist/server/utils/chunking.js +225 -0
- package/dist/server/utils/chunking.js.map +1 -0
- package/dist/server/utils/dynamodb.d.ts +41 -0
- package/dist/server/utils/dynamodb.d.ts.map +1 -0
- package/dist/server/utils/dynamodb.js +83 -0
- package/dist/server/utils/dynamodb.js.map +1 -0
- package/dist/server/utils/filter.d.ts +152 -0
- package/dist/server/utils/filter.d.ts.map +1 -0
- package/dist/server/utils/filter.js +270 -0
- package/dist/server/utils/filter.js.map +1 -0
- package/dist/server/utils/pagination.d.ts +27 -0
- package/dist/server/utils/pagination.d.ts.map +1 -0
- package/dist/server/utils/pagination.js +56 -0
- package/dist/server/utils/pagination.js.map +1 -0
- package/dist/server/utils/timestamps.d.ts +31 -0
- package/dist/server/utils/timestamps.d.ts.map +1 -0
- package/dist/server/utils/timestamps.js +84 -0
- package/dist/server/utils/timestamps.js.map +1 -0
- package/dist/server/utils/ttl.d.ts +17 -0
- package/dist/server/utils/ttl.d.ts.map +1 -0
- package/dist/server/utils/ttl.js +62 -0
- package/dist/server/utils/ttl.js.map +1 -0
- package/dist/server/utils/validation.d.ts +40 -0
- package/dist/server/utils/validation.d.ts.map +1 -0
- package/dist/server/utils/validation.js +54 -0
- package/dist/server/utils/validation.js.map +1 -0
- package/dist/shadows/config.d.ts +54 -0
- package/dist/shadows/config.d.ts.map +1 -0
- package/dist/shadows/config.js +95 -0
- package/dist/shadows/config.js.map +1 -0
- package/dist/shadows/differ.d.ts +42 -0
- package/dist/shadows/differ.d.ts.map +1 -0
- package/dist/shadows/differ.js +66 -0
- package/dist/shadows/differ.js.map +1 -0
- package/dist/shadows/generator.d.ts +63 -0
- package/dist/shadows/generator.d.ts.map +1 -0
- package/dist/shadows/generator.js +107 -0
- package/dist/shadows/generator.js.map +1 -0
- package/dist/shadows/index.d.ts +15 -0
- package/dist/shadows/index.d.ts.map +1 -0
- package/dist/shadows/index.js +17 -0
- package/dist/shadows/index.js.map +1 -0
- package/dist/shadows/types.d.ts +44 -0
- package/dist/shadows/types.d.ts.map +1 -0
- package/dist/shadows/types.js +2 -0
- package/dist/shadows/types.js.map +1 -0
- package/dist/types.d.ts +165 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/ulid.d.ts +46 -0
- package/dist/ulid.d.ts.map +1 -0
- package/dist/ulid.js +66 -0
- package/dist/ulid.js.map +1 -0
- package/package.json +136 -0
- package/terraform/README.md +222 -0
- package/terraform/examples/advanced/README.md +129 -0
- package/terraform/examples/advanced/main.tf +158 -0
- package/terraform/examples/advanced/shadow.config.json +35 -0
- package/terraform/examples/advanced/variables.tf +28 -0
- package/terraform/examples/basic/README.md +53 -0
- package/terraform/examples/basic/main.tf +99 -0
- package/terraform/examples/basic/variables.tf +17 -0
- package/terraform/main.tf +159 -0
- package/terraform/outputs.tf +56 -0
- package/terraform/variables.tf +59 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* find 操作
|
|
3
|
+
* リスト取得(フィルター・ソート・ページネーション対応)
|
|
4
|
+
*
|
|
5
|
+
* 要件: 4.3, 5.4, 5.5, 12.1-12.12
|
|
6
|
+
*/
|
|
7
|
+
import { BatchGetCommand, QueryCommand } from '@aws-sdk/lib-dynamodb';
|
|
8
|
+
import { ConfigError, createLogger, getResourceSchema, getShadowConfig } from '../../index.js';
|
|
9
|
+
import { executeDynamoDBOperation, extractCleanRecord, getDBClient, getTableName, } from '../utils/dynamodb.js';
|
|
10
|
+
import { findOptimizableFilter, matchesAllFilters, parseFilterField, } from '../utils/filter.js';
|
|
11
|
+
import { decodeNextToken, encodeNextToken } from '../utils/pagination.js';
|
|
12
|
+
import { normalizePagination, normalizeSort, validateSortField } from '../utils/validation.js';
|
|
13
|
+
const logger = createLogger({ service: 'records-lambda' });
|
|
14
|
+
/**
|
|
15
|
+
* find 操作を実行する
|
|
16
|
+
*
|
|
17
|
+
* 処理フロー:
|
|
18
|
+
* 1. シャドー設定を読み込み、ソートフィールドを検証
|
|
19
|
+
* 2. シャドーレコードをQueryで取得(ソート済み)
|
|
20
|
+
* 3. 本体レコードをBatchGetItemで取得
|
|
21
|
+
* 4. フィルター条件を適用
|
|
22
|
+
* 5. ページネーション情報を生成してレスポンスを返す
|
|
23
|
+
*
|
|
24
|
+
* @param resource - リソース名
|
|
25
|
+
* @param params - findパラメータ
|
|
26
|
+
* @param requestId - リクエストID
|
|
27
|
+
* @returns リストデータ
|
|
28
|
+
*/
|
|
29
|
+
export async function handleFind(resource, params, requestId) {
|
|
30
|
+
logger.debug('Executing find', {
|
|
31
|
+
requestId,
|
|
32
|
+
resource,
|
|
33
|
+
params,
|
|
34
|
+
});
|
|
35
|
+
// シャドー設定を取得(環境変数からキャッシュ付き)
|
|
36
|
+
const shadowConfig = getShadowConfig();
|
|
37
|
+
logger.debug('Shadow config loaded', {
|
|
38
|
+
requestId,
|
|
39
|
+
resource,
|
|
40
|
+
hasResources: !!shadowConfig.resources,
|
|
41
|
+
resourceNames: Object.keys(shadowConfig.resources || {}),
|
|
42
|
+
});
|
|
43
|
+
// ソート条件を正規化(デフォルト値を適用)
|
|
44
|
+
logger.debug('Normalizing sort', {
|
|
45
|
+
requestId,
|
|
46
|
+
resource,
|
|
47
|
+
inputSort: params.sort,
|
|
48
|
+
});
|
|
49
|
+
const sort = normalizeSort(shadowConfig, resource, params.sort);
|
|
50
|
+
logger.debug('Sort normalized', {
|
|
51
|
+
requestId,
|
|
52
|
+
resource,
|
|
53
|
+
sort,
|
|
54
|
+
});
|
|
55
|
+
// ソートフィールドを検証
|
|
56
|
+
validateSortField(shadowConfig, resource, sort);
|
|
57
|
+
// ページネーション条件を正規化
|
|
58
|
+
const { perPage, nextToken } = normalizePagination(params.pagination);
|
|
59
|
+
// フィルター条件をパース(拡張フィールド構文対応)
|
|
60
|
+
// 要件: 12.1, 12.3, 12.4
|
|
61
|
+
const parsedFilters = [];
|
|
62
|
+
if (params.filter && Object.keys(params.filter).length > 0) {
|
|
63
|
+
for (const [fieldKey, value] of Object.entries(params.filter)) {
|
|
64
|
+
try {
|
|
65
|
+
const parsed = parseFilterField(fieldKey);
|
|
66
|
+
parsedFilters.push({ parsed, value });
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
logger.error('Invalid filter field syntax', {
|
|
70
|
+
requestId,
|
|
71
|
+
fieldKey,
|
|
72
|
+
error: error instanceof Error ? error.message : String(error),
|
|
73
|
+
});
|
|
74
|
+
throw new ConfigError(`Invalid filter field syntax: ${fieldKey}`, {
|
|
75
|
+
field: fieldKey,
|
|
76
|
+
error: error instanceof Error ? error.message : String(error),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const dbClient = getDBClient();
|
|
82
|
+
const tableName = getTableName();
|
|
83
|
+
// sort.field='id'の場合は本体レコードを直接クエリする(最適化)
|
|
84
|
+
// idフィールドは本体レコード(SK = id#{ULID})として既に存在するため、
|
|
85
|
+
// 別途シャドーレコードを参照する必要がない
|
|
86
|
+
if (sort.field === 'id') {
|
|
87
|
+
// filter.idが指定されている場合は、特定のIDのレコードを取得
|
|
88
|
+
const idFilter = parsedFilters.find((f) => f.parsed.field === 'id');
|
|
89
|
+
if (idFilter && idFilter.parsed.operator === 'eq') {
|
|
90
|
+
// 特定のIDのレコードを取得(GetItemの代わりにQueryを使用)
|
|
91
|
+
const targetId = String(idFilter.value);
|
|
92
|
+
const queryResult = await executeDynamoDBOperation(() => dbClient.send(new QueryCommand({
|
|
93
|
+
TableName: tableName,
|
|
94
|
+
KeyConditionExpression: 'PK = :pk AND SK = :sk',
|
|
95
|
+
ExpressionAttributeValues: {
|
|
96
|
+
':pk': resource,
|
|
97
|
+
':sk': `id#${targetId}`,
|
|
98
|
+
},
|
|
99
|
+
ConsistentRead: true,
|
|
100
|
+
})), 'Query');
|
|
101
|
+
const mainRecords = queryResult.Items || [];
|
|
102
|
+
// クリーンなレコードに変換
|
|
103
|
+
const items = mainRecords.map((item) => extractCleanRecord(item));
|
|
104
|
+
logger.info('find succeeded (id sort optimization with filter)', {
|
|
105
|
+
requestId,
|
|
106
|
+
resource,
|
|
107
|
+
count: items.length,
|
|
108
|
+
hasNextPage: false,
|
|
109
|
+
});
|
|
110
|
+
return {
|
|
111
|
+
items,
|
|
112
|
+
pageInfo: {
|
|
113
|
+
hasNextPage: false,
|
|
114
|
+
hasPreviousPage: false,
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
// filter.idが指定されていない場合は、全レコードを取得
|
|
119
|
+
// ExclusiveStartKeyの設定(nextTokenがある場合)
|
|
120
|
+
let exclusiveStartKey;
|
|
121
|
+
if (nextToken) {
|
|
122
|
+
const decoded = decodeNextToken(nextToken);
|
|
123
|
+
exclusiveStartKey = {
|
|
124
|
+
PK: decoded.PK,
|
|
125
|
+
SK: decoded.SK,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// 本体レコードを直接Queryで取得
|
|
129
|
+
const queryResult = await executeDynamoDBOperation(() => dbClient.send(new QueryCommand({
|
|
130
|
+
TableName: tableName,
|
|
131
|
+
KeyConditionExpression: 'PK = :pk AND begins_with(SK, :skPrefix)',
|
|
132
|
+
ExpressionAttributeValues: {
|
|
133
|
+
':pk': resource,
|
|
134
|
+
':skPrefix': 'id#',
|
|
135
|
+
},
|
|
136
|
+
ScanIndexForward: sort.order === 'ASC', // ASC: true, DESC: false
|
|
137
|
+
Limit: perPage,
|
|
138
|
+
ExclusiveStartKey: exclusiveStartKey,
|
|
139
|
+
ConsistentRead: true,
|
|
140
|
+
})), 'Query');
|
|
141
|
+
const mainRecords = queryResult.Items || [];
|
|
142
|
+
// 本体レコードが0件の場合は空レスポンスを返す
|
|
143
|
+
if (mainRecords.length === 0) {
|
|
144
|
+
return {
|
|
145
|
+
items: [],
|
|
146
|
+
pageInfo: {
|
|
147
|
+
hasNextPage: false,
|
|
148
|
+
hasPreviousPage: false,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
// クリーンなレコードに変換
|
|
153
|
+
let items = mainRecords.map((item) => extractCleanRecord(item));
|
|
154
|
+
// フィルター条件を適用(メモリ内フィルタリング)
|
|
155
|
+
// 要件: 12.6, 12.9
|
|
156
|
+
if (parsedFilters.length > 0) {
|
|
157
|
+
items = items.filter((record) => matchesAllFilters(record, parsedFilters));
|
|
158
|
+
}
|
|
159
|
+
// ページネーション情報を生成
|
|
160
|
+
// DynamoDBのクエリ結果がperPage件未満の場合、次のページは確実にない
|
|
161
|
+
// クエリ結果がperPage件ちょうどの場合、LastEvaluatedKeyがあれば次のページがある可能性がある
|
|
162
|
+
const hasNextPage = mainRecords.length < perPage ? false : queryResult.LastEvaluatedKey !== undefined;
|
|
163
|
+
const nextTokenValue = hasNextPage && queryResult.LastEvaluatedKey
|
|
164
|
+
? encodeNextToken(queryResult.LastEvaluatedKey.PK, queryResult.LastEvaluatedKey.SK)
|
|
165
|
+
: undefined;
|
|
166
|
+
logger.info('find succeeded (id sort optimization)', {
|
|
167
|
+
requestId,
|
|
168
|
+
resource,
|
|
169
|
+
count: items.length,
|
|
170
|
+
hasNextPage,
|
|
171
|
+
});
|
|
172
|
+
return {
|
|
173
|
+
items,
|
|
174
|
+
pageInfo: {
|
|
175
|
+
hasNextPage,
|
|
176
|
+
hasPreviousPage: !!nextToken,
|
|
177
|
+
},
|
|
178
|
+
...(nextTokenValue && { nextToken: nextTokenValue }),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
// 通常のシャドーレコードクエリ(sort.field != 'id'の場合)
|
|
182
|
+
// シャドーフィールドの型情報を取得
|
|
183
|
+
const shadowSchema = getResourceSchema(shadowConfig, resource);
|
|
184
|
+
const sortFieldType = shadowSchema.sortableFields[sort.field]?.type;
|
|
185
|
+
if (!sortFieldType) {
|
|
186
|
+
throw new ConfigError(`Sort field type not found: ${sort.field}`, {
|
|
187
|
+
field: sort.field,
|
|
188
|
+
resource,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
// Query最適化: ソートフィールドと一致するフィルター条件を検出
|
|
192
|
+
// 要件: 12.7
|
|
193
|
+
const optimizableFilter = findOptimizableFilter(sort.field, parsedFilters);
|
|
194
|
+
// シャドーSKのプレフィックスを生成
|
|
195
|
+
const skPrefix = `${sort.field}#`;
|
|
196
|
+
// ExclusiveStartKeyの設定(nextTokenがある場合)
|
|
197
|
+
let exclusiveStartKey;
|
|
198
|
+
if (nextToken) {
|
|
199
|
+
const decoded = decodeNextToken(nextToken);
|
|
200
|
+
exclusiveStartKey = {
|
|
201
|
+
PK: decoded.PK,
|
|
202
|
+
SK: decoded.SK,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
// KeyConditionExpressionを構築(Query最適化を適用)
|
|
206
|
+
let keyConditionExpression;
|
|
207
|
+
const expressionAttributeValues = {
|
|
208
|
+
':pk': resource,
|
|
209
|
+
};
|
|
210
|
+
if (optimizableFilter) {
|
|
211
|
+
// 最適化あり: ソートフィールドのフィルター条件をKeyConditionExpressionに含める
|
|
212
|
+
const { operator, type } = optimizableFilter.parsed;
|
|
213
|
+
const value = optimizableFilter.value;
|
|
214
|
+
// 値をシャドーSK形式にエンコード(簡易実装)
|
|
215
|
+
let encodedValue;
|
|
216
|
+
if (type === 'number') {
|
|
217
|
+
encodedValue = String(value).padStart(20, '0');
|
|
218
|
+
}
|
|
219
|
+
else if (type === 'date') {
|
|
220
|
+
encodedValue = new Date(String(value)).toISOString();
|
|
221
|
+
}
|
|
222
|
+
else if (type === 'boolean') {
|
|
223
|
+
encodedValue = String(value);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
// string型: エスケープ処理(# → ##、スペース → #)
|
|
227
|
+
encodedValue = String(value).replace(/#/g, '##').replace(/ /g, '#');
|
|
228
|
+
}
|
|
229
|
+
// SK形式: {field}#{encodedValue}#id#{recordId}
|
|
230
|
+
const skValue = `${sort.field}#${encodedValue}`;
|
|
231
|
+
switch (operator) {
|
|
232
|
+
case 'eq':
|
|
233
|
+
// 完全一致: {field}#{value}#id# で始まるレコードを取得
|
|
234
|
+
keyConditionExpression = 'PK = :pk AND begins_with(SK, :skValue)';
|
|
235
|
+
expressionAttributeValues[':skValue'] = `${skValue}#id#`;
|
|
236
|
+
break;
|
|
237
|
+
case 'gt':
|
|
238
|
+
// より大きい: {field}#{value}#id#~ より大きいSKを取得
|
|
239
|
+
// ~ は # の次の文字なので、{field}#{value}#id#{任意のID} より大きくなる
|
|
240
|
+
keyConditionExpression = 'PK = :pk AND SK > :skValue';
|
|
241
|
+
expressionAttributeValues[':skValue'] = `${skValue}#id#~`;
|
|
242
|
+
break;
|
|
243
|
+
case 'gte':
|
|
244
|
+
// 以上: {field}#{value}#id# 以上のSKを取得
|
|
245
|
+
keyConditionExpression = 'PK = :pk AND SK >= :skValue';
|
|
246
|
+
expressionAttributeValues[':skValue'] = `${skValue}#id#`;
|
|
247
|
+
break;
|
|
248
|
+
case 'lt':
|
|
249
|
+
// より小さい: {field}#{value}#id# より小さいSKを取得
|
|
250
|
+
keyConditionExpression = 'PK = :pk AND SK < :skValue';
|
|
251
|
+
expressionAttributeValues[':skValue'] = `${skValue}#id#`;
|
|
252
|
+
break;
|
|
253
|
+
case 'lte':
|
|
254
|
+
// 以下: {field}#{value}#id#~ 以下のSKを取得
|
|
255
|
+
keyConditionExpression = 'PK = :pk AND SK <= :skValue';
|
|
256
|
+
expressionAttributeValues[':skValue'] = `${skValue}#id#~`;
|
|
257
|
+
break;
|
|
258
|
+
case 'starts':
|
|
259
|
+
// 前方一致: {field}#{value} で始まるSKを取得
|
|
260
|
+
keyConditionExpression = 'PK = :pk AND begins_with(SK, :skValue)';
|
|
261
|
+
expressionAttributeValues[':skValue'] = `${skValue}`;
|
|
262
|
+
break;
|
|
263
|
+
default:
|
|
264
|
+
// フォールバック: 前方一致のみ
|
|
265
|
+
keyConditionExpression = 'PK = :pk AND begins_with(SK, :skPrefix)';
|
|
266
|
+
expressionAttributeValues[':skPrefix'] = skPrefix;
|
|
267
|
+
}
|
|
268
|
+
logger.debug('Query optimization applied', {
|
|
269
|
+
requestId,
|
|
270
|
+
sortField: sort.field,
|
|
271
|
+
operator,
|
|
272
|
+
type,
|
|
273
|
+
value,
|
|
274
|
+
skValue,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
// 最適化なし: 前方一致のみ
|
|
279
|
+
keyConditionExpression = 'PK = :pk AND begins_with(SK, :skPrefix)';
|
|
280
|
+
expressionAttributeValues[':skPrefix'] = skPrefix;
|
|
281
|
+
}
|
|
282
|
+
// シャドーレコードをQueryで取得
|
|
283
|
+
const queryResult = await executeDynamoDBOperation(() => dbClient.send(new QueryCommand({
|
|
284
|
+
TableName: tableName,
|
|
285
|
+
KeyConditionExpression: keyConditionExpression,
|
|
286
|
+
ExpressionAttributeValues: expressionAttributeValues,
|
|
287
|
+
ScanIndexForward: sort.order === 'ASC', // ASC: true, DESC: false
|
|
288
|
+
Limit: perPage,
|
|
289
|
+
ExclusiveStartKey: exclusiveStartKey,
|
|
290
|
+
ConsistentRead: true,
|
|
291
|
+
})), 'Query');
|
|
292
|
+
const shadowItems = queryResult.Items || [];
|
|
293
|
+
// シャドーレコードが0件の場合は空レスポンスを返す
|
|
294
|
+
if (shadowItems.length === 0) {
|
|
295
|
+
return {
|
|
296
|
+
items: [],
|
|
297
|
+
pageInfo: {
|
|
298
|
+
hasNextPage: false,
|
|
299
|
+
hasPreviousPage: false,
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
// シャドーSKからレコードIDを抽出
|
|
304
|
+
const recordIds = shadowItems.map((item) => {
|
|
305
|
+
const sk = item.SK;
|
|
306
|
+
// SK形式: {field}#{value}#id#{recordId}
|
|
307
|
+
const parts = sk.split('#id#');
|
|
308
|
+
return parts[parts.length - 1];
|
|
309
|
+
});
|
|
310
|
+
// 重複を除去(同じレコードに対して複数のシャドーレコードが存在する場合)
|
|
311
|
+
const uniqueRecordIds = Array.from(new Set(recordIds));
|
|
312
|
+
// 本体レコードをBatchGetItemで取得
|
|
313
|
+
const batchGetResult = await executeDynamoDBOperation(() => dbClient.send(new BatchGetCommand({
|
|
314
|
+
RequestItems: {
|
|
315
|
+
[tableName]: {
|
|
316
|
+
Keys: uniqueRecordIds.map((id) => ({
|
|
317
|
+
PK: resource,
|
|
318
|
+
SK: `id#${id}`,
|
|
319
|
+
})),
|
|
320
|
+
ConsistentRead: true,
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
})), 'BatchGetItem');
|
|
324
|
+
const mainRecords = batchGetResult.Responses?.[tableName] || [];
|
|
325
|
+
// IDでマッピングを作成(順序を保持するため)
|
|
326
|
+
const recordMap = new Map(mainRecords.map((item) => {
|
|
327
|
+
const data = item.data;
|
|
328
|
+
return [data.id, extractCleanRecord(item)];
|
|
329
|
+
}));
|
|
330
|
+
// シャドーの順序でレコードを並べる(重複を除去)
|
|
331
|
+
// 元のrecordIdsの順序を保持しつつ、重複を除去する
|
|
332
|
+
const seenIds = new Set();
|
|
333
|
+
let items = recordIds
|
|
334
|
+
.filter((id) => {
|
|
335
|
+
if (seenIds.has(id)) {
|
|
336
|
+
return false; // 既に処理済みのIDはスキップ
|
|
337
|
+
}
|
|
338
|
+
seenIds.add(id);
|
|
339
|
+
return true;
|
|
340
|
+
})
|
|
341
|
+
.map((id) => recordMap.get(id))
|
|
342
|
+
.filter((record) => record !== undefined);
|
|
343
|
+
// フィルター条件を適用(メモリ内フィルタリング)
|
|
344
|
+
// 要件: 12.6, 12.9, 12.11
|
|
345
|
+
// Query 最適化はあくまで候補を絞り込むためのもので、完全なフィルタリングではない
|
|
346
|
+
// (例: starts オペレーターは SK の前方一致だが、値の途中にマッチする可能性がある)
|
|
347
|
+
// そのため、すべてのフィルター条件をメモリ内で再適用する
|
|
348
|
+
if (parsedFilters.length > 0) {
|
|
349
|
+
const itemsBeforeFilter = items.length;
|
|
350
|
+
items = items.filter((record) => matchesAllFilters(record, parsedFilters));
|
|
351
|
+
logger.debug('Memory filtering applied', {
|
|
352
|
+
requestId,
|
|
353
|
+
filtersCount: parsedFilters.length,
|
|
354
|
+
itemsBeforeFilter,
|
|
355
|
+
itemsAfterFilter: items.length,
|
|
356
|
+
filtered: itemsBeforeFilter - items.length,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
// ページネーション情報を生成
|
|
360
|
+
// DynamoDBのクエリ結果がperPage件未満の場合、次のページは確実にない
|
|
361
|
+
// クエリ結果がperPage件ちょうどの場合、LastEvaluatedKeyがあれば次のページがある可能性がある
|
|
362
|
+
// (フィルタリング後のitemsが少なくても、次のページにフィルタリング後のデータがある可能性がある)
|
|
363
|
+
const hasNextPage = shadowItems.length < perPage ? false : queryResult.LastEvaluatedKey !== undefined;
|
|
364
|
+
const nextTokenValue = hasNextPage && queryResult.LastEvaluatedKey
|
|
365
|
+
? encodeNextToken(queryResult.LastEvaluatedKey.PK, queryResult.LastEvaluatedKey.SK)
|
|
366
|
+
: undefined;
|
|
367
|
+
logger.info('find succeeded', {
|
|
368
|
+
requestId,
|
|
369
|
+
resource,
|
|
370
|
+
count: items.length,
|
|
371
|
+
hasNextPage,
|
|
372
|
+
});
|
|
373
|
+
return {
|
|
374
|
+
items,
|
|
375
|
+
pageInfo: {
|
|
376
|
+
hasNextPage,
|
|
377
|
+
hasPreviousPage: !!nextToken,
|
|
378
|
+
},
|
|
379
|
+
...(nextTokenValue && { nextToken: nextTokenValue }),
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
//# sourceMappingURL=find.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find.js","sourceRoot":"","sources":["../../../src/server/operations/find.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FindManyParams, FindManyResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* findMany 操作を実行する
|
|
4
|
+
*
|
|
5
|
+
* BatchGetItemで複数のメインレコードを取得し、__shadowKeysを除外してレスポンスを返す。
|
|
6
|
+
*
|
|
7
|
+
* @param resource - リソース名
|
|
8
|
+
* @param params - findManyパラメータ
|
|
9
|
+
* @param requestId - リクエストID
|
|
10
|
+
* @returns レコードデータの配列
|
|
11
|
+
*/
|
|
12
|
+
export declare function handleFindMany(resource: string, params: FindManyParams, requestId: string): Promise<FindManyResult>;
|
|
13
|
+
//# sourceMappingURL=findMany.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findMany.d.ts","sourceRoot":"","sources":["../../../src/server/operations/findMany.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAUlE;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,CAAC,CAqDzB"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* findMany 操作
|
|
3
|
+
* 複数レコードをIDリストで取得する
|
|
4
|
+
*
|
|
5
|
+
* 要件: 4.3, 5.4, 5.5
|
|
6
|
+
*/
|
|
7
|
+
import { BatchGetCommand } from '@aws-sdk/lib-dynamodb';
|
|
8
|
+
import { createLogger } from '../../index.js';
|
|
9
|
+
import { generateMainRecordSK } from '../shadow/index.js';
|
|
10
|
+
import { executeDynamoDBOperation, extractCleanRecord, getDBClient, getTableName, } from '../utils/dynamodb.js';
|
|
11
|
+
const logger = createLogger({ service: 'records-lambda' });
|
|
12
|
+
/**
|
|
13
|
+
* findMany 操作を実行する
|
|
14
|
+
*
|
|
15
|
+
* BatchGetItemで複数のメインレコードを取得し、__shadowKeysを除外してレスポンスを返す。
|
|
16
|
+
*
|
|
17
|
+
* @param resource - リソース名
|
|
18
|
+
* @param params - findManyパラメータ
|
|
19
|
+
* @param requestId - リクエストID
|
|
20
|
+
* @returns レコードデータの配列
|
|
21
|
+
*/
|
|
22
|
+
export async function handleFindMany(resource, params, requestId) {
|
|
23
|
+
const { ids } = params;
|
|
24
|
+
logger.debug('Executing findMany', {
|
|
25
|
+
requestId,
|
|
26
|
+
resource,
|
|
27
|
+
count: ids.length,
|
|
28
|
+
});
|
|
29
|
+
// IDが空の場合は空配列を返す
|
|
30
|
+
if (ids.length === 0) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
const dbClient = getDBClient();
|
|
34
|
+
const tableName = getTableName();
|
|
35
|
+
// BatchGetItemのキーを生成
|
|
36
|
+
const keys = ids.map((id) => ({
|
|
37
|
+
PK: resource,
|
|
38
|
+
SK: generateMainRecordSK(id),
|
|
39
|
+
}));
|
|
40
|
+
// BatchGetItemでレコードを取得(ConsistentRead=true)
|
|
41
|
+
const result = await executeDynamoDBOperation(() => dbClient.send(new BatchGetCommand({
|
|
42
|
+
RequestItems: {
|
|
43
|
+
[tableName]: {
|
|
44
|
+
Keys: keys,
|
|
45
|
+
ConsistentRead: true,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
})), 'BatchGetItem');
|
|
49
|
+
// レスポンスからレコードを抽出
|
|
50
|
+
const items = result.Responses?.[tableName] || [];
|
|
51
|
+
// data属性から__shadowKeysを除外
|
|
52
|
+
const records = items.map((item) => extractCleanRecord(item));
|
|
53
|
+
logger.info('findMany succeeded', {
|
|
54
|
+
requestId,
|
|
55
|
+
resource,
|
|
56
|
+
requested: ids.length,
|
|
57
|
+
found: records.length,
|
|
58
|
+
});
|
|
59
|
+
return records;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=findMany.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findMany.js","sourceRoot":"","sources":["../../../src/server/operations/findMany.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE1D,OAAO,EACL,wBAAwB,EACxB,kBAAkB,EAClB,WAAW,EACX,YAAY,GACb,MAAM,sBAAsB,CAAC;AAE9B,MAAM,MAAM,GAAG,YAAY,CAAC,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC,CAAC;AAE3D;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,QAAgB,EAChB,MAAsB,EACtB,SAAiB;IAEjB,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC;IAEvB,MAAM,CAAC,KAAK,CAAC,oBAAoB,EAAE;QACjC,SAAS;QACT,QAAQ;QACR,KAAK,EAAE,GAAG,CAAC,MAAM;KAClB,CAAC,CAAC;IAEH,iBAAiB;IACjB,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IAEjC,qBAAqB;IACrB,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QAC5B,EAAE,EAAE,QAAQ;QACZ,EAAE,EAAE,oBAAoB,CAAC,EAAE,CAAC;KAC7B,CAAC,CAAC,CAAC;IAEJ,4CAA4C;IAC5C,MAAM,MAAM,GAAG,MAAM,wBAAwB,CAC3C,GAAG,EAAE,CACH,QAAQ,CAAC,IAAI,CACX,IAAI,eAAe,CAAC;QAClB,YAAY,EAAE;YACZ,CAAC,SAAS,CAAC,EAAE;gBACX,IAAI,EAAE,IAAI;gBACV,cAAc,EAAE,IAAI;aACrB;SACF;KACF,CAAC,CACH,EACH,cAAc,CACf,CAAC;IAEF,iBAAiB;IACjB,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;IAElD,0BAA0B;IAC1B,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;IAE9D,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE;QAChC,SAAS;QACT,QAAQ;QACR,SAAS,EAAE,GAAG,CAAC,MAAM;QACrB,KAAK,EAAE,OAAO,CAAC,MAAM;KACtB,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FindManyReferenceParams, FindManyReferenceResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* findManyReference 操作を実行する
|
|
4
|
+
*
|
|
5
|
+
* 処理フロー:
|
|
6
|
+
* 1. シャドー設定を読み込み、ソートフィールドを検証
|
|
7
|
+
* 2. シャドーレコードをQueryで取得(ソート済み)
|
|
8
|
+
* 3. 本体レコードをBatchGetItemで取得
|
|
9
|
+
* 4. target/idフィルターとユーザー指定フィルターを適用
|
|
10
|
+
* 5. ページネーション情報を生成してレスポンスを返す
|
|
11
|
+
*
|
|
12
|
+
* @param resource - リソース名
|
|
13
|
+
* @param params - findManyReferenceパラメータ
|
|
14
|
+
* @param requestId - リクエストID
|
|
15
|
+
* @returns リストデータ
|
|
16
|
+
*/
|
|
17
|
+
export declare function handleFindManyReference(resource: string, params: FindManyReferenceParams, requestId: string): Promise<FindManyReferenceResult>;
|
|
18
|
+
//# sourceMappingURL=findManyReference.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findManyReference.d.ts","sourceRoot":"","sources":["../../../src/server/operations/findManyReference.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAYpF;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,uBAAuB,EAC/B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,uBAAuB,CAAC,CAkKlC"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* findManyReference 操作
|
|
3
|
+
* 参照レコード取得(外部キー指定)
|
|
4
|
+
*
|
|
5
|
+
* 要件: 4.3, 5.4, 5.5
|
|
6
|
+
*/
|
|
7
|
+
import { BatchGetCommand, QueryCommand } from '@aws-sdk/lib-dynamodb';
|
|
8
|
+
import { ConfigError, createLogger, getResourceSchema, getShadowConfig } from '../../index.js';
|
|
9
|
+
import { executeDynamoDBOperation, extractCleanRecord, getDBClient, getTableName, } from '../utils/dynamodb.js';
|
|
10
|
+
import { decodeNextToken, encodeNextToken } from '../utils/pagination.js';
|
|
11
|
+
import { normalizePagination, normalizeSort, validateSortField } from '../utils/validation.js';
|
|
12
|
+
const logger = createLogger({ service: 'records-lambda' });
|
|
13
|
+
/**
|
|
14
|
+
* findManyReference 操作を実行する
|
|
15
|
+
*
|
|
16
|
+
* 処理フロー:
|
|
17
|
+
* 1. シャドー設定を読み込み、ソートフィールドを検証
|
|
18
|
+
* 2. シャドーレコードをQueryで取得(ソート済み)
|
|
19
|
+
* 3. 本体レコードをBatchGetItemで取得
|
|
20
|
+
* 4. target/idフィルターとユーザー指定フィルターを適用
|
|
21
|
+
* 5. ページネーション情報を生成してレスポンスを返す
|
|
22
|
+
*
|
|
23
|
+
* @param resource - リソース名
|
|
24
|
+
* @param params - findManyReferenceパラメータ
|
|
25
|
+
* @param requestId - リクエストID
|
|
26
|
+
* @returns リストデータ
|
|
27
|
+
*/
|
|
28
|
+
export async function handleFindManyReference(resource, params, requestId) {
|
|
29
|
+
const { target, id, filter, sort: sortParam, pagination } = params;
|
|
30
|
+
logger.debug('Executing findManyReference', {
|
|
31
|
+
requestId,
|
|
32
|
+
resource,
|
|
33
|
+
target,
|
|
34
|
+
id,
|
|
35
|
+
});
|
|
36
|
+
// シャドー設定を取得(環境変数からキャッシュ付き)
|
|
37
|
+
const shadowConfig = getShadowConfig();
|
|
38
|
+
// ソート条件を正規化(デフォルト値を適用)
|
|
39
|
+
const sort = normalizeSort(shadowConfig, resource, sortParam);
|
|
40
|
+
// ソートフィールドを検証
|
|
41
|
+
validateSortField(shadowConfig, resource, sort);
|
|
42
|
+
// ページネーション条件を正規化
|
|
43
|
+
const { perPage, nextToken } = normalizePagination(pagination);
|
|
44
|
+
const dbClient = getDBClient();
|
|
45
|
+
const tableName = getTableName();
|
|
46
|
+
// シャドーフィールドの型情報を取得
|
|
47
|
+
const shadowSchema = getResourceSchema(shadowConfig, resource);
|
|
48
|
+
const sortFieldType = shadowSchema.sortableFields[sort.field]?.type;
|
|
49
|
+
if (!sortFieldType) {
|
|
50
|
+
throw new ConfigError(`Sort field type not found: ${sort.field}`, {
|
|
51
|
+
field: sort.field,
|
|
52
|
+
resource,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// シャドーSKのプレフィックスを生成
|
|
56
|
+
const skPrefix = `${sort.field}#`;
|
|
57
|
+
// ExclusiveStartKeyの設定(nextTokenがある場合)
|
|
58
|
+
let exclusiveStartKey;
|
|
59
|
+
if (nextToken) {
|
|
60
|
+
const decoded = decodeNextToken(nextToken);
|
|
61
|
+
exclusiveStartKey = {
|
|
62
|
+
PK: decoded.PK,
|
|
63
|
+
SK: decoded.SK,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// シャドーレコードをQueryで取得
|
|
67
|
+
const queryResult = await executeDynamoDBOperation(() => dbClient.send(new QueryCommand({
|
|
68
|
+
TableName: tableName,
|
|
69
|
+
KeyConditionExpression: 'PK = :pk AND begins_with(SK, :skPrefix)',
|
|
70
|
+
ExpressionAttributeValues: {
|
|
71
|
+
':pk': resource,
|
|
72
|
+
':skPrefix': skPrefix,
|
|
73
|
+
},
|
|
74
|
+
ScanIndexForward: sort.order === 'ASC',
|
|
75
|
+
Limit: perPage,
|
|
76
|
+
ExclusiveStartKey: exclusiveStartKey,
|
|
77
|
+
ConsistentRead: true,
|
|
78
|
+
})), 'Query');
|
|
79
|
+
const shadowItems = queryResult.Items || [];
|
|
80
|
+
// シャドーレコードが0件の場合は空レスポンスを返す
|
|
81
|
+
if (shadowItems.length === 0) {
|
|
82
|
+
return {
|
|
83
|
+
items: [],
|
|
84
|
+
pageInfo: {
|
|
85
|
+
hasNextPage: false,
|
|
86
|
+
hasPreviousPage: false,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// シャドーSKからレコードIDを抽出
|
|
91
|
+
const recordIds = shadowItems.map((item) => {
|
|
92
|
+
const sk = item.SK;
|
|
93
|
+
const parts = sk.split('#id#');
|
|
94
|
+
return parts[parts.length - 1];
|
|
95
|
+
});
|
|
96
|
+
// 本体レコードをBatchGetItemで取得
|
|
97
|
+
const batchGetResult = await executeDynamoDBOperation(() => dbClient.send(new BatchGetCommand({
|
|
98
|
+
RequestItems: {
|
|
99
|
+
[tableName]: {
|
|
100
|
+
Keys: recordIds.map((recordId) => ({
|
|
101
|
+
PK: resource,
|
|
102
|
+
SK: `id#${recordId}`,
|
|
103
|
+
})),
|
|
104
|
+
ConsistentRead: true,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
})), 'BatchGetItem');
|
|
108
|
+
const mainRecords = batchGetResult.Responses?.[tableName] || [];
|
|
109
|
+
// IDでマッピングを作成
|
|
110
|
+
const recordMap = new Map(mainRecords.map((item) => {
|
|
111
|
+
const data = item.data;
|
|
112
|
+
return [data.id, extractCleanRecord(item)];
|
|
113
|
+
}));
|
|
114
|
+
// シャドーの順序でレコードを並べる
|
|
115
|
+
let items = recordIds
|
|
116
|
+
.map((recordId) => recordMap.get(recordId))
|
|
117
|
+
.filter((record) => record !== undefined);
|
|
118
|
+
// target/idフィルターを適用
|
|
119
|
+
items = items.filter((record) => record[target] === id);
|
|
120
|
+
// 追加のフィルター条件を適用
|
|
121
|
+
if (filter && Object.keys(filter).length > 0) {
|
|
122
|
+
items = items.filter((record) => {
|
|
123
|
+
return Object.entries(filter).every(([key, value]) => {
|
|
124
|
+
return record[key] === value;
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// ページネーション情報を生成
|
|
129
|
+
const hasNextPage = queryResult.LastEvaluatedKey !== undefined;
|
|
130
|
+
const nextTokenValue = hasNextPage && queryResult.LastEvaluatedKey
|
|
131
|
+
? encodeNextToken(queryResult.LastEvaluatedKey.PK, queryResult.LastEvaluatedKey.SK)
|
|
132
|
+
: undefined;
|
|
133
|
+
logger.info('findManyReference succeeded', {
|
|
134
|
+
requestId,
|
|
135
|
+
resource,
|
|
136
|
+
target,
|
|
137
|
+
id,
|
|
138
|
+
count: items.length,
|
|
139
|
+
hasNextPage,
|
|
140
|
+
});
|
|
141
|
+
return {
|
|
142
|
+
items,
|
|
143
|
+
pageInfo: {
|
|
144
|
+
hasNextPage,
|
|
145
|
+
hasPreviousPage: !!nextToken,
|
|
146
|
+
},
|
|
147
|
+
...(nextTokenValue && { nextToken: nextTokenValue }),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=findManyReference.js.map
|