electrodb 2.7.2 → 2.8.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
@@ -1780,6 +1780,8 @@ export interface IndexWithSortKey {
1780
1780
 
1781
1781
  export type AccessPatternCollection<C extends string> = C | ReadonlyArray<C>;
1782
1782
 
1783
+ export type KeyCastOption = 'string' | 'number'
1784
+
1783
1785
  export interface Schema<A extends string, F extends string, C extends string> {
1784
1786
  readonly model: {
1785
1787
  readonly entity: string;
@@ -1800,12 +1802,14 @@ export interface Schema<A extends string, F extends string, C extends string> {
1800
1802
  readonly field: string;
1801
1803
  readonly composite: ReadonlyArray<F>;
1802
1804
  readonly template?: string;
1805
+ readonly cast?: KeyCastOption;
1803
1806
  }
1804
1807
  readonly sk?: {
1805
1808
  readonly casing?: "upper" | "lower" | "none" | "default";
1806
1809
  readonly field: string;
1807
1810
  readonly composite: ReadonlyArray<F>;
1808
1811
  readonly template?: string;
1812
+ readonly cast?: KeyCastOption;
1809
1813
  }
1810
1814
  }
1811
1815
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electrodb",
3
- "version": "2.7.2",
3
+ "version": "2.8.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
@@ -26,6 +26,7 @@ const {
26
26
  PartialComparisons,
27
27
  MethodTypeTranslation,
28
28
  TransactionCommitSymbol,
29
+ CastKeyOptions,
29
30
  } = require("./types");
30
31
  const { FilterFactory } = require("./filters");
31
32
  const { FilterOperations } = require("./operations");
@@ -1279,7 +1280,7 @@ class Entity {
1279
1280
  }
1280
1281
  indexParts = { ...indexParts, ...tableIndexParts };
1281
1282
  }
1282
- let noPartsFound = Object.keys(indexParts).length === 0;
1283
+ let noPartsFound = Object.keys(indexParts).length === 0 && this.model.facets.byIndex[tableIndex].all.length > 0;
1283
1284
  let partsAreIncomplete = this.model.facets.byIndex[tableIndex].all.find(facet => indexParts[facet.name] === undefined);
1284
1285
  if (noPartsFound || partsAreIncomplete) {
1285
1286
  // In this case no suitable record could be found be the deconstructed pager.
@@ -2592,7 +2593,8 @@ class Entity {
2592
2593
  version = "1",
2593
2594
  tableIndex,
2594
2595
  modelVersion,
2595
- isClustered
2596
+ isClustered,
2597
+ schema
2596
2598
  }) {
2597
2599
  /*
2598
2600
  Collections will prefix the sort key so they can be queried with
@@ -2608,12 +2610,14 @@ class Entity {
2608
2610
  field: tableIndex.pk.field,
2609
2611
  casing: tableIndex.pk.casing,
2610
2612
  isCustom: tableIndex.customFacets.pk,
2613
+ cast: tableIndex.pk.cast,
2611
2614
  },
2612
2615
  sk: {
2613
2616
  prefix: "",
2614
2617
  casing: tableIndex.sk.casing,
2615
2618
  isCustom: tableIndex.customFacets.sk,
2616
2619
  field: tableIndex.sk ? tableIndex.sk.field : undefined,
2620
+ cast: tableIndex.sk ? tableIndex.sk.cast : undefined,
2617
2621
  }
2618
2622
  };
2619
2623
 
@@ -2652,7 +2656,7 @@ class Entity {
2652
2656
  }
2653
2657
  }
2654
2658
 
2655
- // If keys arent custom, set the prefixes
2659
+ // If keys are not custom, set the prefixes
2656
2660
  if (!keys.pk.isCustom) {
2657
2661
  keys.pk.prefix = u.formatKeyCasing(pk, tableIndex.pk.casing);
2658
2662
  }
@@ -2662,6 +2666,40 @@ class Entity {
2662
2666
  keys.sk.postfix = u.formatKeyCasing(postfix, tableIndex.sk.casing);
2663
2667
  }
2664
2668
 
2669
+ const castKeys = tableIndex.hasSk
2670
+ ? [tableIndex.pk, tableIndex.sk]
2671
+ : [tableIndex.pk];
2672
+
2673
+ for (const castKey of castKeys) {
2674
+ if (castKey.cast === CastKeyOptions.string) {
2675
+ keys[castKey.type].cast = CastKeyOptions.string;
2676
+ } else if (
2677
+ // custom keys with only one facet and no labels are numeric by default
2678
+ castKey.cast === undefined &&
2679
+ castKey.isCustom &&
2680
+ castKey.facets.length === 1 &&
2681
+ castKey.facetLabels.every(({label}) => !label) &&
2682
+ schema.attributes[castKey.facets[0]] &&
2683
+ schema.attributes[castKey.facets[0]].type === 'number'
2684
+ ) {
2685
+ keys[castKey.type].cast = CastKeyOptions.number;
2686
+ } else if (
2687
+ castKey.cast === CastKeyOptions.number &&
2688
+ castKey.facets.length === 1 &&
2689
+ schema.attributes[castKey.facets[0]] &&
2690
+ ['number', 'string', 'boolean'].includes(schema.attributes[castKey.facets[0]].type)
2691
+ ) {
2692
+ keys[castKey.type].cast = CastKeyOptions.number;
2693
+ } else if (
2694
+ castKey.cast === CastKeyOptions.number &&
2695
+ castKey.facets.length > 1
2696
+ ) {
2697
+ throw new e.ElectroError(e.ErrorCodes.InvalidModel, `Invalid "cast" option provided for ${castKey.type} definition on index "${u.formatIndexNameForDisplay(tableIndex.index)}". Keys can only be cast to 'number' if they are a composite of one numeric attribute.`);
2698
+ } else {
2699
+ keys[castKey.type].cast = CastKeyOptions.string;
2700
+ }
2701
+ }
2702
+
2665
2703
  return keys;
2666
2704
  }
2667
2705
 
@@ -2743,7 +2781,12 @@ class Entity {
2743
2781
  throw new Error(`Invalid index: ${index}`);
2744
2782
  }
2745
2783
  // let partitionKey = this._makeKey(prefixes.pk, facets.pk, pkFacets, this.model.facets.labels[index].pk, { excludeLabelTail: true });
2746
- let partitionKey = this._makeKey(prefixes.pk, facets.pk, pkFacets, this.model.facets.labels[index].pk);
2784
+ let partitionKey = this._makeKey(
2785
+ prefixes.pk,
2786
+ facets.pk,
2787
+ pkFacets,
2788
+ this.model.facets.labels[index].pk,
2789
+ );
2747
2790
  let pk = partitionKey.key;
2748
2791
  let sk = [];
2749
2792
  let fulfilled = false;
@@ -2822,21 +2865,45 @@ class Entity {
2822
2865
  };
2823
2866
  }
2824
2867
 
2825
- _isNumericKey(isCustom, facets = [], labels = []) {
2826
- let attribute = this.model.schema.attributes[facets[0]];
2827
- let isSingleComposite = facets.length === 1;
2828
- let hasNoLabels = isCustom && labels.every(({label}) => !label);
2829
- let facetIsNonStringPrimitive = attribute && attribute.type === "number";
2830
- return isCustom && isSingleComposite && hasNoLabels && facetIsNonStringPrimitive
2868
+ _formatNumericCastKey(attributeName, key) {
2869
+ const fulfilled = key !== undefined;
2870
+ if (!fulfilled) {
2871
+ return {
2872
+ fulfilled,
2873
+ key,
2874
+ }
2875
+ }
2876
+ if (typeof key === 'number') {
2877
+ return {
2878
+ fulfilled,
2879
+ key,
2880
+ }
2881
+ }
2882
+ if (typeof key === 'string') {
2883
+ const parsed = parseInt(key);
2884
+ if (!isNaN(parsed)) {
2885
+ return {
2886
+ fulfilled,
2887
+ key: parsed,
2888
+ }
2889
+ }
2890
+ }
2891
+
2892
+ if (typeof key === 'boolean') {
2893
+ return {
2894
+ fulfilled,
2895
+ key: key === true ? 1 : 0,
2896
+ }
2897
+ }
2898
+
2899
+ throw new e.ElectroAttributeValidationError(attributeName, `Invalid key value provided, could not cast composite attribute ${attributeName} to number for index`);
2831
2900
  }
2832
2901
 
2902
+
2833
2903
  /* istanbul ignore next */
2834
- _makeKey({prefix, isCustom, casing, postfix} = {}, facets = [], supplied = {}, labels = [], {excludeLabelTail, excludePostfix, transform = (val) => val} = {}) {
2835
- if (this._isNumericKey(isCustom, facets, labels)) {
2836
- return {
2837
- fulfilled: supplied[facets[0]] !== undefined,
2838
- key: supplied[facets[0]],
2839
- };
2904
+ _makeKey({prefix, isCustom, casing, postfix, cast} = {}, facets = [], supplied = {}, labels = [], {excludeLabelTail, excludePostfix, transform = (val) => val} = {}) {
2905
+ if (cast === CastKeyOptions.number) {
2906
+ return this._formatNumericCastKey(facets[0], supplied[facets[0]]);
2840
2907
  }
2841
2908
 
2842
2909
  let key = prefix;
@@ -3153,7 +3220,6 @@ class Entity {
3153
3220
  };
3154
3221
  let seenIndexes = {};
3155
3222
  let seenIndexFields = {};
3156
-
3157
3223
  let accessPatterns = Object.keys(indexes);
3158
3224
 
3159
3225
  for (let i in accessPatterns) {
@@ -3202,6 +3268,7 @@ class Entity {
3202
3268
  index: indexName,
3203
3269
  casing: pkCasing,
3204
3270
  type: KeyTypes.pk,
3271
+ cast: index.pk.cast,
3205
3272
  field: index.pk.field || "",
3206
3273
  facets: parsedPKAttributes.attributes,
3207
3274
  isCustom: parsedPKAttributes.isCustom,
@@ -3219,6 +3286,7 @@ class Entity {
3219
3286
  index: indexName,
3220
3287
  casing: skCasing,
3221
3288
  type: KeyTypes.sk,
3289
+ cast: index.sk.cast,
3222
3290
  field: index.sk.field || "",
3223
3291
  facets: parsedSKAttributes.attributes,
3224
3292
  isCustom: parsedSKAttributes.isCustom,
@@ -3410,7 +3478,7 @@ class Entity {
3410
3478
  return normalized;
3411
3479
  }
3412
3480
 
3413
- _normalizeKeyFixings({service, entity, version, indexes, modelVersion, clusteredIndexes}) {
3481
+ _normalizeKeyFixings({service, entity, version, indexes, modelVersion, clusteredIndexes, schema}) {
3414
3482
  let prefixes = {};
3415
3483
  for (let accessPattern of Object.keys(indexes)) {
3416
3484
  let tableIndex = indexes[accessPattern];
@@ -3421,6 +3489,7 @@ class Entity {
3421
3489
  tableIndex,
3422
3490
  modelVersion,
3423
3491
  isClustered: clusteredIndexes.has(accessPattern),
3492
+ schema,
3424
3493
  });
3425
3494
  }
3426
3495
  return prefixes;
@@ -3526,7 +3595,6 @@ class Entity {
3526
3595
 
3527
3596
  _parseModel(model, config = {}) {
3528
3597
  /** start beta/v1 condition **/
3529
- const {client} = config;
3530
3598
  let modelVersion = u.getModelVersion(model);
3531
3599
  let service, entity, version, table, name;
3532
3600
  switch(modelVersion) {
@@ -3566,20 +3634,23 @@ class Entity {
3566
3634
  indexAccessPattern,
3567
3635
  indexHasSubCollections,
3568
3636
  } = this._normalizeIndexes(model.indexes);
3569
- let schema = new Schema(model.attributes, facets, {client, isRoot: true});
3637
+ let schema = new Schema(model.attributes, facets, {
3638
+ getClient: () => this.client,
3639
+ isRoot: true,
3640
+ });
3570
3641
  let filters = this._normalizeFilters(model.filters);
3571
3642
  // todo: consider a rename
3572
- let prefixes = this._normalizeKeyFixings({service, entity, version, indexes, modelVersion, clusteredIndexes});
3643
+ let prefixes = this._normalizeKeyFixings({service, entity, version, indexes, modelVersion, clusteredIndexes, schema});
3573
3644
 
3574
3645
  // apply model defined labels
3575
3646
  let schemaDefinedLabels = schema.getLabels();
3647
+ const deconstructors = {};
3576
3648
  facets.labels = this._mergeKeyDefinitions(facets.labels, schemaDefinedLabels);
3577
3649
  for (let indexName of Object.keys(facets.labels)) {
3578
- indexes[indexAccessPattern.fromIndexToAccessPattern[indexName]].pk.labels = facets.labels[indexName].pk;
3579
- indexes[indexAccessPattern.fromIndexToAccessPattern[indexName]].sk.labels = facets.labels[indexName].sk;
3580
- }
3581
- const deconstructors = {};
3582
- for (const indexName of Object.keys(facets.labels)) {
3650
+ const accessPattern = indexAccessPattern.fromIndexToAccessPattern[indexName];
3651
+ indexes[accessPattern].pk.labels = facets.labels[indexName].pk;
3652
+ indexes[accessPattern].sk.labels = facets.labels[indexName].sk;
3653
+
3583
3654
  const keyTypes = prefixes[indexName] || {};
3584
3655
  deconstructors[indexName] = {};
3585
3656
  for (const keyType in keyTypes) {
package/src/errors.js CHANGED
@@ -148,7 +148,7 @@ const ErrorCodes = {
148
148
  InconsistentIndexDefinition: {
149
149
  code: 1022,
150
150
  section: "inconsistent-index-definition",
151
- name: "InvalidClientProvided",
151
+ name: "Inconsistent Index Definition",
152
152
  sym: ErrorCode,
153
153
  },
154
154
  MissingAttribute: {
package/src/schema.js CHANGED
@@ -127,7 +127,7 @@ class Attribute {
127
127
  this.parent = { parentType: this.type, parentPath: this.path };
128
128
  this.get = this._makeGet(definition.get);
129
129
  this.set = this._makeSet(definition.set);
130
- this.client = definition.client;
130
+ this.getClient = definition.getClient;
131
131
  }
132
132
 
133
133
  static buildChildAttributes(type, definition, parent) {
@@ -145,25 +145,25 @@ class Attribute {
145
145
  }
146
146
 
147
147
  static buildChildListItems(definition, parent) {
148
- const {items, client} = definition;
148
+ const {items, getClient} = definition;
149
149
  const prop = {...items, ...parent};
150
150
  // The use of "*" is to ensure the child's name is "*" when added to the traverser and searching for the children of a list
151
- return Schema.normalizeAttributes({ '*': prop }, {}, {client, traverser: parent.traverser, parent}).attributes["*"];
151
+ return Schema.normalizeAttributes({ '*': prop }, {}, {getClient, traverser: parent.traverser, parent}).attributes["*"];
152
152
  }
153
153
 
154
154
  static buildChildSetItems(definition, parent) {
155
- const {items, client} = definition;
155
+ const {items, getClient} = definition;
156
156
 
157
157
  const allowedTypes = [AttributeTypes.string, AttributeTypes.boolean, AttributeTypes.number, AttributeTypes.enum];
158
158
  if (!Array.isArray(items) && !allowedTypes.includes(items)) {
159
159
  throw new e.ElectroError(e.ErrorCodes.InvalidAttributeDefinition, `Invalid "items" definition for Set attribute: "${definition.path}". Acceptable item types include ${u.commaSeparatedString(allowedTypes)}`);
160
160
  }
161
161
  const prop = {type: items, ...parent};
162
- return Schema.normalizeAttributes({ prop }, {}, {client, traverser: parent.traverser, parent}).attributes.prop;
162
+ return Schema.normalizeAttributes({ prop }, {}, {getClient, traverser: parent.traverser, parent}).attributes.prop;
163
163
  }
164
164
 
165
165
  static buildChildMapProperties(definition, parent) {
166
- const {properties, client} = definition;
166
+ const {properties, getClient} = definition;
167
167
  if (!properties || typeof properties !== "object") {
168
168
  throw new e.ElectroError(e.ErrorCodes.InvalidAttributeDefinition, `Invalid "properties" definition for Map attribute: "${definition.path}". The "properties" definition must describe the attributes that the Map will accept`);
169
169
  }
@@ -171,7 +171,7 @@ class Attribute {
171
171
  for (let name of Object.keys(properties)) {
172
172
  attributes[name] = {...properties[name], ...parent};
173
173
  }
174
- return Schema.normalizeAttributes(attributes, {}, {client, traverser: parent.traverser, parent});
174
+ return Schema.normalizeAttributes(attributes, {}, {getClient, traverser: parent.traverser, parent});
175
175
  }
176
176
 
177
177
  static buildPath(name, type, parentPath) {
@@ -886,11 +886,12 @@ class SetAttribute extends Attribute {
886
886
  }
887
887
 
888
888
  _createDDBSet(value) {
889
- if (this.client && typeof this.client.createSet === "function") {
889
+ const client = this.getClient();
890
+ if (client && typeof client.createSet === "function") {
890
891
  value = Array.isArray(value)
891
892
  ? Array.from(new Set(value))
892
893
  : value;
893
- return this.client.createSet(value, { validate: true });
894
+ return client.createSet(value, { validate: true });
894
895
  } else {
895
896
  return new DynamoDBSet(value, this.items.type);
896
897
  }
@@ -1005,10 +1006,10 @@ class SetAttribute extends Attribute {
1005
1006
  }
1006
1007
 
1007
1008
  class Schema {
1008
- constructor(properties = {}, facets = {}, {traverser = new AttributeTraverser(), client, parent, isRoot} = {}) {
1009
+ constructor(properties = {}, facets = {}, {traverser = new AttributeTraverser(), getClient, parent, isRoot} = {}) {
1009
1010
  this._validateProperties(properties, parent);
1010
- let schema = Schema.normalizeAttributes(properties, facets, {traverser, client, parent, isRoot});
1011
- this.client = client;
1011
+ let schema = Schema.normalizeAttributes(properties, facets, {traverser, getClient, parent, isRoot});
1012
+ this.getClient = getClient;
1012
1013
  this.attributes = schema.attributes;
1013
1014
  this.enums = schema.enums;
1014
1015
  this.translationForTable = schema.translationForTable;
@@ -1021,7 +1022,7 @@ class Schema {
1021
1022
  this.isRoot = !!isRoot;
1022
1023
  }
1023
1024
 
1024
- static normalizeAttributes(attributes = {}, facets = {}, {traverser, client, parent, isRoot} = {}) {
1025
+ static normalizeAttributes(attributes = {}, facets = {}, {traverser, getClient, parent, isRoot} = {}) {
1025
1026
  const attributeHasParent = !!parent;
1026
1027
  let invalidProperties = [];
1027
1028
  let normalized = {};
@@ -1114,12 +1115,12 @@ class Schema {
1114
1115
  let definition = {
1115
1116
  name,
1116
1117
  field,
1117
- client,
1118
+ getClient,
1118
1119
  casing,
1119
1120
  prefix,
1120
1121
  postfix,
1121
1122
  traverser,
1122
- isKeyField,
1123
+ isKeyField: isKeyField || isKey,
1123
1124
  isRoot: !!isRoot,
1124
1125
  label: attribute.label,
1125
1126
  required: !!attribute.required,
package/src/types.js CHANGED
@@ -312,6 +312,11 @@ const DynamoDBAttributeTypes = Object.entries({
312
312
  return obj;
313
313
  }, {});
314
314
 
315
+ const CastKeyOptions = {
316
+ string: 'string',
317
+ number: 'number',
318
+ }
319
+
315
320
  module.exports = {
316
321
  Pager,
317
322
  KeyTypes,
@@ -331,6 +336,7 @@ module.exports = {
331
336
  ItemOperations,
332
337
  AttributeTypes,
333
338
  EntityVersions,
339
+ CastKeyOptions,
334
340
  ServiceVersions,
335
341
  ExpressionTypes,
336
342
  ElectroInstance,
@@ -113,6 +113,11 @@ const Index = {
113
113
  type: "string",
114
114
  enum: ["upper", "lower", "none", "default"],
115
115
  required: false,
116
+ },
117
+ cast: {
118
+ type: "string",
119
+ enum: ["string", "number"],
120
+ required: false,
116
121
  }
117
122
  },
118
123
  },
@@ -146,6 +151,11 @@ const Index = {
146
151
  type: "string",
147
152
  enum: ["upper", "lower", "none", "default"],
148
153
  required: false,
154
+ },
155
+ cast: {
156
+ type: "string",
157
+ enum: ["string", "number"],
158
+ required: false,
149
159
  }
150
160
  },
151
161
  },
package/output DELETED
@@ -1,168 +0,0 @@
1
- "target": "top",
2
- "target": "byOrganization",
3
- "target": "byCategory",
4
- "target": "top",
5
- "target": "byOrganization",
6
- "target": "byCategory",
7
- "target": "top",
8
- "target": "byOrganization",
9
- "target": "byCategory",
10
- "target": "top",
11
- "target": "byOrganization",
12
- "target": "byCategory",
13
- "target": "top",
14
- "target": "byOrganization",
15
- "target": "byCategory",
16
- "target": "top",
17
- "target": "byOrganization",
18
- "target": "byCategory",
19
- "target": "top",
20
- "target": "byOrganization",
21
- "target": "byCategory",
22
- "target": "top",
23
- "target": "byOrganization",
24
- "target": "byCategory",
25
- "target": "top",
26
- "target": "byOrganization",
27
- "target": "byCategory",
28
- "target": "top",
29
- "target": "byOrganization",
30
- "target": "byCategory",
31
- "target": "top",
32
- "target": "byOrganization",
33
- "target": "byCategory",
34
- "target": "top",
35
- "target": "byOrganization",
36
- "target": "byCategory",
37
- "target": "top",
38
- "target": "byOrganization",
39
- "target": "byCategory",
40
- "target": "top",
41
- "target": "byOrganization",
42
- "target": "byCategory",
43
- "target": "top",
44
- "target": "byOrganization",
45
- "target": "byCategory",
46
- "target": "top",
47
- "target": "byOrganization",
48
- "target": "byCategory",
49
- "target": "top",
50
- "target": "byOrganization",
51
- "target": "byCategory",
52
- "target": "top",
53
- "target": "byOrganization",
54
- "target": "byCategory",
55
- "target": "top",
56
- "target": "byOrganization",
57
- "target": "byCategory",
58
- "target": "top",
59
- "target": "byOrganization",
60
- "target": "byCategory",
61
- "target": "top",
62
- "target": "byOrganization",
63
- "target": "byCategory",
64
- "target": "top",
65
- "target": "byOrganization",
66
- "target": "byCategory",
67
- "target": "top",
68
- "target": "byOrganization",
69
- "target": "byCategory",
70
- "target": "top",
71
- "target": "byOrganization",
72
- "target": "byCategory",
73
- "target": "top",
74
- "target": "byOrganization",
75
- "target": "byCategory",
76
- "target": "top",
77
- "target": "byOrganization",
78
- "target": "byCategory",
79
- "target": "top",
80
- "target": "byOrganization",
81
- "target": "byCategory",
82
- "target": "top",
83
- "target": "byOrganization",
84
- "target": "byCategory",
85
- "target": "top",
86
- "target": "byOrganization",
87
- "target": "byCategory",
88
- "target": "top",
89
- "target": "byOrganization",
90
- "target": "byCategory",
91
- "target": "top",
92
- "target": "byOrganization",
93
- "target": "byCategory",
94
- "target": "top",
95
- "target": "byOrganization",
96
- "target": "byCategory",
97
- "target": "top",
98
- "target": "byOrganization",
99
- "target": "byCategory",
100
- "target": "top",
101
- "target": "byOrganization",
102
- "target": "byCategory",
103
- "target": "top",
104
- "target": "byOrganization",
105
- "target": "byCategory",
106
- "target": "top",
107
- "target": "byOrganization",
108
- "target": "byCategory",
109
- "target": "top",
110
- "target": "byOrganization",
111
- "target": "byCategory",
112
- "target": "top",
113
- "target": "byOrganization",
114
- "target": "byCategory",
115
- "target": "top",
116
- "target": "byOrganization",
117
- "target": "byCategory",
118
- "target": "top",
119
- "target": "byOrganization",
120
- "target": "byCategory",
121
- "target": "top",
122
- "target": "byOrganization",
123
- "target": "byCategory",
124
- "target": "top",
125
- "target": "byOrganization",
126
- "target": "byCategory",
127
- "target": "top",
128
- "target": "byOrganization",
129
- "target": "byCategory",
130
- "target": "top",
131
- "target": "byOrganization",
132
- "target": "byCategory",
133
- "target": "top",
134
- "target": "byOrganization",
135
- "target": "byCategory",
136
- "target": "top",
137
- "target": "byOrganization",
138
- "target": "byCategory",
139
- "target": "top",
140
- "target": "byOrganization",
141
- "target": "byCategory",
142
- "target": "top",
143
- "target": "byOrganization",
144
- "target": "byCategory",
145
- "target": "top",
146
- "target": "byOrganization",
147
- "target": "byCategory",
148
- "target": "top",
149
- "target": "byOrganization",
150
- "target": "byCategory",
151
- "target": "top",
152
- "target": "byOrganization",
153
- "target": "byCategory",
154
- "target": "top",
155
- "target": "byOrganization",
156
- "target": "byCategory",
157
- "target": "top",
158
- "target": "byOrganization",
159
- "target": "byCategory",
160
- "target": "top",
161
- "target": "byOrganization",
162
- "target": "byCategory",
163
- "target": "top",
164
- "target": "byOrganization",
165
- "target": "byCategory",
166
- "target": "top",
167
- "target": "byOrganization",
168
- "target": "byCategory",