dyno-table 2.0.2 → 2.1.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.
Files changed (48) hide show
  1. package/README.md +126 -0
  2. package/dist/builders/condition-check-builder.cjs.map +1 -1
  3. package/dist/builders/condition-check-builder.js.map +1 -1
  4. package/dist/builders/delete-builder.cjs.map +1 -1
  5. package/dist/builders/delete-builder.js.map +1 -1
  6. package/dist/builders/paginator.cjs.map +1 -1
  7. package/dist/builders/paginator.js.map +1 -1
  8. package/dist/builders/put-builder.cjs.map +1 -1
  9. package/dist/builders/put-builder.js.map +1 -1
  10. package/dist/builders/query-builder.cjs +44 -21
  11. package/dist/builders/query-builder.cjs.map +1 -1
  12. package/dist/builders/query-builder.d.cts +1 -1
  13. package/dist/builders/query-builder.d.ts +1 -1
  14. package/dist/builders/query-builder.js +44 -21
  15. package/dist/builders/query-builder.js.map +1 -1
  16. package/dist/builders/transaction-builder.cjs.map +1 -1
  17. package/dist/builders/transaction-builder.js.map +1 -1
  18. package/dist/builders/update-builder.cjs.map +1 -1
  19. package/dist/builders/update-builder.js.map +1 -1
  20. package/dist/conditions.cjs.map +1 -1
  21. package/dist/conditions.js.map +1 -1
  22. package/dist/entity.cjs +196 -47
  23. package/dist/entity.cjs.map +1 -1
  24. package/dist/entity.d.cts +19 -7
  25. package/dist/entity.d.ts +19 -7
  26. package/dist/entity.js +196 -47
  27. package/dist/entity.js.map +1 -1
  28. package/dist/index.cjs +246 -61
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +2 -2
  31. package/dist/index.d.ts +2 -2
  32. package/dist/index.js +246 -61
  33. package/dist/index.js.map +1 -1
  34. package/dist/{query-builder-BNWRCrJW.d.ts → query-builder-CUWdavZw.d.ts} +2 -0
  35. package/dist/{query-builder-DZ9JKgBN.d.cts → query-builder-DoZzZz_c.d.cts} +2 -0
  36. package/dist/{table-BhEeYauU.d.ts → table-4UxlW_wD.d.ts} +2 -1
  37. package/dist/{table-BpNOboD9.d.cts → table-D-xNCVFa.d.cts} +2 -1
  38. package/dist/table.cjs +58 -22
  39. package/dist/table.cjs.map +1 -1
  40. package/dist/table.d.cts +2 -2
  41. package/dist/table.d.ts +2 -2
  42. package/dist/table.js +58 -22
  43. package/dist/table.js.map +1 -1
  44. package/dist/types.d.cts +9 -2
  45. package/dist/types.d.ts +9 -2
  46. package/dist/utils.cjs.map +1 -1
  47. package/dist/utils.js.map +1 -1
  48. 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,140 @@ 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];
45
- if (!gsiConfig) {
46
- throw new Error(`GSI configuration not found for index: ${indexName}`);
47
- }
48
- if (key.pk) {
49
- acc[gsiConfig.partitionKey] = key.pk;
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;
50
97
  }
