dinah 0.13.1 → 0.15.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.
package/dist/cdk.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as Table } from "./table-D814Vwjm.cjs";
1
+ import { t as Table } from "./table-GSI2RvTa.cjs";
2
2
  import { TSchema } from "typebox";
3
3
  import * as cdk from "aws-cdk-lib/aws-dynamodb";
4
4
 
package/dist/cdk.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as Table } from "./table-CatYaz7J.mjs";
1
+ import { t as Table } from "./table-O4EneQbj.mjs";
2
2
  import { TSchema } from "typebox";
3
3
  import * as cdk from "aws-cdk-lib/aws-dynamodb";
4
4
 
package/dist/index.cjs CHANGED
@@ -6,8 +6,9 @@ _aws_sdk_lib_dynamodb = require_util.__toESM(_aws_sdk_lib_dynamodb, 1);
6
6
  //#region src/error.ts
7
7
  function dinahErrorMessage(details) {
8
8
  switch (details.type) {
9
- case "NOT_FOUND": return `${details.resource ?? "Item"} not found: ${JSON.stringify(details.key)}`;
10
- case "CONDITIONAL_CHECK_FAILED": return details.key ? `${details.resource ?? "Item"} condition check failed: ${JSON.stringify(details.key)}` : `${details.resource ?? "Item"} condition check failed`;
9
+ case "NOT_FOUND": return `${details.resource ?? "Item"} not found.`;
10
+ case "ALREADY_EXISTS": return `${details.resource ?? "Item"} already exists.`;
11
+ case "CONDITIONAL_CHECK_FAILED": return `${details.resource ?? "Item"} condition check failed.`;
11
12
  case "VALIDATION": return `Validation error: ${details.message}`;
12
13
  case "DATA_INTEGRITY": return `Data integrity error: ${details.message}`;
13
14
  case "TRANSACTION_CANCELED": return `Transaction canceled: ${details.reasons.map((r) => r.type).join(", ")}`;
@@ -116,7 +117,10 @@ var ExpressionBuilder = class {
116
117
  const setDelOperations = [];
117
118
  for (const [path, valOrOperation] of Object.entries(expression)) {
118
119
  const placeholder = this.getPathSub(path);
119
- if (valOrOperation === void 0) removeOperations.push(placeholder);
120
+ if (valOrOperation === void 0) throw new DinahError({
121
+ type: "VALIDATION",
122
+ message: `Field "${path}" cannot be set to undefined in an update expression. Use { $remove: true } to remove an optional attribute.`
123
+ });
120
124
  else if (isOperation(valOrOperation)) if (valOrOperation.$remove === true) removeOperations.push(placeholder);
121
125
  else if (valOrOperation.$setAdd !== void 0 || valOrOperation.$setDel !== void 0) {
122
126
  const operations = valOrOperation.$setAdd ? setAddOperations : setDelOperations;
@@ -204,7 +208,10 @@ var ExpressionBuilder = class {
204
208
  const sets = [];
205
209
  const removes = [];
206
210
  const params = [];
207
- for (const [path, valOrOp] of Object.entries(expression)) if (valOrOp === void 0) removes.push(`"${path}"`);
211
+ for (const [path, valOrOp] of Object.entries(expression)) if (valOrOp === void 0) throw new DinahError({
212
+ type: "VALIDATION",
213
+ message: `Field "${path}" cannot be set to undefined in an update expression. Use { $remove: true } to remove an optional attribute.`
214
+ });
208
215
  else if (isOperation(valOrOp)) if (valOrOp.$remove === true) removes.push(`"${path}"`);
209
216
  else sets.push(`"${path}"=${this.resolvePartiQLSetOperand(path, valOrOp, params)}`);
210
217
  else {
@@ -311,18 +318,25 @@ var Repo = class {
311
318
  });
312
319
  return this.applyTransformIfNeeded(result);
313
320
  }
314
- async create(item, options) {
315
- const { condition: otherCondition, ...otherOptions } = options ?? {};
321
+ async create(item) {
316
322
  const condition = { $and: [{ [this.table.def.partitionKey]: { $exists: false } }] };
317
- if (otherCondition) condition.$and.push(otherCondition);
318
323
  const normalizedItem = this.applyCreateTransforms(item);
319
- const result = await this.db.put({
320
- table: this.tableName,
321
- item: normalizedItem,
322
- resource: this.resourceName,
323
- condition,
324
- ...otherOptions
325
- });
324
+ let result;
325
+ try {
326
+ result = await this.db.put({
327
+ table: this.tableName,
328
+ item: normalizedItem,
329
+ resource: this.resourceName,
330
+ condition
331
+ });
332
+ } catch (err) {
333
+ if (err instanceof DinahError && err.details.type === "CONDITIONAL_CHECK_FAILED") throw new DinahError({
334
+ type: "ALREADY_EXISTS",
335
+ key: this.extractKey(item),
336
+ resource: this.resourceName
337
+ }, { cause: err });
338
+ throw err;
339
+ }
326
340
  return this.applyTransformIfNeeded(result);
327
341
  }
328
342
  async delete(key, options) {
@@ -557,8 +571,8 @@ var Repo = class {
557
571
  async trxUpdate(keys, update, options) {
558
572
  return this.db.trxWrite(...keys.map((key) => this.trxUpdateRequest(key, update, options)));
559
573
  }
560
- async trxCreate(items, options) {
561
- return this.db.trxWrite(...items.map((item) => this.trxCreateRequest(item, options)));
574
+ async trxCreate(items) {
575
+ return this.db.trxWrite(...items.map((item) => this.trxCreateRequest(item)));
562
576
  }
563
577
  trxGetRequest(key, options) {
564
578
  return {
@@ -602,17 +616,14 @@ var Repo = class {
602
616
  condition: this.withDiscriminatorCondition(key, options?.condition)
603
617
  };
604
618
  }
605
- trxCreateRequest(item, options) {
606
- const { condition: otherCondition, ...otherOptions } = options ?? {};
619
+ trxCreateRequest(item) {
607
620
  const condition = { $and: [{ [this.table.def.partitionKey]: { $exists: false } }] };
608
- if (otherCondition) condition.$and.push(otherCondition);
609
621
  const normalizedItem = this.applyCreateTransforms(item);
610
622
  return {
611
623
  table: this.tableName,
612
624
  type: "PUT",
613
625
  item: normalizedItem,
614
- condition,
615
- ...otherOptions
626
+ condition
616
627
  };
617
628
  }
618
629
  applyCreateTransforms(item) {
@@ -632,7 +643,7 @@ var Repo = class {
632
643
  }
633
644
  if (computedAttributes) for (const [key, def] of Object.entries(computedAttributes)) {
634
645
  const { from, compute } = def;
635
- const computed = compute(merged[from]);
646
+ const computed = compute(...Array.isArray(from) ? from.map((k) => merged[k]) : [merged[from]]);
636
647
  if (computed !== void 0) merged[key] = computed;
637
648
  else delete merged[key];
638
649
  }
@@ -673,15 +684,42 @@ var Repo = class {
673
684
  }
674
685
  if (computedAttributes) for (const [computedKey, def] of Object.entries(computedAttributes)) {
675
686
  const { from, compute } = def;
676
- if (!(from in result)) continue;
677
- const val = result[from];
678
- if (val === void 0 || isOperation(val) && val.$remove === true) result[computedKey] = void 0;
679
- else if (!isOperation(val)) result[computedKey] = compute(val);
680
- else if (val.$set !== void 0) result[computedKey] = compute(val.$set);
681
- else throw new DinahError({
682
- type: "VALIDATION",
683
- message: `Field "${from}" drives computed field "${computedKey}" and cannot use arithmetic or list operators in updates.`
684
- });
687
+ if (Array.isArray(from)) {
688
+ const fromArr = from;
689
+ if (fromArr.filter((k) => k in result).length === 0) continue;
690
+ const missingKeys = fromArr.filter((k) => !(k in result));
691
+ if (missingKeys.length > 0) throw new DinahError({
692
+ type: "VALIDATION",
693
+ message: `Field "${computedKey}" is computed from [${fromArr.join(", ")}]. All source fields must be included in the update or none. Missing: [${missingKeys.join(", ")}].`
694
+ });
695
+ const args = [];
696
+ for (const key of fromArr) {
697
+ const val = result[key];
698
+ if (val === void 0 || isOperation(val) && val.$remove === true) args.push(void 0);
699
+ else if (!isOperation(val)) args.push(val);
700
+ else if (val.$set !== void 0) args.push(val.$set);
701
+ else throw new DinahError({
702
+ type: "VALIDATION",
703
+ message: `Field "${key}" drives computed field "${computedKey}" and cannot use arithmetic or list operators in updates.`
704
+ });
705
+ }
706
+ const computed = compute(...args);
707
+ result[computedKey] = computed !== void 0 ? computed : { $remove: true };
708
+ } else {
709
+ const fromKey = from;
710
+ if (!(fromKey in result)) continue;
711
+ const val = result[fromKey];
712
+ let resolvedVal;
713
+ if (val === void 0 || isOperation(val) && val.$remove === true) resolvedVal = void 0;
714
+ else if (!isOperation(val)) resolvedVal = val;
715
+ else if (val.$set !== void 0) resolvedVal = val.$set;
716
+ else throw new DinahError({
717
+ type: "VALIDATION",
718
+ message: `Field "${fromKey}" drives computed field "${computedKey}" and cannot use arithmetic or list operators in updates.`
719
+ });
720
+ const computed = compute(resolvedVal);
721
+ result[computedKey] = computed !== void 0 ? computed : { $remove: true };
722
+ }
685
723
  }
686
724
  return result;
687
725
  }
@@ -725,12 +763,19 @@ var Db = class {
725
763
  else {
726
764
  const { endpoint, ...clientConfig } = clientOrConfig;
727
765
  this.client = _aws_sdk_lib_dynamodb.DynamoDBDocumentClient.from(new _aws_sdk_client_dynamodb.DynamoDBClient({
728
- endpoint: endpoint || void 0,
766
+ ...endpoint ? { endpoint } : {},
729
767
  ...clientConfig
730
768
  }));
731
769
  }
732
770
  this.config = dbConfig;
733
771
  }
772
+ normalizeUpdate(expression) {
773
+ const behavior = this.config?.updateUndefinedBehavior ?? "throw";
774
+ if (behavior === "throw") return expression;
775
+ const entries = Object.entries(expression);
776
+ if (behavior === "strip") return Object.fromEntries(entries.filter(([, v]) => v !== void 0));
777
+ return Object.fromEntries(entries.map(([k, v]) => [k, v === void 0 ? { $remove: true } : v]));
778
+ }
734
779
  makeRepo(table, config) {
735
780
  return new Repo(this, table, config);
736
781
  }
@@ -807,7 +852,7 @@ var Db = class {
807
852
  Key: data.key,
808
853
  ReturnValues: "ALL_NEW",
809
854
  ReturnValuesOnConditionCheckFailure: "ALL_OLD",
810
- UpdateExpression: exp.update(data.update),
855
+ UpdateExpression: exp.update(this.normalizeUpdate(data.update)),
811
856
  ConditionExpression: exp.condition(condition),
812
857
  ExpressionAttributeNames: exp.attributeNames,
813
858
  ExpressionAttributeValues: exp.attributeValues
@@ -1074,7 +1119,7 @@ var Db = class {
1074
1119
  while (queue.length && retryCount < 5) {
1075
1120
  const batch = queue.splice(0, batchSize || 1);
1076
1121
  const statements = batch.map(([table, key, update]) => {
1077
- const { sets, removes, params } = new ExpressionBuilder().partiqlUpdate(update);
1122
+ const { sets, removes, params } = new ExpressionBuilder().partiqlUpdate(this.normalizeUpdate(update));
1078
1123
  const whereParts = [];
1079
1124
  for (const [field, value] of Object.entries(key)) {
1080
1125
  whereParts.push(`"${field}"=?`);
@@ -1159,7 +1204,7 @@ var Db = class {
1159
1204
  return { Update: {
1160
1205
  Key: request.key,
1161
1206
  TableName: request.table,
1162
- UpdateExpression: exp.update(request.update),
1207
+ UpdateExpression: exp.update(this.normalizeUpdate(request.update)),
1163
1208
  ConditionExpression: exp.condition(request.condition),
1164
1209
  ExpressionAttributeNames: exp.attributeNames,
1165
1210
  ExpressionAttributeValues: exp.attributeValues