@karmaniverous/entity-manager 7.2.0 → 7.3.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.
@@ -15,6 +15,10 @@ var _EntityManager_config;
15
15
  * query strategy to NoSql data.
16
16
  *
17
17
  * @typeParam CC - {@link ConfigMap | `ConfigMap`} that defines the configuration's {@link EntityMap | `EntityMap`}, key properties, and {@link TranscodeRegistry | `TranscodeRegistry`}. If omitted, defaults to {@link BaseConfigMap | `BaseConfigMap`}.
18
+ * @typeParam CF - Values-first config literal type captured at construction
19
+ * time (phantom generic; type-only). This is used by downstream
20
+ * adapters to infer index-token unions (ITS) and per-index page
21
+ * key shapes.
18
22
  *
19
23
  * @remarks
20
24
  * 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.
@@ -87,19 +91,11 @@ class EntityManager {
87
91
  }
88
92
  return removeKeys.removeKeys(this, entityToken, i);
89
93
  }
90
- /**
91
- * Find an index token in a {@link Config | `Config`} object based on the index `hashKey` and `rangeKey`.
92
- *
93
- * @param hashKeyToken - Index hash key.
94
- * @param rangeKeyToken - Index range key.
95
- * @param suppressError - Suppress error if no match found.
96
- *
97
- * @returns Index token if found.
98
- *
99
- * @throws `Error` if no match found and `suppressError` is not `true`.
100
- */
101
94
  findIndexToken(hashKeyToken, rangeKeyToken, suppressError) {
102
- return findIndexToken.findIndexToken(this, hashKeyToken, rangeKeyToken, suppressError);
95
+ // Dispatch with a literal to satisfy overload selection.
96
+ return suppressError === true
97
+ ? findIndexToken.findIndexToken(this, hashKeyToken, rangeKeyToken, true)
98
+ : findIndexToken.findIndexToken(this, hashKeyToken, rangeKeyToken, false);
103
99
  }
104
100
  /**
105
101
  * Query a database entity across shards in a provider-generic fashion.
@@ -227,8 +227,19 @@ const configSchema = zod.z
227
227
  path: ['generatedProperties', 'unsharded', property],
228
228
  });
229
229
  // Validate indexes.
230
- // TODO: Vaidate no two indexes have the same hashKey & rangeKey.
230
+ // Validate no two indexes have the same hashKey & rangeKey.
231
+ const seenIndexPairs = new Set();
231
232
  for (const [indexKey, { hashKey, rangeKey, projections }] of Object.entries(data.indexes)) {
233
+ const pairSig = `${hashKey}|${rangeKey}`;
234
+ if (seenIndexPairs.has(pairSig)) {
235
+ ctx.addIssue({
236
+ code: 'custom',
237
+ message: `duplicate index hashKey/rangeKey pair`,
238
+ path: ['indexes', indexKey],
239
+ });
240
+ }
241
+ else
242
+ seenIndexPairs.add(pairSig);
232
243
  // Validate hash key is sharded.
233
244
  if (![data.hashKey, ...shardedKeys].includes(hashKey)) {
234
245
  ctx.addIssue({
@@ -11,6 +11,10 @@ var EntityManager = require('./EntityManager.js');
11
11
  * `satisfies` at call sites to preserve literal keys.
12
12
  * @typeParam EM - EntityMap for the manager. Defaults to a minimal derived map
13
13
  * from `CC.entitiesSchema` when present; otherwise falls back to EntityMap.
14
+ *
15
+ * @returns An {@link EntityManager | `EntityManager`} instance whose type
16
+ * captures CF from the single values-first config literal ({@link ConfigInput | `ConfigInput`})
17
+ * as the second generic parameter (phantom; type-only).
14
18
  */
