@ftschopp/dynatable-core 2.1.0 → 2.2.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.
- package/CHANGELOG.md +7 -0
- package/README.md +1 -0
- package/dist/builders/update/create-update-builder.d.ts +1 -1
- package/dist/builders/update/create-update-builder.d.ts.map +1 -1
- package/dist/builders/update/create-update-builder.js +97 -12
- package/dist/builders/update/types.d.ts +19 -0
- package/dist/builders/update/types.d.ts.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# @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
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **core:** add setIfNotExists to UpdateBuilder for immutable upsert fields ([#54](https://github.com/ftschopp/dynatable/issues/54)) ([a35d208](https://github.com/ftschopp/dynatable/commit/a35d2087f7b28c46c95a6da98dca910b4a8ee97c))
|
|
7
|
+
|
|
1
8
|
# @ftschopp/dynatable-core [2.1.0](https://github.com/ftschopp/dynatable/compare/@ftschopp/dynatable-core@2.0.0...@ftschopp/dynatable-core@2.1.0) (2026-06-01)
|
|
2
9
|
|
|
3
10
|
|
package/README.md
CHANGED
|
@@ -258,6 +258,7 @@ await table.entities.User.put({
|
|
|
258
258
|
// UPDATE - Modify attributes
|
|
259
259
|
await table.entities.User.update({ username: 'alice' })
|
|
260
260
|
.set('name', 'Alice Johnson')
|
|
261
|
+
.setIfNotExists('createdAt', new Date().toISOString()) // write only on first insert
|
|
261
262
|
.add('followerCount', 1)
|
|
262
263
|
.remove('email')
|
|
263
264
|
.returning('ALL_NEW')
|
|
@@ -16,5 +16,5 @@ export declare function createUpdateBuilder<Model>(tableName: string, key: Parti
|
|
|
16
16
|
remove: UpdateAction[];
|
|
17
17
|
add: UpdateAction[];
|
|
18
18
|
delete: UpdateAction[];
|
|
19
|
-
}, returnMode?: 'NONE' | 'ALL_OLD' | 'ALL_NEW' | 'UPDATED_OLD' | 'UPDATED_NEW', valueCounter?: number, enableTimestamps?: boolean, logger?: DynamoDBLogger, indexContext?: IndexContext, setInputs?: Record<string, any>, consumedCapacity?: 'INDEXES' | 'TOTAL' | 'NONE'): UpdateBuilder<Model>;
|
|
19
|
+
}, returnMode?: 'NONE' | 'ALL_OLD' | 'ALL_NEW' | 'UPDATED_OLD' | 'UPDATED_NEW', valueCounter?: number, enableTimestamps?: boolean, logger?: DynamoDBLogger, indexContext?: IndexContext, setInputs?: Record<string, any>, consumedCapacity?: 'INDEXES' | 'TOTAL' | 'NONE', setIfNotExistsInputs?: Record<string, any>): UpdateBuilder<Model>;
|
|
20
20
|
//# sourceMappingURL=create-update-builder.d.ts.map
|
|
@@ -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;AAyB7D;;;;;;;;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,
|
|
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;AAyB7D;;;;;;;;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,CA4jBtB"}
|
|
@@ -34,7 +34,14 @@ function actionAttrName(action) {
|
|
|
34
34
|
* cannot be fully resolved from the primary-key vars plus the updates, building
|
|
35
35
|
* the params throws — the caller must include the missing fields in `.set()`.
|
|
36
36
|
*/
|
|
37
|
-
function createUpdateBuilder(tableName, key, client, prevConditions = [], updateActions = { set: [], remove: [], add: [], delete: [] }, returnMode = 'NONE', valueCounter = 0, enableTimestamps = false, logger, indexContext, setInputs = {}, consumedCapacity
|
|
37
|
+
function createUpdateBuilder(tableName, key, client, prevConditions = [], updateActions = { set: [], remove: [], add: [], delete: [] }, returnMode = 'NONE', valueCounter = 0, enableTimestamps = false, logger, indexContext, setInputs = {}, consumedCapacity,
|
|
38
|
+
// Tracks attributes targeted by `.setIfNotExists()` separately from
|
|
39
|
+
// `setInputs`. Index recomputation reads `setInputs` to resolve template
|
|
40
|
+
// values; conditional writes can't supply a static value (DynamoDB picks
|
|
41
|
+
// current vs `:v` at write time), so they must NOT participate in template
|
|
42
|
+
// resolution — but the PK-template / index-template guards still need to
|
|
43
|
+
// see them to reject schema-incompatible usage.
|
|
44
|
+
setIfNotExistsInputs = {}) {
|
|
38
45
|
const conditions = [...prevConditions];
|
|
39
46
|
const getUniqueValueName = (baseName) => {
|
|
40
47
|
return `${baseName}_${valueCounter++}`;
|
|
@@ -54,7 +61,7 @@ function createUpdateBuilder(tableName, key, client, prevConditions = [], update
|
|
|
54
61
|
});
|
|
55
62
|
const opBuilder = (0, shared_1.createOpBuilder)();
|
|
56
63
|
const condition = fn(attrs, opBuilder);
|
|
57
|
-
return createUpdateBuilder(tableName, key, client, [...conditions, condition], updateActions, returnMode, valueCounter, enableTimestamps, logger, indexContext, setInputs, consumedCapacity);
|
|
64
|
+
return createUpdateBuilder(tableName, key, client, [...conditions, condition], updateActions, returnMode, valueCounter, enableTimestamps, logger, indexContext, setInputs, consumedCapacity, setIfNotExistsInputs);
|
|
58
65
|
},
|
|
59
66
|
set(attrOrUpdates, value) {
|
|
60
67
|
// When no value is provided and the first arg is a plain object,
|
|
@@ -77,7 +84,7 @@ function createUpdateBuilder(tableName, key, client, prevConditions = [], update
|
|
|
77
84
|
});
|
|
78
85
|
newSetInputs[attrName] = val;
|
|
79
86
|
}
|
|
80
|
-
return createUpdateBuilder(tableName, key, client, conditions, { ...updateActions, set: [...updateActions.set, ...newActions] }, returnMode, valueCounter, enableTimestamps, logger, indexContext, newSetInputs, consumedCapacity);
|
|
87
|
+
return createUpdateBuilder(tableName, key, client, conditions, { ...updateActions, set: [...updateActions.set, ...newActions] }, returnMode, valueCounter, enableTimestamps, logger, indexContext, newSetInputs, consumedCapacity, setIfNotExistsInputs);
|
|
81
88
|
}
|
|
82
89
|
// Single update case
|
|
83
90
|
const attrName = normalizeAttr(attrOrUpdates);
|
|
@@ -87,7 +94,38 @@ function createUpdateBuilder(tableName, key, client, prevConditions = [], update
|
|
|
87
94
|
names: { [`#${attrName}`]: attrName },
|
|
88
95
|
values: { [`:${valueName}`]: value },
|
|
89
96
|
};
|
|
90
|
-
return createUpdateBuilder(tableName, key, client, conditions, { ...updateActions, set: [...updateActions.set, action] }, returnMode, valueCounter, enableTimestamps, logger, indexContext, { ...setInputs, [attrName]: value }, consumedCapacity);
|
|
97
|
+
return createUpdateBuilder(tableName, key, client, conditions, { ...updateActions, set: [...updateActions.set, action] }, returnMode, valueCounter, enableTimestamps, logger, indexContext, { ...setInputs, [attrName]: value }, consumedCapacity, setIfNotExistsInputs);
|
|
98
|
+
},
|
|
99
|
+
setIfNotExists(attrOrUpdates, value) {
|
|
100
|
+
// Object form: treat as Partial<Model>. The AttrRef overload always
|
|
101
|
+
// passes a value, so it routes through the single-update path below.
|
|
102
|
+
if (value === undefined &&
|
|
103
|
+
typeof attrOrUpdates === 'object' &&
|
|
104
|
+
attrOrUpdates !== null) {
|
|
105
|
+
const updates = attrOrUpdates;
|
|
106
|
+
const newActions = [];
|
|
107
|
+
const newSetIfNotExistsInputs = { ...setIfNotExistsInputs };
|
|
108
|
+
for (const [attr, val] of Object.entries(updates)) {
|
|
109
|
+
const attrName = attr;
|
|
110
|
+
const valueName = getUniqueValueName(attrName);
|
|
111
|
+
newActions.push({
|
|
112
|
+
expression: `#${attrName} = if_not_exists(#${attrName}, :${valueName})`,
|
|
113
|
+
names: { [`#${attrName}`]: attrName },
|
|
114
|
+
values: { [`:${valueName}`]: val },
|
|
115
|
+
});
|
|
116
|
+
newSetIfNotExistsInputs[attrName] = val;
|
|
117
|
+
}
|
|
118
|
+
return createUpdateBuilder(tableName, key, client, conditions, { ...updateActions, set: [...updateActions.set, ...newActions] }, returnMode, valueCounter, enableTimestamps, logger, indexContext, setInputs, consumedCapacity, newSetIfNotExistsInputs);
|
|
119
|
+
}
|
|
120
|
+
// Single update case
|
|
121
|
+
const attrName = normalizeAttr(attrOrUpdates);
|
|
122
|
+
const valueName = getUniqueValueName(attrName);
|
|
123
|
+
const action = {
|
|
124
|
+
expression: `#${attrName} = if_not_exists(#${attrName}, :${valueName})`,
|
|
125
|
+
names: { [`#${attrName}`]: attrName },
|
|
126
|
+
values: { [`:${valueName}`]: value },
|
|
127
|
+
};
|
|
128
|
+
return createUpdateBuilder(tableName, key, client, conditions, { ...updateActions, set: [...updateActions.set, action] }, returnMode, valueCounter, enableTimestamps, logger, indexContext, setInputs, consumedCapacity, { ...setIfNotExistsInputs, [attrName]: value });
|
|
91
129
|
},
|
|
92
130
|
remove(attr) {
|
|
93
131
|
const attrName = normalizeAttr(attr);
|
|
@@ -95,7 +133,7 @@ function createUpdateBuilder(tableName, key, client, prevConditions = [], update
|
|
|
95
133
|
expression: `#${attrName}`,
|
|
96
134
|
names: { [`#${attrName}`]: attrName },
|
|
97
135
|
};
|
|
98
|
-
return createUpdateBuilder(tableName, key, client, conditions, { ...updateActions, remove: [...updateActions.remove, action] }, returnMode, valueCounter, enableTimestamps, logger, indexContext, setInputs, consumedCapacity);
|
|
136
|
+
return createUpdateBuilder(tableName, key, client, conditions, { ...updateActions, remove: [...updateActions.remove, action] }, returnMode, valueCounter, enableTimestamps, logger, indexContext, setInputs, consumedCapacity, setIfNotExistsInputs);
|
|
99
137
|
},
|
|
100
138
|
add(attr, value) {
|
|
101
139
|
const attrName = normalizeAttr(attr);
|
|
@@ -105,7 +143,7 @@ function createUpdateBuilder(tableName, key, client, prevConditions = [], update
|
|
|
105
143
|
names: { [`#${attrName}`]: attrName },
|
|
106
144
|
values: { [`:${valueName}`]: value },
|
|
107
145
|
};
|
|
108
|
-
return createUpdateBuilder(tableName, key, client, conditions, { ...updateActions, add: [...updateActions.add, action] }, returnMode, valueCounter, enableTimestamps, logger, indexContext, setInputs, consumedCapacity);
|
|
146
|
+
return createUpdateBuilder(tableName, key, client, conditions, { ...updateActions, add: [...updateActions.add, action] }, returnMode, valueCounter, enableTimestamps, logger, indexContext, setInputs, consumedCapacity, setIfNotExistsInputs);
|
|
109
147
|
},
|
|
110
148
|
delete(attr, value) {
|
|
111
149
|
const attrName = normalizeAttr(attr);
|
|
@@ -115,13 +153,13 @@ function createUpdateBuilder(tableName, key, client, prevConditions = [], update
|
|
|
115
153
|
names: { [`#${attrName}`]: attrName },
|
|
116
154
|
values: { [`:${valueName}`]: value },
|
|
117
155
|
};
|
|
118
|
-
return createUpdateBuilder(tableName, key, client, conditions, { ...updateActions, delete: [...updateActions.delete, action] }, returnMode, valueCounter, enableTimestamps, logger, indexContext, setInputs, consumedCapacity);
|
|
156
|
+
return createUpdateBuilder(tableName, key, client, conditions, { ...updateActions, delete: [...updateActions.delete, action] }, returnMode, valueCounter, enableTimestamps, logger, indexContext, setInputs, consumedCapacity, setIfNotExistsInputs);
|
|
119
157
|
},
|
|
120
158
|
returning(mode) {
|
|
121
|
-
return createUpdateBuilder(tableName, key, client, conditions, updateActions, mode, valueCounter, enableTimestamps, logger, indexContext, setInputs, consumedCapacity);
|
|
159
|
+
return createUpdateBuilder(tableName, key, client, conditions, updateActions, mode, valueCounter, enableTimestamps, logger, indexContext, setInputs, consumedCapacity, setIfNotExistsInputs);
|
|
122
160
|
},
|
|
123
161
|
returnConsumedCapacity(mode) {
|
|
124
|
-
return createUpdateBuilder(tableName, key, client, conditions, updateActions, returnMode, valueCounter, enableTimestamps, logger, indexContext, setInputs, mode);
|
|
162
|
+
return createUpdateBuilder(tableName, key, client, conditions, updateActions, returnMode, valueCounter, enableTimestamps, logger, indexContext, setInputs, mode, setIfNotExistsInputs);
|
|
125
163
|
},
|
|
126
164
|
dbParams() {
|
|
127
165
|
// Clone updateActions to avoid mutation
|
|
@@ -136,6 +174,7 @@ function createUpdateBuilder(tableName, key, client, prevConditions = [], update
|
|
|
136
174
|
// structured) — for remove/add/delete we read from the action's
|
|
137
175
|
// attribute-name map.
|
|
138
176
|
const setFields = Object.keys(setInputs);
|
|
177
|
+
const setIfNotExistsFields = Object.keys(setIfNotExistsInputs);
|
|
139
178
|
const removeFields = updateActions.remove
|
|
140
179
|
.map(actionAttrName)
|
|
141
180
|
.filter((n) => !!n);
|
|
@@ -150,15 +189,21 @@ function createUpdateBuilder(tableName, key, client, prevConditions = [], update
|
|
|
150
189
|
// the user changes a field that participates in the PK/SK template,
|
|
151
190
|
// the row's PK doesn't move (DynamoDB doesn't allow that) but the
|
|
152
191
|
// attribute does — leaving an inconsistent row whose PK encodes the
|
|
153
|
-
// old value. Catch this on every op (.set / .
|
|
154
|
-
// before it leaves the process.
|
|
192
|
+
// old value. Catch this on every op (.set / .setIfNotExists /
|
|
193
|
+
// .remove / .add / .delete) before it leaves the process.
|
|
155
194
|
const primaryKeyTemplateVars = new Set();
|
|
156
195
|
for (const keyDef of Object.values(indexContext.model.key)) {
|
|
157
196
|
for (const v of (0, model_utils_1.extractTemplateVars)(keyDef.value)) {
|
|
158
197
|
primaryKeyTemplateVars.add(v);
|
|
159
198
|
}
|
|
160
199
|
}
|
|
161
|
-
const allTouched = [
|
|
200
|
+
const allTouched = [
|
|
201
|
+
...setFields,
|
|
202
|
+
...setIfNotExistsFields,
|
|
203
|
+
...removeFields,
|
|
204
|
+
...addFields,
|
|
205
|
+
...deleteFields,
|
|
206
|
+
];
|
|
162
207
|
const pkConflicts = [
|
|
163
208
|
...new Set(allTouched.filter((f) => primaryKeyTemplateVars.has(f))),
|
|
164
209
|
];
|
|
@@ -174,6 +219,13 @@ function createUpdateBuilder(tableName, key, client, prevConditions = [], update
|
|
|
174
219
|
// the new value, REMOVE strips the field entirely, and DELETE
|
|
175
220
|
// mutates a Set without naming a scalar. Switching to .set(field,
|
|
176
221
|
// newValue) lets the recompute path handle it correctly.
|
|
222
|
+
//
|
|
223
|
+
// Guard 3: .setIfNotExists() against a field used in a SECONDARY-index
|
|
224
|
+
// template. The resolved value is decided by DynamoDB at write time
|
|
225
|
+
// (current attr vs `:v`), so the recompute path cannot statically
|
|
226
|
+
// produce a value that's guaranteed consistent with the stored
|
|
227
|
+
// attribute. Allowing it would silently corrupt the index whenever
|
|
228
|
+
// the conditional write keeps the existing value.
|
|
177
229
|
if (indexContext.model.index) {
|
|
178
230
|
const indexTemplateVars = new Set();
|
|
179
231
|
for (const indexDef of Object.values(indexContext.model.index)) {
|
|
@@ -192,6 +244,18 @@ function createUpdateBuilder(tableName, key, client, prevConditions = [], update
|
|
|
192
244
|
`recomputed without an explicit new value. Use .set(field, ` +
|
|
193
245
|
`newValue) instead so the index key is recomputed atomically.`);
|
|
194
246
|
}
|
|
247
|
+
const ifNotExistsGsiConflicts = [
|
|
248
|
+
...new Set(setIfNotExistsFields.filter((f) => indexTemplateVars.has(f))),
|
|
249
|
+
];
|
|
250
|
+
if (ifNotExistsGsiConflicts.length > 0) {
|
|
251
|
+
throw new Error(`Cannot use .setIfNotExists() on field(s) ` +
|
|
252
|
+
`[${ifNotExistsGsiConflicts.join(', ')}] — they participate in a ` +
|
|
253
|
+
`secondary-index template, and if_not_exists() may keep the ` +
|
|
254
|
+
`existing value at write time, which would leave the index key ` +
|
|
255
|
+
`inconsistent with the stored attribute. Either restructure the ` +
|
|
256
|
+
`schema so this field is not part of any GSI template, or perform ` +
|
|
257
|
+
`a get + conditional .set() in two steps.`);
|
|
258
|
+
}
|
|
195
259
|
}
|
|
196
260
|
const { actions: idxActions, missing } = (0, model_utils_1.computeIndexUpdates)(indexContext.model, indexContext.keyVars, setInputs);
|
|
197
261
|
if (missing.length > 0) {
|
|
@@ -237,6 +301,27 @@ function createUpdateBuilder(tableName, key, client, prevConditions = [], update
|
|
|
237
301
|
};
|
|
238
302
|
actionsToProcess.set = [...actionsToProcess.set, timestampAction];
|
|
239
303
|
}
|
|
304
|
+
// Dedup guard: DynamoDB rejects overlapping document paths inside a
|
|
305
|
+
// SET expression (e.g. `SET #foo = :a, #foo = :b` → ValidationException
|
|
306
|
+
// "Two document paths overlap"). Catch the common footguns —
|
|
307
|
+
// .set().set() on the same key, .set() + .setIfNotExists() on the
|
|
308
|
+
// same key, and enableTimestamps + .set/.setIfNotExists('updatedAt')
|
|
309
|
+
// — before the network round-trip. Runs AFTER the timestamp injection
|
|
310
|
+
// so #updatedAt collisions are also reported.
|
|
311
|
+
const setActionNames = actionsToProcess.set
|
|
312
|
+
.map(actionAttrName)
|
|
313
|
+
.filter((n) => !!n);
|
|
314
|
+
const duplicateSetAttrs = [
|
|
315
|
+
...new Set(setActionNames.filter((n, i) => setActionNames.indexOf(n) !== i)),
|
|
316
|
+
];
|
|
317
|
+
if (duplicateSetAttrs.length > 0) {
|
|
318
|
+
throw new Error(`Update would emit multiple SET actions targeting the same ` +
|
|
319
|
+
`attribute(s) [${duplicateSetAttrs.join(', ')}]. DynamoDB rejects ` +
|
|
320
|
+
`overlapping document paths. Check that you're not combining ` +
|
|
321
|
+
`.set() and .setIfNotExists() on the same field, calling .set() ` +
|
|
322
|
+
`twice for the same key, or targeting an attribute that ` +
|
|
323
|
+
`enableTimestamps already manages (updatedAt).`);
|
|
324
|
+
}
|
|
240
325
|
const buildSection = (label, actions, includeValues) => actions.length === 0
|
|
241
326
|
? null
|
|
242
327
|
: {
|
|
@@ -40,6 +40,25 @@ export interface UpdateBuilder<Model> extends Omit<OperationBuilder<Model>, 'dbP
|
|
|
40
40
|
*/
|
|
41
41
|
set(attr: keyof Model | AttrRef, value: any): UpdateBuilder<Model>;
|
|
42
42
|
set(updates: Partial<Model>): UpdateBuilder<Model>;
|
|
43
|
+
/**
|
|
44
|
+
* Conditionally sets an attribute only if it does not already exist on the
|
|
45
|
+
* item, using DynamoDB's `if_not_exists()` SET function. Emits
|
|
46
|
+
* `#attr = if_not_exists(#attr, :v)`.
|
|
47
|
+
*
|
|
48
|
+
* Primary use case: upsert patterns where some fields must be written only
|
|
49
|
+
* on first insert (immutable `createdAt`, `firstSeenAt`, initial counters).
|
|
50
|
+
*
|
|
51
|
+
* Restrictions enforced at `dbParams()` time:
|
|
52
|
+
* - The field must not participate in the primary-key template (PK is
|
|
53
|
+
* immutable; the conditional write is meaningless).
|
|
54
|
+
* - The field must not participate in any secondary-index template,
|
|
55
|
+
* because the resolved value is decided by DynamoDB at write time and
|
|
56
|
+
* the index key cannot be recomputed safely.
|
|
57
|
+
* - The same attribute cannot also be targeted by `.set()` in the same
|
|
58
|
+
* update — DynamoDB rejects overlapping document paths.
|
|
59
|
+
*/
|
|
60
|
+
setIfNotExists(attr: keyof Model | AttrRef, value: any): UpdateBuilder<Model>;
|
|
61
|
+
setIfNotExists(updates: Partial<Model>): UpdateBuilder<Model>;
|
|
43
62
|
/**
|
|
44
63
|
* Removes an attribute from the item
|
|
45
64
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/builders/update/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEzF;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC9B,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,eAAe,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC9B,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,KAAK,CAAE,SAAQ,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC;IAC/F;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,SAAS,KAAK,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAExF;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,EAAE,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IACnE,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAEnD;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,OAAO,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAE1D;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,EAAE,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAEnE;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,EAAE,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAEtE;;OAEG;IACH,SAAS,CACP,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,GAAG,aAAa,GACnE,aAAa,CAAC,KAAK,CAAC,CAAC;IAExB;;;;;;;;OAQG;IACH,sBAAsB,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAEjF;;OAEG;IACH,QAAQ,IAAI,kBAAkB,CAAC;CAChC"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/builders/update/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEzF;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC9B,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,eAAe,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC9B,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,KAAK,CAAE,SAAQ,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC;IAC/F;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,SAAS,KAAK,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAExF;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,EAAE,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IACnE,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAEnD;;;;;;;;;;;;;;;;OAgBG;IACH,cAAc,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,EAAE,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAC9E,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAE9D;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,OAAO,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAE1D;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,EAAE,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAEnE;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,EAAE,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAEtE;;OAEG;IACH,SAAS,CACP,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,GAAG,aAAa,GACnE,aAAa,CAAC,KAAK,CAAC,CAAC;IAExB;;;;;;;;OAQG;IACH,sBAAsB,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAEjF;;OAEG;IACH,QAAQ,IAAI,kBAAkB,CAAC;CAChC"}
|