graphddb 0.2.1 → 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.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ChangeCaptureRegistry,
3
3
  resolveModelClass
4
- } from "./chunk-PWV7JDMR.js";
4
+ } from "./chunk-3MI43FHU.js";
5
5
 
6
6
  // src/cdc/prng.ts
7
7
  var SeededRandom = class {
@@ -1345,6 +1345,29 @@ function resolveSegmentedKey(segmented, values, context = "key") {
1345
1345
  };
1346
1346
  }
1347
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
+
1348
1371
  // src/capture/registry.ts
1349
1372
  var ChangeCaptureRegistryImpl = class {
1350
1373
  subscribers = /* @__PURE__ */ new Set();
@@ -1450,18 +1473,18 @@ function buildPutInput(modelClass, item, options) {
1450
1473
  dynamoItem[emb.propertyName] = value;
1451
1474
  }
1452
1475
  }
1476
+ const available = {};
1477
+ for (const fieldName of meta.fields.map((f) => f.propertyName)) {
1478
+ available[fieldName] = serialized[fieldName] ?? item[fieldName];
1479
+ }
1453
1480
  for (const gsi2 of meta.gsiDefinitions) {
1454
- const gsiInput = {};
1455
- for (const fieldName of gsi2.inputFieldNames) {
1456
- gsiInput[fieldName] = serialized[fieldName] ?? item[fieldName];
1457
- }
1458
- const { pk: gsiPk, sk: gsiSk } = resolveSegmentedKey(
1459
- gsi2.segmented,
1460
- gsiInput,
1481
+ const { pkAttr, pk: gsiPk, skAttr, sk: gsiSk } = deriveGsiKey(
1482
+ gsi2,
1483
+ available,
1461
1484
  `${modelClass.name} GSI '${gsi2.indexName}'`
1462
1485
  );
1463
- dynamoItem[`${gsi2.indexName}PK`] = gsiPk;
1464
- dynamoItem[`${gsi2.indexName}SK`] = gsiSk ?? "";
1486
+ dynamoItem[pkAttr] = gsiPk;
1487
+ dynamoItem[skAttr] = gsiSk;
1465
1488
  }
1466
1489
  const putInput = {
1467
1490
  TableName: tableName,
@@ -1621,6 +1644,64 @@ function resolveUpdateKey(modelClass, primaryKey, entity, fieldMap) {
1621
1644
  );
1622
1645
  return { pk, sk: sk ?? "" };
1623
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
+ }
1624
1705
  function buildUpdateInput(modelClass, entity, changes, options) {
1625
1706
  const meta = MetadataRegistry.get(modelClass);
1626
1707
  if (!meta.primaryKey) {
@@ -1661,6 +1742,16 @@ function buildUpdateInput(modelClass, entity, changes, options) {
1661
1742
  const updateResult = buildUpdateExpression(serializedChanges, embeddedFieldNames);
1662
1743
  const names = { ...updateResult.names };
1663
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
+ );
1664
1755
  let conditionExpression;
1665
1756
  if (options?.condition) {
1666
1757
  const condResult = buildConditionExpression(options.condition);
@@ -1674,7 +1765,7 @@ function buildUpdateInput(modelClass, entity, changes, options) {
1674
1765
  PK: pk,
1675
1766
  SK: sk
1676
1767
  },
1677
- UpdateExpression: updateResult.expression,
1768
+ UpdateExpression: appendSetClauses(updateResult.expression, gsiSetClauses),
1678
1769
  ExpressionAttributeNames: names
1679
1770
  };
1680
1771
  if (Object.keys(values).length > 0) {
@@ -1695,8 +1786,72 @@ function serializeChanges(modelClass, changes) {
1695
1786
  }
1696
1787
  return out;
1697
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
+ }
1698
1823
  async function executeUpdate(modelClass, entity, changes, options) {
1699
- 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
+ }
1700
1855
  const wantsOld = ChangeCaptureRegistry.wantsOldImage(modelClass);
1701
1856
  const result = await ClientManager.getExecutor().update(
1702
1857
  updateInput,
@@ -32,7 +32,7 @@ import {
32
32
  serializeFieldValue,
33
33
  serializeRawCondition,
34
34
  skTemplate
35
- } from "./chunk-PWV7JDMR.js";
35
+ } from "./chunk-3MI43FHU.js";
36
36
 
37
37
  // src/spec/types.ts
38
38
  var SPEC_VERSION = "1.0";
@@ -4683,12 +4683,51 @@ function buildWriteItem(instr, forEachSource, txName) {
4683
4683
  )
4684
4684
  };
4685
4685
  }
4686
+ const changes = templateRecord2(instr.changes ?? {}, `${txName} update changes`);
4687
+ injectTransactionGsiRederive(
4688
+ metadata,
4689
+ instr.key ?? {},
4690
+ instr.changes ?? {},
4691
+ changes,
4692
+ `${txName} update on '${entity}'`
4693
+ );
4686
4694
  return {
4687
4695
  ...base,
4688
4696
  keyCondition: keyConditionTemplate2(metadata, instr.key ?? {}, `${txName} update`),
4689
- changes: templateRecord2(instr.changes ?? {}, `${txName} update changes`)
4697
+ changes
4690
4698
  };
4691
4699
  }
