@karmaniverous/entity-manager 8.0.0 → 8.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.
@@ -2,6 +2,7 @@
2
2
 
3
3
  var radash = require('radash');
4
4
 
5
+ /* eslint-disable @typescript-eslint/no-unused-vars */
5
6
  /**
6
7
  * Abstract base class supporting a fluent API for building a {@link ShardQueryMap | `ShardQueryMap`} using a database client.
7
8
  *
@@ -36,6 +37,16 @@ class BaseQueryBuilder {
36
37
  build() {
37
38
  return radash.mapValues(this.indexParamsMap, (_indexConfig, indexToken) => this.getShardQueryFunction(indexToken));
38
39
  }
40
+ /**
41
+ * Execute the built query across shards and indexes via {@link EntityManager.query | `EntityManager.query`}.
42
+ *
43
+ * @param options - Query options excluding `entityToken`, `pageKeyMap`, and `shardQueryMap`, which are supplied by the builder.
44
+ *
45
+ * @returns The merged, de-duplicated, sorted query result, including a compact `pageKeyMap` token for the next page.
46
+ *
47
+ * @remarks
48
+ * This delegates orchestration to Entity Manager; provider-specific behavior lives in {@link getShardQueryFunction | `getShardQueryFunction`}.
49
+ */
39
50
  async query(options) {
40
51
  const { entityClient: { entityManager }, entityToken, pageKeyMap, } = this;
41
52
  const shardQueryMap = this.build();
@@ -29,19 +29,54 @@ const componentArray = zod.z
29
29
  .array(zod.z.string().min(1))
30
30
  .nonempty()
31
31
  .superRefine(validateArrayUnique);
32
+ /**
33
+ * Runtime configuration schema for {@link EntityManager | `EntityManager`}.
34
+ *
35
+ * @remarks
36
+ * This is authoritative at runtime and used internally by the {@link EntityManager | `EntityManager`} constructor.
37
+ * It is also used by tests. It is not intended as a user-facing TypeDoc artifact.
38
+ *
39
+ * @hidden
40
+ */
32
41
  const configSchema = zod.z
33
42
  .object({
34
43
  entities: zod.z
35
44
  .record(zod.z.string(), zod.z
36
45
  .object({
37
- defaultLimit: zod.z.number().int().positive().optional().default(10),
38
- defaultPageSize: zod.z.number().int().positive().optional().default(10),
46
+ defaultLimit: zod.z
47
+ .number()
48
+ .int()
49
+ .positive()
50
+ .optional()
51
+ .default(10)
52
+ .describe('Default max items returned by EntityManager.query for this entity (across all shards).'),
53
+ defaultPageSize: zod.z
54
+ .number()
55
+ .int()
56
+ .positive()
57
+ .optional()
58
+ .default(10)
59
+ .describe('Default per-shard page size used by EntityManager.query for this entity.'),
39
60
  shardBumps: zod.z
40
61
  .array(zod.z
41
62
  .object({
42
- timestamp: zod.z.number().int().nonnegative(),
43
- charBits: zod.z.number().int().min(1).max(5),
44
- chars: zod.z.number().int().min(0).max(40),
63
+ timestamp: zod.z
64
+ .number()
65
+ .int()
66
+ .nonnegative()
67
+ .describe('Start timestamp (ms) for this shard bump (inclusive).'),
68
+ charBits: zod.z
69
+ .number()
70
+ .int()
71
+ .min(1)
72
+ .max(5)
73
+ .describe('Bits per shard character (radix = 2**charBits).'),
74
+ chars: zod.z
75
+ .number()
76
+ .int()
77
+ .min(0)
78
+ .max(40)
79
+ .describe('Shard suffix width (chars); controls shard space.'),
45
80
  })
46
81
  .strict())
47
82
  .optional()
@@ -73,45 +108,92 @@ const configSchema = zod.z
73
108
  });
74
109
  }
75
110
  }),
76
- timestampProperty: zod.z.string().min(1),
77
- uniqueProperty: zod.z.string().min(1),
111
+ timestampProperty: zod.z
112
+ .string()
113
+ .min(1)
114
+ .describe('Property token whose value selects the shard bump (typically a timestamp).'),
115
+ uniqueProperty: zod.z
116
+ .string()
117
+ .min(1)
118
+ .describe('Property token used to dedupe and build the global range key.'),
78
119
  })
79
120
  .strict())
80
121
  .optional()
81
- .default({}),
122
+ .default({})
123
+ .describe('Entity definitions keyed by entity token.'),
82
124
  generatedProperties: zod.z
83
125
  .object({
84
- sharded: zod.z.record(zod.z.string(), componentArray).optional().default({}),
85
- unsharded: zod.z.record(zod.z.string(), componentArray).optional().default({}),
126
+ sharded: zod.z
127
+ .record(zod.z.string(), componentArray)
128
+ .optional()
129
+ .default({})
130
+ .describe('Sharded generated property tokens (hash-side); atomic encoding semantics.'),
131
+ unsharded: zod.z
132
+ .record(zod.z.string(), componentArray)
133
+ .optional()
134
+ .default({})
135
+ .describe('Unsharded generated property tokens (range-side); non-atomic encoding semantics.'),
86
136
  })
87
137
  .optional()
88
138
  .default({ sharded: {}, unsharded: {} }),
89
- hashKey: zod.z.string(),
139
+ hashKey: zod.z.string().describe('Global hash key property name.'),
90
140
  indexes: zod.z
91
141
  .record(zod.z.string(), zod.z.object({
92
- hashKey: zod.z.string().min(1),
93
- rangeKey: zod.z.string().min(1),
142
+ hashKey: zod.z
143
+ .string()
144
+ .min(1)
145
+ .describe('Index hash key token (global hash key or a sharded generated key).'),
146
+ rangeKey: zod.z
147
+ .string()
148
+ .min(1)
149
+ .describe('Index range key token (global range key, an unsharded generated key, or a transcoded property).'),
94
150
  projections: zod.z
95
151
  .array(zod.z.string().min(1))
96
152
  .superRefine(validateArrayUnique)
97
153
  .optional(),
98
154
  }))
99
155
  .optional()
100
- .default({}),
101
- generatedKeyDelimiter: zod.z.string().regex(/\W+/).optional().default('|'),
102
- generatedValueDelimiter: zod.z.string().regex(/\W+/).optional().default('#'),
156
+ .default({})
157
+ .describe('Index definitions keyed by index token.'),
158
+ generatedKeyDelimiter: zod.z
159
+ .string()
160
+ .regex(/\W+/)
161
+ .optional()
162
+ .default('|')
163
+ .describe('Delimiter between generated key elements (default `|`).'),
164
+ generatedValueDelimiter: zod.z
165
+ .string()
166
+ .regex(/\W+/)
167
+ .optional()
168
+ .default('#')
169
+ .describe('Delimiter between generated element name and value (default `#`).'),
103
170
  propertyTranscodes: zod.z.record(zod.z.string(), zod.z.string()).optional().default({}),
104
- rangeKey: zod.z.string(),
105
- shardKeyDelimiter: zod.z.string().regex(/\W+/).optional().default('!'),
106
- throttle: zod.z.number().int().positive().optional().default(10),
171
+ rangeKey: zod.z.string().describe('Global range key property name.'),
172
+ shardKeyDelimiter: zod.z
173
+ .string()
174
+ .regex(/\W+/)
175
+ .optional()
176
+ .default('!')
177
+ .describe('Delimiter between entity token and shard suffix in hash key values.'),
178
+ throttle: zod.z
179
+ .number()
180
+ .int()
181
+ .positive()
182
+ .optional()
183
+ .default(10)
184
+ .describe('Default max concurrency for shard queries during EntityManager.query.'),
107
185
  transcodes: zod.z
