dyno-table 2.0.1 → 2.1.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.
Files changed (72) hide show
  1. package/README.md +10 -0
  2. package/dist/{batch-builder-TpkmiBp5.d.ts → batch-builder-BytHNL_u.d.ts} +1 -1
  3. package/dist/{batch-builder-CzNAxWNT.d.cts → batch-builder-CcxFDKhe.d.cts} +1 -1
  4. package/dist/builders/condition-check-builder.cjs +19 -0
  5. package/dist/builders/condition-check-builder.cjs.map +1 -1
  6. package/dist/builders/condition-check-builder.d.cts +12 -3
  7. package/dist/builders/condition-check-builder.d.ts +12 -3
  8. package/dist/builders/condition-check-builder.js +19 -0
  9. package/dist/builders/condition-check-builder.js.map +1 -1
  10. package/dist/builders/delete-builder.cjs +19 -0
  11. package/dist/builders/delete-builder.cjs.map +1 -1
  12. package/dist/builders/delete-builder.d.cts +12 -3
  13. package/dist/builders/delete-builder.d.ts +12 -3
  14. package/dist/builders/delete-builder.js +19 -0
  15. package/dist/builders/delete-builder.js.map +1 -1
  16. package/dist/builders/paginator.cjs.map +1 -1
  17. package/dist/builders/paginator.js.map +1 -1
  18. package/dist/builders/put-builder.cjs +19 -0
  19. package/dist/builders/put-builder.cjs.map +1 -1
  20. package/dist/builders/put-builder.d.cts +12 -3
  21. package/dist/builders/put-builder.d.ts +12 -3
  22. package/dist/builders/put-builder.js +19 -0
  23. package/dist/builders/put-builder.js.map +1 -1
  24. package/dist/builders/query-builder.cjs.map +1 -1
  25. package/dist/builders/query-builder.d.cts +2 -2
  26. package/dist/builders/query-builder.d.ts +2 -2
  27. package/dist/builders/query-builder.js.map +1 -1
  28. package/dist/builders/transaction-builder.cjs +19 -0
  29. package/dist/builders/transaction-builder.cjs.map +1 -1
  30. package/dist/builders/transaction-builder.d.cts +2 -2
  31. package/dist/builders/transaction-builder.d.ts +2 -2
  32. package/dist/builders/transaction-builder.js +19 -0
  33. package/dist/builders/transaction-builder.js.map +1 -1
  34. package/dist/builders/update-builder.cjs +19 -0
  35. package/dist/builders/update-builder.cjs.map +1 -1
  36. package/dist/builders/update-builder.d.cts +11 -2
  37. package/dist/builders/update-builder.d.ts +11 -2
  38. package/dist/builders/update-builder.js +19 -0
  39. package/dist/builders/update-builder.js.map +1 -1
  40. package/dist/{conditions-3ae5znV_.d.cts → conditions-CC3NDfUU.d.cts} +15 -13
  41. package/dist/{conditions-BtynAviC.d.ts → conditions-DD0bvyHm.d.ts} +15 -13
  42. package/dist/conditions.cjs.map +1 -1
  43. package/dist/conditions.d.cts +1 -1
  44. package/dist/conditions.d.ts +1 -1
  45. package/dist/conditions.js.map +1 -1
  46. package/dist/entity.cjs +192 -42
  47. package/dist/entity.cjs.map +1 -1
  48. package/dist/entity.d.cts +20 -8
  49. package/dist/entity.d.ts +20 -8
  50. package/dist/entity.js +192 -42
  51. package/dist/entity.js.map +1 -1
  52. package/dist/index.cjs +203 -34
  53. package/dist/index.cjs.map +1 -1
  54. package/dist/index.d.cts +4 -4
  55. package/dist/index.d.ts +4 -4
  56. package/dist/index.js +203 -34
  57. package/dist/index.js.map +1 -1
  58. package/dist/{query-builder-BK9eM-gt.d.ts → query-builder-BNWRCrJW.d.ts} +1 -1
  59. package/dist/{query-builder-BALW-vW_.d.cts → query-builder-DZ9JKgBN.d.cts} +1 -1
  60. package/dist/{table-CEL7Lt1r.d.ts → table-BhEeYauU.d.ts} +3 -3
  61. package/dist/{table-DhvYVXQ6.d.cts → table-BpNOboD9.d.cts} +3 -3
  62. package/dist/table.cjs +19 -0
  63. package/dist/table.cjs.map +1 -1
  64. package/dist/table.d.cts +4 -4
  65. package/dist/table.d.ts +4 -4
  66. package/dist/table.js +19 -0
  67. package/dist/table.js.map +1 -1
  68. package/dist/types.d.cts +9 -2
  69. package/dist/types.d.ts +9 -2
  70. package/dist/utils.cjs.map +1 -1
  71. package/dist/utils.js.map +1 -1
  72. package/package.json +1 -1
