electrodb 3.0.1 → 3.2.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/index.d.ts CHANGED
@@ -2716,23 +2716,23 @@ type GoBatchGetTerminal<
2716
2716
  > = <Options extends GoBatchGetTerminalOptions<keyof ResponseItem>>(
2717
2717
  options?: Options,
2718
2718
  ) => Options extends GoBatchGetTerminalOptions<infer Attr>
2719
- ? "preserveBatchOrder" extends keyof Options
2720
- ? Options["preserveBatchOrder"] extends true
2721
- ? Promise<{
2722
- data: Array<
2723
- Resolve<
2724
- | {
2725
- [Name in keyof ResponseItem as Name extends Attr
2726
- ? Name
2727
- : never]: ResponseItem[Name];
2728
- }
2729
- | null
2730
- >
2731
- >;
2732
- unprocessed: Array<
2733
- Resolve<AllTableIndexCompositeAttributes<A, F, C, S>>
2734
- >;
2735
- }>
2719
+ ? "preserveBatchOrder" extends keyof Options
2720
+ ? Options["preserveBatchOrder"] extends true
2721
+ ? Promise<{
2722
+ data: Array<
2723
+ Resolve<
2724
+ | {
2725
+ [Name in keyof ResponseItem as Name extends Attr
2726
+ ? Name
2727
+ : never]: ResponseItem[Name];
2728
+ }
2729
+ | null
2730
+ >
2731
+ >;
2732
+ unprocessed: Array<
2733
+ Resolve<AllTableIndexCompositeAttributes<A, F, C, S>>
2734
+ >;
2735
+ }>
2736
2736
  : Promise<{
2737
2737
  data: Array<
2738
2738
  Resolve<{
@@ -2758,23 +2758,23 @@ type GoBatchGetTerminal<
2758
2758
  >;
2759
2759
  }>
2760
2760
  : "preserveBatchOrder" extends keyof Options
2761
- ? Options["preserveBatchOrder"] extends true
2762
- ? {
2763
- data: Array<Resolve<ResponseItem | null>>;
2764
- unprocessed: Array<
2765
- Resolve<AllTableIndexCompositeAttributes<A, F, C, S>>
2766
- >;
2767
- }
2768
- : {
2761
+ ? Options["preserveBatchOrder"] extends true
2762
+ ? Promise<{
2763
+ data: Array<Resolve<ResponseItem | null>>;
2764
+ unprocessed: Array<
2765
+ Resolve<AllTableIndexCompositeAttributes<A, F, C, S>>
2766
+ >;
2767
+ }>
2768
+ : Promise<{
2769
+ data: Array<Resolve<ResponseItem>>;
2770
+ unprocessed: Array<
2771
+ Resolve<AllTableIndexCompositeAttributes<A, F, C, S>>
2772
+ >;
2773
+ }>
2774
+ : Promise<{
2769
2775
  data: Array<Resolve<ResponseItem>>;
2770
- unprocessed: Array<
2771
- Resolve<AllTableIndexCompositeAttributes<A, F, C, S>>
2772
- >;
2773
- }
2774
- : {
2775
- data: Array<Resolve<ResponseItem>>;
2776
- unprocessed: Array<Resolve<AllTableIndexCompositeAttributes<A, F, C, S>>>;
2777
- };
2776
+ unprocessed: Array<Resolve<AllTableIndexCompositeAttributes<A, F, C, S>>>;
2777
+ }>;
2778
2778
 
2779
2779
  type GoGetTerminal<
2780
2780
  A extends string,
@@ -2935,9 +2935,10 @@ export class ElectroError<E extends Error = Error> extends Error {
2935
2935
  readonly name: "ElectroError";
2936
2936
  readonly code: number;
2937
2937
  readonly date: number;
2938
- readonly isElectroError: boolean;
2939
2938
  readonly cause: E | undefined;
2940
- ref: {
2939
+ readonly isElectroError: boolean;
2940
+ readonly params: <T = Record<string, unknown>>() => T | null;
2941
+ readonly ref: {
2941
2942
  readonly code: number;
2942
2943
  readonly section: string;
2943
2944
  readonly name: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electrodb",
3
- "version": "3.0.1",
3
+ "version": "3.2.0",
4
4
  "description": "A library to more easily create and interact with multiple entities and heretical relationships in dynamodb",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/src/entity.js CHANGED
@@ -466,8 +466,10 @@ class Entity {
466
466
  if (err.__isAWSError) {
467
467
  stackTrace.message = `Error thrown by DynamoDB client: "${err.message}" - For more detail on this error reference: https://electrodb.dev/en/reference/errors/#aws-error`;
468
468
  stackTrace.cause = err;
469
+ e.applyParamsFn(stackTrace, err.__edb_params);
469
470
  return Promise.reject(stackTrace);
470
471
  } else if (err.isElectroError) {
472
+ e.applyParamsFn(err, err.__edb_params);
471
473
  return Promise.reject(err);
472
474
  } else {
473
475
  stackTrace.message = new e.ElectroError(
@@ -475,6 +477,7 @@ class Entity {
475
477
  err.message,
476
478
  err,
477
479
  ).message;
480
+ e.applyParamsFn(stackTrace, err.__edb_params);
478
481
  return Promise.reject(stackTrace);
479
482
  }
480
483
  }
@@ -516,6 +519,10 @@ class Entity {
516
519
  .catch((err) => {
517
520
  notifyQuery();
518
521
  notifyResults(err, false);
522
+ Object.defineProperty(err, '__edb_params', {
523
+ enumerable: false,
524
+ value: params,
525
+ });
519
526
  err.__isAWSError = true;
520
527
  throw err;
521
528
  });
@@ -935,7 +942,7 @@ class Entity {
935
942
  response.Item,
936
943
  config,
937
944
  );
938
- if (Object.keys(results).length === 0) {
945
+ if (Object.keys(results).length === 0 && !config._objectOnEmpty) {
939
946
  results = null;
940
947
  }
941
948
  } else if (!config._objectOnEmpty) {
@@ -957,7 +964,7 @@ class Entity {
957
964
  item,
958
965
  config,
959
966
  );
960
- if (Object.keys(record).length > 0) {
967
+ if (Object.keys(record).length > 0 || config._objectOnEmpty) {
961
968
  results.push(record);
962
969
  }
963
970
  }
@@ -967,7 +974,7 @@ class Entity {
967
974
  response.Attributes,
968
975
  config,
969
976
  );
970
- if (Object.keys(results).length === 0) {
977
+ if (Object.keys(results).length === 0 && !config._objectOnEmpty) {
971
978
  results = null;
972
979
  }
973
980
  } else if (config._objectOnEmpty) {
@@ -1646,6 +1653,7 @@ class Entity {
1646
1653
  order: undefined,
1647
1654
  hydrate: false,
1648
1655
  hydrator: (_entity, _indexName, items) => items,
1656
+ _objectOnEmpty: false,
1649
1657
  _includeOnResponseItem: {},
1650
1658
  };
1651
1659
 
@@ -1727,6 +1735,9 @@ class Entity {
1727
1735
 
1728
1736
  if (Array.isArray(option.attributes)) {
1729
1737
  config.attributes = config.attributes.concat(option.attributes);
1738
+ if (config.attributes.length > 0) {
1739
+ config._objectOnEmpty = true;
1740
+ }
1730
1741
  }
1731
1742
 
1732
1743
  if (option.preserveBatchOrder === true) {
@@ -2898,9 +2909,7 @@ class Entity {
2898
2909
  _getComparisonOperator(comparison, skType, comparisonType) {
2899
2910
  if (skType === "number") {
2900
2911
  return Comparisons[comparison];
2901
- } else if (
2902
- comparisonType === ComparisonTypes.v2
2903
- ) {
2912
+ } else if (comparisonType === ComparisonTypes.v2) {
2904
2913
  return KeyAttributesComparisons[comparison];
2905
2914
  } else {
2906
2915
  return Comparisons[comparison];
@@ -4267,6 +4276,7 @@ class Entity {
4267
4276
  facets: parsedPKAttributes.attributes,
4268
4277
  isCustom: parsedPKAttributes.isCustom,
4269
4278
  facetLabels: parsedPKAttributes.labels,
4279
+ template: index.pk.template,
4270
4280
  };
4271
4281
  let sk = {};
4272
4282
  let parsedSKAttributes = {};
@@ -4285,6 +4295,7 @@ class Entity {
4285
4295
  facets: parsedSKAttributes.attributes,
4286
4296
  isCustom: parsedSKAttributes.isCustom,
4287
4297
  facetLabels: parsedSKAttributes.labels,
4298
+ template: index.sk.template,
4288
4299
  };
4289
4300
  facets.fields.push(sk.field);
4290
4301
  }
@@ -4400,10 +4411,12 @@ class Entity {
4400
4411
  const definition = Object.values(facets.byField[pk.field]).find(
4401
4412
  (definition) => definition.index !== indexName,
4402
4413
  );
4414
+
4403
4415
  const definitionsMatch = validations.stringArrayMatch(
4404
4416
  pk.facets,
4405
4417
  definition.facets,
4406
4418
  );
4419
+
4407
4420
  if (!definitionsMatch) {
4408
4421
  throw new e.ElectroError(
4409
4422
  e.ErrorCodes.InconsistentIndexDefinition,
@@ -4418,6 +4431,20 @@ class Entity {
4418
4431
  )}'. Key fields must have the same composite attribute definitions across all indexes they are involved with`,
4419
4432
  );
4420
4433
  }
4434
+
4435
+ const keyTemplatesMatch = pk.template === definition.template
4436
+
4437
+ if (!keyTemplatesMatch) {
4438
+ throw new e.ElectroError(
4439
+ e.ErrorCodes.IncompatibleKeyCompositeAttributeTemplate,
4440
+ `Partition Key (pk) on Access Pattern '${u.formatIndexNameForDisplay(
4441
+ accessPattern,
4442
+ )}' is defined with the template ${pk.template || '(undefined)'}, but the accessPattern '${u.formatIndexNameForDisplay(
4443
+ definition.index,
4444
+ )}' defines this field with the key labels ${definition.template || '(undefined)'}'. Key fields must have the same template definitions across all indexes they are involved with`,
4445
+ );
4446
+ }
4447
+
4421
4448
  seenIndexFields[pk.field].push({ accessPattern, type: "pk" });
4422
4449
  } else {
4423
4450
  seenIndexFields[pk.field] = [];
@@ -4438,7 +4465,8 @@ class Entity {
4438
4465
  const isAlsoDefinedAsPK = seenIndexFields[sk.field].find(
4439
4466
  (field) => field.type === "pk",
4440
4467
  );
4441
- if (isAlsoDefinedAsPK) {
4468
+
4469
+ if (isAlsoDefinedAsPK && !sk.isCustom) {
4442
4470
  throw new e.ElectroError(
4443
4471
  e.ErrorCodes.InconsistentIndexDefinition,
4444
4472
  `The Sort Key (sk) on Access Pattern '${u.formatIndexNameForDisplay(
@@ -4447,16 +4475,19 @@ class Entity {
4447
4475
  pk.field
4448
4476
  }' which is already referenced by the Access Pattern(s) '${u.formatIndexNameForDisplay(
4449
4477
  isAlsoDefinedAsPK.accessPattern,
4450
- )}' as a Partition Key. Fields mapped to Partition Keys cannot be also mapped to Sort Keys.`,
4478
+ )}' as a Partition Key. Fields mapped to Partition Keys cannot be also mapped to Sort Keys unless their format is defined with a 'template'.`,
4451
4479
  );
4452
4480
  }
4481
+
4453
4482
  const definition = Object.values(facets.byField[sk.field]).find(
4454
4483
  (definition) => definition.index !== indexName,
4455
4484
  );
4485
+
4456
4486
  const definitionsMatch = validations.stringArrayMatch(
4457
4487
  sk.facets,
4458
4488
  definition.facets,
4459
- );
4489
+ )
4490
+
4460
4491
  if (!definitionsMatch) {
4461
4492
  throw new e.ElectroError(
4462
4493
  e.ErrorCodes.DuplicateIndexFields,
@@ -4471,6 +4502,20 @@ class Entity {
4471
4502
  )}'. Key fields must have the same composite attribute definitions across all indexes they are involved with`,
4472
4503
  );
4473
4504
  }
4505
+
4506
+ const keyTemplatesMatch = sk.template === definition.template
4507
+
4508
+ if (!keyTemplatesMatch) {
4509
+ throw new e.ElectroError(
4510
+ e.ErrorCodes.IncompatibleKeyCompositeAttributeTemplate,
4511
+ `Sort Key (sk) on Access Pattern '${u.formatIndexNameForDisplay(
4512
+ accessPattern,
4513
+ )}' is defined with the template ${sk.template || '(undefined)'}, but the accessPattern '${u.formatIndexNameForDisplay(
4514
+ definition.index,
4515
+ )}' defines this field with the key labels ${definition.template || '(undefined)'}'. Key fields must have the same template definitions across all indexes they are involved with`,
4516
+ );
4517
+ }
4518
+
4474
4519
  seenIndexFields[sk.field].push({ accessPattern, type: "sk" });
4475
4520
  } else {
4476
4521
  seenIndexFields[sk.field] = [];
package/src/errors.js CHANGED
@@ -280,7 +280,7 @@ function makeMessage(message, section) {
280
280
  }
281
281
 
282
282
  class ElectroError extends Error {
283
- constructor(code, message, cause) {
283
+ constructor(code, message, cause, params = null) {
284
284
  super(message, { cause });
285
285
  let detail = ErrorCodes.UnknownError;
286
286
  if (code && code.sym === ErrorCode) {
@@ -298,9 +298,21 @@ class ElectroError extends Error {
298
298
  this.code = detail.code;
299
299
  this.date = Date.now();
300
300
  this.isElectroError = true;
301
+ applyParamsFn(this, params);
301
302
  }
302
303
  }
303
304
 
305
+ function applyParamsFn(error, params = null) {
306
+ Object.defineProperty(error, 'params', {
307
+ enumerable: false,
308
+ writable: true,
309
+ configurable: true,
310
+ value: () => {
311
+ return params;
312
+ }
313
+ });
314
+ }
315
+
304
316
  class ElectroValidationError extends ElectroError {
305
317
  constructor(errors = []) {
306
318
  const fields = [];
@@ -389,6 +401,7 @@ class ElectroAttributeValidationError extends ElectroError {
389
401
  module.exports = {
390
402
  ErrorCodes,
391
403
  ElectroError,
404
+ applyParamsFn,
392
405
  ElectroValidationError,
393
406
  ElectroUserValidationError,
394
407
  ElectroAttributeValidationError,
package/src/operations.js CHANGED
@@ -19,6 +19,7 @@ class ExpressionState {
19
19
  this.expression = "";
20
20
  this.prefix = prefix || "";
21
21
  this.refs = {};
22
+ this.formattedNameToOriginalNameMap = new Map();
22
23
  }
23
24
 
24
25
  incrementName(name) {
@@ -30,14 +31,28 @@ class ExpressionState {
30
31
 
31
32
  formatName(name = "") {
32
33
  const nameWasNotANumber = isNaN(name);
33
- name = `${name}`.replaceAll(/[^\w]/g, "");
34
- if (name.length === 0) {
35
- name = "p";
36
- } else if (nameWasNotANumber !== isNaN(name)) {
34
+ const originalName = `${name}`;
35
+ let formattedName = originalName.replaceAll(/[^\w]/g, "");
36
+
37
+ if (formattedName.length === 0) {
38
+ formattedName = "p";
39
+ } else if (nameWasNotANumber !== isNaN(formattedName)) {
37
40
  // name became number due to replace
38
- name = `p${name}`;
41
+ formattedName = `p${formattedName}`;
39
42
  }
40
- return name;
43
+
44
+ const originalFormattedName = formattedName;
45
+ let nameSuffix = 1;
46
+
47
+ while (
48
+ this.formattedNameToOriginalNameMap.has(formattedName) &&
49
+ this.formattedNameToOriginalNameMap.get(formattedName) !== originalName
50
+ ) {
51
+ formattedName = `${originalFormattedName}_${++nameSuffix}`;
52
+ }
53
+
54
+ this.formattedNameToOriginalNameMap.set(formattedName, originalName);
55
+ return formattedName;
41
56
  }
42
57
 
43
58
  // todo: make the structure: name, value, paths