@karmaniverous/entity-manager 6.14.0 → 6.14.2
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/ParsedConfig.js +46 -55
- package/dist/cjs/EntityManager/addKeys.js +1 -0
- package/dist/cjs/EntityManager/decodeElement.js +2 -1
- package/dist/cjs/EntityManager/dehydratePageKeyMap.js +3 -1
- package/dist/cjs/EntityManager/encodeElement.js +2 -2
- package/dist/cjs/EntityManager/encodeGeneratedProperty.js +1 -1
- package/dist/cjs/EntityManager/getHashKeySpace.js +16 -3
- package/dist/cjs/EntityManager/rehydratePageKeyMap.js +3 -1
- package/dist/cjs/EntityManager/removeKeys.js +10 -8
- package/dist/cjs/EntityManager/updateItemHashKey.js +4 -1
- package/dist/index.d.ts +17 -208
- package/dist/mjs/EntityManager/ParsedConfig.js +46 -55
- package/dist/mjs/EntityManager/addKeys.js +1 -0
- package/dist/mjs/EntityManager/decodeElement.js +2 -1
- package/dist/mjs/EntityManager/dehydratePageKeyMap.js +3 -1
- package/dist/mjs/EntityManager/encodeElement.js +2 -2
- package/dist/mjs/EntityManager/encodeGeneratedProperty.js +1 -1
- package/dist/mjs/EntityManager/getHashKeySpace.js +16 -3
- package/dist/mjs/EntityManager/rehydratePageKeyMap.js +3 -1
- package/dist/mjs/EntityManager/removeKeys.js +10 -8
- package/dist/mjs/EntityManager/updateItemHashKey.js +4 -1
- package/package.json +44 -45
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var radash = require('radash');
|
|
4
|
+
var stringHash = require('string-hash');
|
|
4
5
|
var encodeGeneratedProperty = require('./encodeGeneratedProperty.js');
|
|
5
6
|
var validateGeneratedProperty = require('./validateGeneratedProperty.js');
|
|
6
7
|
|
|
@@ -24,6 +25,9 @@ function getHashKeySpace(entityManager, entityToken, hashKeyToken, item, timesta
|
|
|
24
25
|
if (hashKeyToken !== entityManager.config.hashKey)
|
|
25
26
|
validateGeneratedProperty.validateGeneratedProperty(entityManager, hashKeyToken, true);
|
|
26
27
|
const { shardBumps } = entityManager.config.entities[entityToken];
|
|
28
|
+
// Detect presence of the entity's unique property on the item.
|
|
29
|
+
const uniqueProp = entityManager.config.entities[entityToken].uniqueProperty;
|
|
30
|
+
const uniqueValue = item[uniqueProp];
|
|
27
31
|
const hashKeySpace = shardBumps
|
|
28
32
|
// Filter shard bumps by timestamp range.
|
|
29
33
|
.filter((bump, i) => (i === shardBumps.length - 1 ||
|
|
@@ -32,9 +36,18 @@ function getHashKeySpace(entityManager, entityToken, hashKeyToken, item, timesta
|
|
|
32
36
|
// Generate shard key space.
|
|
33
37
|
.flatMap(({ charBits, chars }) => {
|
|
34
38
|
const radix = 2 ** charBits;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
// If the item's unique property is present, deterministically
|
|
40
|
+
// compute exactly one shard suffix for this bump. Otherwise,
|
|
41
|
+
// enumerate the full shard space for the bump.
|
|
42
|
+
if (chars) {
|
|
43
|
+
if (uniqueValue) {
|
|
44
|
+
const space = radix ** chars;
|
|
45
|
+
const mod = stringHash(uniqueValue) % space;
|
|
46
|
+
return mod.toString(radix).padStart(chars, '0');
|
|
47
|
+
}
|
|
48
|
+
return [...radash.range(0, radix ** chars - 1)].map((char) => char.toString(radix).padStart(chars, '0'));
|
|
49
|
+
}
|
|
50
|
+
return '';
|
|
38
51
|
})
|
|
39
52
|
// Map shard keys to hash keys.
|
|
40
53
|
.map((shardKey) => {
|
|
@@ -47,7 +47,9 @@ function rehydratePageKeyMap(entityManager, entityToken, indexTokens, item, dehy
|
|
|
47
47
|
if (hashKeys.length > 1)
|
|
48
48
|
throw new Error('inconsistent hashKeys');
|
|
49
49
|
const [hashKeyToken] = hashKeys;
|
|
50
|
-
indexTokens.map((index) =>
|
|
50
|
+
indexTokens.map((index) => {
|
|
51
|
+
validateIndexToken.validateIndexToken(entityManager, index);
|
|
52
|
+
});
|
|
51
53
|
// Shortcut empty dehydrated.
|
|
52
54
|
if (dehydrated && !dehydrated.length)
|
|
53
55
|
return [hashKeyToken, {}];
|
|
@@ -17,14 +17,16 @@ function removeKeys(entityManager, entityToken, item) {
|
|
|
17
17
|
try {
|
|
18
18
|
// Validate params.
|
|
19
19
|
validateEntityToken.validateEntityToken(entityManager, entityToken);
|
|
20
|
-
//
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
// Build a set of keys to strip (hash, range, and all generated properties).
|
|
21
|
+
const { hashKey, rangeKey, generatedProperties: { sharded, unsharded }, } = entityManager.config;
|
|
22
|
+
const keysToStrip = new Set([
|
|
23
|
+
hashKey,
|
|
24
|
+
rangeKey,
|
|
25
|
+
...Object.keys(sharded),
|
|
26
|
+
...Object.keys(unsharded),
|
|
27
|
+
]);
|
|
28
|
+
// Create a shallow copy of item omitting the keys above (no delete operator).
|
|
29
|
+
const newItem = Object.fromEntries(Object.entries(item).filter(([key]) => !keysToStrip.has(key)));
|
|
28
30
|
entityManager.logger.debug('stripped entity item generated properties', {
|
|
29
31
|
entityToken,
|
|
30
32
|
item,
|
|
@@ -42,12 +42,15 @@ function updateItemHashKey(entityManager, entityToken, item, overwrite = false)
|
|
|
42
42
|
if (chars) {
|
|
43
43
|
// Radix is the numerical base of the shardKey.
|
|
44
44
|
const radix = 2 ** charBits;
|
|
45
|
+
// Compute the full shard space for this bump. Use radix ** chars to ensure
|
|
46
|
+
// all placeholders are utilized (e.g., chars=2, charBits=2 => 16 combos).
|
|
47
|
+
const space = radix ** chars;
|
|
45
48
|
// Get item unique property & validate.
|
|
46
49
|
const uniqueId = item[entityManager.config.entities[entityToken]
|
|
47
50
|
.uniqueProperty];
|
|
48
51
|
if (entityTools.isNil(uniqueId))
|
|
49
52
|
throw new Error(`missing item unique property`);
|
|
50
|
-
hashKey += (stringHash(uniqueId
|
|
53
|
+
hashKey += (stringHash(uniqueId) % space)
|
|
51
54
|
.toString(radix)
|
|
52
55
|
.padStart(chars, '0');
|
|
53
56
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -157,31 +157,15 @@ type EntityRecord<C extends BaseConfigMap> = EntityItem<C> & EntityKey<C>;
|
|
|
157
157
|
*/
|
|
158
158
|
type EntityToken<C extends BaseConfigMap> = keyof Exactify<C['EntityMap']> & string;
|
|
159
159
|
|
|
160
|
-
declare const configSchema: z.
|
|
160
|
+
declare const configSchema: z.ZodObject<{
|
|
161
161
|
entities: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
162
162
|
defaultLimit: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
163
163
|
defaultPageSize: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
164
|
-
shardBumps: z.
|
|
164
|
+
shardBumps: z.ZodPipe<z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
165
165
|
timestamp: z.ZodNumber;
|
|
166
166
|
charBits: z.ZodNumber;
|
|
167
167
|
chars: z.ZodNumber;
|
|
168
|
-
},
|
|
169
|
-
timestamp: number;
|
|
170
|
-
charBits: number;
|
|
171
|
-
chars: number;
|
|
172
|
-
}, {
|
|
173
|
-
timestamp: number;
|
|
174
|
-
charBits: number;
|
|
175
|
-
chars: number;
|
|
176
|
-
}>, "many">>>, {
|
|
177
|
-
timestamp: number;
|
|
178
|
-
charBits: number;
|
|
179
|
-
chars: number;
|
|
180
|
-
}[], {
|
|
181
|
-
timestamp: number;
|
|
182
|
-
charBits: number;
|
|
183
|
-
chars: number;
|
|
184
|
-
}[] | undefined>, {
|
|
168
|
+
}, z.core.$strict>>>>, z.ZodTransform<{
|
|
185
169
|
timestamp: number;
|
|
186
170
|
charBits: number;
|
|
187
171
|
chars: number;
|
|
@@ -189,62 +173,20 @@ declare const configSchema: z.ZodEffects<z.ZodObject<{
|
|
|
189
173
|
timestamp: number;
|
|
190
174
|
charBits: number;
|
|
191
175
|
chars: number;
|
|
192
|
-
}[]
|
|
193
|
-
timestamp: number;
|
|
194
|
-
charBits: number;
|
|
195
|
-
chars: number;
|
|
196
|
-
}[], {
|
|
197
|
-
timestamp: number;
|
|
198
|
-
charBits: number;
|
|
199
|
-
chars: number;
|
|
200
|
-
}[] | undefined>;
|
|
176
|
+
}[]>>;
|
|
201
177
|
timestampProperty: z.ZodString;
|
|
202
178
|
uniqueProperty: z.ZodString;
|
|
203
|
-
},
|
|
204
|
-
defaultLimit: number;
|
|
205
|
-
defaultPageSize: number;
|
|
206
|
-
shardBumps: {
|
|
207
|
-
timestamp: number;
|
|
208
|
-
charBits: number;
|
|
209
|
-
chars: number;
|
|
210
|
-
}[];
|
|
211
|
-
timestampProperty: string;
|
|
212
|
-
uniqueProperty: string;
|
|
213
|
-
}, {
|
|
214
|
-
timestampProperty: string;
|
|
215
|
-
uniqueProperty: string;
|
|
216
|
-
defaultLimit?: number | undefined;
|
|
217
|
-
defaultPageSize?: number | undefined;
|
|
218
|
-
shardBumps?: {
|
|
219
|
-
timestamp: number;
|
|
220
|
-
charBits: number;
|
|
221
|
-
chars: number;
|
|
222
|
-
}[] | undefined;
|
|
223
|
-
}>>>>;
|
|
179
|
+
}, z.core.$strict>>>>;
|
|
224
180
|
generatedProperties: z.ZodDefault<z.ZodOptional<z.ZodObject<{
|
|
225
|
-
sharded: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodString, z.
|
|
226
|
-
unsharded: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodString, z.
|
|
227
|
-
},
|
|
228
|
-
sharded: Record<string, [string, ...string[]]>;
|
|
229
|
-
unsharded: Record<string, [string, ...string[]]>;
|
|
230
|
-
}, {
|
|
231
|
-
sharded?: Record<string, [string, ...string[]]> | undefined;
|
|
232
|
-
unsharded?: Record<string, [string, ...string[]]> | undefined;
|
|
233
|
-
}>>>;
|
|
181
|
+
sharded: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>>;
|
|
182
|
+
unsharded: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>>;
|
|
183
|
+
}, z.core.$strip>>>;
|
|
234
184
|
hashKey: z.ZodString;
|
|
235
185
|
indexes: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
236
186
|
hashKey: z.ZodString;
|
|
237
187
|
rangeKey: z.ZodString;
|
|
238
|
-
projections: z.ZodOptional<z.
|
|
239
|
-
},
|
|
240
|
-
hashKey: string;
|
|
241
|
-
rangeKey: string;
|
|
242
|
-
projections?: string[] | undefined;
|
|
243
|
-
}, {
|
|
244
|
-
hashKey: string;
|
|
245
|
-
rangeKey: string;
|
|
246
|
-
projections?: string[] | undefined;
|
|
247
|
-
}>>>>;
|
|
188
|
+
projections: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
189
|
+
}, z.core.$strip>>>>;
|
|
248
190
|
generatedKeyDelimiter: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
249
191
|
generatedValueDelimiter: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
250
192
|
propertyTranscodes: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>>;
|
|
@@ -252,144 +194,10 @@ declare const configSchema: z.ZodEffects<z.ZodObject<{
|
|
|
252
194
|
shardKeyDelimiter: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
253
195
|
throttle: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
254
196
|
transcodes: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
255
|
-
encode: z.
|
|
256
|
-
decode: z.
|
|
257
|
-
},
|
|
258
|
-
|
|
259
|
-
decode: (args_0: string, ...args: unknown[]) => any;
|
|
260
|
-
}, {
|
|
261
|
-
encode: (args_0: any, ...args: unknown[]) => string;
|
|
262
|
-
decode: (args_0: string, ...args: unknown[]) => any;
|
|
263
|
-
}>>>>;
|
|
264
|
-
}, "strict", z.ZodTypeAny, {
|
|
265
|
-
hashKey: string;
|
|
266
|
-
rangeKey: string;
|
|
267
|
-
entities: Record<string, {
|
|
268
|
-
defaultLimit: number;
|
|
269
|
-
defaultPageSize: number;
|
|
270
|
-
shardBumps: {
|
|
271
|
-
timestamp: number;
|
|
272
|
-
charBits: number;
|
|
273
|
-
chars: number;
|
|
274
|
-
}[];
|
|
275
|
-
timestampProperty: string;
|
|
276
|
-
uniqueProperty: string;
|
|
277
|
-
}>;
|
|
278
|
-
generatedProperties: {
|
|
279
|
-
sharded: Record<string, [string, ...string[]]>;
|
|
280
|
-
unsharded: Record<string, [string, ...string[]]>;
|
|
281
|
-
};
|
|
282
|
-
propertyTranscodes: Record<string, string>;
|
|
283
|
-
transcodes: Record<string, {
|
|
284
|
-
encode: (args_0: any, ...args: unknown[]) => string;
|
|
285
|
-
decode: (args_0: string, ...args: unknown[]) => any;
|
|
286
|
-
}>;
|
|
287
|
-
indexes: Record<string, {
|
|
288
|
-
hashKey: string;
|
|
289
|
-
rangeKey: string;
|
|
290
|
-
projections?: string[] | undefined;
|
|
291
|
-
}>;
|
|
292
|
-
generatedKeyDelimiter: string;
|
|
293
|
-
generatedValueDelimiter: string;
|
|
294
|
-
shardKeyDelimiter: string;
|
|
295
|
-
throttle: number;
|
|
296
|
-
}, {
|
|
297
|
-
hashKey: string;
|
|
298
|
-
rangeKey: string;
|
|
299
|
-
entities?: Record<string, {
|
|
300
|
-
timestampProperty: string;
|
|
301
|
-
uniqueProperty: string;
|
|
302
|
-
defaultLimit?: number | undefined;
|
|
303
|
-
defaultPageSize?: number | undefined;
|
|
304
|
-
shardBumps?: {
|
|
305
|
-
timestamp: number;
|
|
306
|
-
charBits: number;
|
|
307
|
-
chars: number;
|
|
308
|
-
}[] | undefined;
|
|
309
|
-
}> | undefined;
|
|
310
|
-
generatedProperties?: {
|
|
311
|
-
sharded?: Record<string, [string, ...string[]]> | undefined;
|
|
312
|
-
unsharded?: Record<string, [string, ...string[]]> | undefined;
|
|
313
|
-
} | undefined;
|
|
314
|
-
propertyTranscodes?: Record<string, string> | undefined;
|
|
315
|
-
transcodes?: Record<string, {
|
|
316
|
-
encode: (args_0: any, ...args: unknown[]) => string;
|
|
317
|
-
decode: (args_0: string, ...args: unknown[]) => any;
|
|
318
|
-
}> | undefined;
|
|
319
|
-
indexes?: Record<string, {
|
|
320
|
-
hashKey: string;
|
|
321
|
-
rangeKey: string;
|
|
322
|
-
projections?: string[] | undefined;
|
|
323
|
-
}> | undefined;
|
|
324
|
-
generatedKeyDelimiter?: string | undefined;
|
|
325
|
-
generatedValueDelimiter?: string | undefined;
|
|
326
|
-
shardKeyDelimiter?: string | undefined;
|
|
327
|
-
throttle?: number | undefined;
|
|
328
|
-
}>, {
|
|
329
|
-
hashKey: string;
|
|
330
|
-
rangeKey: string;
|
|
331
|
-
entities: Record<string, {
|
|
332
|
-
defaultLimit: number;
|
|
333
|
-
defaultPageSize: number;
|
|
334
|
-
shardBumps: {
|
|
335
|
-
timestamp: number;
|
|
336
|
-
charBits: number;
|
|
337
|
-
chars: number;
|
|
338
|
-
}[];
|
|
339
|
-
timestampProperty: string;
|
|
340
|
-
uniqueProperty: string;
|
|
341
|
-
}>;
|
|
342
|
-
generatedProperties: {
|
|
343
|
-
sharded: Record<string, [string, ...string[]]>;
|
|
344
|
-
unsharded: Record<string, [string, ...string[]]>;
|
|
345
|
-
};
|
|
346
|
-
propertyTranscodes: Record<string, string>;
|
|
347
|
-
transcodes: Record<string, {
|
|
348
|
-
encode: (args_0: any, ...args: unknown[]) => string;
|
|
349
|
-
decode: (args_0: string, ...args: unknown[]) => any;
|
|
350
|
-
}>;
|
|
351
|
-
indexes: Record<string, {
|
|
352
|
-
hashKey: string;
|
|
353
|
-
rangeKey: string;
|
|
354
|
-
projections?: string[] | undefined;
|
|
355
|
-
}>;
|
|
356
|
-
generatedKeyDelimiter: string;
|
|
357
|
-
generatedValueDelimiter: string;
|
|
358
|
-
shardKeyDelimiter: string;
|
|
359
|
-
throttle: number;
|
|
360
|
-
}, {
|
|
361
|
-
hashKey: string;
|
|
362
|
-
rangeKey: string;
|
|
363
|
-
entities?: Record<string, {
|
|
364
|
-
timestampProperty: string;
|
|
365
|
-
uniqueProperty: string;
|
|
366
|
-
defaultLimit?: number | undefined;
|
|
367
|
-
defaultPageSize?: number | undefined;
|
|
368
|
-
shardBumps?: {
|
|
369
|
-
timestamp: number;
|
|
370
|
-
charBits: number;
|
|
371
|
-
chars: number;
|
|
372
|
-
}[] | undefined;
|
|
373
|
-
}> | undefined;
|
|
374
|
-
generatedProperties?: {
|
|
375
|
-
sharded?: Record<string, [string, ...string[]]> | undefined;
|
|
376
|
-
unsharded?: Record<string, [string, ...string[]]> | undefined;
|
|
377
|
-
} | undefined;
|
|
378
|
-
propertyTranscodes?: Record<string, string> | undefined;
|
|
379
|
-
transcodes?: Record<string, {
|
|
380
|
-
encode: (args_0: any, ...args: unknown[]) => string;
|
|
381
|
-
decode: (args_0: string, ...args: unknown[]) => any;
|
|
382
|
-
}> | undefined;
|
|
383
|
-
indexes?: Record<string, {
|
|
384
|
-
hashKey: string;
|
|
385
|
-
rangeKey: string;
|
|
386
|
-
projections?: string[] | undefined;
|
|
387
|
-
}> | undefined;
|
|
388
|
-
generatedKeyDelimiter?: string | undefined;
|
|
389
|
-
generatedValueDelimiter?: string | undefined;
|
|
390
|
-
shardKeyDelimiter?: string | undefined;
|
|
391
|
-
throttle?: number | undefined;
|
|
392
|
-
}>;
|
|
197
|
+
encode: z.ZodCustom<unknown, unknown>;
|
|
198
|
+
decode: z.ZodCustom<unknown, unknown>;
|
|
199
|
+
}, z.core.$strict>>>>;
|
|
200
|
+
}, z.core.$strict>;
|
|
393
201
|
/**
|
|
394
202
|
* Simplified type taken on by a {@link Config | `Config`} object after parsing in the {@link EntityManager | `EntityManager`} constructor.
|
|
395
203
|
*
|
|
@@ -612,7 +420,7 @@ declare class EntityManager<C extends BaseConfigMap> {
|
|
|
612
420
|
* Update generated properties, hash key, and range key on an array of {@link EntityItem | `EntityItem`} objects.
|
|
613
421
|
*
|
|
614
422
|
* @param entityToken - {@link Config | `Config`} `entities` key.
|
|
615
|
-
* @param
|
|
423
|
+
* @param item - Array of {@link EntityItem | `EntityItem`} objects.
|
|
616
424
|
* @param overwrite - Overwrite existing properties (default `false`).
|
|
617
425
|
*
|
|
618
426
|
* @returns An array of {@link EntityRecord | `EntityRecord`} objects with updated properties.
|
|
@@ -817,4 +625,5 @@ declare abstract class BaseQueryBuilder<C extends BaseConfigMap, EntityClient ex
|
|
|
817
625
|
query(options: QueryBuilderQueryOptions<C>): Promise<QueryResult<C>>;
|
|
818
626
|
}
|
|
819
627
|
|
|
820
|
-
export {
|
|
628
|
+
export { BaseEntityClient, BaseQueryBuilder, EntityManager, configSchema };
|
|
629
|
+
export type { BaseConfigMap, BaseEntityClientOptions, BaseQueryBuilderOptions, Config, ConfigMap, EntityItem, EntityKey, EntityRecord, EntityToken, PageKey, ParsedConfig, QueryBuilderQueryOptions, QueryOptions, QueryResult, ShardBump, ShardQueryFunction, ShardQueryMap, ShardQueryResult, ValidateConfigMap };
|
|
@@ -8,7 +8,7 @@ const validateArrayUnique = (arr, ctx, identity = (item) => item, path = []) =>
|
|
|
8
8
|
for (const [element, count] of Object.entries(counts)) {
|
|
9
9
|
if (count > 1)
|
|
10
10
|
ctx.addIssue({
|
|
11
|
-
code:
|
|
11
|
+
code: 'custom',
|
|
12
12
|
message: `duplicate array element`,
|
|
13
13
|
params: { element },
|
|
14
14
|
path,
|
|
@@ -19,7 +19,7 @@ const validateKeysExclusive = (keys, label, ref, ctx) => {
|
|
|
19
19
|
const intersection = keys.filter((key) => ref.includes(key));
|
|
20
20
|
if (intersection.length)
|
|
21
21
|
ctx.addIssue({
|
|
22
|
-
code:
|
|
22
|
+
code: 'custom',
|
|
23
23
|
message: `${label} key collision: ${intersection.toString()}`,
|
|
24
24
|
});
|
|
25
25
|
};
|
|
@@ -30,26 +30,14 @@ const componentArray = z
|
|
|
30
30
|
const configSchema = z
|
|
31
31
|
.object({
|
|
32
32
|
entities: z
|
|
33
|
-
.record(z
|
|
33
|
+
.record(z.string(), z
|
|
34
34
|
.object({
|
|
35
|
-
defaultLimit: z
|
|
36
|
-
|
|
37
|
-
.int()
|
|
38
|
-
.positive()
|
|
39
|
-
.safe()
|
|
40
|
-
.optional()
|
|
41
|
-
.default(10),
|
|
42
|
-
defaultPageSize: z
|
|
43
|
-
.number()
|
|
44
|
-
.int()
|
|
45
|
-
.positive()
|
|
46
|
-
.safe()
|
|
47
|
-
.optional()
|
|
48
|
-
.default(10),
|
|
35
|
+
defaultLimit: z.number().int().positive().optional().default(10),
|
|
36
|
+
defaultPageSize: z.number().int().positive().optional().default(10),
|
|
49
37
|
shardBumps: z
|
|
50
38
|
.array(z
|
|
51
39
|
.object({
|
|
52
|
-
timestamp: z.number().
|
|
40
|
+
timestamp: z.number().int().nonnegative(),
|
|
53
41
|
charBits: z.number().int().min(1).max(5),
|
|
54
42
|
chars: z.number().int().min(0).max(40),
|
|
55
43
|
})
|
|
@@ -77,7 +65,7 @@ const configSchema = z
|
|
|
77
65
|
for (let i = 1; i < val.length; i++)
|
|
78
66
|
if (val[i].chars <= val[i - 1].chars)
|
|
79
67
|
ctx.addIssue({
|
|
80
|
-
code:
|
|
68
|
+
code: 'custom',
|
|
81
69
|
message: `shardBump chars do not monotonically increase at timestamp ${val[i].timestamp.toString()}`,
|
|
82
70
|
path: [i],
|
|
83
71
|
});
|
|
@@ -91,14 +79,14 @@ const configSchema = z
|
|
|
91
79
|
.default({}),
|
|
92
80
|
generatedProperties: z
|
|
93
81
|
.object({
|
|
94
|
-
sharded: z.record(componentArray).optional().default({}),
|
|
95
|
-
unsharded: z.record(componentArray).optional().default({}),
|
|
82
|
+
sharded: z.record(z.string(), componentArray).optional().default({}),
|
|
83
|
+
unsharded: z.record(z.string(), componentArray).optional().default({}),
|
|
96
84
|
})
|
|
97
85
|
.optional()
|
|
98
86
|
.default({ sharded: {}, unsharded: {} }),
|
|
99
87
|
hashKey: z.string(),
|
|
100
88
|
indexes: z
|
|
101
|
-
.record(z.object({
|
|
89
|
+
.record(z.string(), z.object({
|
|
102
90
|
hashKey: z.string().min(1),
|
|
103
91
|
rangeKey: z.string().min(1),
|
|
104
92
|
projections: z
|
|
@@ -110,15 +98,18 @@ const configSchema = z
|
|
|
110
98
|
.default({}),
|
|
111
99
|
generatedKeyDelimiter: z.string().regex(/\W+/).optional().default('|'),
|
|
112
100
|
generatedValueDelimiter: z.string().regex(/\W+/).optional().default('#'),
|
|
113
|
-
propertyTranscodes: z.record(z.string()).optional().default({}),
|
|
101
|
+
propertyTranscodes: z.record(z.string(), z.string()).optional().default({}),
|
|
114
102
|
rangeKey: z.string(),
|
|
115
103
|
shardKeyDelimiter: z.string().regex(/\W+/).optional().default('!'),
|
|
116
|
-
throttle: z.number().int().positive().
|
|
104
|
+
throttle: z.number().int().positive().optional().default(10),
|
|
117
105
|
transcodes: z
|
|
118
|
-
.record(z
|
|
106
|
+
.record(z.string(), z
|
|
119
107
|
.object({
|
|
120
|
-
|
|
121
|
-
|
|
108
|
+
// Accept function shapes without relying on z.function()
|
|
109
|
+
// to avoid TS inference conflicts across Zod versions and
|
|
110
|
+
// to remain compatible with narrower parameter types.
|
|
111
|
+
encode: z.custom((fn) => typeof fn === 'function'),
|
|
112
|
+
decode: z.custom((fn) => typeof fn === 'function'),
|
|
122
113
|
})
|
|
123
114
|
.strict())
|
|
124
115
|
.optional()
|
|
@@ -129,7 +120,7 @@ const configSchema = z
|
|
|
129
120
|
// validate no generated key delimiter collision
|
|
130
121
|
if (data.generatedKeyDelimiter.includes(data.generatedValueDelimiter))
|
|
131
122
|
ctx.addIssue({
|
|
132
|
-
code:
|
|
123
|
+
code: 'custom',
|
|
133
124
|
message: 'generatedKeyDelimiter contains generatedValueDelimiter',
|
|
134
125
|
params: {
|
|
135
126
|
generatedKeyDelimiter: data.generatedKeyDelimiter,
|
|
@@ -139,7 +130,7 @@ const configSchema = z
|
|
|
139
130
|
});
|
|
140
131
|
if (data.generatedKeyDelimiter.includes(data.shardKeyDelimiter))
|
|
141
132
|
ctx.addIssue({
|
|
142
|
-
code:
|
|
133
|
+
code: 'custom',
|
|
143
134
|
message: 'generatedKeyDelimiter contains shardKeyDelimiter',
|
|
144
135
|
params: {
|
|
145
136
|
generatedKeyDelimiter: data.generatedKeyDelimiter,
|
|
@@ -150,7 +141,7 @@ const configSchema = z
|
|
|
150
141
|
// validate no generated value delimiter collision
|
|
151
142
|
if (data.generatedValueDelimiter.includes(data.generatedKeyDelimiter))
|
|
152
143
|
ctx.addIssue({
|
|
153
|
-
code:
|
|
144
|
+
code: 'custom',
|
|
154
145
|
message: 'generatedValueDelimiter contains generatedKeyDelimiter',
|
|
155
146
|
params: {
|
|
156
147
|
generatedValueDelimiter: data.generatedValueDelimiter,
|
|
@@ -160,7 +151,7 @@ const configSchema = z
|
|
|
160
151
|
});
|
|
161
152
|
if (data.generatedValueDelimiter.includes(data.shardKeyDelimiter))
|
|
162
153
|
ctx.addIssue({
|
|
163
|
-
code:
|
|
154
|
+
code: 'custom',
|
|
164
155
|
message: 'generatedValueDelimiter contains shardKeyDelimiter',
|
|
165
156
|
params: {
|
|
166
157
|
generatedValueDelimiter: data.generatedValueDelimiter,
|
|
@@ -171,7 +162,7 @@ const configSchema = z
|
|
|
171
162
|
// validate no shard key delimiter collision
|
|
172
163
|
if (data.shardKeyDelimiter.includes(data.generatedKeyDelimiter))
|
|
173
164
|
ctx.addIssue({
|
|
174
|
-
code:
|
|
165
|
+
code: 'custom',
|
|
175
166
|
message: 'shardKeyDelimiter contains generatedKeyDelimiter',
|
|
176
167
|
params: {
|
|
177
168
|
generatedKeyDelimiter: data.generatedKeyDelimiter,
|
|
@@ -181,7 +172,7 @@ const configSchema = z
|
|
|
181
172
|
});
|
|
182
173
|
if (data.shardKeyDelimiter.includes(data.generatedValueDelimiter))
|
|
183
174
|
ctx.addIssue({
|
|
184
|
-
code:
|
|
175
|
+
code: 'custom',
|
|
185
176
|
message: 'shardKeyDelimiter contains generatedValueDelimiter',
|
|
186
177
|
params: {
|
|
187
178
|
generatedValueDelimiter: data.generatedValueDelimiter,
|
|
@@ -211,19 +202,17 @@ const configSchema = z
|
|
|
211
202
|
for (const [property, transcode] of Object.entries(data.propertyTranscodes))
|
|
212
203
|
if (!transcodes.includes(transcode))
|
|
213
204
|
ctx.addIssue({
|
|
214
|
-
code:
|
|
215
|
-
|
|
205
|
+
code: 'custom',
|
|
206
|
+
message: `propertyTranscodes['${property}'] references unknown transcode '${transcode}'`,
|
|
216
207
|
path: ['propertyTranscodes', property],
|
|
217
|
-
received: transcode,
|
|
218
208
|
});
|
|
219
209
|
// Validate all sharded property elements are transcoded properties.
|
|
220
210
|
for (const [property, elements] of Object.entries(data.generatedProperties.sharded))
|
|
221
211
|
for (const element of elements)
|
|
222
212
|
if (!transcodedProperties.includes(element))
|
|
223
213
|
ctx.addIssue({
|
|
224
|
-
code:
|
|
225
|
-
|
|
226
|
-
received: element,
|
|
214
|
+
code: 'custom',
|
|
215
|
+
message: `generatedProperties.sharded['${property}'] contains non-transcoded element '${element}'`,
|
|
227
216
|
path: ['generatedProperties', 'sharded', property],
|
|
228
217
|
});
|
|
229
218
|
// Validate all unsharded property elements are transcoded properties.
|
|
@@ -231,9 +220,8 @@ const configSchema = z
|
|
|
231
220
|
for (const element of elements)
|
|
232
221
|
if (!transcodedProperties.includes(element))
|
|
233
222
|
ctx.addIssue({
|
|
234
|
-
code:
|
|
235
|
-
|
|
236
|
-
received: element,
|
|
223
|
+
code: 'custom',
|
|
224
|
+
message: `generatedProperties.unsharded['${property}'] contains non-transcoded element '${element}'`,
|
|
237
225
|
path: ['generatedProperties', 'unsharded', property],
|
|
238
226
|
});
|
|
239
227
|
// Validate indexes.
|
|
@@ -242,19 +230,24 @@ const configSchema = z
|
|
|
242
230
|
// Validate hash key is sharded.
|
|
243
231
|
if (![data.hashKey, ...shardedKeys].includes(hashKey)) {
|
|
244
232
|
ctx.addIssue({
|
|
245
|
-
code:
|
|
246
|
-
|
|
233
|
+
code: 'custom',
|
|
234
|
+
message: `index '${indexKey}' hashKey '${hashKey}' must be one of [${[
|
|
235
|
+
data.hashKey,
|
|
236
|
+
...shardedKeys,
|
|
237
|
+
].join(', ')}]`,
|
|
247
238
|
path: ['indexes', indexKey, 'hashKey'],
|
|
248
|
-
received: hashKey,
|
|
249
239
|
});
|
|
250
240
|
}
|
|
251
241
|
// Validate range key is unsharded or transcodable.
|
|
252
242
|
if (![data.rangeKey, ...unshardedKeys, ...transcodedProperties].includes(rangeKey)) {
|
|
253
243
|
ctx.addIssue({
|
|
254
|
-
code:
|
|
255
|
-
|
|
244
|
+
code: 'custom',
|
|
245
|
+
message: `index '${indexKey}' rangeKey '${rangeKey}' must be one of [${[
|
|
246
|
+
data.rangeKey,
|
|
247
|
+
...unshardedKeys,
|
|
248
|
+
...transcodedProperties,
|
|
249
|
+
].join(', ')}]`,
|
|
256
250
|
path: ['indexes', indexKey, 'rangeKey'],
|
|
257
|
-
received: rangeKey,
|
|
258
251
|
});
|
|
259
252
|
}
|
|
260
253
|
// Validate no index projections are keys.
|
|
@@ -269,7 +262,7 @@ const configSchema = z
|
|
|
269
262
|
...unshardedKeys,
|
|
270
263
|
].includes(projection))
|
|
271
264
|
ctx.addIssue({
|
|
272
|
-
code:
|
|
265
|
+
code: 'custom',
|
|
273
266
|
message: 'index projection is a key',
|
|
274
267
|
params: { projection },
|
|
275
268
|
path: ['indexes', indexKey, 'projections'],
|
|
@@ -280,18 +273,16 @@ const configSchema = z
|
|
|
280
273
|
// validate timestampProperty is a transcoded property.
|
|
281
274
|
if (!transcodedProperties.includes(timestampProperty))
|
|
282
275
|
ctx.addIssue({
|
|
283
|
-
code:
|
|
284
|
-
|
|
276
|
+
code: 'custom',
|
|
277
|
+
message: `entities['${entityToken}'].timestampProperty '${timestampProperty}' must be one of [${transcodedProperties.join(', ')}]`,
|
|
285
278
|
path: ['entities', entityToken, 'timestampProperty'],
|
|
286
|
-
received: timestampProperty,
|
|
287
279
|
});
|
|
288
280
|
// validate uniqueProperty is a transcoded property.
|
|
289
281
|
if (!transcodedProperties.includes(uniqueProperty))
|
|
290
282
|
ctx.addIssue({
|
|
291
|
-
code:
|
|
292
|
-
|
|
283
|
+
code: 'custom',
|
|
284
|
+
message: `entities['${entityToken}'].uniqueProperty '${uniqueProperty}' must be one of [${transcodedProperties.join(', ')}]`,
|
|
293
285
|
path: ['entities', entityToken, 'uniqueProperty'],
|
|
294
|
-
received: uniqueProperty,
|
|
295
286
|
});
|
|
296
287
|
}
|
|
297
288
|
});
|
|
@@ -22,7 +22,8 @@ function decodeElement(entityManager, element, value) {
|
|
|
22
22
|
if (!value)
|
|
23
23
|
return;
|
|
24
24
|
const { propertyTranscodes, transcodes } = entityManager.config;
|
|
25
|
-
const
|
|
25
|
+
const decodeFn = transcodes[propertyTranscodes[element]].decode;
|
|
26
|
+
const decoded = decodeFn(value);
|
|
26
27
|
entityManager.logger.debug('decoded entity element', {
|
|
27
28
|
element,
|
|
28
29
|
value,
|
|
@@ -38,7 +38,9 @@ function dehydratePageKeyMap(entityManager, entityToken, pageKeyMap) {
|
|
|
38
38
|
}
|
|
39
39
|
// Extract, sort & validate indexs.
|
|
40
40
|
const indexes = Object.keys(pageKeyMap).sort();
|
|
41
|
-
indexes.map((index) =>
|
|
41
|
+
indexes.map((index) => {
|
|
42
|
+
validateIndexToken(entityManager, index);
|
|
43
|
+
});
|
|
42
44
|
// Extract & sort hash keys.
|
|
43
45
|
const hashKeys = Object.keys(pageKeyMap[indexes[0]]);
|
|
44
46
|
// Dehydrate page keys.
|
|
@@ -19,8 +19,8 @@ function encodeElement(entityManager, element, item) {
|
|
|
19
19
|
const value = item[element];
|
|
20
20
|
if (value === undefined || [hashKey, rangeKey].includes(element))
|
|
21
21
|
return value;
|
|
22
|
-
const
|
|
23
|
-
|
|
22
|
+
const encodeFn = transcodes[propertyTranscodes[element]].encode;
|
|
23
|
+
const encoded = encodeFn(item[element]) || undefined;
|
|
24
24
|
entityManager.logger.debug('encoded entity element', {
|
|
25
25
|
element,
|
|
26
26
|
item,
|