package/dist/entity.js CHANGED
@@ -1,11 +1,3 @@
1
- // src/conditions.ts
2
- var createComparisonCondition = (type) => (attr, value) => ({
3
- type,
4
- attr,
5
- value
6
- });
7
- var eq = createComparisonCondition("eq");
8
-
9
1
  // src/builders/entity-aware-builders.ts
10
2
  function createEntityAwareBuilder(builder, entityName) {
11
3
  return new Proxy(builder, {
@@ -34,27 +26,141 @@ function createEntityAwareDeleteBuilder(builder, entityName) {
34
26
  return createEntityAwareBuilder(builder, entityName);
35
27
  }
36
28
 
37
- // src/entity.ts
38
- function defineEntity(config) {
39
- const entityTypeAttributeName = config.settings?.entityTypeAttributeName ?? "entityType";
40
- const buildIndexes = (dataForKeyGeneration, table) => {
41
- return Object.entries(config.indexes ?? {}).reduce(
42
- (acc, [indexName, index]) => {
43
- const key = index.generateKey(dataForKeyGeneration);
44
- const gsiConfig = table.gsis[indexName];
29
+ // src/conditions.ts
30
+ var createComparisonCondition = (type) => (attr, value) => ({
31
+ type,
32
+ attr,
33
+ value
34
+ });
35
+ var eq = createComparisonCondition("eq");
36
+
37
+ // src/entity/ddb-indexing.ts
38
+ var IndexBuilder = class {
39
+ /**
40
+ * Creates a new IndexBuilder instance
41
+ *
42
+ * @param table - The DynamoDB table instance
43
+ * @param indexes - The index definitions
44
+ */
45
+ constructor(table, indexes = {}) {
46
+ this.table = table;
47
+ this.indexes = indexes;
48
+ }
49
+ /**
50
+ * Build index attributes for item creation
51
+ *
52
+ * @param item - The item to generate indexes for
53
+ * @param options - Options for building indexes
54
+ * @returns Record of GSI attribute names to their values
55
+ */
56
+ buildForCreate(item, options = {}) {
57
+ const attributes = {};
58
+ for (const [indexName, indexDef] of Object.entries(this.indexes)) {
59
+ if (options.excludeReadOnly && indexDef.isReadOnly) {
60
+ continue;
61
+ }
62
+ const key = indexDef.generateKey(item);
63
+ const gsiConfig = this.table.gsis[indexName];
64
+ if (!gsiConfig) {
65
+ throw new Error(`GSI configuration not found for index: ${indexName}`);
66
+ }
67
+ if (key.pk) {
68
+ attributes[gsiConfig.partitionKey] = key.pk;
69
+ }
70
+ if (key.sk && gsiConfig.sortKey) {
71
+ attributes[gsiConfig.sortKey] = key.sk;
72
+ }
73
+ }
74
+ return attributes;
75
+ }
76
+ /**
77
+ * Build index attributes for item updates
78
+ *
79
+ * @param currentData - The current data before update
80
+ * @param updates - The update data
81
+ * @param options - Options for building indexes
82
+ * @returns Record of GSI attribute names to their updated values
83
+ */
84
+ buildForUpdate(currentData, updates) {
85
+ const attributes = {};
86
+ const updatedItem = { ...currentData, ...updates };
87
+ for (const [indexName, indexDef] of Object.entries(this.indexes)) {
88
+ if (indexDef.isReadOnly) {
89
+ continue;
90
+ }
91
+ let shouldUpdateIndex = false;
92
+ try {
93
+ const currentKey = indexDef.generateKey(currentData);
94
+ const updatedKey = indexDef.generateKey(updatedItem);
95
+ if (currentKey.pk !== updatedKey.pk || currentKey.sk !== updatedKey.sk) {
96
+ shouldUpdateIndex = true;
97
+ }
98
+ } catch {
99
+ shouldUpdateIndex = true;
100
+ }
101
+ if (!shouldUpdateIndex) {
102
+ continue;
103
+ }
104
+ try {
105
+ const key = indexDef.generateKey(updatedItem);
106
+ if (this.hasUndefinedValues(key)) {
107
+ throw new Error(
108
+ `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.`
109
+ );
110
+ }
111
+ const gsiConfig = this.table.gsis[indexName];
45
112
  if (!gsiConfig) {
46
113
  throw new Error(`GSI configuration not found for index: ${indexName}`);
47
114
  }
48
115
  if (key.pk) {
49
- acc[gsiConfig.partitionKey] = key.pk;
116
+ attributes[gsiConfig.partitionKey] = key.pk;
50
117
  }
51
118
  if (key.sk && gsiConfig.sortKey) {
52
- acc[gsiConfig.sortKey] = key.sk;
119
+ attributes[gsiConfig.sortKey] = key.sk;
120
+ }
121
+ } catch (error) {
122
+ if (error instanceof Error && error.message.includes("insufficient data")) {
123
+ throw error;
53
124
  }
54
- return acc;
55
- },
56
- {}
57
- );
125
+ throw new Error(
126
+ `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 readOnly.`
127
+ );
128
+ }
129
+ }
130
+ return attributes;
131
+ }
132
+ /**
133
+ * Check if a key has undefined values
134
+ *
135
+ * @param key - The index key to check
136
+ * @returns True if the key contains undefined values, false otherwise
137
+ */
138
+ hasUndefinedValues(key) {
139
+ return (key.pk?.includes("undefined") ?? false) || (key.sk?.includes("undefined") ?? false);
140
+ }
141
+ };
142
+
143
+ // src/entity/index-utils.ts
144
+ function buildIndexes(dataForKeyGeneration, table, indexes, excludeReadOnly = false) {
145
+ if (!indexes) {
146
+ return {};
147
+ }
148
+ const indexBuilder = new IndexBuilder(table, indexes);
149
+ return indexBuilder.buildForCreate(dataForKeyGeneration, { excludeReadOnly });
150
+ }
151
+ function buildIndexUpdates(currentData, updates, table, indexes) {
152
+ if (!indexes) {
153
+ return {};
154
+ }
155
+ const indexBuilder = new IndexBuilder(table, indexes);
156
+ return indexBuilder.buildForUpdate(currentData, updates);
157
+ }
158
+
159
+ // src/entity/entity.ts
160
+ function defineEntity(config) {
161
+ const entityTypeAttributeName = config.settings?.entityTypeAttributeName ?? "entityType";
162
+ const buildIndexes2 = (dataForKeyGeneration, table, excludeReadOnly = false) => {
163
+ return buildIndexes(dataForKeyGeneration, table, config.indexes, excludeReadOnly);
58
164
  };
59
165
  const wrapMethodWithPreparation = (originalMethod, prepareFn, context) => {
60
166
  const wrappedMethod = (...args) => {
@@ -106,7 +212,7 @@ function defineEntity(config) {
106
212
  ...generateTimestamps(["createdAt", "updatedAt"], validatedData.value)
107
213
  };
108
214
  const primaryKey = config.primaryKey.generateKey(dataForKeyGeneration);
109
- const indexes = buildIndexes(dataForKeyGeneration, table);
215
+ const indexes = buildIndexes(dataForKeyGeneration, table, config.indexes, false);
110
216
  const validatedItem = {
111
217
  ...dataForKeyGeneration,
112
218
  [entityTypeAttributeName]: config.name,
@@ -132,7 +238,7 @@ function defineEntity(config) {
132
238
  ...generateTimestamps(["createdAt", "updatedAt"], validationResult.value)
133
239
  };
134
240
  const primaryKey = config.primaryKey.generateKey(dataForKeyGeneration);
135
- const indexes = buildIndexes(dataForKeyGeneration, table);
241
+ const indexes = buildIndexes(dataForKeyGeneration, table, config.indexes, false);
136
242
  const validatedItem = {
137
243
  ...dataForKeyGeneration,
138
244
  [entityTypeAttributeName]: config.name,
@@ -174,7 +280,7 @@ function defineEntity(config) {
174
280
  ...generateTimestamps(["createdAt", "updatedAt"], validatedData.value)
175
281
  };
176
282
  const primaryKey = config.primaryKey.generateKey(dataForKeyGeneration);
177
- const indexes = buildIndexes(dataForKeyGeneration, table);
283
+ const indexes = buildIndexes2(dataForKeyGeneration, table, false);
178
284
  const validatedItem = {
179
285
  [table.partitionKey]: primaryKey.pk,
180
286
  ...table.sortKey ? { [table.sortKey]: primaryKey.sk } : {},
@@ -200,7 +306,7 @@ function defineEntity(config) {
200
306
  ...generateTimestamps(["createdAt", "updatedAt"], validationResult.value)
201
307
  };
202
308
  const primaryKey = config.primaryKey.generateKey(dataForKeyGeneration);
203
- const indexes = buildIndexes(dataForKeyGeneration, table);
309
+ const indexes = buildIndexes(dataForKeyGeneration, table, config.indexes, false);
204
310
  const validatedItem = {
205
311
  [table.partitionKey]: primaryKey.pk,
206
312
  ...table.sortKey ? { [table.sortKey]: primaryKey.sk } : {},
@@ -234,13 +340,21 @@ function defineEntity(config) {
234
340
  }
235
341
  return createEntityAwarePutBuilder(builder, config.name);
236
342
  },
237
- get: (key) => createEntityAwareGetBuilder(table.get(config.primaryKey.generateKey(key)), config.name),
343
+ get: (key) => {
344
+ return createEntityAwareGetBuilder(table.get(config.primaryKey.generateKey(key)), config.name);
345
+ },
238
346
  update: (key, data) => {
239
347
  const primaryKeyObj = config.primaryKey.generateKey(key);
240
348
  const builder = table.update(primaryKeyObj);
241
349
  builder.condition(eq(entityTypeAttributeName, config.name));
242
350
  const timestamps = generateTimestamps(["updatedAt"], data);
243
- builder.set({ ...data, ...timestamps });
351
+ const indexUpdates = buildIndexUpdates(
352
+ { ...key },
353
+ { ...data, ...timestamps },
354
+ table,
355
+ config.indexes
356
+ );
357
+ builder.set({ ...data, ...timestamps, ...indexUpdates });
244
358
  return builder;
245
359
  },
246
360
  delete: (key) => {
@@ -311,21 +425,57 @@ function createQueries() {
311
425
  }
312
426
  function createIndex() {
313
427
  return {
314
- input: (schema) => ({
315
- partitionKey: (pkFn) => ({
316
- sortKey: (skFn) => ({
317
- name: "custom",
318
- partitionKey: "pk",
319
- sortKey: "sk",
320
- generateKey: (item) => ({ pk: pkFn(item), sk: skFn(item) })
428
+ input: (schema) => {
429
+ const createIndexBuilder = (isReadOnly = false) => ({
430
+ partitionKey: (pkFn) => ({
431
+ sortKey: (skFn) => {
432
+ const index = {
433
+ name: "custom",
434
+ partitionKey: "pk",
435
+ sortKey: "sk",
436
+ isReadOnly,
437
+ generateKey: (item) => {
438
+ const data = schema["~standard"].validate(item);
439
+ if ("issues" in data && data.issues) {
440
+ throw new Error(`Index validation failed: ${data.issues.map((i) => i.message).join(", ")}`);
441
+ }
442
+ const validData = "value" in data ? data.value : item;
443
+ return { pk: pkFn(validData), sk: skFn(validData) };
444
+ }
445
+ };
446
+ return Object.assign(index, {
447
+ readOnly: (value = false) => ({
448
+ ...index,
449
+ isReadOnly: value
450
+ })
451
+ });
452
+ },
453
+ withoutSortKey: () => {
454
+ const index = {
455
+ name: "custom",
456
+ partitionKey: "pk",
457
+ isReadOnly,
458
+ generateKey: (item) => {
459
+ const data = schema["~standard"].validate(item);
460
+ if ("issues" in data && data.issues) {
461
+ throw new Error(`Index validation failed: ${data.issues.map((i) => i.message).join(", ")}`);
462
+ }
463
+ const validData = "value" in data ? data.value : item;
464
+ return { pk: pkFn(validData) };
465
+ }
466
+ };
467
+ return Object.assign(index, {
468
+ readOnly: (value = true) => ({
469
+ ...index,
470
+ isReadOnly: value
471
+ })
472
+ });
473
+ }
321
474
  }),
322
- withoutSortKey: () => ({
323
- name: "custom",
324
- partitionKey: "pk",
325
- generateKey: (item) => ({ pk: pkFn(item) })
326
- })
327
- })
328
- })
475
+ readOnly: (value = true) => createIndexBuilder(value)
476
+ });
477
+ return createIndexBuilder(false);
478
+ }
329
479
  };
330
480
  }
331
481