electrodb 2.9.3 → 2.10.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/src/clauses.js CHANGED
@@ -1,1108 +1,1557 @@
1
- const { QueryTypes, MethodTypes, ItemOperations, ExpressionTypes, TransactionCommitSymbol, TransactionOperations, TerminalOperation, KeyTypes, IndexTypes } = require("./types");
2
- const {AttributeOperationProxy, UpdateOperations, FilterOperationNames, UpdateOperationNames} = require("./operations");
3
- const {UpdateExpression} = require("./update");
4
- const {FilterExpression} = require("./where");
1
+ const {
2
+ QueryTypes,
3
+ MethodTypes,
4
+ ItemOperations,
5
+ ExpressionTypes,
6
+ TransactionCommitSymbol,
7
+ TransactionOperations,
8
+ TerminalOperation,
9
+ KeyTypes,
10
+ IndexTypes,
11
+ UpsertOperations,
12
+ } = require("./types");
13
+ const {
14
+ AttributeOperationProxy,
15
+ UpdateOperations,
16
+ FilterOperationNames,
17
+ UpdateOperationNames,
18
+ } = require("./operations");
19
+ const { UpdateExpression } = require("./update");
20
+ const { FilterExpression } = require("./where");
5
21
  const v = require("./validations");
6
22
  const e = require("./errors");
7
23
  const u = require("./util");
8
24
 
25
+ const methodChildren = {
26
+ upsert: [
27
+ "upsertSet",
28
+ "upsertAppend",
29
+ "upsertAdd",
30
+ "go",
31
+ "params",
32
+ "upsertSubtract",
33
+ "commit",
34
+ "upsertIfNotExists",
35
+ "where",
36
+ ],
37
+ update: [
38
+ "data",
39
+ "set",
40
+ "append",
41
+ "add",
42
+ "updateRemove",
43
+ "updateDelete",
44
+ "go",
45
+ "params",
46
+ "subtract",
47
+ "commit",
48
+ "composite",
49
+ "ifNotExists",
50
+ "where",
51
+ ],
52
+ put: ["where", "params", "go", "commit"],
53
+ del: ["where", "params", "go", "commit"],
54
+ };
55
+
9
56
  function batchAction(action, type, entity, state, payload) {
10
- if (state.getError() !== null) {
11
- return state;
12
- }
13
- try {
14
- state.setMethod(type);
15
- for (let facets of payload) {
16
- let batchState = action(entity, state.createSubState(), facets);
17
- if (batchState.getError() !== null) {
18
- throw batchState.getError();
19
- }
20
- }
21
- return state;
22
- } catch(err) {
23
- state.setError(err);
24
- return state;
25
- }
57
+ if (state.getError() !== null) {
58
+ return state;
59
+ }
60
+ try {
61
+ state.setMethod(type);
62
+ for (let facets of payload) {
63
+ let batchState = action(entity, state.createSubState(), facets);
64
+ if (batchState.getError() !== null) {
65
+ throw batchState.getError();
66
+ }
67
+ }
68
+ return state;
69
+ } catch (err) {
70
+ state.setError(err);
71
+ return state;
72
+ }
26
73
  }
27
74
 
