@ftschopp/dynatable-core 1.2.5 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/builders/query/create-query-builder.d.ts +1 -1
  3. package/dist/builders/query/create-query-builder.d.ts.map +1 -1
  4. package/dist/builders/query/create-query-builder.js +56 -10
  5. package/dist/builders/query/types.d.ts +16 -0
  6. package/dist/builders/query/types.d.ts.map +1 -1
  7. package/dist/builders/scan/create-scan-builder.d.ts.map +1 -1
  8. package/dist/builders/scan/create-scan-builder.js +32 -0
  9. package/dist/builders/scan/types.d.ts +35 -0
  10. package/dist/builders/scan/types.d.ts.map +1 -1
  11. package/dist/builders/shared/operators.d.ts.map +1 -1
  12. package/dist/builders/shared/operators.js +8 -0
  13. package/dist/builders/shared/types.d.ts +1 -0
  14. package/dist/builders/shared/types.d.ts.map +1 -1
  15. package/dist/builders/update/create-update-builder.d.ts +8 -2
  16. package/dist/builders/update/create-update-builder.d.ts.map +1 -1
  17. package/dist/builders/update/create-update-builder.js +45 -16
  18. package/dist/builders/update/types.d.ts +13 -0
  19. package/dist/builders/update/types.d.ts.map +1 -1
  20. package/dist/entity/create-entity-api.d.ts.map +1 -1
  21. package/dist/entity/create-entity-api.js +8 -4
  22. package/dist/utils/model-utils.d.ts +28 -0
  23. package/dist/utils/model-utils.d.ts.map +1 -1
  24. package/dist/utils/model-utils.js +34 -1
  25. package/package.json +1 -1
  26. package/src/builders/query/create-query-builder.test.ts +324 -0
  27. package/src/builders/query/create-query-builder.ts +73 -10
  28. package/src/builders/query/types.ts +17 -0
  29. package/src/builders/scan/create-scan-builder.test.ts +121 -0
  30. package/src/builders/scan/create-scan-builder.ts +35 -1
  31. package/src/builders/scan/types.ts +39 -0
  32. package/src/builders/shared/operators.ts +8 -0
  33. package/src/builders/shared/types.ts +1 -0
  34. package/src/builders/update/create-update-builder.test.ts +197 -0
  35. package/src/builders/update/create-update-builder.ts +72 -19
  36. package/src/builders/update/types.ts +14 -0
  37. package/src/entity/create-entity-api.ts +8 -4
  38. package/src/utils/model-utils.test.ts +91 -1
  39. package/src/utils/model-utils.ts +54 -0
  40. package/tests/integration/instagram-clone.integration.test.ts +23 -3
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # @ftschopp/dynatable-core [1.4.0](https://github.com/ftschopp/dynatable/compare/@ftschopp/dynatable-core@1.3.0...@ftschopp/dynatable-core@1.4.0) (2026-05-08)
2
+
3
+
4
+ ### Features
5
+
6
+ * **core:** add iterate() async iterator and Scan executeWithPagination ([#21](https://github.com/ftschopp/dynatable/issues/21)) ([c31d458](https://github.com/ftschopp/dynatable/commit/c31d45861e92ba0c52240128084939128a6e4acc)), closes [#4](https://github.com/ftschopp/dynatable/issues/4)
7
+
8
+ # @ftschopp/dynatable-core [1.3.0](https://github.com/ftschopp/dynatable/compare/@ftschopp/dynatable-core@1.2.5...@ftschopp/dynatable-core@1.3.0) (2026-05-07)
9
+
10
+
11
+ ### Features
12
+
13
+ * **core:** auto-recompute index keys on update and improve composite-key queries ([ce4b7dc](https://github.com/ftschopp/dynatable/commit/ce4b7dc1d783ffe2d78fee7f43b999a179a9f9f4))
14
+
1
15
  ## @ftschopp/dynatable-core [1.2.5](https://github.com/ftschopp/dynatable/compare/@ftschopp/dynatable-core@1.2.4...@ftschopp/dynatable-core@1.2.5) (2026-05-05)
2
16
 
3
17
 
@@ -5,5 +5,5 @@ import { DynamoDBLogger } from '../../utils/dynamodb-logger';
5
5
  /**
6
6
  * Creates a QueryBuilder for a table
7
7
  */
8
- export declare function createQueryBuilder<Model>(tableName: string, client: DynamoDBClient, model?: ModelDefinition, logger?: DynamoDBLogger): QueryBuilder<Model>;
8
+ export declare function createQueryBuilder<Model>(tableName: string, client: DynamoDBClient, model?: ModelDefinition, logger?: DynamoDBLogger, entityType?: string): QueryBuilder<Model>;
9
9
  //# sourceMappingURL=create-query-builder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-query-builder.d.ts","sourceRoot":"","sources":["../../../src/builders/query/create-query-builder.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAK1D,OAAO,EAAE,YAAY,EAA8B,MAAM,SAAS,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAoT7D;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EACtC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,cAAc,EACtB,KAAK,CAAC,EAAE,eAAe,EACvB,MAAM,CAAC,EAAE,cAAc,GACtB,YAAY,CAAC,KAAK,CAAC,CA4BrB"}
1
+ {"version":3,"file":"create-query-builder.d.ts","sourceRoot":"","sources":["../../../src/builders/query/create-query-builder.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAK1D,OAAO,EAAE,YAAY,EAA8B,MAAM,SAAS,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAiX7D;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EACtC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,cAAc,EACtB,KAAK,CAAC,EAAE,eAAe,EACvB,MAAM,CAAC,EAAE,cAAc,EACvB,UAAU,CAAC,EAAE,MAAM,GAClB,YAAY,CAAC,KAAK,CAAC,CA6BrB"}
@@ -33,11 +33,24 @@ function getKeyNameForAttribute(fieldName, model, indexName) {
33
33
  return null;
34
34
  }
35
35
  /**
36
- * Apply key template to transform attribute value to key value
37
- * e.g., applyKeyTemplate("UP#${username}", "username", "johndoe") -> "UP#johndoe"
36
+ * Apply key template to transform an attribute value to its key value.
37
+ *
38
+ * For multi-variable templates (e.g. "RES#${category}#${code}") we can only fill
39
+ * the variable matching `fieldName`. For `beginsWith` queries we truncate the
40
+ * result at the first remaining `${...}` placeholder so the prefix is usable.
41
+ * For other operators an exact match is required, so we throw a clear error.
38
42
  */
39
- function applyKeyTemplate(template, fieldName, fieldValue) {
40
- return template.replace(`\${${fieldName}}`, fieldValue);
43
+ function applyKeyTemplate(template, fieldName, fieldValue, keyOperator) {
44
+ const substituted = template.replace(`\${${fieldName}}`, String(fieldValue));
45
+ const nextPlaceholder = substituted.indexOf('${');
46
+ if (nextPlaceholder === -1)
47
+ return substituted;
48
+ if (keyOperator === 'beginsWith') {
49
+ return substituted.slice(0, nextPlaceholder);
50
+ }
51
+ throw new Error(`Cannot use operator "${keyOperator ?? 'unknown'}" on key template "${template}" ` +
52
+ `with only "${fieldName}" provided — the template still contains unfilled ` +
53
+ `variable(s). Use beginsWith for prefix queries on composite keys.`);
41
54
  }
42
55
  /**
43
56
  * Separates a condition tree into key conditions and filter conditions
@@ -82,7 +95,7 @@ function separateConditions(condition, model, indexName) {
82
95
  if (cond.values) {
83
96
  for (const [valuePlaceholder, value] of Object.entries(cond.values)) {
84
97
  // Apply the key template to transform the value
85
- const transformedValue = applyKeyTemplate(keyInfo.template, fieldName, value);
98
+ const transformedValue = applyKeyTemplate(keyInfo.template, fieldName, value, cond.keyOperator);
86
99
  newValues[valuePlaceholder] = transformedValue;
87
100
  }
88
101
  }
@@ -178,18 +191,29 @@ function createQueryExecutor(state) {
178
191
  const { keyConditions, filterConditions } = separateConditions(state.condition, state.model, state.indexName);
179
192
  // Build KeyConditionExpression (simple AND)
180
193
  const keyExpr = buildSimpleAndExpression(keyConditions);
194
+ // Auto-inject an entity-type filter so query() only returns items
195
+ // belonging to this entity (single-table designs share PKs across
196
+ // entities, especially on GSIs).
197
+ const allFilters = [...filterConditions];
198
+ if (state.entityType) {
199
+ allFilters.push({
200
+ expression: '#_type = :_type',
201
+ names: { '#_type': '_type' },
202
+ values: { ':_type': state.entityType },
203
+ });
204
+ }
181
205
  // Build FilterExpression (can be complex with OR, NOT, etc.)
182
206
  let filterExpr = { expression: '', names: {}, values: {} };
183
- if (filterConditions.length > 0) {
184
- if (filterConditions.length === 1 && filterConditions[0]) {
185
- filterExpr = (0, conditions_1.buildExpression)(filterConditions[0]);
207
+ if (allFilters.length > 0) {
208
+ if (allFilters.length === 1 && allFilters[0]) {
209
+ filterExpr = (0, conditions_1.buildExpression)(allFilters[0]);
186
210
  }
187
211
  else {
188
212
  // Multiple filter conditions - combine with AND
189
213
  filterExpr = (0, conditions_1.buildExpression)({
190
214
  expression: '',
191
215
  operator: 'AND',
192
- children: filterConditions,
216
+ children: allFilters,
193
217
  });
194
218
  }
195
219
  }
@@ -231,6 +255,14 @@ function createQueryExecutor(state) {
231
255
  }),
232
256
  };
233
257
  },
258
+ /**
259
+ * ⚠️ Returns only the FIRST page of results. DynamoDB caps each Query
260
+ * response at ~1MB (or `Limit` if set). If the matching set is larger,
261
+ * the remaining items are silently dropped.
262
+ *
263
+ * Use {@link executeWithPagination} when you need to drive pagination
264
+ * yourself, or {@link iterate} to walk every matching item lazily.
265
+ */
234
266
  async execute() {
235
267
  const params = this.dbParams();
236
268
  const result = await state.client.send(new lib_dynamodb_1.QueryCommand(params));
@@ -248,12 +280,25 @@ function createQueryExecutor(state) {
248
280
  scannedCount: result.ScannedCount,
249
281
  };
250
282
  },
283
+ async *iterate() {
284
+ const baseParams = this.dbParams();
285
+ let cursor = baseParams.ExclusiveStartKey;
286
+ do {
287
+ const params = { ...baseParams, ExclusiveStartKey: cursor };
288
+ const result = await state.client.send(new lib_dynamodb_1.QueryCommand(params));
289
+ state.logger?.log('QueryCommand', params, result);
290
+ for (const item of (result.Items ?? [])) {
291
+ yield item;
292
+ }
293
+ cursor = result.LastEvaluatedKey;
294
+ } while (cursor);
295
+ },
251
296
  };
252
297
  }
253
298
  /**
254
299
  * Creates a QueryBuilder for a table
255
300
  */
256
- function createQueryBuilder(tableName, client, model, logger) {
301
+ function createQueryBuilder(tableName, client, model, logger, entityType) {
257
302
  return {
258
303
  where(fn) {
259
304
  // Create attribute builder proxy
@@ -273,6 +318,7 @@ function createQueryBuilder(tableName, client, model, logger) {
273
318
  model,
274
319
  condition,
275
320
  logger,
321
+ entityType,
276
322
  };
277
323
  return createQueryExecutor(initialState);
278
324
  },
@@ -60,6 +60,22 @@ export interface QueryExecutor<Model> extends ExecutableBuilder<Model[]> {
60
60
  * Executes the query and returns result with pagination metadata
61
61
  */
62
62
  executeWithPagination(): Promise<QueryResult<Model>>;
63
+ /**
64
+ * Returns an async iterator that paginates internally and yields one item at
65
+ * a time. Memory stays at one page; you can `break` out of the loop early.
66
+ *
67
+ * `Limit` set via `.limit()` is forwarded to DynamoDB as a *per-request*
68
+ * cap, not a total cap. To cap the total, count yourself inside the loop:
69
+ *
70
+ * ```ts
71
+ * let n = 0;
72
+ * for await (const item of builder.iterate()) {
73
+ * if (n++ >= 1000) break;
74
+ * process(item);
75
+ * }
76
+ * ```
77
+ */
78
+ iterate(): AsyncIterableIterator<Model>;
63
79
  }
64
80
  /**
65
81
  * Main Query Builder interface with type-safe where clause
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/builders/query/types.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEjF;;GAEG;AACH,MAAM,WAAW,WAAW,CAAC,KAAK;IAChC;;OAEG;IACH,KAAK,EAAE,KAAK,EAAE,CAAC;IAEf;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAEvC;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,KAAK,CAAE,SAAQ,iBAAiB,CAAC,KAAK,EAAE,CAAC;IACtE;;OAEG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAE3C;;OAEG;IACH,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAEzD;;OAEG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAElD;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAErD;;OAEG;IACH,cAAc,IAAI,aAAa,CAAC,KAAK,CAAC,CAAC;IAEvC;;OAEG;IACH,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAE1D;;OAEG;IACH,QAAQ,IAAI,GAAG,CAAC;IAEhB;;OAEG;IACH,OAAO,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IAE5B;;OAEG;IACH,qBAAqB,IAAI,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;CACtD;AAED;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,KAAK;IACjC;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,SAAS,KAAK,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;CACzF"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/builders/query/types.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEjF;;GAEG;AACH,MAAM,WAAW,WAAW,CAAC,KAAK;IAChC;;OAEG;IACH,KAAK,EAAE,KAAK,EAAE,CAAC;IAEf;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAEvC;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,KAAK,CAAE,SAAQ,iBAAiB,CAAC,KAAK,EAAE,CAAC;IACtE;;OAEG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAE3C;;OAEG;IACH,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAEzD;;OAEG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAElD;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAErD;;OAEG;IACH,cAAc,IAAI,aAAa,CAAC,KAAK,CAAC,CAAC;IAEvC;;OAEG;IACH,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAE1D;;OAEG;IACH,QAAQ,IAAI,GAAG,CAAC;IAEhB;;OAEG;IACH,OAAO,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IAE5B;;OAEG;IACH,qBAAqB,IAAI,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;IAErD;;;;;;;;;;;;;;OAcG;IACH,OAAO,IAAI,qBAAqB,CAAC,KAAK,CAAC,CAAC;CACzC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,KAAK;IACjC;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,SAAS,KAAK,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;CACzF"}
@@ -1 +1 @@
1
- {"version":3,"file":"create-scan-builder.d.ts","sourceRoot":"","sources":["../../../src/builders/scan/create-scan-builder.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,EAAgC,SAAS,EAAmB,MAAM,WAAW,CAAC;AACrF,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EACrC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE,SAAS,EAAO,EACzB,eAAe,GAAE,CAAC,MAAM,KAAK,CAAC,EAAO,EACrC,UAAU,CAAC,EAAE,MAAM,EACnB,gBAAgB,UAAQ,EACxB,SAAS,CAAC,EAAE,MAAM,EAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACvC,aAAa,CAAC,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,EAC1D,MAAM,CAAC,EAAE,cAAc,GACtB,WAAW,CAAC,KAAK,CAAC,CAmMpB"}
1
+ {"version":3,"file":"create-scan-builder.d.ts","sourceRoot":"","sources":["../../../src/builders/scan/create-scan-builder.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,EAAgC,SAAS,EAAmB,MAAM,WAAW,CAAC;AACrF,OAAO,EAAE,WAAW,EAAc,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EACrC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE,SAAS,EAAO,EACzB,eAAe,GAAE,CAAC,MAAM,KAAK,CAAC,EAAO,EACrC,UAAU,CAAC,EAAE,MAAM,EACnB,gBAAgB,UAAQ,EACxB,SAAS,CAAC,EAAE,MAAM,EAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACvC,aAAa,CAAC,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,EAC1D,MAAM,CAAC,EAAE,cAAc,GACtB,WAAW,CAAC,KAAK,CAAC,CAqOpB"}
@@ -92,12 +92,44 @@ function createScanBuilder(tableName, client, filters = [], projectionAttrs = []
92
92
  }
93
93
  return params;
94
94
  },
95
+ /**
96
+ * ⚠️ Returns only the FIRST page of results. DynamoDB caps each Scan
97
+ * response at ~1MB (or `Limit` if set). If the matching set is larger,
98
+ * the remaining items are silently dropped.
99
+ *
100
+ * Use {@link executeWithPagination} when you need to drive pagination
101
+ * yourself, or {@link iterate} to walk every matching item lazily.
102
+ */
95
103
  async execute() {
96
104
  const params = build().dbParams();
97
105
  const response = await client.send(new lib_dynamodb_1.ScanCommand(params));
98
106
  logger?.log('ScanCommand', params, response);
99
107
  return (response.Items || []);
100
108
  },
109
+ async executeWithPagination() {
110
+ const params = build().dbParams();
111
+ const response = await client.send(new lib_dynamodb_1.ScanCommand(params));
112
+ logger?.log('ScanCommand', params, response);
113
+ return {
114
+ items: (response.Items ?? []),
115
+ lastEvaluatedKey: response.LastEvaluatedKey,
116
+ count: response.Count,
117
+ scannedCount: response.ScannedCount,
118
+ };
119
+ },
120
+ async *iterate() {
121
+ const baseParams = build().dbParams();
122
+ let cursor = baseParams.ExclusiveStartKey;
123
+ do {
124
+ const params = { ...baseParams, ExclusiveStartKey: cursor };
125
+ const response = await client.send(new lib_dynamodb_1.ScanCommand(params));
126
+ logger?.log('ScanCommand', params, response);
127
+ for (const item of (response.Items ?? [])) {
128
+ yield item;
129
+ }
130
+ cursor = response.LastEvaluatedKey;
131
+ } while (cursor);
132
+ },
101
133
  });
102
134
  return build();
103
135
  }
@@ -1,4 +1,17 @@
1
1
  import { ExecutableBuilder, AttrBuilder, OpBuilder, Condition } from '../shared';
2
+ /**
3
+ * A single page of scan results plus metadata for manual pagination.
4
+ */
5
+ export interface ScanResult<Model> {
6
+ /** Items returned in this page. */
7
+ items: Model[];
8
+ /** Cursor for the next page; `undefined` when there are no more results. */
9
+ lastEvaluatedKey?: Record<string, any>;
10
+ /** Number of items returned after applying the filter. */
11
+ count?: number;
12
+ /** Number of items examined before applying the filter. */
13
+ scannedCount?: number;
14
+ }
2
15
  /**
3
16
  * Builder for DynamoDB Scan operations.
4
17
  * Scans the entire table or index without requiring key conditions.
@@ -39,5 +52,27 @@ export interface ScanBuilder<Model> extends ExecutableBuilder<Model[]> {
39
52
  * Returns a new immutable builder.
40
53
  */
41
54
  segment(segmentNumber: number, totalSegments: number): ScanBuilder<Model>;
55
+ /**
56
+ * Executes a single Scan request and returns the page along with the
57
+ * `lastEvaluatedKey` cursor and counts. Use this when you want to drive
58
+ * pagination yourself (e.g. expose a cursor to a client).
59
+ */
60
+ executeWithPagination(): Promise<ScanResult<Model>>;
61
+ /**
62
+ * Returns an async iterator that paginates internally and yields one item at
63
+ * a time. Memory stays at one page; you can `break` out of the loop early.
64
+ *
65
+ * `Limit` set via `.limit()` is forwarded to DynamoDB as a *per-request*
66
+ * cap, not a total cap. To cap the total, count yourself inside the loop:
67
+ *
68
+ * ```ts
69
+ * let n = 0;
70
+ * for await (const item of builder.iterate()) {
71
+ * if (n++ >= 1000) break;
72
+ * process(item);
73
+ * }
74
+ * ```
75
+ */
76
+ iterate(): AsyncIterableIterator<Model>;
42
77
  }
43
78
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/builders/scan/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEjF;;;GAGG;AACH,MAAM,WAAW,WAAW,CAAC,KAAK,CAAE,SAAQ,iBAAiB,CAAC,KAAK,EAAE,CAAC;IACpE;;;OAGG;IACH,MAAM,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,SAAS,KAAK,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAEvF;;;OAGG;IACH,MAAM,CAAC,KAAK,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAEnD;;;OAGG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAEzC;;;OAGG;IACH,cAAc,IAAI,WAAW,CAAC,KAAK,CAAC,CAAC;IAErC;;;OAGG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAElD;;;OAGG;IACH,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAEtE;;;OAGG;IACH,OAAO,CAAC,aAAa,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;CAC3E"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/builders/scan/types.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEjF;;GAEG;AACH,MAAM,WAAW,UAAU,CAAC,KAAK;IAC/B,mCAAmC;IACnC,KAAK,EAAE,KAAK,EAAE,CAAC;IACf,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACvC,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW,CAAC,KAAK,CAAE,SAAQ,iBAAiB,CAAC,KAAK,EAAE,CAAC;IACpE;;;OAGG;IACH,MAAM,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,SAAS,KAAK,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAEvF;;;OAGG;IACH,MAAM,CAAC,KAAK,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAEnD;;;OAGG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAEzC;;;OAGG;IACH,cAAc,IAAI,WAAW,CAAC,KAAK,CAAC,CAAC;IAErC;;;OAGG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAElD;;;OAGG;IACH,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAEtE;;;OAGG;IACH,OAAO,CAAC,aAAa,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAE1E;;;;OAIG;IACH,qBAAqB,IAAI,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;IAEpD;;;;;;;;;;;;;;OAcG;IACH,OAAO,IAAI,qBAAqB,CAAC,KAAK,CAAC,CAAC;CACzC"}
@@ -1 +1 @@
1
- {"version":3,"file":"operators.d.ts","sourceRoot":"","sources":["../../../src/builders/shared/operators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEpC;;;GAGG;AACH,wBAAgB,eAAe,IAAI,SAAS,CA2L3C;AAED;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,SAA6B,CAAC"}
1
+ {"version":3,"file":"operators.d.ts","sourceRoot":"","sources":["../../../src/builders/shared/operators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEpC;;;GAGG;AACH,wBAAgB,eAAe,IAAI,SAAS,CAmM3C;AAED;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,SAA6B,CAAC"}
@@ -21,6 +21,7 @@ function createOpBuilder() {
21
21
  expression: `#${attr.name} = :${valueName}`,
22
22
  names: { [`#${attr.name}`]: attr.name },
23
23
  values: { [`:${valueName}`]: value },
24
+ keyOperator: 'eq',
24
25
  };
25
26
  },
26
27
  ne: (attr, value) => {
@@ -29,6 +30,7 @@ function createOpBuilder() {
29
30
  expression: `#${attr.name} <> :${valueName}`,
30
31
  names: { [`#${attr.name}`]: attr.name },
31
32
  values: { [`:${valueName}`]: value },
33
+ keyOperator: 'ne',
32
34
  };
33
35
  },
34
36
  lt: (attr, value) => {
@@ -37,6 +39,7 @@ function createOpBuilder() {
37
39
  expression: `#${attr.name} < :${valueName}`,
38
40
  names: { [`#${attr.name}`]: attr.name },
39
41
  values: { [`:${valueName}`]: value },
42
+ keyOperator: 'lt',
40
43
  };
41
44
  },
42
45
  lte: (attr, value) => {
@@ -45,6 +48,7 @@ function createOpBuilder() {
45
48
  expression: `#${attr.name} <= :${valueName}`,
46
49
  names: { [`#${attr.name}`]: attr.name },
47
50
  values: { [`:${valueName}`]: value },
51
+ keyOperator: 'lte',
48
52
  };
49
53
  },
50
54
  gt: (attr, value) => {
@@ -53,6 +57,7 @@ function createOpBuilder() {
53
57
  expression: `#${attr.name} > :${valueName}`,
54
58
  names: { [`#${attr.name}`]: attr.name },
55
59
  values: { [`:${valueName}`]: value },
60
+ keyOperator: 'gt',
56
61
  };
57
62
  },
58
63
  gte: (attr, value) => {
@@ -61,6 +66,7 @@ function createOpBuilder() {
61
66
  expression: `#${attr.name} >= :${valueName}`,
62
67
  names: { [`#${attr.name}`]: attr.name },
63
68
  values: { [`:${valueName}`]: value },
69
+ keyOperator: 'gte',
64
70
  };
65
71
  },
66
72
  between: (attr, low, high) => {
@@ -73,6 +79,7 @@ function createOpBuilder() {
73
79
  [`:${lowName}`]: low,
74
80
  [`:${highName}`]: high,
75
81
  },
82
+ keyOperator: 'between',
76
83
  };
77
84
  },
78
85
  beginsWith: (attr, value) => {
@@ -81,6 +88,7 @@ function createOpBuilder() {
81
88
  expression: `begins_with(#${attr.name}, :${valueName})`,
82
89
  names: { [`#${attr.name}`]: attr.name },
83
90
  values: { [`:${valueName}`]: value },
91
+ keyOperator: 'beginsWith',
84
92
  };
85
93
  },
86
94
  contains: (attr, value) => {
@@ -9,6 +9,7 @@ export type Condition = {
9
9
  names?: Record<string, string>;
10
10
  values?: Record<string, any>;
11
11
  operator?: 'AND' | 'OR';
12
+ keyOperator?: 'eq' | 'ne' | 'lt' | 'lte' | 'gt' | 'gte' | 'between' | 'beginsWith';
12
13
  isNegated?: boolean;
13
14
  children?: Condition[];
14
15
  };
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/builders/shared/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/builders/shared/types.ts"],"names":[],"mappings":"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"}
@@ -1,14 +1,20 @@
1
1
  import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
2
2
  import { Condition } from '../shared';
3
- import { UpdateBuilder, UpdateAction } from './types';
3
+ import { UpdateBuilder, UpdateAction, IndexContext } from './types';
4
4
  import { DynamoDBLogger } from '../../utils/dynamodb-logger';
5
5
  /**
6
6
  * Creates an UpdateBuilder for an item key and table.
7
+ *
8
+ * When `indexContext` is provided, fields written via `.set()` that participate
9
+ * in any secondary-index template are detected and the affected index keys are
10
+ * recomputed automatically and included in the SET expression. If a template
11
+ * cannot be fully resolved from the primary-key vars plus the updates, building
12
+ * the params throws — the caller must include the missing fields in `.set()`.
7
13
  */
8
14
  export declare function createUpdateBuilder<Model>(tableName: string, key: Partial<Model>, client: DynamoDBClient, prevConditions?: Condition[], updateActions?: {
9
15
  set: UpdateAction[];
10
16
  remove: UpdateAction[];
11
17
  add: UpdateAction[];
12
18
  delete: UpdateAction[];
13
- }, returnMode?: 'NONE' | 'ALL_OLD' | 'ALL_NEW' | 'UPDATED_OLD' | 'UPDATED_NEW', valueCounter?: number, enableTimestamps?: boolean, logger?: DynamoDBLogger): UpdateBuilder<Model>;
19
+ }, returnMode?: 'NONE' | 'ALL_OLD' | 'ALL_NEW' | 'UPDATED_OLD' | 'UPDATED_NEW', valueCounter?: number, enableTimestamps?: boolean, logger?: DynamoDBLogger, indexContext?: IndexContext, setInputs?: Record<string, any>): UpdateBuilder<Model>;
14
20
  //# sourceMappingURL=create-update-builder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-update-builder.d.ts","sourceRoot":"","sources":["../../../src/builders/update/create-update-builder.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,EAAgC,SAAS,EAA4B,MAAM,WAAW,CAAC;AAC9F,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EACvC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,OAAO,CAAC,KAAK,CAAC,EACnB,MAAM,EAAE,cAAc,EACtB,cAAc,GAAE,SAAS,EAAO,EAChC,aAAa,GAAE;IACb,GAAG,EAAE,YAAY,EAAE,CAAC;IACpB,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,GAAG,EAAE,YAAY,EAAE,CAAC;IACpB,MAAM,EAAE,YAAY,EAAE,CAAC;CACuB,EAChD,UAAU,GAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,GAAG,aAAsB,EACnF,YAAY,SAAI,EAChB,gBAAgB,UAAQ,EACxB,MAAM,CAAC,EAAE,cAAc,GACtB,aAAa,CAAC,KAAK,CAAC,CAwStB"}
1
+ {"version":3,"file":"create-update-builder.d.ts","sourceRoot":"","sources":["../../../src/builders/update/create-update-builder.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,EAAgC,SAAS,EAA4B,MAAM,WAAW,CAAC;AAC9F,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAG7D;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EACvC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,OAAO,CAAC,KAAK,CAAC,EACnB,MAAM,EAAE,cAAc,EACtB,cAAc,GAAE,SAAS,EAAO,EAChC,aAAa,GAAE;IACb,GAAG,EAAE,YAAY,EAAE,CAAC;IACpB,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,GAAG,EAAE,YAAY,EAAE,CAAC;IACpB,MAAM,EAAE,YAAY,EAAE,CAAC;CACuB,EAChD,UAAU,GAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,GAAG,aAAsB,EACnF,YAAY,SAAI,EAChB,gBAAgB,UAAQ,EACxB,MAAM,CAAC,EAAE,cAAc,EACvB,YAAY,CAAC,EAAE,YAAY,EAC3B,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,GAClC,aAAa,CAAC,KAAK,CAAC,CAoVtB"}
@@ -3,10 +3,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createUpdateBuilder = createUpdateBuilder;
4
4
  const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
5
5
  const shared_1 = require("../shared");
6
+ const model_utils_1 = require("../../utils/model-utils");
6
7
  /**
7
8
  * Creates an UpdateBuilder for an item key and table.
9
+ *
10
+ * When `indexContext` is provided, fields written via `.set()` that participate
11
+ * in any secondary-index template are detected and the affected index keys are
12
+ * recomputed automatically and included in the SET expression. If a template
13
+ * cannot be fully resolved from the primary-key vars plus the updates, building
14
+ * the params throws — the caller must include the missing fields in `.set()`.
8
15
  */
9
- function createUpdateBuilder(tableName, key, client, prevConditions = [], updateActions = { set: [], remove: [], add: [], delete: [] }, returnMode = 'NONE', valueCounter = 0, enableTimestamps = false, logger) {
16
+ function createUpdateBuilder(tableName, key, client, prevConditions = [], updateActions = { set: [], remove: [], add: [], delete: [] }, returnMode = 'NONE', valueCounter = 0, enableTimestamps = false, logger, indexContext, setInputs = {}) {
10
17
  const conditions = [...prevConditions];
11
18
  const getUniqueValueName = (baseName) => {
12
19
  return `${baseName}_${valueCounter++}`;
@@ -26,21 +33,19 @@ function createUpdateBuilder(tableName, key, client, prevConditions = [], update
26
33
  });
27
34
  const opBuilder = (0, shared_1.createOpBuilder)();
28
35
  const condition = fn(attrs, opBuilder);
29
- return createUpdateBuilder(tableName, key, client, [...conditions, condition], updateActions, returnMode, valueCounter, enableTimestamps, logger);
36
+ return createUpdateBuilder(tableName, key, client, [...conditions, condition], updateActions, returnMode, valueCounter, enableTimestamps, logger, indexContext, setInputs);
30
37
  },
31
38
  set(attrOrUpdates, value) {
32
- // Check if this is a multiple updates object
33
- // An AttrRef has only 'name' property and is a string value
34
- // Multiple updates is an object with no value parameter
39
+ // When no value is provided and the first arg is a plain object,
40
+ // treat it as a Partial<Model>. The AttrRef overload always passes
41
+ // a value, so it's handled by the single-update path below.
35
42
  if (value === undefined &&
36
43
  typeof attrOrUpdates === 'object' &&
37
- attrOrUpdates !== null &&
38
- !(Object.keys(attrOrUpdates).length === 1 &&
39
- 'name' in attrOrUpdates &&
40
- typeof attrOrUpdates.name === 'string')) {
44
+ attrOrUpdates !== null) {
41
45
  // Multiple updates case
42
46
  const updates = attrOrUpdates;
43
47
  const newActions = [];
48
+ const newSetInputs = { ...setInputs };
44
49
  for (const [attr, val] of Object.entries(updates)) {
45
50
  const attrName = attr;
46
51
  const valueName = getUniqueValueName(attrName);
@@ -49,8 +54,9 @@ function createUpdateBuilder(tableName, key, client, prevConditions = [], update
49
54
  names: { [`#${attrName}`]: attrName },
50
55
  values: { [`:${valueName}`]: val },
51
56
  });
57
+ newSetInputs[attrName] = val;
52
58
  }
53
- return createUpdateBuilder(tableName, key, client, conditions, { ...updateActions, set: [...updateActions.set, ...newActions] }, returnMode, valueCounter, enableTimestamps, logger);
59
+ return createUpdateBuilder(tableName, key, client, conditions, { ...updateActions, set: [...updateActions.set, ...newActions] }, returnMode, valueCounter, enableTimestamps, logger, indexContext, newSetInputs);
54
60
  }
55
61
  // Single update case
56
62
  const attrName = normalizeAttr(attrOrUpdates);
@@ -60,7 +66,7 @@ function createUpdateBuilder(tableName, key, client, prevConditions = [], update
60
66
  names: { [`#${attrName}`]: attrName },
61
67
  values: { [`:${valueName}`]: value },
62
68
  };
63
- return createUpdateBuilder(tableName, key, client, conditions, { ...updateActions, set: [...updateActions.set, action] }, returnMode, valueCounter, enableTimestamps, logger);
69
+ return createUpdateBuilder(tableName, key, client, conditions, { ...updateActions, set: [...updateActions.set, action] }, returnMode, valueCounter, enableTimestamps, logger, indexContext, { ...setInputs, [attrName]: value });
64
70
  },
65
71
  remove(attr) {
66
72
  const attrName = normalizeAttr(attr);
@@ -68,7 +74,7 @@ function createUpdateBuilder(tableName, key, client, prevConditions = [], update
68
74
  expression: `#${attrName}`,
69
75
  names: { [`#${attrName}`]: attrName },
70
76
  };
71
- return createUpdateBuilder(tableName, key, client, conditions, { ...updateActions, remove: [...updateActions.remove, action] }, returnMode, valueCounter, enableTimestamps, logger);
77
+ return createUpdateBuilder(tableName, key, client, conditions, { ...updateActions, remove: [...updateActions.remove, action] }, returnMode, valueCounter, enableTimestamps, logger, indexContext, setInputs);
72
78
  },
73
79
  add(attr, value) {
74
80
  const attrName = normalizeAttr(attr);
@@ -78,7 +84,7 @@ function createUpdateBuilder(tableName, key, client, prevConditions = [], update
78
84
  names: { [`#${attrName}`]: attrName },
79
85
  values: { [`:${valueName}`]: value },
80
86
  };
81
- return createUpdateBuilder(tableName, key, client, conditions, { ...updateActions, add: [...updateActions.add, action] }, returnMode, valueCounter, enableTimestamps, logger);
87
+ return createUpdateBuilder(tableName, key, client, conditions, { ...updateActions, add: [...updateActions.add, action] }, returnMode, valueCounter, enableTimestamps, logger, indexContext, setInputs);
82
88
  },
83
89
  delete(attr, value) {
84
90
  const attrName = normalizeAttr(attr);
@@ -88,10 +94,10 @@ function createUpdateBuilder(tableName, key, client, prevConditions = [], update
88
94
  names: { [`#${attrName}`]: attrName },
89
95
  values: { [`:${valueName}`]: value },
90
96
  };
91
- return createUpdateBuilder(tableName, key, client, conditions, { ...updateActions, delete: [...updateActions.delete, action] }, returnMode, valueCounter, enableTimestamps, logger);
97
+ return createUpdateBuilder(tableName, key, client, conditions, { ...updateActions, delete: [...updateActions.delete, action] }, returnMode, valueCounter, enableTimestamps, logger, indexContext, setInputs);
92
98
  },
93
99
  returning(mode) {
94
- return createUpdateBuilder(tableName, key, client, conditions, updateActions, mode, valueCounter, enableTimestamps, logger);
100
+ return createUpdateBuilder(tableName, key, client, conditions, updateActions, mode, valueCounter, enableTimestamps, logger, indexContext, setInputs);
95
101
  },
96
102
  dbParams() {
97
103
  // Build UpdateExpression from actions
@@ -99,7 +105,30 @@ function createUpdateBuilder(tableName, key, client, prevConditions = [], update
99
105
  const allNames = {};
100
106
  const allValues = {};
101
107
  // Clone updateActions to avoid mutation
102
- const actionsToProcess = { ...updateActions };
108
+ const actionsToProcess = { ...updateActions, set: [...updateActions.set] };
109
+ // Auto-recompute secondary-index keys whose templates depend on any
110
+ // updated field. If a template can't be fully resolved from the primary
111
+ // key vars + the .set() payload, we throw — recomputing from the
112
+ // existing item would require an extra read the builder won't do.
113
+ if (indexContext) {
114
+ const { actions: idxActions, missing } = (0, model_utils_1.computeIndexUpdates)(indexContext.model, indexContext.keyVars, setInputs);
115
+ if (missing.length > 0) {
116
+ const details = missing
117
+ .map((m) => ` - ${m.index} ("${m.template}"): missing ${m.missing.join(', ')}`)
118
+ .join('\n');
119
+ throw new Error(`Update touches fields that participate in secondary index templates, ` +
120
+ `but the templates cannot be fully resolved from the update payload. ` +
121
+ `Include the missing fields in .set():\n${details}`);
122
+ }
123
+ for (const [indexName, resolved] of Object.entries(idxActions)) {
124
+ const valueName = getUniqueValueName(indexName);
125
+ actionsToProcess.set.push({
126
+ expression: `#${indexName} = :${valueName}`,
127
+ names: { [`#${indexName}`]: indexName },
128
+ values: { [`:${valueName}`]: resolved },
129
+ });
130
+ }
131
+ }
103
132
  // Add updatedAt timestamp if enabled
104
133
  if (enableTimestamps) {
105
134
  const now = new Date().toISOString();
@@ -1,4 +1,5 @@
1
1
  import type { UpdateCommandInput } from '@aws-sdk/lib-dynamodb';
2
+ import { ModelDefinition } from '../../core/types';
2
3
  import { OperationBuilder, AttrRef } from '../shared';
3
4
  /**
4
5
  * Update actions that can be performed on attributes
@@ -8,6 +9,18 @@ export type UpdateAction = {
8
9
  names?: Record<string, string>;
9
10
  values?: Record<string, any>;
10
11
  };
12
+ /**
13
+ * Context that lets the update builder auto-recompute secondary-index keys
14
+ * when a `.set()` touches a field that participates in their templates.
15
+ *
16
+ * `keyVars` carries the original template-variable values used to build the
17
+ * primary key (e.g. `{ id: '123' }` from `update({ id: '123' })`) so they can
18
+ * be combined with the user's `.set()` payload when resolving index templates.
19
+ */
20
+ export type IndexContext = {
21
+ model: ModelDefinition;
22
+ keyVars: Record<string, any>;
23
+ };
11
24
  /**
12
25
  * Builder interface for DynamoDB UpdateItem operations
13
26
  */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/builders/update/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEtD;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC9B,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,KAAK,CAAE,SAAQ,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC;IACrF;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,EAAE,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IACnE,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAEnD;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,OAAO,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAE1D;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,EAAE,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAEnE;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,EAAE,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAEtE;;OAEG;IACH,SAAS,CACP,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,GAAG,aAAa,GACnE,aAAa,CAAC,KAAK,CAAC,CAAC;IAExB;;OAEG;IACH,QAAQ,IAAI,kBAAkB,CAAC;CAChC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/builders/update/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEtD;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC9B,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,eAAe,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC9B,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,KAAK,CAAE,SAAQ,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC;IACrF;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,EAAE,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IACnE,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAEnD;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,OAAO,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAE1D;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,EAAE,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAEnE;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,EAAE,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAEtE;;OAEG;IACH,SAAS,CACP,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,GAAG,aAAa,GACnE,aAAa,CAAC,KAAK,CAAC,CAAC;IAExB;;OAEG;IACH,QAAQ,IAAI,kBAAkB,CAAC;CAChC"}
@@ -1 +1 @@
1
- {"version":3,"file":"create-entity-api.d.ts","sourceRoot":"","sources":["../../src/entity/create-entity-api.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AActF,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAMtD;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,SAAS,eAAe,EAC3D,WAAW,MAAM,EACjB,WAAW,MAAM,EACjB,OAAO,KAAK,EACZ,QAAQ,cAAc,EACtB,UAAS,gBAAqB,KAC7B,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,KAAK,CAAC,CAqLtE,CAAC"}
1
+ {"version":3,"file":"create-entity-api.d.ts","sourceRoot":"","sources":["../../src/entity/create-entity-api.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AActF,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAMtD;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,SAAS,eAAe,EAC3D,WAAW,MAAM,EACjB,WAAW,MAAM,EACjB,OAAO,KAAK,EACZ,QAAQ,cAAc,EACtB,UAAS,gBAAqB,KAC7B,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,KAAK,CAAC,CAyLtE,CAAC"}
@@ -49,9 +49,10 @@ const createEntityAPI = (tableName, modelName, model, client, options = {}) => {
49
49
  return (0, builders_1.createPutBuilder)(tableName, fullItem, client, [], false, 'NONE', false, logger);
50
50
  },
51
51
  query() {
52
- // Query builder doesn't have execute() until after .where() is called
53
- // The cleanInternalKeys will be handled in the builder itself
54
- return (0, builders_1.createQueryBuilder)(tableName, client, model, logger);
52
+ // Auto-filter by entity type so query() only returns items for this
53
+ // entity. Without this, queries on shared GSIs return items from other
54
+ // entities that share the same partition key.
55
+ return (0, builders_1.createQueryBuilder)(tableName, client, model, logger, modelName);
55
56
  },
56
57
  scan() {
57
58
  // Auto-filter by entity type so scan only returns items for this entity
@@ -68,7 +69,10 @@ const createEntityAPI = (tableName, modelName, model, client, options = {}) => {
68
69
  (0, key_validation_1.validateKeyFields)(modelName, model, key, 'update()');
69
70
  // Resolve any key defaults or computed keys
70
71
  const fullKey = (0, model_utils_1.resolveKeys)(model, key);
71
- const builder = (0, builders_1.createUpdateBuilder)(tableName, fullKey, client, [], { set: [], remove: [], add: [], delete: [] }, 'NONE', 0, timestamps, logger);
72
+ const builder = (0, builders_1.createUpdateBuilder)(tableName, fullKey, client, [], { set: [], remove: [], add: [], delete: [] }, 'NONE', 0, timestamps, logger,
73
+ // Carry the original template-vars-bearing key (e.g. { username: 'jane' })
74
+ // so the builder can recompute index keys when .set() touches their template fields.
75
+ { model, keyVars: key });
72
76
  return (0, with_middleware_1.withMiddleware)(builder, (0, factories_1.createCleanKeysMiddleware)(cleanInternalKeys));
73
77
  },
74
78
  delete(key) {