51
- if (key.sk && gsiConfig.sortKey) {
52
- acc[gsiConfig.sortKey] = key.sk;
98
+ } catch {
99
+ shouldUpdateIndex = true;
100
+ }
101
+ if (!shouldUpdateIndex) {
102
+ continue;
103
+ }
104
+ let key;
105
+ try {
106
+ key = indexDef.generateKey(updatedItem);
107
+ } catch (error) {
108
+ if (error instanceof Error) {
109
+ throw new Error(`Missing attributes: ${error.message}`);
53
110
  }
54
- return acc;
55
- },
56
- {}
57
- );
111
+ throw error;
112
+ }
113
+ if (this.hasUndefinedValues(key)) {
114
+ throw new Error(
115
+ `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.`
116
+ );
117
+ }
118
+ const gsiConfig = this.table.gsis[indexName];
119
+ if (!gsiConfig) {
120
+ throw new Error(`GSI configuration not found for index: ${indexName}`);
121
+ }
122
+ if (key.pk) {
123
+ attributes[gsiConfig.partitionKey] = key.pk;
124
+ }
125
+ if (key.sk && gsiConfig.sortKey) {
126
+ attributes[gsiConfig.sortKey] = key.sk;
127
+ }
128
+ }
129
+ return attributes;
130
+ }
131
+ /**
132
+ * Check if a key has undefined values
133
+ *
134
+ * @param key - The index key to check
135
+ * @returns True if the key contains undefined values, false otherwise
136
+ */
137
+ hasUndefinedValues(key) {
138
+ return (key.pk?.includes("undefined") ?? false) || (key.sk?.includes("undefined") ?? false);
139
+ }
140
+ };
141
+
142
+ // src/entity/index-utils.ts
143
+ function buildIndexes(dataForKeyGeneration, table, indexes, excludeReadOnly = false) {
144
+ if (!indexes) {
145
+ return {};
146
+ }
147
+ const indexBuilder = new IndexBuilder(table, indexes);
148
+ return indexBuilder.buildForCreate(dataForKeyGeneration, { excludeReadOnly });
149
+ }
150
+ function buildIndexUpdates(currentData, updates, table, indexes) {
151
+ if (!indexes) {
152
+ return {};
153
+ }
154
+ const indexBuilder = new IndexBuilder(table, indexes);
155
+ return indexBuilder.buildForUpdate(currentData, updates);
156
+ }
157
+
158
+ // src/entity/entity.ts
159
+ function defineEntity(config) {
160
+ const entityTypeAttributeName = config.settings?.entityTypeAttributeName ?? "entityType";
161
+ const buildIndexes2 = (dataForKeyGeneration, table, excludeReadOnly = false) => {
162
+ return buildIndexes(dataForKeyGeneration, table, config.indexes, excludeReadOnly);
58
163
  };
59
164
  const wrapMethodWithPreparation = (originalMethod, prepareFn, context) => {
60
165
  const wrappedMethod = (...args) => {
@@ -106,7 +211,7 @@ function defineEntity(config) {
106
211
  ...generateTimestamps(["createdAt", "updatedAt"], validatedData.value)
107
212
  };
108
213
  const primaryKey = config.primaryKey.generateKey(dataForKeyGeneration);
109
- const indexes = buildIndexes(dataForKeyGeneration, table);
214
+ const indexes = buildIndexes(dataForKeyGeneration, table, config.indexes, false);
110
215
  const validatedItem = {
111
216
  ...dataForKeyGeneration,
112
217
  [entityTypeAttributeName]: config.name,
@@ -132,7 +237,7 @@ function defineEntity(config) {
132
237
  ...generateTimestamps(["createdAt", "updatedAt"], validationResult.value)
133
238
  };
134
239
  const primaryKey = config.primaryKey.generateKey(dataForKeyGeneration);
135
- const indexes = buildIndexes(dataForKeyGeneration, table);
240
+ const indexes = buildIndexes(dataForKeyGeneration, table, config.indexes, false);
136
241
  const validatedItem = {
137
242
  ...dataForKeyGeneration,
138
243
  [entityTypeAttributeName]: config.name,
@@ -174,7 +279,7 @@ function defineEntity(config) {
174
279
  ...generateTimestamps(["createdAt", "updatedAt"], validatedData.value)
175
280
  };
176
281
  const primaryKey = config.primaryKey.generateKey(dataForKeyGeneration);
177
- const indexes = buildIndexes(dataForKeyGeneration, table);
282
+ const indexes = buildIndexes2(dataForKeyGeneration, table, false);
178
283
  const validatedItem = {
179
284
  [table.partitionKey]: primaryKey.pk,
180
285
  ...table.sortKey ? { [table.sortKey]: primaryKey.sk } : {},
@@ -200,7 +305,7 @@ function defineEntity(config) {
200
305
  ...generateTimestamps(["createdAt", "updatedAt"], validationResult.value)
201
306
  };
202
307
  const primaryKey = config.primaryKey.generateKey(dataForKeyGeneration);
203
- const indexes = buildIndexes(dataForKeyGeneration, table);
308
+ const indexes = buildIndexes(dataForKeyGeneration, table, config.indexes, false);
204
309
  const validatedItem = {
205
310
  [table.partitionKey]: primaryKey.pk,
206
311
  ...table.sortKey ? { [table.sortKey]: primaryKey.sk } : {},
@@ -234,13 +339,21 @@ function defineEntity(config) {
234
339
  }
235
340
  return createEntityAwarePutBuilder(builder, config.name);
236
341
  },
237
- get: (key) => createEntityAwareGetBuilder(table.get(config.primaryKey.generateKey(key)), config.name),
342
+ get: (key) => {
343
+ return createEntityAwareGetBuilder(table.get(config.primaryKey.generateKey(key)), config.name);
344
+ },
238
345
  update: (key, data) => {
239
346
  const primaryKeyObj = config.primaryKey.generateKey(key);
240
347
  const builder = table.update(primaryKeyObj);
241
348
  builder.condition(eq(entityTypeAttributeName, config.name));
242
349
  const timestamps = generateTimestamps(["updatedAt"], data);
243
- builder.set({ ...data, ...timestamps });
350
+ const indexUpdates = buildIndexUpdates(
351
+ { ...key },
352
+ { ...data, ...timestamps },
353
+ table,
354
+ config.indexes
355
+ );
356
+ builder.set({ ...data, ...timestamps, ...indexUpdates });
244
357
  return builder;
245
358
  },
246
359
  delete: (key) => {
@@ -311,21 +424,57 @@ function createQueries() {
311
424
  }
312
425
  function createIndex() {
313
426
  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) })
427
+ input: (schema) => {
428
+ const createIndexBuilder = (isReadOnly = false) => ({
429
+ partitionKey: (pkFn) => ({
430
+ sortKey: (skFn) => {
431
+ const index = {
432
+ name: "custom",
433
+ partitionKey: "pk",
434
+ sortKey: "sk",
435
+ isReadOnly,
436
+ generateKey: (item) => {
437
+ const data = schema["~standard"].validate(item);
438
+ if ("issues" in data && data.issues) {
439
+ throw new Error(`Index validation failed: ${data.issues.map((i) => i.message).join(", ")}`);
440
+ }
441
+ const validData = "value" in data ? data.value : item;
442
+ return { pk: pkFn(validData), sk: skFn(validData) };
443
+ }
444
+ };
445
+ return Object.assign(index, {
446
+ readOnly: (value = false) => ({
447
+ ...index,
448
+ isReadOnly: value
449
+ })
450
+ });
451
+ },
452
+ withoutSortKey: () => {
453
+ const index = {
454
+ name: "custom",
455
+ partitionKey: "pk",
456
+ isReadOnly,
457
+ generateKey: (item) => {
458
+ const data = schema["~standard"].validate(item);
459
+ if ("issues" in data && data.issues) {
460
+ throw new Error(`Index validation failed: ${data.issues.map((i) => i.message).join(", ")}`);
461
+ }
462
+ const validData = "value" in data ? data.value : item;
463
+ return { pk: pkFn(validData) };
464
+ }
465
+ };
466
+ return Object.assign(index, {
467
+ readOnly: (value = true) => ({
468
+ ...index,
469
+ isReadOnly: value
470
+ })
471
+ });
472
+ }
321
473
  }),
322
- withoutSortKey: () => ({
323
- name: "custom",
324
- partitionKey: "pk",
325
- generateKey: (item) => ({ pk: pkFn(item) })
326
- })
327
- })
328
- })
474
+ readOnly: (value = true) => createIndexBuilder(value)
475
+ });
476
+ return createIndexBuilder(false);
477
+ }
329
478
  };
330
479
  }
331
480