@ftschopp/dynatable-core 2.2.0 → 2.2.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.
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## @ftschopp/dynatable-core [2.2.1](https://github.com/ftschopp/dynatable/compare/@ftschopp/dynatable-core@2.2.0...@ftschopp/dynatable-core@2.2.1) (2026-06-03)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **core:** reject undefined in update actions with actionable error ([#56](https://github.com/ftschopp/dynatable/issues/56)) ([5b8bee9](https://github.com/ftschopp/dynatable/commit/5b8bee9e9c041abdbff1b2b97d1eaba16730f257)), closes [#55](https://github.com/ftschopp/dynatable/issues/55)
|
|
7
|
+
|
|
1
8
|
# @ftschopp/dynatable-core [2.2.0](https://github.com/ftschopp/dynatable/compare/@ftschopp/dynatable-core@2.1.0...@ftschopp/dynatable-core@2.2.0) (2026-06-02)
|
|
2
9
|
|
|
3
10
|
|
package/README.md
CHANGED
|
@@ -265,6 +265,13 @@ await table.entities.User.update({ username: 'alice' })
|
|
|
265
265
|
.where((attr, op) => op.gt(attr.followerCount, 0))
|
|
266
266
|
.execute();
|
|
267
267
|
|
|
268
|
+
// Note: `.set()`, `.setIfNotExists()`, `.add()` and `.delete()` reject
|
|
269
|
+
// `undefined` values up front. DynamoDB can't encode `undefined` in
|
|
270
|
+
// `ExpressionAttributeValues`, so the builder throws with the offending
|
|
271
|
+
// keys instead of letting the request fail server-side. Use `.remove(attr)`
|
|
272
|
+
// to clear an attribute, or filter `undefined` out of your payload before
|
|
273
|
+
// calling `.set()`. `null` is allowed — it writes the DynamoDB `NULL` type.
|
|
274
|
+
|
|
268
275
|
// DELETE - Remove item
|
|
269
276
|
await table.entities.User.delete({ username: 'alice' })
|
|
270
277
|
.returning('ALL_OLD')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-update-builder.d.ts","sourceRoot":"","sources":["../../../src/builders/update/create-update-builder.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,EAAgC,SAAS,EAA4B,MAAM,WAAW,CAAC;AAC9F,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"create-update-builder.d.ts","sourceRoot":"","sources":["../../../src/builders/update/create-update-builder.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,EAAgC,SAAS,EAA4B,MAAM,WAAW,CAAC;AAC9F,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAkE7D;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EACvC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,OAAO,CAAC,KAAK,CAAC,EACnB,MAAM,EAAE,cAAc,EACtB,cAAc,GAAE,SAAS,EAAO,EAChC,aAAa,GAAE;IACb,GAAG,EAAE,YAAY,EAAE,CAAC;IACpB,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,GAAG,EAAE,YAAY,EAAE,CAAC;IACpB,MAAM,EAAE,YAAY,EAAE,CAAC;CACuB,EAChD,UAAU,GAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,GAAG,aAAsB,EACnF,YAAY,SAAI,EAChB,gBAAgB,UAAQ,EACxB,MAAM,CAAC,EAAE,cAAc,EACvB,YAAY,CAAC,EAAE,YAAY,EAC3B,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,EACnC,gBAAgB,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,EAO/C,oBAAoB,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,GAC7C,aAAa,CAAC,KAAK,CAAC,CAkkBtB"}
|
|
@@ -25,6 +25,37 @@ function actionAttrName(action) {
|
|
|
25
25
|
const values = Object.values(action.names);
|
|
26
26
|
return values[0];
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Reject `undefined` before it reaches DynamoDB. SET / ADD / DELETE /
|
|
30
|
+
* setIfNotExists all encode the value into `ExpressionAttributeValues` — an
|
|
31
|
+
* `undefined` there is either silently dropped by the marshaller (when
|
|
32
|
+
* `removeUndefinedValues` is on, which then fails server-side with an unused
|
|
33
|
+
* expression value error) or rejected outright. Catching it at the call site
|
|
34
|
+
* gives a useful error and a pointer to the right primitive.
|
|
35
|
+
*
|
|
36
|
+
* `null` is intentionally allowed — DynamoDB has a NULL attribute type and
|
|
37
|
+
* the caller may legitimately want to write it.
|
|
38
|
+
*/
|
|
39
|
+
function assertNoUndefined(method, payload) {
|
|
40
|
+
const undefinedKeys = 'attr' in payload
|
|
41
|
+
? payload.value === undefined
|
|
42
|
+
? [payload.attr]
|
|
43
|
+
: []
|
|
44
|
+
: Object.entries(payload)
|
|
45
|
+
.filter(([, v]) => v === undefined)
|
|
46
|
+
.map(([k]) => k);
|
|
47
|
+
if (undefinedKeys.length === 0)
|
|
48
|
+
return;
|
|
49
|
+
const guidance = method === 'set'
|
|
50
|
+
? `Use .remove(attr) to clear an attribute, or filter undefined out ` +
|
|
51
|
+
`before calling .set().`
|
|
52
|
+
: method === 'setIfNotExists'
|
|
53
|
+
? `setIfNotExists requires a concrete value; omit the key or filter ` +
|
|
54
|
+
`it out.`
|
|
55
|
+
: `.${method}() requires a concrete value for each attribute.`;
|
|
56
|
+
throw new Error(`.${method}() received undefined for key(s) [${undefinedKeys.join(', ')}]. ` +
|
|
57
|
+
`DynamoDB cannot encode undefined in ExpressionAttributeValues. ${guidance}`);
|
|
58
|
+
}
|
|
28
59
|
/**
|
|
29
60
|
* Creates an UpdateBuilder for an item key and table.
|
|
30
61
|
*
|
|
@@ -72,6 +103,7 @@ setIfNotExistsInputs = {}) {
|
|
|
72
103
|
attrOrUpdates !== null) {
|
|
73
104
|
// Multiple updates case
|
|
74
105
|
const updates = attrOrUpdates;
|
|
106
|
+
assertNoUndefined('set', updates);
|
|
75
107
|
const newActions = [];
|
|
76
108
|
const newSetInputs = { ...setInputs };
|
|
77
109
|
for (const [attr, val] of Object.entries(updates)) {
|
|
@@ -88,6 +120,7 @@ setIfNotExistsInputs = {}) {
|
|
|
88
120
|
}
|
|
89
121
|
// Single update case
|
|
90
122
|
const attrName = normalizeAttr(attrOrUpdates);
|
|
123
|
+
assertNoUndefined('set', { attr: attrName, value });
|
|
91
124
|
const valueName = getUniqueValueName(attrName);
|
|
92
125
|
const action = {
|
|
93
126
|
expression: `#${attrName} = :${valueName}`,
|
|
@@ -103,6 +136,7 @@ setIfNotExistsInputs = {}) {
|
|
|
103
136
|
typeof attrOrUpdates === 'object' &&
|
|
104
137
|
attrOrUpdates !== null) {
|
|
105
138
|
const updates = attrOrUpdates;
|
|
139
|
+
assertNoUndefined('setIfNotExists', updates);
|
|
106
140
|
const newActions = [];
|
|
107
141
|
const newSetIfNotExistsInputs = { ...setIfNotExistsInputs };
|
|
108
142
|
for (const [attr, val] of Object.entries(updates)) {
|
|
@@ -119,6 +153,7 @@ setIfNotExistsInputs = {}) {
|
|
|
119
153
|
}
|
|
120
154
|
// Single update case
|
|
121
155
|
const attrName = normalizeAttr(attrOrUpdates);
|
|
156
|
+
assertNoUndefined('setIfNotExists', { attr: attrName, value });
|
|
122
157
|
const valueName = getUniqueValueName(attrName);
|
|
123
158
|
const action = {
|
|
124
159
|
expression: `#${attrName} = if_not_exists(#${attrName}, :${valueName})`,
|
|
@@ -137,6 +172,7 @@ setIfNotExistsInputs = {}) {
|
|
|
137
172
|
},
|
|
138
173
|
add(attr, value) {
|
|
139
174
|
const attrName = normalizeAttr(attr);
|
|
175
|
+
assertNoUndefined('add', { attr: attrName, value });
|
|
140
176
|
const valueName = getUniqueValueName(attrName);
|
|
141
177
|
const action = {
|
|
142
178
|
expression: `#${attrName} :${valueName}`,
|
|
@@ -147,6 +183,7 @@ setIfNotExistsInputs = {}) {
|
|
|
147
183
|
},
|
|
148
184
|
delete(attr, value) {
|
|
149
185
|
const attrName = normalizeAttr(attr);
|
|
186
|
+
assertNoUndefined('delete', { attr: attrName, value });
|
|
150
187
|
const valueName = getUniqueValueName(attrName);
|
|
151
188
|
const action = {
|
|
152
189
|
expression: `#${attrName} :${valueName}`,
|