dyno-table 2.3.3 → 2.4.0

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.
@@ -1,3 +1,4 @@
1
+ import { EntityErrors, OperationErrors, DynoTableError, ValidationErrors, IndexErrors, ConfigurationErrors } from './chunk-FF7FYGDH.js';
1
2
  import { eq } from './chunk-2WIBY7PZ.js';
2
3
 
3
4
  // src/builders/entity-aware-builders.ts
@@ -112,9 +113,6 @@ var EntityAwareUpdateBuilder = class {
112
113
  if (typeof valuesOrPath === "object") {
113
114
  this.builder.set(valuesOrPath);
114
115
  } else {
115
- if (value === void 0) {
116
- throw new Error("Value is required when setting a single path");
117
- }
118
116
  this.builder.set(valuesOrPath, value);
119
117
  }
120
118
  return this;
@@ -159,6 +157,136 @@ function createEntityAwareUpdateBuilder(builder, entityName) {
159
157
  return new EntityAwareUpdateBuilder(builder, entityName);
160
158
  }
161
159
 
160
+ // src/utils/error-utils.ts
161
+ function isConditionalCheckFailed(error) {
162
+ if (typeof error === "object" && error !== null && "name" in error) {
163
+ return error.name === "ConditionalCheckFailedException";
164
+ }
165
+ return false;
166
+ }
167
+ function isTransactionCanceled(error) {
168
+ if (typeof error === "object" && error !== null && "name" in error) {
169
+ return error.name === "TransactionCanceledException";
170
+ }
171
+ return false;
172
+ }
173
+ function isValidationException(error) {
174
+ if (typeof error === "object" && error !== null && "name" in error) {
175
+ return error.name === "ValidationException";
176
+ }
177
+ return false;
178
+ }
179
+ function isProvisionedThroughputExceeded(error) {
180
+ if (typeof error === "object" && error !== null && "name" in error) {
181
+ return error.name === "ProvisionedThroughputExceededException";
182
+ }
183
+ return false;
184
+ }
185
+ function isRetryableError(error) {
186
+ if (typeof error === "object" && error !== null && "name" in error) {
187
+ const errorName = error.name;
188
+ return errorName === "ProvisionedThroughputExceededException" || errorName === "ThrottlingException" || errorName === "RequestLimitExceeded" || errorName === "InternalServerError" || errorName === "ServiceUnavailable";
189
+ }
190
+ return false;
191
+ }
192
+ function getAwsErrorCode(error) {
193
+ if (typeof error === "object" && error !== null && "name" in error) {
194
+ return error.name;
195
+ }
196
+ return void 0;
197
+ }
198
+ function getAwsErrorMessage(error) {
199
+ if (error instanceof Error) {
200
+ return error.message;
201
+ }
202
+ if (typeof error === "object" && error !== null && "message" in error) {
203
+ return String(error.message);
204
+ }
205
+ return void 0;
206
+ }
207
+ function extractRequiredAttributes(error) {
208
+ const message = getAwsErrorMessage(error);
209
+ if (!message) return void 0;
210
+ const patterns = [
211
+ /(?:missing|required)\s+(?:attribute|field|property)(?:s)?[:\s]+([a-zA-Z0-9_,\s]+)/i,
212
+ /(?:attribute|field|property)[:\s]+([a-zA-Z0-9_]+)\s+is\s+(?:missing|required)/i,
213
+ /"([a-zA-Z0-9_]+)"\s+is\s+(?:missing|required)/i
214
+ ];
215
+ for (const pattern of patterns) {
216
+ const match = message.match(pattern);
217
+ if (match?.[1]) {
218
+ return match[1].split(",").map((attr) => attr.trim()).filter((attr) => attr.length > 0);
219
+ }
220
+ }
221
+ return void 0;
222
+ }
223
+ function formatErrorContext(context, indent = 0) {
224
+ const indentStr = " ".repeat(indent);
225
+ const lines = [];
226
+ for (const [key, value] of Object.entries(context)) {
227
+ if (value === void 0 || value === null) {
228
+ lines.push(`${indentStr}${key}: ${value}`);
229
+ } else if (Array.isArray(value)) {
230
+ lines.push(`${indentStr}${key}: [${value.map((v) => JSON.stringify(v)).join(", ")}]`);
231
+ } else if (typeof value === "object") {
232
+ lines.push(`${indentStr}${key}:`);
233
+ lines.push(formatErrorContext(value, indent + 1));
234
+ } else if (typeof value === "string" && value.length > 100) {
235
+ lines.push(`${indentStr}${key}: ${value.substring(0, 100)}...`);
236
+ } else {
237
+ lines.push(`${indentStr}${key}: ${JSON.stringify(value)}`);
238
+ }
239
+ }
240
+ return lines.join("\n");
241
+ }
242
+ function getErrorSummary(error) {
243
+ const parts = [];
244
+ parts.push(`Error: ${error.name}`);
245
+ parts.push(`Code: ${error.code}`);
246
+ parts.push(`Message: ${error.message}`);
247
+ if (Object.keys(error.context).length > 0) {
248
+ parts.push("Context:");
249
+ parts.push(formatErrorContext(error.context, 1));
250
+ }
251
+ if (error.cause) {
252
+ parts.push(`Caused by: ${error.cause.name}: ${error.cause.message}`);
253
+ }
254
+ return parts.join("\n");
255
+ }
256
+ function isDynoTableError(error) {
257
+ return typeof error === "object" && error !== null && "code" in error && "context" in error && error instanceof Error;
258
+ }
259
+ function isValidationError(error) {
260
+ return error instanceof Error && error.name === "ValidationError";
261
+ }
262
+ function isOperationError(error) {
263
+ return error instanceof Error && error.name === "OperationError";
264
+ }
265
+ function isTransactionError(error) {
266
+ return error instanceof Error && error.name === "TransactionError";
267
+ }
268
+ function isBatchError(error) {
269
+ return error instanceof Error && error.name === "BatchError";
270
+ }
271
+ function isExpressionError(error) {
272
+ return error instanceof Error && error.name === "ExpressionError";
273
+ }
274
+ function isConfigurationError(error) {
275
+ return error instanceof Error && error.name === "ConfigurationError";
276
+ }
277
+ function isEntityError(error) {
278
+ return error instanceof Error && error.name === "EntityError";
279
+ }
280
+ function isKeyGenerationError(error) {
281
+ return error instanceof Error && error.name === "KeyGenerationError";
282
+ }
283
+ function isIndexGenerationError(error) {
284
+ return error instanceof Error && error.name === "IndexGenerationError";
285
+ }
286
+ function isEntityValidationError(error) {
287
+ return error instanceof Error && error.name === "EntityValidationError";
288
+ }
289
+
162
290
  // src/entity/ddb-indexing.ts
163
291
  var IndexBuilder = class {
164
292
  /**
@@ -184,10 +312,26 @@ var IndexBuilder = class {
184
312
  if (options.excludeReadOnly && indexDef.isReadOnly) {
185
313
  continue;
186
314
  }
187
- const key = indexDef.generateKey(item);
315
+ let key;
316
+ try {
317
+ key = indexDef.generateKey(item);
318
+ if (this.hasUndefinedValues(key)) {
319
+ throw IndexErrors.undefinedValues(indexName, "create", key, item);
320
+ }
321
+ } catch (error) {
322
+ if (error instanceof DynoTableError) throw error;
323
+ throw IndexErrors.generationFailed(
324
+ indexName,
325
+ "create",
326
+ item,
327
+ indexDef.partitionKey,
328
+ indexDef.sortKey,
329
+ error instanceof Error ? error : void 0
330
+ );
331
+ }
188
332
  const gsiConfig = this.table.gsis[indexName];
189
333
  if (!gsiConfig) {
190
- throw new Error(`GSI configuration not found for index: ${indexName}`);
334
+ throw ConfigurationErrors.gsiNotFound(indexName, this.table.tableName, Object.keys(this.table.gsis));
191
335
  }
192
336
  if (key.pk) {
193
337
  attributes[gsiConfig.partitionKey] = key.pk;
@@ -212,9 +356,7 @@ var IndexBuilder = class {
212
356
  if (options.forceRebuildIndexes && options.forceRebuildIndexes.length > 0) {
213
357
  const invalidIndexes = options.forceRebuildIndexes.filter((indexName) => !this.indexes[indexName]);
214
358
  if (invalidIndexes.length > 0) {
215
- throw new Error(
216
- `Cannot force rebuild unknown indexes: ${invalidIndexes.join(", ")}. Available indexes: ${Object.keys(this.indexes).join(", ")}`
217
- );
359
+ throw IndexErrors.notFound(invalidIndexes, Object.keys(this.indexes), void 0, this.table.tableName);
218
360
  }
219
361
  }
220
362
  for (const [indexName, indexDef] of Object.entries(this.indexes)) {
@@ -241,19 +383,22 @@ var IndexBuilder = class {
241
383
  try {
242
384
  key = indexDef.generateKey(updatedItem);
243
385
  } catch (error) {
244
- if (error instanceof Error) {
245
- throw new Error(`Missing attributes: ${error.message}`);
246
- }
247
- throw error;
386
+ if (error instanceof DynoTableError) throw error;
387
+ throw IndexErrors.missingAttributes(
388
+ indexName,
389
+ "update",
390
+ [],
391
+ // We don't know which specific attributes are missing from the error
392
+ updates,
393
+ indexDef.isReadOnly
394
+ );
248
395
  }
249
396
  if (this.hasUndefinedValues(key)) {
250
- throw new Error(
251
- `Missing attributes: Cannot update entity: insufficient data to regenerate index "${indexName}". All attributes required by the index must be provided in the update operation, or the index must be marked as readOnly.`
252
- );
397
+ throw IndexErrors.undefinedValues(indexName, "update", key, updates);
253
398
  }
254
399
  const gsiConfig = this.table.gsis[indexName];
255
400
  if (!gsiConfig) {
256
- throw new Error(`GSI configuration not found for index: ${indexName}`);
401
+ throw ConfigurationErrors.gsiNotFound(indexName, this.table.tableName, Object.keys(this.table.gsis));
257
402
  }
258
403
  if (key.pk) {
259
404
  attributes[gsiConfig.partitionKey] = key.pk;
@@ -340,13 +485,28 @@ function defineEntity(config) {
340
485
  const prepareValidatedItemAsync = async () => {
341
486
  const validatedData = await config.schema["~standard"].validate(data);
342
487
  if ("issues" in validatedData && validatedData.issues) {
343
- throw new Error(`Validation failed: ${validatedData.issues.map((i) => i.message).join(", ")}`);
488
+ throw EntityErrors.validationFailed(config.name, "create", validatedData.issues, data);
344
489
  }
345
490
  const dataForKeyGeneration = {
346
491
  ...validatedData.value,
347
492
  ...generateTimestamps(["createdAt", "updatedAt"], validatedData.value)
348
493
  };
349
- const primaryKey = config.primaryKey.generateKey(dataForKeyGeneration);
494
+ let primaryKey;
495
+ try {
496
+ primaryKey = config.primaryKey.generateKey(dataForKeyGeneration);
497
+ if (primaryKey.pk === void 0 || primaryKey.pk === null) {
498
+ throw EntityErrors.keyInvalidFormat(config.name, "create", dataForKeyGeneration, primaryKey);
499
+ }
500
+ } catch (error) {
501
+ if (error instanceof DynoTableError) throw error;
502
+ throw EntityErrors.keyGenerationFailed(
503
+ config.name,
504
+ "create",
505
+ dataForKeyGeneration,
506
+ extractRequiredAttributes(error),
507
+ error instanceof Error ? error : void 0
508
+ );
509
+ }
350
510
  const indexes = buildIndexes(dataForKeyGeneration, table, config.indexes, false);
351
511
  const validatedItem = {
352
512
  ...dataForKeyGeneration,
@@ -361,18 +521,31 @@ function defineEntity(config) {
361
521
  const prepareValidatedItemSync = () => {
362
522
  const validationResult = config.schema["~standard"].validate(data);
363
523
  if (validationResult instanceof Promise) {
364
- throw new Error(
365
- "Async validation is not supported in withBatch or withTransaction. The schema must support synchronous validation for compatibility."
366
- );
524
+ throw EntityErrors.asyncValidationNotSupported(config.name, "create");
367
525
  }
368
526
  if ("issues" in validationResult && validationResult.issues) {
369
- throw new Error(`Validation failed: ${validationResult.issues.map((i) => i.message).join(", ")}`);
527
+ throw EntityErrors.validationFailed(config.name, "create", validationResult.issues, data);
370
528
  }
371
529
  const dataForKeyGeneration = {
372
530
  ...validationResult.value,
373
531
  ...generateTimestamps(["createdAt", "updatedAt"], validationResult.value)
374
532
  };
375
- const primaryKey = config.primaryKey.generateKey(dataForKeyGeneration);
533
+ let primaryKey;
534
+ try {
535
+ primaryKey = config.primaryKey.generateKey(dataForKeyGeneration);
536
+ if (primaryKey.pk === void 0 || primaryKey.pk === null) {
537
+ throw EntityErrors.keyInvalidFormat(config.name, "create", dataForKeyGeneration, primaryKey);
538
+ }
539
+ } catch (error) {
540
+ if (error instanceof DynoTableError) throw error;
541
+ throw EntityErrors.keyGenerationFailed(
542
+ config.name,
543
+ "create",
544
+ dataForKeyGeneration,
545
+ extractRequiredAttributes(error),
546
+ error instanceof Error ? error : void 0
547
+ );
548
+ }
376
549
  const indexes = buildIndexes(dataForKeyGeneration, table, config.indexes, false);
377
550
  const validatedItem = {
378
551
  ...dataForKeyGeneration,
@@ -408,13 +581,28 @@ function defineEntity(config) {
408
581
  const prepareValidatedItemAsync = async () => {
409
582
  const validatedData = await config.schema["~standard"].validate(data);
410
583
  if ("issues" in validatedData && validatedData.issues) {
411
- throw new Error(`Validation failed: ${validatedData.issues.map((i) => i.message).join(", ")}`);
584
+ throw EntityErrors.validationFailed(config.name, "upsert", validatedData.issues, data);
412
585
  }
413
586
  const dataForKeyGeneration = {
414
587
  ...validatedData.value,
415
588
  ...generateTimestamps(["createdAt", "updatedAt"], validatedData.value)
416
589
  };
417
- const primaryKey = config.primaryKey.generateKey(dataForKeyGeneration);
590
+ let primaryKey;
591
+ try {
592
+ primaryKey = config.primaryKey.generateKey(dataForKeyGeneration);
593
+ if (primaryKey.pk === void 0 || primaryKey.pk === null) {
594
+ throw EntityErrors.keyInvalidFormat(config.name, "upsert", dataForKeyGeneration, primaryKey);
595
+ }
596
+ } catch (error) {
597
+ if (error instanceof DynoTableError) throw error;
598
+ throw EntityErrors.keyGenerationFailed(
599
+ config.name,
600
+ "upsert",
601
+ dataForKeyGeneration,
602
+ extractRequiredAttributes(error),
603
+ error instanceof Error ? error : void 0
604
+ );
605
+ }
418
606
  const indexes = buildIndexes2(dataForKeyGeneration, table, false);
419
607
  const validatedItem = {
420
608
  [table.partitionKey]: primaryKey.pk,
@@ -429,18 +617,31 @@ function defineEntity(config) {
429
617
  const prepareValidatedItemSync = () => {
430
618
  const validationResult = config.schema["~standard"].validate(data);
431
619
  if (validationResult instanceof Promise) {
432
- throw new Error(
433
- "Async validation is not supported in withTransaction or withBatch. Use execute() instead."
434
- );
620
+ throw EntityErrors.asyncValidationNotSupported(config.name, "upsert");
435
621
  }
436
622
  if ("issues" in validationResult && validationResult.issues) {
437
- throw new Error(`Validation failed: ${validationResult.issues.map((i) => i.message).join(", ")}`);
623
+ throw EntityErrors.validationFailed(config.name, "upsert", validationResult.issues, data);
438
624
  }
439
625
  const dataForKeyGeneration = {
440
626
  ...validationResult.value,
441
627
  ...generateTimestamps(["createdAt", "updatedAt"], validationResult.value)
442
628
  };
443
- const primaryKey = config.primaryKey.generateKey(dataForKeyGeneration);
629
+ let primaryKey;
630
+ try {
631
+ primaryKey = config.primaryKey.generateKey(dataForKeyGeneration);
632
+ if (primaryKey.pk === void 0 || primaryKey.pk === null) {
633
+ throw EntityErrors.keyInvalidFormat(config.name, "upsert", dataForKeyGeneration, primaryKey);
634
+ }
635
+ } catch (error) {
636
+ if (error instanceof DynoTableError) throw error;
637
+ throw EntityErrors.keyGenerationFailed(
638
+ config.name,
639
+ "upsert",
640
+ dataForKeyGeneration,
641
+ extractRequiredAttributes(error),
642
+ error instanceof Error ? error : void 0
643
+ );
644
+ }
444
645
  const indexes = buildIndexes(dataForKeyGeneration, table, config.indexes, false);
445
646
  const validatedItem = {
446
647
  [table.partitionKey]: primaryKey.pk,
@@ -457,7 +658,7 @@ function defineEntity(config) {
457
658
  await prepareValidatedItemAsync();
458
659
  const result = await originalExecute.call(builder);
459
660
  if (!result) {
460
- throw new Error("Failed to upsert item");
661
+ throw OperationErrors.putFailed(config.name, {}, void 0);
461
662
  }
462
663
  return result;
463
664
  };
@@ -476,7 +677,8 @@ function defineEntity(config) {
476
677
  return createEntityAwarePutBuilder(builder, config.name);
477
678
  },
478
679
  get: (key) => {
479
- return createEntityAwareGetBuilder(table.get(config.primaryKey.generateKey(key)), config.name);
680
+ const builder = table.get(config.primaryKey.generateKey(key));
681
+ return createEntityAwareGetBuilder(builder, config.name);
480
682
  },
481
683
  update: (key, data) => {
482
684
  const primaryKeyObj = config.primaryKey.generateKey(key);
@@ -522,15 +724,13 @@ function defineEntity(config) {
522
724
  if (schema?.["~standard"]?.validate && typeof schema["~standard"].validate === "function") {
523
725
  const validationResult = schema["~standard"].validate(input);
524
726
  if ("issues" in validationResult && validationResult.issues) {
525
- throw new Error(
526
- `Validation failed: ${validationResult.issues.map((issue) => issue.message).join(", ")}`
527
- );
727
+ throw EntityErrors.queryInputValidationFailed(config.name, key, validationResult.issues, input);
528
728
  }
529
729
  }
530
730
  }
531
731
  const result = await originalExecute.call(builder);
532
732
  if (!result) {
533
- throw new Error("Failed to execute query");
733
+ throw OperationErrors.queryFailed(config.name, { queryName: key }, void 0);
534
734
  }
535
735
  return result;
536
736
  };
@@ -576,7 +776,7 @@ function createIndex() {
576
776
  generateKey: (item) => {
577
777
  const data = schema["~standard"].validate(item);
578
778
  if ("issues" in data && data.issues) {
579
- throw new Error(`Index validation failed: ${data.issues.map((i) => i.message).join(", ")}`);
779
+ throw ValidationErrors.indexSchemaValidationFailed(data.issues, "both");
580
780
  }
581
781
  const validData = "value" in data ? data.value : item;
582
782
  return { pk: pkFn(validData), sk: skFn(validData) };
@@ -597,7 +797,7 @@ function createIndex() {
597
797
  generateKey: (item) => {
598
798
  const data = schema["~standard"].validate(item);
599
799
  if ("issues" in data && data.issues) {
600
- throw new Error(`Index validation failed: ${data.issues.map((i) => i.message).join(", ")}`);
800
+ throw ValidationErrors.indexSchemaValidationFailed(data.issues, "partition");
601
801
  }
602
802
  const validData = "value" in data ? data.value : item;
603
803
  return { pk: pkFn(validData) };
@@ -618,4 +818,4 @@ function createIndex() {
618
818
  };
619
819
  }
620
820
 
621
- export { createIndex, createQueries, defineEntity };
821
+ export { createIndex, createQueries, defineEntity, extractRequiredAttributes, formatErrorContext, getAwsErrorCode, getAwsErrorMessage, getErrorSummary, isBatchError, isConditionalCheckFailed, isConfigurationError, isDynoTableError, isEntityError, isEntityValidationError, isExpressionError, isIndexGenerationError, isKeyGenerationError, isOperationError, isProvisionedThroughputExceeded, isRetryableError, isTransactionCanceled, isTransactionError, isValidationError, isValidationException };