electrodb 2.10.0 → 2.10.2

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