@karmaniverous/entity-manager 6.7.5 → 6.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/BaseEntityClient.js +1 -1
  3. package/dist/cjs/BaseQueryBuilder.js +5 -1
  4. package/dist/cjs/EntityManager.js +18 -9
  5. package/dist/cjs/ParsedConfig.js +143 -113
  6. package/dist/cjs/addKeys.js +6 -6
  7. package/dist/{mjs/decodeEntityElement.js → cjs/decodeElement.js} +10 -12
  8. package/dist/cjs/decodeGeneratedProperty.js +6 -10
  9. package/dist/cjs/dehydrateIndexItem.js +12 -11
  10. package/dist/cjs/dehydratePageKeyMap.js +10 -7
  11. package/dist/{mjs/encodeEntityElement.js → cjs/encodeElement.js} +8 -10
  12. package/dist/cjs/encodeGeneratedProperty.js +14 -18
  13. package/dist/cjs/getHashKeySpace.js +6 -6
  14. package/dist/cjs/getIndexComponents.js +6 -8
  15. package/dist/cjs/getShardBump.js +1 -1
  16. package/dist/cjs/index.js +0 -2
  17. package/dist/cjs/query.js +1 -1
  18. package/dist/cjs/rehydrateIndexItem.js +12 -10
  19. package/dist/cjs/rehydratePageKeyMap.js +18 -11
  20. package/dist/cjs/removeKeys.js +3 -2
  21. package/dist/cjs/unwrapIndex.js +25 -11
  22. package/dist/cjs/updateItemHashKey.js +5 -3
  23. package/dist/cjs/updateItemRangeKey.js +6 -4
  24. package/dist/cjs/validateGeneratedProperty.js +23 -0
  25. package/dist/cjs/validateIndexToken.js +16 -0
  26. package/dist/cjs/validateTranscodedProperty.js +16 -0
  27. package/dist/index.d.ts +265 -538
  28. package/dist/mjs/BaseEntityClient.js +1 -1
  29. package/dist/mjs/BaseQueryBuilder.js +5 -1
  30. package/dist/mjs/EntityManager.js +18 -9
  31. package/dist/mjs/ParsedConfig.js +143 -113
  32. package/dist/mjs/addKeys.js +6 -6
  33. package/dist/{cjs/decodeEntityElement.js → mjs/decodeElement.js} +8 -14
  34. package/dist/mjs/decodeGeneratedProperty.js +6 -10
  35. package/dist/mjs/dehydrateIndexItem.js +12 -11
  36. package/dist/mjs/dehydratePageKeyMap.js +10 -7
  37. package/dist/{cjs/encodeEntityElement.js → mjs/encodeElement.js} +7 -13
  38. package/dist/mjs/encodeGeneratedProperty.js +14 -18
  39. package/dist/mjs/getHashKeySpace.js +6 -6
  40. package/dist/mjs/getIndexComponents.js +6 -8
  41. package/dist/mjs/getShardBump.js +1 -1
  42. package/dist/mjs/index.js +0 -1
  43. package/dist/mjs/query.js +1 -1
  44. package/dist/mjs/rehydrateIndexItem.js +12 -10
  45. package/dist/mjs/rehydratePageKeyMap.js +18 -11
  46. package/dist/mjs/removeKeys.js +3 -2
  47. package/dist/mjs/unwrapIndex.js +26 -12
  48. package/dist/mjs/updateItemHashKey.js +5 -3
  49. package/dist/mjs/updateItemRangeKey.js +6 -4
  50. package/dist/mjs/validateGeneratedProperty.js +21 -0
  51. package/dist/mjs/validateIndexToken.js +14 -0
  52. package/dist/mjs/validateTranscodedProperty.js +14 -0
  53. package/package.json +6 -6
  54. package/dist/cjs/conditionalize.js +0 -25
  55. package/dist/cjs/validateEntityGeneratedProperty.js +0 -29
  56. package/dist/cjs/validateEntityIndexToken.js +0 -22
  57. package/dist/mjs/conditionalize.js +0 -23
  58. package/dist/mjs/validateEntityGeneratedProperty.js +0 -27
  59. package/dist/mjs/validateEntityIndexToken.js +0 -20