4700
+ function injectTransactionGsiRederive(metadata, keyStructure, changeStructure, changes, context) {
4701
+ if (metadata.gsiDefinitions.length === 0) return;
4702
+ const changedFields = new Set(Object.keys(changeStructure));
4703
+ const nameByField = /* @__PURE__ */ new Map();
4704
+ for (const [field, value] of Object.entries(keyStructure)) {
4705
+ if (isTransactionRef(value)) nameByField.set(field, tokenName(value.token));
4706
+ }
4707
+ for (const [field, value] of Object.entries(changeStructure)) {
4708
+ if (isTransactionRef(value)) nameByField.set(field, tokenName(value.token));
4709
+ }
4710
+ const available = new Set(nameByField.keys());
4711
+ for (const gsi of metadata.gsiDefinitions) {
4712
+ if (!gsi.inputFieldNames.some((f) => changedFields.has(f))) continue;
4713
+ const missing = gsi.inputFieldNames.filter((f) => !available.has(f));
4714
+ if (missing.length > 0) {
4715
+ const changed = gsi.inputFieldNames.filter((f) => changedFields.has(f));
4716
+ throw new Error(
4717
+ `defineTransaction: ${context}: updating ${changed.map((f) => `'${f}'`).join(", ")} affects index '${gsi.indexName}' (also depends on ${missing.map((f) => `'${f}'`).join(", ")}); add ${missing.length > 1 ? "them" : "it"} to the update key or changes.`
4718
+ );
4719
+ }
4720
+ const present = new Set(gsi.inputFieldNames);
4721
+ const { pk, sk } = evaluateKey(
4722
+ gsi.segmented,
4723
+ "param",
4724
+ present,
4725
+ (f) => nameByField.get(f) ?? f
4726
+ );
4727
+ changes[`${gsi.indexName}PK`] = pk;
4728
+ if (sk !== void 0) changes[`${gsi.indexName}SK`] = sk;
4729
+ }
4730
+ }
4692
4731
  function buildTransactionSpec(txName, def) {
4693
4732
  const items = [];
4694
4733
  let hasForEach = false;
package/dist/cli.js CHANGED
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  buildBridgeBundle,
4
4
  normalizeSelectSpec
5
- } from "./chunk-4B4MUPUJ.js";
5
+ } from "./chunk-CXIAECRS.js";
6
6
  import {
7
7
  MetadataRegistry
8
- } from "./chunk-PWV7JDMR.js";
8
+ } from "./chunk-3MI43FHU.js";
9
9
 
10
10
  // src/cli.ts
