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.
@@ -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 nameAlias = `#k${i}`;
829
- const valueAlias = `:k${i}`;
830
- exprAttrNames[nameAlias] = attrName;
831
- exprAttrValues[valueAlias] = value;
832
- conditionParts.push(`${nameAlias} = ${valueAlias}`);
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 nameAlias = `#k${idx}`;
838
- const valueAlias = `:k${idx}`;
839
- exprAttrNames[nameAlias] = rc.key;
840
- exprAttrValues[valueAlias] = rc.value;
841
- conditionParts.push(`begins_with(${nameAlias}, ${valueAlias})`);
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 clauses = [];
891
- const names = {};
892
- const values = {};
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 gsiInput = {};
1147
- for (const fieldName of gsi2.inputFieldNames) {
1148
- gsiInput[fieldName] = serialized[fieldName] ?? item[fieldName];
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[`${gsi2.indexName}PK`] = gsiPk;
1156
- dynamoItem[`${gsi2.indexName}SK`] = gsiSk ?? "";
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
- const updateInput = buildUpdateInput(modelClass, entity, changes, options);
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 cond = buildConditionExpression(options.condition);
2185
+ const cond2 = buildConditionExpression(options.condition);
1723
2186
  const check = {
1724
2187
  TableName,
1725
2188
  Key,
1726
- ConditionExpression: cond.expression
2189
+ ConditionExpression: cond2.expression
1727
2190
  };
1728
- if (Object.keys(cond.names).length > 0) {
1729
- check.ExpressionAttributeNames = cond.names;
2191
+ if (Object.keys(cond2.names).length > 0) {
2192
+ check.ExpressionAttributeNames = cond2.names;
1730
2193
  }
1731
- if (Object.keys(cond.values).length > 0) {
1732
- check.ExpressionAttributeValues = cond.values;
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,