@karmaniverous/entity-manager 6.13.3 → 6.14.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 +330 -16
- package/dist/cjs/{EntityManager.js → EntityManager/EntityManager.js} +18 -44
- package/dist/cjs/{ParsedConfig.js → EntityManager/ParsedConfig.js} +46 -55
- package/dist/cjs/{addKeys.js → EntityManager/addKeys.js} +1 -0
- package/dist/cjs/{decodeElement.js → EntityManager/decodeElement.js} +2 -1
- package/dist/cjs/{dehydratePageKeyMap.js → EntityManager/dehydratePageKeyMap.js} +3 -1
- package/dist/cjs/{encodeElement.js → EntityManager/encodeElement.js} +2 -2
- package/dist/cjs/{encodeGeneratedProperty.js → EntityManager/encodeGeneratedProperty.js} +1 -1
- package/dist/cjs/EntityManager/getPrimaryKey.js +25 -0
- package/dist/cjs/{rehydratePageKeyMap.js → EntityManager/rehydratePageKeyMap.js} +3 -1
- package/dist/cjs/{removeKeys.js → EntityManager/removeKeys.js} +10 -8
- package/dist/cjs/{updateItemHashKey.js → EntityManager/updateItemHashKey.js} +4 -1
- package/dist/cjs/index.js +5 -3
- package/dist/index.d.ts +109 -252
- package/dist/mjs/{EntityManager.js → EntityManager/EntityManager.js} +18 -44
- package/dist/mjs/{ParsedConfig.js → EntityManager/ParsedConfig.js} +46 -55
- package/dist/mjs/{addKeys.js → EntityManager/addKeys.js} +1 -0
- package/dist/mjs/{decodeElement.js → EntityManager/decodeElement.js} +2 -1
- package/dist/mjs/{dehydratePageKeyMap.js → EntityManager/dehydratePageKeyMap.js} +3 -1
- package/dist/mjs/{encodeElement.js → EntityManager/encodeElement.js} +2 -2
- package/dist/mjs/{encodeGeneratedProperty.js → EntityManager/encodeGeneratedProperty.js} +1 -1
- package/dist/mjs/EntityManager/getPrimaryKey.js +23 -0
- package/dist/mjs/{rehydratePageKeyMap.js → EntityManager/rehydratePageKeyMap.js} +3 -1
- package/dist/mjs/{removeKeys.js → EntityManager/removeKeys.js} +10 -8
- package/dist/mjs/{updateItemHashKey.js → EntityManager/updateItemHashKey.js} +4 -1
- package/dist/mjs/index.js +4 -3
- package/package.json +44 -45
- /package/dist/cjs/{BaseEntityClient.js → BaseEntityClient/BaseEntityClient.js} +0 -0
- /package/dist/cjs/{BaseQueryBuilder.js → BaseQueryBuilder/BaseQueryBuilder.js} +0 -0
- /package/dist/cjs/{decodeGeneratedProperty.js → EntityManager/decodeGeneratedProperty.js} +0 -0
- /package/dist/cjs/{dehydrateIndexItem.js → EntityManager/dehydrateIndexItem.js} +0 -0
- /package/dist/cjs/{findIndexToken.js → EntityManager/findIndexToken.js} +0 -0
- /package/dist/cjs/{getHashKeySpace.js → EntityManager/getHashKeySpace.js} +0 -0
- /package/dist/cjs/{getIndexComponents.js → EntityManager/getIndexComponents.js} +0 -0
- /package/dist/cjs/{getShardBump.js → EntityManager/getShardBump.js} +0 -0
- /package/dist/cjs/{query.js → EntityManager/query.js} +0 -0
- /package/dist/cjs/{rehydrateIndexItem.js → EntityManager/rehydrateIndexItem.js} +0 -0
- /package/dist/cjs/{unwrapIndex.js → EntityManager/unwrapIndex.js} +0 -0
- /package/dist/cjs/{updateItemRangeKey.js → EntityManager/updateItemRangeKey.js} +0 -0
- /package/dist/cjs/{validateEntityToken.js → EntityManager/validateEntityToken.js} +0 -0
- /package/dist/cjs/{validateGeneratedProperty.js → EntityManager/validateGeneratedProperty.js} +0 -0
- /package/dist/cjs/{validateIndexToken.js → EntityManager/validateIndexToken.js} +0 -0
- /package/dist/cjs/{validateTranscodedProperty.js → EntityManager/validateTranscodedProperty.js} +0 -0
- /package/dist/mjs/{BaseEntityClient.js → BaseEntityClient/BaseEntityClient.js} +0 -0
- /package/dist/mjs/{BaseQueryBuilder.js → BaseQueryBuilder/BaseQueryBuilder.js} +0 -0
- /package/dist/mjs/{decodeGeneratedProperty.js → EntityManager/decodeGeneratedProperty.js} +0 -0
- /package/dist/mjs/{dehydrateIndexItem.js → EntityManager/dehydrateIndexItem.js} +0 -0
- /package/dist/mjs/{findIndexToken.js → EntityManager/findIndexToken.js} +0 -0
- /package/dist/mjs/{getHashKeySpace.js → EntityManager/getHashKeySpace.js} +0 -0
- /package/dist/mjs/{getIndexComponents.js → EntityManager/getIndexComponents.js} +0 -0
- /package/dist/mjs/{getShardBump.js → EntityManager/getShardBump.js} +0 -0
- /package/dist/mjs/{query.js → EntityManager/query.js} +0 -0
- /package/dist/mjs/{rehydrateIndexItem.js → EntityManager/rehydrateIndexItem.js} +0 -0
- /package/dist/mjs/{unwrapIndex.js → EntityManager/unwrapIndex.js} +0 -0
- /package/dist/mjs/{updateItemRangeKey.js → EntityManager/updateItemRangeKey.js} +0 -0
- /package/dist/mjs/{validateEntityToken.js → EntityManager/validateEntityToken.js} +0 -0
- /package/dist/mjs/{validateGeneratedProperty.js → EntityManager/validateGeneratedProperty.js} +0 -0
- /package/dist/mjs/{validateIndexToken.js → EntityManager/validateIndexToken.js} +0 -0
- /package/dist/mjs/{validateTranscodedProperty.js → EntityManager/validateTranscodedProperty.js} +0 -0
package/README.md
CHANGED
|
@@ -1,34 +1,348 @@
|
|
|
1
|
-
|
|
1
|
+
# entity-manager
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@karmaniverous/entity-manager)  <!-- TYPEDOC_EXCLUDE --> [](https://docs.karmanivero.us/entity-manager) [](https://github.com/karmaniverous/entity-manager/tree/main/CHANGELOG.md)<!-- /TYPEDOC_EXCLUDE --> [](https://github.com/karmaniverous/entity-manager/tree/main/LICENSE.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
EntityManager implements rational indexing & cross-shard querying at scale in your NoSQL database so you can focus on your application logic.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
If you have any questions, please [start a discussion](https://github.com/karmaniverous/entity-manager/discussions). Otherwise stay tuned!
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## What is this?
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Entity Manager is a TypeScript-first library that applies a provider‑agnostic, highly opinionated single‑table design to your NoSQL data. It lets you:
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
- Define a global hash key and range key, plus additional generated properties used by your indexes.
|
|
14
|
+
- Encode/decode indexable elements via transcodes so strings sort like their original types.
|
|
15
|
+
- Configure a time‑based sharding strategy (shard bumps) that grows as you scale.
|
|
16
|
+
- Query across many shards and indexes in parallel through injected “shard query” functions — results are combined, de‑duplicated, sorted, and returned with a compact, dehydrated page key for the next request.
|
|
17
|
+
|
|
18
|
+
It is designed to work with stores like DynamoDB but keeps the orchestration provider‑neutral.
|
|
19
|
+
|
|
20
|
+
Key links:
|
|
21
|
+
|
|
22
|
+
- API: https://docs.karmanivero.us/entity-manager
|
|
23
|
+
- Requirements: see .stan/system/stan.requirements.md (authoritative for v6.14.0)
|
|
24
|
+
- Example test configuration: see test/config.ts
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- Global model for generated properties, indexes, and property transcodes
|
|
29
|
+
- generatedProperties.sharded and generatedProperties.unsharded
|
|
30
|
+
- Global indexes: indexToken → { hashKey, rangeKey, projections? }
|
|
31
|
+
- Global propertyTranscodes: property → transcodeName
|
|
32
|
+
- Deterministic sharding
|
|
33
|
+
- Time‑windowed shard bumps: { timestamp, charBits, chars }
|
|
34
|
+
- Full shard space assignment per bump (uses radix\*\*chars placeholders)
|
|
35
|
+
- Cross‑bump query enumeration over all applicable shards
|
|
36
|
+
- Page key dehydration/rehydration
|
|
37
|
+
- Compact string arrays and lz‑string compression for transport
|
|
38
|
+
- Rehydrate back to pageKey objects for each index+shard
|
|
39
|
+
- Provider‑agnostic parallel query orchestration
|
|
40
|
+
- Inject shard query functions for each index
|
|
41
|
+
- Parallel fan‑out with configurable throttle
|
|
42
|
+
- Combine, dedupe by unique property, and sort results
|
|
43
|
+
- Strong typing + runtime validation
|
|
44
|
+
- Zod‑validated config parsing
|
|
45
|
+
- Robust TypeScript surface for config, items, keys, queries
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install @karmaniverous/entity-manager
|
|
51
|
+
# optional: testing support used in the repo
|
|
52
|
+
npm install --save-dev @karmaniverous/mock-db
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
TypeScript is strongly recommended. The library will validate configuration at runtime for JavaScript users, but you lose compile‑time guarantees.
|
|
56
|
+
|
|
57
|
+
## Usage overview
|
|
58
|
+
|
|
59
|
+
The pattern has three parts:
|
|
60
|
+
|
|
61
|
+
1. Define your entity types and a config map
|
|
62
|
+
|
|
63
|
+
- Each entity type lists all properties that exist on your records.
|
|
64
|
+
- Entity‑level behaviors live in the config’s entities block (timestamp property, unique property, shard bumps).
|
|
65
|
+
- Generated properties, indexes, and transcodes are defined globally.
|
|
66
|
+
|
|
67
|
+
2. Create an EntityManager instance
|
|
68
|
+
|
|
69
|
+
- Pass your config (validated with Zod).
|
|
70
|
+
- Optionally inject a logger with debug/error methods (defaults to console).
|
|
71
|
+
|
|
72
|
+
3. Use EntityManager helpers
|
|
73
|
+
|
|
74
|
+
- addKeys / getPrimaryKey / removeKeys
|
|
75
|
+
- encodeGeneratedProperty / decodeGeneratedProperty
|
|
76
|
+
- getIndexComponents / unwrapIndex / dehydrateIndexItem / rehydrateIndexItem
|
|
77
|
+
- dehydratePageKeyMap / rehydratePageKeyMap
|
|
78
|
+
- query(options) to orchestrate cross‑shard multi‑index queries
|
|
79
|
+
|
|
80
|
+
## Quick start (TypeScript)
|
|
81
|
+
|
|
82
|
+
Below is a minimal end‑to‑end example showing shape and intent. It mirrors the current implementation’s global config model.
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import {
|
|
86
|
+
defaultTranscodes,
|
|
87
|
+
type ConfigMap,
|
|
88
|
+
} from '@karmaniverous/entity-manager';
|
|
89
|
+
import { EntityManager } from '@karmaniverous/entity-manager';
|
|
90
|
+
|
|
91
|
+
// 1) Entity definitions (Typescript types)
|
|
92
|
+
interface User {
|
|
93
|
+
userId: string; // unique property
|
|
94
|
+
created: number; // timestamp property
|
|
95
|
+
updated: number;
|
|
96
|
+
firstNameCanonical: string;
|
|
97
|
+
lastNameCanonical: string;
|
|
98
|
+
// Generated properties exist on stored items but are configured globally:
|
|
99
|
+
// e.g., firstNameRK (unsharded), lastNameRK (unsharded), userPK (sharded)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
type MyConfigMap = ConfigMap<{
|
|
103
|
+
EntityMap: { user: User };
|
|
104
|
+
HashKey: 'hashKey'; // defaults are 'hashKey' / 'rangeKey' if omitted
|
|
105
|
+
RangeKey: 'rangeKey';
|
|
106
|
+
ShardedKeys: 'userPK'; // token(s) for sharded generated properties
|
|
107
|
+
UnshardedKeys: 'firstNameRK' | 'lastNameRK';
|
|
108
|
+
TranscodedProperties:
|
|
109
|
+
| 'userId'
|
|
110
|
+
| 'created'
|
|
111
|
+
| 'updated'
|
|
112
|
+
| 'firstNameCanonical'
|
|
113
|
+
| 'lastNameCanonical';
|
|
114
|
+
}>;
|
|
115
|
+
|
|
116
|
+
// 2) Build a config with the global model
|
|
117
|
+
const now = Date.now();
|
|
118
|
+
const manager = new EntityManager<MyConfigMap>({
|
|
119
|
+
// Per-entity: unique + timestamp + shard schedule (+ optional defaults)
|
|
120
|
+
entities: {
|
|
121
|
+
user: {
|
|
122
|
+
uniqueProperty: 'userId',
|
|
123
|
+
timestampProperty: 'created',
|
|
124
|
+
shardBumps: [
|
|
125
|
+
// records with timestamp < now → effectively unsharded
|
|
126
|
+
{ timestamp: now, charBits: 1, chars: 0 },
|
|
127
|
+
// records with timestamp ≥ now → 1 char at radix 4 (2^2) gives 4 shards
|
|
128
|
+
{ timestamp: now, charBits: 2, chars: 1 },
|
|
129
|
+
],
|
|
130
|
+
// optional defaults (used by query if omitted in options)
|
|
131
|
+
defaultLimit: 10,
|
|
132
|
+
defaultPageSize: 10,
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
// Global generated properties (tokens → element lists)
|
|
137
|
+
generatedProperties: {
|
|
138
|
+
sharded: {
|
|
139
|
+
userPK: ['userId'], // atomic (all required)
|
|
140
|
+
},
|
|
141
|
+
unsharded: {
|
|
142
|
+
firstNameRK: ['firstNameCanonical', 'lastNameCanonical', 'created'],
|
|
143
|
+
lastNameRK: ['lastNameCanonical', 'firstNameCanonical', 'created'],
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
// Global key tokens
|
|
148
|
+
hashKey: 'hashKey',
|
|
149
|
+
rangeKey: 'rangeKey',
|
|
150
|
+
|
|
151
|
+
// Global indexes (hashKey, rangeKey must match allowed token sets)
|
|
152
|
+
indexes: {
|
|
153
|
+
created: { hashKey: 'hashKey', rangeKey: 'created' },
|
|
154
|
+
updated: { hashKey: 'hashKey', rangeKey: 'updated' },
|
|
155
|
+
firstName: { hashKey: 'hashKey', rangeKey: 'firstNameRK' },
|
|
156
|
+
lastName: { hashKey: 'hashKey', rangeKey: 'lastNameRK' },
|
|
157
|
+
userCreated: { hashKey: 'userPK', rangeKey: 'created' }, // sharded alt hash
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
// Transcode mapping for scalar/unsharded elements and properties
|
|
161
|
+
propertyTranscodes: {
|
|
162
|
+
userId: 'string',
|
|
163
|
+
created: 'timestamp',
|
|
164
|
+
updated: 'timestamp',
|
|
165
|
+
firstNameCanonical: 'string',
|
|
166
|
+
lastNameCanonical: 'string',
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
// Transcodes (can override/extend defaultTranscodes)
|
|
170
|
+
transcodes: defaultTranscodes,
|
|
171
|
+
|
|
172
|
+
// Delimiters and query throttle (defaults shown)
|
|
173
|
+
generatedKeyDelimiter: '|',
|
|
174
|
+
generatedValueDelimiter: '#',
|
|
175
|
+
shardKeyDelimiter: '!',
|
|
176
|
+
throttle: 10,
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Generate keys on items
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
// A partial item (no keys yet)
|
|
184
|
+
const user = {
|
|
185
|
+
userId: 'u123',
|
|
186
|
+
created: Date.now(),
|
|
187
|
+
updated: Date.now(),
|
|
188
|
+
firstNameCanonical: 'lee',
|
|
189
|
+
lastNameCanonical: 'zhang',
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// Add hashKey, rangeKey, and generated properties
|
|
193
|
+
const record = manager.addKeys('user', user); // returns EntityRecord<...>
|
|
194
|
+
|
|
195
|
+
// Get just the primary key
|
|
196
|
+
const keyOnly = manager.getPrimaryKey('user', user); // { hashKey, rangeKey }
|
|
197
|
+
|
|
198
|
+
// Remove generated keys from a stored record
|
|
199
|
+
const pruned = manager.removeKeys('user', record);
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Encode/decode generated property strings
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
// Encode an unsharded generated property (always returns a string)
|
|
206
|
+
const fn = manager.encodeGeneratedProperty('firstNameRK', record);
|
|
207
|
+
// e.g. "firstNameCanonical#lee|lastNameCanonical#zhang|created#000001711234567"
|
|
208
|
+
|
|
209
|
+
// Decode back into an object fragment
|
|
210
|
+
import { decodeGeneratedProperty } from '@karmaniverous/entity-manager';
|
|
211
|
+
const decoded = decodeGeneratedProperty(manager, fn); // { firstNameCanonical: 'lee', ... }
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Query across shards and indexes
|
|
215
|
+
|
|
216
|
+
Entity Manager relies on injected shard query functions to perform provider‑specific queries on each shard/index page. The library orchestrates:
|
|
217
|
+
|
|
218
|
+
- page‑key rehydration → parallel shard queries → de‑duplication and sorting → page‑key dehydration.
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
import type {
|
|
222
|
+
QueryOptions,
|
|
223
|
+
ShardQueryFunction,
|
|
224
|
+
} from '@karmaniverous/entity-manager';
|
|
225
|
+
|
|
226
|
+
// Example shard query using a made-up client (see @karmaniverous/mock-db in repo tests)
|
|
227
|
+
const firstNameQuery: ShardQueryFunction<MyConfigMap> = async (
|
|
228
|
+
hashKey,
|
|
229
|
+
pageKey,
|
|
230
|
+
pageSize,
|
|
231
|
+
) => {
|
|
232
|
+
// Return { count, items, pageKey? } for this shard+index page
|
|
233
|
+
// pageKey is a partial item object with necessary index components
|
|
234
|
+
// ... perform provider-specific work here ...
|
|
235
|
+
return { count: 0, items: [], pageKey };
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Invoke query with shardQueryMap
|
|
239
|
+
const result = await manager.query({
|
|
240
|
+
entityToken: 'user',
|
|
241
|
+
item: {}, // often used to supply elements for alternate hash keys
|
|
242
|
+
shardQueryMap: { firstName: firstNameQuery },
|
|
243
|
+
limit: 50,
|
|
244
|
+
pageSize: 10,
|
|
245
|
+
// optional: pageKeyMap: previousResult.pageKeyMap,
|
|
246
|
+
// optional: timestampFrom / timestampTo for shard-space windowing
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Notes:
|
|
251
|
+
|
|
252
|
+
- The result includes a compressed pageKeyMap string for the next call.
|
|
253
|
+
- Entity Manager enumerates the hash key space for the time window, rehydrates the prior page keys (if any), and fans out queries across all shard+index pairs in `shardQueryMap` (up to `throttle`).
|
|
254
|
+
- Items are deduplicated by the entity’s unique property and sorted by `sortOrder` (if provided).
|
|
255
|
+
|
|
256
|
+
## Configuration reference (current model, v6.14.0)
|
|
257
|
+
|
|
258
|
+
- entities: Record<entityToken, { timestampProperty, uniqueProperty, shardBumps?, defaultLimit?, defaultPageSize? }>
|
|
259
|
+
- generatedProperties:
|
|
260
|
+
- sharded: Record<ShardedKey, TranscodedProperties[]>
|
|
261
|
+
- unsharded: Record<UnshardedKey, TranscodedProperties[]>
|
|
262
|
+
- indexes: Record<indexToken, {
|
|
263
|
+
- hashKey: HashKey | ShardedKey
|
|
264
|
+
- rangeKey: RangeKey | UnshardedKey | TranscodedProperties
|
|
265
|
+
- projections?: string[]
|
|
266
|
+
}>
|
|
267
|
+
- propertyTranscodes: Record<TranscodedProperties, keyof TranscodeMap>
|
|
268
|
+
- transcodes: Record<transcodeName, { encode, decode }> (defaults to defaultTranscodes)
|
|
269
|
+
- hashKey: HashKey (e.g., 'hashKey')
|
|
270
|
+
- rangeKey: RangeKey (e.g., 'rangeKey')
|
|
271
|
+
- generatedKeyDelimiter: string (default '|', must match /\W+/)
|
|
272
|
+
- generatedValueDelimiter: string (default '#', must match /\W+/)
|
|
273
|
+
- shardKeyDelimiter: string (default '!', must match /\W+/)
|
|
274
|
+
- throttle: number (default 10)
|
|
275
|
+
|
|
276
|
+
Validation highlights:
|
|
277
|
+
|
|
278
|
+
- Delimiters must not contain each other.
|
|
279
|
+
- Keys and tokens must be mutually exclusive as required.
|
|
280
|
+
- Generated property element lists are non‑empty and have no duplicates.
|
|
281
|
+
- propertyTranscodes values must exist in transcodes.
|
|
282
|
+
- Index hashKey/rangeKey must use valid token sets.
|
|
283
|
+
- shardBumps are sorted, include a zero‑timestamp bump if missing, and chars must increase monotonically with timestamp.
|
|
284
|
+
|
|
285
|
+
Sharding:
|
|
286
|
+
|
|
287
|
+
- For assignment: a record always uses all placeholders for its applicable bump; suffix space is (2**charBits) ** chars.
|
|
288
|
+
- For queries: hash key space spans all bumps overlapping [timestampFrom, timestampTo].
|
|
289
|
+
|
|
290
|
+
## ESM / CJS
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
// ESM
|
|
294
|
+
import {
|
|
295
|
+
EntityManager,
|
|
296
|
+
defaultTranscodes,
|
|
297
|
+
} from '@karmaniverous/entity-manager';
|
|
298
|
+
|
|
299
|
+
// CJS
|
|
300
|
+
const {
|
|
301
|
+
EntityManager,
|
|
302
|
+
defaultTranscodes,
|
|
303
|
+
} = require('@karmaniverous/entity-manager');
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Logging
|
|
307
|
+
|
|
308
|
+
All helpers log debug context and error detail via the injected logger (defaults to `console`). In tests, you may supply a quiet logger:
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
const logger = { debug: () => undefined, error: console.error };
|
|
312
|
+
const manager = new EntityManager(config, logger);
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## Delimiter safety
|
|
316
|
+
|
|
317
|
+
Generated key/value delimiters and the shard key delimiter are used when composing strings:
|
|
14
318
|
|
|
15
|
-
|
|
319
|
+
- generatedKeyDelimiter: '|' (between pairs)
|
|
320
|
+
- generatedValueDelimiter: '#' (between key and value)
|
|
321
|
+
- shardKeyDelimiter: '!' (between entity token and shard suffix)
|
|
16
322
|
|
|
17
|
-
|
|
323
|
+
Your scalar property values used in generated properties should not include these delimiters. If they must, set custom delimiters (must match /\W+/ and not contain each other).
|
|
18
324
|
|
|
19
|
-
|
|
325
|
+
## Types you’ll use most
|
|
20
326
|
|
|
21
|
-
|
|
327
|
+
- ConfigMap<M>
|
|
328
|
+
- EntityItem<C>, EntityRecord<C>, EntityKey<C>, EntityToken<C>
|
|
329
|
+
- QueryOptions<C>, QueryResult<C>
|
|
330
|
+
- PageKey<C>, PageKeyMap<C>
|
|
331
|
+
- ShardQueryFunction<C>, ShardQueryMap<C>, ShardBump
|
|
22
332
|
|
|
23
|
-
|
|
333
|
+
See the full API: https://docs.karmanivero.us/entity-manager
|
|
24
334
|
|
|
25
|
-
|
|
335
|
+
## Scripts (repo)
|
|
26
336
|
|
|
27
|
-
-
|
|
337
|
+
- build: rollup outputs ESM/CJS + .d.ts
|
|
338
|
+
- test: vitest with coverage
|
|
339
|
+
- lint: ESLint (type‑aware) + Prettier integration
|
|
340
|
+
- docs: TypeDoc (links to external type docs for shared utility packages)
|
|
341
|
+
- typecheck: tsc + tsd (type‑level tests)
|
|
28
342
|
|
|
29
|
-
|
|
343
|
+
## License
|
|
30
344
|
|
|
31
|
-
|
|
345
|
+
BSD‑3‑Clause (see package.json).
|
|
32
346
|
|
|
33
347
|
---
|
|
34
348
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var tslib = require('tslib');
|
|
4
|
-
var radash = require('radash');
|
|
5
4
|
var addKeys = require('./addKeys.js');
|
|
6
5
|
var encodeGeneratedProperty = require('./encodeGeneratedProperty.js');
|
|
7
6
|
var findIndexToken = require('./findIndexToken.js');
|
|
7
|
+
var getPrimaryKey = require('./getPrimaryKey.js');
|
|
8
8
|
var ParsedConfig = require('./ParsedConfig.js');
|
|
9
9
|
var query = require('./query.js');
|
|
10
10
|
var removeKeys = require('./removeKeys.js');
|
|
@@ -52,59 +52,33 @@ class EntityManager {
|
|
|
52
52
|
/**
|
|
53
53
|
* Encode a generated property value. Returns a string or undefined if atomicity requirement of sharded properties not met.
|
|
54
54
|
*
|
|
55
|
-
* @param property - {@link Config
|
|
55
|
+
* @param property - {@link Config | Config} `generatedProperties` key.
|
|
56
56
|
* @param item - {@link EntityItem | `EntityItem`} object.
|
|
57
57
|
*
|
|
58
58
|
* @returns Encoded generated property value.
|
|
59
59
|
*
|
|
60
|
-
* @throws `Error` if `property` is not a {@link Config
|
|
60
|
+
* @throws `Error` if `property` is not a {@link Config | Config} `generatedProperties` key.
|
|
61
61
|
*/
|
|
62
62
|
encodeGeneratedProperty(property, item) {
|
|
63
63
|
return encodeGeneratedProperty.encodeGeneratedProperty(this, property, item);
|
|
64
64
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
* @param overwrite - Overwrite existing properties (default `false`).
|
|
71
|
-
*
|
|
72
|
-
* @returns {@link EntityRecord | `EntityRecord`} object with updated properties.
|
|
73
|
-
*
|
|
74
|
-
* @throws `Error` if `entityToken` is invalid.
|
|
75
|
-
*/
|
|
76
|
-
addKeys(entityToken, item, overwrite = false) {
|
|
77
|
-
return addKeys.addKeys(this, entityToken, item, overwrite);
|
|
65
|
+
addKeys(entityToken, i, overwrite = false) {
|
|
66
|
+
if (Array.isArray(i)) {
|
|
67
|
+
return i.map((item) => addKeys.addKeys(this, entityToken, item, overwrite));
|
|
68
|
+
}
|
|
69
|
+
return addKeys.addKeys(this, entityToken, i, overwrite);
|
|
78
70
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
* @param overwrite - Overwrite existing properties (default `false`).
|
|
85
|
-
*
|
|
86
|
-
* @returns {@link EntityKey | `EntityKey`} extracted from shallow clone of `item` with updated properties.
|
|
87
|
-
*
|
|
88
|
-
* @throws `Error` if `entityToken` is invalid.
|
|
89
|
-
*/
|
|
90
|
-
getPrimaryKey(entityToken, item, overwrite = false) {
|
|
91
|
-
const { hashKey, rangeKey } = this.config;
|
|
92
|
-
return radash.pick(!overwrite && item[hashKey] && item[rangeKey]
|
|
93
|
-
? item
|
|
94
|
-
: addKeys.addKeys(this, entityToken, item, overwrite), [this.config.hashKey, this.config.rangeKey]);
|
|
71
|
+
getPrimaryKey(entityToken, i, overwrite = false) {
|
|
72
|
+
if (Array.isArray(i)) {
|
|
73
|
+
return i.map((item) => getPrimaryKey.getPrimaryKey(this, entityToken, item, overwrite));
|
|
74
|
+
}
|
|
75
|
+
return getPrimaryKey.getPrimaryKey(this, entityToken, i, overwrite);
|
|
95
76
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
*
|
|
102
|
-
* @returns {@link EntityItem | `EntityItem`} with generated properties, hash key & range key removed.
|
|
103
|
-
*
|
|
104
|
-
* @throws `Error` if `entityToken` is invalid.
|
|
105
|
-
*/
|
|
106
|
-
removeKeys(entityToken, item) {
|
|
107
|
-
return removeKeys.removeKeys(this, entityToken, item);
|
|
77
|
+
removeKeys(entityToken, i) {
|
|
78
|
+
if (Array.isArray(i)) {
|
|
79
|
+
return i.map((item) => removeKeys.removeKeys(this, entityToken, item));
|
|
80
|
+
}
|
|
81
|
+
return removeKeys.removeKeys(this, entityToken, i);
|
|
108
82
|
}
|
|
109
83
|
/**
|
|
110
84
|
* Find an index token in a {@link Config | `Config`} object based on the index `hashKey` and `rangeKey`.
|