11
11
  import { createRequire } from "module";
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as SelectableOf, f as PrimaryKeyOf, E as Executor, g as ExecutionPlanSpec, h as SegmentedKey, i as SelectBuilderSpec, R as RawCondition, j as TransactionSpec, k as Manifest, l as ExecutionPlan, m as ResolvedKey, D as DynamoDBOperation, a as ExecutorResult, B as BatchGetExecInput, P as PutInput, W as WriteExecOptions, b as WriteResult, U as UpdateInput, c as DeleteInput, d as BatchWriteExecItem, T as TransactWriteExecItem, n as CdcEmulatorOptions, o as ChangeHandler, p as Unsubscribe, F as FaultSpec, q as ConcurrentRecomputeRef, C as ChangeEvent, r as EventLog, s as ReplayOptions, t as ShardId, u as TransactionItemSpec, v as Param, w as ParamDescriptor, x as DefinitionMap, O as OperationDefinition, e as DDBModel, M as ModelStatic, y as WriteDefinitionOptions, z as PartialQueryKeyOf, A as StrictSelectSpec, G as EntityInput, H as UniqueQueryKeyOf, I as EntityRef, J as ConditionInput, Q as QueryModelContract, K as QueryMethodSpec, L as CommandModelContract, N as CommandMethodSpec, V as ContractSpec, X as QuerySpec, Y as CommandSpec, Z as ContextSpec, _ as OperationsDocument, $ as AnyOperationDefinition, a0 as BridgeBundle, a1 as ConditionSpec } from './types-DPJ4tPjX.js';
2
- export { a2 as BatchDeleteRequest, a3 as BatchGetRequest, a4 as BatchGetResult, a5 as BatchPutRequest, a6 as BatchResult, a7 as BatchWriteRequest, a8 as CONTRACT_RANGE_FANOUT_CONCURRENCY, a9 as CdcMode, aa as ChangeBatch, ab as ChangeEventName, ac as ClockMode, ad as Column, ae as ColumnMap, af as CommandContractMethodSpec, ag as CommandInputShape, ah as CommandMethod, ai as CommandPlan, aj as CommandResolutionTarget, ak as CommandResultKind, al as CommandSelectShape, am as CompiledFragment, an as ComposeSpec, ao as CondSlot, ap as ConditionCheckInput, aq as Connection, ar as ContractCallSignature, as as ContractCardinality, at as ContractCommandParams, au as ContractCommandResult, av as ContractComposeNode, aw as ContractFromRef, ax as ContractInputArity, ay as ContractItem, az as ContractKeyFieldRef, aA as ContractKeyInput, aB as ContractKeyRef, aC as ContractKeySpec, aD as ContractKind, aE as ContractMethodOp, aF as ContractParamRef, aG as ContractQueryParams, aH as ContractResolution, aI as DeleteOptions, aJ as DeriveEffect, aK as DerivedEdgeWrite, aL as DerivedUpdate, aM as DescriptorBinding, aN as ENTITY_WRITES_MARKER, aO as EdgeEffect, aP as EffectPath, aQ as EmitEffect, aR as EntityWritesDefinition, aS as EntityWritesShape, aT as ExecutableCommandContract, aU as ExecutableQueryContract, aV as FilterInput, aW as FilterSpec, aX as FragmentInput, aY as GsiDefinitionMarker, aZ as GsiOptions, a_ as IdempotencyEffect, a$ as InProcessWriteDescriptor, b0 as InputArity, b1 as KeyDefinitionMarker, b2 as KeySegment, b3 as KeySlot, b4 as KeyStructure, b5 as KeyedResult, b6 as LIFECYCLE_CONTRACT_MARKER, b7 as LifecycleContract, b8 as LifecycleEffects, b9 as LiteralParam, ba as ManifestEntity, bb as ManifestField, bc as ManifestFieldType, bd as ManifestGsi, be as ManifestKey, bf as ManifestRelation, bg as ManifestTable, bh as ModelRef, bi as MutateMode, bj as MutateOptions, bk as MutateParallelResult, bl as MutateTransactionResult, bm as MutationBody, bn as MutationDescriptorMap, bo as MutationFragment, bp as MutationInputProxy, bq as MutationInputRef, br as MutationIntent, bs as NumberParam, bt as OperationKind, bu as OperationSpec, bv as ParallelOpResult, bw as ParamKind, bx as ParamSpec, by as ParamStructure, bz as PlannedCommandMethod, bA as PutOptions, bB as QueryContractMethodSpec, bC as QueryEnvelopeResult, bD as QueryKeyOf, bE as QueryMethod, bF as QueryResult, bG as RangeConditionSpec, bH as ReadEnvelope, bI as ReadOperationType, bJ as ReadRouteDescriptor, bK as ReadRouteOptions, bL as ReadRouteResult, bM as RecordedCompose, bN as RelationBuilder, bO as RelationSelect, bP as RelationSpec, bQ as RequiresEffect, bR as Resolution, bS as SPEC_VERSION, bT as SegmentSpec, bU as SelectBuilder, bV as SelectOf, bW as StartingPosition, bX as StreamViewType, bY as StringParam, bZ as TransactionContext, b_ as TransactionItemType, b$ as UniqueEffect, c0 as Updatable, c1 as UpdateOptions, c2 as WhenSpec, c3 as WriteDescriptor, c4 as WriteEnvelope, c5 as WriteLifecyclePhase, c6 as WriteOperationType, c7 as WriteRecorder, c8 as WriteResultProjection, c9 as attachModelClass, ca as buildDeleteInput, cb as buildPutInput, cc as buildUpdateInput, cd as compileFragment, ce as compileMutationPlan, cf as compileSingleFragmentPlan, cg as cond, ch as contractOfMethodSpec, ci as definePlan, cj as entityWrites, ck as executeBatchGet, cl as executeBatchWrite, cm as executeCommandMethod, cn as executeDelete, co as executeKeyedBatchGet, cp as executePut, cq as executeQueryMethod, cr as executeRangeFanout, cs as executeTransaction, ct as executeUpdate, cu as from, cv as getEntityWrites, cw as gsi, cx as isColumn, cy as isCommandModelContract, cz as isCommandPlan, cA as isContractComposeNode, cB as isContractFromRef, cC as isContractKeyFieldRef, cD as isContractKeyRef, cE as isContractParamRef, cF as isEntityWritesDefinition, cG as isKeySegment, cH as isLifecycleContract, cI as isMutationFragment, cJ as isMutationInputRef, cK as isParam, cL as isPlannedCommandMethod, cM as isQueryModelContract, cN as k, cO as key, cP as lifecyclePhaseForIntent, cQ as mintContractKeyFieldRef, cR as mintContractParamRef, cS as mutation, cT as param, cU as publicCommandModel, cV as publicQueryModel, cW as query, cX as resolveLifecycle, cY as wholeKeysSentinel } from './types-DPJ4tPjX.js';
1
+ import { S as SelectableOf, f as PrimaryKeyOf, E as Executor, g as ExecutionPlanSpec, h as SegmentedKey, i as SelectBuilderSpec, R as RawCondition, j as TransactionSpec, k as Manifest, l as ExecutionPlan, m as ResolvedKey, D as DynamoDBOperation, a as ExecutorResult, B as BatchGetExecInput, P as PutInput, W as WriteExecOptions, b as WriteResult, U as UpdateInput, c as DeleteInput, d as BatchWriteExecItem, T as TransactWriteExecItem, n as CdcEmulatorOptions, o as ChangeHandler, p as Unsubscribe, F as FaultSpec, q as ConcurrentRecomputeRef, C as ChangeEvent, r as EventLog, s as ReplayOptions, t as ShardId, u as TransactionItemSpec, v as Param, w as ParamDescriptor, x as DefinitionMap, O as OperationDefinition, e as DDBModel, M as ModelStatic, y as WriteDefinitionOptions, z as PartialQueryKeyOf, A as StrictSelectSpec, G as EntityInput, H as UniqueQueryKeyOf, I as EntityRef, J as ConditionInput, Q as QueryModelContract, K as QueryMethodSpec, L as CommandModelContract, N as CommandMethodSpec, V as ContractSpec, X as QuerySpec, Y as CommandSpec, Z as ContextSpec, _ as OperationsDocument, $ as AnyOperationDefinition, a0 as BridgeBundle, a1 as ConditionSpec } from './types-SKxZQbQO.js';
2
+ export { a2 as BatchDeleteRequest, a3 as BatchGetRequest, a4 as BatchGetResult, a5 as BatchPutRequest, a6 as BatchResult, a7 as BatchWriteRequest, a8 as CONTRACT_RANGE_FANOUT_CONCURRENCY, a9 as CdcMode, aa as ChangeBatch, ab as ChangeEventName, ac as ClockMode, ad as Column, ae as ColumnMap, af as CommandContractMethodSpec, ag as CommandInputShape, ah as CommandMethod, ai as CommandPlan, aj as CommandResolutionTarget, ak as CommandResultKind, al as CommandSelectShape, am as CompiledFragment, an as ComposeSpec, ao as CondSlot, ap as ConditionCheckInput, aq as Connection, ar as ContractCallSignature, as as ContractCardinality, at as ContractCommandParams, au as ContractCommandResult, av as ContractComposeNode, aw as ContractFromRef, ax as ContractInputArity, ay as ContractItem, az as ContractKeyFieldRef, aA as ContractKeyInput, aB as ContractKeyRef, aC as ContractKeySpec, aD as ContractKind, aE as ContractMethodOp, aF as ContractParamRef, aG as ContractQueryParams, aH as ContractResolution, aI as DeleteOptions, aJ as DeriveEffect, aK as DerivedEdgeWrite, aL as DerivedUpdate, aM as DescriptorBinding, aN as ENTITY_WRITES_MARKER, aO as EdgeEffect, aP as EffectPath, aQ as EmitEffect, aR as EntityWritesDefinition, aS as EntityWritesShape, aT as ExecutableCommandContract, aU as ExecutableQueryContract, aV as FilterInput, aW as FilterSpec, aX as FragmentInput, aY as GsiDefinitionMarker, aZ as GsiOptions, a_ as IdempotencyEffect, a$ as InProcessWriteDescriptor, b0 as InputArity, b1 as KeyDefinitionMarker, b2 as KeySegment, b3 as KeySlot, b4 as KeyStructure, b5 as KeyedResult, b6 as LIFECYCLE_CONTRACT_MARKER, b7 as LifecycleContract, b8 as LifecycleEffects, b9 as LiteralParam, ba as ManifestEntity, bb as ManifestField, bc as ManifestFieldType, bd as ManifestGsi, be as ManifestKey, bf as ManifestRelation, bg as ManifestTable, bh as ModelRef, bi as MutateMode, bj as MutateOptions, bk as MutateParallelResult, bl as MutateTransactionResult, bm as MutationBody, bn as MutationDescriptorMap, bo as MutationFragment, bp as MutationInputProxy, bq as MutationInputRef, br as MutationIntent, bs as NumberParam, bt as OperationKind, bu as OperationSpec, bv as ParallelOpResult, bw as ParamKind, bx as ParamSpec, by as ParamStructure, bz as PlannedCommandMethod, bA as PutOptions, bB as QueryContractMethodSpec, bC as QueryEnvelopeResult, bD as QueryKeyOf, bE as QueryMethod, bF as QueryResult, bG as RangeConditionSpec, bH as ReadEnvelope, bI as ReadOperationType, bJ as ReadRouteDescriptor, bK as ReadRouteOptions, bL as ReadRouteResult, bM as RecordedCompose, bN as RelationBuilder, bO as RelationSelect, bP as RelationSpec, bQ as RequiresEffect, bR as Resolution, bS as SPEC_VERSION, bT as SegmentSpec, bU as SelectBuilder, bV as SelectOf, bW as StartingPosition, bX as StreamViewType, bY as StringParam, bZ as TransactionContext, b_ as TransactionItemType, b$ as UniqueEffect, c0 as Updatable, c1 as UpdateOptions, c2 as WhenSpec, c3 as WriteDescriptor, c4 as WriteEnvelope, c5 as WriteLifecyclePhase, c6 as WriteOperationType, c7 as WriteRecorder, c8 as WriteResultProjection, c9 as attachModelClass, ca as buildDeleteInput, cb as buildPutInput, cc as buildUpdateInput, cd as compileFragment, ce as compileMutationPlan, cf as compileSingleFragmentPlan, cg as cond, ch as contractOfMethodSpec, ci as definePlan, cj as entityWrites, ck as executeBatchGet, cl as executeBatchWrite, cm as executeCommandMethod, cn as executeDelete, co as executeKeyedBatchGet, cp as executePut, cq as executeQueryMethod, cr as executeRangeFanout, cs as executeTransaction, ct as executeUpdate, cu as from, cv as getEntityWrites, cw as gsi, cx as isColumn, cy as isCommandModelContract, cz as isCommandPlan, cA as isContractComposeNode, cB as isContractFromRef, cC as isContractKeyFieldRef, cD as isContractKeyRef, cE as isContractParamRef, cF as isEntityWritesDefinition, cG as isKeySegment, cH as isLifecycleContract, cI as isMutationFragment, cJ as isMutationInputRef, cK as isParam, cL as isPlannedCommandMethod, cM as isQueryModelContract, cN as k, cO as key, cP as lifecyclePhaseForIntent, cQ as mintContractKeyFieldRef, cR as mintContractParamRef, cS as mutation, cT as param, cU as publicCommandModel, cV as publicQueryModel, cW as query, cX as resolveLifecycle, cY as wholeKeysSentinel } from './types-SKxZQbQO.js';
3
3
  import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
