graphddb 0.2.0 → 0.2.2
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/README.md +68 -3
- package/dist/{chunk-UNRQ5YJT.js → chunk-27XBTYVK.js} +1 -1
- package/dist/{chunk-347U24SB.js → chunk-3MI43FHU.js} +519 -48
- package/dist/{chunk-F27INYI2.js → chunk-CXIAECRS.js} +272 -97
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +5 -19
- package/dist/index.js +113 -7
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +2 -2
- package/dist/{types-D6qLpw2M.d.ts → types-SKxZQbQO.d.ts} +230 -122
- package/package.json +1 -1
|
@@ -525,6 +525,51 @@ var TableMapping = class {
|
|
|
525
525
|
}
|
|
526
526
|
};
|
|
527
527
|
|
|
528
|
+
// src/define/param.ts
|
|
529
|
+
function makeParam(kind, literals) {
|
|
530
|
+
return literals === void 0 ? { kind } : { kind, literals };
|
|
531
|
+
}
|
|
532
|
+
var param = {
|
|
533
|
+
/** A placeholder for a `string` value. */
|
|
534
|
+
string() {
|
|
535
|
+
return makeParam("string");
|
|
536
|
+
},
|
|
537
|
+
/** A placeholder for a `number` value. */
|
|
538
|
+
number() {
|
|
539
|
+
return makeParam("number");
|
|
540
|
+
},
|
|
541
|
+
/**
|
|
542
|
+
* A placeholder constrained to one of the given literal values. The value
|
|
543
|
+
* type of the resulting {@link Param} is the **union of the literals**, so it
|
|
544
|
+
* is preserved precisely in the IR and the inferred definition types.
|
|
545
|
+
*
|
|
546
|
+
* @example
|
|
547
|
+
* ```ts
|
|
548
|
+
* param.literal('active', 'disabled'); // Param<'active' | 'disabled'>
|
|
549
|
+
* ```
|
|
550
|
+
*/
|
|
551
|
+
literal(...values) {
|
|
552
|
+
return makeParam("literal", values);
|
|
553
|
+
},
|
|
554
|
+
/**
|
|
555
|
+
* A placeholder for an **array** parameter whose elements have the given field
|
|
556
|
+
* shape. Used by `defineTransaction`'s `tx.forEach(p.<arrayParam>, …)` to bind
|
|
557
|
+
* each element's fields to `{item.<field>}` templates.
|
|
558
|
+
*
|
|
559
|
+
* @example
|
|
560
|
+
* ```ts
|
|
561
|
+
* param.array({ userId: param.string(), role: param.string() });
|
|
562
|
+
* // Param<{ userId: string; role: string }[]>
|
|
563
|
+
* ```
|
|
564
|
+
*/
|
|
565
|
+
array(element) {
|
|
566
|
+
return { kind: "array", element };
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
function isParam(value) {
|
|
570
|
+
return typeof value === "object" && value !== null && "kind" in value && (value.kind === "string" || value.kind === "number" || value.kind === "literal" || value.kind === "array");
|
|
571
|
+
}
|
|
572
|
+
|
|
528
573
|
// src/client/lib-dynamodb.ts
|
|
529
574
|
var cached = null;
|
|
530
575
|
async function loadLibDynamoDB() {
|
|
@@ -825,20 +870,20 @@ async function executeQuery(docClient, operation) {
|
|
|
825
870
|
};
|
|
826
871
|
for (let i = 0; i < keyEntries.length; i++) {
|
|
827
872
|
const [attrName, value] = keyEntries[i];
|
|
828
|
-
const
|
|
829
|
-
const
|
|
830
|
-
exprAttrNames[
|
|
831
|
-
exprAttrValues[
|
|
832
|
-
conditionParts.push(`${
|
|
873
|
+
const nameAlias2 = `#k${i}`;
|
|
874
|
+
const valueAlias2 = `:k${i}`;
|
|
875
|
+
exprAttrNames[nameAlias2] = attrName;
|
|
876
|
+
exprAttrValues[valueAlias2] = value;
|
|
877
|
+
conditionParts.push(`${nameAlias2} = ${valueAlias2}`);
|
|
833
878
|
}
|
|
834
879
|
if (operation.rangeCondition) {
|
|
835
880
|
const rc = operation.rangeCondition;
|
|
836
881
|
const idx = keyEntries.length;
|
|
837
|
-
const
|
|
838
|
-
const
|
|
839
|
-
exprAttrNames[
|
|
840
|
-
exprAttrValues[
|
|
841
|
-
conditionParts.push(`begins_with(${
|
|
882
|
+
const nameAlias2 = `#k${idx}`;
|
|
883
|
+
const valueAlias2 = `:k${idx}`;
|
|
884
|
+
exprAttrNames[nameAlias2] = rc.key;
|
|
885
|
+
exprAttrValues[valueAlias2] = rc.value;
|
|
886
|
+
conditionParts.push(`begins_with(${nameAlias2}, ${valueAlias2})`);
|
|
842
887
|
}
|
|
843
888
|
const cmd = new QueryCommand({
|
|
844
889
|
TableName: operation.tableName,
|
|
@@ -860,14 +905,290 @@ async function executeQuery(docClient, operation) {
|
|
|
860
905
|
};
|
|
861
906
|
}
|
|
862
907
|
|
|
908
|
+
// src/select/cond.ts
|
|
909
|
+
var RAW_COND = /* @__PURE__ */ Symbol.for("graphddb.rawCond");
|
|
910
|
+
function isRawCondition(value) {
|
|
911
|
+
return typeof value === "object" && value !== null && value[RAW_COND] === true;
|
|
912
|
+
}
|
|
913
|
+
function cond(template, ...parts) {
|
|
914
|
+
return {
|
|
915
|
+
[RAW_COND]: true,
|
|
916
|
+
template,
|
|
917
|
+
parts
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
function compileRawCondition(raw, allocName, allocValue) {
|
|
921
|
+
let out = raw.template[0];
|
|
922
|
+
for (let i = 0; i < raw.parts.length; i++) {
|
|
923
|
+
const part = raw.parts[i];
|
|
924
|
+
if (isColumn(part)) {
|
|
925
|
+
out += allocName(part.name);
|
|
926
|
+
} else {
|
|
927
|
+
out += allocValue(part);
|
|
928
|
+
}
|
|
929
|
+
out += raw.template[i + 1];
|
|
930
|
+
}
|
|
931
|
+
return out;
|
|
932
|
+
}
|
|
933
|
+
function condParamName(index) {
|
|
934
|
+
return `cond_p${index}`;
|
|
935
|
+
}
|
|
936
|
+
function serializeRawCondition(raw, renderSlot) {
|
|
937
|
+
const names = {};
|
|
938
|
+
const values = {};
|
|
939
|
+
let valueCounter = 0;
|
|
940
|
+
let paramIndex = 0;
|
|
941
|
+
const nameAlias2 = (column) => {
|
|
942
|
+
const alias = `#cr_${column}`;
|
|
943
|
+
names[alias] = column;
|
|
944
|
+
return alias;
|
|
945
|
+
};
|
|
946
|
+
const valueAlias2 = (value) => {
|
|
947
|
+
const alias = `:cr${valueCounter++}`;
|
|
948
|
+
values[alias] = value;
|
|
949
|
+
return alias;
|
|
950
|
+
};
|
|
951
|
+
let out = raw.template[0];
|
|
952
|
+
for (let i = 0; i < raw.parts.length; i++) {
|
|
953
|
+
const part = raw.parts[i];
|
|
954
|
+
if (isColumn(part)) {
|
|
955
|
+
out += nameAlias2(part.name);
|
|
956
|
+
} else {
|
|
957
|
+
const custom = renderSlot?.(part, paramIndex);
|
|
958
|
+
if (custom !== void 0) {
|
|
959
|
+
if (isParam(part)) paramIndex++;
|
|
960
|
+
out += valueAlias2(custom);
|
|
961
|
+
} else if (isParam(part)) {
|
|
962
|
+
out += valueAlias2({ $param: condParamName(paramIndex++) });
|
|
963
|
+
} else if (part instanceof Date) {
|
|
964
|
+
out += valueAlias2(part.toISOString());
|
|
965
|
+
} else {
|
|
966
|
+
out += valueAlias2(part);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
out += raw.template[i + 1];
|
|
970
|
+
}
|
|
971
|
+
return { expression: out, names, values };
|
|
972
|
+
}
|
|
973
|
+
function collectRawConditionParams(raw) {
|
|
974
|
+
const out = [];
|
|
975
|
+
let paramIndex = 0;
|
|
976
|
+
for (const part of raw.parts) {
|
|
977
|
+
if (isColumn(part)) continue;
|
|
978
|
+
if (isParam(part)) {
|
|
979
|
+
out.push({ name: condParamName(paramIndex++), param: part });
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
return out;
|
|
983
|
+
}
|
|
984
|
+
|
|
863
985
|
// src/expression/condition-expression.ts
|
|
986
|
+
var OPERATOR_KEYS = /* @__PURE__ */ new Set([
|
|
987
|
+
"eq",
|
|
988
|
+
"ne",
|
|
989
|
+
"gt",
|
|
990
|
+
"ge",
|
|
991
|
+
"lt",
|
|
992
|
+
"le",
|
|
993
|
+
"between",
|
|
994
|
+
"in",
|
|
995
|
+
"beginsWith",
|
|
996
|
+
"contains",
|
|
997
|
+
"notContains",
|
|
998
|
+
"attributeExists",
|
|
999
|
+
"attributeType",
|
|
1000
|
+
"size"
|
|
1001
|
+
]);
|
|
1002
|
+
function nameAlias(ctx, field) {
|
|
1003
|
+
const alias = `#cond_${field}`;
|
|
1004
|
+
ctx.names[alias] = field;
|
|
1005
|
+
return alias;
|
|
1006
|
+
}
|
|
1007
|
+
function valueAlias(ctx, value) {
|
|
1008
|
+
const alias = `:cond${ctx.valueCounter.n++}`;
|
|
1009
|
+
ctx.values[alias] = value;
|
|
1010
|
+
return alias;
|
|
1011
|
+
}
|
|
1012
|
+
function isOperatorObject(value) {
|
|
1013
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date || value instanceof Uint8Array || value instanceof Set) {
|
|
1014
|
+
return false;
|
|
1015
|
+
}
|
|
1016
|
+
const keys = Object.keys(value);
|
|
1017
|
+
if (keys.length === 0) return false;
|
|
1018
|
+
return keys.every((k2) => OPERATOR_KEYS.has(k2));
|
|
1019
|
+
}
|
|
1020
|
+
function joinAnd(clauses) {
|
|
1021
|
+
if (clauses.length === 1) return clauses[0];
|
|
1022
|
+
return clauses.map((c) => wrap(c)).join(" AND ");
|
|
1023
|
+
}
|
|
1024
|
+
function wrap(expr) {
|
|
1025
|
+
if (isAlreadyWrapped(expr)) return expr;
|
|
1026
|
+
if (expr.includes(" AND ") || expr.includes(" OR ")) return `(${expr})`;
|
|
1027
|
+
return expr;
|
|
1028
|
+
}
|
|
1029
|
+
function isAlreadyWrapped(expr) {
|
|
1030
|
+
if (!expr.startsWith("(") || !expr.endsWith(")")) return false;
|
|
1031
|
+
let depth = 0;
|
|
1032
|
+
for (let i = 0; i < expr.length; i++) {
|
|
1033
|
+
if (expr[i] === "(") depth++;
|
|
1034
|
+
else if (expr[i] === ")") {
|
|
1035
|
+
depth--;
|
|
1036
|
+
if (depth === 0 && i < expr.length - 1) return false;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
return depth === 0;
|
|
1040
|
+
}
|
|
1041
|
+
function compileField(ctx, field, condition) {
|
|
1042
|
+
const n = nameAlias(ctx, field);
|
|
1043
|
+
if (!isOperatorObject(condition)) {
|
|
1044
|
+
return `${n} = ${valueAlias(ctx, condition)}`;
|
|
1045
|
+
}
|
|
1046
|
+
const ops = condition;
|
|
1047
|
+
const clauses = [];
|
|
1048
|
+
for (const [op, value] of Object.entries(ops)) {
|
|
1049
|
+
switch (op) {
|
|
1050
|
+
case "eq":
|
|
1051
|
+
clauses.push(`${n} = ${valueAlias(ctx, value)}`);
|
|
1052
|
+
break;
|
|
1053
|
+
case "ne":
|
|
1054
|
+
clauses.push(`${n} <> ${valueAlias(ctx, value)}`);
|
|
1055
|
+
break;
|
|
1056
|
+
case "gt":
|
|
1057
|
+
clauses.push(`${n} > ${valueAlias(ctx, value)}`);
|
|
1058
|
+
break;
|
|
1059
|
+
case "ge":
|
|
1060
|
+
clauses.push(`${n} >= ${valueAlias(ctx, value)}`);
|
|
1061
|
+
break;
|
|
1062
|
+
case "lt":
|
|
1063
|
+
clauses.push(`${n} < ${valueAlias(ctx, value)}`);
|
|
1064
|
+
break;
|
|
1065
|
+
case "le":
|
|
1066
|
+
clauses.push(`${n} <= ${valueAlias(ctx, value)}`);
|
|
1067
|
+
break;
|
|
1068
|
+
case "between": {
|
|
1069
|
+
const [lo, hi] = value;
|
|
1070
|
+
clauses.push(
|
|
1071
|
+
`${n} BETWEEN ${valueAlias(ctx, lo)} AND ${valueAlias(ctx, hi)}`
|
|
1072
|
+
);
|
|
1073
|
+
break;
|
|
1074
|
+
}
|
|
1075
|
+
case "in": {
|
|
1076
|
+
const aliases = value.map((v) => valueAlias(ctx, v));
|
|
1077
|
+
clauses.push(`${n} IN (${aliases.join(", ")})`);
|
|
1078
|
+
break;
|
|
1079
|
+
}
|
|
1080
|
+
case "beginsWith":
|
|
1081
|
+
clauses.push(`begins_with(${n}, ${valueAlias(ctx, value)})`);
|
|
1082
|
+
break;
|
|
1083
|
+
case "contains":
|
|
1084
|
+
clauses.push(`contains(${n}, ${valueAlias(ctx, value)})`);
|
|
1085
|
+
break;
|
|
1086
|
+
case "notContains":
|
|
1087
|
+
clauses.push(`NOT contains(${n}, ${valueAlias(ctx, value)})`);
|
|
1088
|
+
break;
|
|
1089
|
+
case "attributeExists":
|
|
1090
|
+
clauses.push(
|
|
1091
|
+
value === false ? `attribute_not_exists(${n})` : `attribute_exists(${n})`
|
|
1092
|
+
);
|
|
1093
|
+
break;
|
|
1094
|
+
case "attributeType":
|
|
1095
|
+
clauses.push(`attribute_type(${n}, ${valueAlias(ctx, value)})`);
|
|
1096
|
+
break;
|
|
1097
|
+
case "size":
|
|
1098
|
+
clauses.push(`size(${n}) = ${valueAlias(ctx, value)}`);
|
|
1099
|
+
break;
|
|
1100
|
+
default:
|
|
1101
|
+
throw new Error(`Unknown condition operator '${op}' on field '${field}'`);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
return joinAnd(clauses);
|
|
1105
|
+
}
|
|
1106
|
+
function compileRaw(ctx, raw) {
|
|
1107
|
+
return compileRawCondition(
|
|
1108
|
+
raw,
|
|
1109
|
+
(column) => nameAlias(ctx, column),
|
|
1110
|
+
(value) => valueAlias(ctx, value)
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
function compileNode(ctx, node) {
|
|
1114
|
+
if (isRawCondition(node)) {
|
|
1115
|
+
return compileRaw(ctx, node);
|
|
1116
|
+
}
|
|
1117
|
+
const clauses = [];
|
|
1118
|
+
for (const [key2, value] of Object.entries(node)) {
|
|
1119
|
+
if (value === void 0) continue;
|
|
1120
|
+
if (key2 === "and" || key2 === "or") {
|
|
1121
|
+
const parts = value.map((sub) => compileNode(ctx, sub)).filter((s) => s.length > 0);
|
|
1122
|
+
if (parts.length === 0) continue;
|
|
1123
|
+
if (parts.length === 1) {
|
|
1124
|
+
clauses.push(parts[0]);
|
|
1125
|
+
} else {
|
|
1126
|
+
const sep = key2 === "and" ? " AND " : " OR ";
|
|
1127
|
+
clauses.push(`(${parts.map((p) => wrap(p)).join(sep)})`);
|
|
1128
|
+
}
|
|
1129
|
+
continue;
|
|
1130
|
+
}
|
|
1131
|
+
if (key2 === "not") {
|
|
1132
|
+
const inner = compileNode(ctx, value);
|
|
1133
|
+
if (inner.length > 0) clauses.push(`NOT ${wrap(inner)}`);
|
|
1134
|
+
continue;
|
|
1135
|
+
}
|
|
1136
|
+
const clause = compileField(ctx, key2, value);
|
|
1137
|
+
if (clause.length > 0) clauses.push(clause);
|
|
1138
|
+
}
|
|
1139
|
+
return joinAnd(clauses);
|
|
1140
|
+
}
|
|
1141
|
+
function resolveConditionTree(declarative, resolveParam) {
|
|
1142
|
+
return resolveNode(declarative, resolveParam);
|
|
1143
|
+
}
|
|
1144
|
+
function isParamLeaf(value) {
|
|
1145
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.$param === "string";
|
|
1146
|
+
}
|
|
1147
|
+
function resolveLeaf(value, resolveParam) {
|
|
1148
|
+
if (isParamLeaf(value)) return resolveParam(value.$param);
|
|
1149
|
+
return value;
|
|
1150
|
+
}
|
|
1151
|
+
function resolveNode(node, resolveParam) {
|
|
1152
|
+
const obj = node;
|
|
1153
|
+
const out = {};
|
|
1154
|
+
for (const [key2, value] of Object.entries(obj)) {
|
|
1155
|
+
if (key2 === "and" || key2 === "or") {
|
|
1156
|
+
out[key2] = value.map((s) => resolveNode(s, resolveParam));
|
|
1157
|
+
} else if (key2 === "not") {
|
|
1158
|
+
out[key2] = resolveNode(value, resolveParam);
|
|
1159
|
+
} else {
|
|
1160
|
+
const ops = value;
|
|
1161
|
+
const rendered = {};
|
|
1162
|
+
for (const [op, opVal] of Object.entries(ops)) {
|
|
1163
|
+
if (op === "between") {
|
|
1164
|
+
const [lo, hi] = opVal;
|
|
1165
|
+
rendered[op] = [
|
|
1166
|
+
resolveLeaf(lo, resolveParam),
|
|
1167
|
+
resolveLeaf(hi, resolveParam)
|
|
1168
|
+
];
|
|
1169
|
+
} else if (op === "in") {
|
|
1170
|
+
rendered[op] = opVal.map(
|
|
1171
|
+
(v) => resolveLeaf(v, resolveParam)
|
|
1172
|
+
);
|
|
1173
|
+
} else if (op === "attributeExists") {
|
|
1174
|
+
rendered[op] = opVal;
|
|
1175
|
+
} else {
|
|
1176
|
+
rendered[op] = resolveLeaf(opVal, resolveParam);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
out[key2] = rendered;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
return out;
|
|
1183
|
+
}
|
|
864
1184
|
function buildConditionExpression(condition) {
|
|
1185
|
+
if (isRawCondition(condition)) {
|
|
1186
|
+
const ctx2 = { names: {}, values: {}, valueCounter: { n: 0 } };
|
|
1187
|
+
const expression2 = compileRaw(ctx2, condition);
|
|
1188
|
+
return { expression: expression2, names: ctx2.names, values: ctx2.values };
|
|
1189
|
+
}
|
|
865
1190
|
if (condition.notExists === true) {
|
|
866
|
-
return {
|
|
867
|
-
expression: "attribute_not_exists(PK)",
|
|
868
|
-
names: {},
|
|
869
|
-
values: {}
|
|
870
|
-
};
|
|
1191
|
+
return { expression: "attribute_not_exists(PK)", names: {}, values: {} };
|
|
871
1192
|
}
|
|
872
1193
|
if (typeof condition.attributeExists === "string") {
|
|
873
1194
|
const field = condition.attributeExists;
|
|
@@ -887,22 +1208,9 @@ function buildConditionExpression(condition) {
|
|
|
887
1208
|
values: {}
|
|
888
1209
|
};
|
|
889
1210
|
}
|
|
890
|
-
const
|
|
891
|
-
const
|
|
892
|
-
|
|
893
|
-
let counter = 0;
|
|
894
|
-
for (const [fieldName, value] of Object.entries(condition)) {
|
|
895
|
-
const nameKey = `#cond_${fieldName}`;
|
|
896
|
-
const valueKey = `:cond${counter++}`;
|
|
897
|
-
names[nameKey] = fieldName;
|
|
898
|
-
values[valueKey] = value;
|
|
899
|
-
clauses.push(`${nameKey} = ${valueKey}`);
|
|
900
|
-
}
|
|
901
|
-
return {
|
|
902
|
-
expression: clauses.join(" AND "),
|
|
903
|
-
names,
|
|
904
|
-
values
|
|
905
|
-
};
|
|
1211
|
+
const ctx = { names: {}, values: {}, valueCounter: { n: 0 } };
|
|
1212
|
+
const expression = compileNode(ctx, condition);
|
|
1213
|
+
return { expression, names: ctx.names, values: ctx.values };
|
|
906
1214
|
}
|
|
907
1215
|
|
|
908
1216
|
// src/operations/serialize.ts
|
|
@@ -1037,6 +1345,29 @@ function resolveSegmentedKey(segmented, values, context = "key") {
|
|
|
1037
1345
|
};
|
|
1038
1346
|
}
|
|
1039
1347
|
|
|
1348
|
+
// src/operations/gsi-derive.ts
|
|
1349
|
+
function deriveGsiKey(gsi2, available, context) {
|
|
1350
|
+
const gsiInput = {};
|
|
1351
|
+
for (const fieldName of gsi2.inputFieldNames) {
|
|
1352
|
+
gsiInput[fieldName] = available[fieldName];
|
|
1353
|
+
}
|
|
1354
|
+
const { pk, sk } = resolveSegmentedKey(gsi2.segmented, gsiInput, context);
|
|
1355
|
+
return {
|
|
1356
|
+
pkAttr: `${gsi2.indexName}PK`,
|
|
1357
|
+
pk,
|
|
1358
|
+
skAttr: `${gsi2.indexName}SK`,
|
|
1359
|
+
sk: sk ?? ""
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
function affectedGsis(meta, changedFields) {
|
|
1363
|
+
return meta.gsiDefinitions.filter(
|
|
1364
|
+
(gsi2) => gsi2.inputFieldNames.some((f) => changedFields.has(f))
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1367
|
+
function missingGsiFields(gsi2, available) {
|
|
1368
|
+
return gsi2.inputFieldNames.filter((f) => available[f] === void 0);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1040
1371
|
// src/capture/registry.ts
|
|
1041
1372
|
var ChangeCaptureRegistryImpl = class {
|
|
1042
1373
|
subscribers = /* @__PURE__ */ new Set();
|
|
@@ -1142,18 +1473,18 @@ function buildPutInput(modelClass, item, options) {
|
|
|
1142
1473
|
dynamoItem[emb.propertyName] = value;
|
|
1143
1474
|
}
|
|
1144
1475
|
}
|
|
1476
|
+
const available = {};
|
|
1477
|
+
for (const fieldName of meta.fields.map((f) => f.propertyName)) {
|
|
1478
|
+
available[fieldName] = serialized[fieldName] ?? item[fieldName];
|
|
1479
|
+
}
|
|
1145
1480
|
for (const gsi2 of meta.gsiDefinitions) {
|
|
1146
|
-
const
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
}
|
|
1150
|
-
const { pk: gsiPk, sk: gsiSk } = resolveSegmentedKey(
|
|
1151
|
-
gsi2.segmented,
|
|
1152
|
-
gsiInput,
|
|
1481
|
+
const { pkAttr, pk: gsiPk, skAttr, sk: gsiSk } = deriveGsiKey(
|
|
1482
|
+
gsi2,
|
|
1483
|
+
available,
|
|
1153
1484
|
`${modelClass.name} GSI '${gsi2.indexName}'`
|
|
1154
1485
|
);
|
|
1155
|
-
dynamoItem[
|
|
1156
|
-
dynamoItem[
|
|
1486
|
+
dynamoItem[pkAttr] = gsiPk;
|
|
1487
|
+
dynamoItem[skAttr] = gsiSk;
|
|
1157
1488
|
}
|
|
1158
1489
|
const putInput = {
|
|
1159
1490
|
TableName: tableName,
|
|
@@ -1313,6 +1644,64 @@ function resolveUpdateKey(modelClass, primaryKey, entity, fieldMap) {
|
|
|
1313
1644
|
);
|
|
1314
1645
|
return { pk, sk: sk ?? "" };
|
|
1315
1646
|
}
|
|
1647
|
+
function buildGsiRederiveSets(modelClass, meta, entity, serializedChanges, fieldMap, names, values, options) {
|
|
1648
|
+
const changedFields = new Set(Object.keys(serializedChanges));
|
|
1649
|
+
const affected = affectedGsis(meta, changedFields);
|
|
1650
|
+
if (affected.length === 0) return [];
|
|
1651
|
+
const available = {};
|
|
1652
|
+
for (const [fieldName, raw] of Object.entries(entity)) {
|
|
1653
|
+
if (raw === void 0) continue;
|
|
1654
|
+
const fm = fieldMap.get(fieldName);
|
|
1655
|
+
available[fieldName] = fm ? serializeFieldValue(raw, fm) : raw;
|
|
1656
|
+
}
|
|
1657
|
+
Object.assign(available, serializedChanges);
|
|
1658
|
+
const clauses = [];
|
|
1659
|
+
let i = 0;
|
|
1660
|
+
for (const gsi2 of affected) {
|
|
1661
|
+
const missing = missingGsiFields(gsi2, available);
|
|
1662
|
+
if (missing.length > 0) {
|
|
1663
|
+
if (options?.rederive === "read-modify-write") continue;
|
|
1664
|
+
const changed = gsi2.inputFieldNames.filter((f) => changedFields.has(f));
|
|
1665
|
+
throw new Error(
|
|
1666
|
+
`Cannot update '${modelClass.name}': updating ${fmtFields(changed)} affects index '${gsi2.indexName}' (also depends on ${fmtFields(missing)}); provide ${missing.length > 1 ? "them" : "it"}, or use { rederive: 'read-modify-write' }.`
|
|
1667
|
+
);
|
|
1668
|
+
}
|
|
1669
|
+
const { pkAttr, pk, skAttr, sk } = deriveGsiKey(
|
|
1670
|
+
gsi2,
|
|
1671
|
+
available,
|
|
1672
|
+
`${modelClass.name} GSI '${gsi2.indexName}'`
|
|
1673
|
+
);
|
|
1674
|
+
const pkN = `#gsi${i}pk`;
|
|
1675
|
+
const pkV = `:gsi${i}pk`;
|
|
1676
|
+
names[pkN] = pkAttr;
|
|
1677
|
+
values[pkV] = pk;
|
|
1678
|
+
clauses.push(`${pkN} = ${pkV}`);
|
|
1679
|
+
const skN = `#gsi${i}sk`;
|
|
1680
|
+
const skV = `:gsi${i}sk`;
|
|
1681
|
+
names[skN] = skAttr;
|
|
1682
|
+
values[skV] = sk;
|
|
1683
|
+
clauses.push(`${skN} = ${skV}`);
|
|
1684
|
+
i++;
|
|
1685
|
+
}
|
|
1686
|
+
return clauses;
|
|
1687
|
+
}
|
|
1688
|
+
function fmtFields(fields) {
|
|
1689
|
+
return fields.map((f) => `'${f}'`).join(", ");
|
|
1690
|
+
}
|
|
1691
|
+
function appendSetClauses(expression, clauses) {
|
|
1692
|
+
if (clauses.length === 0) return expression;
|
|
1693
|
+
const extra = clauses.join(", ");
|
|
1694
|
+
if (/(^|\s)SET\s/.test(expression)) {
|
|
1695
|
+
const removeIdx = expression.indexOf(" REMOVE ");
|
|
1696
|
+
if (removeIdx === -1) {
|
|
1697
|
+
return `${expression}, ${extra}`;
|
|
1698
|
+
}
|
|
1699
|
+
const setPart = expression.slice(0, removeIdx);
|
|
1700
|
+
const rest = expression.slice(removeIdx);
|
|
1701
|
+
return `${setPart}, ${extra}${rest}`;
|
|
1702
|
+
}
|
|
1703
|
+
return `SET ${extra} ${expression}`.trimEnd();
|
|
1704
|
+
}
|
|
1316
1705
|
function buildUpdateInput(modelClass, entity, changes, options) {
|
|
1317
1706
|
const meta = MetadataRegistry.get(modelClass);
|
|
1318
1707
|
if (!meta.primaryKey) {
|
|
@@ -1353,6 +1742,16 @@ function buildUpdateInput(modelClass, entity, changes, options) {
|
|
|
1353
1742
|
const updateResult = buildUpdateExpression(serializedChanges, embeddedFieldNames);
|
|
1354
1743
|
const names = { ...updateResult.names };
|
|
1355
1744
|
const values = { ...updateResult.values };
|
|
1745
|
+
const gsiSetClauses = buildGsiRederiveSets(
|
|
1746
|
+
modelClass,
|
|
1747
|
+
meta,
|
|
1748
|
+
entity,
|
|
1749
|
+
serializedChanges,
|
|
1750
|
+
fieldMap,
|
|
1751
|
+
names,
|
|
1752
|
+
values,
|
|
1753
|
+
options
|
|
1754
|
+
);
|
|
1356
1755
|
let conditionExpression;
|
|
1357
1756
|
if (options?.condition) {
|
|
1358
1757
|
const condResult = buildConditionExpression(options.condition);
|
|
@@ -1366,7 +1765,7 @@ function buildUpdateInput(modelClass, entity, changes, options) {
|
|
|
1366
1765
|
PK: pk,
|
|
1367
1766
|
SK: sk
|
|
1368
1767
|
},
|
|
1369
|
-
UpdateExpression: updateResult.expression,
|
|
1768
|
+
UpdateExpression: appendSetClauses(updateResult.expression, gsiSetClauses),
|
|
1370
1769
|
ExpressionAttributeNames: names
|
|
1371
1770
|
};
|
|
1372
1771
|
if (Object.keys(values).length > 0) {
|
|
@@ -1387,8 +1786,72 @@ function serializeChanges(modelClass, changes) {
|
|
|
1387
1786
|
}
|
|
1388
1787
|
return out;
|
|
1389
1788
|
}
|
|
1789
|
+
function needsReadForRederive(meta, entity, serializedChanges, fieldMap) {
|
|
1790
|
+
const changedFields = new Set(Object.keys(serializedChanges));
|
|
1791
|
+
const affected = affectedGsis(meta, changedFields);
|
|
1792
|
+
if (affected.length === 0) return false;
|
|
1793
|
+
const available = {};
|
|
1794
|
+
for (const [fieldName, raw] of Object.entries(entity)) {
|
|
1795
|
+
if (raw === void 0) continue;
|
|
1796
|
+
const fm = fieldMap.get(fieldName);
|
|
1797
|
+
available[fieldName] = fm ? serializeFieldValue(raw, fm) : raw;
|
|
1798
|
+
}
|
|
1799
|
+
Object.assign(available, serializedChanges);
|
|
1800
|
+
return affected.some((gsi2) => missingGsiFields(gsi2, available).length > 0);
|
|
1801
|
+
}
|
|
1802
|
+
async function rederiveByReadModifyWrite(modelClass, entity, changes, options, pk, sk, tableName) {
|
|
1803
|
+
const result = await ClientManager.getExecutor().execute({
|
|
1804
|
+
type: "GetItem",
|
|
1805
|
+
tableName,
|
|
1806
|
+
keyCondition: { PK: pk, SK: sk },
|
|
1807
|
+
consistentRead: true
|
|
1808
|
+
});
|
|
1809
|
+
const current = result.items[0];
|
|
1810
|
+
if (!current) {
|
|
1811
|
+
throw new Error(
|
|
1812
|
+
`Cannot read-modify-write update '${modelClass.name}': no item exists at PK=${pk}, SK=${sk}. The row must exist to re-derive its GSI keys.`
|
|
1813
|
+
);
|
|
1814
|
+
}
|
|
1815
|
+
const enriched = { ...current, ...entity };
|
|
1816
|
+
const guarded = {
|
|
1817
|
+
...options,
|
|
1818
|
+
rederive: void 0,
|
|
1819
|
+
condition: options?.condition ?? { attributeExists: "PK" }
|
|
1820
|
+
};
|
|
1821
|
+
return buildUpdateInput(modelClass, enriched, changes, guarded);
|
|
1822
|
+
}
|
|
1390
1823
|
async function executeUpdate(modelClass, entity, changes, options) {
|
|
1391
|
-
|
|
1824
|
+
let updateInput;
|
|
1825
|
+
if (options?.rederive === "read-modify-write") {
|
|
1826
|
+
const meta = MetadataRegistry.get(modelClass);
|
|
1827
|
+
const fieldMap = new Map(meta.fields.map((f) => [f.propertyName, f]));
|
|
1828
|
+
const serializedChanges = {};
|
|
1829
|
+
for (const [fieldName, value] of Object.entries(changes)) {
|
|
1830
|
+
const fm = fieldMap.get(fieldName);
|
|
1831
|
+
serializedChanges[fieldName] = fm && value !== void 0 ? serializeFieldValue(value, fm) : value;
|
|
1832
|
+
}
|
|
1833
|
+
if (meta.primaryKey && needsReadForRederive(meta, entity, serializedChanges, fieldMap)) {
|
|
1834
|
+
const { pk, sk } = resolveUpdateKey(
|
|
1835
|
+
modelClass,
|
|
1836
|
+
meta.primaryKey,
|
|
1837
|
+
entity,
|
|
1838
|
+
fieldMap
|
|
1839
|
+
);
|
|
1840
|
+
updateInput = await rederiveByReadModifyWrite(
|
|
1841
|
+
modelClass,
|
|
1842
|
+
entity,
|
|
1843
|
+
changes,
|
|
1844
|
+
options,
|
|
1845
|
+
pk,
|
|
1846
|
+
sk,
|
|
1847
|
+
TableMapping.resolve(meta.tableName)
|
|
1848
|
+
);
|
|
1849
|
+
} else {
|
|
1850
|
+
updateInput = buildUpdateInput(modelClass, entity, changes, options);
|
|
1851
|
+
}
|
|
1852
|
+
} else {
|
|
1853
|
+
updateInput = buildUpdateInput(modelClass, entity, changes, options);
|
|
1854
|
+
}
|
|
1392
1855
|
const wantsOld = ChangeCaptureRegistry.wantsOldImage(modelClass);
|
|
1393
1856
|
const result = await ClientManager.getExecutor().update(
|
|
1394
1857
|
updateInput,
|
|
@@ -1719,17 +2182,17 @@ var TransactionContext = class {
|
|
|
1719
2182
|
this.assertWithinLimit();
|
|
1720
2183
|
const modelClass = resolveModelClass(model);
|
|
1721
2184
|
const { TableName, Key } = buildDeleteInput(modelClass, key2);
|
|
1722
|
-
const
|
|
2185
|
+
const cond2 = buildConditionExpression(options.condition);
|
|
1723
2186
|
const check = {
|
|
1724
2187
|
TableName,
|
|
1725
2188
|
Key,
|
|
1726
|
-
ConditionExpression:
|
|
2189
|
+
ConditionExpression: cond2.expression
|
|
1727
2190
|
};
|
|
1728
|
-
if (Object.keys(
|
|
1729
|
-
check.ExpressionAttributeNames =
|
|
2191
|
+
if (Object.keys(cond2.names).length > 0) {
|
|
2192
|
+
check.ExpressionAttributeNames = cond2.names;
|
|
1730
2193
|
}
|
|
1731
|
-
if (Object.keys(
|
|
1732
|
-
check.ExpressionAttributeValues =
|
|
2194
|
+
if (Object.keys(cond2.values).length > 0) {
|
|
2195
|
+
check.ExpressionAttributeValues = cond2.values;
|
|
1733
2196
|
}
|
|
1734
2197
|
this.items.push({ ConditionCheck: check });
|
|
1735
2198
|
}
|
|
@@ -1791,10 +2254,18 @@ export {
|
|
|
1791
2254
|
pkTemplate,
|
|
1792
2255
|
skTemplate,
|
|
1793
2256
|
resolveSegmentedKey,
|
|
2257
|
+
param,
|
|
2258
|
+
isParam,
|
|
1794
2259
|
BATCH_GET_MAX_KEYS,
|
|
1795
2260
|
BATCH_WRITE_MAX_ITEMS,
|
|
1796
2261
|
DynamoExecutor,
|
|
1797
2262
|
ClientManager,
|
|
2263
|
+
isRawCondition,
|
|
2264
|
+
cond,
|
|
2265
|
+
compileRawCondition,
|
|
2266
|
+
serializeRawCondition,
|
|
2267
|
+
collectRawConditionParams,
|
|
2268
|
+
resolveConditionTree,
|
|
1798
2269
|
buildConditionExpression,
|
|
1799
2270
|
serializeFieldValue,
|
|
1800
2271
|
ChangeCaptureRegistry,
|