@karmaniverous/entity-manager 0.0.12 → 0.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/README.md CHANGED
@@ -38,8 +38,8 @@ The `entity-manager` configuration object describes each entity's structured key
38
38
 
39
39
  // These tagged templates are used below to simplify template literals.
40
40
  // sn2e - Template literal returns empty string if any expression is nil.
41
- // sn2n - Template literal returns undefined if any expression is nil.
42
- import { sn2e, sn2n } from '@karmaniverous/tagged-templates';
41
+ // sn2u - Template literal returns undefined if any expression is nil.
42
+ import { sn2e, sn2u } from '@karmaniverous/tagged-templates';
43
43
 
44
44
  // Entity config object named export.
45
45
  export const config = {
@@ -57,31 +57,31 @@ export const config = {
57
57
 
58
58
  // Table RANGE key.
59
59
  entitySK: ({ timestamp, transactionId }) =>
60
- sn2n`timestamp#${timestamp}|transactionId#${transactionId}`,
60
+ sn2u`timestamp#${timestamp}|transactionId#${transactionId}`,
61
61
 
62
62
  // merchants GSI HASH key. Note the optional shardId.
63
63
  merchantPK: ({ merchantId, shardId }) =>
64
- sn2n`merchantId#${merchantId}|transaction${sn2e`!${shardId}`}`,
64
+ sn2u`merchantId#${merchantId}|transaction${sn2e`!${shardId}`}`,
65
65
 
66
66
  // merchants GSI RANGE key.
67
67
  merchantSK: ({ methodId, timestamp, transactionId }) =>
68
- sn2n`timestamp#${timestamp}|methodId#${methodId}|transactionId#${transactionId}`,
68
+ sn2u`timestamp#${timestamp}|methodId#${methodId}|transactionId#${transactionId}`,
69
69
 
70
70
  // methods GSI HASH key. Note the optional shardId.
71
71
  methodPK: ({ methodId, shardId }) =>
72
- sn2n`method#${methodId}|transaction${sn2e`!${shardId}`}`,
72
+ sn2u`method#${methodId}|transaction${sn2e`!${shardId}`}`,
73
73
 
74
74
  // methods GSI RANGE key.
75
75
  methodSK: ({ merchantId, timestamp, transactionId }) =>
76
- sn2n`timestamp#${timestamp}|merchantId#${merchantId}|transactionId#${transactionId}`,
76
+ sn2u`timestamp#${timestamp}|merchantId#${merchantId}|transactionId#${transactionId}`,
77
77
 
78
78
  // users GSI HASH key. Note the optional shardId.
79
79
  userPK: ({ shardId, userId }) =>
80
- sn2n`user#${userId}|transaction${sn2e`!${shardId}`}`,
80
+ sn2u`user#${userId}|transaction${sn2e`!${shardId}`}`,
81
81
 
82
82
  // users GSI RANGE key.
83
83
  userSK: ({ merchantId, timestamp, transactionId }) =>
84
- sn2n`timestamp#${timestamp}|merchantId#${merchantId}|transactionId#${transactionId}`,
84
+ sn2u`timestamp#${timestamp}|merchantId#${merchantId}|transactionId#${transactionId}`,
85
85
  },
86
86
 
87
87
  // The sharding configuration for this entity.
@@ -216,10 +216,16 @@ This change can be accomplished with no breaking changes to existing implementat
216
216
  ## entity-manager
217
217
 
218
218
  * [entity-manager](#module_entity-manager)
219
- * [.EntityManager](#module_entity-manager.EntityManager)
220
- * [new exports.EntityManager(options)](#new_module_entity-manager.EntityManager_new)
221
- * [.addKeys(entityToken, item, [overwrite])](#module_entity-manager.EntityManager+addKeys) ⇒ <code>object</code>
222
- * [.getKeySpace(entityToken, item, keyToken, timestamp)](#module_entity-manager.EntityManager+getKeySpace) ⇒ <code>Array.&lt;string&gt;</code>
219
+ * _static_
220
+ * [.EntityManager](#module_entity-manager.EntityManager)
221
+ * [new exports.EntityManager(options)](#new_module_entity-manager.EntityManager_new)
222
+ * [.addKeys(entityToken, item, [overwrite])](#module_entity-manager.EntityManager+addKeys) ⇒ <code>object</code>
223
+ * [.getKeySpace(entityToken, keyToken, item, timestamp)](#module_entity-manager.EntityManager+getKeySpace) ⇒ <code>Array.&lt;string&gt;</code>
224
+ * [.query(entityToken, keyToken, item, shardQuery, [shardPageKeys])](#module_entity-manager.EntityManager+query) ⇒ <code>Promise.&lt;ShardedQueryResult&gt;</code>
225
+ * _inner_
226
+ * [~ShardQueryResult](#module_entity-manager..ShardQueryResult) : <code>object</code>
227
+ * [~ShardQueryFunction](#module_entity-manager..ShardQueryFunction) ⇒ <code>Promise.&lt;ShardQueryResult&gt;</code>
228
+ * [~ShardedQueryResult](#module_entity-manager..ShardedQueryResult) : <code>object</code>
223
229
 
224
230
  <a name="module_entity-manager.EntityManager"></a>
225
231
 
@@ -231,7 +237,8 @@ Manage DynamoDb entities.
231
237
  * [.EntityManager](#module_entity-manager.EntityManager)
232
238
  * [new exports.EntityManager(options)](#new_module_entity-manager.EntityManager_new)
233
239
  * [.addKeys(entityToken, item, [overwrite])](#module_entity-manager.EntityManager+addKeys) ⇒ <code>object</code>
234
- * [.getKeySpace(entityToken, item, keyToken, timestamp)](#module_entity-manager.EntityManager+getKeySpace) ⇒ <code>Array.&lt;string&gt;</code>
240
+ * [.getKeySpace(entityToken, keyToken, item, timestamp)](#module_entity-manager.EntityManager+getKeySpace) ⇒ <code>Array.&lt;string&gt;</code>
241
+ * [.query(entityToken, keyToken, item, shardQuery, [shardPageKeys])](#module_entity-manager.EntityManager+query) ⇒ <code>Promise.&lt;ShardedQueryResult&gt;</code>
235
242
 
236
243
  <a name="new_module_entity-manager.EntityManager_new"></a>
237
244
 
@@ -272,7 +279,7 @@ Decorate an entity item with keys.
272
279
 
273
280
  <a name="module_entity-manager.EntityManager+getKeySpace"></a>
274
281
 
275
- #### entityManager.getKeySpace(entityToken, item, keyToken, timestamp) ⇒ <code>Array.&lt;string&gt;</code>
282
+ #### entityManager.getKeySpace(entityToken, keyToken, item, timestamp) ⇒ <code>Array.&lt;string&gt;</code>
276
283
  Return an array of sharded keys valid for a given entity token & timestamp.
277
284
 
278
285
  **Kind**: instance method of [<code>EntityManager</code>](#module_entity-manager.EntityManager)
@@ -288,10 +295,61 @@ Return an array of sharded keys valid for a given entity token & timestamp.
288
295
  | Param | Type | Description |
289
296
  | --- | --- | --- |
290
297
  | entityToken | <code>string</code> | Entity token. |
291
- | item | <code>object</code> | Entity item. |
292
298
  | keyToken | <code>string</code> | Key token. |
299
+ | item | <code>object</code> | Entity item sufficiently populated to generate property keyToken. |
293
300
  | timestamp | <code>number</code> | Timestamp. |
294
301
 
302
+ <a name="module_entity-manager.EntityManager+query"></a>
303
+
304
+ #### entityManager.query(entityToken, keyToken, item, shardQuery, [shardPageKeys]) ⇒ <code>Promise.&lt;ShardedQueryResult&gt;</code>
305
+ Query an entity across shards.
306
+
307
+ **Kind**: instance method of [<code>EntityManager</code>](#module_entity-manager.EntityManager)
308
+ **Returns**: <code>Promise.&lt;ShardedQueryResult&gt;</code> - Sharded query result.
309
+
310
+ | Param | Type | Description |
311
+ | --- | --- | --- |
312
+ | entityToken | <code>string</code> | Entity token. |
313
+ | keyToken | <code>string</code> | Key token. |
314
+ | item | <code>object</code> | Entity item sufficiently populated to generate property keyToken. |
315
+ | shardQuery | <code>ShardQueryFunction</code> | Sharded query function. |
316
+ | [shardPageKeys] | <code>object</code> | Map of shard page keys. |
317
+
318
+ <a name="module_entity-manager..ShardQueryResult"></a>
319
+
320
+ ### entity-manager~ShardQueryResult : <code>object</code>
321
+ **Kind**: inner typedef of [<code>entity-manager</code>](#module_entity-manager)
322
+ **Properties**
323
+
324
+ | Name | Type | Description |
325
+ | --- | --- | --- |
326
+ | items | <code>Array.&lt;any&gt;</code> | Query result array. |
327
+ | pageKey | <code>\*</code> | Shard page key. |
328
+
329
+ <a name="module_entity-manager..ShardQueryFunction"></a>
330
+
331
+ ### entity-manager~ShardQueryFunction ⇒ <code>Promise.&lt;ShardQueryResult&gt;</code>
332
+ Shard query function
333
+
334
+ **Kind**: inner typedef of [<code>entity-manager</code>](#module_entity-manager)
335
+ **Returns**: <code>Promise.&lt;ShardQueryResult&gt;</code> - Sharded query result.
336
+
337
+ | Param | Type | Description |
338
+ | --- | --- | --- |
339
+ | shardedKey | <code>string</code> | Sharded key. |
340
+ | [pageKey] | <code>\*</code> | Page key. |
341
+
342
+ <a name="module_entity-manager..ShardedQueryResult"></a>
343
+
344
+ ### entity-manager~ShardedQueryResult : <code>object</code>
345
+ **Kind**: inner typedef of [<code>entity-manager</code>](#module_entity-manager)
346
+ **Properties**
347
+
348
+ | Name | Type | Description |
349
+ | --- | --- | --- |
350
+ | items | <code>Array.&lt;any&gt;</code> | Query result array. |
351
+ | shardPageKeys | <code>object</code> | Shard page keys. |
352
+
295
353
 
296
354
  ---
297
355
 
@@ -4,6 +4,9 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.EntityManager = void 0;
7
+ var _isUndefined2 = _interopRequireDefault(require("lodash/isUndefined"));
8
+ var _map2 = _interopRequireDefault(require("lodash/map"));
9
+ var _fromPairs2 = _interopRequireDefault(require("lodash/fromPairs"));
7
10
  var _sortedUniq2 = _interopRequireDefault(require("lodash/sortedUniq"));
8
11
  var _forEach2 = _interopRequireDefault(require("lodash/forEach"));
9
12
  var _isNil2 = _interopRequireDefault(require("lodash/isNil"));
@@ -94,8 +97,8 @@ class EntityManager {
94
97
  * Return an array of sharded keys valid for a given entity token & timestamp.
95
98
  *
96
99
  * @param {string} entityToken - Entity token.
97
- * @param {object} item - Entity item.
98
100
  * @param {string} keyToken - Key token.
101
+ * @param {object} item - Entity item sufficiently populated to generate property keyToken.
99
102
  * @param {number} timestamp - Timestamp.
100
103
  * @returns {string[]} Array of keys.
101
104
  * @throws {Error} If entityToken is invalid.
@@ -103,7 +106,7 @@ class EntityManager {
103
106
  * @throws {Error} If keyToken is invalid.
104
107
  * @throws {Error} If timestamp is invalid.
105
108
  */
106
- getKeySpace(entityToken, item, keyToken) {
109
+ getKeySpace(entityToken, keyToken, item) {
107
110
  let timestamp = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : Date.now();
108
111
  _classPrivateFieldGet(this, _entityManager).logger.debug(`getting shard key space for ${entityToken} on key '${keyToken}' at timestamp ${timestamp}...`, item);
109
112
  const shardKeySpace = _classPrivateFieldGet(this, _entityManager).getShardKeySpace(entityToken, timestamp);
@@ -114,5 +117,76 @@ class EntityManager {
114
117
  _classPrivateFieldGet(this, _entityManager).logger.debug('done', result);
115
118
  return result;
116
119
  }
120
+
121
+ /**
122
+ * @typedef {object} ShardQueryResult
123
+ * @property {any[]} items - Query result array.
124
+ * @property {*} pageKey - Shard page key.
125
+ */
126
+
127
+ /**
128
+ * Shard query function
129
+ *
130
+ * @callback ShardQueryFunction
131
+ * @param {string} shardedKey - Sharded key.
132
+ * @param {*} [pageKey] - Page key.
133
+ * @returns {Promise<ShardQueryResult>} Sharded query result.
134
+ */
135
+
136
+ /**
137
+ * @typedef {object} ShardedQueryResult
138
+ * @property {any[]} items - Query result array.
139
+ * @property {object} shardPageKeys - Shard page keys.
140
+ */
141
+
142
+ /**
143
+ * Query an entity across shards.
144
+ *
145
+ * @param {string} entityToken - Entity token.
146
+ * @param {string} keyToken - Key token.
147
+ * @param {object} item - Entity item sufficiently populated to generate property keyToken.
148
+ * @param {ShardQueryFunction} shardQuery - Sharded query function.
149
+ * @param {object} [shardPageKeys] - Map of shard page keys.
150
+ * @returns {Promise<ShardedQueryResult>} Sharded query result.
151
+ */
152
+ async query(entityToken, keyToken, item, shardQuery, shardPageKeys) {
153
+ // Generate default shardPageKeys if not provided
154
+ shardPageKeys ??= (0, _fromPairs2.default)(this.getKeySpace(entityToken, keyToken, item).map(shardedKey => [shardedKey, undefined]));
155
+
156
+ // Query every shard in shardPageKeys.
157
+ const shardQueryResults = await Promise.all((0, _map2.default)(shardPageKeys, (pageKey, shardedKey) => new Promise(resolve => shardQuery(shardedKey, pageKey).then(_ref2 => {
158
+ let {
159
+ items,
160
+ pageKey
161
+ } = _ref2;
162
+ return resolve({
163
+ shardedKey,
164
+ items,
165
+ pageKey
166
+ });
167
+ }))));
168
+
169
+ // Reduce shardQueryResults into a single result.
170
+ const result = shardQueryResults.reduce((shardedQueryResult, _ref3) => {
171
+ let {
172
+ shardedKey,
173
+ items,
174
+ pageKey
175
+ } = _ref3;
176
+ return {
177
+ items: [...shardedQueryResult.items, ...items],
178
+ shardPageKeys: {
179
+ ...shardedQueryResult.shardPageKeys,
180
+ ...((0, _isUndefined2.default)(pageKey) ? {} : {
181
+ [shardedKey]: pageKey
182
+ })
183
+ }
184
+ };
185
+ }, {
186
+ items: [],
187
+ shardPageKeys: {}
188
+ });
189
+ return result;
190
+ }
117
191
  }
118
192
  exports.EntityManager = EntityManager;
@@ -78,8 +78,8 @@ export class EntityManager {
78
78
  * Return an array of sharded keys valid for a given entity token & timestamp.
79
79
  *
80
80
  * @param {string} entityToken - Entity token.
81
- * @param {object} item - Entity item.
82
81
  * @param {string} keyToken - Key token.
82
+ * @param {object} item - Entity item sufficiently populated to generate property keyToken.
83
83
  * @param {number} timestamp - Timestamp.
84
84
  * @returns {string[]} Array of keys.
85
85
  * @throws {Error} If entityToken is invalid.
@@ -87,7 +87,7 @@ export class EntityManager {
87
87
  * @throws {Error} If keyToken is invalid.
88
88
  * @throws {Error} If timestamp is invalid.
89
89
  */
90
- getKeySpace(entityToken, item, keyToken, timestamp = Date.now()) {
90
+ getKeySpace(entityToken, keyToken, item, timestamp = Date.now()) {
91
91
  this.#entityManager.logger.debug(
92
92
  `getting shard key space for ${entityToken} on key '${keyToken}' at timestamp ${timestamp}...`,
93
93
  item
@@ -113,4 +113,72 @@ export class EntityManager {
113
113
 
114
114
  return result;
115
115
  }
116
+
117
+ /**
118
+ * @typedef {object} ShardQueryResult
119
+ * @property {any[]} items - Query result array.
120
+ * @property {*} pageKey - Shard page key.
121
+ */
122
+
123
+ /**
124
+ * Shard query function
125
+ *
126
+ * @callback ShardQueryFunction
127
+ * @param {string} shardedKey - Sharded key.
128
+ * @param {*} [pageKey] - Page key.
129
+ * @returns {Promise<ShardQueryResult>} Sharded query result.
130
+ */
131
+
132
+ /**
133
+ * @typedef {object} ShardedQueryResult
134
+ * @property {any[]} items - Query result array.
135
+ * @property {object} shardPageKeys - Shard page keys.
136
+ */
137
+
138
+ /**
139
+ * Query an entity across shards.
140
+ *
141
+ * @param {string} entityToken - Entity token.
142
+ * @param {string} keyToken - Key token.
143
+ * @param {object} item - Entity item sufficiently populated to generate property keyToken.
144
+ * @param {ShardQueryFunction} shardQuery - Sharded query function.
145
+ * @param {object} [shardPageKeys] - Map of shard page keys.
146
+ * @returns {Promise<ShardedQueryResult>} Sharded query result.
147
+ */
148
+ async query(entityToken, keyToken, item, shardQuery, shardPageKeys) {
149
+ // Generate default shardPageKeys if not provided
150
+ shardPageKeys ??= _.fromPairs(
151
+ this.getKeySpace(entityToken, keyToken, item).map((shardedKey) => [
152
+ shardedKey,
153
+ undefined,
154
+ ])
155
+ );
156
+
157
+ // Query every shard in shardPageKeys.
158
+ const shardQueryResults = await Promise.all(
159
+ _.map(
160
+ shardPageKeys,
161
+ (pageKey, shardedKey) =>
162
+ new Promise((resolve) =>
163
+ shardQuery(shardedKey, pageKey).then(({ items, pageKey }) =>
164
+ resolve({ shardedKey, items, pageKey })
165
+ )
166
+ )
167
+ )
168
+ );
169
+
170
+ // Reduce shardQueryResults into a single result.
171
+ const result = shardQueryResults.reduce(
172
+ (shardedQueryResult, { shardedKey, items, pageKey }) => ({
173
+ items: [...shardedQueryResult.items, ...items],
174
+ shardPageKeys: {
175
+ ...shardedQueryResult.shardPageKeys,
176
+ ...(_.isUndefined(pageKey) ? {} : { [shardedKey]: pageKey }),
177
+ },
178
+ }),
179
+ { items: [], shardPageKeys: {} }
180
+ );
181
+
182
+ return result;
183
+ }
116
184
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karmaniverous/entity-manager",
3
- "version": "0.0.12",
3
+ "version": "0.1.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -63,7 +63,8 @@
63
63
  "require": [
64
64
  "@babel/register"
65
65
  ],
66
- "spec": "./**/*.test.!(*.*)"
66
+ "spec": "./**/*.test.!(*.*)",
67
+ "timeout": 60000
67
68
  },
68
69
  "release-it": {
69
70
  "github": {