@@ -7,7 +7,7 @@ class BaseEntityClient {
7
7
  /**
8
8
  * DynamoDB EntityClient constructor.
9
9
  *
10
- * @param options - {@link EntityClientOptions | `EntityClientOptions`} object.
10
+ * @param options - {@link BaseEntityClientOptions | `BaseEntityClientOptions`} object.
11
11
  */
12
12
  constructor(options) {
13
13
  const { batchProcessOptions = {}, logger = console } = options;
@@ -3,7 +3,11 @@ import { mapValues } from 'radash';
3
3
  /**
4
4
  * Abstract base class supporting a fluent API for building a {@link ShardQueryMap | `ShardQueryMap`} using a database client.
5
5
  *
6
- * @category ShardQueryMapBuilder
6
+ * @typeParam C - {@link ConfigMap | `ConfigMap`} that defines an {@link Config | `EntityManager configuration`}'s {@link EntityMap | `EntityMap`}, key properties, and {@link TranscodeMap | `TranscodeMap`}. If omitted, defaults to {@link BaseConfigMap | `BaseConfigMap`}.
7
+ * @typeParam EntityClient - {@link BaseEntityClient | `BaseEntityClient`} derived class instance.
8
+ * @typeParam IndexParams - Database platform-specific, index-specific query parameters.
9
+ *
10
+ * @category QueryBuilder
7
11
  */
8
12
  class BaseQueryBuilder {
9
13
  /** BaseQueryBuilder constructor. */
@@ -9,7 +9,12 @@ var _EntityManager_config;
9
9
  * The EntityManager class applies a configuration-driven sharded data model &
10
10
  * query strategy to NoSql data.
11
11
  *
12
- * @category Entity Manager
12
+ * @typeParam C - {@link ConfigMap | `ConfigMap`} that defines the configuration's {@link EntityMap | `EntityMap`}, key properties, and {@link TranscodeMap | `TranscodeMap`}. If omitted, defaults to {@link BaseConfigMap | `BaseConfigMap`}.
13
+ *
14
+ * @remarks
15
+ * While the {@link EntityManager.query | `query`} method is `public`, normally it should not be called directly. The `query` method is used by a platform-specific {@link BaseQueryBuilder.query | `QueryBuilder.query`} method to provide a fluent query API.
16
+ *
17
+ * @category EntityManager
13
18
  */
14
19
  class EntityManager {
15
20
  /**
@@ -40,10 +45,10 @@ class EntityManager {
40
45
  __classPrivateFieldSet(this, _EntityManager_config, configSchema.parse(value), "f");
41
46
  }
42
47
  /**
43
- * Update generated properties, hash key, and range key on an {@link ItemMap | `ItemMap`} object.
48
+ * Update generated properties, hash key, and range key on an {@link EntityItem | `EntityItem`} object.
44
49
  *
45
- * @param entityToken - {@link ConfigKeys.entities | `this.config.entities`} key.
46
- * @param item - {@link ItemMap | `ItemMap`} object.
50
+ * @param entityToken - {@link Config | `Config`} `entities` key.
51
+ * @param item - {@link EntityItem | `EntityItem`} object.
47
52
  * @param overwrite - Overwrite existing properties (default `false`).
48
53
  *
49
54
  * @returns Shallow clone of `item` with updated properties.
@@ -54,10 +59,10 @@ class EntityManager {
54
59
  return addKeys(this, entityToken, item, overwrite);
55
60
  }
56
61
  /**
57
- * Strips generated properties, hash key, and range key from an {@link ItemMap | `ItemMap`} object.
62
+ * Strips generated properties, hash key, and range key from an {@link EntityItem | `EntityItem`} object.
58
63
  *
59
- * @param entityToken - {@link ConfigKeys.entities | `this.config.entities`} key.
60
- * @param item - {@link ItemMap | `ItemMap`} object.
64
+ * @param entityToken - {@link Config | `Config`} `entities` key.
65
+ * @param item - {@link EntityItem | `EntityItem`} object.
61
66
  *
62
67
  * @returns Shallow clone of `item` without generated properties, hash key or range key.
63
68
  *
@@ -72,17 +77,21 @@ class EntityManager {
72
77
  * @remarks
73
78
  * The provided `shardQueryMap` performs the actual query of individual data pages on individual index/shard combinations.
74
79
  *
75
- * Individual shard query results will be combined, deduped by {@link ConfigEntity.uniqueProperty} property value, and sorted by {@link QueryOptions.sortOrder | `sortOrder`}.
80
+ * Individual shard query results will be combined, deduped by {@link Config | `Config`} `uniqueProperty` value, and sorted by {@link QueryOptions.sortOrder | `sortOrder`}.
76
81
  *
77
82
  * In queries on sharded data, expect the leading and trailing edges of returned data pages to interleave somewhat with preceding & following pages.
78
83
  *
79
84
  * Unsharded query results should sort & page as expected.
80
85
  *
86
+ * **Normally this method should not be called directly!** It is used by a platform-specific {@link BaseQueryBuilder.query | `QueryBuilder.query`} method to provide a fluent query API.
87
+ *
81
88
  * @param options - {@link QueryOptions | `QueryOptions`} object.
82
89
  *
83
90
  * @returns {@link QueryResult} object.
84
91
  *
85
- * @throws Error if {@link QueryOptions.shardQueryMapBuilder | `shardQueryMapBuilder`} `pageKeyMap` keys do not match its `shardQueryMap` keys.
92
+ * @throws Error if `options` {@link QueryOptions.pageKeyMap | `pageKeyMap`} `pageKeyMap` keys do not match {@link QueryOptions.shardQueryMap | `shardQueryMap`} keys.
93
+ *
94
+ * @protected
86
95
  */
87
96
  async query(options) {
88
97
  return await query(this, options);
@@ -9,16 +9,18 @@ const validateArrayUnique = (arr, ctx, identity = (item) => item, path = []) =>
9
9
  if (count > 1)
10
10
  ctx.addIssue({
11
11
  code: z.ZodIssueCode.custom,
12
- message: `duplicate array element '${element}'`,
12
+ message: `duplicate array element`,
13
+ params: { element },
13
14
  path,
14
15
  });
15
16
  }
16
17
  };
17
- const validateKeyExclusive = (key, label, ref, ctx) => {
18
- if (ref.includes(key))
18
+ const validateKeysExclusive = (keys, label, ref, ctx) => {
19
+ const intersection = keys.filter((key) => ref.includes(key));
20
+ if (intersection.length)
19
21
  ctx.addIssue({
20
22
  code: z.ZodIssueCode.custom,
21
- message: `${label} '${key}' is not exclusive`,
23
+ message: `${label} key collision: ${intersection.toString()}`,
22
24
  });
23
25
  };
24
26
  const componentArray = z
@@ -44,25 +46,6 @@ const configSchema = z
44
46
  .safe()
45
47
  .optional()
46
48
  .default(10),
47
- generated: z
48
- .record(z
49
- .object({
50
- atomic: z.boolean().optional().default(false),
51
- elements: componentArray,
52
- sharded: z.boolean().optional().default(false),
53
- })
54
- .optional())
55
- .optional()
56
- .default({}),
57
- elementTranscodes: z.record(z.string()).optional().default({}),
58
- indexes: z
59
- .record(z.object({
60
- hashKey: z.string().min(1),
61
- rangeKey: z.string().min(1),
62
- projections: componentArray.optional(),
63
- }))
64
- .optional()
65
- .default({}),
66
49
  shardBumps: z
67
50
  .array(z
68
51
  .object({
@@ -103,21 +86,30 @@ const configSchema = z
103
86
  timestampProperty: z.string().min(1),
104
87
  uniqueProperty: z.string().min(1),
105
88
  })
106
- .strict()
107
- .superRefine((data, ctx) => {
108
- const generatedKeys = Object.keys(data.generated);
109
- // validate timestampProperty is not a generated key.
110
- validateKeyExclusive(data.timestampProperty, 'timestampProperty', generatedKeys, ctx);
111
- // validate uniqueProperty is not a generated key.
112
- validateKeyExclusive(data.uniqueProperty, 'uniqueProperty', generatedKeys, ctx);
89
+ .strict())
90
+ .optional()
91
+ .default({}),
92
+ generatedProperties: z
93
+ .object({
94
+ sharded: z.record(componentArray).optional().default({}),
95
+ unsharded: z.record(componentArray).optional().default({}),
96
+ })
97
+ .optional()
98
+ .default({ sharded: {}, unsharded: {} }),
99
+ hashKey: z.string(),
100
+ indexes: z
101
+ .record(z.object({
102
+ hashKey: z.string().min(1),
103
+ rangeKey: z.string().min(1),
104
+ projections: componentArray.optional(),
113
105
  }))
114
106
  .optional()
115
107
  .default({}),
116
108
  generatedKeyDelimiter: z.string().regex(/\W+/).optional().default('|'),
117
109
  generatedValueDelimiter: z.string().regex(/\W+/).optional().default('#'),
110
+ propertyTranscodes: z.record(z.string()).optional().default({}),
111
+ rangeKey: z.string(),
118
112
  shardKeyDelimiter: z.string().regex(/\W+/).optional().default('!'),
119
- hashKey: z.string().optional().default('hashKey'),
120
- rangeKey: z.string().optional().default('rangeKey'),
121
113
  throttle: z.number().int().positive().safe().optional().default(10),
122
114
  transcodes: z
123
115
  .record(z
@@ -136,12 +128,20 @@ const configSchema = z
136
128
  ctx.addIssue({
137
129
  code: z.ZodIssueCode.custom,
138
130
  message: 'generatedKeyDelimiter contains generatedValueDelimiter',
131
+ params: {
132
+ generatedKeyDelimiter: data.generatedKeyDelimiter,
133
+ generatedValueDelimiter: data.generatedValueDelimiter,
134
+ },
139
135
  path: ['generatedKeyDelimiter'],
140
136
  });
141
137
  if (data.generatedKeyDelimiter.includes(data.shardKeyDelimiter))
142
138
  ctx.addIssue({
143
139
  code: z.ZodIssueCode.custom,
144
140
  message: 'generatedKeyDelimiter contains shardKeyDelimiter',
141
+ params: {
142
+ generatedKeyDelimiter: data.generatedKeyDelimiter,
143
+ shardKeyDelimiter: data.shardKeyDelimiter,
144
+ },
145
145
  path: ['generatedKeyDelimiter'],
146
146
  });
147
147
  // validate no generated value delimiter collision
@@ -149,12 +149,20 @@ const configSchema = z
149
149
  ctx.addIssue({
150
150
  code: z.ZodIssueCode.custom,
151
151
  message: 'generatedValueDelimiter contains generatedKeyDelimiter',
152
+ params: {
153
+ generatedValueDelimiter: data.generatedValueDelimiter,
154
+ generatedKeyDelimiter: data.generatedKeyDelimiter,
155
+ },
152
156
  path: ['generatedValueDelimiter'],
153
157
  });
154
158
  if (data.generatedValueDelimiter.includes(data.shardKeyDelimiter))
155
159
  ctx.addIssue({
156
160
  code: z.ZodIssueCode.custom,
157
161
  message: 'generatedValueDelimiter contains shardKeyDelimiter',
162
+ params: {
163
+ generatedValueDelimiter: data.generatedValueDelimiter,
164
+ shardKeyDelimiter: data.shardKeyDelimiter,
165
+ },
158
166
  path: ['generatedValueDelimiter'],
159
167
  });
160
168
  // validate no shard key delimiter collision
@@ -162,103 +170,125 @@ const configSchema = z
162
170
  ctx.addIssue({
163
171
  code: z.ZodIssueCode.custom,
164
172
  message: 'shardKeyDelimiter contains generatedKeyDelimiter',
173
+ params: {
174
+ generatedKeyDelimiter: data.generatedKeyDelimiter,
175
+ shardKeyDelimiter: data.shardKeyDelimiter,
176
+ },
165
177
  path: ['shardKeyDelimiter'],
166
178
  });
167
179
  if (data.shardKeyDelimiter.includes(data.generatedValueDelimiter))
168
180
  ctx.addIssue({
169
181
  code: z.ZodIssueCode.custom,
170
182
  message: 'shardKeyDelimiter contains generatedValueDelimiter',
183
+ params: {
184
+ generatedValueDelimiter: data.generatedValueDelimiter,
185
+ shardKeyDelimiter: data.shardKeyDelimiter,
186
+ },
171
187
  path: ['shardKeyDelimiter'],
172
188
  });
173
- const reservedKeys = Object.values(data.entities).reduce((reserved, { generated, timestampProperty, uniqueProperty }) => new Set([
174
- ...reserved,
175
- ...Object.keys(generated),
176
- timestampProperty,
177
- uniqueProperty,
178
- ]), new Set());
179
- // validate hashKey is not a reserved key.
180
- validateKeyExclusive(data.hashKey, 'hashKey', [...reservedKeys, data.rangeKey], ctx);
181
- // validate rangeKey is not a reserved key.
182
- validateKeyExclusive(data.rangeKey, 'rangeKey', [...reservedKeys, data.hashKey], ctx);
183
- // validate entities
189
+ // get reserved keys
190
+ const shardedKeys = Object.keys(data.generatedProperties.sharded);
191
+ const unshardedKeys = Object.keys(data.generatedProperties.unsharded);
192
+ const transcodedProperties = Object.keys(data.propertyTranscodes);
193
+ // validate hashKey exclusive.
194
+ validateKeysExclusive([data.hashKey], 'hashKey', [
195
+ data.rangeKey,
196
+ ...shardedKeys,
197
+ ...unshardedKeys,
198
+ ...transcodedProperties,
199
+ ], ctx);
200
+ // validate rangeKey exclusive.
201
+ validateKeysExclusive([data.rangeKey], 'rangeKey', [...shardedKeys, ...unshardedKeys, ...transcodedProperties], ctx);
202
+ // validate shardedKeys exclusive.
203
+ validateKeysExclusive(shardedKeys, 'shardedKeys', [...unshardedKeys, ...transcodedProperties], ctx);
204
+ // validate unshardedKeys exclusive.
205
+ validateKeysExclusive(unshardedKeys, 'unshardedKeys', transcodedProperties, ctx);
206
+ // validate all propertyTranscode values are transcode keys.
184
207
  const transcodes = Object.keys(data.transcodes);
185
- for (const [entityToken, entity] of Object.entries(data.entities)) {
186
- // validate all entity generated element type values are transcode keys.
187
- for (const [element, generatedElementType] of Object.entries(entity.elementTranscodes))
188
- if (!transcodes.includes(generatedElementType))
208
+ for (const [property, transcode] of Object.entries(data.propertyTranscodes))
209
+ if (!transcodes.includes(transcode))
210
+ ctx.addIssue({
211
+ code: z.ZodIssueCode.invalid_enum_value,
212
+ options: transcodes,
213
+ path: ['propertyTranscodes', property],
214
+ received: transcode,
215
+ });
216
+ // Validate all sharded property elements are transcoded properties.
217
+ for (const [property, elements] of Object.entries(data.generatedProperties.sharded))
218
+ for (const element of elements)
219
+ if (!transcodedProperties.includes(element))
189
220
  ctx.addIssue({
190
221
  code: z.ZodIssueCode.invalid_enum_value,
191
- options: transcodes,
192
- path: ['entities', entityToken, 'elementTranscodes', element],
193
- received: generatedElementType,
194
- });
195
- // validate all entity generated property elements have a corresponding entity element type.
196
- const typedElements = Object.keys(entity.elementTranscodes);
197
- for (const [generatedKey, generated] of Object.entries(entity.generated))
198
- for (const element of generated?.elements ?? [])
199
- if (!typedElements.includes(element))
200
- ctx.addIssue({
201
- code: z.ZodIssueCode.invalid_enum_value,
202
- options: typedElements,
203
- path: [
204
- 'entities',
205
- entityToken,
206
- 'generated',
207
- generatedKey,
208
- 'elements',
209
- ],
210
- received: element,
211
- });
212
- // validate indexes.
213
- const generatedProperties = Object.keys(entity.generated);
214
- for (const [indexKey, { hashKey, rangeKey, projections },] of Object.entries(entity.indexes)) {
215
- // validate index hash key is sharded
216
- if (hashKey !== data.hashKey && !entity.generated[hashKey]?.sharded)
217
- ctx.addIssue({
218
- code: z.ZodIssueCode.custom,
219
- message: 'index hash key is not sharded',
220
- path: ['entities', entityToken, 'indexes', indexKey, 'hashKey'],
222
+ options: transcodedProperties,
223
+ received: element,
224
+ path: ['generatedProperties', 'sharded', property],
221
225
  });
222
- // validate index range key is unsharded
223
- if (rangeKey !== data.rangeKey && entity.generated[rangeKey]?.sharded)
226
+ // Validate all unsharded property elements are transcoded properties.
227
+ for (const [property, elements] of Object.entries(data.generatedProperties.unsharded))
228
+ for (const element of elements)
229
+ if (!transcodedProperties.includes(element))
224
230
  ctx.addIssue({
225
- code: z.ZodIssueCode.custom,
226
- message: 'index range key is sharded',
227
- path: ['entities', entityToken, 'indexes', indexKey, 'rangeKey'],
231
+ code: z.ZodIssueCode.invalid_enum_value,
232
+ options: transcodedProperties,
233
+ received: element,
234
+ path: ['generatedProperties', 'unsharded', property],
228
235
  });
229
- // validate all ungenerated entity index components have a corresponding entity element type
230
- for (const component of [hashKey, rangeKey])
231
- if (![data.hashKey, data.rangeKey, ...generatedProperties].includes(component) &&
232
- !typedElements.includes(component))
236
+ // Validate indexes.
237
+ for (const [indexKey, { hashKey, rangeKey, projections }] of Object.entries(data.indexes)) {
238
+ // Validate hash key is sharded.
239
+ if (![data.hashKey, ...shardedKeys].includes(hashKey)) {
240
+ ctx.addIssue({
241
+ code: z.ZodIssueCode.invalid_enum_value,
242
+ options: [data.hashKey, ...shardedKeys],
243
+ path: ['indexes', indexKey, 'hashKey'],
244
+ received: hashKey,
245
+ });
246
+ }
247
+ // Validate range key is unsharded or transcodable.
248
+ if (![data.rangeKey, ...unshardedKeys, ...transcodedProperties].includes(rangeKey)) {
249
+ ctx.addIssue({
250
+ code: z.ZodIssueCode.invalid_enum_value,
251
+ options: [data.rangeKey, ...unshardedKeys],
252
+ path: ['indexes', indexKey, 'rangeKey'],
253
+ received: rangeKey,
254
+ });
255
+ }
256
+ // Validate no index projections are keys.
257
+ if (projections)
258
+ for (const projection of projections)
259
+ if ([
260
+ data.hashKey,
261
+ data.rangeKey,
262
+ hashKey,
263
+ rangeKey,
264
+ ...shardedKeys,
265
+ ...unshardedKeys,
266
+ ].includes(projection))
233
267
  ctx.addIssue({
234
- code: z.ZodIssueCode.invalid_enum_value,
235
- options: typedElements,
236
- path: [
237
- 'entities',
238
- entityToken,
239
- 'indexes',
240
- indexKey,
241
- 'components',
242
- ],
243
- received: component,
268
+ code: z.ZodIssueCode.custom,
269
+ message: 'index projection is a key',
270
+ params: { projection },
271
+ path: ['indexes', indexKey, 'projections'],
244
272
  });
245
- // validate no index projections are index components, hashKey, or rangeKey
246
- if (projections)
247
- for (const projection of projections) {
248
- if ([data.hashKey, data.rangeKey, hashKey, rangeKey].includes(projection))
249
- ctx.addIssue({
250
- code: z.ZodIssueCode.custom,
251
- message: 'index projection is an index component, hash key, or range key',
252
- path: [
253
- 'entities',
254
- entityToken,
255
- 'indexes',
256
- indexKey,
257
- 'projections',
258
- ],
259
- });
260
- }
261
- }
273
+ }
274
+ // validate entities
275
+ for (const [entityToken, { timestampProperty, uniqueProperty },] of Object.entries(data.entities)) {
276
+ // validate timestampProperty is a transcoded property.
277
+ if (!transcodedProperties.includes(timestampProperty))
278
+ ctx.addIssue({
279
+ code: z.ZodIssueCode.invalid_enum_value,
280
+ options: transcodedProperties,
281
+ path: ['entities', entityToken, 'timestampProperty'],
282
+ received: timestampProperty,
283
+ });
284
+ // validate uniqueProperty is a transcoded property.
285
+ if (!transcodedProperties.includes(uniqueProperty))
286
+ ctx.addIssue({
287
+ code: z.ZodIssueCode.invalid_enum_value,
288
+ options: transcodedProperties,
289
+ path: ['entities', entityToken, 'uniqueProperty'],
290
+ received: uniqueProperty,
291
+ });
262
292
  }
263
293
  });
264
294
 
@@ -5,11 +5,11 @@ import { updateItemRangeKey } from './updateItemRangeKey.js';
5
5
  import { validateEntityToken } from './validateEntityToken.js';
6
6
 
7
7
  /**
8
- * Update generated properties, hash key, and range key on an {@link ItemMap | `ItemMap`} object.
8
+ * Update generated properties, hash key, and range key on an {@link EntityItem | `EntityItem`} object.
9
9
  *
10
10
  * @param entityManager - {@link EntityManager | `EntityManager`} instance.
11
- * @param entityToken - {@link ConfigKeys.entities | `this.config.entities`} key.
12
- * @param item - {@link ItemMap | `ItemMap`} object.
11
+ * @param entityToken - {@link ConfigEntity.uniqueProperty | `this.config.entities`} key.
12
+ * @param item - {@link EntityItem | `EntityItem`} object.
13
13
  * @param overwrite - Overwrite existing properties (default `false`).
14
14
  *
15
15
  * @returns Shallow clone of `item` with updated properties.
@@ -25,10 +25,10 @@ function addKeys(entityManager, entityToken, item, overwrite = false) {
25
25
  // Update range key.
26
26
  newItem = updateItemRangeKey(entityManager, entityToken, newItem, overwrite);
27
27
  // Update generated properties.
28
- for (const property in entityManager.config.entities[entityToken]
29
- .generated) {
28
+ const { sharded, unsharded } = entityManager.config.generatedProperties;
29
+ for (const property in { ...sharded, ...unsharded }) {
30
30
  if (overwrite || isNil(item[property])) {
31
- const encoded = encodeGeneratedProperty(entityManager, entityToken, property, newItem);
31
+ const encoded = encodeGeneratedProperty(entityManager, property, newItem);
32
32
  if (encoded)
33
33
  Object.assign(newItem, { [property]: encoded });
34
34
  else
@@ -1,16 +1,13 @@
1
- 'use strict';
2
-
3
- var validateEntityToken = require('./validateEntityToken.js');
1
+ import { validateTranscodedProperty } from './validateTranscodedProperty.js';
4
2
 
5
3
  /**
6
- * Decode an {@link Entity | `Entity`} generated property element or ungenerated index component using the associated {@link Transcodes | Transcodes} `encode` function.
4
+ * Decode an {@link EntityItem | `EntityItem`} generated property element or ungenerated index component using the associated {@link Transcodes | Transcodes} `encode` function.
7
5
  *
8
6
  * Returns all `undefined` values.
9
7
  *
10
8
  * If `element` is the {@link Config.hashKey | `hashKey`} or {@link Config.rangeKey | `rangeKey`}, returns the value as-is.
11
9
  *
12
10
  * @param entityManager - {@link EntityManager | `EntityManager`} instance.
13
- * @param entityToken - {@link ConfigKeys.entities | `entityManager.config.entities`} key.
14
11
  * @param element - The {@link Entity | `Entity`} generated property element or ungenerated index component to encode.
15
12
  * @param value - Encoded entity element.
16
13
  *
@@ -18,17 +15,15 @@ var validateEntityToken = require('./validateEntityToken.js');
18
15
  *
19
16
  * @throws `Error` if `entityToken` is invalid.
20
17
  */
21
- function decodeEntityElement(entityManager, entityToken, element, value) {
18
+ function decodeElement(entityManager, element, value) {
22
19
  try {
23
- validateEntityToken.validateEntityToken(entityManager, entityToken);
24
- const { entities, hashKey, rangeKey, transcodes } = entityManager.config;
20
+ // Validate params.
21
+ validateTranscodedProperty(entityManager, element);
25
22
  if (!value)
26
23
  return;
27
- if ([hashKey, rangeKey].includes(element))
28
- return value;
29
- const decoded = transcodes[entities[entityToken].elementTranscodes[element]].decode(value);
24
+ const { propertyTranscodes, transcodes } = entityManager.config;
25
+ const decoded = transcodes[propertyTranscodes[element]].decode(value);
30
26
  entityManager.logger.debug('decoded entity element', {
31
- entityToken,
32
27
  element,
33
28
  value,
34
29
  decoded,
@@ -38,7 +33,6 @@ function decodeEntityElement(entityManager, entityToken, element, value) {
38
33
  catch (error) {
39
34
  if (error instanceof Error)
40
35
  entityManager.logger.error(error.message, {
41
- entityToken,
42
36
  element,
43
37
  value,
44
38
  });
@@ -46,4 +40,4 @@ function decodeEntityElement(entityManager, entityToken, element, value) {
46
40
  }
47
41
  }
48
42
 
49
- exports.decodeEntityElement = decodeEntityElement;
43
+ export { decodeElement };
@@ -1,23 +1,20 @@
1
1
  import { objectify } from 'radash';
2
- import { decodeEntityElement } from './decodeEntityElement.js';
3
- import { validateEntityToken } from './validateEntityToken.js';
2
+ import { decodeElement } from './decodeElement.js';
4
3
 
5
4
  /**
6
- * Decode a generated property value. Returns a partial ItemMap.
5
+ * Decode a generated property value. Returns an {@link EntityItem | `EntityItem`}.
7
6
  *
8
7
  * @param entityManager - {@link EntityManager | `EntityManager`} instance.
9
8
  * @param entityToken - `entityManager.config.entities` key.
10
9
  * @param encoded - Encoded generated property value.
11
10
  *
12
- * @returns Partial {@link ItemMap | `ItemMap`} object with updated properties decoded from `encoded`.
11
+ * @returns {@link EntityItem | `EntityItem`} object with updated properties decoded from `encoded`.
13
12
  *
14
13
  * @throws `Error` if `entityToken` is invalid.
15
14
  */
16
- function decodeGeneratedProperty(entityManager, entityToken, encoded) {
15
+ function decodeGeneratedProperty(entityManager, encoded) {
17
16
  try {
18
17
  const { generatedKeyDelimiter, generatedValueDelimiter, hashKey, shardKeyDelimiter, } = entityManager.config;
19
- // Validate params.
20
- validateEntityToken(entityManager, entityToken);
21
18
  // Handle degenerate case.
22
19
  if (!encoded)
23
20
  return {};
@@ -35,9 +32,8 @@ function decodeGeneratedProperty(entityManager, entityToken, encoded) {
35
32
  return pair;
36
33
  });
37
34
  // Assign decoded properties.
38
- Object.assign(decoded, objectify(values, ([key]) => key, ([key, value]) => decodeEntityElement(entityManager, entityToken, key, value)));
35
+ Object.assign(decoded, objectify(values, ([key]) => key, ([key, value]) => decodeElement(entityManager, key, value)));
39
36
  entityManager.logger.debug('decoded generated property', {
40
- entityToken,
41
37
  encoded,
42
38
  decoded,
43
39
  });
@@ -45,7 +41,7 @@ function decodeGeneratedProperty(entityManager, entityToken, encoded) {
45
41
  }
46
42
  catch (error) {
47
43
  if (error instanceof Error)
48
- entityManager.logger.error(error.message, { entityToken, encoded });
44
+ entityManager.logger.error(error.message, { encoded });
49
45
  throw error;
50
46
  }
51
47
  }
@@ -1,9 +1,10 @@
1
- import { encodeEntityElement } from './encodeEntityElement.js';
1
+ import { encodeElement } from './encodeElement.js';
2
2
  import { unwrapIndex } from './unwrapIndex.js';
3
- import { validateEntityIndexToken } from './validateEntityIndexToken.js';
3
+ import { validateEntityToken } from './validateEntityToken.js';
4
+ import { validateIndexToken } from './validateIndexToken.js';
4
5
 
5
6
  /**
6
- * Condense a partial {@link ItemMap | `ItemMap`} object into a delimited string representing the deduped, sorted, ungenerated component elements of an {@link ConfigEntity.indexes | Entity index}.
7
+ * Condense an {@link EntityItem | `EntityItem`} into a delimited string representing the deduped, sorted, ungenerated component elements of an {@link Config.indexes | index}, leaving out those of the index hash key.
7
8
  *
8
9
  * @remarks
9
10
  * Reverses {@link EntityManager.rehydrateIndexItem | `rehydrateIndexItem`}.
@@ -16,31 +17,31 @@ import { validateEntityIndexToken } from './validateEntityIndexToken.js';
16
17
  * `item` must be populated with all required index component elements!
17
18
  *
18
19
  * @param entityManager - {@link EntityManager | `EntityManager`} instance.
19
- * @param entityToken - {@link ConfigKeys.entities | `entityManager.config.entities`} key.
20
- * @param indexToken - {@link ConfigEntity.indexes | `entityManager.config.entities.<entityToken>.indexes`} key.
21
- * @param item - Partial {@link ItemMap | `ItemMap`} object.
20
+ * @param entityToken - {@link Config.entities | `entityManager.config.entities`} key.
21
+ * @param indexToken - {@link ConfigEntity.indexes | `entityManager.config.indexes`} key.
22
+ * @param item - {@link EntityItem | `EntityItem`} object.
22
23
  *
23
24
  * @returns Dehydrated index value.
24
25
  *
25
- * @throws `Error` if `entityToken` is invalid.
26
26
  * @throws `Error` if `indexToken` is invalid.
27
27
  */
28
28
  function dehydrateIndexItem(entityManager, entityToken, indexToken, item) {
29
29
  try {
30
- const { generatedKeyDelimiter } = entityManager.config;
31
30
  // Validate params.
32
- validateEntityIndexToken(entityManager, entityToken, indexToken);
31
+ validateEntityToken(entityManager, entityToken);
32
+ validateIndexToken(entityManager, indexToken);
33
33
  // Handle degenerate case.
34
34
  if (!item)
35
35
  return '';
36
36
  // Unwrap index elements.
37
- const { hashKey } = entityManager.config.entities[entityToken].indexes[indexToken];
37
+ const { hashKey } = entityManager.config.indexes[indexToken];
38
38
  const elements = unwrapIndex(entityManager, entityToken, indexToken, [
39
39
  hashKey,
40
40
  ]);
41
41
  // Join index element values.
42
+ const { generatedKeyDelimiter } = entityManager.config;
42
43
  const dehydrated = elements
43
- .map((element) => encodeEntityElement(entityManager, entityToken, element, item))
44
+ .map((element) => encodeElement(entityManager, element, item))
44
45
  .join(generatedKeyDelimiter);
45
46
  entityManager.logger.debug('dehydrated index', {
46
47
  item,