4
4
  import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
5
5
 
package/dist/index.js CHANGED
@@ -86,11 +86,11 @@ import {
86
86
  validateDepth,
87
87
  when,
88
88
  wholeKeysSentinel
89
- } from "./chunk-4B4MUPUJ.js";
89
+ } from "./chunk-CXIAECRS.js";
90
90
  import {
91
91
  CdcEmulator,
92
92
  createCdcEmulator
93
- } from "./chunk-SBNP62H7.js";
93
+ } from "./chunk-27XBTYVK.js";
94
94
  import {
95
95
  BATCH_GET_MAX_KEYS,
96
96
  BATCH_WRITE_MAX_ITEMS,
@@ -134,7 +134,7 @@ import {
134
134
  resolveKey,
135
135
  resolveModelClass,
136
136
  serializeFieldValue
137
- } from "./chunk-PWV7JDMR.js";
137
+ } from "./chunk-3MI43FHU.js";
138
138
 
139
139
  // src/metadata/prefix.ts
140
140
  function derivePrefix(customPrefix, className) {
@@ -1,4 +1,4 @@
1
- import { E as Executor, D as DynamoDBOperation, a as ExecutorResult, B as BatchGetExecInput, P as PutInput, W as WriteExecOptions, b as WriteResult, U as UpdateInput, c as DeleteInput, d as BatchWriteExecItem, T as TransactWriteExecItem, M as ModelStatic, e as DDBModel, C as ChangeEvent } from '../types-DPJ4tPjX.js';
1
+ import { E as Executor, D as DynamoDBOperation, a as ExecutorResult, B as BatchGetExecInput, P as PutInput, W as WriteExecOptions, b as WriteResult, U as UpdateInput, c as DeleteInput, d as BatchWriteExecItem, T as TransactWriteExecItem, M as ModelStatic, e as DDBModel, C as ChangeEvent } from '../types-SKxZQbQO.js';
2
2
  import '@aws-sdk/client-dynamodb';
3
3
 
4
4
  /**
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  createCdcEmulator
3
- } from "../chunk-SBNP62H7.js";
3
+ } from "../chunk-27XBTYVK.js";
4
4
  import {
5
5
  ClientManager,
6
6
  MetadataRegistry,
7
7
  TableMapping,
8
8
  resolveModelClass
9
- } from "../chunk-PWV7JDMR.js";
9
+ } from "../chunk-3MI43FHU.js";
10
10
 
11
11
  // src/memory/memory-store.ts
12
12
  function deepClone(value) {
@@ -864,6 +864,21 @@ interface PutOptions<T = unknown> {
864
864
  }
865
865
  interface UpdateOptions<T = unknown> {
866
866
  condition?: WriteCondition<T>;
867
+ /**
868
+ * How to re-derive a GSI key whose composing fields are changed but **not all**
869
+ * available from `{ ...key, ...changes }` (issue #115).
870
+ *
871
+ * - **unset (default)** — refuse with an explicit error rather than let the
872
+ * index silently rot. The error names the index, the changed field, and the
873
+ * missing field(s). The happy path (all composing fields available) re-derives
874
+ * in the SAME `UpdateExpression` and is unaffected by this option.
875
+ * - `'read-modify-write'` — read the current item, merge `{ ...item, ...changes }`,
876
+ * re-derive every affected GSI key from the merged image, and write back under
877
+ * an optimistic condition (the item must still exist / be unchanged). This costs
878
+ * one extra read and is NOT atomic with the original update, but always re-derives
879
+ * correctly.
880
+ */
881
+ rederive?: 'read-modify-write';
867
882
  }
868
883
  interface DeleteOptions<T = unknown> {
869
884
  condition?: WriteCondition<T>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphddb",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Graph data modeling on DynamoDB with adjacency list pattern",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",