108
186
  .record(zod.z.string(), zod.z
109
187
  .object({
110
188
  // Accept function shapes without relying on z.function()
111
189
  // to avoid TS inference conflicts across Zod versions and
112
190
  // to remain compatible with narrower parameter types.
113
- encode: zod.z.custom((fn) => typeof fn === 'function'),
114
- decode: zod.z.custom((fn) => typeof fn === 'function'),
191
+ encode: zod.z
192
+ .custom((fn) => typeof fn === 'function')
193
+ .describe('Encode a value to a lexicographically sortable string.'),
194
+ decode: zod.z
195
+ .custom((fn) => typeof fn === 'function')
196
+ .describe('Decode a previously encoded string back to the value type.'),
115
197
  })
116
198
  .strict())
117
199
  .optional()
@@ -1,8 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  function findIndexToken(entityManager, hashKeyToken, rangeKeyToken, suppressError) {
4
- const indexToken = (Object.entries(entityManager.config.indexes).find(([, index]) => index.hashKey === hashKeyToken &&
5
- index.rangeKey === rangeKeyToken)?.[0] ?? undefined);
4
+ const indexToken = (Object.entries(entityManager.config.indexes).find(([, index]) => index.hashKey === hashKeyToken && index.rangeKey === rangeKeyToken)?.[0] ?? undefined);
6
5
  if (!indexToken && !suppressError)
7
6
  throw new Error(`No index token found for hashKey '${hashKeyToken}' & rangeKey '${rangeKeyToken}'.`);
8
7
  return indexToken;
@@ -17,12 +17,7 @@ function getIndexComponents(entityManager, indexToken) {
17
17
  validateIndexToken.validateIndexToken(entityManager, indexToken);
18
18
  const { hashKey, rangeKey, indexes } = entityManager.config;
19
19
  const { hashKey: indexHashKey, rangeKey: indexRangeKey } = indexes[indexToken];
20
- return radash.unique([
21
- hashKey,
22
- rangeKey,
23
- indexHashKey,
24
- indexRangeKey,
25
- ]);
20
+ return radash.unique([hashKey, rangeKey, indexHashKey, indexRangeKey]);
26
21
  }
27
22
 
28
23
  exports.getIndexComponents = getIndexComponents;
@@ -40,7 +40,7 @@ async function query(entityManager, options) {
40
40
  if (!(radash.isInt(pageSize) && pageSize >= 1))
41
41
  throw new Error('pageSize must be a positive integer');
42
42
  // Rehydrate pageKeyMap.
43
- const [hashKeyToken, rehydratedPageKeyMap] = rehydratePageKeyMap.rehydratePageKeyMap(entityManager, entityToken, Object.keys(shardQueryMap), item, pageKeyMap
43
+ const [hashKeyToken, rehydratedPageKeyMap] = rehydratePageKeyMap.rehydratePageKeyMap(entityManager, entityToken, Object.keys(shardQueryMap).sort(), item, pageKeyMap
44
44
  ? JSON.parse(decompressFromEncodedURIComponent(pageKeyMap))
45
45
  : undefined, timestampFrom, timestampTo);
46
46
  // Shortcut if pageKeyMap is empty.
package/dist/index.d.ts CHANGED
@@ -9,12 +9,19 @@ import { BatchProcessOptions } from '@karmaniverous/batch-process';
9
9
  * @category EntityManager
10
10
  */
11
11
  interface BaseConfigMap {
12
+ /** Entity map type (entity token -\> entity shape). */
12
13
  EntityMap: EntityMap;
14
+ /** Global hash-key property name used by the storage layer. */
13
15
  HashKey: string;
16
+ /** Global range-key property name used by the storage layer. */
14
17
  RangeKey: string;
18
+ /** Union of sharded generated key tokens (string-valued generated properties). */
15
19
  ShardedKeys: string;
20
+ /** Union of unsharded generated key tokens (string-valued generated properties). */
16
21
  UnshardedKeys: string;
22
+ /** Union of transcoded property tokens (domain properties mapped in `propertyTranscodes`). */
17
23
  TranscodedProperties: string;
24
+ /** Transcode registry mapping transcode names to value types. */
18
25
  TranscodeRegistry: TranscodeRegistry;
19
26
  }
20
27
 
@@ -55,25 +62,45 @@ interface ShardBump {
55
62
  */
56
63
  type Config<C extends BaseConfigMap = BaseConfigMap> = ConditionalProperty<'entities', keyof Exactify<C['EntityMap']>, {
57
64
  [E in keyof Exactify<C['EntityMap']>]: {
65
+ /** Default max number of items returned by {@link EntityManager.query | `query`} for this entity (across all shards). */
58
66
  defaultLimit?: number;
67
+ /** Default per-shard page size used by {@link EntityManager.query | `query`} for this entity. */
59
68
  defaultPageSize?: number;
69
+ /** Shard bump schedule for this entity (time-based sharding scale-up). */
60
70
  shardBumps?: ShardBump[];
71
+ /** Property token whose value selects the active shard bump (must be a transcoded numeric property). */
61
72
  timestampProperty: Extract<Extract<C['TranscodedProperties'], PropertiesOfType<C['EntityMap'][E], number>>, TranscodableProperties<C['EntityMap'], C['TranscodeRegistry']>>;
73
+ /** Property token used as the logical unique identifier for this entity (must be a transcoded scalar). */
62
74
  uniqueProperty: Extract<Extract<C['TranscodedProperties'], keyof C['EntityMap'][E]>, TranscodableProperties<C['EntityMap'], C['TranscodeRegistry']>>;
63
75
  };
64
76
  }> & ConditionalProperty<'generatedProperties', C['ShardedKeys'] | C['UnshardedKeys'], ConditionalProperty<'sharded', C['ShardedKeys'], Record<C['ShardedKeys'], (C['TranscodedProperties'] & TranscodableProperties<C['EntityMap'], C['TranscodeRegistry']>)[]>> & ConditionalProperty<'unsharded', C['UnshardedKeys'], Record<C['UnshardedKeys'], (C['TranscodedProperties'] & TranscodableProperties<C['EntityMap'], C['TranscodeRegistry']>)[]>>> & ConditionalProperty<'propertyTranscodes', C['TranscodedProperties'] & TranscodableProperties<C['EntityMap'], C['TranscodeRegistry']>, {
65
77
  [P in C['TranscodedProperties'] & TranscodableProperties<C['EntityMap'], C['TranscodeRegistry']>]: PropertiesOfType<C['TranscodeRegistry'], FlattenEntityMap<C['EntityMap']>[P]>;
66
78
  }> & ConditionalProperty<'transcodes', keyof C['TranscodeRegistry'], Transcodes<C['TranscodeRegistry']>> & {
79
+ /** Delimiter between generated key elements (default `|`). Must not collide with other delimiters. */
67
80
  generatedKeyDelimiter?: string;
81
+ /** Delimiter between a generated property key and its encoded value (default `#`). Must not collide with other delimiters. */
68
82
  generatedValueDelimiter?: string;
83
+ /** Global hash key property name. */
69
84
  hashKey: C['HashKey'];
85
+ /**
86
+ * Index token map. Keys are index names; values define the index hash/range key tokens and optional projections.
87
+ *
88
+ * @remarks
89
+ * This is provider-agnostic metadata used for page-key narrowing and (de)hydration; provider adapters map this to platform-specific indexes.
90
+ */
70
91
  indexes?: Record<string, {
92
+ /** Index hash key token (global hash key or a sharded generated key token). */
71
93
  hashKey: C['HashKey'] | C['ShardedKeys'];
94
+ /** Index range key token (global range key, an unsharded generated key token, or a transcoded scalar property token). */
72
95
  rangeKey: C['RangeKey'] | C['UnshardedKeys'] | (C['TranscodedProperties'] & TranscodableProperties<C['EntityMap'], C['TranscodeRegistry']>);
96
+ /** Optional list of projected attribute names (validated to exclude key tokens). */
73
97
  projections?: string[];
74
98
  }>;
99
+ /** Global range key property name. */
75
100
  rangeKey: C['RangeKey'];
101
+ /** Delimiter between entity token and shard suffix in the global hash key value (default `!`). */
76
102
  shardKeyDelimiter?: string;
103
+ /** Maximum number of shard queries to execute concurrently during {@link EntityManager.query | `query`}. */
77
104
  throttle?: number;
78
105
  };
79
106
 
@@ -109,12 +136,19 @@ type ValidateConfigMap<CC extends BaseConfigMap> = MutuallyExclusive<[
109
136
  * @category EntityManager
110
137
  */
111
138
  type ConfigMap<M extends Partial<BaseConfigMap> = Partial<BaseConfigMap>> = ValidateConfigMap<{
139
+ /** Entity map type (entity token -\> entity shape). */
112
140
  EntityMap: 'EntityMap' extends keyof M ? NonNullable<M['EntityMap']> : Record<string, never>;
141
+ /** Global hash key property name (defaults to `"hashKey"`). */
113
142
  HashKey: 'HashKey' extends keyof M ? NonNullable<M['HashKey']> : 'hashKey';
143
+ /** Global range key property name (defaults to `"rangeKey"`). */
114
144
  RangeKey: 'RangeKey' extends keyof M ? NonNullable<M['RangeKey']> : 'rangeKey';
145
+ /** Union of sharded generated key tokens (defaults to `never`). */
115
146
  ShardedKeys: 'ShardedKeys' extends keyof M ? NonNullable<M['ShardedKeys']> : never;
147
+ /** Union of unsharded generated key tokens (defaults to `never`). */
116
148
  UnshardedKeys: 'UnshardedKeys' extends keyof M ? NonNullable<M['UnshardedKeys']> : never;
149
+ /** Union of transcoded property tokens (defaults to `never`). */
117
150
  TranscodedProperties: 'TranscodedProperties' extends keyof M ? NonNullable<M['TranscodedProperties']> : never;
151
+ /** Transcode registry type (defaults to {@link DefaultTranscodeRegistry | `DefaultTranscodeRegistry`}). */
118
152
  TranscodeRegistry: 'TranscodeRegistry' extends keyof M ? NonNullable<M['TranscodeRegistry']> : DefaultTranscodeRegistry;
119
153
  }>;
120
154
 
@@ -167,13 +201,23 @@ type PageKey<CC extends BaseConfigMap> = Pick<StorageItem<CC>, CC['HashKey'] | C
167
201
  * literal types when available.
168
202
  */
169
203
  type IndexHashKeyOf<CF, IT extends string> = CF extends {
204
+ /** Optional values-first index map used for literal narrowing. */
170
205
  indexes?: infer I;
171
206
  } ? I extends Record<string, unknown> ? IT extends keyof I ? I[IT] extends {
207
+ /** Index hash key token. */
172
208
  hashKey: infer HK;
173
209
  } ? HK & string : never : never : never : never;
210
+ /**
211
+ * Derive the index range-key token for a specific index token.
212
+ *
213
+ * When CF carries an `indexes` object and IT is a member key, this extracts the
214
+ * concrete `rangeKey` token type.
215
+ */
174
216
  type IndexRangeKeyOf<CF, IT extends string> = CF extends {
217
+ /** Optional values-first index map used for literal narrowing. */
175
218
  indexes?: infer I;
176
219
  } ? I extends Record<string, unknown> ? IT extends keyof I ? I[IT] extends {
220
+ /** Index range key token. */
177
221
  rangeKey: infer RK;
178
222
  } ? RK & string : never : never : never : never;
179
223
  /**
@@ -183,9 +227,17 @@ type IndexRangeKeyOf<CF, IT extends string> = CF extends {
183
227
  * this helper captures the index token union. Falls back to `string` if absent.
184
228
  */
185
229
  type IndexTokensOf<CF> = CF extends {
230
+ /** Optional values-first index map used for index-token narrowing. */
186
231
  indexes?: infer I;
187
232
  } ? I extends Record<string, unknown> ? Extract<keyof I, string> : string : string;
233
+ /**
234
+ * Test whether a values-first config literal CF carries a concrete index IT.
235
+ *
236
+ * @remarks
237
+ * Used to decide whether index-aware narrowing is available.
238
+ */
188
239
  type HasIndexFor<CF, IT extends string> = CF extends {
240
+ /** Optional values-first index map used for literal narrowing. */
189
241
  indexes?: infer I;
190
242
  } ? I extends Record<string, unknown> ? IT extends keyof I ? true : false : false : false;
191
243
  /**
@@ -216,6 +268,13 @@ type PresentIndexTokenSet<CC extends BaseConfigMap, CF, IT extends string> = Rec
216
268
  * @category QueryBuilder
217
269
  */
218
270
  type FallbackIndexTokenSet<CC extends BaseConfigMap> = Record<CC['HashKey'] | CC['RangeKey'] | CC['ShardedKeys'] | CC['UnshardedKeys'] | CC['TranscodedProperties'], true>;
271
+ /**
272
+ * Derive the union of token names that may appear in a page key for a specific index.
273
+ *
274
+ * @remarks
275
+ * If CF carries a concrete `indexes` map and IT is a member key, this narrows to the
276
+ * exact component-token set for that index. Otherwise, it falls back to the broad key set.
277
+ */
219
278
  type IndexComponentTokens<CC extends BaseConfigMap, CF, IT extends string> = HasIndexFor<CF, IT> extends true ? keyof PresentIndexTokenSet<CC, CF, IT> : keyof FallbackIndexTokenSet<CC>;
220
279
  /**
221
280
  * Page key typed for a specific index token.
@@ -226,6 +285,15 @@ type IndexComponentTokens<CC extends BaseConfigMap, CF, IT extends string> = Has
226
285
  */
227
286
  type PageKeyByIndex<CC extends BaseConfigMap, ET extends EntityToken<CC>, IT extends string = string, CF = unknown> = Pick<StorageItem<CC>, IndexComponentTokens<CC, CF, IT>>;
228
287
 
288
+ /**
289
+ * Runtime configuration schema for {@link EntityManager | `EntityManager`}.
290
+ *
291
+ * @remarks
292
+ * This is authoritative at runtime and used internally by the {@link EntityManager | `EntityManager`} constructor.
293
+ * It is also used by tests. It is not intended as a user-facing TypeDoc artifact.
294
+ *
295
+ * @hidden
296
+ */
229
297
  declare const configSchema: z$1.ZodObject<{
230
298
  entities: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodRecord<z$1.ZodString, z$1.ZodObject<{
231
299
  defaultLimit: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodNumber>>;
@@ -268,11 +336,88 @@ declare const configSchema: z$1.ZodObject<{
268
336
  }, z$1.core.$strict>>>>;
269
337
  }, z$1.core.$strict>;
270
338
  /**
271
- * Simplified type taken on by a {@link Config | `Config`} object after parsing in the {@link EntityManager | `EntityManager`} constructor.
339
+ * Parsed transcoder entry.
340
+ *
341
+ * @remarks
342
+ * This reflects the runtime contract enforced by Zod for entries in `transcodes`.
343
+ */
344
+ interface ParsedTranscoder {
345
+ /** Encode a value to a lexicographically sortable string. */
346
+ encode: unknown;
347
+ /** Decode a previously encoded string back to the value type. */
348
+ decode: unknown;
349
+ }
350
+ /**
351
+ * Parsed index definition (provider-agnostic).
352
+ *
353
+ * @remarks
354
+ * Provider adapters map these tokens to provider-specific index queries.
355
+ */
356
+ interface ParsedIndexConfig {
357
+ /** Index hash key token (global hash key or a sharded generated key). */
358
+ hashKey: string;
359
+ /** Index range key token (global range key, an unsharded generated key, or a transcoded scalar). */
360
+ rangeKey: string;
361
+ /** Optional list of projected attribute names (validated to exclude key tokens). */
362
+ projections?: string[] | undefined;
363
+ }
364
+ /**
365
+ * Parsed generated properties configuration.
366
+ */
367
+ interface ParsedGeneratedPropertiesConfig {
368
+ /** Sharded generated property tokens (hash-side); atomic encoding semantics. */
369
+ sharded: Record<string, string[]>;
370
+ /** Unsharded generated property tokens (range-side); non-atomic encoding semantics. */
371
+ unsharded: Record<string, string[]>;
372
+ }
373
+ /**
374
+ * Parsed per-entity configuration.
375
+ */
376
+ interface ParsedEntityConfig {
377
+ /** Default max items returned by EntityManager.query for this entity (across all shards). */
378
+ defaultLimit: number;
379
+ /** Default per-shard page size used by EntityManager.query for this entity. */
380
+ defaultPageSize: number;
381
+ /** Shard bump schedule for this entity (time-based sharding scale-up). */
382
+ shardBumps: ShardBump[];
383
+ /** Property token whose value selects the shard bump (typically a timestamp). */
384
+ timestampProperty: string;
385
+ /** Property token used to dedupe and build the global range key. */
386
+ uniqueProperty: string;
387
+ }
388
+ /**
389
+ * Simplified runtime configuration shape after parsing/validation.
390
+ *
391
+ * @remarks
392
+ * This is the type exposed by {@link EntityManager.config | `EntityManager.config`}.
393
+ * It mirrors the validated Zod schema output.
272
394
  *
273
395
  * @category EntityManager
274
396
  */
275
- type ParsedConfig = z$1.infer<typeof configSchema>;
397
+ interface ParsedConfig {
398
+ /** Entity definitions keyed by entity token. */
399
+ entities: Record<string, ParsedEntityConfig>;
400
+ /** Generated property token maps. */
401
+ generatedProperties: ParsedGeneratedPropertiesConfig;
402
+ /** Global hash key property name. */
403
+ hashKey: string;
404
+ /** Provider-agnostic index definitions keyed by index token. */
405
+ indexes: Record<string, ParsedIndexConfig>;
406
+ /** Delimiter between generated key elements (default `|`). */
407
+ generatedKeyDelimiter: string;
408
+ /** Delimiter between generated element name and value (default `#`). */
409
+ generatedValueDelimiter: string;
410
+ /** Map of transcoded property token -\> transcode name. */
411
+ propertyTranscodes: Record<string, string>;
412
+ /** Global range key property name. */
413
+ rangeKey: string;
414
+ /** Delimiter between entity token and shard suffix in hash key values (default `!`). */
415
+ shardKeyDelimiter: string;
416
+ /** Default max concurrency for shard queries during EntityManager.query. */
417
+ throttle: number;
418
+ /** Transcoder registry used for encoding/decoding values. */
419
+ transcodes: Record<string, ParsedTranscoder>;
420
+ }
276
421
 
277
422
  /** EntityOfToken — resolves the concrete entity shape for a specific entity token. */
278
423
  type EntityOfToken<CC extends BaseConfigMap, ET extends EntityToken<CC>> = Exactify<CC['EntityMap']>[ET];
@@ -340,6 +485,7 @@ interface ShardQueryResult<CC extends BaseConfigMap, ET extends EntityToken<CC>,
340
485
  * @protected
341
486
  */
342
487
  type ShardQueryFunction<CC extends BaseConfigMap, ET extends EntityToken<CC>, IT extends string, CF = unknown, K = unknown> = CF extends {
488
+ /** Optional values-first index map used for index-token narrowing. */
343
489
  indexes?: infer I;
344
490
  } ? I extends Record<string, unknown> ? IT extends Extract<keyof I, string> ? (hashKey: string, pageKey?: PageKeyByIndex<CC, ET, IT, CF>, pageSize?: number) => Promise<ShardQueryResult<CC, ET, IT, CF, K>> : never : (hashKey: string, pageKey?: PageKeyByIndex<CC, ET, IT, CF>, pageSize?: number) => Promise<ShardQueryResult<CC, ET, IT, CF, K>> : (hashKey: string, pageKey?: PageKeyByIndex<CC, ET, IT, CF>, pageSize?: number) => Promise<ShardQueryResult<CC, ET, IT, CF, K>>;
345
491
 
@@ -360,6 +506,7 @@ type ShardQueryFunction<CC extends BaseConfigMap, ET extends EntityToken<CC>, IT
360
506
  * @protected
361
507
  */
362
508
  type ShardQueryMap<CC extends BaseConfigMap, ET extends EntityToken<CC>, ITS extends string, CF = unknown, K = unknown> = CF extends {
509
+ /** Optional values-first index map used for index-token narrowing. */
363
510
  indexes?: infer I;
364
511
  } ? I extends Record<string, unknown> ? Record<Extract<ITS, Extract<keyof I, string>>, ShardQueryFunction<CC, ET, Extract<ITS, Extract<keyof I, string>>, CF, K>> : Record<ITS, ShardQueryFunction<CC, ET, ITS, CF, K>> : Record<ITS, ShardQueryFunction<CC, ET, ITS, CF, K>>;
365
512
  /**
@@ -567,9 +714,9 @@ declare class EntityManager<CC extends BaseConfigMap, CF = unknown> {
567
714
  /**
568
715
  * Update generated properties, hash key, and range key on an {@link EntityItem | `EntityItem`} object.
569
716
  *
570
- * @param entityToken - {@link Config | `Config`} `entities` key.
571
- * @param item - {@link EntityItem | `EntityItem`} object.
572
- * @param overwrite - Overwrite existing properties (default `false`).
717
+ * @param entityToken - Entity token (narrows types by token).
718
+ * @param item - Single item to update with generated keys/properties.
719
+ * @param overwrite - Overwrite existing keys/properties (default `false`).
573
720
  *
574
721
  * @returns {@link EntityRecord | `EntityRecord`} object with updated properties.
575
722
  *
@@ -579,22 +726,44 @@ declare class EntityManager<CC extends BaseConfigMap, CF = unknown> {
579
726
  */
580
727
  addKeys<ET extends EntityToken<CC>>(entityToken: ET, item: EntityItemPartial<CC, ET>, overwrite?: boolean): EntityRecordPartial<CC, ET>;
581
728
  /**
729
+ * Update generated properties, hash key, and range key on an array of {@link EntityItem | `EntityItem`} objects.
730
+ *
731
+ * @param entityToken - Entity token (narrows types by token).
732
+ * @param item - Items to update with generated keys/properties.
733
+ * @param overwrite - Overwrite existing keys/properties (default `false`).
734
+ *
735
+ * @returns Array of {@link EntityRecord | `EntityRecord`} objects with updated properties.
736
+ *
737
+ * @throws `Error` if `entityToken` is invalid.
738
+ *
582
739
  * @overload
583
740
  */
584
741
  addKeys<ET extends EntityToken<CC>>(entityToken: ET, item: EntityItemPartial<CC, ET>[], overwrite?: boolean): EntityRecordPartial<CC, ET>[];
585
742
  /**
586
743
  * Convert one or more {@link EntityItem | `EntityItem`} objects into an array of {@link EntityKey | `EntityKey`} values.
587
744
  *
588
- * @param entityToken - {@link Config | `Config`} `entities` key.
589
- * @param item - {@link EntityItem | `EntityItem`} object.
590
- * @param overwrite - Overwrite existing properties (default `false`).
745
+ * @param entityToken - Entity token (narrows types by token).
746
+ * @param item - Single item to derive primary keys for.
747
+ * @param overwrite - Overwrite existing keys on the item before deriving (default `false`).
591
748
  *
592
749
  * @returns Array of {@link EntityKey | `EntityKey`} values derived from `item`.
593
750
  *
594
751
  * @throws `Error` if `entityToken` is invalid.
752
+ *
753
+ * @overload
595
754
  */
596
755
  getPrimaryKey<ET extends EntityToken<CC>>(entityToken: ET, item: EntityItemPartial<CC, ET>, overwrite?: boolean): EntityKey<CC>[];
597
756
  /**
757
+ * Convert an array of {@link EntityItem | `EntityItem`} objects into {@link EntityKey | `EntityKey`} values.
758
+ *
759
+ * @param entityToken - Entity token (narrows types by token).
760
+ * @param items - Array of items to derive primary keys for.
761
+ * @param overwrite - Overwrite existing keys on each item before deriving (default `false`).
762
+ *
763
+ * @returns Array of {@link EntityKey | `EntityKey`} values derived from all `items`.
764
+ *
765
+ * @throws `Error` if `entityToken` is invalid.
766
+ *
598
767
  * @overload
599
768
  */
600
769
  getPrimaryKey<ET extends EntityToken<CC>>(entityToken: ET, items: EntityItemPartial<CC, ET>[], overwrite?: boolean): EntityKey<CC>[];
@@ -660,56 +829,110 @@ declare class EntityManager<CC extends BaseConfigMap, CF = unknown> {
660
829
  * Keep this intentionally permissive to maximize inference from `as const`.
661
830
  */
662
831
  interface ConfigInput {
832
+ /** Global hash key property name (e.g., `"pk"`). */
663
833
  hashKey: string;
834
+ /** Global range key property name (e.g., `"sk"`). */
664
835
  rangeKey: string;
836
+ /**
837
+ * Optional generated property token maps.
838
+ *
839
+ * @remarks
840
+ * - `sharded` keys are hash-side generated property tokens and are encoded atomically.
841
+ * - `unsharded` keys are range-side generated property tokens.
842
+ */
665
843
  generatedProperties?: {
844
+ /** Sharded generated property tokens (hash-side). */
666
845
  sharded?: Record<string, readonly string[]>;
846
+ /** Unsharded generated property tokens (range-side). */
667
847
  unsharded?: Record<string, readonly string[]>;
668
848
  };
849
+ /**
850
+ * Optional map of transcodable property token -\> transcode name.
851
+ *
852
+ * @remarks
853
+ * Only properties present here are treated as “transcoded properties”.
854
+ */
669
855
  propertyTranscodes?: Record<string, string>;
856
+ /**
857
+ * Optional index token map used for typing and paging-key narrowing.
858
+ *
859
+ * @remarks
860
+ * This is provider-agnostic metadata (not a provider-specific index definition).
861
+ */
670
862
  indexes?: Record<string, {
863
+ /** Index hash key token (global hash key or sharded generated key). */
671
864
  hashKey: string;
865
+ /** Index range key token (global range key, unsharded generated key, or transcoded scalar). */
672
866
  rangeKey: string;
867
+ /** Optional list of projected attribute names (validated at runtime to exclude key tokens). */
673
868
  projections?: string[];
674
869
  }>;
870
+ /**
871
+ * Optional per-entity configuration (runtime semantics).
872
+ *
873
+ * @remarks
874
+ * This is intentionally permissive in `ConfigInput`; runtime validation occurs
875
+ * in the {@link EntityManager | `EntityManager`} constructor via Zod.
876
+ */
675
877
  entities?: Record<string, unknown>;
676
878
  /**
677
879
  * Optional Zod schemas for per-entity domain shapes (non-generated fields only).
678
880
  *
679
- * Important: Schemas MUST declare only base (non-generated) properties. Do not include
680
- * global keys (hashKey/rangeKey) or any generated tokens (sharded/unsharded).
881
+ * @remarks
882
+ * Schemas MUST declare only base (non-generated) properties. Do not include:
883
+ * - global keys (hashKey/rangeKey), or
884
+ * - generated property tokens (sharded/unsharded keys).
681
885
  */
682
886
  entitiesSchema?: Record<string, ZodType>;
887
+ /** Optional delimiter between generated key elements (default `|`). */
683
888
  generatedKeyDelimiter?: string;
889
+ /** Optional delimiter between a generated element name and its value (default `#`). */
684
890
  generatedValueDelimiter?: string;
891
+ /** Optional delimiter between entity token and shard suffix in hash key values (default `!`). */
685
892
  shardKeyDelimiter?: string;
893
+ /** Optional transcode registry/value (validated at runtime). */
686
894
  transcodes?: unknown;
895
+ /** Optional maximum concurrency for shard queries. */
687
896
  throttle?: number;
688
897
  }
898
+ /** Extract the hash key token string literal from a values-first config input type. */
689
899
  type HashKeyFrom<CC> = CC extends {
900
+ /** Hash key token property name. */
690
901
  hashKey: infer H;
691
902
  } ? H & string : 'hashKey';
903
+ /** Extract the range key token string literal from a values-first config input type. */
692
904
  type RangeKeyFrom<CC> = CC extends {
905
+ /** Range key token property name. */
693
906
  rangeKey: infer R;
694
907
  } ? R & string : 'rangeKey';
908
+ /** Extract the union of sharded generated key tokens from a values-first config input type. */
695
909
  type ShardedKeysFrom<CC> = CC extends {
910
+ /** Optional generated properties object containing sharded/unsharded maps. */
696
911
  generatedProperties?: infer GP;
697
912
  } ? GP extends {
913
+ /** Sharded generated property token map. */
698
914
  sharded?: infer S;
699
915
  } ? keyof S & string : never : never;
916
+ /** Extract the union of unsharded generated key tokens from a values-first config input type. */
700
917
  type UnshardedKeysFrom<CC> = CC extends {
918
+ /** Optional generated properties object containing sharded/unsharded maps. */
701
919
  generatedProperties?: infer GP;
702
920
  } ? GP extends {
921
+ /** Unsharded generated property token map. */
703
922
  unsharded?: infer U;
704
923
  } ? keyof U & string : never : never;
924
+ /** Extract the union of transcoded property tokens from a values-first config input type. */
705
925
  type TranscodedPropertiesFrom<CC> = CC extends {
926
+ /** Optional map of property token -\> transcode name. */
706
927
  propertyTranscodes?: infer PT;
707
928
  } ? keyof PT & string : never;
708
929
  /**
709
930
  * Derive an EntityMap from CC.entitiesSchema when provided (values-first, no generics).
931
+ *
710
932
  * Fallback to broad EntityMap if schemas are absent.
711
933
  */
712
934
  type EntitiesFromSchema<CC> = CC extends {
935
+ /** Optional per-entity Zod schema map used only for type inference. */
713
936
  entitiesSchema?: infer S;
714
937
  } ? S extends Record<string, ZodType> ? {
715
938
  [K in Extract<keyof S, string>]: z.infer<S[K]>;
@@ -722,6 +945,7 @@ type EntitiesFromSchema<CC> = CC extends {
722
945
  * index token union. Falls back to `string` if absent.
723
946
  */
724
947
  type IndexTokensFrom<CC> = CC extends {
948
+ /** Optional index token map used for index-token inference. */
725
949
  indexes?: infer I;
726
950
  } ? keyof I & string : string;
727
951
  /**
@@ -729,12 +953,19 @@ type IndexTokensFrom<CC> = CC extends {
729
953
  * and an EntityMap (defaults to MinimalEntityMapFrom<CC>).
730
954
  */
731
955
  interface CapturedConfigMapFrom<CC, EM extends EntityMap> extends BaseConfigMap {
956
+ /** Entity map type (from schemas when provided; otherwise broad). */
732
957
  EntityMap: EM;
958
+ /** Hash key token captured from the config literal. */
733
959
  HashKey: HashKeyFrom<CC>;
960
+ /** Range key token captured from the config literal. */
734
961
  RangeKey: RangeKeyFrom<CC>;
962
+ /** Sharded generated key token union captured from the config literal. */
735
963
  ShardedKeys: ShardedKeysFrom<CC>;
964
+ /** Unsharded generated key token union captured from the config literal. */
736
965
  UnshardedKeys: UnshardedKeysFrom<CC>;
966
+ /** Transcoded property token union captured from the config literal. */
737
967
  TranscodedProperties: TranscodedPropertiesFrom<CC>;
968
+ /** Transcode registry type (default registry; runtime validation still applies). */
738
969
  TranscodeRegistry: DefaultTranscodeRegistry;
739
970
  }
740
971
  /**
@@ -804,8 +1035,32 @@ declare abstract class BaseEntityClient<CC extends BaseConfigMap, CF = unknown>
804
1035
  constructor(options: BaseEntityClientOptions<CC, CF>);
805
1036
  }
806
1037
 
1038
+ /**
1039
+ * Extract the captured config map type from a {@link BaseEntityClient | `BaseEntityClient`} instance type.
1040
+ *
1041
+ * @typeParam EC - A {@link BaseEntityClient | `BaseEntityClient`} instance type.
1042
+ *
1043
+ * @remarks
1044
+ * This is a pure type-level helper used to derive token-aware types from a client
1045
+ * instance type without requiring callers to restate the config map type.
1046
+ */
807
1047
  type ConfigOfClient<EC> = EC extends BaseEntityClient<infer CC> ? CC : never;
1048
+ /**
1049
+ * Map a client instance type + entity token to the storage-facing record type.
1050
+ *
1051
+ * @typeParam EC - A {@link BaseEntityClient | `BaseEntityClient`} instance type.
1052
+ * @typeParam ET - An {@link EntityToken | `EntityToken`} for that client’s config.
1053
+ */
808
1054
  type EntityClientRecordByToken<EC, ET extends EntityToken<ConfigOfClient<EC>>> = EntityRecord<ConfigOfClient<EC>, ET>;
1055
+ /**
1056
+ * Map a client instance type + entity token to the domain-facing item type.
1057
+ *
1058
+ * This is the “item-facing” shape (global keys and generated property tokens are
1059
+ * not required and are typically stripped via {@link EntityManager.removeKeys | `removeKeys`}).
1060
+ *
1061
+ * @typeParam EC - A {@link BaseEntityClient | `BaseEntityClient`} instance type.
1062
+ * @typeParam ET - An {@link EntityToken | `EntityToken`} for that client’s config.
1063
+ */
809
1064
  type EntityClientItemByToken<EC, ET extends EntityToken<ConfigOfClient<EC>>> = EntityItem<ConfigOfClient<EC>, ET>;
810
1065
 
811
1066
  /**
@@ -867,6 +1122,23 @@ declare abstract class BaseQueryBuilder<CC extends BaseConfigMap, EntityClient e
867
1122
  readonly indexParamsMap: Record<ITS, IndexParams>;
868
1123
  /** BaseQueryBuilder constructor. */
869
1124
  constructor(options: BaseQueryBuilderOptions<CC, EntityClient>);
1125
+ /**
1126
+ * Build a shard query function for a specific index token.
1127
+ *
1128
+ * @param indexToken - Index token identifying which index to query.
1129
+ *
1130
+ * @returns A {@link ShardQueryFunction | `ShardQueryFunction`} that queries a single shard for this index.
1131
+ *
1132
+ * @remarks
1133
+ * Implementations are provider-specific (e.g., DynamoDB). The returned function must:
1134
+ * - Query exactly one shard (partition) per invocation using the provided hash key value.
1135
+ * - Respect `pageKey` and `pageSize` for pagination.
1136
+ * - Return `pageKey: undefined` when that shard is exhausted.
1137
+ *
1138
+ * Entity Manager will orchestrate calling these functions across shards and indexes.
1139
+ *
1140
+ * @protected
1141
+ */
870
1142
  protected abstract getShardQueryFunction(indexToken: ITS): ShardQueryFunction<CC, ET, ITS, CF, K>;
871
1143
  /**
872
1144
  * Builds a {@link ShardQueryMap | `ShardQueryMap`} object.
@@ -874,8 +1146,18 @@ declare abstract class BaseQueryBuilder<CC extends BaseConfigMap, EntityClient e
874
1146
  * @returns - The {@link ShardQueryMap | `ShardQueryMap`} object.
875
1147
  */
876
1148
  build(): ShardQueryMap<CC, ET, ITS, CF, K>;
1149
+ /**
1150
+ * Execute the built query across shards and indexes via {@link EntityManager.query | `EntityManager.query`}.
1151
+ *
1152
+ * @param options - Query options excluding `entityToken`, `pageKeyMap`, and `shardQueryMap`, which are supplied by the builder.
1153
+ *
1154
+ * @returns The merged, de-duplicated, sorted query result, including a compact `pageKeyMap` token for the next page.
1155
+ *
1156
+ * @remarks
1157
+ * This delegates orchestration to Entity Manager; provider-specific behavior lives in {@link getShardQueryFunction | `getShardQueryFunction`}.
1158
+ */
877
1159
  query(options: QueryBuilderQueryOptions<CC, ET, CF>): Promise<QueryResult<CC, ET, ITS, K>>;
878
1160
  }
879
1161
 
880
1162
  export { BaseEntityClient, BaseQueryBuilder, EntityManager, configSchema, createEntityManager };
881
- export type { BaseConfigMap, BaseEntityClientOptions, BaseKeyTokens, BaseQueryBuilderOptions, CapturedConfigMapFrom, Config, ConfigInput, ConfigMap, ConfigOfClient, EntitiesFromSchema, EntityClientItemByToken, EntityClientRecordByToken, EntityItem, EntityItemPartial, EntityKey, EntityOfToken, EntityRecord, EntityRecordPartial, EntityToken, FallbackIndexTokenSet, HasIndexFor, HashKeyFrom, IndexComponentTokens, IndexHashKeyOf, IndexRangeKeyOf, IndexTokensFrom, IndexTokensOf, KeysFrom, PageKey, PageKeyByIndex, ParsedConfig, PresentIndexTokenSet, Projected, QueryBuilderQueryOptions, QueryOptions, QueryOptionsByCC, QueryOptionsByCF, QueryResult, RangeKeyFrom, ShardBump, ShardQueryFunction, ShardQueryMap, ShardQueryMapByCC, ShardQueryMapByCF, ShardQueryResult, ShardedKeysFrom, StorageItem, StorageRecord, TranscodedPropertiesFrom, UnshardedKeysFrom, ValidateConfigMap };
1163
+ export type { BaseConfigMap, BaseEntityClientOptions, BaseKeyTokens, BaseQueryBuilderOptions, CapturedConfigMapFrom, Config, ConfigInput, ConfigMap, ConfigOfClient, EntitiesFromSchema, EntityClientItemByToken, EntityClientRecordByToken, EntityItem, EntityItemPartial, EntityKey, EntityOfToken, EntityRecord, EntityRecordPartial, EntityToken, FallbackIndexTokenSet, HasIndexFor, HashKeyFrom, IndexComponentTokens, IndexHashKeyOf, IndexRangeKeyOf, IndexTokensFrom, IndexTokensOf, KeysFrom, PageKey, PageKeyByIndex, ParsedConfig, ParsedEntityConfig, ParsedGeneratedPropertiesConfig, ParsedIndexConfig, ParsedTranscoder, PresentIndexTokenSet, Projected, QueryBuilderQueryOptions, QueryOptions, QueryOptionsByCC, QueryOptionsByCF, QueryResult, RangeKeyFrom, ShardBump, ShardQueryFunction, ShardQueryMap, ShardQueryMapByCC, ShardQueryMapByCF, ShardQueryResult, ShardedKeysFrom, StorageItem, StorageRecord, TranscodedPropertiesFrom, UnshardedKeysFrom, ValidateConfigMap };
@@ -1,5 +1,6 @@
1
1
  import { mapValues } from 'radash';
2
2
 
3
+ /* eslint-disable @typescript-eslint/no-unused-vars */
3
4
  /**
4
5
  * Abstract base class supporting a fluent API for building a {@link ShardQueryMap | `ShardQueryMap`} using a database client.
5
6
  *
@@ -34,6 +35,16 @@ class BaseQueryBuilder {
34
35
  build() {
35
36
  return mapValues(this.indexParamsMap, (_indexConfig, indexToken) => this.getShardQueryFunction(indexToken));
36
37
  }
38
+ /**
39
+ * Execute the built query across shards and indexes via {@link EntityManager.query | `EntityManager.query`}.
40
+ *
41
+ * @param options - Query options excluding `entityToken`, `pageKeyMap`, and `shardQueryMap`, which are supplied by the builder.
42
+ *
43
+ * @returns The merged, de-duplicated, sorted query result, including a compact `pageKeyMap` token for the next page.
44
+ *
45
+ * @remarks
46
+ * This delegates orchestration to Entity Manager; provider-specific behavior lives in {@link getShardQueryFunction | `getShardQueryFunction`}.
47
+ */
37
48
  async query(options) {
38
49
  const { entityClient: { entityManager }, entityToken, pageKeyMap, } = this;
39
50
  const shardQueryMap = this.build();
@@ -27,19 +27,54 @@ const componentArray = z
27
27
  .array(z.string().min(1))
28
28
  .nonempty()
29
29
  .superRefine(validateArrayUnique);
30
+ /**
31
+ * Runtime configuration schema for {@link EntityManager | `EntityManager`}.
32
+ *
33
+ * @remarks
34
+ * This is authoritative at runtime and used internally by the {@link EntityManager | `EntityManager`} constructor.
35
+ * It is also used by tests. It is not intended as a user-facing TypeDoc artifact.
36
+ *
37
+ * @hidden
38
+ */
30
39
  const configSchema = z
31
40
  .object({
32
41
  entities: z
33
42
  .record(z.string(), z
34
43
  .object({
35
- defaultLimit: z.number().int().positive().optional().default(10),
36
- defaultPageSize: z.number().int().positive().optional().default(10),
44
+ defaultLimit: z
45
+ .number()
46
+ .int()
47
+ .positive()
48
+ .optional()
49
+ .default(10)
50
+ .describe('Default max items returned by EntityManager.query for this entity (across all shards).'),
51
+ defaultPageSize: z
52
+ .number()
53
+ .int()
54
+ .positive()
55
+ .optional()
56
+ .default(10)
57
+ .describe('Default per-shard page size used by EntityManager.query for this entity.'),
37
58
  shardBumps: z
38
59
  .array(z
39
60
  .object({
40
- timestamp: z.number().int().nonnegative(),
41
- charBits: z.number().int().min(1).max(5),
42
- chars: z.number().int().min(0).max(40),
61
+ timestamp: z
62
+ .number()
63
+ .int()
64
+ .nonnegative()
65
+ .describe('Start timestamp (ms) for this shard bump (inclusive).'),
66
+ charBits: z
67
+ .number()
68
+ .int()
69
+ .min(1)
70
+ .max(5)
71
+ .describe('Bits per shard character (radix = 2**charBits).'),
72
+ chars: z
73
+ .number()
74
+ .int()
75
+ .min(0)
76
+ .max(40)
77
+ .describe('Shard suffix width (chars); controls shard space.'),
43
78
  })
44
79
  .strict())
45
80
  .optional()
@@ -71,45 +106,92 @@ const configSchema = z
71
106
  });
72
107
  }
73
108
  }),
74
- timestampProperty: z.string().min(1),
75
- uniqueProperty: z.string().min(1),
109
+ timestampProperty: z
110
+ .string()
111
+ .min(1)
112
+ .describe('Property token whose value selects the shard bump (typically a timestamp).'),
113
+ uniqueProperty: z
114
+ .string()
115
+ .min(1)
116
+ .describe('Property token used to dedupe and build the global range key.'),
76
117
  })
77
118
  .strict())
78
119
  .optional()
79
- .default({}),
120
+ .default({})
121
+ .describe('Entity definitions keyed by entity token.'),
80
122
  generatedProperties: z
81
123
  .object({
82
- sharded: z.record(z.string(), componentArray).optional().default({}),
83
- unsharded: z.record(z.string(), componentArray).optional().default({}),
124
+ sharded: z
125
+ .record(z.string(), componentArray)
126
+ .optional()
127
+ .default({})
128
+ .describe('Sharded generated property tokens (hash-side); atomic encoding semantics.'),
129
+ unsharded: z
130
+ .record(z.string(), componentArray)
131
+ .optional()
132
+ .default({})
133
+ .describe('Unsharded generated property tokens (range-side); non-atomic encoding semantics.'),
84
134
  })
85
135
  .optional()
86
136
  .default({ sharded: {}, unsharded: {} }),
87
- hashKey: z.string(),
137
+ hashKey: z.string().describe('Global hash key property name.'),
88
138
  indexes: z
89
139
  .record(z.string(), z.object({
90
- hashKey: z.string().min(1),
91
- rangeKey: z.string().min(1),
140
+ hashKey: z
141
+ .string()
142
+ .min(1)
143
+ .describe('Index hash key token (global hash key or a sharded generated key).'),
144
+ rangeKey: z
145
+ .string()
146
+ .min(1)
147
+ .describe('Index range key token (global range key, an unsharded generated key, or a transcoded property).'),
92
148
  projections: z
93
149
  .array(z.string().min(1))
94
150
  .superRefine(validateArrayUnique)
95
151
  .optional(),
96
152
  }))
97
153
  .optional()
98
- .default({}),
99
- generatedKeyDelimiter: z.string().regex(/\W+/).optional().default('|'),
100
- generatedValueDelimiter: z.string().regex(/\W+/).optional().default('#'),
154
+ .default({})
155
+ .describe('Index definitions keyed by index token.'),
156
+ generatedKeyDelimiter: z
157
+ .string()
158
+ .regex(/\W+/)
159
+ .optional()
160
+ .default('|')
161
+ .describe('Delimiter between generated key elements (default `|`).'),
162
+ generatedValueDelimiter: z
163
+ .string()
164
+ .regex(/\W+/)
165
+ .optional()
166
+ .default('#')
167
+ .describe('Delimiter between generated element name and value (default `#`).'),
101
168
  propertyTranscodes: z.record(z.string(), z.string()).optional().default({}),
102
- rangeKey: z.string(),
103
- shardKeyDelimiter: z.string().regex(/\W+/).optional().default('!'),
104
- throttle: z.number().int().positive().optional().default(10),
169
+ rangeKey: z.string().describe('Global range key property name.'),
170
+ shardKeyDelimiter: z
171
+ .string()
172
+ .regex(/\W+/)
173
+ .optional()
174
+ .default('!')
175
+ .describe('Delimiter between entity token and shard suffix in hash key values.'),
176
+ throttle: z
177
+ .number()
178
+ .int()
179
+ .positive()
180
+ .optional()
181
+ .default(10)
182
+ .describe('Default max concurrency for shard queries during EntityManager.query.'),
105
183
  transcodes: z
106
184
  .record(z.string(), z
107
185
  .object({
108
186
  // Accept function shapes without relying on z.function()
109
187
  // to avoid TS inference conflicts across Zod versions and
110
188
  // to remain compatible with narrower parameter types.
111
- encode: z.custom((fn) => typeof fn === 'function'),
112
- decode: z.custom((fn) => typeof fn === 'function'),
189
+ encode: z
190
+ .custom((fn) => typeof fn === 'function')
191
+ .describe('Encode a value to a lexicographically sortable string.'),
192
+ decode: z
193
+ .custom((fn) => typeof fn === 'function')
194
+ .describe('Decode a previously encoded string back to the value type.'),
113
195
  })
114
196
  .strict())
115
197
  .optional()
@@ -1,6 +1,5 @@
1
1
  function findIndexToken(entityManager, hashKeyToken, rangeKeyToken, suppressError) {
2
- const indexToken = (Object.entries(entityManager.config.indexes).find(([, index]) => index.hashKey === hashKeyToken &&
3
- index.rangeKey === rangeKeyToken)?.[0] ?? undefined);
2
+ const indexToken = (Object.entries(entityManager.config.indexes).find(([, index]) => index.hashKey === hashKeyToken && index.rangeKey === rangeKeyToken)?.[0] ?? undefined);
4
3
  if (!indexToken && !suppressError)
5
4
  throw new Error(`No index token found for hashKey '${hashKeyToken}' & rangeKey '${rangeKeyToken}'.`);
6
5
  return indexToken;
@@ -15,12 +15,7 @@ function getIndexComponents(entityManager, indexToken) {
15
15
  validateIndexToken(entityManager, indexToken);
16
16
  const { hashKey, rangeKey, indexes } = entityManager.config;
17
17
  const { hashKey: indexHashKey, rangeKey: indexRangeKey } = indexes[indexToken];
18
- return unique([
19
- hashKey,
20
- rangeKey,
21
- indexHashKey,
22
- indexRangeKey,
23
- ]);
18
+ return unique([hashKey, rangeKey, indexHashKey, indexRangeKey]);
24
19
  }
25
20
 
26
21
  export { getIndexComponents };
@@ -38,7 +38,7 @@ async function query(entityManager, options) {
38
38
  if (!(isInt(pageSize) && pageSize >= 1))
39
39
  throw new Error('pageSize must be a positive integer');
40
40
  // Rehydrate pageKeyMap.
41
- const [hashKeyToken, rehydratedPageKeyMap] = rehydratePageKeyMap(entityManager, entityToken, Object.keys(shardQueryMap), item, pageKeyMap
41
+ const [hashKeyToken, rehydratedPageKeyMap] = rehydratePageKeyMap(entityManager, entityToken, Object.keys(shardQueryMap).sort(), item, pageKeyMap
42
42
  ? JSON.parse(decompressFromEncodedURIComponent(pageKeyMap))
43
43
  : undefined, timestampFrom, timestampTo);
44
44
  // Shortcut if pageKeyMap is empty.
package/package.json CHANGED
@@ -10,53 +10,53 @@
10
10
  "url": "https://github.com/karmaniverous/entity-manager/issues"
11
11
  },
12
12
  "dependencies": {
13
- "@karmaniverous/batch-process": "^0.1.0",
14
- "@karmaniverous/entity-tools": "^0.8.0",
15
- "@karmaniverous/string-utilities": "^0.2.2",
13
+ "@karmaniverous/batch-process": "^0.1.1",
14
+ "@karmaniverous/entity-tools": "^0.8.1",
15
+ "@karmaniverous/string-utilities": "^0.2.3",
16
16
  "lz-string": "^1.5.0",
17
17
  "radash": "^12.1.1",
18
18
  "string-hash": "^1.1.3",
19
- "zod": "^4.1.13"
19
+ "zod": "^4.4.3"
20
20
  },
21
21
  "description": "Rational indexing & cross-shard querying at scale in your NoSQL database so you can focus on your application logic.",
22
22
  "devDependencies": {
23
- "@dotenvx/dotenvx": "^1.51.1",
24
- "@eslint/js": "^9.39.1",
25
- "@faker-js/faker": "^10.1.0",
26
- "@karmaniverous/mock-db": "^0.4.0",
23
+ "@dotenvx/dotenvx": "^2.9.0",
24
+ "@eslint/js": "^10.0.1",
25
+ "@faker-js/faker": "^10.5.0",
26
+ "@karmaniverous/mock-db": "^0.4.1",
27
27
  "@rollup/plugin-alias": "^6.0.0",
28
- "@rollup/plugin-commonjs": "^29.0.0",
28
+ "@rollup/plugin-commonjs": "^29.0.3",
29
29
  "@rollup/plugin-json": "^6.1.0",
30
30
  "@rollup/plugin-node-resolve": "^16.0.3",
31
31
  "@rollup/plugin-strip": "^3.0.4",
32
32
  "@rollup/plugin-typescript": "^12.3.0",
33
- "@types/node": "^24.10.1",
33
+ "@types/node": "^26.1.1",
34
34
  "@types/string-hash": "^1.1.3",
35
- "auto-changelog": "^2.5.0",
35
+ "auto-changelog": "^2.6.0",
36
36
  "cross-env": "^10.1.0",
37
- "eslint": "^9.39.1",
37
+ "eslint": "^10.7.0",
38
38
  "eslint-config-prettier": "^10.1.8",
39
- "eslint-plugin-prettier": "^5.5.4",
40
- "eslint-plugin-simple-import-sort": "^12.1.1",
41
- "eslint-plugin-tsdoc": "^0.5.0",
42
- "knip": "^5.70.2",
43
- "lefthook": "^2.0.4",
44
- "prettier": "^3.7.3",
45
- "release-it": "^19.0.6",
46
- "rimraf": "^6.1.2",
47
- "rollup": "^4.53.3",
48
- "rollup-plugin-dts": "^6.3.0",
39
+ "eslint-plugin-prettier": "^5.5.6",
40
+ "eslint-plugin-simple-import-sort": "^13.0.0",
41
+ "eslint-plugin-tsdoc": "^0.5.2",
42
+ "knip": "^6.27.0",
43
+ "lefthook": "^2.1.10",
44
+ "prettier": "^3.9.5",
45
+ "release-it": "^20.2.1",
46
+ "rimraf": "^6.1.3",
47
+ "rollup": "^4.62.2",
48
+ "rollup-plugin-dts": "^6.4.1",
49
49
  "tsd": "^0.33.0",
50
50
  "tslib": "^2.8.1",
51
- "typedoc": "^0.28.15",
52
- "typedoc-plugin-mdn-links": "^5.0.10",
51
+ "typedoc": "^0.28.20",
52
+ "typedoc-plugin-mdn-links": "^5.1.1",
53
53
  "typedoc-plugin-replace-text": "^4.2.0",
54
54
  "typedoc-plugin-zod": "^1.4.3",
55
- "@vitest/coverage-v8": "^4.0.14",
56
- "@vitest/eslint-plugin": "^1.5.1",
57
- "vitest": "^4.0.14",
58
- "typescript": "^5.9.3",
59
- "typescript-eslint": "^8.48.0"
55
+ "@vitest/coverage-v8": "^4.1.10",
56
+ "@vitest/eslint-plugin": "^1.6.23",
57
+ "vitest": "^4.1.10",
58
+ "typescript": "^6.0.3",
59
+ "typescript-eslint": "^8.64.0"
60
60
  },
61
61
  "exports": {
62
62
  ".": {
@@ -101,15 +101,15 @@
101
101
  "npm run knip",
102
102
  "npm run build"
103
103
  ],
104
- "before:npm:release": [
105
- "npx auto-changelog -p",
106
- "npm run docs",
107
- "git add -A"
108
- ],
109
104
  "after:release": [
110
105
  "git switch -c release/${version}",
111
106
  "git push -u origin release/${version}",
112
107
  "git switch ${branchName}"
108
+ ],
109
+ "after:bump": [
110
+ "npx auto-changelog -p",
111
+ "npm run docs",
112
+ "git add CHANGELOG.md"
113
113
  ]
114
114
  },
115
115
  "npm": {
@@ -134,5 +134,5 @@
134
134
  },
135
135
  "type": "module",
136
136
  "types": "dist/index.d.ts",
137
- "version": "8.0.0"
137
+ "version": "8.0.1"
138
138
  }