electrodb 3.5.3 → 3.6.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/src/schema.js CHANGED
@@ -1215,7 +1215,7 @@ class Schema {
1215
1215
  constructor(
1216
1216
  properties = {},
1217
1217
  facets = {},
1218
- { traverser = new AttributeTraverser(), getClient, parent, isRoot } = {},
1218
+ { traverser = new AttributeTraverser(), getClient, parent, isRoot, identifiers } = {},
1219
1219
  ) {
1220
1220
  this._validateProperties(properties, parent);
1221
1221
  let schema = Schema.normalizeAttributes(properties, facets, {
@@ -1223,6 +1223,7 @@ class Schema {
1223
1223
  getClient,
1224
1224
  parent,
1225
1225
  isRoot,
1226
+ identifiers,
1226
1227
  });
1227
1228
  this.getClient = getClient;
1228
1229
  this.attributes = schema.attributes;
@@ -1242,7 +1243,7 @@ class Schema {
1242
1243
  static normalizeAttributes(
1243
1244
  attributes = {},
1244
1245
  facets = {},
1245
- { traverser, getClient, parent, isRoot } = {},
1246
+ { traverser, getClient, parent, isRoot, identifiers = {} } = {},
1246
1247
  ) {
1247
1248
  const attributeHasParent = !!parent;
1248
1249
  let invalidProperties = [];
@@ -1540,11 +1541,33 @@ class Schema {
1540
1541
  );
1541
1542
  }
1542
1543
 
1544
+ let missingProjectionAttributes = Array.isArray(facets.projections)
1545
+ ? facets.projections.filter(({ name }) => !normalized[name] && !identifiers[name])
1546
+ : [];
1547
+
1548
+ if (missingProjectionAttributes.length > 0) {
1549
+ const byAccessPattern = facets.projections.reduce((groups, { name, accessPattern }) => {
1550
+ groups[accessPattern] = groups[accessPattern] || [];
1551
+ groups[accessPattern].push(name);
1552
+ return groups;
1553
+ }, {});
1554
+
1555
+ const invalidDefinitionsByAccessPattern = Object.entries(byAccessPattern).map(([accessPattern, attributes]) => {
1556
+ return `${accessPattern}: ${u.commaSeparatedString(attributes)}`
1557
+ });
1558
+
1559
+ throw new e.ElectroError(
1560
+ e.ErrorCodes.InvalidProjectionDefinition,
1561
+ `Unknown index projection attributes provided. The following access patterns were defined with unknown attributes: ${u.commaSeparatedString(invalidDefinitionsByAccessPattern, '', '')}`,
1562
+ );
1563
+ }
1564
+
1543
1565
  let missingFacetAttributes = Array.isArray(facets.attributes)
1544
1566
  ? facets.attributes
1545
1567
  .filter(({ name }) => !normalized[name])
1546
1568
  .map((facet) => `"${facet.type}: ${facet.name}"`)
1547
1569
  : [];
1570
+
1548
1571
  if (missingFacetAttributes.length > 0) {
1549
1572
  throw new e.ElectroError(
1550
1573
  e.ErrorCodes.InvalidKeyCompositeAttributeTemplate,
package/src/service.js CHANGED
@@ -13,7 +13,7 @@ const {
13
13
  ElectroInstanceTypes,
14
14
  ModelVersions,
15
15
  IndexTypes,
16
- DataOptions,
16
+ DataOptions, IndexProjectionOptions,
17
17
  } = require("./types");
18
18
  const { FilterFactory } = require("./filters");
19
19
  const { FilterOperations } = require("./operations");
@@ -369,6 +369,7 @@ class Service {
369
369
  record,
370
370
  entities: this.entities,
371
371
  allowMatchOnKeys: config.ignoreOwnership,
372
+ config,
372
373
  });
373
374
 
374
375
  if (!entityAlias) {
@@ -460,7 +461,7 @@ class Service {
460
461
  const expression = identifiers.expression || "";
461
462
 
462
463
  let options = {
463
- // expressions, // DynamoDB doesnt return what I expect it would when provided with these entity filters
464
+ // expressions, // DynamoDB doesn't return what I expect it would when provided with these entity filters
464
465
  parse: (options, data) => {
465
466
  if (options.data === DataOptions.raw) {
466
467
  return data;
@@ -497,29 +498,21 @@ class Service {
497
498
  hydrator: undefined,
498
499
  _isCollectionQuery: false,
499
500
  ignoreOwnership: config._providedIgnoreOwnership,
501
+ attributes: config._providedAttributes,
500
502
  });
501
503
  }
502
504
 
503
- // let itemLookup = [];
504
505
  let entityItemRefs = {};
505
- // let entityResultRefs = {};
506
506
  for (let i = 0; i < items.length; i++) {
507
507
  const item = items[i];
508
508
  for (let entityName in entities) {
509
509
  entityItemRefs[entityName] = entityItemRefs[entityName] || [];
510
510
  const entity = entities[entityName];
511
- // if (entity.ownsKeys({ keys: item })) {
512
- if (entity.ownsKeys(item)) {
513
- // const entityItemRefsIndex =
511
+ if (entity.is(item, config)) {
514
512
  entityItemRefs[entityName].push({
515
513
  item,
516
514
  itemSlot: i,
517
515
  });
518
- // itemLookup[i] = {
519
- // entityName,
520
- // entityItemRefsIndex,
521
- // originalItem: item,
522
- // }
523
516
  }
524
517
  }
525
518
  }
@@ -536,6 +529,7 @@ class Service {
536
529
  hydrator: undefined,
537
530
  _isCollectionQuery: false,
538
531
  ignoreOwnership: config._providedIgnoreOwnership,
532
+ attributes: config._providedAttributes,
539
533
  });
540
534
  unprocessed = unprocessed.concat(results.unprocessed);
541
535
  if (results.data.length !== itemRefs.length) {
@@ -575,6 +569,12 @@ class Service {
575
569
  };
576
570
  }
577
571
 
572
+ _validateIndexProjectionsMatch(definition = {}, providedIndex = {}) {
573
+ const definitionProjection = definition.projection;
574
+ const providedProjection = providedIndex.projection;
575
+ return v.isMatchingProjection(providedIndex.projection, definition.projection)
576
+ }
577
+
578
578
  _validateCollectionDefinition(definition = {}, providedIndex = {}) {
579
579
  let isCustomMatchPK = definition.pk.isCustom === providedIndex.pk.isCustom;
580
580
  let isCustomMatchSK =
@@ -594,6 +594,11 @@ class Service {
594
594
  providedIndex,
595
595
  );
596
596
 
597
+ const matchingProjection = v.isMatchingProjection(
598
+ providedIndex.projection,
599
+ definition.projection
600
+ )
601
+
597
602
  for (
598
603
  let i = 0;
599
604
  i < Math.max(definition.pk.labels.length, providedIndex.pk.labels.length);
@@ -699,6 +704,12 @@ class Service {
699
704
  );
700
705
  }
701
706
 
707
+ if (!matchingProjection) {
708
+ collectionDifferences.push(
709
+ `The provided projection definition ${u.commaSeparatedString(providedIndex.projection ?? '<undefined>')} does not match the established projection definition ${u.commaSeparatedString(definition.projection)} on index ${providedIndexName}. Index projection definitions must match across all entities participating in a collection`
710
+ );
711
+ }
712
+
702
713
  if (!matchingKeyCasing.sk) {
703
714
  const definedSk = definition.sk || {};
704
715
  const providedSk = providedIndex.sk || {};
package/src/types.js CHANGED
@@ -335,6 +335,18 @@ const CastKeyOptions = {
335
335
  number: "number",
336
336
  };
337
337
 
338
+ const IndexProjectionOptions = {
339
+ all: 'all',
340
+ keys_only: 'keys_only',
341
+ };
342
+
343
+ const EntityIdentifiers = {
344
+ entity: "__edb_e__",
345
+ version: "__edb_v__",
346
+ }
347
+
348
+ const EntityIdentifierFields = ["__edb_e__", "__edb_v__"];
349
+
338
350
  module.exports = {
339
351
  Pager,
340
352
  KeyTypes,
@@ -381,4 +393,7 @@ module.exports = {
381
393
  UpsertOperations,
382
394
  BatchWriteTypes,
383
395
  DefaultKeyCasing,
396
+ IndexProjectionOptions,
397
+ EntityIdentifiers,
398
+ EntityIdentifierFields,
384
399
  };
package/src/util.js CHANGED
@@ -79,9 +79,19 @@ function batchItems(arr = [], size) {
79
79
  }
80
80
 
81
81
  function commaSeparatedString(array = [], prefix = '"', postfix = '"') {
82
+ if (typeof array === 'string') {
83
+ array = [array];
84
+ }
82
85
  return array.map((value) => `${prefix}${value}${postfix}`).join(", ");
83
86
  }
84
87
 
88
+ function toDisplayString(value) {
89
+ if (value === undefined) {
90
+ return "<undefined>";
91
+ }
92
+ return JSON.stringify(value);
93
+ }
94
+
85
95
  function formatStringCasing(str, casing, defaultCase) {
86
96
  if (typeof str !== "string") {
87
97
  return str;
@@ -275,6 +285,7 @@ module.exports = {
275
285
  removeFixings,
276
286
  parseJSONPath,
277
287
  shiftSortOrder,
288
+ toDisplayString,
278
289
  getFirstDefined,
279
290
  getInstanceType,
280
291
  getModelVersion,
@@ -1,5 +1,5 @@
1
1
  const e = require("./errors");
2
- const { KeyCasing } = require("./types");
2
+ const { KeyCasing, IndexProjectionOptions } = require("./types");
3
3
 
4
4
  const Validator = require("jsonschema").Validator;
5
5
  Validator.prototype.customFormats.isFunction = function (input) {
@@ -179,6 +179,12 @@ const Index = {
179
179
  required: false,
180
180
  format: "isFunction",
181
181
  },
182
+ projection: {
183
+ type: ["array", "string"],
184
+ items: {
185
+ type: "string",
186
+ }
187
+ },
182
188
  },
183
189
  };
184
190
 
@@ -398,10 +404,25 @@ function isMatchingCasing(casing1, casing2) {
398
404
  }
399
405
  }
400
406
 
407
+ function isValueOrUndefined(received, expected) {
408
+ return expected === received || received === undefined;
409
+ }
410
+
411
+ function isMatchingProjection(received, expected) {
412
+ if (isStringHasLength(received) && isStringHasLength(expected)) {
413
+ return received === expected;
414
+ } else if (Array.isArray(received) && Array.isArray(expected)) {
415
+ return received.length === expected.length && expected.every((attribute) => received.includes(attribute));
416
+ } else {
417
+ return isValueOrUndefined(received, IndexProjectionOptions.all) && isValueOrUndefined(expected, IndexProjectionOptions.all)
418
+ }
419
+ }
420
+
401
421
  module.exports = {
402
422
  testModel,
403
423
  isFunction,
404
424
  stringArrayMatch,
425
+ isMatchingProjection,
405
426
  isMatchingCasing,
406
427
  isArrayHasLength,
407
428
  isStringHasLength,