28
75
  let clauses = {
29
- index: {
30
- name: "index",
31
- children: ["check", "get", "delete", "update", "query", "upsert", "put", "scan", "collection", "clusteredCollection", "create", "remove", "patch", "batchPut", "batchDelete", "batchGet"],
32
- },
33
- clusteredCollection: {
34
- name: "clusteredCollection",
35
- action(entity, state, collection = "", facets /* istanbul ignore next */ = {}) {
36
- if (state.getError() !== null) {
37
- return state;
38
- }
39
- try {
40
- const { pk, sk } = state.getCompositeAttributes();
41
- return state
42
- .setType(QueryTypes.clustered_collection)
43
- .setMethod(MethodTypes.query)
44
- .setCollection(collection)
45
- .setPK(entity._expectFacets(facets, pk))
46
- .ifSK(() => {
47
- const {composites, unused} = state.identifyCompositeAttributes(facets, sk, pk);
48
- state.setSK(composites);
49
- // we must apply eq on filter on all provided because if the user then does a sort key operation, it'd actually then unexpect results
50
- if (sk.length > 1) {
51
- state.filterProperties(FilterOperationNames.eq, {...unused, ...composites});
52
- }
53
- })
54
- .whenOptions(({ options, state }) => {
55
- if (!options.ignoreOwnership && !state.getParams()) {
56
- state.query.options.expressions.names = {
57
- ...state.query.options.expressions.names,
58
- ...state.query.options.identifiers.names,
59
- };
60
- state.query.options.expressions.values = {
61
- ...state.query.options.expressions.values,
62
- ...state.query.options.identifiers.values,
63
- };
64
- state.query.options.expressions.expression =
65
- state.query.options.expressions.expression.length > 1
66
- ? `(${state.query.options.expressions.expression}) AND ${state.query.options.identifiers.expression}`
67
- : `${state.query.options.identifiers.expression}`;
68
- }
69
- });
76
+ index: {
77
+ name: "index",
78
+ children: [
79
+ "check",
80
+ "get",
81
+ "delete",
82
+ "update",
83
+ "query",
84
+ "upsert",
85
+ "put",
86
+ "scan",
87
+ "collection",
88
+ "clusteredCollection",
89
+ "create",
90
+ "remove",
91
+ "patch",
92
+ "batchPut",
93
+ "batchDelete",
94
+ "batchGet",
95
+ ],
96
+ },
97
+ clusteredCollection: {
98
+ name: "clusteredCollection",
99
+ action(
100
+ entity,
101
+ state,
102
+ collection = "",
103
+ facets /* istanbul ignore next */ = {},
104
+ ) {
105
+ if (state.getError() !== null) {
106
+ return state;
107
+ }
108
+ try {
109
+ const { pk, sk } = state.getCompositeAttributes();
110
+ return state
111
+ .setType(QueryTypes.clustered_collection)
112
+ .setMethod(MethodTypes.query)
113
+ .setCollection(collection)
114
+ .setPK(entity._expectFacets(facets, pk))
115
+ .ifSK(() => {
116
+ const { composites, unused } = state.identifyCompositeAttributes(
117
+ facets,
118
+ sk,
119
+ pk,
120
+ );
121
+ state.setSK(composites);
122
+ // we must apply eq on filter on all provided because if the user then does a sort key operation, it'd actually then unexpect results
123
+ if (sk.length > 1) {
124
+ state.filterProperties(FilterOperationNames.eq, {
125
+ ...unused,
126
+ ...composites,
127
+ });
128
+ }
129
+ })
130
+ .whenOptions(({ options, state }) => {
131
+ if (!options.ignoreOwnership && !state.getParams()) {
132
+ state.query.options.expressions.names = {
133
+ ...state.query.options.expressions.names,
134
+ ...state.query.options.identifiers.names,
135
+ };
136
+ state.query.options.expressions.values = {
137
+ ...state.query.options.expressions.values,
138
+ ...state.query.options.identifiers.values,
139
+ };
140
+ state.query.options.expressions.expression =
141
+ state.query.options.expressions.expression.length > 1
142
+ ? `(${state.query.options.expressions.expression}) AND ${state.query.options.identifiers.expression}`
143
+ : `${state.query.options.identifiers.expression}`;
144
+ }
145
+ });
146
+ } catch (err) {
147
+ state.setError(err);
148
+ return state;
149
+ }
150
+ },
151
+ children: ["between", "gte", "gt", "lte", "lt", "begins", "params", "go"],
152
+ },
153
+ collection: {
154
+ name: "collection",
155
+ /* istanbul ignore next */
156
+ action(
157
+ entity,
158
+ state,
159
+ collection = "",
160
+ facets /* istanbul ignore next */ = {},
161
+ ) {
162
+ if (state.getError() !== null) {
163
+ return state;
164
+ }
165
+ try {
166
+ const { pk, sk } = state.getCompositeAttributes();
167
+ return state
168
+ .setType(QueryTypes.collection)
169
+ .setMethod(MethodTypes.query)
170
+ .setCollection(collection)
171
+ .setPK(entity._expectFacets(facets, pk))
172
+ .whenOptions(({ options, state }) => {
173
+ if (!options.ignoreOwnership && !state.getParams()) {
174
+ state.query.options.expressions.names = {
175
+ ...state.query.options.expressions.names,
176
+ ...state.query.options.identifiers.names,
177
+ };
178
+ state.query.options.expressions.values = {
179
+ ...state.query.options.expressions.values,
180
+ ...state.query.options.identifiers.values,
181
+ };
182
+ state.query.options.expressions.expression =
183
+ state.query.options.expressions.expression.length > 1
184
+ ? `(${state.query.options.expressions.expression}) AND ${state.query.options.identifiers.expression}`
185
+ : `${state.query.options.identifiers.expression}`;
186
+ }
187
+ });
188
+ } catch (err) {
189
+ state.setError(err);
190
+ return state;
191
+ }
192
+ },
193
+ children: ["params", "go"],
194
+ },
195
+ scan: {
196
+ name: "scan",
197
+ action(entity, state, config) {
198
+ if (state.getError() !== null) {
199
+ return state;
200
+ }
201
+ try {
202
+ return state
203
+ .setMethod(MethodTypes.scan)
204
+ .whenOptions(({ state, options }) => {
205
+ if (!options.ignoreOwnership && !state.getParams()) {
206
+ state.unsafeApplyFilter(
207
+ FilterOperationNames.eq,
208
+ entity.identifiers.entity,
209
+ entity.getName(),
210
+ );
211
+ state.unsafeApplyFilter(
212
+ FilterOperationNames.eq,
213
+ entity.identifiers.version,
214
+ entity.getVersion(),
215
+ );
216
+ }
217
+ });
218
+ } catch (err) {
219
+ state.setError(err);
220
+ return state;
221
+ }
222
+ },
223
+ children: ["params", "go"],
224
+ },
225
+ get: {
226
+ name: "get",
227
+ /* istanbul ignore next */
228
+ action(entity, state, facets = {}) {
229
+ if (state.getError() !== null) {
230
+ return state;
231
+ }
232
+ try {
233
+ const { pk, sk } = state.getCompositeAttributes();
234
+ const { composites } = state.identifyCompositeAttributes(
235
+ facets,
236
+ sk,
237
+ pk,
238
+ );
239
+ return state
240
+ .setMethod(MethodTypes.get)
241
+ .setType(QueryTypes.eq)
242
+ .setPK(entity._expectFacets(facets, pk))
243
+ .ifSK(() => {
244
+ entity._expectFacets(facets, sk);
245
+ state.setSK(composites);
246
+ });
247
+ } catch (err) {
248
+ state.setError(err);
249
+ return state;
250
+ }
251
+ },
252
+ children: ["params", "go", "commit"],
253
+ },
254
+ check: {
255
+ name: "check",
256
+ action(...params) {
257
+ return clauses.get.action(...params).setMethod(MethodTypes.check);
258
+ },
259
+ children: ["commit"],
260
+ },
261
+ batchGet: {
262
+ name: "batchGet",
263
+ action: (entity, state, payload) =>
264
+ batchAction(
265
+ clauses.get.action,
266
+ MethodTypes.batchGet,
267
+ entity,
268
+ state,
269
+ payload,
270
+ ),
271
+ children: ["params", "go"],
272
+ },
273
+ batchDelete: {
274
+ name: "batchDelete",
275
+ action: (entity, state, payload) =>
276
+ batchAction(
277
+ clauses.delete.action,
278
+ MethodTypes.batchWrite,
279
+ entity,
280
+ state,
281
+ payload,
282
+ ),
283
+ children: ["params", "go"],
284
+ },
285
+ delete: {
286
+ name: "delete",
287
+ /* istanbul ignore next */
288
+ action(entity, state, facets = {}) {
289
+ if (state.getError() !== null) {
290
+ return state;
291
+ }
292
+ try {
293
+ const { pk, sk } = state.getCompositeAttributes();
294
+ const pkComposite = entity._expectFacets(facets, pk);
295
+ state.addOption("_includeOnResponseItem", pkComposite);
296
+ return state
297
+ .setMethod(MethodTypes.delete)
298
+ .setType(QueryTypes.eq)
299
+ .setPK(pkComposite)
300
+ .ifSK(() => {
301
+ entity._expectFacets(facets, sk);
302
+ const skComposite = state.buildQueryComposites(facets, sk);
303
+ state.setSK(skComposite);
304
+ state.addOption("_includeOnResponseItem", {
305
+ ...skComposite,
306
+ ...pkComposite,
307
+ });
308
+ });
309
+ } catch (err) {
310
+ state.setError(err);
311
+ return state;
312
+ }
313
+ },
314
+ children: ["where", "params", "go", "commit"],
315
+ },
316
+ remove: {
317
+ name: "remove",
318
+ /* istanbul ignore next */
319
+ action(entity, state, facets = {}) {
320
+ if (state.getError() !== null) {
321
+ return state;
322
+ }
323
+ try {
324
+ const attributes = state.getCompositeAttributes();
325
+ const filter = state.query.filter[ExpressionTypes.ConditionExpression];
326
+ const { pk, sk } = entity._getPrimaryIndexFieldNames();
327
+ filter.unsafeSet(FilterOperationNames.exists, pk);
328
+ if (sk) {
329
+ filter.unsafeSet(FilterOperationNames.exists, sk);
330
+ }
331
+ const pkComposite = entity._expectFacets(facets, attributes.pk);
332
+ state.addOption("_includeOnResponseItem", pkComposite);
333
+ return state
334
+ .setMethod(MethodTypes.delete)
335
+ .setType(QueryTypes.eq)
336
+ .setPK(pkComposite)
337
+ .ifSK(() => {
338
+ entity._expectFacets(facets, attributes.sk);
339
+ const skComposite = state.buildQueryComposites(
340
+ facets,
341
+ attributes.sk,
342
+ );
343
+ state.setSK(skComposite);
344
+ state.addOption("_includeOnResponseItem", {
345
+ ...skComposite,
346
+ ...pkComposite,
347
+ });
348
+ });
349
+ } catch (err) {
350
+ state.setError(err);
351
+ return state;
352
+ }
353
+ },
354
+ children: methodChildren.del,
355
+ },
356
+ upsert: {
357
+ name: "upsert",
358
+ action(entity, state, payload = {}) {
359
+ if (state.getError() !== null) {
360
+ return state;
361
+ }
362
+ try {
363
+ return state
364
+ .setMethod(MethodTypes.upsert)
365
+ .setType(QueryTypes.eq)
366
+ .applyUpsert(UpsertOperations.set, payload)
367
+ .beforeBuildParams(({ state }) => {
368
+ const { upsert, update, updateProxy } = state.query;
369
+
370
+ state.query.update.set(entity.identifiers.entity, entity.getName());
371
+ state.query.update.set(
372
+ entity.identifiers.version,
373
+ entity.getVersion(),
374
+ );
375
+
376
+ // only "set" data is used to make keys
377
+ const setData = {};
378
+ const nonSetData = {};
379
+ let allData = {};
380
+
381
+ for (const name in upsert.data) {
382
+ const { operation, value } = upsert.data[name];
383
+ allData[name] = value;
384
+ if (operation === UpsertOperations.set) {
385
+ setData[name] = value;
386
+ } else {
387
+ nonSetData[name] = value;
388
+ }
389
+ }
390
+
391
+ const upsertData = entity.model.schema.checkCreate({ ...allData });
392
+ const attributes = state.getCompositeAttributes();
393
+ const pkComposite = entity._expectFacets(upsertData, attributes.pk);
394
+
395
+ state
396
+ .addOption("_includeOnResponseItem", pkComposite)
397
+ .setPK(pkComposite)
398
+ .ifSK(() => {
399
+ entity._expectFacets(upsertData, attributes.sk);
400
+ const skComposite = entity._buildQueryFacets(
401
+ upsertData,
402
+ attributes.sk,
403
+ );
404
+ state.setSK(skComposite);
405
+ state.addOption("_includeOnResponseItem", {
406
+ ...skComposite,
407
+ ...pkComposite,
408
+ });
409
+ });
410
+
411
+ const appliedData = entity.model.schema.applyAttributeSetters({
412
+ ...upsertData,
413
+ });
414
+
415
+ const onlySetAppliedData = {};
416
+ const nonSetAppliedData = {};
417
+ for (const name in appliedData) {
418
+ const value = appliedData[name];
419
+ const isSetOperation = setData[name] !== undefined;
420
+ const cameFromApplyingSetters = allData[name] === undefined;
421
+ const isNotUndefined = appliedData[name] !== undefined;
422
+ const applyAsSet = isSetOperation || cameFromApplyingSetters;
423
+ if (applyAsSet && isNotUndefined) {
424
+ onlySetAppliedData[name] = value;
425
+ } else {
426
+ nonSetAppliedData[name] = value;
427
+ }
428
+ }
70
429
 
71
- } catch(err) {
72
- state.setError(err);
73
- return state;
74
- }
75
- },
76
- children: ["between", "gte", "gt", "lte", "lt", "begins", "params", "go"],
77
- },
78
- collection: {
79
- name: "collection",
80
- /* istanbul ignore next */
81
- action(entity, state, collection = "", facets /* istanbul ignore next */ = {}) {
82
- if (state.getError() !== null) {
83
- return state;
84
- }
85
- try {
86
- const {pk, sk} = state.getCompositeAttributes();
87
- return state
88
- .setType(QueryTypes.collection)
89
- .setMethod(MethodTypes.query)
90
- .setCollection(collection)
91
- .setPK(entity._expectFacets(facets, pk))
92
- .whenOptions(({ options, state }) => {
93
- if (!options.ignoreOwnership && !state.getParams()) {
94
- state.query.options.expressions.names = {
95
- ...state.query.options.expressions.names,
96
- ...state.query.options.identifiers.names,
97
- };
98
- state.query.options.expressions.values = {
99
- ...state.query.options.expressions.values,
100
- ...state.query.options.identifiers.values,
101
- };
102
- state.query.options.expressions.expression =
103
- state.query.options.expressions.expression.length > 1
104
- ? `(${state.query.options.expressions.expression}) AND ${state.query.options.identifiers.expression}`
105
- : `${state.query.options.identifiers.expression}`;
106
- }
107
- });
108
- } catch(err) {
109
- state.setError(err);
110
- return state;
111
- }
112
- },
113
- children: ["params", "go"],
114
- },
115
- scan: {
116
- name: "scan",
117
- action(entity, state, config) {
118
- if (state.getError() !== null) {
119
- return state;
120
- }
121
- try {
122
- return state.setMethod(MethodTypes.scan)
123
- .whenOptions(({ state, options }) => {
124
- if (!options.ignoreOwnership && !state.getParams()) {
125
- state.unsafeApplyFilter(FilterOperationNames.eq, entity.identifiers.entity, entity.getName());
126
- state.unsafeApplyFilter(FilterOperationNames.eq, entity.identifiers.version, entity.getVersion());
127
- }
128
- });
129
- } catch(err) {
130
- state.setError(err);
131
- return state;
132
- }
133
- },
134
- children: ["params", "go"],
135
- },
136
- get: {
137
- name: "get",
138
- /* istanbul ignore next */
139
- action(entity, state, facets = {}) {
140
- if (state.getError() !== null) {
141
- return state;
142
- }
143
- try {
144
- const {pk, sk} = state.getCompositeAttributes();
145
- const {composites} = state.identifyCompositeAttributes(facets, sk, pk);
146
- return state
147
- .setMethod(MethodTypes.get)
148
- .setType(QueryTypes.eq)
149
- .setPK(entity._expectFacets(facets, pk))
150
- .ifSK(() => {
151
- entity._expectFacets(facets, sk);
152
- state.setSK(composites);
153
- });
154
- } catch(err) {
155
- state.setError(err);
156
- return state;
157
- }
158
- },
159
- children: ["params", "go", "commit"],
160
- },
161
- check: {
162
- name: 'check',
163
- action(...params) {
164
- return clauses.get.action(...params)
165
- .setMethod(MethodTypes.check);
166
- },
167
- children: ["commit"],
168
- },
169
- batchGet: {
170
- name: "batchGet",
171
- action: (entity, state, payload) => batchAction(clauses.get.action, MethodTypes.batchGet, entity, state, payload),
172
- children: ["params", "go"],
173
- },
174
- batchDelete: {
175
- name: "batchDelete",
176
- action: (entity, state, payload) => batchAction(clauses.delete.action, MethodTypes.batchWrite, entity, state, payload),
177
- children: ["params", "go"],
178
- },
179
- delete: {
180
- name: "delete",
181
- /* istanbul ignore next */
182
- action(entity, state, facets = {}) {
183
- if (state.getError() !== null) {
184
- return state;
185
- }
186
- try {
187
- const {pk, sk} = state.getCompositeAttributes();
188
- const pkComposite = entity._expectFacets(facets, pk);
189
- state.addOption('_includeOnResponseItem', pkComposite);
190
- return state
191
- .setMethod(MethodTypes.delete)
192
- .setType(QueryTypes.eq)
193
- .setPK(pkComposite)
194
- .ifSK(() => {
195
- entity._expectFacets(facets, sk);
196
- const skComposite = state.buildQueryComposites(facets, sk);
197
- state.setSK(skComposite);
198
- state.addOption('_includeOnResponseItem', {...skComposite, ...pkComposite});
199
- });
200
- } catch(err) {
201
- state.setError(err);
202
- return state;
203
- }
204
- },
205
- children: ["where", "params", "go", "commit"],
206
- },
207
- remove: {
208
- name: "remove",
209
- /* istanbul ignore next */
210
- action(entity, state, facets = {}) {
211
- if (state.getError() !== null) {
212
- return state;
213
- }
214
- try {
215
- const attributes = state.getCompositeAttributes();
216
- const filter = state.query.filter[ExpressionTypes.ConditionExpression];
217
- const {pk, sk} = entity._getPrimaryIndexFieldNames();
218
- filter.unsafeSet(FilterOperationNames.exists, pk);
219
- if (sk) {
220
- filter.unsafeSet(FilterOperationNames.exists, sk);
221
- }
222
- const pkComposite = entity._expectFacets(facets, attributes.pk);
223
- state.addOption('_includeOnResponseItem', pkComposite);
224
- return state
225
- .setMethod(MethodTypes.delete)
226
- .setType(QueryTypes.eq)
227
- .setPK(pkComposite)
228
- .ifSK(() => {
229
- entity._expectFacets(facets, attributes.sk);
230
- const skComposite = state.buildQueryComposites(facets, attributes.sk);
231
- state.setSK(skComposite);
232
- state.addOption('_includeOnResponseItem', {...skComposite, ...pkComposite});
233
- });
234
- } catch(err) {
235
- state.setError(err);
236
- return state;
237
- }
238
- },
239
- children: ["where", "params", "go", "commit"],
240
- },
241
- upsert: {
242
- name: 'upsert',
243
- action(entity, state, payload = {}) {
244
- if (state.getError() !== null) {
245
- return state;
246
- }
247
- try {
248
- let record = entity.model.schema.checkCreate({...payload});
249
- const attributes = state.getCompositeAttributes();
250
- const pkComposite = entity._expectFacets(record, attributes.pk);
251
- state.addOption('_includeOnResponseItem', pkComposite);
252
- return state
253
- .setMethod(MethodTypes.upsert)
254
- .setType(QueryTypes.eq)
255
- .applyUpsert(record)
256
- .setPK(pkComposite)
257
- .ifSK(() => {
258
- entity._expectFacets(record, attributes.sk);
259
- const skComposite = entity._buildQueryFacets(record, attributes.sk);
260
- state.setSK(skComposite);
261
- state.addOption('_includeOnResponseItem', {...skComposite, ...pkComposite});
262
- })
263
- .whenOptions(({ state, options }) => {
264
- if (!state.getParams()) {
265
- state.query.update.set(entity.identifiers.entity, entity.getName());
266
- state.query.update.set(entity.identifiers.version, entity.getVersion());
267
- }
268
- });
269
- } catch(err) {
270
- state.setError(err);
271
- return state;
272
- }
273
- },
274
- children: ["params", "go", "where", "commit"],
275
- },
276
- put: {
277
- name: "put",
278
- /* istanbul ignore next */
279
- action(entity, state, payload = {}) {
280
- if (state.getError() !== null) {
281
- return state;
282
- }
283
- try {
284
- let record = entity.model.schema.checkCreate({...payload});
285
- const attributes = state.getCompositeAttributes();
286
- return state
287
- .setMethod(MethodTypes.put)
288
- .setType(QueryTypes.eq)
289
- .applyPut(record)
290
- .setPK(entity._expectFacets(record, attributes.pk))
291
- .ifSK(() => {
292
- entity._expectFacets(record, attributes.sk);
293
- state.setSK(state.buildQueryComposites(record, attributes.sk));
294
- });
295
- } catch(err) {
296
- state.setError(err);
297
- return state;
298
- }
299
- },
300
- children: ["params", "go", "commit"],
301
- },
302
- batchPut: {
303
- name: "batchPut",
304
- action: (entity, state, payload) => batchAction(clauses.put.action, MethodTypes.batchWrite, entity, state, payload),
305
- children: ["params", "go"],
306
- },
307
- create: {
308
- name: "create",
309
- action(entity, state, payload) {
310
- if (state.getError() !== null) {
311
- return state;
312
- }
313
- try {
314
- let record = entity.model.schema.checkCreate({...payload});
315
- const attributes = state.getCompositeAttributes();
316
- const filter = state.query.filter[ExpressionTypes.ConditionExpression];
317
- const { pk, sk } = entity._getPrimaryIndexFieldNames();
318
- filter.unsafeSet(FilterOperationNames.notExists, pk);
319
- if (sk) {
320
- filter.unsafeSet(FilterOperationNames.notExists, sk);
321
- }
322
- return state
323
- .setMethod(MethodTypes.put)
324
- .setType(QueryTypes.eq)
325
- .applyPut(record)
326
- .setPK(entity._expectFacets(record, attributes.pk))
327
- .ifSK(() => {
328
- entity._expectFacets(record, attributes.sk);
329
- state.setSK(state.buildQueryComposites(record, attributes.sk));
330
- });
331
- } catch(err) {
332
- state.setError(err);
333
- return state;
334
- }
335
- },
336
- children: ["params", "go", "commit"],
337
- },
338
- patch: {
339
- name: "patch",
340
- action(entity, state, facets) {
341
- if (state.getError() !== null) {
342
- return state;
343
- }
344
- try {
345
- const attributes = state.getCompositeAttributes();
346
- const filter = state.query.filter[ExpressionTypes.ConditionExpression];
347
- const {pk, sk} = entity._getPrimaryIndexFieldNames();
348
- filter.unsafeSet(FilterOperationNames.exists, pk);
349
- if (sk) {
350
- filter.unsafeSet(FilterOperationNames.exists, sk);
351
- }
352
- const pkComposite = entity._expectFacets(facets, attributes.pk);
353
- state.addOption('_includeOnResponseItem', pkComposite);
354
- return state
355
- .setMethod(MethodTypes.update)
356
- .setType(QueryTypes.eq)
357
- .setPK(pkComposite)
358
- .ifSK(() => {
359
- entity._expectFacets(facets, attributes.sk);
360
- const skComposite = state.buildQueryComposites(facets, attributes.sk);
361
- state.setSK(skComposite);
362
- state.addOption('_includeOnResponseItem', {...skComposite, ...pkComposite});
363
- });
364
- } catch(err) {
365
- state.setError(err);
366
- return state;
367
- }
368
- },
369
- children: ["set", "append","updateRemove", "updateDelete", "add", "subtract", "data", "composite", "commit"],
370
- },
371
- update: {
372
- name: "update",
373
- action(entity, state, facets) {
374
- if (state.getError() !== null) {
375
- return state;
376
- }
377
- try {
378
- const attributes = state.getCompositeAttributes();
379
- const pkComposite = entity._expectFacets(facets, attributes.pk);
380
- state.addOption('_includeOnResponseItem', pkComposite);
381
- return state
382
- .setMethod(MethodTypes.update)
383
- .setType(QueryTypes.eq)
384
- .setPK(pkComposite)
385
- .ifSK(() => {
386
- entity._expectFacets(facets, attributes.sk);
387
- const skComposite = state.buildQueryComposites(facets, attributes.sk);
388
- state.setSK(skComposite);
389
- state.addOption('_includeOnResponseItem', {...pkComposite, ...skComposite});
390
- });
391
- } catch(err) {
392
- state.setError(err);
393
- return state;
394
- }
395
- },
396
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit", "composite"],
397
- },
398
- data: {
399
- name: "data",
400
- action(entity, state, cb) {
401
- if (state.getError() !== null) {
402
- return state;
403
- }
404
- try {
405
- state.query.updateProxy.invokeCallback(cb);
406
- for (const path of Object.keys(state.query.update.refs)) {
407
- const operation = state.query.update.impacted[path];
408
- const attribute = state.query.update.refs[path];
409
- // note: keyValue will be empty if the user used `name`/`value` operations
410
- // because it becomes hard to know how they are used and which attribute
411
- // should validate the change. This is an edge case however, this change
412
- // still improves on the existing implementation.
413
- const keyValue = state.query.update.paths[path] || {};
414
- if (!attribute) {
415
- throw new e.ElectroAttributeValidationError(path, `Attribute "${path}" does not exist on model.`);
416
- }
430
+ // we build this above, and set them to state, but use it here, not ideal but
431
+ // the way it worked out so that this could be wrapped in beforeBuildParams
432
+ const { pk } = state.query.keys;
433
+ const sk = state.query.keys.sk[0];
417
434
 
418
- entity.model.schema.checkOperation(attribute, operation, keyValue.value);
419
- }
420
- return state;
421
- } catch(err) {
422
- state.setError(err);
423
- return state;
424
- }
425
- },
426
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit", "composite"],
427
- },
428
- set: {
429
- name: "set",
430
- action(entity, state, data) {
431
- if (state.getError() !== null) {
432
- return state;
433
- }
434
- try {
435
- entity.model.schema.checkUpdate(data);
436
- state.query.updateProxy.fromObject(ItemOperations.set, data);
437
- return state;
438
- } catch(err) {
439
- state.setError(err);
440
- return state;
441
- }
442
- },
443
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit", "composite"],
444
- },
445
- composite: {
446
- name: "composite",
447
- action(entity, state, composites = {}) {
448
- if (state.getError() !== null) {
449
- return state;
450
- }
451
- try {
452
- for (const attrName in composites) {
453
- // todo: validate attrName is facet
454
- if (entity.model.facets.byAttr[attrName]) {
455
- const wasSet = state.query.update.addComposite(attrName, composites[attrName]);
456
- if (!wasSet) {
457
- throw new e.ElectroError(e.ErrorCodes.DuplicateUpdateCompositesProvided, `The composite attribute ${attrName} has been provided more than once with different values. Remove the duplication before running again`);
458
- }
459
- state.applyCondition(FilterOperationNames.eq, attrName, composites[attrName]);
460
- }
461
- }
462
- return state;
463
- } catch(err) {
464
- state.setError(err);
465
- return state;
466
- }
467
- },
468
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit", "composite"],
469
- },
470
- append: {
471
- name: "append",
472
- action(entity, state, data = {}) {
473
- if (state.getError() !== null) {
474
- return state;
475
- }
476
- try {
477
- entity.model.schema.checkUpdate(data);
478
- state.query.updateProxy.fromObject(ItemOperations.append, data);
479
- return state;
480
- } catch(err) {
481
- state.setError(err);
482
- return state;
483
- }
484
- },
485
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit", "composite"],
486
- },
487
- updateRemove: {
488
- name: "remove",
489
- action(entity, state, data) {
490
- if (state.getError() !== null) {
491
- return state;
492
- }
493
- try {
494
- if (!Array.isArray(data)) {
495
- throw new Error("Update method 'remove' expects type Array");
496
- }
497
- entity.model.schema.checkRemove(data);
498
- state.query.updateProxy.fromArray(ItemOperations.remove, data);
499
- return state;
500
- } catch(err) {
501
- state.setError(err);
502
- return state;
503
- }
504
- },
505
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit", "composite"],
506
- },
507
- updateDelete: {
508
- name: "delete",
509
- action(entity, state, data) {
510
- if (state.getError() !== null) {
511
- return state;
512
- }
513
- try {
514
- entity.model.schema.checkUpdate(data);
515
- state.query.updateProxy.fromObject(ItemOperations.delete, data);
516
- return state;
517
- } catch(err) {
518
- state.setError(err);
519
- return state;
520
- }
521
- },
522
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit", "composite"],
523
- },
524
- add: {
525
- name: "add",
526
- action(entity, state, data) {
527
- if (state.getError() !== null) {
528
- return state;
529
- }
530
- try {
531
- entity.model.schema.checkUpdate(data);
532
- state.query.updateProxy.fromObject(ItemOperations.add, data);
533
- return state;
534
- } catch(err) {
535
- state.setError(err);
536
- return state;
537
- }
538
- },
539
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit", "composite"],
540
- },
541
- subtract: {
542
- name: "subtract",
543
- action(entity, state, data) {
544
- if (state.getError() !== null) {
545
- return state;
546
- }
547
- try {
548
- entity.model.schema.checkUpdate(data);
549
- state.query.updateProxy.fromObject(ItemOperations.subtract, data);
550
- return state;
551
- } catch(err) {
552
- state.setError(err);
553
- return state;
554
- }
555
- },
556
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit", "composite"],
557
- },
558
- query: {
559
- name: "query",
560
- action(entity, state, facets, options = {}) {
561
- if (state.getError() !== null) {
562
- return state;
563
- }
564
- try {
565
- state.addOption('_isPagination', true);
566
- const {pk, sk} = state.getCompositeAttributes();
567
- return state
568
- .setMethod(MethodTypes.query)
569
- .setType(QueryTypes.is)
570
- .setPK(entity._expectFacets(facets, pk))
571
- .ifSK(() => {
572
- const {composites, unused} = state.identifyCompositeAttributes(facets, sk, pk);
573
- state.setSK(state.buildQueryComposites(facets, sk));
574
- // we must apply eq on filter on all provided because if the user then does a sort key operation, it'd actually then unexpect results
575
- if (sk.length > 1) {
576
- state.filterProperties(FilterOperationNames.eq, {...unused, ...composites});
577
- }
435
+ const { updatedKeys, setAttributes, indexKey } = entity._getPutKeys(
436
+ pk,
437
+ sk && sk.facets,
438
+ onlySetAppliedData,
439
+ );
578
440
 
579
- state.whenOptions(({ options, state }) => {
580
- if (state.query.options.indexType === IndexTypes.clustered && Object.keys(composites).length < sk.length && !options.ignoreOwnership && !state.getParams()) {
581
- state.unsafeApplyFilter(FilterOperationNames.eq, entity.identifiers.entity, entity.getName())
582
- .unsafeApplyFilter(FilterOperationNames.eq, entity.identifiers.version, entity.getVersion());
583
- }
584
- });
585
- });
586
- } catch(err) {
587
- state.setError(err);
588
- return state;
589
- }
590
- },
591
- children: ["between", "gte", "gt", "lte", "lt", "begins", "params", "go"],
592
- },
593
- between: {
594
- name: "between",
595
- action(entity, state, startingFacets = {}, endingFacets = {}) {
596
- if (state.getError() !== null) {
597
- return state;
598
- }
599
- try {
600
- const {pk, sk} = state.getCompositeAttributes();
601
- const endingSk = state.identifyCompositeAttributes(endingFacets, sk, pk);
602
- const startingSk = state.identifyCompositeAttributes(startingFacets, sk, pk);
603
- return state
604
- .setType(QueryTypes.and)
605
- .setSK(endingSk.composites)
606
- .setType(QueryTypes.between)
607
- .setSK(startingSk.composites)
608
- .filterProperties(FilterOperationNames.lte, endingSk.composites);
609
- } catch(err) {
610
- state.setError(err);
611
- return state;
612
- }
613
- },
614
- children: ["go", "params"],
615
- },
616
- begins: {
617
- name: "begins",
618
- action(entity, state, facets = {}) {
619
- if (state.getError() !== null) {
620
- return state;
621
- }
622
- try {
623
- return state
624
- .setType(QueryTypes.begins)
625
- .ifSK(state => {
626
- const attributes = state.getCompositeAttributes();
627
- state.setSK(state.buildQueryComposites(facets, attributes.sk));
628
- });
629
- } catch(err) {
630
- state.setError(err);
631
- return state;
632
- }
633
- },
634
- children: ["go", "params"],
635
- },
636
- gt: {
637
- name: "gt",
638
- action(entity, state, facets = {}) {
639
- if (state.getError() !== null) {
640
- return state;
641
- }
642
- try {
441
+ // calculated here but needs to be used when building the params
442
+ upsert.indexKey = indexKey;
643
443
 
644
- return state
645
- .setType(QueryTypes.gt)
646
- .ifSK(state => {
647
- const {pk, sk} = state.getCompositeAttributes();
648
- const {composites} = state.identifyCompositeAttributes(facets, sk, pk);
649
- state.setSK(composites);
650
- state.filterProperties(FilterOperationNames.gt, {
651
- ...composites,
652
- });
653
- });
654
- } catch(err) {
655
- state.setError(err);
656
- return state;
657
- }
658
- },
659
- children: ["go", "params"],
660
- },
661
- gte: {
662
- name: "gte",
663
- action(entity, state, facets = {}) {
664
- if (state.getError() !== null) {
665
- return state;
666
- }
667
- try {
668
- return state
669
- .setType(QueryTypes.gte)
670
- .ifSK(state => {
671
- const attributes = state.getCompositeAttributes();
672
- state.setSK(state.buildQueryComposites(facets, attributes.sk));
673
- });
674
- } catch(err) {
675
- state.setError(err);
676
- return state;
677
- }
678
- },
679
- children: ["go", "params"],
680
- },
681
- lt: {
682
- name: "lt",
683
- action(entity, state, facets = {}) {
684
- if (state.getError() !== null) {
685
- return state;
686
- }
687
- try {
688
- return state.setType(QueryTypes.lt)
689
- .ifSK(state => {
690
- const {pk, sk} = state.getCompositeAttributes();
691
- const {composites} = state.identifyCompositeAttributes(facets, sk, pk);
692
- state.setSK(composites);
693
- });
694
- } catch(err) {
695
- state.setError(err);
696
- return state;
697
- }
698
- },
699
- children: ["go", "params"],
700
- },
701
- lte: {
702
- name: "lte",
703
- action(entity, state, facets = {}) {
704
- if (state.getError() !== null) {
705
- return state;
706
- }
707
- try {
708
- return state.setType(QueryTypes.lte)
709
- .ifSK(state => {
710
- const {pk, sk} = state.getCompositeAttributes();
711
- const {composites} = state.identifyCompositeAttributes(facets, sk, pk);
712
- state.setSK(composites);
713
- state.filterProperties(FilterOperationNames.lte, {
714
- ...composites,
715
- });
716
- });
717
- } catch(err) {
718
- state.setError(err);
719
- return state;
720
- }
721
- },
722
- children: ["go", "params"],
723
- },
724
- commit: {
725
- name: 'commit',
726
- action(entity, state, options) {
727
- if (state.getError() !== null) {
728
- throw state.error;
729
- }
444
+ // only "set" data is used to make keys
445
+ const setFields = Object.entries(
446
+ entity.model.schema.translateToFields(setAttributes),
447
+ );
730
448
 
731
- const results = clauses.params.action(entity, state, {
732
- ...options,
733
- _returnOptions: true,
734
- _isTransaction: true,
735
- });
449
+ // add the keys impacted except for the table index keys; they are upserted
450
+ // automatically by dynamo
451
+ for (const key in updatedKeys) {
452
+ const value = updatedKeys[key];
453
+ if (indexKey[key] === undefined) {
454
+ setFields.push([key, value]);
455
+ }
456
+ }
736
457
 
737
- const method = TransactionOperations[state.query.method];
738
- if (!method) {
739
- throw new Error('Invalid commit method');
740
- }
458
+ entity._maybeApplyUpsertUpdate({
459
+ fields: setFields,
460
+ operation: UpsertOperations.set,
461
+ updateProxy,
462
+ update,
463
+ });
741
464
 
742
- return {
743
- [method]: results.params,
744
- [TransactionCommitSymbol]: () => {
745
- return {
746
- entity,
747
- }
748
- },
749
- }
750
- },
751
- children: [],
752
- },
753
- params: {
754
- name: "params",
755
- action(entity, state, options = {}) {
756
- if (state.getError() !== null) {
757
- throw state.error;
758
- }
759
- try {
760
- if (!v.isStringHasLength(options.table) && !v.isStringHasLength(entity.getTableName())) {
761
- throw new e.ElectroError(e.ErrorCodes.MissingTable, `Table name not defined. Table names must be either defined on the model, instance configuration, or as a query option.`);
762
- }
763
- const method = state.getMethod();
764
- const normalizedOptions = entity._normalizeExecutionOptions({
765
- provided: [ state.getOptions(), state.query.options, options ],
766
- context: { operation: options._isTransaction ? MethodTypes.transactWrite : undefined }
767
- });
465
+ for (const name in nonSetData) {
466
+ const value = appliedData[name];
467
+ if (value === undefined || upsert.data[name] === undefined) {
468
+ continue;
469
+ }
768
470
 
769
- state.applyWithOptions(normalizedOptions);
471
+ const { operation } = upsert.data[name];
472
+ const fields = entity.model.schema.translateToFields({
473
+ [name]: value,
474
+ });
475
+ entity._maybeApplyUpsertUpdate({
476
+ fields: Object.entries(fields),
477
+ updateProxy,
478
+ operation,
479
+ update,
480
+ });
481
+ }
482
+ });
483
+ } catch (err) {
484
+ state.setError(err);
485
+ return state;
486
+ }
487
+ },
488
+ children: methodChildren.upsert,
489
+ },
490
+ put: {
491
+ name: "put",
492
+ /* istanbul ignore next */
493
+ action(entity, state, payload = {}) {
494
+ if (state.getError() !== null) {
495
+ return state;
496
+ }
497
+ try {
498
+ let record = entity.model.schema.checkCreate({ ...payload });
499
+ const attributes = state.getCompositeAttributes();
500
+ return state
501
+ .setMethod(MethodTypes.put)
502
+ .setType(QueryTypes.eq)
503
+ .applyPut(record)
504
+ .setPK(entity._expectFacets(record, attributes.pk))
505
+ .ifSK(() => {
506
+ entity._expectFacets(record, attributes.sk);
507
+ state.setSK(state.buildQueryComposites(record, attributes.sk));
508
+ });
509
+ } catch (err) {
510
+ state.setError(err);
511
+ return state;
512
+ }
513
+ },
514
+ children: methodChildren.put,
515
+ },
516
+ batchPut: {
517
+ name: "batchPut",
518
+ action: (entity, state, payload) =>
519
+ batchAction(
520
+ clauses.put.action,
521
+ MethodTypes.batchWrite,
522
+ entity,
523
+ state,
524
+ payload,
525
+ ),
526
+ children: ["params", "go"],
527
+ },
528
+ create: {
529
+ name: "create",
530
+ action(entity, state, payload) {
531
+ if (state.getError() !== null) {
532
+ return state;
533
+ }
534
+ try {
535
+ let record = entity.model.schema.checkCreate({ ...payload });
536
+ const attributes = state.getCompositeAttributes();
537
+ const filter = state.query.filter[ExpressionTypes.ConditionExpression];
538
+ const { pk, sk } = entity._getPrimaryIndexFieldNames();
539
+ filter.unsafeSet(FilterOperationNames.notExists, pk);
540
+ if (sk) {
541
+ filter.unsafeSet(FilterOperationNames.notExists, sk);
542
+ }
543
+ return state
544
+ .setMethod(MethodTypes.put)
545
+ .setType(QueryTypes.eq)
546
+ .applyPut(record)
547
+ .setPK(entity._expectFacets(record, attributes.pk))
548
+ .ifSK(() => {
549
+ entity._expectFacets(record, attributes.sk);
550
+ state.setSK(state.buildQueryComposites(record, attributes.sk));
551
+ });
552
+ } catch (err) {
553
+ state.setError(err);
554
+ return state;
555
+ }
556
+ },
557
+ children: methodChildren.put,
558
+ },
559
+ patch: {
560
+ name: "patch",
561
+ action(entity, state, facets) {
562
+ if (state.getError() !== null) {
563
+ return state;
564
+ }
565
+ try {
566
+ const attributes = state.getCompositeAttributes();
567
+ const filter = state.query.filter[ExpressionTypes.ConditionExpression];
568
+ const { pk, sk } = entity._getPrimaryIndexFieldNames();
569
+ filter.unsafeSet(FilterOperationNames.exists, pk);
570
+ if (sk) {
571
+ filter.unsafeSet(FilterOperationNames.exists, sk);
572
+ }
573
+ const pkComposite = entity._expectFacets(facets, attributes.pk);
574
+ state.addOption("_includeOnResponseItem", pkComposite);
575
+ return state
576
+ .setMethod(MethodTypes.update)
577
+ .setType(QueryTypes.eq)
578
+ .setPK(pkComposite)
579
+ .ifSK(() => {
580
+ entity._expectFacets(facets, attributes.sk);
581
+ const skComposite = state.buildQueryComposites(
582
+ facets,
583
+ attributes.sk,
584
+ );
585
+ state.setSK(skComposite);
586
+ state.addOption("_includeOnResponseItem", {
587
+ ...skComposite,
588
+ ...pkComposite,
589
+ });
590
+ });
591
+ } catch (err) {
592
+ state.setError(err);
593
+ return state;
594
+ }
595
+ },
596
+ children: methodChildren.update,
597
+ },
598
+ update: {
599
+ name: "update",
600
+ action(entity, state, facets) {
601
+ if (state.getError() !== null) {
602
+ return state;
603
+ }
604
+ try {
605
+ const attributes = state.getCompositeAttributes();
606
+ const pkComposite = entity._expectFacets(facets, attributes.pk);
607
+ state.addOption("_includeOnResponseItem", pkComposite);
608
+ return state
609
+ .setMethod(MethodTypes.update)
610
+ .setType(QueryTypes.eq)
611
+ .setPK(pkComposite)
612
+ .ifSK(() => {
613
+ entity._expectFacets(facets, attributes.sk);
614
+ const skComposite = state.buildQueryComposites(
615
+ facets,
616
+ attributes.sk,
617
+ );
618
+ state.setSK(skComposite);
619
+ state.addOption("_includeOnResponseItem", {
620
+ ...pkComposite,
621
+ ...skComposite,
622
+ });
623
+ });
624
+ } catch (err) {
625
+ state.setError(err);
626
+ return state;
627
+ }
628
+ },
629
+ children: methodChildren.update,
630
+ },
631
+ data: {
632
+ name: "data",
633
+ action(entity, state, cb) {
634
+ if (state.getError() !== null) {
635
+ return state;
636
+ }
637
+ try {
638
+ state.query.updateProxy.invokeCallback(cb);
639
+ for (const path of Object.keys(state.query.update.refs)) {
640
+ const operation = state.query.update.impacted[path];
641
+ const attribute = state.query.update.refs[path];
642
+ // note: keyValue will be empty if the user used `name`/`value` operations
643
+ // because it becomes hard to know how they are used and which attribute
644
+ // should validate the change. This is an edge case however, this change
645
+ // still improves on the existing implementation.
646
+ const keyValue = state.query.update.paths[path] || {};
647
+ if (!attribute) {
648
+ throw new e.ElectroAttributeValidationError(
649
+ path,
650
+ `Attribute "${path}" does not exist on model.`,
651
+ );
652
+ }
770
653
 
771
- let results;
772
- switch (method) {
773
- case MethodTypes.query: {
774
- results = entity._queryParams(state, normalizedOptions);
775
- break;
776
- }
777
- case MethodTypes.batchWrite: {
778
- results = entity._batchWriteParams(state, normalizedOptions);
779
- break;
780
- }
781
- case MethodTypes.batchGet: {
782
- results = entity._batchGetParams(state, normalizedOptions);
783
- break;
784
- }
785
- default: {
786
- results = entity._params(state, normalizedOptions);
787
- break;
788
- }
789
- }
654
+ entity.model.schema.checkOperation(
655
+ attribute,
656
+ operation,
657
+ keyValue.value,
658
+ );
659
+ }
660
+ return state;
661
+ } catch (err) {
662
+ state.setError(err);
663
+ return state;
664
+ }
665
+ },
666
+ children: methodChildren.update,
667
+ },
668
+ set: {
669
+ name: "set",
670
+ action(entity, state, data) {
671
+ if (state.getError() !== null) {
672
+ return state;
673
+ }
674
+ try {
675
+ entity.model.schema.checkUpdate(data);
676
+ state.query.updateProxy.fromObject(ItemOperations.set, data);
677
+ return state;
678
+ } catch (err) {
679
+ state.setError(err);
680
+ return state;
681
+ }
682
+ },
683
+ children: methodChildren.update,
684
+ },
685
+ upsertSet: {
686
+ name: "set",
687
+ action(entity, state, data) {
688
+ if (state.getError() !== null) {
689
+ return state;
690
+ }
691
+ try {
692
+ entity.model.schema.checkUpdate(data, { allowReadOnly: true });
693
+ state.query.upsert.addData(UpsertOperations.set, data);
694
+ return state;
695
+ } catch (err) {
696
+ state.setError(err);
697
+ return state;
698
+ }
699
+ },
700
+ children: methodChildren.upsert,
701
+ },
702
+ composite: {
703
+ name: "composite",
704
+ action(entity, state, composites = {}) {
705
+ if (state.getError() !== null) {
706
+ return state;
707
+ }
708
+ try {
709
+ for (const attrName in composites) {
710
+ // todo: validate attrName is facet
711
+ if (entity.model.facets.byAttr[attrName]) {
712
+ const wasSet = state.query.update.addComposite(
713
+ attrName,
714
+ composites[attrName],
715
+ );
716
+ if (!wasSet) {
717
+ throw new e.ElectroError(
718
+ e.ErrorCodes.DuplicateUpdateCompositesProvided,
719
+ `The composite attribute ${attrName} has been provided more than once with different values. Remove the duplication before running again`,
720
+ );
721
+ }
722
+ state.applyCondition(
723
+ FilterOperationNames.eq,
724
+ attrName,
725
+ composites[attrName],
726
+ );
727
+ }
728
+ }
729
+ return state;
730
+ } catch (err) {
731
+ state.setError(err);
732
+ return state;
733
+ }
734
+ },
735
+ children: methodChildren.update,
736
+ },
737
+ append: {
738
+ name: "append",
739
+ action(entity, state, data = {}) {
740
+ if (state.getError() !== null) {
741
+ return state;
742
+ }
743
+ try {
744
+ entity.model.schema.checkUpdate(data);
745
+ state.query.updateProxy.fromObject(ItemOperations.append, data);
746
+ return state;
747
+ } catch (err) {
748
+ state.setError(err);
749
+ return state;
750
+ }
751
+ },
752
+ children: methodChildren.update,
753
+ },
754
+ ifNotExists: {
755
+ name: "ifNotExists",
756
+ action(entity, state, data = {}) {
757
+ entity.model.schema.checkUpdate(data);
758
+ state.query.updateProxy.fromObject(ItemOperations.ifNotExists, data);
759
+ return state;
760
+ },
761
+ children: methodChildren.update,
762
+ },
763
+ upsertIfNotExists: {
764
+ name: "ifNotExists",
765
+ action(entity, state, data = {}) {
766
+ if (state.getError() !== null) {
767
+ return state;
768
+ }
769
+ try {
770
+ entity.model.schema.checkUpdate(data, { allowReadOnly: true });
771
+ state.query.upsert.addData(UpsertOperations.ifNotExists, data);
772
+ return state;
773
+ } catch (err) {
774
+ state.setError(err);
775
+ return state;
776
+ }
777
+ },
778
+ children: methodChildren.upsert,
779
+ },
780
+ upsertAppend: {
781
+ name: "append",
782
+ action(entity, state, data = {}) {
783
+ if (state.getError() !== null) {
784
+ return state;
785
+ }
786
+ try {
787
+ entity.model.schema.checkUpdate(data, { allowReadOnly: true });
788
+ state.query.upsert.addData(UpsertOperations.append, data);
789
+ return state;
790
+ } catch (err) {
791
+ state.setError(err);
792
+ return state;
793
+ }
794
+ },
795
+ children: methodChildren.upsert,
796
+ },
797
+ updateRemove: {
798
+ name: "remove",
799
+ action(entity, state, data) {
800
+ if (state.getError() !== null) {
801
+ return state;
802
+ }
803
+ try {
804
+ if (!Array.isArray(data)) {
805
+ throw new Error("Update method 'remove' expects type Array");
806
+ }
807
+ entity.model.schema.checkRemove(data);
808
+ state.query.updateProxy.fromArray(ItemOperations.remove, data);
809
+ return state;
810
+ } catch (err) {
811
+ state.setError(err);
812
+ return state;
813
+ }
814
+ },
815
+ children: methodChildren.update,
816
+ },
817
+ updateDelete: {
818
+ name: "delete",
819
+ action(entity, state, data) {
820
+ if (state.getError() !== null) {
821
+ return state;
822
+ }
823
+ try {
824
+ entity.model.schema.checkUpdate(data);
825
+ state.query.updateProxy.fromObject(ItemOperations.delete, data);
826
+ return state;
827
+ } catch (err) {
828
+ state.setError(err);
829
+ return state;
830
+ }
831
+ },
832
+ children: methodChildren.update,
833
+ },
834
+ add: {
835
+ name: "add",
836
+ action(entity, state, data) {
837
+ if (state.getError() !== null) {
838
+ return state;
839
+ }
840
+ try {
841
+ entity.model.schema.checkUpdate(data);
842
+ state.query.updateProxy.fromObject(ItemOperations.add, data);
843
+ return state;
844
+ } catch (err) {
845
+ state.setError(err);
846
+ return state;
847
+ }
848
+ },
849
+ children: methodChildren.update,
850
+ },
851
+ upsertAdd: {
852
+ name: "add",
853
+ action(entity, state, data) {
854
+ if (state.getError() !== null) {
855
+ return state;
856
+ }
857
+ try {
858
+ entity.model.schema.checkUpdate(data, { allowReadOnly: true });
859
+ state.query.upsert.addData(UpsertOperations.add, data);
860
+ return state;
861
+ } catch (err) {
862
+ state.setError(err);
863
+ return state;
864
+ }
865
+ },
866
+ children: methodChildren.upsert,
867
+ },
868
+ upsertSubtract: {
869
+ name: "subtract",
870
+ action(entity, state, data) {
871
+ if (state.getError() !== null) {
872
+ return state;
873
+ }
874
+ try {
875
+ entity.model.schema.checkUpdate(data, { allowReadOnly: true });
876
+ state.query.upsert.addData(UpsertOperations.subtract, data);
877
+ return state;
878
+ } catch (err) {
879
+ state.setError(err);
880
+ return state;
881
+ }
882
+ },
883
+ children: methodChildren.upsert,
884
+ },
885
+ subtract: {
886
+ name: "subtract",
887
+ action(entity, state, data) {
888
+ if (state.getError() !== null) {
889
+ return state;
890
+ }
891
+ try {
892
+ entity.model.schema.checkUpdate(data);
893
+ state.query.updateProxy.fromObject(ItemOperations.subtract, data);
894
+ return state;
895
+ } catch (err) {
896
+ state.setError(err);
897
+ return state;
898
+ }
899
+ },
900
+ children: methodChildren.update,
901
+ },
902
+ query: {
903
+ name: "query",
904
+ action(entity, state, facets, options = {}) {
905
+ if (state.getError() !== null) {
906
+ return state;
907
+ }
908
+ try {
909
+ state.addOption("_isPagination", true);
910
+ const { pk, sk } = state.getCompositeAttributes();
911
+ return state
912
+ .setMethod(MethodTypes.query)
913
+ .setType(QueryTypes.is)
914
+ .setPK(entity._expectFacets(facets, pk))
915
+ .ifSK(() => {
916
+ const { composites, unused } = state.identifyCompositeAttributes(
917
+ facets,
918
+ sk,
919
+ pk,
920
+ );
921
+ state.setSK(state.buildQueryComposites(facets, sk));
922
+ // we must apply eq on filter on all provided because if the user then does a sort key operation, it'd actually then unexpect results
923
+ if (sk.length > 1) {
924
+ state.filterProperties(FilterOperationNames.eq, {
925
+ ...unused,
926
+ ...composites,
927
+ });
928
+ }
790
929
 
791
- if (method === MethodTypes.update && results.ExpressionAttributeValues && Object.keys(results.ExpressionAttributeValues).length === 0) {
792
- // An update that only does a `remove` operation would result in an empty object
793
- // todo: change the getValues() method to return undefined in this case (would potentially require a more generous refactor)
794
- delete results.ExpressionAttributeValues;
795
- }
930
+ state.whenOptions(({ options, state }) => {
931
+ if (
932
+ state.query.options.indexType === IndexTypes.clustered &&
933
+ Object.keys(composites).length < sk.length &&
934
+ !options.ignoreOwnership &&
935
+ !state.getParams()
936
+ ) {
937
+ state
938
+ .unsafeApplyFilter(
939
+ FilterOperationNames.eq,
940
+ entity.identifiers.entity,
941
+ entity.getName(),
942
+ )
943
+ .unsafeApplyFilter(
944
+ FilterOperationNames.eq,
945
+ entity.identifiers.version,
946
+ entity.getVersion(),
947
+ );
948
+ }
949
+ });
950
+ });
951
+ } catch (err) {
952
+ state.setError(err);
953
+ return state;
954
+ }
955
+ },
956
+ children: ["between", "gte", "gt", "lte", "lt", "begins", "params", "go"],
957
+ },
958
+ between: {
959
+ name: "between",
960
+ action(entity, state, startingFacets = {}, endingFacets = {}) {
961
+ if (state.getError() !== null) {
962
+ return state;
963
+ }
964
+ try {
965
+ const { pk, sk } = state.getCompositeAttributes();
966
+ const endingSk = state.identifyCompositeAttributes(
967
+ endingFacets,
968
+ sk,
969
+ pk,
970
+ );
971
+ const startingSk = state.identifyCompositeAttributes(
972
+ startingFacets,
973
+ sk,
974
+ pk,
975
+ );
976
+ return state
977
+ .setType(QueryTypes.and)
978
+ .setSK(endingSk.composites)
979
+ .setType(QueryTypes.between)
980
+ .setSK(startingSk.composites)
981
+ .filterProperties(FilterOperationNames.lte, endingSk.composites);
982
+ } catch (err) {
983
+ state.setError(err);
984
+ return state;
985
+ }
986
+ },
987
+ children: ["go", "params"],
988
+ },
989
+ begins: {
990
+ name: "begins",
991
+ action(entity, state, facets = {}) {
992
+ if (state.getError() !== null) {
993
+ return state;
994
+ }
995
+ try {
996
+ return state.setType(QueryTypes.begins).ifSK((state) => {
997
+ const attributes = state.getCompositeAttributes();
998
+ state.setSK(state.buildQueryComposites(facets, attributes.sk));
999
+ });
1000
+ } catch (err) {
1001
+ state.setError(err);
1002
+ return state;
1003
+ }
1004
+ },
1005
+ children: ["go", "params"],
1006
+ },
1007
+ gt: {
1008
+ name: "gt",
1009
+ action(entity, state, facets = {}) {
1010
+ if (state.getError() !== null) {
1011
+ return state;
1012
+ }
1013
+ try {
1014
+ return state.setType(QueryTypes.gt).ifSK((state) => {
1015
+ const { pk, sk } = state.getCompositeAttributes();
1016
+ const { composites } = state.identifyCompositeAttributes(
1017
+ facets,
1018
+ sk,
1019
+ pk,
1020
+ );
1021
+ state.setSK(composites);
1022
+ state.filterProperties(FilterOperationNames.gt, {
1023
+ ...composites,
1024
+ });
1025
+ });
1026
+ } catch (err) {
1027
+ state.setError(err);
1028
+ return state;
1029
+ }
1030
+ },
1031
+ children: ["go", "params"],
1032
+ },
1033
+ gte: {
1034
+ name: "gte",
1035
+ action(entity, state, facets = {}) {
1036
+ if (state.getError() !== null) {
1037
+ return state;
1038
+ }
1039
+ try {
1040
+ return state.setType(QueryTypes.gte).ifSK((state) => {
1041
+ const attributes = state.getCompositeAttributes();
1042
+ state.setSK(state.buildQueryComposites(facets, attributes.sk));
1043
+ });
1044
+ } catch (err) {
1045
+ state.setError(err);
1046
+ return state;
1047
+ }
1048
+ },
1049
+ children: ["go", "params"],
1050
+ },
1051
+ lt: {
1052
+ name: "lt",
1053
+ action(entity, state, facets = {}) {
1054
+ if (state.getError() !== null) {
1055
+ return state;
1056
+ }
1057
+ try {
1058
+ return state.setType(QueryTypes.lt).ifSK((state) => {
1059
+ const { pk, sk } = state.getCompositeAttributes();
1060
+ const { composites } = state.identifyCompositeAttributes(
1061
+ facets,
1062
+ sk,
1063
+ pk,
1064
+ );
1065
+ state.setSK(composites);
1066
+ });
1067
+ } catch (err) {
1068
+ state.setError(err);
1069
+ return state;
1070
+ }
1071
+ },
1072
+ children: ["go", "params"],
1073
+ },
1074
+ lte: {
1075
+ name: "lte",
1076
+ action(entity, state, facets = {}) {
1077
+ if (state.getError() !== null) {
1078
+ return state;
1079
+ }
1080
+ try {
1081
+ return state.setType(QueryTypes.lte).ifSK((state) => {
1082
+ const { pk, sk } = state.getCompositeAttributes();
1083
+ const { composites } = state.identifyCompositeAttributes(
1084
+ facets,
1085
+ sk,
1086
+ pk,
1087
+ );
1088
+ state.setSK(composites);
1089
+ state.filterProperties(FilterOperationNames.lte, {
1090
+ ...composites,
1091
+ });
1092
+ });
1093
+ } catch (err) {
1094
+ state.setError(err);
1095
+ return state;
1096
+ }
1097
+ },
1098
+ children: ["go", "params"],
1099
+ },
1100
+ commit: {
1101
+ name: "commit",
1102
+ action(entity, state, options) {
1103
+ if (state.getError() !== null) {
1104
+ throw state.error;
1105
+ }
796
1106
 
797
- state.setParams(results);
1107
+ const results = clauses.params.action(entity, state, {
1108
+ ...options,
1109
+ _returnOptions: true,
1110
+ _isTransaction: true,
1111
+ });
798
1112
 
799
- if (options._returnOptions) {
800
- return {
801
- params: results,
802
- options: normalizedOptions,
803
- }
804
- }
1113
+ const method = TransactionOperations[state.query.method];
1114
+ if (!method) {
1115
+ throw new Error("Invalid commit method");
1116
+ }
805
1117
 
806
- return results;
807
- } catch(err) {
808
- throw err;
809
- }
810
- },
811
- children: [],
812
- },
813
- go: {
814
- name: "go",
815
- action(entity, state, options = {}) {
816
- if (state.getError() !== null) {
817
- return Promise.reject(state.error);
818
- }
819
- try {
820
- if (entity.client === undefined) {
821
- throw new e.ElectroError(e.ErrorCodes.NoClientDefined, "No client defined on model");
822
- }
823
- options.terminalOperation = TerminalOperation.go;
824
- const paramResults = clauses.params.action(entity, state, { ...options, _returnOptions: true });
825
- return entity.go(state.getMethod(), paramResults.params, paramResults.options);
826
- } catch(err) {
827
- return Promise.reject(err);
828
- }
829
- },
830
- children: [],
831
- },
1118
+ return {
1119
+ [method]: results.params,
1120
+ [TransactionCommitSymbol]: () => {
1121
+ return {
1122
+ entity,
1123
+ };
1124
+ },
1125
+ };
1126
+ },
1127
+ children: [],
1128
+ },
1129
+ params: {
1130
+ name: "params",
1131
+ action(entity, state, options = {}) {
1132
+ if (state.getError() !== null) {
1133
+ throw state.error;
1134
+ }
1135
+ try {
1136
+ if (
1137
+ !v.isStringHasLength(options.table) &&
1138
+ !v.isStringHasLength(entity.getTableName())
1139
+ ) {
1140
+ throw new e.ElectroError(
1141
+ e.ErrorCodes.MissingTable,
1142
+ `Table name not defined. Table names must be either defined on the model, instance configuration, or as a query option.`,
1143
+ );
1144
+ }
1145
+ const method = state.getMethod();
1146
+ const normalizedOptions = entity._normalizeExecutionOptions({
1147
+ provided: [state.getOptions(), state.query.options, options],
1148
+ context: {
1149
+ operation: options._isTransaction
1150
+ ? MethodTypes.transactWrite
1151
+ : undefined,
1152
+ },
1153
+ });
1154
+
1155
+ state.applyWithOptions(normalizedOptions);
1156
+ state.applyBeforeBuildParams(normalizedOptions);
1157
+
1158
+ let results;
1159
+ switch (method) {
1160
+ case MethodTypes.query: {
1161
+ results = entity._queryParams(state, normalizedOptions);
1162
+ break;
1163
+ }
1164
+ case MethodTypes.batchWrite: {
1165
+ results = entity._batchWriteParams(state, normalizedOptions);
1166
+ break;
1167
+ }
1168
+ case MethodTypes.batchGet: {
1169
+ results = entity._batchGetParams(state, normalizedOptions);
1170
+ break;
1171
+ }
1172
+ default: {
1173
+ results = entity._params(state, normalizedOptions);
1174
+ break;
1175
+ }
1176
+ }
1177
+
1178
+ if (
1179
+ method === MethodTypes.update &&
1180
+ results.ExpressionAttributeValues &&
1181
+ Object.keys(results.ExpressionAttributeValues).length === 0
1182
+ ) {
1183
+ // An update that only does a `remove` operation would result in an empty object
1184
+ // todo: change the getValues() method to return undefined in this case (would potentially require a more generous refactor)
1185
+ delete results.ExpressionAttributeValues;
1186
+ }
1187
+
1188
+ if (options._returnOptions) {
1189
+ results = {
1190
+ params: results,
1191
+ options: normalizedOptions,
1192
+ };
1193
+ }
1194
+
1195
+ state.setParams(results);
1196
+
1197
+ return results;
1198
+ } catch (err) {
1199
+ throw err;
1200
+ }
1201
+ },
1202
+ children: [],
1203
+ },
1204
+ go: {
1205
+ name: "go",
1206
+ action(entity, state, options = {}) {
1207
+ if (state.getError() !== null) {
1208
+ return Promise.reject(state.error);
1209
+ }
1210
+ try {
1211
+ if (entity.client === undefined) {
1212
+ throw new e.ElectroError(
1213
+ e.ErrorCodes.NoClientDefined,
1214
+ "No client defined on model",
1215
+ );
1216
+ }
1217
+ options.terminalOperation = TerminalOperation.go;
1218
+ const paramResults = clauses.params.action(entity, state, {
1219
+ ...options,
1220
+ _returnOptions: true,
1221
+ });
1222
+ return entity.go(
1223
+ state.getMethod(),
1224
+ paramResults.params,
1225
+ paramResults.options,
1226
+ );
1227
+ } catch (err) {
1228
+ return Promise.reject(err);
1229
+ }
1230
+ },
1231
+ children: [],
1232
+ },
832
1233
  };