15
19
  function createEntityManager(config, logger = console) {
16
20
  // Cast to the existing Config<C> shape for runtime parsing; Zod validation
@@ -1,19 +1,8 @@
1
1
  'use strict';
2
2
 
3
- /**
4
- * Find an index token in a {@link Config | `Config`} object based on the index `hashKey` and `rangeKey`.
5
- *
6
- * @param entityManager - {@link EntityManager | `EntityManager`} instance.
7
- * @param hashKeyToken - Index hash key.
8
- * @param rangeKeyToken - Index range key.
9
- * @param suppressError - Suppress error if no match found.
10
- *
11
- * @returns Index token if found.
12
- *
13
- * @throws `Error` if no match found and `suppressError` is not `true`.
14
- */
15
3
  function findIndexToken(entityManager, hashKeyToken, rangeKeyToken, suppressError) {
16
- const indexToken = Object.entries(entityManager.config.indexes).find(([, index]) => index.hashKey === hashKeyToken && index.rangeKey === rangeKeyToken)?.[0];
4
+ const indexToken = (Object.entries(entityManager.config.indexes).find(([, index]) => index.hashKey === hashKeyToken &&
5
+ index.rangeKey === rangeKeyToken)?.[0] ?? undefined);
17
6
  if (!indexToken && !suppressError)
18
7
  throw new Error(`No index token found for hashKey '${hashKeyToken}' & rangeKey '${rangeKeyToken}'.`);
19
8
  return indexToken;
package/dist/index.d.ts CHANGED
@@ -148,54 +148,6 @@ type EntityKey<CC extends BaseConfigMap> = Record<CC['HashKey'] | CC['RangeKey']
148
148
  */
149
149
  type EntityToken<CC extends BaseConfigMap> = keyof Exactify<CC['EntityMap']> & string;
150
150
 
151
- declare const configSchema: z$1.ZodObject<{
152
- entities: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodRecord<z$1.ZodString, z$1.ZodObject<{
153
- defaultLimit: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodNumber>>;
154
- defaultPageSize: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodNumber>>;
155
- shardBumps: z$1.ZodPipe<z$1.ZodDefault<z$1.ZodOptional<z$1.ZodArray<z$1.ZodObject<{
156
- timestamp: z$1.ZodNumber;
157
- charBits: z$1.ZodNumber;
158
- chars: z$1.ZodNumber;
159
- }, z$1.core.$strict>>>>, z$1.ZodTransform<{
160
- timestamp: number;
161
- charBits: number;
162
- chars: number;
163
- }[], {
164
- timestamp: number;
165
- charBits: number;
166
- chars: number;
167
- }[]>>;
168
- timestampProperty: z$1.ZodString;
169
- uniqueProperty: z$1.ZodString;
170
- }, z$1.core.$strict>>>>;
171
- generatedProperties: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodObject<{
172
- sharded: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodRecord<z$1.ZodString, z$1.ZodArray<z$1.ZodString>>>>;
173
- unsharded: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodRecord<z$1.ZodString, z$1.ZodArray<z$1.ZodString>>>>;
174
- }, z$1.core.$strip>>>;
175
- hashKey: z$1.ZodString;
176
- indexes: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodRecord<z$1.ZodString, z$1.ZodObject<{
177
- hashKey: z$1.ZodString;
178
- rangeKey: z$1.ZodString;
179
- projections: z$1.ZodOptional<z$1.ZodArray<z$1.ZodString>>;
180
- }, z$1.core.$strip>>>>;
181
- generatedKeyDelimiter: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodString>>;
182
- generatedValueDelimiter: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodString>>;
183
- propertyTranscodes: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodRecord<z$1.ZodString, z$1.ZodString>>>;
184
- rangeKey: z$1.ZodString;
185
- shardKeyDelimiter: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodString>>;
186
- throttle: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodNumber>>;
187
- transcodes: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodRecord<z$1.ZodString, z$1.ZodObject<{
188
- encode: z$1.ZodCustom<unknown, unknown>;
189
- decode: z$1.ZodCustom<unknown, unknown>;
190
- }, z$1.core.$strict>>>>;
191
- }, z$1.core.$strict>;
192
- /**
193
- * Simplified type taken on by a {@link Config | `Config`} object after parsing in the {@link EntityManager | `EntityManager`} constructor.
194
- *
195
- * @category EntityManager
196
- */
197
- type ParsedConfig = z$1.infer<typeof configSchema>;
198
-
199
151
  /**
200
152
  * A partial {@link EntityItem | `EntityItem`} restricted to keys defined in `C`.
201
153
  *
@@ -244,6 +196,54 @@ type IndexComponentTokens<CC extends BaseConfigMap, CF, IT extends string> = Has
244
196
  */
245
197
  type PageKeyByIndex<CC extends BaseConfigMap, ET extends EntityToken<CC>, IT extends string = string, CF = unknown> = Pick<EntityItem<CC>, IndexComponentTokens<CC, CF, IT>>;
246
198
 
199
+ declare const configSchema: z$1.ZodObject<{
200
+ entities: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodRecord<z$1.ZodString, z$1.ZodObject<{
201
+ defaultLimit: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodNumber>>;
202
+ defaultPageSize: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodNumber>>;
203
+ shardBumps: z$1.ZodPipe<z$1.ZodDefault<z$1.ZodOptional<z$1.ZodArray<z$1.ZodObject<{
204
+ timestamp: z$1.ZodNumber;
205
+ charBits: z$1.ZodNumber;
206
+ chars: z$1.ZodNumber;
207
+ }, z$1.core.$strict>>>>, z$1.ZodTransform<{
208
+ timestamp: number;
209
+ charBits: number;
210
+ chars: number;
211
+ }[], {
212
+ timestamp: number;
213
+ charBits: number;
214
+ chars: number;
215
+ }[]>>;
216
+ timestampProperty: z$1.ZodString;
217
+ uniqueProperty: z$1.ZodString;
218
+ }, z$1.core.$strict>>>>;
219
+ generatedProperties: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodObject<{
220
+ sharded: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodRecord<z$1.ZodString, z$1.ZodArray<z$1.ZodString>>>>;
221
+ unsharded: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodRecord<z$1.ZodString, z$1.ZodArray<z$1.ZodString>>>>;
222
+ }, z$1.core.$strip>>>;
223
+ hashKey: z$1.ZodString;
224
+ indexes: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodRecord<z$1.ZodString, z$1.ZodObject<{
225
+ hashKey: z$1.ZodString;
226
+ rangeKey: z$1.ZodString;
227
+ projections: z$1.ZodOptional<z$1.ZodArray<z$1.ZodString>>;
228
+ }, z$1.core.$strip>>>>;
229
+ generatedKeyDelimiter: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodString>>;
230
+ generatedValueDelimiter: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodString>>;
231
+ propertyTranscodes: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodRecord<z$1.ZodString, z$1.ZodString>>>;
232
+ rangeKey: z$1.ZodString;
233
+ shardKeyDelimiter: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodString>>;
234
+ throttle: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodNumber>>;
235
+ transcodes: z$1.ZodDefault<z$1.ZodOptional<z$1.ZodRecord<z$1.ZodString, z$1.ZodObject<{
236
+ encode: z$1.ZodCustom<unknown, unknown>;
237
+ decode: z$1.ZodCustom<unknown, unknown>;
238
+ }, z$1.core.$strict>>>>;
239
+ }, z$1.core.$strict>;
240
+ /**
241
+ * Simplified type taken on by a {@link Config | `Config`} object after parsing in the {@link EntityManager | `EntityManager`} constructor.
242
+ *
243
+ * @category EntityManager
244
+ */
245
+ type ParsedConfig = z$1.infer<typeof configSchema>;
246
+
247
247
  /** EntityOfToken — resolves the concrete entity shape for a specific entity token. */
248
248
  type EntityOfToken<CC extends BaseConfigMap, ET extends EntityToken<CC>> = Exactify<CC['EntityMap']>[ET];
249
249
  /**
@@ -489,13 +489,17 @@ interface QueryResult<CC extends BaseConfigMap, ET extends EntityToken<CC>, ITS
489
489
  * query strategy to NoSql data.
490
490
  *
491
491
  * @typeParam CC - {@link ConfigMap | `ConfigMap`} that defines the configuration's {@link EntityMap | `EntityMap`}, key properties, and {@link TranscodeRegistry | `TranscodeRegistry`}. If omitted, defaults to {@link BaseConfigMap | `BaseConfigMap`}.
492
+ * @typeParam CF - Values-first config literal type captured at construction
493
+ * time (phantom generic; type-only). This is used by downstream
494
+ * adapters to infer index-token unions (ITS) and per-index page
495
+ * key shapes.
492
496
  *
493
497
  * @remarks
494
498
  * 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.
495
499
  *
496
500
  * @category EntityManager
497
501
  */
498
- declare class EntityManager<CC extends BaseConfigMap> {
502
+ declare class EntityManager<CC extends BaseConfigMap, CF = unknown> {
499
503
  #private;
500
504
  /** Logger object (defaults to `console`, must support `debug` & `error` methods). */
501
505
  readonly logger: Pick<Console, 'debug' | 'error'>;
@@ -582,17 +586,18 @@ declare class EntityManager<CC extends BaseConfigMap> {
582
586
  */
583
587
  removeKeys<ET extends EntityToken<CC>>(entityToken: ET, items: EntityRecordByToken<CC, ET>[]): EntityItemByToken<CC, ET>[];
584
588
  /**
585
- * Find an index token in a {@link Config | `Config`} object based on the index `hashKey` and `rangeKey`.
589
+ * Find an index token based on the configured hash and range key tokens.
586
590
  *
587
- * @param hashKeyToken - Index hash key.
588
- * @param rangeKeyToken - Index range key.
589
- * @param suppressError - Suppress error if no match found.
591
+ * @param hashKeyToken - Index hash key token (global hashKey or a sharded generated key).
592
+ * @param rangeKeyToken - Index range key token (global rangeKey, unsharded generated key, or a transcodable scalar).
593
+ * @param suppressError - When false (default), throws if no match; when true, returns undefined instead.
590
594
  *
591
- * @returns Index token if found.
595
+ * @returns A configured index token (narrowed to the CF.indexes key union) or undefined when allowed.
592
596
  *
593
- * @throws `Error` if no match found and `suppressError` is not `true`.
597
+ * @throws `Error` if no match is found and `suppressError` is not `true`.
594
598
  */
595
- findIndexToken(hashKeyToken: string, rangeKeyToken: string, suppressError?: boolean): string | undefined;
599
+ findIndexToken(hashKeyToken: CC['HashKey'] | CC['ShardedKeys'], rangeKeyToken: CC['RangeKey'] | CC['UnshardedKeys'] | CC['TranscodedProperties'], suppressError?: false): IndexTokensOf<CF>;
600
+ findIndexToken(hashKeyToken: CC['HashKey'] | CC['ShardedKeys'], rangeKeyToken: CC['RangeKey'] | CC['UnshardedKeys'] | CC['TranscodedProperties'], suppressError: true): IndexTokensOf<CF> | undefined;
596
601
  /**
597
602
  * Query a database entity across shards in a provider-generic fashion.
598
603
  *
@@ -712,8 +717,12 @@ type CapturedConfigMapFrom<CC, EM extends EntityMap> = {
712
717
  * `satisfies` at call sites to preserve literal keys.
713
718
  * @typeParam EM - EntityMap for the manager. Defaults to a minimal derived map
714
719
  * from `CC.entitiesSchema` when present; otherwise falls back to EntityMap.
720
+ *
721
+ * @returns An {@link EntityManager | `EntityManager`} instance whose type
722
+ * captures CF from the single values-first config literal ({@link ConfigInput | `ConfigInput`})
723
+ * as the second generic parameter (phantom; type-only).
715
724
  */
716
- declare function createEntityManager<const CC extends ConfigInput, EM extends EntityMap = EntitiesFromSchema<CC>>(config: CC, logger?: Pick<Console, 'debug' | 'error'>): EntityManager<CapturedConfigMapFrom<CC, EM>>;
725
+ declare function createEntityManager<const CC extends ConfigInput, EM extends EntityMap = EntitiesFromSchema<CC>>(config: CC, logger?: Pick<Console, 'debug' | 'error'>): EntityManager<CapturedConfigMapFrom<CC, EM>, CC>;
717
726
 
718
727
  /**
719
728
  * Database-facing record type from a {@link BaseConfigMap | `ConfigMap`} with required hash & range keys.
@@ -13,6 +13,10 @@ var _EntityManager_config;
13
13
  * query strategy to NoSql data.
14
14
  *
15
15
  * @typeParam CC - {@link ConfigMap | `ConfigMap`} that defines the configuration's {@link EntityMap | `EntityMap`}, key properties, and {@link TranscodeRegistry | `TranscodeRegistry`}. If omitted, defaults to {@link BaseConfigMap | `BaseConfigMap`}.
16
+ * @typeParam CF - Values-first config literal type captured at construction
17
+ * time (phantom generic; type-only). This is used by downstream
18
+ * adapters to infer index-token unions (ITS) and per-index page
19
+ * key shapes.
16
20
  *
17
21
  * @remarks
18
22
  * 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.
@@ -85,19 +89,11 @@ class EntityManager {
85
89
  }
86
90
  return removeKeys(this, entityToken, i);
87
91
  }
88
- /**
89
- * Find an index token in a {@link Config | `Config`} object based on the index `hashKey` and `rangeKey`.
90
- *
91
- * @param hashKeyToken - Index hash key.
92
- * @param rangeKeyToken - Index range key.
93
- * @param suppressError - Suppress error if no match found.
94
- *
95
- * @returns Index token if found.
96
- *
97
- * @throws `Error` if no match found and `suppressError` is not `true`.
98
- */
99
92
  findIndexToken(hashKeyToken, rangeKeyToken, suppressError) {
100
- return findIndexToken(this, hashKeyToken, rangeKeyToken, suppressError);
93
+ // Dispatch with a literal to satisfy overload selection.
94
+ return suppressError === true
95
+ ? findIndexToken(this, hashKeyToken, rangeKeyToken, true)
96
+ : findIndexToken(this, hashKeyToken, rangeKeyToken, false);
101
97
  }
102
98
  /**
103
99
  * Query a database entity across shards in a provider-generic fashion.
@@ -225,8 +225,19 @@ const configSchema = z
225
225
  path: ['generatedProperties', 'unsharded', property],
226
226
  });
227
227
  // Validate indexes.
228
- // TODO: Vaidate no two indexes have the same hashKey & rangeKey.
228
+ // Validate no two indexes have the same hashKey & rangeKey.
229
+ const seenIndexPairs = new Set();
229
230
  for (const [indexKey, { hashKey, rangeKey, projections }] of Object.entries(data.indexes)) {
231
+ const pairSig = `${hashKey}|${rangeKey}`;
232
+ if (seenIndexPairs.has(pairSig)) {
233
+ ctx.addIssue({
234
+ code: 'custom',
235
+ message: `duplicate index hashKey/rangeKey pair`,
236
+ path: ['indexes', indexKey],
237
+ });
238
+ }
239
+ else
240
+ seenIndexPairs.add(pairSig);
230
241
  // Validate hash key is sharded.
231
242
  if (![data.hashKey, ...shardedKeys].includes(hashKey)) {
232
243
  ctx.addIssue({
@@ -9,6 +9,10 @@ import { EntityManager } from './EntityManager.js';
9
9
  * `satisfies` at call sites to preserve literal keys.
10
10
  * @typeParam EM - EntityMap for the manager. Defaults to a minimal derived map
11
11
  * from `CC.entitiesSchema` when present; otherwise falls back to EntityMap.
12
+ *
13
+ * @returns An {@link EntityManager | `EntityManager`} instance whose type
14
+ * captures CF from the single values-first config literal ({@link ConfigInput | `ConfigInput`})
15
+ * as the second generic parameter (phantom; type-only).
12
16
  */
13
17
  function createEntityManager(config, logger = console) {
14
18
  // Cast to the existing Config<C> shape for runtime parsing; Zod validation
@@ -1,17 +1,6 @@
1
- /**
2
- * Find an index token in a {@link Config | `Config`} object based on the index `hashKey` and `rangeKey`.
3
- *
4
- * @param entityManager - {@link EntityManager | `EntityManager`} instance.
5
- * @param hashKeyToken - Index hash key.
6
- * @param rangeKeyToken - Index range key.
7
- * @param suppressError - Suppress error if no match found.
8
- *
9
- * @returns Index token if found.
10
- *
11
- * @throws `Error` if no match found and `suppressError` is not `true`.
12
- */
13
1
  function findIndexToken(entityManager, hashKeyToken, rangeKeyToken, suppressError) {
14
- const indexToken = Object.entries(entityManager.config.indexes).find(([, index]) => index.hashKey === hashKeyToken && index.rangeKey === rangeKeyToken)?.[0];
2
+ const indexToken = (Object.entries(entityManager.config.indexes).find(([, index]) => index.hashKey === hashKeyToken &&
3
+ index.rangeKey === rangeKeyToken)?.[0] ?? undefined);
15
4
  if (!indexToken && !suppressError)
16
5
  throw new Error(`No index token found for hashKey '${hashKeyToken}' & rangeKey '${rangeKeyToken}'.`);
17
6
  return indexToken;
package/package.json CHANGED
@@ -1,5 +1,11 @@
1
1
  {
2
2
  "author": "Jason Williscroft",
3
+ "auto-changelog": {
4
+ "commitLimit": false,
5
+ "hideCredit": true,
6
+ "output": "CHANGELOG.md",
7
+ "unreleased": true
8
+ },
3
9
  "bugs": {
4
10
  "url": "https://github.com/karmaniverous/entity-manager/issues"
5
11
  },
@@ -81,7 +87,7 @@
81
87
  },
82
88
  "release-it": {
83
89
  "git": {
84
- "changelog": "npx auto-changelog --stdout --commit-limit false --unreleased --template https://raw.githubusercontent.com/release-it/release-it/main/templates/changelog-compact.hbs",
90
+ "changelog": "npx auto-changelog --stdout --unreleased --template https://raw.githubusercontent.com/release-it/release-it/main/templates/changelog-compact.hbs",
85
91
  "commitMessage": "chore: release v${version}",
86
92
  "requireBranch": "main"
87
93
  },
@@ -128,5 +134,5 @@
128
134
  },
129
135
  "type": "module",
130
136
  "types": "dist/index.d.ts",
131
- "version": "7.2.0"
137
+ "version": "7.3.1"
132
138
  }