@exabugs/dynamodb-client 1.26.0 → 1.27.1
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 +45 -0
- package/dist/__generated__/models/apiOperation.d.ts +1 -0
- package/dist/__generated__/models/apiOperation.d.ts.map +1 -1
- package/dist/__generated__/models/apiOperation.js +1 -0
- package/dist/__generated__/models/apiOperation.js.map +1 -1
- package/dist/__generated__/models/bulkOperationResult.d.ts +2 -0
- package/dist/__generated__/models/bulkOperationResult.d.ts.map +1 -1
- package/dist/__generated__/models/countParams.d.ts +31 -0
- package/dist/__generated__/models/countParams.d.ts.map +1 -0
- package/dist/__generated__/models/countParams.js +2 -0
- package/dist/__generated__/models/countParams.js.map +1 -0
- package/dist/__generated__/models/countResult.d.ts +37 -0
- package/dist/__generated__/models/countResult.d.ts.map +1 -0
- package/dist/__generated__/models/countResult.js +2 -0
- package/dist/__generated__/models/countResult.js.map +1 -0
- package/dist/__generated__/models/countResultConsumedCapacity.d.ts +35 -0
- package/dist/__generated__/models/countResultConsumedCapacity.d.ts.map +1 -0
- package/dist/__generated__/models/countResultConsumedCapacity.js +25 -0
- package/dist/__generated__/models/countResultConsumedCapacity.js.map +1 -0
- package/dist/__generated__/models/executeOperationBodyOp.d.ts +1 -0
- package/dist/__generated__/models/executeOperationBodyOp.d.ts.map +1 -1
- package/dist/__generated__/models/executeOperationBodyOp.js +1 -0
- package/dist/__generated__/models/executeOperationBodyOp.js.map +1 -1
- package/dist/__generated__/models/findResultPageInfo.d.ts +2 -0
- package/dist/__generated__/models/findResultPageInfo.d.ts.map +1 -1
- package/dist/__generated__/models/index.d.ts +3 -0
- package/dist/__generated__/models/index.d.ts.map +1 -1
- package/dist/__generated__/models/index.js +3 -0
- package/dist/__generated__/models/index.js.map +1 -1
- package/dist/client/Collection.d.ts +7 -0
- package/dist/client/Collection.d.ts.map +1 -1
- package/dist/client/Collection.js +20 -1
- package/dist/client/Collection.js.map +1 -1
- package/dist/scripts/repair-shadows.js +86 -42
- package/dist/scripts/repair-shadows.js.map +1 -1
- package/dist/scripts/utils/cliParser.d.ts +9 -22
- package/dist/scripts/utils/cliParser.d.ts.map +1 -1
- package/dist/scripts/utils/cliParser.js +14 -37
- package/dist/scripts/utils/cliParser.js.map +1 -1
- package/dist/scripts/utils/shadowAudit.d.ts +49 -0
- package/dist/scripts/utils/shadowAudit.d.ts.map +1 -0
- package/dist/scripts/utils/shadowAudit.js +84 -0
- package/dist/scripts/utils/shadowAudit.js.map +1 -0
- package/dist/scripts/utils/shadowRepair.d.ts +42 -0
- package/dist/scripts/utils/shadowRepair.d.ts.map +1 -0
- package/dist/scripts/utils/shadowRepair.js +110 -0
- package/dist/scripts/utils/shadowRepair.js.map +1 -0
- package/dist/server/handler.cjs +1237 -1122
- package/dist/server/handler.cjs.map +4 -4
- package/dist/server/handler.js +2 -2
- package/dist/server/handler.js.map +1 -1
- package/dist/server/media-handler.cjs +2 -2
- package/dist/server/media-process-handler-dlq.cjs +19 -3
- package/dist/server/media-process-handler-dlq.cjs.map +2 -2
- package/dist/server/media-process-handler.cjs +19 -3
- package/dist/server/media-process-handler.cjs.map +2 -2
- package/dist/server/operations/count.d.ts +3 -0
- package/dist/server/operations/count.d.ts.map +1 -0
- package/dist/server/operations/count.js +81 -0
- package/dist/server/operations/count.js.map +1 -0
- package/dist/server/operations/deleteMany.d.ts.map +1 -1
- package/dist/server/operations/deleteMany.js +2 -1
- package/dist/server/operations/deleteMany.js.map +1 -1
- package/dist/server/operations/deleteOne.d.ts.map +1 -1
- package/dist/server/operations/deleteOne.js +3 -12
- package/dist/server/operations/deleteOne.js.map +1 -1
- package/dist/server/operations/find/handler.d.ts.map +1 -1
- package/dist/server/operations/find/handler.js +4 -1
- package/dist/server/operations/find/handler.js.map +1 -1
- package/dist/server/operations/find/keyCondition.d.ts +38 -0
- package/dist/server/operations/find/keyCondition.d.ts.map +1 -0
- package/dist/server/operations/find/keyCondition.js +184 -0
- package/dist/server/operations/find/keyCondition.js.map +1 -0
- package/dist/server/operations/find/nearQuery.d.ts +4 -1
- package/dist/server/operations/find/nearQuery.d.ts.map +1 -1
- package/dist/server/operations/find/nearQuery.js +30 -42
- package/dist/server/operations/find/nearQuery.js.map +1 -1
- package/dist/server/operations/find/shadowQuery.d.ts.map +1 -1
- package/dist/server/operations/find/shadowQuery.js +16 -143
- package/dist/server/operations/find/shadowQuery.js.map +1 -1
- package/dist/server/operations/find/utils.d.ts.map +1 -1
- package/dist/server/operations/find/utils.js +0 -5
- package/dist/server/operations/find/utils.js.map +1 -1
- package/dist/server/operations/insertMany.d.ts.map +1 -1
- package/dist/server/operations/insertMany.js +1 -4
- package/dist/server/operations/insertMany.js.map +1 -1
- package/dist/server/operations/insertOne.d.ts.map +1 -1
- package/dist/server/operations/insertOne.js +2 -6
- package/dist/server/operations/insertOne.js.map +1 -1
- package/dist/server/operations/operationDispatcher.d.ts.map +1 -1
- package/dist/server/operations/operationDispatcher.js +3 -0
- package/dist/server/operations/operationDispatcher.js.map +1 -1
- package/dist/server/operations/updateMany.d.ts.map +1 -1
- package/dist/server/operations/updateMany.js +23 -17
- package/dist/server/operations/updateMany.js.map +1 -1
- package/dist/server/operations/updateOne.d.ts.map +1 -1
- package/dist/server/operations/updateOne.js +10 -21
- package/dist/server/operations/updateOne.js.map +1 -1
- package/dist/server/query/nearSearch.d.ts +37 -25
- package/dist/server/query/nearSearch.d.ts.map +1 -1
- package/dist/server/query/nearSearch.js +87 -125
- package/dist/server/query/nearSearch.js.map +1 -1
- package/dist/server/types.d.ts +7 -1
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/utils/bulkError.d.ts +12 -0
- package/dist/server/utils/bulkError.d.ts.map +1 -0
- package/dist/server/utils/bulkError.js +21 -0
- package/dist/server/utils/bulkError.js.map +1 -0
- package/dist/server/utils/chunking.d.ts +9 -1
- package/dist/server/utils/chunking.d.ts.map +1 -1
- package/dist/server/utils/chunking.js +20 -3
- package/dist/server/utils/chunking.js.map +1 -1
- package/dist/server/utils/filter.d.ts +3 -105
- package/dist/server/utils/filter.d.ts.map +1 -1
- package/dist/server/utils/filter.js +14 -218
- package/dist/server/utils/filter.js.map +1 -1
- package/dist/server/utils/updateOperators.d.ts +12 -1
- package/dist/server/utils/updateOperators.d.ts.map +1 -1
- package/dist/server/utils/updateOperators.js +53 -6
- package/dist/server/utils/updateOperators.js.map +1 -1
- package/docs/README.md +9 -12
- package/package.json +1 -1
- package/dist/scripts/operations/bulkRecordRepair.d.ts +0 -17
- package/dist/scripts/operations/bulkRecordRepair.d.ts.map +0 -1
- package/dist/scripts/operations/bulkRecordRepair.js +0 -76
- package/dist/scripts/operations/bulkRecordRepair.js.map +0 -1
- package/dist/scripts/operations/singleRecordRepair.d.ts +0 -18
- package/dist/scripts/operations/singleRecordRepair.d.ts.map +0 -1
- package/dist/scripts/operations/singleRecordRepair.js +0 -46
- package/dist/scripts/operations/singleRecordRepair.js.map +0 -1
- package/dist/scripts/utils/awsUtils.d.ts +0 -15
- package/dist/scripts/utils/awsUtils.d.ts.map +0 -1
- package/dist/scripts/utils/awsUtils.js +0 -31
- package/dist/scripts/utils/awsUtils.js.map +0 -1
- package/dist/scripts/utils/clientFactory.d.ts +0 -22
- package/dist/scripts/utils/clientFactory.d.ts.map +0 -1
- package/dist/scripts/utils/clientFactory.js +0 -36
- package/dist/scripts/utils/clientFactory.js.map +0 -1
- package/dist/scripts/utils/repairUtils.d.ts +0 -58
- package/dist/scripts/utils/repairUtils.d.ts.map +0 -1
- package/dist/scripts/utils/repairUtils.js +0 -52
- package/dist/scripts/utils/repairUtils.js.map +0 -1
- package/dist/server/utils/ttl.d.ts +0 -17
- package/dist/server/utils/ttl.d.ts.map +0 -1
- package/dist/server/utils/ttl.js +0 -64
- package/dist/server/utils/ttl.js.map +0 -1
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* FilterExpression の構築とシャドーレコード除外条件の生成
|
|
2
|
+
* フィルターのフィールド指定(`field:$op:type`)の解析
|
|
4
3
|
*
|
|
5
|
-
* 要件:
|
|
4
|
+
* 要件: 12.1-12.12
|
|
6
5
|
*/
|
|
7
6
|
/**
|
|
8
7
|
* フィルター演算子(MongoDB互換)
|
|
9
8
|
*
|
|
10
9
|
* 要件: 12.2
|
|
11
10
|
*/
|
|
12
|
-
export type FilterOperator = '$eq' | '$ne' | '$lt' | '$lte' | '$gt' | '$gte' | '$in' | '$nin' | '$starts' | '$ends' | '$contains' | '$
|
|
11
|
+
export type FilterOperator = '$eq' | '$ne' | '$lt' | '$lte' | '$gt' | '$gte' | '$in' | '$nin' | '$starts' | '$ends' | '$contains' | '$exists' | '$near';
|
|
13
12
|
/**
|
|
14
13
|
* フィルター型
|
|
15
14
|
*
|
|
@@ -29,17 +28,6 @@ export interface ParsedFilterField {
|
|
|
29
28
|
/** データ型 */
|
|
30
29
|
type: FilterType;
|
|
31
30
|
}
|
|
32
|
-
/**
|
|
33
|
-
* フィルター式の構築結果
|
|
34
|
-
*/
|
|
35
|
-
export interface FilterExpressionResult {
|
|
36
|
-
/** FilterExpression 文字列 */
|
|
37
|
-
expression: string;
|
|
38
|
-
/** ExpressionAttributeNames マッピング */
|
|
39
|
-
names: Record<string, string>;
|
|
40
|
-
/** ExpressionAttributeValues マッピング */
|
|
41
|
-
values: Record<string, unknown>;
|
|
42
|
-
}
|
|
43
31
|
/**
|
|
44
32
|
* フィルターフィールド名をパースする
|
|
45
33
|
*
|
|
@@ -59,94 +47,4 @@ export interface FilterExpressionResult {
|
|
|
59
47
|
* @throws Error - 構文エラーの場合
|
|
60
48
|
*/
|
|
61
49
|
export declare function parseFilterField(fieldKey: string): ParsedFilterField;
|
|
62
|
-
/**
|
|
63
|
-
* シャドーレコードを除外するフィルター式を生成する
|
|
64
|
-
*
|
|
65
|
-
* シャドーレコードの特徴:
|
|
66
|
-
* - SK に '#' が含まれる(例: "name#value#id#ULID")
|
|
67
|
-
* - 本体レコードは "id#ULID" 形式で '#' が1つのみ
|
|
68
|
-
*
|
|
69
|
-
* 除外条件:
|
|
70
|
-
* - attribute_exists(data) AND NOT contains(SK, '#id#')
|
|
71
|
-
*
|
|
72
|
-
* @returns シャドー除外フィルター式
|
|
73
|
-
*/
|
|
74
|
-
export declare function createShadowExclusionFilter(): FilterExpressionResult;
|
|
75
|
-
/**
|
|
76
|
-
* ユーザー指定のフィルター条件から FilterExpression を構築する
|
|
77
|
-
*
|
|
78
|
-
* 注意: この実装は基本的な等価比較のみをサポート
|
|
79
|
-
* より複雑な条件(範囲検索、部分一致など)は将来の拡張として実装可能
|
|
80
|
-
*
|
|
81
|
-
* @param filter - フィルター条件(キー: フィールド名、値: 期待値)
|
|
82
|
-
* @returns フィルター式の構築結果
|
|
83
|
-
*/
|
|
84
|
-
export declare function buildFilterExpression(filter: Record<string, unknown>): FilterExpressionResult | null;
|
|
85
|
-
/**
|
|
86
|
-
* 複数のフィルター式を AND 条件で結合する
|
|
87
|
-
*
|
|
88
|
-
* @param filters - 結合するフィルター式の配列
|
|
89
|
-
* @returns 結合されたフィルター式
|
|
90
|
-
*/
|
|
91
|
-
export declare function combineFilters(filters: (FilterExpressionResult | null)[]): FilterExpressionResult | null;
|
|
92
|
-
/**
|
|
93
|
-
* フィルター式を DynamoDB Query/Scan パラメータに適用する
|
|
94
|
-
*
|
|
95
|
-
* @param params - DynamoDB Query/Scan パラメータ
|
|
96
|
-
* @param filter - フィルター式
|
|
97
|
-
* @returns フィルター式が適用されたパラメータ
|
|
98
|
-
*/
|
|
99
|
-
export declare function applyFilterExpression<T extends Record<string, unknown>>(params: T, filter: FilterExpressionResult | null): T;
|
|
100
|
-
/**
|
|
101
|
-
* 型変換ヘルパー
|
|
102
|
-
*
|
|
103
|
-
* 要件: 12.5
|
|
104
|
-
*
|
|
105
|
-
* @param value - 変換する値
|
|
106
|
-
* @param type - 変換先の型
|
|
107
|
-
* @returns 変換後の値
|
|
108
|
-
*/
|
|
109
|
-
export declare function convertType(value: unknown, type: FilterType): string | number | Date | boolean;
|
|
110
|
-
/**
|
|
111
|
-
* レコードが単一のフィルター条件に一致するかチェックする
|
|
112
|
-
*
|
|
113
|
-
* 要件: 12.2, 12.6, 12.8, 12.9
|
|
114
|
-
*
|
|
115
|
-
* @param record - チェック対象レコード
|
|
116
|
-
* @param parsed - パース済みフィルター条件
|
|
117
|
-
* @param value - フィルター値
|
|
118
|
-
* @returns 一致する場合true
|
|
119
|
-
*/
|
|
120
|
-
export declare function matchesFilter(record: Record<string, unknown>, parsed: ParsedFilterField, value: unknown): boolean;
|
|
121
|
-
/**
|
|
122
|
-
* レコードがすべてのフィルター条件に一致するかチェックする(AND条件)
|
|
123
|
-
*
|
|
124
|
-
* 要件: 12.9
|
|
125
|
-
*
|
|
126
|
-
* @param record - チェック対象レコード
|
|
127
|
-
* @param parsedFilters - パース済みフィルター条件の配列
|
|
128
|
-
* @returns すべての条件に一致する場合true
|
|
129
|
-
*/
|
|
130
|
-
export declare function matchesAllFilters(record: Record<string, unknown>, parsedFilters: Array<{
|
|
131
|
-
parsed: ParsedFilterField;
|
|
132
|
-
value: unknown;
|
|
133
|
-
}>): boolean;
|
|
134
|
-
/**
|
|
135
|
-
* Query最適化が可能かチェックする
|
|
136
|
-
*
|
|
137
|
-
* ソートフィールドと一致し、Query可能な演算子を持つフィルター条件を検出する
|
|
138
|
-
*
|
|
139
|
-
* 要件: 12.7
|
|
140
|
-
*
|
|
141
|
-
* @param sortField - ソート対象フィールド
|
|
142
|
-
* @param parsedFilters - パース済みフィルター条件
|
|
143
|
-
* @returns 最適化可能なフィルター条件(なければnull)
|
|
144
|
-
*/
|
|
145
|
-
export declare function findOptimizableFilter(sortField: string, parsedFilters: Array<{
|
|
146
|
-
parsed: ParsedFilterField;
|
|
147
|
-
value: unknown;
|
|
148
|
-
}>): {
|
|
149
|
-
parsed: ParsedFilterField;
|
|
150
|
-
value: unknown;
|
|
151
|
-
} | null;
|
|
152
50
|
//# sourceMappingURL=filter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../../../src/server/utils/filter.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../../../src/server/utils/filter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;GAIG;AACH,MAAM,MAAM,cAAc,GACtB,KAAK,GACL,KAAK,GACL,KAAK,GACL,MAAM,GACN,KAAK,GACL,MAAM,GACN,KAAK,GACL,MAAM,GACN,SAAS,GACT,OAAO,GACP,WAAW,GACX,SAAS,GACT,OAAO,CAAC;AAEZ;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAElE;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,gBAAgB;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU;IACV,QAAQ,EAAE,cAAc,CAAC;IACzB,WAAW;IACX,IAAI,EAAE,UAAU,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,iBAAiB,CA6BpE"}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* FilterExpression の構築とシャドーレコード除外条件の生成
|
|
2
|
+
* フィルターのフィールド指定(`field:$op:type`)の解析
|
|
4
3
|
*
|
|
5
|
-
* 要件:
|
|
4
|
+
* 要件: 12.1-12.12
|
|
6
5
|
*/
|
|
7
6
|
/**
|
|
8
7
|
* フィルターフィールド名をパースする
|
|
@@ -30,19 +29,15 @@ export function parseFilterField(fieldKey) {
|
|
|
30
29
|
}
|
|
31
30
|
if (parts.length === 2) {
|
|
32
31
|
// "priority:$gte" → { field: "priority", operator: "$gte", type: "string" }
|
|
32
|
+
assertSupportedOperator(parts[1]);
|
|
33
33
|
const operator = parts[1];
|
|
34
|
-
if (!isValidOperator(operator)) {
|
|
35
|
-
throw new Error(`Invalid filter operator: ${parts[1]}`);
|
|
36
|
-
}
|
|
37
34
|
return { field: parts[0], operator, type: 'string' };
|
|
38
35
|
}
|
|
39
36
|
if (parts.length === 3) {
|
|
40
37
|
// "priority:$gte:number" → { field: "priority", operator: "$gte", type: "number" }
|
|
38
|
+
assertSupportedOperator(parts[1]);
|
|
41
39
|
const operator = parts[1];
|
|
42
40
|
const type = parts[2];
|
|
43
|
-
if (!isValidOperator(operator)) {
|
|
44
|
-
throw new Error(`Invalid filter operator: ${parts[1]}`);
|
|
45
|
-
}
|
|
46
41
|
if (!isValidType(type)) {
|
|
47
42
|
throw new Error(`Invalid filter type: ${parts[2]}`);
|
|
48
43
|
}
|
|
@@ -66,226 +61,27 @@ function isValidOperator(operator) {
|
|
|
66
61
|
'$starts',
|
|
67
62
|
'$ends',
|
|
68
63
|
'$contains',
|
|
69
|
-
'$regex',
|
|
70
64
|
'$exists',
|
|
71
65
|
'$near',
|
|
72
66
|
].includes(operator);
|
|
73
67
|
}
|
|
74
68
|
/**
|
|
75
|
-
*
|
|
76
|
-
*/
|
|
77
|
-
function isValidType(type) {
|
|
78
|
-
return ['string', 'number', 'date', 'boolean'].includes(type);
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* シャドーレコードを除外するフィルター式を生成する
|
|
82
|
-
*
|
|
83
|
-
* シャドーレコードの特徴:
|
|
84
|
-
* - SK に '#' が含まれる(例: "name#value#id#ULID")
|
|
85
|
-
* - 本体レコードは "id#ULID" 形式で '#' が1つのみ
|
|
86
|
-
*
|
|
87
|
-
* 除外条件:
|
|
88
|
-
* - attribute_exists(data) AND NOT contains(SK, '#id#')
|
|
69
|
+
* 演算子が使えるものかを確かめる(使えなければ例外。find の呼び出し側で 400 になる)
|
|
89
70
|
*
|
|
90
|
-
*
|
|
71
|
+
* `$regex` は正規表現ではなく部分一致として動いていたため、黙って誤った結果を返さないよう受け付けない。
|
|
91
72
|
*/
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
names: {
|
|
96
|
-
'#data': 'data',
|
|
97
|
-
'#sk': 'SK',
|
|
98
|
-
},
|
|
99
|
-
values: {
|
|
100
|
-
':shadowMarker': '#id#',
|
|
101
|
-
},
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* ユーザー指定のフィルター条件から FilterExpression を構築する
|
|
106
|
-
*
|
|
107
|
-
* 注意: この実装は基本的な等価比較のみをサポート
|
|
108
|
-
* より複雑な条件(範囲検索、部分一致など)は将来の拡張として実装可能
|
|
109
|
-
*
|
|
110
|
-
* @param filter - フィルター条件(キー: フィールド名、値: 期待値)
|
|
111
|
-
* @returns フィルター式の構築結果
|
|
112
|
-
*/
|
|
113
|
-
export function buildFilterExpression(filter) {
|
|
114
|
-
const entries = Object.entries(filter);
|
|
115
|
-
// フィルター条件が空の場合は null を返す
|
|
116
|
-
if (entries.length === 0) {
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
const expressions = [];
|
|
120
|
-
const names = {};
|
|
121
|
-
const values = {};
|
|
122
|
-
entries.forEach(([key, value], index) => {
|
|
123
|
-
const nameKey = `#field${index}`;
|
|
124
|
-
const valueKey = `:value${index}`;
|
|
125
|
-
// data.{field} = :value 形式の条件を生成
|
|
126
|
-
expressions.push(`#data.${nameKey} = ${valueKey}`);
|
|
127
|
-
names['#data'] = 'data';
|
|
128
|
-
names[nameKey] = key;
|
|
129
|
-
values[valueKey] = value;
|
|
130
|
-
});
|
|
131
|
-
return {
|
|
132
|
-
expression: expressions.join(' AND '),
|
|
133
|
-
names,
|
|
134
|
-
values,
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* 複数のフィルター式を AND 条件で結合する
|
|
139
|
-
*
|
|
140
|
-
* @param filters - 結合するフィルター式の配列
|
|
141
|
-
* @returns 結合されたフィルター式
|
|
142
|
-
*/
|
|
143
|
-
export function combineFilters(filters) {
|
|
144
|
-
// null を除外
|
|
145
|
-
const validFilters = filters.filter((f) => f !== null);
|
|
146
|
-
if (validFilters.length === 0) {
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
|
-
if (validFilters.length === 1) {
|
|
150
|
-
return validFilters[0];
|
|
73
|
+
function assertSupportedOperator(operator) {
|
|
74
|
+
if (operator === '$regex') {
|
|
75
|
+
throw new Error('Invalid filter operator: $regex is not supported (use $contains or $starts)');
|
|
151
76
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
// names と values をマージ
|
|
155
|
-
const names = {};
|
|
156
|
-
const values = {};
|
|
157
|
-
validFilters.forEach((f) => {
|
|
158
|
-
Object.assign(names, f.names);
|
|
159
|
-
Object.assign(values, f.values);
|
|
160
|
-
});
|
|
161
|
-
return {
|
|
162
|
-
expression,
|
|
163
|
-
names,
|
|
164
|
-
values,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* フィルター式を DynamoDB Query/Scan パラメータに適用する
|
|
169
|
-
*
|
|
170
|
-
* @param params - DynamoDB Query/Scan パラメータ
|
|
171
|
-
* @param filter - フィルター式
|
|
172
|
-
* @returns フィルター式が適用されたパラメータ
|
|
173
|
-
*/
|
|
174
|
-
export function applyFilterExpression(params, filter) {
|
|
175
|
-
if (!filter) {
|
|
176
|
-
return params;
|
|
77
|
+
if (!isValidOperator(operator)) {
|
|
78
|
+
throw new Error(`Invalid filter operator: ${operator}`);
|
|
177
79
|
}
|
|
178
|
-
return {
|
|
179
|
-
...params,
|
|
180
|
-
FilterExpression: filter.expression,
|
|
181
|
-
ExpressionAttributeNames: {
|
|
182
|
-
...params.ExpressionAttributeNames,
|
|
183
|
-
...filter.names,
|
|
184
|
-
},
|
|
185
|
-
ExpressionAttributeValues: {
|
|
186
|
-
...params.ExpressionAttributeValues,
|
|
187
|
-
...filter.values,
|
|
188
|
-
},
|
|
189
|
-
};
|
|
190
80
|
}
|
|
191
81
|
/**
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
* 要件: 12.5
|
|
195
|
-
*
|
|
196
|
-
* @param value - 変換する値
|
|
197
|
-
* @param type - 変換先の型
|
|
198
|
-
* @returns 変換後の値
|
|
199
|
-
*/
|
|
200
|
-
export function convertType(value, type) {
|
|
201
|
-
switch (type) {
|
|
202
|
-
case 'string':
|
|
203
|
-
return String(value);
|
|
204
|
-
case 'number':
|
|
205
|
-
return Number(value);
|
|
206
|
-
case 'date':
|
|
207
|
-
return new Date(String(value));
|
|
208
|
-
case 'boolean':
|
|
209
|
-
// 文字列 "true"/"false" または boolean値を処理
|
|
210
|
-
if (typeof value === 'boolean')
|
|
211
|
-
return value;
|
|
212
|
-
return String(value).toLowerCase() === 'true';
|
|
213
|
-
default:
|
|
214
|
-
return String(value);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* レコードが単一のフィルター条件に一致するかチェックする
|
|
219
|
-
*
|
|
220
|
-
* 要件: 12.2, 12.6, 12.8, 12.9
|
|
221
|
-
*
|
|
222
|
-
* @param record - チェック対象レコード
|
|
223
|
-
* @param parsed - パース済みフィルター条件
|
|
224
|
-
* @param value - フィルター値
|
|
225
|
-
* @returns 一致する場合true
|
|
226
|
-
*/
|
|
227
|
-
export function matchesFilter(record, parsed, value) {
|
|
228
|
-
const fieldValue = record[parsed.field];
|
|
229
|
-
// フィールドが存在しない場合は不一致
|
|
230
|
-
if (fieldValue === undefined || fieldValue === null) {
|
|
231
|
-
return false;
|
|
232
|
-
}
|
|
233
|
-
// 型変換
|
|
234
|
-
const typedFieldValue = convertType(fieldValue, parsed.type);
|
|
235
|
-
const typedFilterValue = convertType(value, parsed.type);
|
|
236
|
-
// 演算子に応じた比較
|
|
237
|
-
switch (parsed.operator) {
|
|
238
|
-
case '$eq':
|
|
239
|
-
return typedFieldValue === typedFilterValue;
|
|
240
|
-
case '$lt':
|
|
241
|
-
return typedFieldValue < typedFilterValue;
|
|
242
|
-
case '$lte':
|
|
243
|
-
return typedFieldValue <= typedFilterValue;
|
|
244
|
-
case '$gt':
|
|
245
|
-
return typedFieldValue > typedFilterValue;
|
|
246
|
-
case '$gte':
|
|
247
|
-
return typedFieldValue >= typedFilterValue;
|
|
248
|
-
case '$in':
|
|
249
|
-
// value は配列のため convertType を使わず直接比較
|
|
250
|
-
return Array.isArray(value) && value.map(String).includes(String(fieldValue));
|
|
251
|
-
case '$nin':
|
|
252
|
-
// value は配列のため convertType を使わず直接比較
|
|
253
|
-
return Array.isArray(value) && !value.map(String).includes(String(fieldValue));
|
|
254
|
-
case '$starts':
|
|
255
|
-
return String(typedFieldValue).startsWith(String(typedFilterValue));
|
|
256
|
-
case '$ends':
|
|
257
|
-
return String(typedFieldValue).endsWith(String(typedFilterValue));
|
|
258
|
-
default:
|
|
259
|
-
return false;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* レコードがすべてのフィルター条件に一致するかチェックする(AND条件)
|
|
264
|
-
*
|
|
265
|
-
* 要件: 12.9
|
|
266
|
-
*
|
|
267
|
-
* @param record - チェック対象レコード
|
|
268
|
-
* @param parsedFilters - パース済みフィルター条件の配列
|
|
269
|
-
* @returns すべての条件に一致する場合true
|
|
270
|
-
*/
|
|
271
|
-
export function matchesAllFilters(record, parsedFilters) {
|
|
272
|
-
return parsedFilters.every((f) => matchesFilter(record, f.parsed, f.value));
|
|
273
|
-
}
|
|
274
|
-
/**
|
|
275
|
-
* Query最適化が可能かチェックする
|
|
276
|
-
*
|
|
277
|
-
* ソートフィールドと一致し、Query可能な演算子を持つフィルター条件を検出する
|
|
278
|
-
*
|
|
279
|
-
* 要件: 12.7
|
|
280
|
-
*
|
|
281
|
-
* @param sortField - ソート対象フィールド
|
|
282
|
-
* @param parsedFilters - パース済みフィルター条件
|
|
283
|
-
* @returns 最適化可能なフィルター条件(なければnull)
|
|
82
|
+
* 型が有効かチェックする
|
|
284
83
|
*/
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const queryableOperators = ['$eq', '$lt', '$lte', '$gt', '$gte', '$starts'];
|
|
288
|
-
// ソートフィールドと一致し、Query可能な演算子を持つフィルターを探す
|
|
289
|
-
return (parsedFilters.find((f) => f.parsed.field === sortField && queryableOperators.includes(f.parsed.operator)) || null);
|
|
84
|
+
function isValidType(type) {
|
|
85
|
+
return ['string', 'number', 'date', 'boolean'].includes(type);
|
|
290
86
|
}
|
|
291
87
|
//# sourceMappingURL=filter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filter.js","sourceRoot":"","sources":["../../../src/server/utils/filter.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"filter.js","sourceRoot":"","sources":["../../../src/server/utils/filter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA2CH;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAElC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,kEAAkE;QAClE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAC9D,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,4EAA4E;QAC5E,uBAAuB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAClC,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAmB,CAAC;QAC5C,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IACvD,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,mFAAmF;QACnF,uBAAuB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAClC,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAmB,CAAC;QAC5C,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAe,CAAC;QAEpC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,wBAAwB,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACtD,CAAC;QAED,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC7C,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,gCAAgC,QAAQ,EAAE,CAAC,CAAC;AAC9D,CAAC;AAED;;GAEG;AACH,SAAS,eAAe,CAAC,QAAgB;IACvC,OAAO;QACL,KAAK;QACL,KAAK;QACL,KAAK;QACL,MAAM;QACN,KAAK;QACL,MAAM;QACN,KAAK;QACL,MAAM;QACN,SAAS;QACT,OAAO;QACP,WAAW;QACX,SAAS;QACT,OAAO;KACR,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AACvB,CAAC;AAED;;;;GAIG;AACH,SAAS,uBAAuB,CAAC,QAAgB;IAC/C,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,6EAA6E,CAAC,CAAC;IACjG,CAAC;IACD,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,4BAA4B,QAAQ,EAAE,CAAC,CAAC;IAC1D,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,WAAW,CAAC,IAAY;IAC/B,OAAO,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAChE,CAAC"}
|
|
@@ -3,14 +3,25 @@ export interface ResolvedUpdate {
|
|
|
3
3
|
patch: Record<string, unknown>;
|
|
4
4
|
/** 新規作成のときだけ入れる値($setOnInsert) */
|
|
5
5
|
setOnInsert: Record<string, unknown>;
|
|
6
|
+
/** 足す値($inc。項目名(ドット記法可)→ 有限の数値)。今の値の読み取りと加算は updateMany が行う */
|
|
7
|
+
inc: Record<string, number>;
|
|
6
8
|
/** レスポンスの items に返す値($set の値。演算子が無ければパッチそのもの) */
|
|
7
9
|
returned: Record<string, unknown>;
|
|
8
10
|
}
|
|
11
|
+
/** ドット記法のパスで、今の値を読む(途中がオブジェクトでなければ undefined) */
|
|
12
|
+
export declare function readByPath(data: Record<string, unknown>, path: string): unknown;
|
|
13
|
+
/**
|
|
14
|
+
* $inc を当てる(今の値に足した値を $set として書くパッチを作る)
|
|
15
|
+
*
|
|
16
|
+
* 項目が無い・null のときは 0 から足す。数値でないときは 400。
|
|
17
|
+
* 返す `added` は、足した後の値(レスポンスの items 用)。
|
|
18
|
+
*/
|
|
19
|
+
export declare function applyInc(data: Record<string, unknown>, inc: Record<string, number>): Record<string, number>;
|
|
9
20
|
/**
|
|
10
21
|
* 更新のデータを解釈する
|
|
11
22
|
*
|
|
12
23
|
* - 演算子(`$` で始まるキー)が無ければ、全体を JSON Merge Patch として扱う
|
|
13
|
-
* - 演算子があれば、$set / $setOnInsert / $unset だけを受け付ける(ほかは 400)
|
|
24
|
+
* - 演算子があれば、$set / $setOnInsert / $unset / $inc だけを受け付ける(ほかは 400)
|
|
14
25
|
*
|
|
15
26
|
* @throws AppError VALIDATION_ERROR(400)
|
|
16
27
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"updateOperators.d.ts","sourceRoot":"","sources":["../../../src/server/utils/updateOperators.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,kCAAkC;IAClC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;
|
|
1
|
+
{"version":3,"file":"updateOperators.d.ts","sourceRoot":"","sources":["../../../src/server/utils/updateOperators.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,kCAAkC;IAClC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,+DAA+D;IAC/D,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAsCD,iDAAiD;AACjD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAO/E;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC1B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAcxB;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,cAAc,CA6BpF;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQzF"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 更新演算子($set / $setOnInsert / $unset)の解釈
|
|
2
|
+
* 更新演算子($set / $setOnInsert / $unset / $inc)の解釈
|
|
3
3
|
*
|
|
4
|
-
* 対応していない演算子($
|
|
4
|
+
* 対応していない演算子($push など)を黙って無視しない。無視すると、呼び出し側は更新できたと
|
|
5
5
|
* 思い込む($unset が無視され、項目を空にする操作が保存されない不具合が実際に起きた)。
|
|
6
6
|
*/
|
|
7
7
|
import { AppError, ErrorCode } from '../../shared/index.js';
|
|
8
|
-
const SUPPORTED_OPERATORS = ['$set', '$setOnInsert', '$unset'];
|
|
8
|
+
const SUPPORTED_OPERATORS = ['$set', '$setOnInsert', '$unset', '$inc'];
|
|
9
9
|
function invalid(message) {
|
|
10
10
|
return new AppError(ErrorCode.VALIDATION_ERROR, message, 400);
|
|
11
11
|
}
|
|
@@ -29,18 +29,58 @@ function unsetFields(value) {
|
|
|
29
29
|
}
|
|
30
30
|
return Object.keys(asObject(value, '$unset'));
|
|
31
31
|
}
|
|
32
|
+
/** $inc は、項目名 → 有限の数値。それ以外は 400 */
|
|
33
|
+
function incFields(value) {
|
|
34
|
+
const inc = {};
|
|
35
|
+
for (const [field, amount] of Object.entries(asObject(value, '$inc'))) {
|
|
36
|
+
if (typeof amount !== 'number' || !Number.isFinite(amount)) {
|
|
37
|
+
throw invalid(`$inc value for "${field}" must be a finite number`);
|
|
38
|
+
}
|
|
39
|
+
inc[field] = amount;
|
|
40
|
+
}
|
|
41
|
+
return inc;
|
|
42
|
+
}
|
|
43
|
+
/** ドット記法のパスで、今の値を読む(途中がオブジェクトでなければ undefined) */
|
|
44
|
+
export function readByPath(data, path) {
|
|
45
|
+
let current = data;
|
|
46
|
+
for (const key of path.split('.')) {
|
|
47
|
+
if (typeof current !== 'object' || current === null)
|
|
48
|
+
return undefined;
|
|
49
|
+
current = current[key];
|
|
50
|
+
}
|
|
51
|
+
return current;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* $inc を当てる(今の値に足した値を $set として書くパッチを作る)
|
|
55
|
+
*
|
|
56
|
+
* 項目が無い・null のときは 0 から足す。数値でないときは 400。
|
|
57
|
+
* 返す `added` は、足した後の値(レスポンスの items 用)。
|
|
58
|
+
*/
|
|
59
|
+
export function applyInc(data, inc) {
|
|
60
|
+
const added = {};
|
|
61
|
+
for (const [field, amount] of Object.entries(inc)) {
|
|
62
|
+
const current = readByPath(data, field);
|
|
63
|
+
if (current !== undefined &&
|
|
64
|
+
current !== null &&
|
|
65
|
+
(typeof current !== 'number' || !Number.isFinite(current))) {
|
|
66
|
+
throw invalid(`Field "${field}" is not a number, $inc cannot be applied`);
|
|
67
|
+
}
|
|
68
|
+
added[field] = (current ?? 0) + amount;
|
|
69
|
+
}
|
|
70
|
+
return added;
|
|
71
|
+
}
|
|
32
72
|
/**
|
|
33
73
|
* 更新のデータを解釈する
|
|
34
74
|
*
|
|
35
75
|
* - 演算子(`$` で始まるキー)が無ければ、全体を JSON Merge Patch として扱う
|
|
36
|
-
* - 演算子があれば、$set / $setOnInsert / $unset だけを受け付ける(ほかは 400)
|
|
76
|
+
* - 演算子があれば、$set / $setOnInsert / $unset / $inc だけを受け付ける(ほかは 400)
|
|
37
77
|
*
|
|
38
78
|
* @throws AppError VALIDATION_ERROR(400)
|
|
39
79
|
*/
|
|
40
80
|
export function resolveUpdateOperators(data) {
|
|
41
81
|
const operators = Object.keys(data).filter((key) => key.startsWith('$'));
|
|
42
82
|
if (operators.length === 0) {
|
|
43
|
-
return { patch: data, setOnInsert: {}, returned: data };
|
|
83
|
+
return { patch: data, setOnInsert: {}, inc: {}, returned: data };
|
|
44
84
|
}
|
|
45
85
|
const unsupported = operators.filter((op) => !SUPPORTED_OPERATORS.includes(op));
|
|
46
86
|
if (unsupported.length > 0) {
|
|
@@ -54,7 +94,14 @@ export function resolveUpdateOperators(data) {
|
|
|
54
94
|
}
|
|
55
95
|
patch[field] = null;
|
|
56
96
|
}
|
|
57
|
-
|
|
97
|
+
const inc = incFields(data.$inc);
|
|
98
|
+
for (const field of Object.keys(inc)) {
|
|
99
|
+
if (field in set)
|
|
100
|
+
throw invalid(`Field "${field}" is in both $set and $inc`);
|
|
101
|
+
if (field in patch)
|
|
102
|
+
throw invalid(`Field "${field}" is in both $unset and $inc`);
|
|
103
|
+
}
|
|
104
|
+
return { patch, setOnInsert: asObject(data.$setOnInsert, '$setOnInsert'), inc, returned: set };
|
|
58
105
|
}
|
|
59
106
|
/**
|
|
60
107
|
* フィルタから、新規作成(upsert)のレコードに入れる値を取り出す
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"updateOperators.js","sourceRoot":"","sources":["../../../src/server/utils/updateOperators.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAE5D,MAAM,mBAAmB,GAAG,CAAC,MAAM,EAAE,cAAc,EAAE,QAAQ,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"updateOperators.js","sourceRoot":"","sources":["../../../src/server/utils/updateOperators.ts"],"names":[],"mappings":"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"}
|
package/docs/README.md
CHANGED
|
@@ -126,23 +126,20 @@ pnpm build:lambda # サーバーのみをビルド
|
|
|
126
126
|
|
|
127
127
|
### シャドーレコード修復
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
運用の道具です(アプリのコードから呼ばない)。テーブルを直接読み、本体とシャドーの食い違い(孤立・欠け・一覧の食い違い・本体の無いシャドー)を数えて直します。Records の HTTP は `__shadowKeys` を返さず、`updateMany` は古い一覧との差分しか書かないので、HTTP 経由では判定も修復もできません。
|
|
130
130
|
|
|
131
131
|
```bash
|
|
132
|
-
#
|
|
133
|
-
|
|
132
|
+
# 確認(既定。読み取りだけ)
|
|
133
|
+
tsx src/scripts/repair-shadows.ts --table <テーブル名> --region <リージョン> --lambda <Records の関数名> --resource articles
|
|
134
134
|
|
|
135
|
-
#
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
# 全レコードを修復
|
|
139
|
-
pnpm repair-shadows -- --resource articles --repair
|
|
135
|
+
# 修復(食い違いのあるレコードだけ、1 レコード 1 トランザクション)
|
|
136
|
+
tsx src/scripts/repair-shadows.ts --table <テーブル名> --region <リージョン> --lambda <Records の関数名> --resource articles --repair
|
|
140
137
|
```
|
|
141
138
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
- `
|
|
145
|
-
-
|
|
139
|
+
- テーブル名・リージョン・Lambda は必須(既定を持たない)。`--table` が Lambda の `TABLE_NAME` と違えば止まる
|
|
140
|
+
- シャドーの設定(`SHADOW_*`)は Lambda の環境変数から読み、実行の最初に表示する
|
|
141
|
+
- 本体の `data`・`updatedAt` は変えない。本体の `__shadowKeys` と `__rev` だけを更新し、孤立を削除し、欠けを作る。本体の無いシャドーは、本体が無いことを同じトランザクションで確かめて削除する
|
|
142
|
+
- 修復中に書き込まれたレコードは、条件に外れて飛ばす。もう一度実行すると直る
|
|
146
143
|
- `RECORDS_API_URL`: Records Lambda Function URL(省略時はAWS CLIで自動取得)
|
|
147
144
|
|
|
148
145
|
## ドキュメント
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exabugs/dynamodb-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.27.1",
|
|
4
4
|
"description": "DynamoDB Single-Table Client SDK with MongoDB-like API, Shadow Records, and Lambda implementation for serverless applications",
|
|
5
5
|
"author": "exabugs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 一括レコード修復処理
|
|
3
|
-
*
|
|
4
|
-
* 全レコードをスキャンして修復する
|
|
5
|
-
*/
|
|
6
|
-
import type { Collection } from '../../client/index.iam.js';
|
|
7
|
-
/**
|
|
8
|
-
* 全レコードをスキャンして修復
|
|
9
|
-
*
|
|
10
|
-
* @param collection - DynamoClientのコレクション
|
|
11
|
-
* @param dryRun - ドライランモード(trueの場合は実際の修復は行わない)
|
|
12
|
-
*/
|
|
13
|
-
export declare function repairAllRecords<TAuthOptions = unknown>(collection: Collection<{
|
|
14
|
-
id: string;
|
|
15
|
-
[key: string]: unknown;
|
|
16
|
-
}, TAuthOptions>, dryRun: boolean): Promise<void>;
|
|
17
|
-
//# sourceMappingURL=bulkRecordRepair.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bulkRecordRepair.d.ts","sourceRoot":"","sources":["../../../src/scripts/operations/bulkRecordRepair.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAS5D;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,YAAY,GAAG,OAAO,EAC3D,UAAU,EAAE,UAAU,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,EAAE,YAAY,CAAC,EAC5E,MAAM,EAAE,OAAO,GACd,OAAO,CAAC,IAAI,CAAC,CAuBf"}
|