@karmaniverous/entity-manager 5.0.9 → 6.0.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/README.md +17 -437
- package/dist/cjs/_virtual/_commonjsHelpers.js +7 -0
- package/dist/cjs/_virtual/lz-string.js +5 -0
- package/dist/cjs/node_modules/@karmaniverous/entity-tools/dist/mjs/Nil.js +14 -0
- package/dist/cjs/node_modules/@karmaniverous/entity-tools/dist/mjs/defaultTranscodes.js +94 -0
- package/dist/cjs/node_modules/@karmaniverous/entity-tools/dist/mjs/sort.js +52 -0
- package/dist/cjs/node_modules/lz-string/libs/lz-string.js +517 -0
- package/dist/cjs/node_modules/radash/dist/esm/array.js +90 -0
- package/dist/cjs/node_modules/radash/dist/esm/async.js +62 -0
- package/dist/cjs/node_modules/radash/dist/esm/object.js +25 -0
- package/dist/cjs/node_modules/radash/dist/esm/typed.js +35 -0
- package/dist/cjs/node_modules/string-hash/index.js +23 -0
- package/dist/cjs/node_modules/tslib/tslib.es6.js +39 -0
- package/dist/cjs/node_modules/zod/lib/index.js +4342 -0
- package/dist/cjs/src/EntityManager.js +93 -0
- package/dist/cjs/src/ParsedConfig.js +227 -0
- package/dist/cjs/src/addKeys.js +47 -0
- package/dist/cjs/src/decodeEntityElement.js +37 -0
- package/dist/cjs/src/decodeGeneratedProperty.js +48 -0
- package/dist/cjs/src/dehydrateIndexItem.js +52 -0
- package/dist/cjs/src/dehydratePageKeyMap.js +75 -0
- package/dist/cjs/src/encodeEntityElement.js +36 -0
- package/dist/cjs/src/encodeGeneratedProperty.js +44 -0
- package/dist/cjs/src/getHashKeySpace.js +41 -0
- package/dist/cjs/src/getShardBump.js +24 -0
- package/dist/cjs/src/index.js +7 -0
- package/dist/cjs/src/query.js +104 -0
- package/dist/cjs/src/rehydrateIndexItem.js +48 -0
- package/dist/cjs/src/rehydratePageKeyMap.js +66 -0
- package/dist/cjs/src/removeKeys.js +33 -0
- package/dist/cjs/src/unwrapIndex.js +40 -0
- package/dist/cjs/src/updateItemHashKey.js +55 -0
- package/dist/cjs/src/updateItemRangeKey.js +45 -0
- package/dist/cjs/src/validateEntityGeneratedProperty.js +29 -0
- package/dist/cjs/src/validateEntityIndexToken.js +22 -0
- package/dist/cjs/src/validateEntityToken.js +17 -0
- package/dist/index.d.cts +826 -0
- package/dist/index.d.mts +826 -0
- package/dist/index.d.ts +826 -0
- package/dist/mjs/_virtual/_commonjsHelpers.js +5 -0
- package/dist/mjs/_virtual/lz-string.js +3 -0
- package/dist/mjs/node_modules/@karmaniverous/entity-tools/dist/mjs/Nil.js +12 -0
- package/dist/mjs/node_modules/@karmaniverous/entity-tools/dist/mjs/defaultTranscodes.js +92 -0
- package/dist/mjs/node_modules/@karmaniverous/entity-tools/dist/mjs/sort.js +50 -0
- package/dist/mjs/node_modules/lz-string/libs/lz-string.js +515 -0
- package/dist/mjs/node_modules/radash/dist/esm/array.js +80 -0
- package/dist/mjs/node_modules/radash/dist/esm/async.js +58 -0
- package/dist/mjs/node_modules/radash/dist/esm/object.js +22 -0
- package/dist/mjs/node_modules/radash/dist/esm/typed.js +28 -0
- package/dist/mjs/node_modules/string-hash/index.js +21 -0
- package/dist/mjs/node_modules/tslib/tslib.es6.js +36 -0
- package/dist/mjs/node_modules/zod/lib/index.js +4233 -0
- package/dist/mjs/src/EntityManager.js +91 -0
- package/dist/mjs/src/ParsedConfig.js +225 -0
- package/dist/mjs/src/addKeys.js +45 -0
- package/dist/mjs/src/decodeEntityElement.js +35 -0
- package/dist/mjs/src/decodeGeneratedProperty.js +46 -0
- package/dist/mjs/src/dehydrateIndexItem.js +50 -0
- package/dist/mjs/src/dehydratePageKeyMap.js +73 -0
- package/dist/mjs/src/encodeEntityElement.js +34 -0
- package/dist/mjs/src/encodeGeneratedProperty.js +42 -0
- package/dist/mjs/src/getHashKeySpace.js +39 -0
- package/dist/mjs/src/getShardBump.js +22 -0
- package/dist/mjs/src/index.js +1 -0
- package/dist/mjs/src/query.js +102 -0
- package/dist/mjs/src/rehydrateIndexItem.js +46 -0
- package/dist/mjs/src/rehydratePageKeyMap.js +64 -0
- package/dist/mjs/src/removeKeys.js +31 -0
- package/dist/mjs/src/unwrapIndex.js +38 -0
- package/dist/mjs/src/updateItemHashKey.js +53 -0
- package/dist/mjs/src/updateItemRangeKey.js +43 -0
- package/dist/mjs/src/validateEntityGeneratedProperty.js +27 -0
- package/dist/mjs/src/validateEntityIndexToken.js +20 -0
- package/dist/mjs/src/validateEntityToken.js +15 -0
- package/package.json +107 -58
- package/.env +0 -1
- package/.github/FUNDING.yml +0 -3
- package/dist/default/lib/EntityManager/EntityManager.js +0 -423
- package/dist/default/lib/EntityManager/PrivateEntityManager.js +0 -421
- package/dist/default/lib/index.js +0 -12
- package/dist/package.json +0 -3
- package/lib/EntityManager/EntityManager.js +0 -396
- package/lib/EntityManager/PrivateEntityManager.js +0 -436
- package/lib/index.js +0 -1
package/README.md
CHANGED
|
@@ -1,455 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
The DynamoDB [single-table design pattern](https://aws.amazon.com/blogs/compute/creating-a-single-table-design-with-amazon-dynamodb/) requires highly-structured key attributes that...
|
|
4
|
-
|
|
5
|
-
- Serve as Global Secondary Index (GSI) keys supporting entity-specific queries.
|
|
6
|
-
- Support sharding across multiple partitions.
|
|
7
|
-
|
|
8
|
-
Entity relationships in a traditional RDBMS are expressed in a set of Foreign Key constraints that map straightforwardly into an entity-relationship diagram (ERD). Keeping implementation and design in sync is painless, and scaling is a matter of hardware and server configuration.
|
|
9
|
-
|
|
10
|
-
All of this takes place at design time. At run time, database structure is more or less fixed.
|
|
11
|
-
|
|
12
|
-
GSIs on a NoSQL platform like DynamoDB are declared as a matter of design-time configuration. Everything ELSE happens at run time, encoded into those critical key attributes at every data write.
|
|
13
|
-
|
|
14
|
-
The logic to accomplish this can be both complex and fragmented across the implementation. Unlike a set of RDBMS foreign key constraints that map one-for-one to the lines in an ERD, the structure of this logic is difficult to visualize and rarely collected in one place.
|
|
15
|
-
|
|
16
|
-
This package shifts the implementation of DynamoDB structure & scaling from logic to configuration. It features:
|
|
17
|
-
|
|
18
|
-
- A simple, declarative configuration format that permits articulation of every index key for every entity, all in one place.
|
|
19
|
-
|
|
20
|
-
- A rational approach to partition sharding that can be encoded directly into table & GSI hash keys and permits scaling over time.
|
|
21
|
-
|
|
22
|
-
- High-performance decoration of entity-specific data objects with configured, shard-aware index values.
|
|
23
|
-
|
|
24
|
-
- High-performance transformation of an entity-specific data object into a key space permitting query of related objects across all partition shards.
|
|
25
|
-
|
|
26
|
-
## Installation
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
npm install @karmaniverous/entity-manager
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Configuration
|
|
33
|
-
|
|
34
|
-
The `entity-manager` configuration object describes each entity's structured keys and sharding strategy. For broadest compatibility, express this object as a named export from an ES6 module like this:
|
|
1
|
+
<!-- TYPEDOC_EXCLUDE -->
|
|
35
2
|
|
|
36
|
-
|
|
37
|
-
// entityConfig.js
|
|
3
|
+
> [API Documentation](https://docs.karmanivero.us/entity-manager/) • [CHANGELOG](https://github.com/karmaniverous/entity-manager/tree/main/CHANGELOG.md)
|
|
38
4
|
|
|
39
|
-
|
|
40
|
-
// sn2e - Template literal returns empty string if any expression is nil.
|
|
41
|
-
// sn2u - Template literal returns undefined if any expression is nil.
|
|
42
|
-
import { sn2e, sn2u } from '@karmaniverous/tagged-templates';
|
|
5
|
+
<!-- /TYPEDOC_EXCLUDE -->
|
|
43
6
|
|
|
44
|
-
|
|
45
|
-
export const config = {
|
|
46
|
-
entities: {
|
|
47
|
-
// Repeat this structure for each entity type. Valid keys match /\w+/
|
|
48
|
-
transaction: {
|
|
49
|
-
// Each property of the keys object defines a structured index key for
|
|
50
|
-
// this entity. Each property value is a function expects an entity
|
|
51
|
-
// object as its one argument. In the examples below, entity attributes
|
|
52
|
-
// are destructured in the function declaration and the return value is
|
|
53
|
-
// expressed as a template literal.
|
|
54
|
-
keys: {
|
|
55
|
-
// Table HASH key. Note the optional shardId.
|
|
56
|
-
entityPK: ({ shardId }) => `transaction${sn2e`!${shardId}`}`,
|
|
57
|
-
|
|
58
|
-
// Table RANGE key.
|
|
59
|
-
transactionSK: ({ timestamp, transactionId }) =>
|
|
60
|
-
sn2u`timestamp#${timestamp}|transactionId#${transactionId}`,
|
|
61
|
-
|
|
62
|
-
// merchants GSI HASH key. Note the optional shardId.
|
|
63
|
-
merchantPK: ({ merchantId, shardId }) =>
|
|
64
|
-
sn2u`merchantId#${merchantId}|transaction${sn2e`!${shardId}`}`,
|
|
65
|
-
|
|
66
|
-
// merchants GSI RANGE key.
|
|
67
|
-
merchantSK: ({ methodId, timestamp, transactionId }) =>
|
|
68
|
-
sn2u`timestamp#${timestamp}|methodId#${methodId}|transactionId#${transactionId}`,
|
|
69
|
-
|
|
70
|
-
// methods GSI HASH key. Note the optional shardId.
|
|
71
|
-
methodPK: ({ methodId, shardId }) =>
|
|
72
|
-
sn2u`method#${methodId}|transaction${sn2e`!${shardId}`}`,
|
|
73
|
-
|
|
74
|
-
// methods GSI RANGE key.
|
|
75
|
-
methodSK: ({ merchantId, timestamp, transactionId }) =>
|
|
76
|
-
sn2u`timestamp#${timestamp}|merchantId#${merchantId}|transactionId#${transactionId}`,
|
|
77
|
-
|
|
78
|
-
// users GSI HASH key. Note the optional shardId.
|
|
79
|
-
userPK: ({ shardId, userId }) =>
|
|
80
|
-
sn2u`user#${userId}|transaction${sn2e`!${shardId}`}`,
|
|
81
|
-
|
|
82
|
-
// users GSI RANGE key.
|
|
83
|
-
userSK: ({ merchantId, timestamp, transactionId }) =>
|
|
84
|
-
sn2u`timestamp#${timestamp}|merchantId#${merchantId}|transactionId#${transactionId}`,
|
|
85
|
-
},
|
|
86
|
-
|
|
87
|
-
// The sharding configuration for this entity.
|
|
88
|
-
sharding: {
|
|
89
|
-
// Default number of shard key characters (0 means no shard key).
|
|
90
|
-
nibbles: 0,
|
|
91
|
-
|
|
92
|
-
// Bits represented by each shard key character, so 3 bits means an
|
|
93
|
-
// octal shard key. Running with no shard key produces a single shard.
|
|
94
|
-
// The first shard key character yields another 8 shards, and the second
|
|
95
|
-
// yields an additional 64, for an eventual total of 73.
|
|
96
|
-
nibbleBits: 3,
|
|
97
|
-
|
|
98
|
-
// Scheduled increases in shard key length. Keys are expressed as
|
|
99
|
-
// millisecond UTC timestamps. In production, these should not be
|
|
100
|
-
// updated after they go into effect.
|
|
101
|
-
bumps: {
|
|
102
|
-
1676874972686: 1,
|
|
103
|
-
1708411134487: 2,
|
|
104
|
-
},
|
|
105
|
-
|
|
106
|
-
// Extracts entity key to be hashed for shard key.
|
|
107
|
-
entityKey: ({ transactionId }) => transactionId,
|
|
108
|
-
|
|
109
|
-
// Extracts timestamp to determine shard key length.
|
|
110
|
-
timestamp: ({ timestamp }) => timestamp,
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
// Will be added to every entity object as appropriate. Pick a value that
|
|
115
|
-
// won't collide with entity data!
|
|
116
|
-
shardKeyToken: 'shardId',
|
|
117
|
-
};
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
## Usage
|
|
121
|
-
|
|
122
|
-
```js
|
|
123
|
-
// Import an optional logger.
|
|
124
|
-
import { Logger } from '@karmaniverous/edge-logger';
|
|
125
|
-
const logger = new Logger('debug');
|
|
126
|
-
|
|
127
|
-
// Import EntityManager & config object.
|
|
128
|
-
import { EntityManager } from '@karmaniverous/entity-manager';
|
|
129
|
-
import { config } from './entityConfig.js';
|
|
7
|
+
# entity-manager
|
|
130
8
|
|
|
131
|
-
|
|
132
|
-
const entityManager = new EntityManager({ config, logger });
|
|
9
|
+
**EntityManager implements rational indexing & cross-shard querying at scale in your NoSQL database so you can focus on your application logic.**
|
|
133
10
|
|
|
134
|
-
|
|
135
|
-
const transaction = {
|
|
136
|
-
methodId: 'methodIdValue',
|
|
137
|
-
merchantId: 'merchantIdValue',
|
|
138
|
-
timestamp: 1676869312851,
|
|
139
|
-
transactionId: 'transactionIdValue',
|
|
140
|
-
userId: 'userIdValue',
|
|
141
|
-
};
|
|
11
|
+
I've just released a full Typescript refactor. Everything works beatuifully, but I'm still fleshing out [the documentation](https://karmanivero.us/projects/entity-manager/intro/).
|
|
142
12
|
|
|
143
|
-
|
|
144
|
-
entityManager.addKeys(transaction);
|
|
13
|
+
If you have any questions, please [start a discussion](https://github.com/karmaniverous/entity-manager/discussions). Otherwise stay tuned!
|
|
145
14
|
|
|
146
|
-
|
|
147
|
-
// debug: {
|
|
148
|
-
// debug: "methodId": "methodIdValue",
|
|
149
|
-
// debug: "merchantId": "merchantIdValue",
|
|
150
|
-
// debug: "timestamp": 1676869312851,
|
|
151
|
-
// debug: "transactionId": "transactionIdValue",
|
|
152
|
-
// debug: "userId": "userIdValue"
|
|
153
|
-
// debug: }
|
|
154
|
-
// debug: generated shard key '7' for transaction id 'transactionIdValue' at timestamp 1676869312851.
|
|
155
|
-
// debug:
|
|
156
|
-
// debug: done
|
|
157
|
-
// debug: {
|
|
158
|
-
// debug: "methodId": "methodIdValue",
|
|
159
|
-
// debug: "merchantId": "merchantIdValue",
|
|
160
|
-
// debug: "timestamp": 1676869312851,
|
|
161
|
-
// debug: "transactionId": "transactionIdValue",
|
|
162
|
-
// debug: "userId": "userIdValue",
|
|
163
|
-
// debug: "shardId": "7",
|
|
164
|
-
// debug: "entityPK": "transaction!7",
|
|
165
|
-
// debug: "transactionSK": "timestamp#1676869312851|transactionId#transactionIdValue",
|
|
166
|
-
// debug: "merchantPK": "merchantId#merchantIdValue|transaction!7",
|
|
167
|
-
// debug: "merchantSK": "timestamp#1676869312851|methodId#methodIdValue|transactionId#transactionIdValue",
|
|
168
|
-
// debug: "methodPK": "method#methodIdValue|transaction!7",
|
|
169
|
-
// debug: "methodSK": "timestamp#1676869312851|merchantId#merchantIdValue|transactionId#transactionIdValue",
|
|
170
|
-
// debug: "userPK": "user#userIdValue|transaction!7",
|
|
171
|
-
// debug: "userSK": "timestamp#1676869312851|merchantId#merchantIdValue|transactionId#transactionIdValue"
|
|
172
|
-
// debug: }
|
|
15
|
+
## Why?
|
|
173
16
|
|
|
174
|
-
|
|
175
|
-
entityManager.getKeySpace('transaction', transaction, 'userPK', 1686874972686);
|
|
17
|
+
Traditional relational database systems like MySQL or SQL Server implement indexing & scaling strategies at a platform level based on schemas defined at design time.
|
|
176
18
|
|
|
177
|
-
|
|
178
|
-
// debug: {
|
|
179
|
-
// debug: "methodId": "methodIdValue",
|
|
180
|
-
// debug: "merchantId": "merchantIdValue",
|
|
181
|
-
// debug: "timestamp": 1676876779118,
|
|
182
|
-
// debug: "transactionId": "transactionIdValue",
|
|
183
|
-
// debug: "userId": "userIdValue"
|
|
184
|
-
// debug: }
|
|
185
|
-
// debug:
|
|
186
|
-
// debug: done
|
|
187
|
-
// debug: [
|
|
188
|
-
// debug: "user#userIdValue|transaction",
|
|
189
|
-
// debug: "user#userIdValue|transaction!0",
|
|
190
|
-
// debug: "user#userIdValue|transaction!1",
|
|
191
|
-
// debug: "user#userIdValue|transaction!2",
|
|
192
|
-
// debug: "user#userIdValue|transaction!3",
|
|
193
|
-
// debug: "user#userIdValue|transaction!4",
|
|
194
|
-
// debug: "user#userIdValue|transaction!5",
|
|
195
|
-
// debug: "user#userIdValue|transaction!6",
|
|
196
|
-
// debug: "user#userIdValue|transaction!7"
|
|
197
|
-
// debug: ]
|
|
198
|
-
```
|
|
19
|
+
NoSQL platforms like DynamoDB offer far better performance at scale, but structured index & shard keys must be defined as data elements and exploited by application logic in data retrieval & cross-shard queries. **They shift the burden of complexity from the database platform to the developer!**
|
|
199
20
|
|
|
200
|
-
|
|
21
|
+
EntityManager encapsulates a provider-agnostic, highly opinionated approach to the [single-table design pattern](https://aws.amazon.com/blogs/compute/creating-a-single-table-design-with-amazon-dynamodb/).
|
|
201
22
|
|
|
202
|
-
|
|
23
|
+
With EntityManager, you can:
|
|
203
24
|
|
|
204
|
-
|
|
25
|
+
- Define related data entities & structured keys wth a simple, declarative configuration format.
|
|
205
26
|
|
|
206
|
-
|
|
27
|
+
- Specify a partition sharding strategy that maximizes query performance while permitting planned, staged scaling over time.
|
|
207
28
|
|
|
208
|
-
|
|
29
|
+
- Add or remove structured index keys from entity data objects with a single method call.
|
|
209
30
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
# API Documentation
|
|
31
|
+
- Perform paged, cross-shard, multi-index queries with a single method call.
|
|
213
32
|
|
|
214
|
-
<a name="module_entity-manager"></a>
|
|
215
|
-
|
|
216
|
-
## entity-manager
|
|
217
|
-
|
|
218
|
-
* [entity-manager](#module_entity-manager)
|
|
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
|
-
* [.calcShardKey(entityToken, item)](#module_entity-manager.EntityManager+calcShardKey) ⇒ <code>string</code>
|
|
224
|
-
* [.dehydrateIndex(entityToken, indexToken, index, [delimiter])](#module_entity-manager.EntityManager+dehydrateIndex) ⇒ <code>string</code>
|
|
225
|
-
* [.getKey(entityToken, keyToken)](#module_entity-manager.EntityManager+getKey) ⇒ <code>object</code>
|
|
226
|
-
* [.getKeySpace(entityToken, keyToken, item, timestamp)](#module_entity-manager.EntityManager+getKeySpace) ⇒ <code>Array.<string></code>
|
|
227
|
-
* [.query(options)](#module_entity-manager.EntityManager+query) ⇒ <code>Promise.<ShardedQueryResult></code>
|
|
228
|
-
* [.rehydrateIndex(entityToken, indexToken, value, [delimiter])](#module_entity-manager.EntityManager+rehydrateIndex) ⇒ <code>object</code>
|
|
229
|
-
* [.removeKeys(entityToken, item)](#module_entity-manager.EntityManager+removeKeys) ⇒ <code>object</code>
|
|
230
|
-
* _inner_
|
|
231
|
-
* [~ShardQueryResult](#module_entity-manager..ShardQueryResult) : <code>object</code>
|
|
232
|
-
* [~ShardQueryFunction](#module_entity-manager..ShardQueryFunction) ⇒ <code>Promise.<ShardQueryResult></code>
|
|
233
|
-
* [~ShardedQueryResult](#module_entity-manager..ShardedQueryResult) : <code>object</code>
|
|
234
|
-
|
|
235
|
-
<a name="module_entity-manager.EntityManager"></a>
|
|
236
|
-
|
|
237
|
-
### entity-manager.EntityManager
|
|
238
|
-
Manage DynamoDb entities.
|
|
239
|
-
|
|
240
|
-
**Kind**: static class of [<code>entity-manager</code>](#module_entity-manager)
|
|
241
|
-
|
|
242
|
-
* [.EntityManager](#module_entity-manager.EntityManager)
|
|
243
|
-
* [new exports.EntityManager(options)](#new_module_entity-manager.EntityManager_new)
|
|
244
|
-
* [.addKeys(entityToken, item, [overwrite])](#module_entity-manager.EntityManager+addKeys) ⇒ <code>object</code>
|
|
245
|
-
* [.calcShardKey(entityToken, item)](#module_entity-manager.EntityManager+calcShardKey) ⇒ <code>string</code>
|
|
246
|
-
* [.dehydrateIndex(entityToken, indexToken, index, [delimiter])](#module_entity-manager.EntityManager+dehydrateIndex) ⇒ <code>string</code>
|
|
247
|
-
* [.getKey(entityToken, keyToken)](#module_entity-manager.EntityManager+getKey) ⇒ <code>object</code>
|
|
248
|
-
* [.getKeySpace(entityToken, keyToken, item, timestamp)](#module_entity-manager.EntityManager+getKeySpace) ⇒ <code>Array.<string></code>
|
|
249
|
-
* [.query(options)](#module_entity-manager.EntityManager+query) ⇒ <code>Promise.<ShardedQueryResult></code>
|
|
250
|
-
* [.rehydrateIndex(entityToken, indexToken, value, [delimiter])](#module_entity-manager.EntityManager+rehydrateIndex) ⇒ <code>object</code>
|
|
251
|
-
* [.removeKeys(entityToken, item)](#module_entity-manager.EntityManager+removeKeys) ⇒ <code>object</code>
|
|
252
|
-
|
|
253
|
-
<a name="new_module_entity-manager.EntityManager_new"></a>
|
|
254
|
-
|
|
255
|
-
#### new exports.EntityManager(options)
|
|
256
|
-
Create an EntityManager instance.
|
|
257
|
-
|
|
258
|
-
**Returns**: <code>EntityManager</code> - EntityManager instance.
|
|
259
|
-
**Throws**:
|
|
260
|
-
|
|
261
|
-
- <code>Error</code> If config is invalid.
|
|
262
|
-
- <code>Error</code> If logger is invalid.
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
| Param | Type | Description |
|
|
266
|
-
| --- | --- | --- |
|
|
267
|
-
| options | <code>object</code> | Options object. |
|
|
268
|
-
| [options.config] | <code>object</code> | EntityManager configuration object (see [README](https://github.com/karmaniverous/entity-manager#configuration) for a breakdown). |
|
|
269
|
-
| [options.logger] | <code>object</code> | Logger instance (defaults to console, must support error & debug methods). |
|
|
270
|
-
|
|
271
|
-
<a name="module_entity-manager.EntityManager+addKeys"></a>
|
|
272
|
-
|
|
273
|
-
#### entityManager.addKeys(entityToken, item, [overwrite]) ⇒ <code>object</code>
|
|
274
|
-
Add sharded keys to an entity item. Does not mutate original item.
|
|
275
|
-
|
|
276
|
-
**Kind**: instance method of [<code>EntityManager</code>](#module_entity-manager.EntityManager)
|
|
277
|
-
**Returns**: <code>object</code> - Decorated entity item.
|
|
278
|
-
**Throws**:
|
|
279
|
-
|
|
280
|
-
- <code>Error</code> If entityToken is invalid.
|
|
281
|
-
- <code>Error</code> If item is invalid.
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
| Param | Type | Default | Description |
|
|
285
|
-
| --- | --- | --- | --- |
|
|
286
|
-
| entityToken | <code>string</code> | | Entity token. |
|
|
287
|
-
| item | <code>object</code> | | Entity item. |
|
|
288
|
-
| [overwrite] | <code>boolean</code> | <code>false</code> | Overwrite existing properties. |
|
|
289
|
-
|
|
290
|
-
<a name="module_entity-manager.EntityManager+calcShardKey"></a>
|
|
291
|
-
|
|
292
|
-
#### entityManager.calcShardKey(entityToken, item) ⇒ <code>string</code>
|
|
293
|
-
Calculated the shard key for an entity item.
|
|
294
|
-
|
|
295
|
-
**Kind**: instance method of [<code>EntityManager</code>](#module_entity-manager.EntityManager)
|
|
296
|
-
**Returns**: <code>string</code> - Shard key.
|
|
297
|
-
|
|
298
|
-
| Param | Type | Description |
|
|
299
|
-
| --- | --- | --- |
|
|
300
|
-
| entityToken | <code>string</code> | Entity token. |
|
|
301
|
-
| item | <code>object</code> | Entity item. |
|
|
302
|
-
|
|
303
|
-
<a name="module_entity-manager.EntityManager+dehydrateIndex"></a>
|
|
304
|
-
|
|
305
|
-
#### entityManager.dehydrateIndex(entityToken, indexToken, index, [delimiter]) ⇒ <code>string</code>
|
|
306
|
-
Condense an index object into a delimited string.
|
|
307
|
-
|
|
308
|
-
**Kind**: instance method of [<code>EntityManager</code>](#module_entity-manager.EntityManager)
|
|
309
|
-
**Returns**: <code>string</code> - Dehydrated index.
|
|
310
|
-
**Throws**:
|
|
311
|
-
|
|
312
|
-
- <code>Error</code> If entityToken is invalid.
|
|
313
|
-
- <code>Error</code> If indexToken is invalid.
|
|
314
|
-
- <code>Error</code> If item is invalid.
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
| Param | Type | Default | Description |
|
|
318
|
-
| --- | --- | --- | --- |
|
|
319
|
-
| entityToken | <code>string</code> | | Entity token. |
|
|
320
|
-
| indexToken | <code>string</code> \| <code>Array.<string></code> | | Index token or array of key tokens. |
|
|
321
|
-
| index | <code>object</code> | | Index object. |
|
|
322
|
-
| [delimiter] | <code>string</code> | <code>"~"</code> | Delimiter. |
|
|
323
|
-
|
|
324
|
-
<a name="module_entity-manager.EntityManager+getKey"></a>
|
|
325
|
-
|
|
326
|
-
#### entityManager.getKey(entityToken, keyToken) ⇒ <code>object</code>
|
|
327
|
-
Return the config for a given entity key token.
|
|
328
|
-
|
|
329
|
-
**Kind**: instance method of [<code>EntityManager</code>](#module_entity-manager.EntityManager)
|
|
330
|
-
**Returns**: <code>object</code> - Entity key config.
|
|
331
|
-
|
|
332
|
-
| Param | Type | Description |
|
|
333
|
-
| --- | --- | --- |
|
|
334
|
-
| entityToken | <code>string</code> | Entity token. |
|
|
335
|
-
| keyToken | <code>string</code> | Key token. |
|
|
336
|
-
|
|
337
|
-
<a name="module_entity-manager.EntityManager+getKeySpace"></a>
|
|
338
|
-
|
|
339
|
-
#### entityManager.getKeySpace(entityToken, keyToken, item, timestamp) ⇒ <code>Array.<string></code>
|
|
340
|
-
Return an array of sharded keys valid for a given entity token & timestamp.
|
|
341
|
-
|
|
342
|
-
**Kind**: instance method of [<code>EntityManager</code>](#module_entity-manager.EntityManager)
|
|
343
|
-
**Returns**: <code>Array.<string></code> - Array of keys.
|
|
344
|
-
**Throws**:
|
|
345
|
-
|
|
346
|
-
- <code>Error</code> If entityToken is invalid.
|
|
347
|
-
- <code>Error</code> If item is invalid.
|
|
348
|
-
- <code>Error</code> If keyToken is invalid.
|
|
349
|
-
- <code>Error</code> If timestamp is invalid.
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
| Param | Type | Description |
|
|
353
|
-
| --- | --- | --- |
|
|
354
|
-
| entityToken | <code>string</code> | Entity token. |
|
|
355
|
-
| keyToken | <code>string</code> | Key token. |
|
|
356
|
-
| item | <code>object</code> | Entity item sufficiently populated to generate property keyToken. |
|
|
357
|
-
| timestamp | <code>number</code> | Timestamp. |
|
|
358
|
-
|
|
359
|
-
<a name="module_entity-manager.EntityManager+query"></a>
|
|
360
|
-
|
|
361
|
-
#### entityManager.query(options) ⇒ <code>Promise.<ShardedQueryResult></code>
|
|
362
|
-
Query an entity across shards.
|
|
363
|
-
|
|
364
|
-
**Kind**: instance method of [<code>EntityManager</code>](#module_entity-manager.EntityManager)
|
|
365
|
-
**Returns**: <code>Promise.<ShardedQueryResult></code> - Sharded query result.
|
|
366
|
-
|
|
367
|
-
| Param | Type | Description |
|
|
368
|
-
| --- | --- | --- |
|
|
369
|
-
| options | <code>object</code> | Query options. |
|
|
370
|
-
| options.entityToken | <code>string</code> | Entity token. |
|
|
371
|
-
| [options.keyToken] | <code>string</code> | Key token. |
|
|
372
|
-
| [options.item] | <code>object</code> | Entity item sufficiently populated to generate property keyToken. |
|
|
373
|
-
| options.shardQuery | <code>ShardQueryFunction</code> | Sharded query function. |
|
|
374
|
-
| [options.limit] | <code>number</code> | Request limit. |
|
|
375
|
-
| [options.pageKeys] | <code>object</code> | Map of shard page keys. |
|
|
376
|
-
| [options.pageSize] | <code>number</code> | Request page size. |
|
|
377
|
-
|
|
378
|
-
<a name="module_entity-manager.EntityManager+rehydrateIndex"></a>
|
|
379
|
-
|
|
380
|
-
#### entityManager.rehydrateIndex(entityToken, indexToken, value, [delimiter]) ⇒ <code>object</code>
|
|
381
|
-
Convert a delimited string into a named index key.
|
|
382
|
-
|
|
383
|
-
**Kind**: instance method of [<code>EntityManager</code>](#module_entity-manager.EntityManager)
|
|
384
|
-
**Returns**: <code>object</code> - Rehydrated index key.
|
|
385
|
-
**Throws**:
|
|
386
|
-
|
|
387
|
-
- <code>Error</code> If entityToken is invalid.
|
|
388
|
-
- <code>Error</code> If indexToken is invalid.
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
| Param | Type | Default | Description |
|
|
392
|
-
| --- | --- | --- | --- |
|
|
393
|
-
| entityToken | <code>string</code> | | Entity token. |
|
|
394
|
-
| indexToken | <code>string</code> \| <code>Array.<string></code> | | Index token or array of key tokens. |
|
|
395
|
-
| value | <code>string</code> | | Dehydrated index value. |
|
|
396
|
-
| [delimiter] | <code>string</code> | <code>"~"</code> | Delimiter. |
|
|
397
|
-
|
|
398
|
-
<a name="module_entity-manager.EntityManager+removeKeys"></a>
|
|
399
|
-
|
|
400
|
-
#### entityManager.removeKeys(entityToken, item) ⇒ <code>object</code>
|
|
401
|
-
Remove sharded keys from an entity item. Does not mutate original item.
|
|
402
|
-
|
|
403
|
-
**Kind**: instance method of [<code>EntityManager</code>](#module_entity-manager.EntityManager)
|
|
404
|
-
**Returns**: <code>object</code> - Stripped entity item.
|
|
405
|
-
**Throws**:
|
|
406
|
-
|
|
407
|
-
- <code>Error</code> If entityToken is invalid.
|
|
408
|
-
- <code>Error</code> If item is invalid.
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
| Param | Type | Description |
|
|
412
|
-
| --- | --- | --- |
|
|
413
|
-
| entityToken | <code>string</code> | Entity token. |
|
|
414
|
-
| item | <code>object</code> | Entity item. |
|
|
415
|
-
|
|
416
|
-
<a name="module_entity-manager..ShardQueryResult"></a>
|
|
417
|
-
|
|
418
|
-
### entity-manager~ShardQueryResult : <code>object</code>
|
|
419
|
-
**Kind**: inner typedef of [<code>entity-manager</code>](#module_entity-manager)
|
|
420
|
-
**Properties**
|
|
421
|
-
|
|
422
|
-
| Name | Type | Description |
|
|
423
|
-
| --- | --- | --- |
|
|
424
|
-
| items | <code>Array.<any></code> | Query result array. |
|
|
425
|
-
| pageKey | <code>\*</code> | Shard page key. |
|
|
426
|
-
|
|
427
|
-
<a name="module_entity-manager..ShardQueryFunction"></a>
|
|
428
|
-
|
|
429
|
-
### entity-manager~ShardQueryFunction ⇒ <code>Promise.<ShardQueryResult></code>
|
|
430
|
-
Shard query function
|
|
431
|
-
|
|
432
|
-
**Kind**: inner typedef of [<code>entity-manager</code>](#module_entity-manager)
|
|
433
|
-
**Returns**: <code>Promise.<ShardQueryResult></code> - Sharded query result.
|
|
434
|
-
|
|
435
|
-
| Param | Type | Description |
|
|
436
|
-
| --- | --- | --- |
|
|
437
|
-
| shardedKey | <code>string</code> | Sharded key. |
|
|
438
|
-
| [pageKey] | <code>\*</code> | Page key. |
|
|
439
|
-
| [limit] | <code>number</code> | Request limit. |
|
|
440
|
-
|
|
441
|
-
<a name="module_entity-manager..ShardedQueryResult"></a>
|
|
442
|
-
|
|
443
|
-
### entity-manager~ShardedQueryResult : <code>object</code>
|
|
444
|
-
**Kind**: inner typedef of [<code>entity-manager</code>](#module_entity-manager)
|
|
445
|
-
**Properties**
|
|
446
|
-
|
|
447
|
-
| Name | Type | Description |
|
|
448
|
-
| --- | --- | --- |
|
|
449
|
-
| items | <code>Array.<any></code> | Query result array. |
|
|
450
|
-
| pageKeys | <code>object</code> | Shard page keys. |
|
|
451
|
-
|
|
452
|
-
|
|
453
33
|
---
|
|
454
34
|
|
|
455
|
-
Built with ❤️ on Bali! Find more great tools & templates on [my GitHub Profile](https://github.com/karmaniverous).
|
|
35
|
+
Built for you with ❤️ on Bali! Find more great tools & templates on [my GitHub Profile](https://github.com/karmaniverous).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tests whether a value is {@link Nil | `Nil`}.
|
|
5
|
+
*
|
|
6
|
+
* @param value - Value.
|
|
7
|
+
*
|
|
8
|
+
* @returns true if `value` is `null` or `undefined`.
|
|
9
|
+
*
|
|
10
|
+
* @category Utilities
|
|
11
|
+
*/
|
|
12
|
+
const isNil = (value) => value === null || value === undefined;
|
|
13
|
+
|
|
14
|
+
exports.isNil = isNil;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var typed = require('../../../../radash/dist/esm/typed.js');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* {@link Transcodes | `Transcodes`} for {@link DefaultTranscodeMap | `DefaultTranscodeMap`}.
|
|
7
|
+
*
|
|
8
|
+
* See {@link https://github.com/karmaniverous/entity-tools/blob/main/src/defaultTranscodes.ts | implementation details}.
|
|
9
|
+
*
|
|
10
|
+
* @category Transcoding
|
|
11
|
+
*/
|
|
12
|
+
const defaultTranscodes = {
|
|
13
|
+
bigint20: {
|
|
14
|
+
encode: (value) => {
|
|
15
|
+
if (typeof value !== 'bigint' ||
|
|
16
|
+
value > 99999999999999999999n ||
|
|
17
|
+
value < -99999999999999999999n)
|
|
18
|
+
throw new Error('invalid bigint20');
|
|
19
|
+
const [prefix, abs] = value < 0n ? ['n', -value] : ['p', value];
|
|
20
|
+
return `${prefix}${abs.toString().padStart(20, '0')}`;
|
|
21
|
+
},
|
|
22
|
+
decode: (value) => {
|
|
23
|
+
if (!typed.isString(value) || !/^[np][0-9]{20}$/.test(value))
|
|
24
|
+
throw new Error('invalid encoded bigint20');
|
|
25
|
+
return (value.startsWith('n') ? -1n : 1n) * BigInt(value.slice(1));
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
boolean: {
|
|
29
|
+
encode: (value) => {
|
|
30
|
+
if (typeof value !== 'boolean')
|
|
31
|
+
throw new Error('invalid boolean');
|
|
32
|
+
return value.toString();
|
|
33
|
+
},
|
|
34
|
+
decode: (value) => {
|
|
35
|
+
if (!typed.isString(value) || !/^(true|false)$/.test(value))
|
|
36
|
+
throw new Error('invalid encoded boolean');
|
|
37
|
+
return value === 'true';
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
fix6: {
|
|
41
|
+
encode: (value) => {
|
|
42
|
+
if (!typed.isNumber(value) ||
|
|
43
|
+
value > Number.MAX_SAFE_INTEGER / 1000000 ||
|
|
44
|
+
value < Number.MIN_SAFE_INTEGER / 1000000)
|
|
45
|
+
throw new Error('invalid fix6');
|
|
46
|
+
const [prefix, abs] = value < 0 ? ['n', -value] : ['p', value];
|
|
47
|
+
return `${prefix}${abs.toFixed(6).padStart(17, '0')}`;
|
|
48
|
+
},
|
|
49
|
+
decode: (value) => {
|
|
50
|
+
if (!typed.isString(value) || !/^[np][0-9]{10}\.[0-9]{6}$/.test(value))
|
|
51
|
+
throw new Error('invalid encoded fix6');
|
|
52
|
+
return (value.startsWith('n') ? -1 : 1) * Number(value.slice(1));
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
int: {
|
|
56
|
+
encode: (value) => {
|
|
57
|
+
if (!typed.isInt(value))
|
|
58
|
+
throw new Error('invalid int');
|
|
59
|
+
const [prefix, abs] = value < 0 ? ['n', -value] : ['p', value];
|
|
60
|
+
return `${prefix}${abs.toString().padStart(16, '0')}`;
|
|
61
|
+
},
|
|
62
|
+
decode: (value) => {
|
|
63
|
+
if (!typed.isString(value) || !/^[np][0-9]{16}$/.test(value))
|
|
64
|
+
throw new Error('invalid encoded int');
|
|
65
|
+
return (value.startsWith('n') ? -1 : 1) * Number(value.slice(1));
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
string: {
|
|
69
|
+
encode: (value) => {
|
|
70
|
+
if (!typed.isString(value))
|
|
71
|
+
throw new Error('invalid string');
|
|
72
|
+
return value;
|
|
73
|
+
},
|
|
74
|
+
decode: (value) => {
|
|
75
|
+
if (!typed.isString(value))
|
|
76
|
+
throw new Error('invalid encoded string');
|
|
77
|
+
return value;
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
timestamp: {
|
|
81
|
+
encode: (value) => {
|
|
82
|
+
if (!typed.isInt(value) || value < 0 || value > 9999999999999)
|
|
83
|
+
throw new Error('invalid timestamp');
|
|
84
|
+
return value.toString().padStart(13, '0');
|
|
85
|
+
},
|
|
86
|
+
decode: (value) => {
|
|
87
|
+
if (!typed.isString(value) || !/^[0-9]{13}$/.test(value))
|
|
88
|
+
throw new Error('invalid encoded timestamp');
|
|
89
|
+
return Number(value);
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
exports.defaultTranscodes = defaultTranscodes;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var Nil = require('./Nil.js');
|
|
4
|
+
var typed = require('../../../../radash/dist/esm/typed.js');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Sort an array of `Item` progressively by `sort`.
|
|
8
|
+
*
|
|
9
|
+
* @typeParam Item - Item type. Must extend {@link Entity | `Entity`}.
|
|
10
|
+
*
|
|
11
|
+
* @param items - Array of `Item`.
|
|
12
|
+
* @param sortOrder - {@link SortOrder | `SortOrder`} array.
|
|
13
|
+
*
|
|
14
|
+
* @returns Sorted `items`.
|
|
15
|
+
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* Sorts `items` progresively by the elements of `sortOrder`, passing to the next element if values at the current element are equal.
|
|
18
|
+
*
|
|
19
|
+
* Comparisons are made as expected for `number`, `string`, and `bigint` types.
|
|
20
|
+
*
|
|
21
|
+
* `null` and `undefined` values are considered equivalent and less than any other value.
|
|
22
|
+
*
|
|
23
|
+
* Other types are compared by truthiness, where truthy is greater than falsy.
|
|
24
|
+
*
|
|
25
|
+
* @category Sort
|
|
26
|
+
*/
|
|
27
|
+
const sort = (items = [], sortOrder = []) => [...items].sort((a, b) => {
|
|
28
|
+
let comp = 0;
|
|
29
|
+
for (const { property, desc } of sortOrder) {
|
|
30
|
+
if (typed.isNumber(a[property]) && typed.isNumber(b[property]))
|
|
31
|
+
comp = a[property] - b[property];
|
|
32
|
+
else if (typed.isString(a[property]) && typed.isString(b[property]))
|
|
33
|
+
comp = a[property].localeCompare(b[property]);
|
|
34
|
+
else if (typeof a[property] === 'bigint' &&
|
|
35
|
+
typeof b[property] === 'bigint')
|
|
36
|
+
comp =
|
|
37
|
+
a[property] < b[property] ? -1 : a[property] > b[property] ? 1 : 0;
|
|
38
|
+
else if (!Nil.isNil(a[property]) && Nil.isNil(b[property]))
|
|
39
|
+
comp = 1;
|
|
40
|
+
else if (Nil.isNil(a[property]) && !Nil.isNil(b[property]))
|
|
41
|
+
comp = -1;
|
|
42
|
+
else if (a[property] && !b[property])
|
|
43
|
+
comp = 1;
|
|
44
|
+
else if (!a[property] && b[property])
|
|
45
|
+
comp = -1;
|
|
46
|
+
if (comp)
|
|
47
|
+
return desc ? -comp : comp;
|
|
48
|
+
}
|
|
49
|
+
return comp;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
exports.sort = sort;
|