833
1234
 
834
1235
  class ChainState {
835
- constructor({index = "", compositeAttributes = {}, attributes = {}, hasSortKey = false, options = {}, parentState = null} = {}) {
836
- const update = new UpdateExpression({prefix: "_u"});
837
- this.parentState = parentState;
838
- this.error = null;
839
- this.attributes = attributes;
840
- this.query = {
841
- collection: "",
842
- index: index,
843
- type: "",
844
- method: "",
845
- facets: { ...compositeAttributes },
846
- update,
847
- updateProxy: new AttributeOperationProxy({
848
- builder: update,
849
- attributes: attributes,
850
- operations: UpdateOperations,
851
- }),
852
- put: {
853
- data: {},
854
- },
855
- upsert: {
856
- data: {},
857
- ifNotExists: {},
858
- },
859
- keys: {
860
- provided: [],
861
- pk: {},
862
- sk: [],
863
- },
864
- filter: {
865
- [ExpressionTypes.ConditionExpression]: new FilterExpression(),
866
- [ExpressionTypes.FilterExpression]: new FilterExpression()
867
- },
868
- options,
869
- };
870
- this.subStates = [];
871
- this.applyAfterOptions = [];
872
- this.hasSortKey = hasSortKey;
873
- this.prev = null;
874
- this.self = null;
875
- this.params = null;
876
- }
1236
+ constructor({
1237
+ index = "",
1238
+ compositeAttributes = {},
1239
+ attributes = {},
1240
+ hasSortKey = false,
1241
+ options = {},
1242
+ parentState = null,
1243
+ } = {}) {
1244
+ const update = new UpdateExpression({ prefix: "_u" });
1245
+ this.parentState = parentState;
1246
+ this.error = null;
1247
+ this.attributes = attributes;
1248
+ this.query = {
1249
+ collection: "",
1250
+ index: index,
1251
+ type: "",
1252
+ method: "",
1253
+ facets: { ...compositeAttributes },
1254
+ update,
1255
+ updateProxy: new AttributeOperationProxy({
1256
+ builder: update,
1257
+ attributes: attributes,
1258
+ operations: UpdateOperations,
1259
+ }),
1260
+ put: {
1261
+ data: {},
1262
+ },
1263
+ upsert: {
1264
+ data: {},
1265
+ indexKey: null,
1266
+ addData(operation = UpsertOperations.set, data = {}) {
1267
+ for (const name of Object.keys(data)) {
1268
+ const value = data[name];
1269
+ this.data[name] = {
1270
+ operation,
1271
+ value,
1272
+ };
1273
+ }
1274
+ },
1275
+ getData(operationFilter) {
1276
+ const results = {};
1277
+ for (const name in this.data) {
1278
+ const { operation, value } = this.data[name];
1279
+ if (!operationFilter || operationFilter === operation) {
1280
+ results[name] = value;
1281
+ }
1282
+ }
1283
+
1284
+ return results;
1285
+ },
1286
+ },
1287
+ keys: {
1288
+ provided: [],
1289
+ pk: {},
1290
+ sk: [],
1291
+ },
1292
+ filter: {
1293
+ [ExpressionTypes.ConditionExpression]: new FilterExpression(),
1294
+ [ExpressionTypes.FilterExpression]: new FilterExpression(),
1295
+ },
1296
+ options,
1297
+ };
1298
+ this.subStates = [];
1299
+ this.hasSortKey = hasSortKey;
1300
+ this.prev = null;
1301
+ this.self = null;
1302
+ this.params = null;
1303
+ this.applyAfterOptions = [];
1304
+ this.beforeBuildParamsOperations = [];
1305
+ this.beforeBuildParamsHasRan = false;
1306
+ }
1307
+
1308
+ getParams() {
1309
+ return this.params;
1310
+ }
1311
+
1312
+ setParams(params) {
1313
+ if (params) {
1314
+ this.params = params;
1315
+ }
1316
+ }
877
1317
 
878
- getParams() {
879
- return this.params;
880
- }
1318
+ init(entity, allClauses, currentClause) {
1319
+ let current = {};
1320
+ for (let child of currentClause.children) {
1321
+ const name = allClauses[child].name;
1322
+ current[name] = (...args) => {
1323
+ this.prev = this.self;
1324
+ this.self = child;
1325
+ let results = allClauses[child].action(entity, this, ...args);
1326
+ if (allClauses[child].children.length) {
1327
+ return this.init(entity, allClauses, allClauses[child]);
1328
+ } else {
1329
+ return results;
1330
+ }
1331
+ };
1332
+ }
1333
+ return current;
1334
+ }
881
1335
 
882
- setParams(params) {
883
- if (params) {
884
- this.params = params;
885
- }
886
- }
1336
+ getMethod() {
1337
+ return this.query.method;
1338
+ }
887
1339
 
888
- init(entity, allClauses, currentClause) {
889
- let current = {};
890
- for (let child of currentClause.children) {
891
- const name = allClauses[child].name;
892
- current[name] = (...args) => {
893
- this.prev = this.self;
894
- this.self = child;
895
- let results = allClauses[child].action(entity, this, ...args);
896
- if (allClauses[child].children.length) {
897
- return this.init(entity, allClauses, allClauses[child]);
898
- } else {
899
- return results;
900
- }
901
- };
902
- }
903
- return current;
904
- }
1340
+ getOptions() {
1341
+ return this.query.options;
1342
+ }
905
1343
 
906
- getMethod() {
907
- return this.query.method;
908
- }
1344
+ addOption(key, value) {
1345
+ this.query.options[key] = value;
1346
+ return this;
1347
+ }
909
1348
 
910
- getOptions() {
911
- return this.query.options;
912
- }
1349
+ _appendProvided(type, attributes) {
1350
+ const newAttributes = Object.keys(attributes).map((attribute) => {
1351
+ return {
1352
+ type,
1353
+ attribute,
1354
+ };
1355
+ });
1356
+ return u.getUnique(this.query.keys.provided, newAttributes);
1357
+ }
913
1358
 
914
- addOption(key, value) {
915
- this.query.options[key] = value;
916
- }
1359
+ setPK(attributes) {
1360
+ this.query.keys.pk = attributes;
1361
+ this.query.keys.provided = this._appendProvided(KeyTypes.pk, attributes);
917
1362
 
918
- _appendProvided(type, attributes) {
919
- const newAttributes = Object.keys(attributes).map(attribute => {
920
- return {
921
- type,
922
- attribute
923
- }
924
- });
925
- return u.getUnique(this.query.keys.provided, newAttributes);
926
- }
1363
+ return this;
1364
+ }
927
1365
 
928
- setPK(attributes) {
929
- this.query.keys.pk = attributes;
930
- this.query.keys.provided = this._appendProvided(KeyTypes.pk, attributes);
1366
+ ifSK(cb) {
1367
+ if (this.hasSortKey) {
1368
+ cb(this);
1369
+ }
1370
+ return this;
1371
+ }
931
1372
 
932
- return this;
933
- }
1373
+ getCompositeAttributes() {
1374
+ return this.query.facets;
1375
+ }
934
1376
 
935
- ifSK(cb) {
936
- if (this.hasSortKey) {
937
- cb(this);
938
- }
939
- return this;
940
- }
1377
+ buildQueryComposites(provided, definition) {
1378
+ return definition
1379
+ .map((name) => [name, provided[name]])
1380
+ .reduce((result, [name, value]) => {
1381
+ if (value !== undefined) {
1382
+ result[name] = value;
1383
+ }
1384
+ return result;
1385
+ }, {});
1386
+ }
941
1387
 
942
- getCompositeAttributes() {
943
- return this.query.facets;
944
- }
1388
+ identifyCompositeAttributes(provided, defined, skip) {
1389
+ // todo: make sure attributes are valid
1390
+ const composites = {};
1391
+ const unused = {};
1392
+ const definedSet = new Set(defined || []);
1393
+ const skipSet = new Set(skip || []);
1394
+ for (const key of Object.keys(provided)) {
1395
+ const value = provided[key];
1396
+ if (definedSet.has(key)) {
1397
+ composites[key] = value;
1398
+ } else if (skipSet.has(key)) {
1399
+ continue;
1400
+ } else {
1401
+ unused[key] = value;
1402
+ }
1403
+ }
945
1404
 
946
- buildQueryComposites(provided, definition) {
947
- return definition
948
- .map(name => [name, provided[name]])
949
- .reduce(
950
- (result, [name, value]) => {
951
- if (value !== undefined) {
952
- result[name] = value;
953
- }
954
- return result;
955
- },
956
- {},
957
- );
958
- }
1405
+ return {
1406
+ composites,
1407
+ unused,
1408
+ };
1409
+ }
959
1410
 
960
- identifyCompositeAttributes(provided, defined, skip) {
961
- // todo: make sure attributes are valid
962
- const composites = {};
963
- const unused = {};
964
- const definedSet = new Set(defined || []);
965
- const skipSet = new Set(skip || []);
966
- for (const key of Object.keys(provided)) {
967
- const value = provided[key];
968
- if (definedSet.has(key)) {
969
- composites[key] = value;
970
- } else if (skipSet.has(key)) {
971
- continue;
972
- } else {
973
- unused[key] = value;
974
- }
975
- }
1411
+ applyFilter(operation, name, ...values) {
1412
+ if (
1413
+ (FilterOperationNames[operation] !== undefined) & (name !== undefined) &&
1414
+ values.length > 0
1415
+ ) {
1416
+ const attribute = this.attributes[name];
1417
+ if (attribute !== undefined) {
1418
+ this.unsafeApplyFilter(operation, attribute.field, ...values);
1419
+ }
1420
+ }
1421
+ return this;
1422
+ }
976
1423
 
977
- return {
978
- composites,
979
- unused,
980
- }
981
- }
1424
+ applyCondition(operation, name, ...values) {
1425
+ if (
1426
+ FilterOperationNames[operation] !== undefined &&
1427
+ name !== undefined &&
1428
+ values.length > 0
1429
+ ) {
1430
+ const attribute = this.attributes[name];
1431
+ if (attribute !== undefined) {
1432
+ const filter = this.query.filter[ExpressionTypes.ConditionExpression];
1433
+ filter.unsafeSet(operation, attribute.field, ...values);
1434
+ }
1435
+ }
1436
+ return this;
1437
+ }
982
1438
 
983
- applyFilter(operation, name, ...values) {
984
- if (FilterOperationNames[operation] !== undefined & name !== undefined && values.length > 0) {
985
- const attribute = this.attributes[name];
986
- if (attribute !== undefined) {
987
- this.unsafeApplyFilter(operation, attribute.field, ...values);
988
- }
989
- }
990
- return this;
991
- }
1439
+ unsafeApplyFilter(operation, name, ...values) {
1440
+ if (
1441
+ (FilterOperationNames[operation] !== undefined) & (name !== undefined) &&
1442
+ values.length > 0
1443
+ ) {
1444
+ const filter = this.query.filter[ExpressionTypes.FilterExpression];
1445
+ filter.unsafeSet(operation, name, ...values);
1446
+ }
1447
+ return this;
1448
+ }
992
1449
 
993
- applyCondition(operation, name, ...values) {
994
- if (FilterOperationNames[operation] !== undefined && name !== undefined && values.length > 0) {
995
- const attribute = this.attributes[name];
996
- if (attribute !== undefined) {
997
- const filter = this.query.filter[ExpressionTypes.ConditionExpression];
998
- filter.unsafeSet(operation, attribute.field, ...values);
999
- }
1000
- }
1001
- return this;
1002
- }
1450
+ filterProperties(operation, obj = {}) {
1451
+ for (const property in obj) {
1452
+ const value = obj[property];
1453
+ if (value !== undefined) {
1454
+ this.applyFilter(operation, property, value);
1455
+ }
1456
+ }
1457
+ return this;
1458
+ }
1003
1459
 
1004
- unsafeApplyFilter(operation, name, ...values) {
1005
- if (FilterOperationNames[operation] !== undefined & name !== undefined && values.length > 0) {
1006
- const filter = this.query.filter[ExpressionTypes.FilterExpression];
1007
- filter.unsafeSet(operation, name, ...values);
1008
- }
1009
- return this;
1010
- }
1460
+ setSK(attributes, type = this.query.type) {
1461
+ if (this.hasSortKey) {
1462
+ this.query.keys.sk.push({
1463
+ type: type,
1464
+ facets: attributes,
1465
+ });
1466
+ this.query.keys.provided = this._appendProvided(KeyTypes.sk, attributes);
1467
+ }
1468
+ return this;
1469
+ }
1011
1470
 
1012
- filterProperties(operation, obj = {}) {
1013
- for (const property in obj) {
1014
- const value = obj[property];
1015
- if (value !== undefined) {
1016
- this.applyFilter(operation, property, value);
1017
- }
1018
- }
1019
- return this;
1020
- }
1471
+ setType(type) {
1472
+ if (!QueryTypes[type]) {
1473
+ throw new Error(`Invalid query type: "${type}"`);
1474
+ }
1475
+ this.query.type = QueryTypes[type];
1476
+ return this;
1477
+ }
1021
1478
 
1022
- setSK(attributes, type = this.query.type) {
1023
- if (this.hasSortKey) {
1024
- this.query.keys.sk.push({
1025
- type: type,
1026
- facets: attributes
1027
- });
1028
- this.query.keys.provided = this._appendProvided(KeyTypes.sk, attributes);
1029
- }
1030
- return this;
1031
- }
1479
+ setMethod(method) {
1480
+ if (!MethodTypes[method]) {
1481
+ throw new Error(`Invalid method type: "${method}"`);
1482
+ }
1483
+ this.query.method = MethodTypes[method];
1484
+ return this;
1485
+ }
1032
1486
 
1033
- setType(type) {
1034
- if (!QueryTypes[type]) {
1035
- throw new Error(`Invalid query type: "${type}"`);
1036
- }
1037
- this.query.type = QueryTypes[type];
1038
- return this;
1039
- }
1487
+ setCollection(collection) {
1488
+ this.query.collection = collection;
1489
+ return this;
1490
+ }
1040
1491
 
1041
- setMethod(method) {
1042
- if (!MethodTypes[method]) {
1043
- throw new Error(`Invalid method type: "${method}"`);
1044
- }
1045
- this.query.method = MethodTypes[method];
1046
- return this;
1047
- }
1492
+ createSubState() {
1493
+ let subState = new ChainState({
1494
+ parentState: this,
1495
+ index: this.query.index,
1496
+ attributes: this.attributes,
1497
+ hasSortKey: this.hasSortKey,
1498
+ options: this.query.options,
1499
+ compositeAttributes: this.query.facets,
1500
+ });
1501
+ this.subStates.push(subState);
1502
+ return subState;
1503
+ }
1048
1504
 
1049
- setCollection(collection) {
1050
- this.query.collection = collection;
1051
- return this;
1052
- }
1505
+ getError() {
1506
+ return this.error;
1507
+ }
1053
1508
 
1054
- createSubState() {
1055
- let subState = new ChainState({
1056
- parentState: this,
1057
- index: this.query.index,
1058
- attributes: this.attributes,
1059
- hasSortKey: this.hasSortKey,
1060
- options: this.query.options,
1061
- compositeAttributes: this.query.facets
1062
- });
1063
- this.subStates.push(subState);
1064
- return subState;
1065
- }
1509
+ setError(err) {
1510
+ this.error = err;
1511
+ if (this.parentState) {
1512
+ this.parentState.setError(err);
1513
+ }
1514
+ }
1066
1515
 
1067
- getError() {
1068
- return this.error;
1069
- }
1516
+ applyUpsert(operation = UpsertOperations.set, data = {}) {
1517
+ this.query.upsert.addData(operation, data);
1518
+ return this;
1519
+ }
1070
1520
 
1071
- setError(err) {
1072
- this.error = err;
1073
- if (this.parentState) {
1074
- this.parentState.setError(err);
1075
- }
1076
- }
1521
+ applyPut(data = {}) {
1522
+ this.query.put.data = { ...this.query.put.data, ...data };
1523
+ return this;
1524
+ }
1077
1525
 
1078
- applyUpsert(data = {}, { ifNotExists } = {}) {
1079
- if (ifNotExists) {
1080
- this.query.upsert.ifNotExists = {...this.query.upsert.ifNotExists, ...data};
1081
- } else {
1082
- this.query.upsert.data = {...this.query.upsert.data, ...data};
1083
- }
1084
- return this;
1085
- }
1526
+ whenOptions(fn) {
1527
+ if (v.isFunction(fn)) {
1528
+ this.applyAfterOptions.push((options) => {
1529
+ fn({ options, state: this });
1530
+ });
1531
+ }
1532
+ }
1086
1533
 
1087
- applyPut(data = {}) {
1088
- this.query.put.data = {...this.query.put.data, ...data};
1089
- return this;
1090
- }
1534
+ applyWithOptions(options = {}) {
1535
+ this.applyAfterOptions.forEach((fn) => fn(options));
1536
+ }
1091
1537
 
1092
- whenOptions(fn) {
1093
- if (v.isFunction(fn)) {
1094
- this.applyAfterOptions.push((options) => {
1095
- fn({ options, state: this });
1096
- });
1097
- }
1098
- }
1538
+ beforeBuildParams(fn) {
1539
+ if (v.isFunction(fn)) {
1540
+ this.beforeBuildParamsOperations.push((options) => {
1541
+ fn({ options, state: this });
1542
+ });
1543
+ }
1544
+ }
1099
1545
 
1100
- applyWithOptions(options = {}) {
1101
- this.applyAfterOptions.forEach((fn) => fn(options));
1102
- }
1546
+ applyBeforeBuildParams(options = {}) {
1547
+ if (!this.beforeBuildParamsHasRan) {
1548
+ this.beforeBuildParamsHasRan = true;
1549
+ this.beforeBuildParamsOperations.forEach((fn) => fn(options));
1550
+ }
1551
+ }
1103
1552
  }
1104
1553
 
1105
1554
  module.exports = {
1106
- clauses,
1107
- ChainState,
1555
+ clauses,
1556
+ ChainState,
1108
1557
  };