electrodb 3.3.0 → 3.4.1

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
@@ -3564,15 +3564,21 @@ export interface NumberSetAttribute {
3564
3564
  readonly watch?: ReadonlyArray<string> | "*";
3565
3565
  }
3566
3566
 
3567
- type CustomAttributeTypeName<T> = { readonly [CustomAttributeSymbol]: T };
3567
+ interface CustomAttributeTypeName<T> {
3568
+ readonly [CustomAttributeSymbol]: T;
3569
+ }
3570
+
3571
+ interface CustomPrimitiveTypeName<T> {
3572
+ readonly [OpaquePrimitiveSymbol]: T;
3573
+ }
3568
3574
 
3569
- type OpaquePrimitiveTypeName<T extends string | number | boolean> =
3575
+ export type OpaquePrimitiveTypeName<T extends string | number | boolean> =
3570
3576
  T extends string
3571
- ? "string" & { readonly [OpaquePrimitiveSymbol]: T }
3577
+ ? "string" & CustomPrimitiveTypeName<T>
3572
3578
  : T extends number
3573
- ? "number" & { readonly [OpaquePrimitiveSymbol]: T }
3579
+ ? "number" & CustomPrimitiveTypeName<T>
3574
3580
  : T extends boolean
3575
- ? "boolean" & { readonly [OpaquePrimitiveSymbol]: T }
3581
+ ? "boolean" & CustomPrimitiveTypeName<T>
3576
3582
  : never;
3577
3583
 
