electrodb 1.12.1 → 2.1.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
@@ -1,4 +1,4 @@
1
- const { CastTypes, ValueTypes, KeyCasing, AttributeTypes, AttributeMutationMethods, AttributeWildCard, PathTypes } = require("./types");
1
+ const { CastTypes, ValueTypes, KeyCasing, AttributeTypes, AttributeMutationMethods, AttributeWildCard, PathTypes, TableIndex } = require("./types");
2
2
  const AttributeTypeNames = Object.keys(AttributeTypes);
3
3
  const ValidFacetTypes = [AttributeTypes.string, AttributeTypes.number, AttributeTypes.boolean, AttributeTypes.enum];
4
4
  const e = require("./errors");
@@ -100,6 +100,9 @@ class Attribute {
100
100
  this.isKeyField = !!definition.isKeyField;
101
101
  this.unformat = this._makeDestructureKey(definition);
102
102
  this.format = this._makeStructureKey(definition);
103
+ this.padding = definition.padding;
104
+ this.applyFixings = this._makeApplyFixings(definition);
105
+ this.applyPadding = this._makePadding(definition);
103
106
  this.indexes = [...(definition.indexes || [])];
104
107
  let {isWatched, isWatcher, watchedBy, watching, watchAll} = Attribute._destructureWatcher(definition);
105
108
  this._isWatched = isWatched
@@ -233,29 +236,72 @@ class Attribute {
233
236
  return set || ((attr) => attr);
234
237
  }
235
238
 
236
- _makeStructureKey({prefix = "", postfix = "", casing= KeyCasing.none} = {}) {
237
- return (key) => {
238
- let value = key;
239
- if (this.type === AttributeTypes.string && v.isStringHasLength(key)) {
240
- value = `${prefix}${key}${postfix}`;
239
+ _makeApplyFixings({ prefix = "", postfix = "", casing= KeyCasing.none } = {}) {
240
+ return (value) => {
241
+ if ([AttributeTypes.string, AttributeTypes.enum].includes(this.type)) {
242
+ value = `${prefix}${value}${postfix}`;
241
243
  }
244
+
242
245
  return u.formatAttributeCasing(value, casing);
243
246
  }
244
247
  }
245
248
 
246
- _makeDestructureKey({prefix = "", postfix = "", casing= KeyCasing.none} = {}) {
249
+ _makeStructureKey() {
250
+ return (key) => {
251
+ return this.applyPadding(key);
252
+ }
253
+ }
254
+
255
+ _isPaddingEligible(padding = {} ) {
256
+ return !!padding && padding.length && v.isStringHasLength(padding.char);
257
+ }
258
+
259
+ _makePadding({ padding = {} }) {
260
+ return (value) => {
261
+ if (typeof value !== 'string') {
262
+ return value;
263
+ } else if (this._isPaddingEligible(padding)) {
264
+ return u.addPadding({padding, value});
265
+ } else {
266
+ return value;
267
+ }
268
+ }
269
+ }
270
+
271
+ _makeRemoveFixings({prefix = "", postfix = "", casing= KeyCasing.none} = {}) {
247
272
  return (key) => {
248
273
  let value = "";
249
274
  if (![AttributeTypes.string, AttributeTypes.enum].includes(this.type) || typeof key !== "string") {
250
- return key;
251
- } else if (key.length > prefix.length) {
275
+ value = key;
276
+ } else if (prefix.length > 0 && key.length > prefix.length) {
252
277
  for (let i = prefix.length; i < key.length - postfix.length; i++) {
253
278
  value += key[i];
254
279
  }
255
280
  } else {
256
281
  value = key;
257
282
  }
258
- return u.formatAttributeCasing(value, casing);
283
+
284
+ return value;
285
+ }
286
+ }
287
+
288
+ _makeDestructureKey({prefix = "", postfix = "", casing= KeyCasing.none, padding = {}} = {}) {
289
+ return (key) => {
290
+ let value = "";
291
+ if (![AttributeTypes.string, AttributeTypes.enum].includes(this.type) || typeof key !== "string") {
292
+ return key;
293
+ } else if (key.length > prefix.length) {
294
+ value = u.removeFixings({prefix, postfix, value: key});
295
+ } else {
296
+ value = key;
297
+ }
298
+
299
+ // todo: if an attribute is also used as a pk or sk directly in one index, but a composite in another, then padding is going to be broken
300
+ // if (padding && padding.length) {
301
+ // value = u.removePadding({padding, value});
302
+ // }
303
+
304
+ return value;
259
305
  };
260
306
  }
261
307
 
@@ -958,7 +1004,7 @@ class Schema {
958
1004
  let definition = facets.byField[field][indexName];
959
1005
  if (definition.facets.length > 1) {
960
1006
  throw new e.ElectroError(
961
- e.ErrorCodes.InvalidIndexCompositeWithAttributeName,
1007
+ e.ErrorCodes.InvalidIndexWithAttributeName,
962
1008
  `Invalid definition for "${definition.type}" field on index "${u.formatIndexNameForDisplay(indexName)}". The ${definition.type} field "${definition.field}" shares a field name with an attribute defined on the Entity, and therefore is not allowed to contain composite references to other attributes. Please either change the field name of the attribute, or redefine the index to use only the single attribute "${definition.field}".`
963
1009
  )
964
1010
  }
@@ -1005,10 +1051,19 @@ class Schema {
1005
1051
  `Invalid use of a collection on index "${u.formatIndexNameForDisplay(indexName)}". The ${definition.type} field "${definition.field}" shares a field name with an attribute defined on the Entity, and therefore the index is not allowed to participate in a Collection. Please either change the field name of the attribute, or remove all collection(s) from the index.`
1006
1052
  )
1007
1053
  }
1054
+
1055
+ if (definition.field === field) {
1056
+ if (attribute.padding !== undefined) {
1057
+ throw new e.ElectroError(
1058
+ e.ErrorCodes.InvalidAttributeDefinition,
1059
+ `Invalid padding definition for the attribute "${name}". Padding is not currently supported for attributes that are also defined as table indexes.`
1060
+ );
1061
+ }
1062
+ }
1008
1063
  }
1009
1064
  }
1010
1065
 
1011
- let isKey = !!facets.byIndex && facets.byIndex[""].all.find((facet) => facet.name === name);
1066
+ let isKey = !!facets.byIndex && facets.byIndex[TableIndex].all.find((facet) => facet.name === name);
1012
1067
  let definition = {
1013
1068
  name,
1014
1069
  field,
@@ -1033,6 +1088,7 @@ class Schema {
1033
1088
  properties: attribute.properties,
1034
1089
  parentPath: attribute.parentPath,
1035
1090
  parentType: attribute.parentType,
1091
+ padding: attribute.padding,
1036
1092
  };
1037
1093
 
1038
1094
  if (definition.type === AttributeTypes.custom) {
package/src/service.js CHANGED
@@ -243,13 +243,8 @@ class Service {
243
243
 
244
244
  cleanseRetrievedData(collection = "", entities, data = {}, config = {}) {
245
245
  if (config.raw) {
246
- if (config._isPagination) {
247
- return [data.LastEvaluatedKey, data];
248
- } else {
249
- return data;
250
- }
246
+ return data;
251
247
  }
252
-
253
248
  data.Items = data.Items || [];
254
249
  let index = this.collectionSchema[collection].index;
255
250
  let results = {};
@@ -268,18 +263,15 @@ class Service {
268
263
  if (!entityAlias) {
269
264
  continue;
270
265
  }
271
-
272
266
  // pager=false because we don't want the entity trying to parse the lastEvaluatedKey
273
- let items = this.collectionSchema[collection].entities[entityAlias].formatResponse({Item: record}, index, {...config, pager: false});
274
- results[entityAlias].push(items);
275
- }
276
-
277
- if (config._isPagination) {
278
- let page = this._formatReturnPager(config, index, data.LastEvaluatedKey, data.Items[data.Items.length - 1]);
279
- return [page, results];
280
- } else {
281
- return results;
267
+ let items = this.collectionSchema[collection].entities[entityAlias].formatResponse({Item: record}, index, {
268
+ ...config,
269
+ pager: false,
270
+ parse: undefined
271
+ });
272
+ results[entityAlias].push(items.data);
282
273
  }
274
+ return results;
283
275
  }
284
276
 
285
277
  findKeyOwner(lastEvaluatedKey) {
@@ -287,92 +279,25 @@ class Service {
287
279
  .find((entity) => entity.ownsLastEvaluatedKey(lastEvaluatedKey));
288
280
  }
289
281
 
290
- findItemPagerOwner(collection, pager = {}) {
291
- if (this.collectionSchema[collection] === undefined) {
292
- throw new Error("Invalid collection");
293
- }
294
- let matchingEntities = [];
295
- if (pager === null) {
296
- return matchingEntities;
297
- }
298
- for (let entity of Object.values(this.collectionSchema[collection].entities)) {
299
- if (entity.ownsPager(pager, this.collectionSchema[collection].index)) {
300
- matchingEntities.push(entity);
301
- }
302
- }
303
- return matchingEntities;
304
- }
305
-
306
- findNamedPagerOwner(pager = {}) {
307
- let identifiers = this._getEntityIdentifiers(this.entities);
308
- for (let identifier of identifiers) {
309
- let hasCorrectFieldProperties = typeof pager[identifier.nameField] === "string" && typeof pager[identifier.versionField] === "string";
310
- let hasMatchingFieldValues = pager[identifier.nameField] === identifier.name && pager[identifier.versionField] === identifier.version;
311
- if (hasCorrectFieldProperties && hasMatchingFieldValues) {
312
- return identifier.entity;
313
- }
282
+ expectKeyOwner(lastEvaluatedKey) {
283
+ const owner = this.findKeyOwner(lastEvaluatedKey);
284
+ if (owner === undefined) {
285
+ throw new e.ElectroError(e.ErrorCodes.NoOwnerForCursor, `Supplied cursor does not resolve to Entity within the Service ${this.service.name}`);
314
286
  }
287
+ return owner;
315
288
  }
316
289
 
317
- expectPagerOwner(type, collection, pager) {
318
- if (type === Pager.raw) {
319
- return Object.values(this.collectionSchema[collection].entities)[0];
320
- } else if (type === Pager.named || type === undefined) {
321
- let owner = this.findNamedPagerOwner(pager);
322
- if (owner === undefined) {
323
- throw new e.ElectroError(e.ErrorCodes.NoOwnerForPager, "Supplied Pager does not resolve to Entity within Service");
324
- }
325
- return owner;
326
- } else if (type === Pager.item) {
327
- let owners = this.findItemPagerOwner(collection, pager);
328
- if (owners.length === 1) {
329
- return owners[0];
330
- } else {
331
- throw new e.ElectroError(e.ErrorCodes.PagerNotUnique, "Supplied Pager did not resolve to single Entity");
332
- }
333
- } else {
334
- throw new e.ElectroError(e.ErrorCodes.InvalidOptions, `Invalid value for option "pager" provider: "${pager}". Allowed values include ${u.commaSeparatedString(Object.keys(Pager))}.`)
335
- }
336
- }
337
-
338
- findPagerIdentifiers(collection, pager = {}) {
339
- if (this.collectionSchema[collection] === undefined) {
340
- throw new Error("Invalid collection");
341
- }
342
- let matchingIdentifiers = [];
343
- if (pager === null) {
344
- return matchingIdentifiers;
345
- }
346
- for (let entity of Object.values(this.collectionSchema[collection].entities)) {
347
- if (entity.ownsPager(pager, this.collectionSchema[collection].index)) {
348
- matchingIdentifiers.push({
349
- [entity.identifiers.entity]: entity.getName(),
350
- [entity.identifiers.version]: entity.getVersion(),
351
- });
352
- }
353
- }
354
- return matchingIdentifiers;
290
+ findCursorOwner(cursor) {
291
+ return Object.values(this.entities)
292
+ .find(entity => entity.ownsCursor(cursor));
355
293
  }
356
294
 
357
- _formatReturnPager(config, index, lastEvaluatedKey, lastReturned) {
358
- if (config.pager === "raw") {
359
- return lastEvaluatedKey;
360
- } else if (!lastEvaluatedKey) {
361
- return null;
362
- } else {
363
- let entity = this.findKeyOwner(lastEvaluatedKey);
364
- if (!entity) {
365
- return lastReturned !== undefined
366
- ? this._formatReturnPager(config, index, lastReturned)
367
- : null
368
- }
369
- let page = entity._formatKeysToItem(index, lastEvaluatedKey);
370
- if (config.pager === "named") {
371
- page[entity.identifiers.entity] = entity.getName();
372
- page[entity.identifiers.version] = entity.getVersion();
373
- }
374
- return page;
295
+ expectCursorOwner(cursor) {
296
+ const owner = this.findCursorOwner(cursor);
297
+ if (owner === undefined) {
298
+ throw new e.ElectroError(e.ErrorCodes.NoOwnerForCursor, `Supplied cursor does not resolve to Entity within the Service ${this.service.name}`);
375
299
  }
300
+ return owner;
376
301
  }
377
302
 
378
303
  _getTableName() {
@@ -389,21 +314,7 @@ class Service {
389
314
  _makeCollectionChain(name = "", attributes = {}, initialClauses = {}, expressions = {}, entities = {}, entity = {}, facets = {}) {
390
315
  let filterBuilder = new FilterFactory(attributes, FilterOperations);
391
316
  let whereBuilder = new WhereFactory(attributes, FilterOperations);
392
-
393
- let pageClause = {...initialClauses.page};
394
- let pageAction = initialClauses.page.action;
395
- pageClause.action = (entity, state, page = null, options = {}) => {
396
- try {
397
- if (page === null) {
398
- return pageAction(entity, state, page, options);
399
- }
400
- let owner = this.expectPagerOwner(options.pager, name, page);
401
- return pageAction(owner, state, page, options);
402
- } catch(err) {
403
- return Promise.reject(err);
404
- }
405
- }
406
- let clauses = {...initialClauses, page: pageClause};
317
+ let clauses = {...initialClauses};
407
318
 
408
319
  clauses = filterBuilder.injectFilterClauses(clauses);
409
320
  clauses = whereBuilder.injectWhereClauses(clauses);
@@ -412,6 +323,14 @@ class Service {
412
323
  // expressions, // DynamoDB doesnt return what I expect it would when provided with these entity filters
413
324
  parse: (options, data) => {
414
325
  return this.cleanseRetrievedData(name, entities, data, options);
326
+ },
327
+ formatCursor: {
328
+ serialize: (key) => {
329
+ return this.expectKeyOwner(key).serializeCursor(key);
330
+ },
331
+ deserialize: (cursor) => {
332
+ return this.expectCursorOwner(cursor).deserilizeCursor(cursor);
333
+ }
415
334
  }
416
335
  };
417
336
 
@@ -508,7 +427,7 @@ class Service {
508
427
  return [!!collectionDifferences.length, collectionDifferences];
509
428
  }
510
429
 
511
- _compareEntityAttributes(definition = {}, providedAttributes = {}) {
430
+ _compareEntityAttributes(entityName, definition = {}, providedAttributes = {}, keys) {
512
431
  let results = {
513
432
  additions: {},
514
433
  invalid: [],
@@ -519,17 +438,29 @@ class Service {
519
438
  results.additions[name] = detail;
520
439
  } else if (defined.field !== detail.field) {
521
440
  results.invalid.push(
522
- `Attribute provided "${name}" with Table Field "${detail.field}" does not match established Table Field "${defined.field}"`,
441
+ `The attribute "${name}" with Table Field "${detail.field}" does not match established Table Field "${defined.field}"`,
523
442
  );
524
443
  }
444
+ if (defined && detail && (defined.padding || detail.padding)) {
445
+ const definedPadding = defined.padding || {};
446
+ const detailPadding = detail.padding || {};
447
+ if (keys.pk.facets.includes(name) &&
448
+ (definedPadding.length !== detailPadding.length ||
449
+ definedPadding.char !== detailPadding.char)
450
+ ) {
451
+ results.invalid.push(
452
+ `The attribute "${name}" contains inconsistent padding definitions that impact how keys are formed`,
453
+ );
454
+ }
455
+ }
525
456
  }
526
457
  return [!!results.invalid.length, results];
527
458
  }
528
459
 
529
- _processEntityAttributes(definition = {}, providedAttributes = {}) {
530
- let [attributesAreIncompatible, attributeResults] = this._compareEntityAttributes(definition, providedAttributes);
460
+ _processEntityAttributes(entityName, definition = {}, providedAttributes = {}, keys) {
461
+ let [attributesAreIncompatible, attributeResults] = this._compareEntityAttributes(entityName, definition, providedAttributes, keys);
531
462
  if (attributesAreIncompatible) {
532
- throw new e.ElectroError(e.ErrorCodes.InvalidJoin, `Invalid entity attributes. The following attributes have already been defined on this model but with incompatible or conflicting properties: ${attributeResults.invalid.join(", ")}`);
463
+ throw new e.ElectroError(e.ErrorCodes.InvalidJoin, `Inconsistent attribute(s) on the entity "${entityName}". The following attribute(s) are defined with incompatible or conflicting definitions across participating entities: ${attributeResults.invalid.join(", ")}. These attribute definitions must match among all members of the collection.`);
533
464
  } else {
534
465
  return {
535
466
  ...definition,
@@ -647,7 +578,7 @@ class Service {
647
578
  this.collectionSchema[collection].table = entity._getTableName();
648
579
  }
649
580
  this.collectionSchema[collection].keys = this._processEntityKeys(name, this.collectionSchema[collection].keys, providedIndex);
650
- this.collectionSchema[collection].attributes = this._processEntityAttributes(this.collectionSchema[collection].attributes, entity.model.schema.attributes);
581
+ this.collectionSchema[collection].attributes = this._processEntityAttributes(name, this.collectionSchema[collection].attributes, entity.model.schema.attributes, this.collectionSchema[collection].keys);
651
582
  this.collectionSchema[collection].entities[name] = entity;
652
583
  this.collectionSchema[collection].identifiers = this._processEntityIdentifiers(this.collectionSchema[collection].identifiers, entity.getIdentifierExpressions(name));
653
584
  this.collectionSchema[collection].index = this._processEntityCollectionIndex(this.collectionSchema[collection].index, providedIndex.index, name, collection);
package/src/types.js CHANGED
@@ -110,7 +110,8 @@ const AttributeMutationMethods = {
110
110
  const Pager = {
111
111
  raw: "raw",
112
112
  named: "named",
113
- item: "item"
113
+ item: "item",
114
+ cursor: "cursor"
114
115
  }
115
116
 
116
117
  const UnprocessedTypes = {
@@ -194,6 +195,15 @@ const TerminalOperation = {
194
195
  page: 'page',
195
196
  }
196
197
 
198
+ const AllPages = 'all';
199
+
200
+ const ResultOrderOption = {
201
+ 'asc': true,
202
+ 'desc': false
203
+ };
204
+
205
+ const ResultOrderParam = 'ScanIndexForward';
206
+
197
207
  module.exports = {
198
208
  Pager,
199
209
  KeyTypes,
@@ -223,5 +233,8 @@ module.exports = {
223
233
  AttributeProxySymbol,
224
234
  ElectroInstanceTypes,
225
235
  EventSubscriptionTypes,
226
- AttributeMutationMethods
236
+ AttributeMutationMethods,
237
+ AllPages,
238
+ ResultOrderOption,
239
+ ResultOrderParam,
227
240
  };
package/src/util.js CHANGED
@@ -161,9 +161,71 @@ function getUnique(arr1, arr2) {
161
161
  ]));
162
162
  }
163
163
 
164
+ const cursorFormatter = {
165
+ serialize: (key) => {
166
+ if (!key) {
167
+ return null;
168
+ } else if (typeof val !== 'string') {
169
+ key = JSON.stringify(key);
170
+ }
171
+ return Buffer.from(key).toString('base64url');
172
+ },
173
+ deserialize: (cursor) => {
174
+ if (!cursor) {
175
+ return undefined;
176
+ } else if (typeof cursor !== 'string') {
177
+ throw new Error(`Invalid cursor provided, expected type 'string' recieved: ${JSON.stringify(cursor)}`);
178
+ }
179
+ try {
180
+ return JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'));
181
+ } catch(err) {
182
+ throw new Error('Unable to parse cursor');
183
+ }
184
+ }
185
+ }
186
+
187
+ function removeFixings({prefix = '', postfix = '', value = ''} = {}) {
188
+ const start = value.toLowerCase().startsWith(prefix.toLowerCase()) ? prefix.length : 0;
189
+ const end = value.length - (value.toLowerCase().endsWith(postfix.toLowerCase()) ? postfix.length : 0);
190
+
191
+ let formatted = '';
192
+ for (let i = start; i < end; i++) {
193
+ formatted += value[i];
194
+ }
195
+
196
+ return formatted;
197
+ }
198
+
199
+ function addPadding({padding = {}, value = ''} = {}) {
200
+ return value.padStart(padding.length, padding.char);
201
+ }
202
+
203
+ function removePadding({padding = {}, value = ''} = {}) {
204
+ if (!padding.length || value.length >= padding.length) {
205
+ return value;
206
+ }
207
+
208
+ let formatted = '';
209
+ let useRemaining = false;
210
+ for (let i = 0; i < value.length; i++) {
211
+ const char = value[i];
212
+ if (useRemaining || i >= padding.length) {
213
+ formatted += char;
214
+ } else if (char !== padding.char) {
215
+ formatted += char;
216
+ useRemaining = true;
217
+ }
218
+ }
219
+
220
+ return formatted;
221
+ }
222
+
164
223
  module.exports = {
165
224
  getUnique,
166
225
  batchItems,
226
+ addPadding,
227
+ removePadding,
228
+ removeFixings,
167
229
  parseJSONPath,
168
230
  getInstanceType,
169
231
  getModelVersion,
@@ -171,6 +233,7 @@ module.exports = {
171
233
  genericizeJSONPath,
172
234
  commaSeparatedString,
173
235
  formatAttributeCasing,
236
+ cursorFormatter,
174
237
  applyBetaModelOverrides,
175
238
  formatIndexNameForDisplay,
176
239
  BatchGetOrderMaintainer,
@@ -64,6 +64,18 @@ const Attribute = {
64
64
  type: "any",
65
65
  format: "isFunction",
66
66
  },
67
+ padding: {
68
+ type: "object",
69
+ required: ['length', 'char'],
70
+ properties: {
71
+ length: {
72
+ type: 'number'
73
+ },
74
+ char: {
75
+ type: 'string',
76
+ }
77
+ }
78
+ }
67
79
  },
68
80
  };
69
81
 
package/src/where.js CHANGED
@@ -120,7 +120,7 @@ class WhereFactory {
120
120
  injected[name] = {
121
121
  name,
122
122
  action: this.buildClause(filter),
123
- children: ["params", "go", "page", "where", ...modelFilters],
123
+ children: ["params", "go", "where", ...modelFilters],
124
124
  };
125
125
  }
126
126
  filterChildren.push("where");
@@ -129,7 +129,7 @@ class WhereFactory {
129
129
  action: (entity, state, fn) => {
130
130
  return this.buildClause(fn)(entity, state);
131
131
  },
132
- children: ["params", "go", "page", "where", ...modelFilters],
132
+ children: ["params", "go", "where", ...modelFilters],
133
133
  };
134
134
  for (let parent of filterParents) {
135
135
  injected[parent] = { ...injected[parent] };
package/notes DELETED
@@ -1,45 +0,0 @@
1
- /*
2
-
3
- todo: better param typing
4
- done: get typing
5
- todo: get implementation
6
- done: get[] typing
7
- todo: get[] implementation
8
- done: delete typing
9
- todo: delete implementation
10
- done: delete[] typing
11
- todo: delete[] implementation
12
- done: put[] typing
13
- todo: put[] implementation
14
- done: put typing
15
- todo: put implementation
16
- todo: update typing
17
- done: update implementation
18
- done: SetRecord
19
- done: RemoveRecord
20
- done: DataUpdateMethodRecord
21
-
22
- done: scan typing
23
- todo: scan implementation
24
- done: query typing
25
- todo: query implementation
26
- done: find typing
27
- todo: find implementation
28
- done: match typing
29
- todo: match implementation
30
- done: parse typing
31
- todo: parse implementation (requires cursor?)
32
- todo: collection query
33
- todo: collection impl
34
-
35
- todo: add where to go?
36
- todo: rethink option names
37
-
38
- todo: add cursor to query options
39
- todo: remove old doc notes on `join` and slim down `service`
40
-
41
-
42
-
43
- find and test any add on set and number
44
- try number enum?
45
- integration test enum set ts
package/output DELETED
@@ -1,106 +0,0 @@
1
- page {
2
- "data": [
3
- {
4
- "prop2": "e3b142b1-e5d7-4b17-8a66-38fcdf6ef1ef",
5
- "prop1": "value1",
6
- "prop4": "value4"
7
- },
8
- {
9
- "prop2": "e8ed19db-3ac4-45d6-8fb8-281ada117e96",
10
- "prop1": "value1",
11
- "prop4": "value4"
12
- },
13
- {
14
- "prop2": "dcca03f5-7478-4afb-9297-20a68ff56559",
15
- "prop1": "value1",
16
- "prop4": "value4"
17
- },
18
- {
19
- "prop2": "03ccd79e-c5e5-4179-8213-196eafcbd4b9",
20
- "prop1": "value1",
21
- "prop4": "value4"
22
- },
23
- {
24
- "prop2": "2900735e-aaa4-4661-a52d-7aecb752e694",
25
- "prop1": "value1",
26
- "prop4": "value4"
27
- },
28
- {
29
- "prop2": "0f1da461-5257-4f6c-8cb3-af7b56819100",
30
- "prop1": "value1",
31
- "prop4": "value4"
32
- },
33
- {
34
- "prop2": "7562c43b-8ac9-483c-b950-4f0e1366c11a",
35
- "prop1": "value1",
36
- "prop4": "value4"
37
- },
38
- {
39
- "prop2": "ee844e97-dc1c-4242-bd2b-45685e39d7fd",
40
- "prop1": "value1",
41
- "prop4": "value4"
42
- },
43
- {
44
- "prop2": "4b55e242-9875-412e-b483-24e51dabf6c8",
45
- "prop1": "value1",
46
- "prop4": "value4"
47
- },
48
- {
49
- "prop2": "77a5b66e-c658-4ec6-9630-4d2469653bd4",
50
- "prop1": "value1",
51
- "prop4": "value4"
52
- },
53
- {
54
- "prop2": "194efbfe-7be6-4662-9982-a1c8eb6ab36d",
55
- "prop1": "value1",
56
- "prop4": "value4"
57
- },
58
- {
59
- "prop2": "value2",
60
- "prop1": "value1",
61
- "prop4": "value4"
62
- },
63
- {
64
- "prop2": "516d8256-3b3e-41e1-a44a-0720fe748e23",
65
- "prop1": "value1",
66
- "prop4": "value4"
67
- },
68
- {
69
- "prop2": "35bf3a7e-9dff-4817-a157-2be09c5fc8a8",
70
- "prop1": "value1",
71
- "prop4": "value4"
72
- },
73
- {
74
- "prop2": "3836452b-6c00-415e-85ce-a9596644b6eb",
75
- "prop1": "value1",
76
- "prop4": "value4"
77
- },
78
- {
79
- "prop2": "value2abc",
80
- "prop1": "value1",
81
- "prop4": "value4"
82
- },
83
- {
84
- "prop2": "887e5301-113c-4ca6-b6d0-ebaf27e217d5",
85
- "prop1": "value1",
86
- "prop4": "value4"
87
- },
88
- {
89
- "prop2": "6c6d9363-3821-40c8-b3d0-7ec2c6d3f1eb",
90
- "prop1": "value1",
91
- "prop4": "value4"
92
- },
93
- {
94
- "prop2": "fcff14a3-ffa9-498e-a0ae-596c747b027b",
95
- "prop1": "value1",
96
- "prop4": "value4"
97
- },
98
- {
99
- "prop2": "1d199eeb-ccc1-4c25-a1b6-1685126d44b3",
100
- "prop1": "value1",
101
- "prop4": "value4"
102
- }
103
- ],
104
- "count": 2,
105
- "next": null
106
- }