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/operations.js CHANGED
@@ -1,566 +1,376 @@
1
- const {AttributeTypes, ItemOperations, AttributeProxySymbol, BuilderTypes, DynamoDBAttributeTypes} = require("./types");
1
+ const {
2
+ AttributeTypes,
3
+ ItemOperations,
4
+ AttributeProxySymbol,
5
+ BuilderTypes,
6
+ } = require("./types");
7
+ const { UpdateOperations } = require("./updateOperations");
8
+ const { FilterOperations } = require("./filterOperations");
2
9
  const e = require("./errors");
3
10
  const u = require("./util");
4
11
 
5
- const deleteOperations = {
6
- canNest: false,
7
- template: function del(options, attr, path, value) {
8
- let operation = "";
9
- let expression = "";
10
- switch(attr.type) {
11
- case AttributeTypes.any:
12
- case AttributeTypes.set:
13
- operation = ItemOperations.delete;
14
- expression = `${path} ${value}`;
15
- break;
16
- default:
17
- throw new Error(`Invalid Update Attribute Operation: "DELETE" Operation can only be performed on attributes with type "set" or "any".`);
18
- }
19
- return {operation, expression};
20
- },
21
- };
22
-
23
- const UpdateOperations = {
24
- ifNotExists: {
25
- template: function if_not_exists(options, attr, path, value) {
26
- const operation = ItemOperations.set;
27
- const expression = `${path} = if_not_exists(${path}, ${value})`;
28
- return {operation, expression};
29
- }
30
- },
31
- name: {
32
- canNest: true,
33
- template: function name(options, attr, path) {
34
- return path;
35
- }
36
- },
37
- value: {
38
- canNest: true,
39
- template: function value(options, attr, path, value) {
40
- return value;
41
- }
42
- },
43
- append: {
44
- canNest: false,
45
- template: function append(options, attr, path, value) {
46
- let operation = "";
47
- let expression = "";
48
- switch(attr.type) {
49
- case AttributeTypes.any:
50
- case AttributeTypes.list:
51
- operation = ItemOperations.set;
52
- expression = `${path} = list_append(${path}, ${value})`;
53
- break;
54
- default:
55
- throw new Error(`Invalid Update Attribute Operation: "APPEND" Operation can only be performed on attributes with type "list" or "any".`);
56
- }
57
- return {operation, expression};
58
- }
59
- },
60
- add: {
61
- canNest: false,
62
- template: function add(options, attr, path, value) {
63
- let operation = "";
64
- let expression = "";
65
- let type = attr.type;
66
- if (type === AttributeTypes.any) {
67
- type = typeof value === 'number'
68
- ? AttributeTypes.number
69
- : AttributeTypes.any;
70
- }
71
- switch(type) {
72
- case AttributeTypes.any:
73
- case AttributeTypes.set:
74
- operation = ItemOperations.add;
75
- expression = `${path} ${value}`;
76
- break;
77
- case AttributeTypes.number:
78
- if (options.nestedValue) {
79
- operation = ItemOperations.set;
80
- expression = `${path} = ${path} + ${value}`;
81
- } else {
82
- operation = ItemOperations.add;
83
- expression = `${path} ${value}`;
84
- }
85
- break;
86
- default:
87
- throw new Error(`Invalid Update Attribute Operation: "ADD" Operation can only be performed on attributes with type "number", "set", or "any".`);
88
- }
89
- return {operation, expression};
90
- }
91
- },
92
- subtract: {
93
- canNest: false,
94
- template: function subtract(options, attr, path, value) {
95
- let operation = "";
96
- let expression = "";
97
- switch(attr.type) {
98
- case AttributeTypes.any:
99
- case AttributeTypes.number:
100
- operation = ItemOperations.set;
101
- expression = `${path} = ${path} - ${value}`;
102
- break;
103
- default:
104
- throw new Error(`Invalid Update Attribute Operation: "SUBTRACT" Operation can only be performed on attributes with type "number" or "any".`);
105
- }
106
-
107
- return {operation, expression};
108
- }
109
- },
110
- set: {
111
- canNest: false,
112
- template: function set(options, attr, path, value) {
113
- let operation = "";
114
- let expression = "";
115
- switch(attr.type) {
116
- case AttributeTypes.set:
117
- case AttributeTypes.list:
118
- case AttributeTypes.map:
119
- case AttributeTypes.enum:
120
- case AttributeTypes.string:
121
- case AttributeTypes.number:
122
- case AttributeTypes.boolean:
123
- case AttributeTypes.any:
124
- operation = ItemOperations.set;
125
- expression = `${path} = ${value}`;
126
- break;
127
- default:
128
- throw new Error(`Invalid Update Attribute Operation: "SET" Operation can only be performed on attributes with type "list", "map", "string", "number", "boolean", or "any".`);
129
- }
130
- return {operation, expression};
131
- }
132
- },
133
- remove: {
134
- canNest: false,
135
- template: function remove(options, attr, ...paths) {
136
- let operation = "";
137
- let expression = "";
138
- switch(attr.type) {
139
- case AttributeTypes.set:
140
- case AttributeTypes.any:
141
- case AttributeTypes.list:
142
- case AttributeTypes.map:
143
- case AttributeTypes.string:
144
- case AttributeTypes.number:
145
- case AttributeTypes.boolean:
146
- case AttributeTypes.enum:
147
- operation = ItemOperations.remove;
148
- expression = paths.join(", ");
149
- break;
150
- default: {
151
- throw new Error(`Invalid Update Attribute Operation: "REMOVE" Operation can only be performed on attributes with type "map", "list", "string", "number", "boolean", or "any".`);
152
- }
153
- }
154
- return {operation, expression};
155
- }
156
- },
157
- del: deleteOperations,
158
- delete: deleteOperations
159
- }
160
-
161
- const FilterOperations = {
162
- escape: {
163
- template: function escape(options, attr) {
164
- return `${attr}`;
165
- },
166
- noAttribute: true,
167
- },
168
- size: {
169
- template: function size(options, attr, name) {
170
- return `size(${name})`
171
- },
172
- strict: false,
173
- },
174
- type: {
175
- template: function attributeType(options, attr, name, value) {
176
- return `attribute_type(${name}, ${value})`;
177
- },
178
- strict: false
179
- },
180
- ne: {
181
- template: function ne(options, attr, name, value) {
182
- return `${name} <> ${value}`;
183
- },
184
- strict: false,
185
- },
186
- eq: {
187
- template: function eq(options, attr, name, value) {
188
- return `${name} = ${value}`;
189
- },
190
- strict: false,
191
- },
192
- gt: {
193
- template: function gt(options, attr, name, value) {
194
- return `${name} > ${value}`;
195
- },
196
- strict: false
197
- },
198
- lt: {
199
- template: function lt(options, attr, name, value) {
200
- return `${name} < ${value}`;
201
- },
202
- strict: false
203
- },
204
- gte: {
205
- template: function gte(options, attr, name, value) {
206
- return `${name} >= ${value}`;
207
- },
208
- strict: false
209
- },
210
- lte: {
211
- template: function lte(options, attr, name, value) {
212
- return `${name} <= ${value}`;
213
- },
214
- strict: false
215
- },
216
- between: {
217
- template: function between(options, attr, name, value1, value2) {
218
- return `(${name} between ${value1} and ${value2})`;
219
- },
220
- strict: false
221
- },
222
- begins: {
223
- template: function begins(options, attr, name, value) {
224
- return `begins_with(${name}, ${value})`;
225
- },
226
- strict: false
227
- },
228
- exists: {
229
- template: function exists(options, attr, name) {
230
- return `attribute_exists(${name})`;
231
- },
232
- strict: false
233
- },
234
- notExists: {
235
- template: function notExists(options, attr, name) {
236
- return `attribute_not_exists(${name})`;
237
- },
238
- strict: false
239
- },
240
- contains: {
241
- template: function contains(options, attr, name, value) {
242
- return `contains(${name}, ${value})`;
243
- },
244
- strict: false
245
- },
246
- notContains: {
247
- template: function notContains(options, attr, name, value) {
248
- return `not contains(${name}, ${value})`;
249
- },
250
- strict: false
251
- },
252
- value: {
253
- template: function(options, attr, name, value) {
254
- return value;
255
- },
256
- strict: false,
257
- canNest: true,
258
- },
259
- name: {
260
- template: function(options, attr, name) {
261
- return name;
262
- },
263
- strict: false,
264
- canNest: true,
265
- },
266
- eqOrNotExists: {
267
- template: function eq(options, attr, name, value) {
268
- return `(${name} = ${value} OR attribute_not_exists(${name}))`;
269
- },
270
- strict: false,
271
- }
272
- };
273
-
274
12
  class ExpressionState {
275
- constructor({prefix} = {}) {
276
- this.names = {};
277
- this.values = {};
278
- this.paths = {};
279
- this.counts = {};
280
- this.impacted = {};
281
- this.expression = "";
282
- this.prefix = prefix || "";
283
- this.refs = {};
284
- }
285
-
286
- incrementName(name) {
287
- if (this.counts[name] === undefined) {
288
- this.counts[name] = 0;
289
- }
290
- return `${this.prefix}${this.counts[name]++}`;
291
- }
292
-
293
- // todo: make the structure: name, value, paths
294
- setName(paths, name, value) {
295
- let json = "";
296
- let expression = "";
297
- const prop = `#${name}`;
298
- if (Object.keys(paths).length === 0) {
299
- json = `${name}`;
300
- expression = `${prop}`;
301
- this.names[prop] = value;
302
- } else if (isNaN(name)) {
303
- json = `${paths.json}.${name}`;
304
- expression = `${paths.expression}.${prop}`;
305
- this.names[prop] = value;
306
- } else {
307
- json = `${paths.json}[*]`;
308
- expression = `${paths.expression}[${name}]`;
309
- }
310
- return {json, expression, prop};
13
+ constructor({ prefix } = {}) {
14
+ this.names = {};
15
+ this.values = {};
16
+ this.paths = {};
17
+ this.counts = {};
18
+ this.impacted = {};
19
+ this.expression = "";
20
+ this.prefix = prefix || "";
21
+ this.refs = {};
22
+ }
23
+
24
+ incrementName(name) {
25
+ if (this.counts[name] === undefined) {
26
+ this.counts[name] = 0;
311
27
  }
312
-
313
- getNames() {
314
- return this.names;
315
- }
316
-
317
- setValue(name, value) {
318
- let valueCount = this.incrementName(name);
319
- let expression = `:${name}${valueCount}`
320
- this.values[expression] = value;
321
- return expression;
322
- }
323
-
324
- updateValue(name, value) {
325
- this.values[name] = value;
326
- }
327
-
328
- getValues() {
329
- return this.values;
330
- }
331
-
332
- setPath(path, value) {
333
- this.paths[path] = value;
334
- }
335
-
336
- setExpression(expression) {
337
- this.expression = expression;
338
- }
339
-
340
- getExpression() {
341
- return this.expression;
342
- }
343
-
344
- setImpacted(operation, path, ref) {
345
- this.impacted[path] = operation;
346
- this.refs[path] = ref;
28
+ return `${this.prefix}${this.counts[name]++}`;
29
+ }
30
+
31
+ // todo: make the structure: name, value, paths
32
+ setName(paths, name, value) {
33
+ let json = "";
34
+ let expression = "";
35
+ const prop = `#${name}`;
36
+ if (Object.keys(paths).length === 0) {
37
+ json = `${name}`;
38
+ expression = `${prop}`;
39
+ this.names[prop] = value;
40
+ } else if (isNaN(name)) {
41
+ json = `${paths.json}.${name}`;
42
+ expression = `${paths.expression}.${prop}`;
43
+ this.names[prop] = value;
44
+ } else {
45
+ json = `${paths.json}[${name}]`;
46
+ expression = `${paths.expression}[${name}]`;
347
47
  }
48
+ return { json, expression, prop };
49
+ }
50
+
51
+ getNames() {
52
+ return this.names;
53
+ }
54
+
55
+ setValue(name, value) {
56
+ let valueCount = this.incrementName(name);
57
+ let expression = `:${name}${valueCount}`;
58
+ this.values[expression] = value;
59
+ return expression;
60
+ }
61
+
62
+ updateValue(name, value) {
63
+ this.values[name] = value;
64
+ }
65
+
66
+ getValues() {
67
+ return this.values;
68
+ }
69
+
70
+ setPath(path, value) {
71
+ this.paths[path] = value;
72
+ }
73
+
74
+ setExpression(expression) {
75
+ this.expression = expression;
76
+ }
77
+
78
+ getExpression() {
79
+ return this.expression;
80
+ }
81
+
82
+ setImpacted(operation, path, ref) {
83
+ this.impacted[path] = operation;
84
+ this.refs[path] = ref;
85
+ }
348
86
  }
349
87
 
350
88
  class AttributeOperationProxy {
351
- constructor({builder, attributes = {}, operations = {}}) {
352
- this.ref = {
353
- attributes,
354
- operations
355
- };
356
- this.attributes = AttributeOperationProxy.buildAttributes(builder, attributes);
357
- this.operations = AttributeOperationProxy.buildOperations(builder, operations);
89
+ constructor({ builder, attributes = {}, operations = {} }) {
90
+ this.ref = {
91
+ attributes,
92
+ operations,
93
+ };
94
+ this.attributes = AttributeOperationProxy.buildAttributes(
95
+ builder,
96
+ attributes,
97
+ );
98
+ this.operations = AttributeOperationProxy.buildOperations(
99
+ builder,
100
+ operations,
101
+ );
102
+ }
103
+
104
+ invokeCallback(op, ...params) {
105
+ return op(this.attributes, this.operations, ...params);
106
+ }
107
+
108
+ performOperation({ operation, path, value, force = false }) {
109
+ if (value === undefined) {
110
+ return;
358
111
  }
359
-
360
- invokeCallback(op, ...params) {
361
- return op(this.attributes, this.operations, ...params);
112
+ const parts = u.parseJSONPath(path);
113
+ let attribute = this.attributes;
114
+ for (let part of parts) {
115
+ attribute = attribute[part];
362
116
  }
363
-
364
- fromObject(operation, record) {
365
- for (let path of Object.keys(record)) {
366
- if (record[path] === undefined) {
367
- continue;
368
- }
369
- const value = record[path];
370
- const parts = u.parseJSONPath(path);
371
- let attribute = this.attributes;
372
- for (let part of parts) {
373
- attribute = attribute[part];
374
- }
375
- if (attribute) {
376
- this.operations[operation](attribute, value);
377
- const {target} = attribute();
378
- if (target.readOnly) {
379
- throw new Error(`Attribute "${target.path}" is Read-Only and cannot be updated`);
380
- }
381
- }
382
- }
117
+ if (attribute) {
118
+ this.operations[operation](attribute, value);
119
+ const { target } = attribute();
120
+ if (target.readOnly && !force) {
121
+ throw new Error(
122
+ `Attribute "${target.path}" is Read-Only and cannot be updated`,
123
+ );
124
+ }
383
125
  }
384
-
385
- fromArray(operation, paths) {
386
- for (let path of paths) {
387
- const parts = u.parseJSONPath(path);
388
- let attribute = this.attributes;
389
- for (let part of parts) {
390
- attribute = attribute[part];
391
- }
392
- if (attribute) {
393
- this.operations[operation](attribute);
394
- const {target} = attribute();
395
- if (target.readOnly) {
396
- throw new Error(`Attribute "${target.path}" is Read-Only and cannot be updated`);
397
- } else if (operation === ItemOperations.remove && target.required) {
398
- throw new Error(`Attribute "${target.path}" is Required and cannot be removed`);
399
- }
400
- }
401
- }
126
+ }
127
+
128
+ fromObject(operation, record) {
129
+ for (let path of Object.keys(record)) {
130
+ this.performOperation({
131
+ operation,
132
+ path,
133
+ value: record[path],
134
+ });
402
135
  }
403
-
404
- static buildOperations(builder, operations) {
405
- let ops = {};
406
- let seen = new Map();
407
- for (let operation of Object.keys(operations)) {
408
- let {template, canNest, noAttribute} = operations[operation];
409
- Object.defineProperty(ops, operation, {
410
- get: () => {
411
- return (property, ...values) => {
412
- if (property === undefined) {
413
- throw new e.ElectroError(e.ErrorCodes.InvalidWhere, `Invalid/Unknown property passed in where clause passed to operation: '${operation}'`);
414
- }
415
- if (property[AttributeProxySymbol]) {
416
- const {commit, target} = property();
417
- const fixedValues = values.map((value) => target.applyFixings(value))
418
- .filter(value => value !== undefined);
419
- const isFilterBuilder = builder.type === BuilderTypes.filter;
420
- const takesValueArgument = template.length > 3;
421
- const isAcceptableValue = fixedValues.every(value => {
422
- const seenAttributes = seen.get(value);
423
- if (seenAttributes) {
424
- return seenAttributes.every(v => target.acceptable(v))
425
- }
426
- return target.acceptable(value);
427
- });
428
-
429
- const shouldCommit =
430
- // if it is a filterBuilder than we don't care what they pass because the user needs more freedom here
431
- isFilterBuilder ||
432
- // if the operation does not take a value argument then not committing here could cause problems.
433
- // this should be revisited to make more robust, we could hypothetically store the commit in the
434
- // "seen" map for when the value is used, but that's a lot of new complexity
435
- !takesValueArgument ||
436
- // if the operation takes a value, we should determine if that value is acceptable. For
437
- // example, in the cases of a "set" we check to see if it is empty, or if the value is
438
- // undefined, we should not commit. The "fixedValues" length check is because the
439
- // "fixedValues" array has been filtered for undefined, so no length there indicates an
440
- // undefined value was passed.
441
- (takesValueArgument && isAcceptableValue && fixedValues.length > 0);
442
-
443
- if (!shouldCommit) {
444
- return '';
445
- }
446
-
447
- const paths = commit();
448
- const attributeValues = [];
449
- let hasNestedValue = false;
450
- for (let fixedValue of fixedValues) {
451
- if (seen.has(fixedValue)) {
452
- attributeValues.push(fixedValue);
453
- hasNestedValue = true;
454
- } else {
455
- let attributeValueName = builder.setValue(target.name, fixedValue);
456
- builder.setPath(paths.json, {
457
- value: fixedValue,
458
- name: attributeValueName
459
- });
460
- attributeValues.push(attributeValueName);
461
- }
462
- }
463
-
464
- const options = {
465
- nestedValue: hasNestedValue
466
- }
467
-
468
- const formatted = template(options, target, paths.expression, ...attributeValues);
469
- builder.setImpacted(operation, paths.json, target);
470
- if (canNest) {
471
- seen.set(paths.expression, attributeValues);
472
- seen.set(formatted, attributeValues);
473
- }
474
-
475
- if (builder.type === BuilderTypes.update && formatted && typeof formatted.operation === "string" && typeof formatted.expression === "string") {
476
- builder.add(formatted.operation, formatted.expression);
477
- return formatted.expression;
478
- }
479
-
480
- return formatted;
481
- } else if (noAttribute) {
482
- // const {json, expression} = builder.setName({}, property, property);
483
- let attributeValueName = builder.setValue(property, property);
484
- builder.setPath(property, {
485
- value: property,
486
- name: attributeValueName,
487
- });
488
- const formatted = template({}, attributeValueName);
489
- seen.set(attributeValueName, [property]);
490
- seen.set(formatted, [property]);
491
- return formatted;
492
- } else {
493
- throw new e.ElectroError(e.ErrorCodes.InvalidWhere, `Invalid Attribute in where clause passed to operation '${operation}'. Use injected attributes only.`);
494
- }
495
- }
496
- }
497
- });
136
+ }
137
+
138
+ fromArray(operation, paths) {
139
+ for (let path of paths) {
140
+ const parts = u.parseJSONPath(path);
141
+ let attribute = this.attributes;
142
+ for (let part of parts) {
143
+ attribute = attribute[part];
144
+ }
145
+ if (attribute) {
146
+ this.operations[operation](attribute);
147
+ const { target } = attribute();
148
+ if (target.readOnly) {
149
+ throw new Error(
150
+ `Attribute "${target.path}" is Read-Only and cannot be updated`,
151
+ );
152
+ } else if (operation === ItemOperations.remove && target.required) {
153
+ throw new Error(
154
+ `Attribute "${target.path}" is Required and cannot be removed`,
155
+ );
498
156
  }
499
- return ops;
157
+ }
500
158
  }
501
-
502
- static pathProxy(build) {
503
- return new Proxy(() => build(), {
504
- get: (_, prop, o) => {
505
- if (prop === AttributeProxySymbol) {
506
- return true;
159
+ }
160
+
161
+ static buildOperations(builder, operations) {
162
+ let ops = {};
163
+ let seen = new Map();
164
+ for (let operation of Object.keys(operations)) {
165
+ let { template, canNest, rawValue, rawField } = operations[operation];
166
+ Object.defineProperty(ops, operation, {
167
+ get: () => {
168
+ return (property, ...values) => {
169
+ if (property === undefined) {
170
+ throw new e.ElectroError(
171
+ e.ErrorCodes.InvalidWhere,
172
+ `Invalid/Unknown property passed in where clause passed to operation: '${operation}'`,
173
+ );
174
+ }
175
+ if (property[AttributeProxySymbol]) {
176
+ const { commit, target } = property();
177
+ const fixedValues = values
178
+ .map((value) => target.applyFixings(value))
179
+ .filter((value) => value !== undefined);
180
+ const isFilterBuilder = builder.type === BuilderTypes.filter;
181
+ const takesValueArgument = template.length > 3;
182
+ const isAcceptableValue = fixedValues.every((value) => {
183
+ const seenAttributes = seen.get(value);
184
+ if (seenAttributes) {
185
+ return seenAttributes.every((v) => target.acceptable(v));
186
+ }
187
+ return target.acceptable(value);
188
+ });
189
+
190
+ const shouldCommit =
191
+ // if it is a filterBuilder than we don't care what they pass because the user needs more freedom here
192
+ isFilterBuilder ||
193
+ // if the operation does not take a value argument then not committing here could cause problems.
194
+ // this should be revisited to make more robust, we could hypothetically store the commit in the
195
+ // "seen" map for when the value is used, but that's a lot of new complexity
196
+ !takesValueArgument ||
197
+ // if the operation takes a value, we should determine if that value is acceptable. For
198
+ // example, in the cases of a "set" we check to see if it is empty, or if the value is
199
+ // undefined, we should not commit. The "fixedValues" length check is because the
200
+ // "fixedValues" array has been filtered for undefined, so no length there indicates an
201
+ // undefined value was passed.
202
+ (takesValueArgument &&
203
+ isAcceptableValue &&
204
+ fixedValues.length > 0);
205
+
206
+ if (!shouldCommit) {
207
+ return "";
208
+ }
209
+
210
+ const paths = commit();
211
+ const attributeValues = [];
212
+ let hasNestedValue = false;
213
+ for (let fixedValue of fixedValues) {
214
+ if (seen.has(fixedValue)) {
215
+ attributeValues.push(fixedValue);
216
+ hasNestedValue = true;
507
217
  } else {
508
- return AttributeOperationProxy.pathProxy(() => {
509
- const { commit, root, target, builder } = build();
510
- const attribute = target.getChild(prop);
511
- let field;
512
- if (attribute === undefined) {
513
- throw new Error(`Invalid attribute "${prop}" at path "${target.path}.${prop}"`);
514
- } else if (attribute === root && attribute.type === AttributeTypes.any) {
515
- // This function is only called if a nested property is called. If this attribute is ultimately the root, don't use the root's field name
516
- field = prop;
517
- } else {
518
- field = attribute.field;
519
- }
520
-
521
- return {
522
- root,
523
- builder,
524
- target: attribute,
525
- commit: () => {
526
- const paths = commit();
527
- return builder.setName(paths, prop, field);
528
- },
529
- }
530
- });
218
+ let attributeValueName = builder.setValue(
219
+ target.name,
220
+ fixedValue,
221
+ );
222
+ builder.setPath(paths.json, {
223
+ value: fixedValue,
224
+ name: attributeValueName,
225
+ });
226
+ attributeValues.push(attributeValueName);
531
227
  }
228
+ }
229
+
230
+ const options = {
231
+ nestedValue: hasNestedValue,
232
+ createValue: (name, value) =>
233
+ builder.setValue(`${target.name}_${name}`, value),
234
+ };
235
+
236
+ const formatted = template(
237
+ options,
238
+ target,
239
+ paths.expression,
240
+ ...attributeValues,
241
+ );
242
+ builder.setImpacted(operation, paths.json, target);
243
+ if (canNest) {
244
+ seen.set(paths.expression, attributeValues);
245
+ seen.set(formatted, attributeValues);
246
+ }
247
+
248
+ if (
249
+ builder.type === BuilderTypes.update &&
250
+ formatted &&
251
+ typeof formatted.operation === "string" &&
252
+ typeof formatted.expression === "string"
253
+ ) {
254
+ builder.add(formatted.operation, formatted.expression);
255
+ return formatted.expression;
256
+ }
257
+
258
+ return formatted;
259
+ } else if (rawValue) {
260
+ // const {json, expression} = builder.setName({}, property, property);
261
+ let attributeValueName = builder.setValue(property, property);
262
+ builder.setPath(property, {
263
+ value: property,
264
+ name: attributeValueName,
265
+ });
266
+ const formatted = template({}, attributeValueName);
267
+ seen.set(attributeValueName, [property]);
268
+ seen.set(formatted, [property]);
269
+ return formatted;
270
+ } else if (rawField) {
271
+ const { prop, expression } = builder.setName(
272
+ {},
273
+ property,
274
+ property,
275
+ );
276
+ const formatted = template({}, null, prop);
277
+ seen.set(expression, [property]);
278
+ seen.set(formatted, [property]);
279
+ return formatted;
280
+ } else {
281
+ throw new e.ElectroError(
282
+ e.ErrorCodes.InvalidWhere,
283
+ `Invalid Attribute in where clause passed to operation '${operation}'. Use injected attributes only.`,
284
+ );
532
285
  }
533
- });
286
+ };
287
+ },
288
+ });
534
289
  }
290
+ return ops;
291
+ }
535
292
 
536
- static buildAttributes(builder, attributes) {
537
- let attr = {};
538
- for (let [name, attribute] of Object.entries(attributes)) {
539
- Object.defineProperty(attr, name, {
540
- get: () => {
541
- return AttributeOperationProxy.pathProxy(() => {
542
- return {
543
- root: attribute,
544
- target: attribute,
545
- builder,
546
- commit: () => builder.setName({}, attribute.name, attribute.field)
547
- }
548
- });
549
- }
550
- });
293
+ static pathProxy(build) {
294
+ return new Proxy(() => build(), {
295
+ get: (_, prop, o) => {
296
+ if (prop === AttributeProxySymbol) {
297
+ return true;
298
+ } else {
299
+ return AttributeOperationProxy.pathProxy(() => {
300
+ const { commit, root, target, builder } = build();
301
+ const attribute = target.getChild(prop);
302
+ let field;
303
+ if (attribute === undefined) {
304
+ throw new Error(
305
+ `Invalid attribute "${prop}" at path "${target.path}.${prop}"`,
306
+ );
307
+ } else if (
308
+ attribute === root &&
309
+ attribute.type === AttributeTypes.any
310
+ ) {
311
+ // This function is only called if a nested property is called. If this attribute is ultimately the root, don't use the root's field name
312
+ field = prop;
313
+ } else {
314
+ field = attribute.field;
315
+ }
316
+
317
+ return {
318
+ root,
319
+ builder,
320
+ target: attribute,
321
+ commit: () => {
322
+ const paths = commit();
323
+ return builder.setName(paths, prop, field);
324
+ },
325
+ };
326
+ });
551
327
  }
552
- return attr;
328
+ },
329
+ });
330
+ }
331
+
332
+ static buildAttributes(builder, attributes) {
333
+ let attr = {};
334
+ for (let [name, attribute] of Object.entries(attributes)) {
335
+ Object.defineProperty(attr, name, {
336
+ get: () => {
337
+ return AttributeOperationProxy.pathProxy(() => {
338
+ return {
339
+ root: attribute,
340
+ target: attribute,
341
+ builder,
342
+ commit: () =>
343
+ builder.setName({}, attribute.name, attribute.field),
344
+ };
345
+ });
346
+ },
347
+ });
553
348
  }
349
+ return attr;
350
+ }
554
351
  }
555
352
 
556
- const FilterOperationNames = Object.keys(FilterOperations).reduce((ops, name) => {
353
+ const FilterOperationNames = Object.keys(FilterOperations).reduce(
354
+ (ops, name) => {
557
355
  ops[name] = name;
558
356
  return ops;
559
- }, {});
357
+ },
358
+ {},
359
+ );
560
360
 
561
- const UpdateOperationNames = Object.keys(UpdateOperations).reduce((ops, name) => {
361
+ const UpdateOperationNames = Object.keys(UpdateOperations).reduce(
362
+ (ops, name) => {
562
363
  ops[name] = name;
563
364
  return ops;
564
- }, {});
565
-
566
- module.exports = {UpdateOperations, UpdateOperationNames, FilterOperations, FilterOperationNames, ExpressionState, AttributeOperationProxy};
365
+ },
366
+ {},
367
+ );
368
+
369
+ module.exports = {
370
+ UpdateOperations,
371
+ UpdateOperationNames,
372
+ FilterOperations,
373
+ FilterOperationNames,
374
+ ExpressionState,
375
+ AttributeOperationProxy,
376
+ };