3578
3584
  type CustomAttribute = {
@@ -3648,6 +3654,8 @@ export type AccessPatternCollection<C extends string> = C | ReadonlyArray<C>;
3648
3654
 
3649
3655
  export type KeyCastOption = "string" | "number";
3650
3656
 
3657
+ export type KeyCasingOption = "upper" | "lower" | "none" | "default";
3658
+
3651
3659
  export interface Schema<A extends string, F extends string, C extends string> {
3652
3660
  readonly model: {
3653
3661
  readonly entity: string;
@@ -3666,14 +3674,14 @@ export interface Schema<A extends string, F extends string, C extends string> {
3666
3674
  readonly collection?: AccessPatternCollection<C>;
3667
3675
  readonly condition?: (composite: Record<string, unknown>) => boolean;
3668
3676
  readonly pk: {
3669
- readonly casing?: "upper" | "lower" | "none" | "default";
3677
+ readonly casing?: KeyCasingOption;
3670
3678
  readonly field: string;
3671
3679
  readonly composite: ReadonlyArray<F>;
3672
3680
  readonly template?: string;
3673
3681
  readonly cast?: KeyCastOption;
3674
3682
  };
3675
3683
  readonly sk?: {
3676
- readonly casing?: "upper" | "lower" | "none" | "default";
3684
+ readonly casing?: KeyCasingOption;
3677
3685
  readonly field: string;
3678
3686
  readonly composite: ReadonlyArray<F>;
3679
3687
  readonly template?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electrodb",
3
- "version": "3.3.0",
3
+ "version": "3.4.1",
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
@@ -3,6 +3,7 @@ const { Schema } = require("./schema");
3
3
  const {
4
4
  AllPages,
5
5
  KeyCasing,
6
+ DefaultKeyCasing,
6
7
  TableIndex,
7
8
  FormatToReturnValues,
8
9
  ReturnValues,
@@ -1369,10 +1370,6 @@ class Entity {
1369
1370
  return this.config.table;
1370
1371
  }
1371
1372
 
1372
- getTableName() {
1373
- return this.config.table;
1374
- }
1375
-
1376
1373
  _chain(state, clauses, clause) {
1377
1374
  let current = {};
1378
1375
  for (let child of clause.children) {
@@ -3519,6 +3516,7 @@ class Entity {
3519
3516
  modelVersion,
3520
3517
  isClustered,
3521
3518
  schema,
3519
+ prefixes = {},
3522
3520
  }) {
3523
3521
  /*
3524
3522
  Collections will prefix the sort key so they can be queried with
@@ -3527,7 +3525,6 @@ class Entity {
3527
3525
  of a customKey AND a collection, the collection is ignored to favor
3528
3526
  the custom key.
3529
3527
  */
3530
-
3531
3528
  let keys = {
3532
3529
  pk: {
3533
3530
  prefix: "",
@@ -3545,6 +3542,28 @@ class Entity {
3545
3542
  },
3546
3543
  };
3547
3544
 
3545
+ let previouslyDefinedPk = null;
3546
+ let previouslyDefinedSk = null;
3547
+ for (const [indexName, definition] of Object.entries(prefixes)) {
3548
+ if (definition.pk.field === tableIndex.pk.field) {
3549
+ previouslyDefinedPk = { indexName, definition: definition.pk };
3550
+ } else if (definition.sk && definition.sk.field === tableIndex.pk.field) {
3551
+ previouslyDefinedPk = { indexName, definition: definition.sk };
3552
+ }
3553
+
3554
+ if (tableIndex.sk) {
3555
+ if (definition.pk.field === tableIndex.sk.field) {
3556
+ previouslyDefinedSk = { indexName, definition: definition.pk };
3557
+ } else if (definition.sk && definition.sk.field === tableIndex.sk.field) {
3558
+ previouslyDefinedSk = { indexName, definition: definition.sk };
3559
+ }
3560
+ }
3561
+
3562
+ if (previouslyDefinedPk && (previouslyDefinedSk || !tableIndex.sk)) {
3563
+ break;
3564
+ }
3565
+ }
3566
+
3548
3567
  let pk = `$${service}`;
3549
3568
  let sk = "";
3550
3569
  let entityKeys = "";
@@ -3636,6 +3655,37 @@ class Entity {
3636
3655
  }
3637
3656
  }
3638
3657
 
3658
+ if (previouslyDefinedPk) {
3659
+ const casingMatch = u.toKeyCasingOption(keys.pk.casing) === u.toKeyCasingOption(previouslyDefinedPk.definition.casing);
3660
+ if (!casingMatch) {
3661
+ throw new e.ElectroError(
3662
+ e.ErrorCodes.IncompatibleKeyCasing,
3663
+ `Partition Key (pk) on Access Pattern '${u.formatIndexNameForDisplay(
3664
+ tableIndex.index,
3665
+ )}' is defined with the casing ${keys.pk.casing}, but the accessPattern '${u.formatIndexNameForDisplay(
3666
+ previouslyDefinedPk.indexName,
3667
+ )}' defines the same index field with the ${previouslyDefinedPk.definition.casing === DefaultKeyCasing ? '(default)' : ''} casing ${previouslyDefinedPk.definition.casing}. Key fields must have the same casing definitions across all indexes they are involved with.`,
3668
+ );
3669
+ }
3670
+
3671
+ keys.pk = previouslyDefinedPk.definition;
3672
+ }
3673
+
3674
+ if (previouslyDefinedSk) {
3675
+ const casingMatch = u.toKeyCasingOption(keys.sk.casing) === u.toKeyCasingOption(previouslyDefinedSk.definition.casing);
3676
+ if (!casingMatch) {
3677
+ throw new e.ElectroError(
3678
+ e.ErrorCodes.IncompatibleKeyCasing,
3679
+ `Sort Key (sk) on Access Pattern '${u.formatIndexNameForDisplay(
3680
+ tableIndex.index,
3681
+ )}' is defined with the casing ${keys.sk.casing}, but the accessPattern '${u.formatIndexNameForDisplay(
3682
+ previouslyDefinedSk.indexName,
3683
+ )}' defines the same index field with the ${previouslyDefinedSk.definition.casing === DefaultKeyCasing ? '(default)' : ''} casing ${previouslyDefinedSk.definition.casing}. Key fields must have the same casing definitions across all indexes they are involved with.`,
3684
+ );
3685
+ }
3686
+ keys.sk = previouslyDefinedSk.definition;
3687
+ }
3688
+
3639
3689
  return keys;
3640
3690
  }
3641
3691
 
@@ -3772,17 +3822,21 @@ class Entity {
3772
3822
  if (!skAttributes.length) {
3773
3823
  skAttributes.push({});
3774
3824
  }
3825
+
3775
3826
  let facets = this.model.facets.byIndex[index];
3827
+
3776
3828
  let prefixes = this.model.prefixes[index];
3777
3829
  if (!prefixes) {
3778
3830
  throw new Error(`Invalid index: ${index}`);
3779
3831
  }
3832
+
3780
3833
  let pk = this._makeKey(
3781
3834
  prefixes.pk,
3782
3835
  facets.pk,
3783
3836
  pkAttributes,
3784
3837
  this.model.facets.labels[index].pk,
3785
3838
  );
3839
+
3786
3840
  let sk = [];
3787
3841
  let fulfilled = false;
3788
3842
  if (this.model.lookup.indexHasSortKeys[index]) {
@@ -3805,6 +3859,7 @@ class Entity {
3805
3859
  }
3806
3860
  }
3807
3861
  }
3862
+
3808
3863
  return {
3809
3864
  pk: pk.key,
3810
3865
  sk,
@@ -3866,8 +3921,9 @@ class Entity {
3866
3921
  for (let i = 0; i < labels.length; i++) {
3867
3922
  const { name, label } = labels[i];
3868
3923
  const attribute = this.model.schema.getAttribute(name);
3924
+
3869
3925
  let value = supplied[name];
3870
- if (supplied[name] === undefined && excludeLabelTail) {
3926
+ if (value === undefined && excludeLabelTail) {
3871
3927
  break;
3872
3928
  }
3873
3929
 
@@ -3880,11 +3936,14 @@ class Entity {
3880
3936
  } else {
3881
3937
  key = `${key}#${label}_`;
3882
3938
  }
3939
+
3883
3940
  // Undefined facet value means we cant build any more of the key
3884
3941
  if (supplied[name] === undefined) {
3885
3942
  break;
3886
3943
  }
3944
+
3887
3945
  foundCount++;
3946
+
3888
3947
  key = `${key}${value}`;
3889
3948
  }
3890
3949
 
@@ -4248,6 +4307,7 @@ class Entity {
4248
4307
  pk: false,
4249
4308
  sk: false,
4250
4309
  };
4310
+
4251
4311
  const pkCasing =
4252
4312
  KeyCasing[index.pk.casing] === undefined
4253
4313
  ? KeyCasing.default
@@ -4462,23 +4522,6 @@ class Entity {
4462
4522
  }' as the field name for both the PK and SK. Fields used for indexes need to be unique to avoid conflicts.`,
4463
4523
  );
4464
4524
  } else if (seenIndexFields[sk.field] !== undefined) {
4465
- const isAlsoDefinedAsPK = seenIndexFields[sk.field].find(
4466
- (field) => field.type === "pk",
4467
- );
4468
-
4469
- if (isAlsoDefinedAsPK && !sk.isCustom) {
4470
- throw new e.ElectroError(
4471
- e.ErrorCodes.InconsistentIndexDefinition,
4472
- `The Sort Key (sk) on Access Pattern '${u.formatIndexNameForDisplay(
4473
- accessPattern,
4474
- )}' references the field '${
4475
- pk.field
4476
- }' which is already referenced by the Access Pattern(s) '${u.formatIndexNameForDisplay(
4477
- isAlsoDefinedAsPK.accessPattern,
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'.`,
4479
- );
4480
- }
4481
-
4482
4525
  const definition = Object.values(facets.byField[sk.field]).find(
4483
4526
  (definition) => definition.index !== indexName,
4484
4527
  );
@@ -4640,6 +4683,7 @@ class Entity {
4640
4683
  modelVersion,
4641
4684
  isClustered: clusteredIndexes.has(accessPattern),
4642
4685
  schema,
4686
+ prefixes,
4643
4687
  });
4644
4688
  }
4645
4689
  return prefixes;
package/src/errors.js CHANGED
@@ -127,6 +127,12 @@ const ErrorCodes = {
127
127
  name: "InvalidIndexCompositeWithAttributeName",
128
128
  sym: ErrorCode,
129
129
  },
130
+ IncompatibleKeyCasing: {
131
+ code: 1020,
132
+ section: "incompatible-key-casing",
133
+ name: "IncompatibleKeyCasing",
134
+ sym: ErrorCode,
135
+ },
130
136
  InvalidListenerProvided: {
131
137
  code: 1020,
132
138
  section: "invalid-listener-provided",
package/src/types.js CHANGED
@@ -295,6 +295,8 @@ const KeyCasing = {
295
295
  default: "default",
296
296
  };
297
297
 
298
+ const DefaultKeyCasing = KeyCasing.lower;
299
+
298
300
  const EventSubscriptionTypes = ["query", "results"];
299
301
 
300
302
  const TerminalOperation = {
@@ -378,4 +380,5 @@ module.exports = {
378
380
  TransactionMethods,
379
381
  UpsertOperations,
380
382
  BatchWriteTypes,
383
+ DefaultKeyCasing,
381
384
  };
package/src/util.js CHANGED
@@ -1,5 +1,4 @@
1
1
  const t = require("./types");
2
- const e = require("./errors");
3
2
  const v = require("./validations");
4
3
 
5
4
  function parseJSONPath(path = "") {
@@ -105,8 +104,24 @@ function formatStringCasing(str, casing, defaultCase) {
105
104
  }
106
105
  }
107
106
 
107
+ function toKeyCasingOption(casing) {
108
+ switch(casing) {
109
+ case t.KeyCasing.upper:
110
+ return t.KeyCasing.upper;
111
+ case t.KeyCasing.none:
112
+ return t.KeyCasing.none;
113
+ case t.KeyCasing.lower:
114
+ return t.KeyCasing.lower;
115
+ case t.KeyCasing.default:
116
+ case undefined:
117
+ return t.DefaultKeyCasing;
118
+ default:
119
+ throw new Error(`Unknown casing option: ${casing}`);
120
+ }
121
+ }
122
+
108
123
  function formatKeyCasing(str, casing) {
109
- return formatStringCasing(str, casing, t.KeyCasing.lower);
124
+ return formatStringCasing(str, casing, t.DefaultKeyCasing);
110
125
  }
111
126
 
112
127
  function formatAttributeCasing(str, casing) {
@@ -268,6 +283,7 @@ module.exports = {
268
283
  getModelVersion,
269
284
  formatKeyCasing,
270
285
  cursorFormatter,
286
+ toKeyCasingOption,
271
287
  genericizeJSONPath,
272
288
  commaSeparatedString,
273
289
  formatAttributeCasing,