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/operations.js CHANGED
@@ -1,313 +1,376 @@
1
- const { AttributeTypes, ItemOperations, AttributeProxySymbol, BuilderTypes} = require("./types");
2
- const { UpdateOperations } = require('./updateOperations');
3
- const { FilterOperations } = require('./filterOperations');
1
+ const {
2
+ AttributeTypes,
3
+ ItemOperations,
4
+ AttributeProxySymbol,
5
+ BuilderTypes,
6
+ } = require("./types");
7
+ const { UpdateOperations } = require("./updateOperations");
8
+ const { FilterOperations } = require("./filterOperations");
4
9
  const e = require("./errors");
5
10
  const u = require("./util");
6
11
 
7
12
  class ExpressionState {
8
- constructor({prefix} = {}) {
9
- this.names = {};
10
- this.values = {};
11
- this.paths = {};
12
- this.counts = {};
13
- this.impacted = {};
14
- this.expression = "";
15
- this.prefix = prefix || "";
16
- this.refs = {};
17
- }
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
+ }
18
23
 
19
- incrementName(name) {
20
- if (this.counts[name] === undefined) {
21
- this.counts[name] = 0;
22
- }
23
- return `${this.prefix}${this.counts[name]++}`;
24
+ incrementName(name) {
25
+ if (this.counts[name] === undefined) {
26
+ this.counts[name] = 0;
24
27
  }
28
+ return `${this.prefix}${this.counts[name]++}`;
29
+ }
25
30
 
26
- // todo: make the structure: name, value, paths
27
- setName(paths, name, value) {
28
- let json = "";
29
- let expression = "";
30
- const prop = `#${name}`;
31
- if (Object.keys(paths).length === 0) {
32
- json = `${name}`;
33
- expression = `${prop}`;
34
- this.names[prop] = value;
35
- } else if (isNaN(name)) {
36
- json = `${paths.json}.${name}`;
37
- expression = `${paths.expression}.${prop}`;
38
- this.names[prop] = value;
39
- } else {
40
- json = `${paths.json}[${name}]`;
41
- expression = `${paths.expression}[${name}]`;
42
- }
43
- return { json, expression, prop };
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}]`;
44
47
  }
48
+ return { json, expression, prop };
49
+ }
45
50
 
46
- getNames() {
47
- return this.names;
48
- }
51
+ getNames() {
52
+ return this.names;
53
+ }
49
54
 
50
- setValue(name, value) {
51
- let valueCount = this.incrementName(name);
52
- let expression = `:${name}${valueCount}`
53
- this.values[expression] = value;
54
- return expression;
55
- }
55
+ setValue(name, value) {
56
+ let valueCount = this.incrementName(name);
57
+ let expression = `:${name}${valueCount}`;
58
+ this.values[expression] = value;
59
+ return expression;
60
+ }
56
61
 
57
- updateValue(name, value) {
58
- this.values[name] = value;
59
- }
62
+ updateValue(name, value) {
63
+ this.values[name] = value;
64
+ }
60
65
 
61
- getValues() {
62
- return this.values;
63
- }
66
+ getValues() {
67
+ return this.values;
68
+ }
64
69
 
65
- setPath(path, value) {
66
- this.paths[path] = value;
67
- }
70
+ setPath(path, value) {
71
+ this.paths[path] = value;
72
+ }
68
73
 
69
- setExpression(expression) {
70
- this.expression = expression;
71
- }
74
+ setExpression(expression) {
75
+ this.expression = expression;
76
+ }
72
77
 
73
- getExpression() {
74
- return this.expression;
75
- }
78
+ getExpression() {
79
+ return this.expression;
80
+ }
76
81
 
77
- setImpacted(operation, path, ref) {
78
- this.impacted[path] = operation;
79
- this.refs[path] = ref;
80
- }
82
+ setImpacted(operation, path, ref) {
83
+ this.impacted[path] = operation;
84
+ this.refs[path] = ref;
85
+ }
81
86
  }
82
87
 
83
88
  class AttributeOperationProxy {
84
- constructor({builder, attributes = {}, operations = {}}) {
85
- this.ref = {
86
- attributes,
87
- operations
88
- };
89
- this.attributes = AttributeOperationProxy.buildAttributes(builder, attributes);
90
- this.operations = AttributeOperationProxy.buildOperations(builder, operations);
91
- }
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
+ }
92
103
 
93
- invokeCallback(op, ...params) {
94
- return op(this.attributes, this.operations, ...params);
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;
111
+ }
112
+ const parts = u.parseJSONPath(path);
113
+ let attribute = this.attributes;
114
+ for (let part of parts) {
115
+ attribute = attribute[part];
95
116
  }
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
+ }
125
+ }
126
+ }
96
127
 
97
- performOperation({operation, path, value, force = false}) {
98
- if (value === undefined) {
99
- return;
100
- }
101
- const parts = u.parseJSONPath(path);
102
- let attribute = this.attributes;
103
- for (let part of parts) {
104
- attribute = attribute[part];
105
- }
106
- if (attribute) {
107
- this.operations[operation](attribute, value);
108
- const {target} = attribute();
109
- if (target.readOnly && !force) {
110
- throw new Error(`Attribute "${target.path}" is Read-Only and cannot be updated`);
111
- }
112
- }
128
+ fromObject(operation, record) {
129
+ for (let path of Object.keys(record)) {
130
+ this.performOperation({
131
+ operation,
132
+ path,
133
+ value: record[path],
134
+ });
113
135
  }
136
+ }
114
137
 
115
- fromObject(operation, record) {
116
- for (let path of Object.keys(record)) {
117
- this.performOperation({
118
- operation,
119
- path,
120
- value: record[path]
121
- });
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
+ );
122
156
  }
157
+ }
123
158
  }
159
+ }
124
160
 
125
- fromArray(operation, paths) {
126
- for (let path of paths) {
127
- const parts = u.parseJSONPath(path);
128
- let attribute = this.attributes;
129
- for (let part of parts) {
130
- attribute = attribute[part];
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
+ );
131
174
  }
132
- if (attribute) {
133
- this.operations[operation](attribute);
134
- const {target} = attribute();
135
- if (target.readOnly) {
136
- throw new Error(`Attribute "${target.path}" is Read-Only and cannot be updated`);
137
- } else if (operation === ItemOperations.remove && target.required) {
138
- throw new Error(`Attribute "${target.path}" is Required and cannot be removed`);
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));
139
186
  }
140
- }
141
- }
142
- }
143
-
144
- static buildOperations(builder, operations) {
145
- let ops = {};
146
- let seen = new Map();
147
- for (let operation of Object.keys(operations)) {
148
- let {template, canNest, rawValue, rawField} = operations[operation];
149
- Object.defineProperty(ops, operation, {
150
- get: () => {
151
- return (property, ...values) => {
152
- if (property === undefined) {
153
- throw new e.ElectroError(e.ErrorCodes.InvalidWhere, `Invalid/Unknown property passed in where clause passed to operation: '${operation}'`);
154
- }
155
- if (property[AttributeProxySymbol]) {
156
- const {commit, target} = property();
157
- const fixedValues = values.map((value) => target.applyFixings(value))
158
- .filter(value => value !== undefined);
159
- const isFilterBuilder = builder.type === BuilderTypes.filter;
160
- const takesValueArgument = template.length > 3;
161
- const isAcceptableValue = fixedValues.every(value => {
162
- const seenAttributes = seen.get(value);
163
- if (seenAttributes) {
164
- return seenAttributes.every(v => target.acceptable(v))
165
- }
166
- return target.acceptable(value);
167
- });
187
+ return target.acceptable(value);
188
+ });
168
189
 
169
- const shouldCommit =
170
- // if it is a filterBuilder than we don't care what they pass because the user needs more freedom here
171
- isFilterBuilder ||
172
- // if the operation does not take a value argument then not committing here could cause problems.
173
- // this should be revisited to make more robust, we could hypothetically store the commit in the
174
- // "seen" map for when the value is used, but that's a lot of new complexity
175
- !takesValueArgument ||
176
- // if the operation takes a value, we should determine if that value is acceptable. For
177
- // example, in the cases of a "set" we check to see if it is empty, or if the value is
178
- // undefined, we should not commit. The "fixedValues" length check is because the
179
- // "fixedValues" array has been filtered for undefined, so no length there indicates an
180
- // undefined value was passed.
181
- (takesValueArgument && isAcceptableValue && fixedValues.length > 0);
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);
182
205
 
183
- if (!shouldCommit) {
184
- return '';
185
- }
206
+ if (!shouldCommit) {
207
+ return "";
208
+ }
186
209
 
187
- const paths = commit();
188
- const attributeValues = [];
189
- let hasNestedValue = false;
190
- for (let fixedValue of fixedValues) {
191
- if (seen.has(fixedValue)) {
192
- attributeValues.push(fixedValue);
193
- hasNestedValue = true;
194
- } else {
195
- let attributeValueName = builder.setValue(target.name, fixedValue);
196
- builder.setPath(paths.json, {
197
- value: fixedValue,
198
- name: attributeValueName
199
- });
200
- attributeValues.push(attributeValueName);
201
- }
202
- }
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;
217
+ } else {
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);
227
+ }
228
+ }
203
229
 
204
- const options = {
205
- nestedValue: hasNestedValue,
206
- createValue: (name, value) => builder.setValue(`${target.name}_${name}`, value),
207
- }
230
+ const options = {
231
+ nestedValue: hasNestedValue,
232
+ createValue: (name, value) =>
233
+ builder.setValue(`${target.name}_${name}`, value),
234
+ };
208
235
 
209
- const formatted = template(options, target, paths.expression, ...attributeValues);
210
- builder.setImpacted(operation, paths.json, target);
211
- if (canNest) {
212
- seen.set(paths.expression, attributeValues);
213
- seen.set(formatted, attributeValues);
214
- }
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
+ }
215
247
 
216
- if (builder.type === BuilderTypes.update && formatted && typeof formatted.operation === "string" && typeof formatted.expression === "string") {
217
- builder.add(formatted.operation, formatted.expression);
218
- return formatted.expression;
219
- }
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
+ }
220
257
 
221
- return formatted;
222
- } else if (rawValue) {
223
- // const {json, expression} = builder.setName({}, property, property);
224
- let attributeValueName = builder.setValue(property, property);
225
- builder.setPath(property, {
226
- value: property,
227
- name: attributeValueName,
228
- });
229
- const formatted = template({}, attributeValueName);
230
- seen.set(attributeValueName, [property]);
231
- seen.set(formatted, [property]);
232
- return formatted;
233
- } else if (rawField) {
234
- const { prop, expression } = builder.setName({}, property, property);
235
- const formatted = template({}, null, prop);
236
- seen.set(expression, [property]);
237
- seen.set(formatted, [property]);
238
- return formatted;
239
- } else {
240
- throw new e.ElectroError(e.ErrorCodes.InvalidWhere, `Invalid Attribute in where clause passed to operation '${operation}'. Use injected attributes only.`);
241
- }
242
- }
243
- }
244
- });
245
- }
246
- return ops;
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
+ );
285
+ }
286
+ };
287
+ },
288
+ });
247
289
  }
290
+ return ops;
291
+ }
248
292
 
249
- static pathProxy(build) {
250
- return new Proxy(() => build(), {
251
- get: (_, prop, o) => {
252
- if (prop === AttributeProxySymbol) {
253
- return true;
254
- } else {
255
- return AttributeOperationProxy.pathProxy(() => {
256
- const { commit, root, target, builder } = build();
257
- const attribute = target.getChild(prop);
258
- let field;
259
- if (attribute === undefined) {
260
- throw new Error(`Invalid attribute "${prop}" at path "${target.path}.${prop}"`);
261
- } else if (attribute === root && attribute.type === AttributeTypes.any) {
262
- // 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
263
- field = prop;
264
- } else {
265
- field = attribute.field;
266
- }
267
-
268
- return {
269
- root,
270
- builder,
271
- target: attribute,
272
- commit: () => {
273
- const paths = commit();
274
- return builder.setName(paths, prop, field);
275
- },
276
- }
277
- });
278
- }
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;
279
315
  }
280
- });
281
- }
282
316
 
283
- static buildAttributes(builder, attributes) {
284
- let attr = {};
285
- for (let [name, attribute] of Object.entries(attributes)) {
286
- Object.defineProperty(attr, name, {
287
- get: () => {
288
- return AttributeOperationProxy.pathProxy(() => {
289
- return {
290
- root: attribute,
291
- target: attribute,
292
- builder,
293
- commit: () => builder.setName({}, attribute.name, attribute.field)
294
- }
295
- });
296
- }
297
- });
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
+ });
298
327
  }
299
- 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
+ });
300
348
  }
349
+ return attr;
350
+ }
301
351
  }
302
352
 
303
- const FilterOperationNames = Object.keys(FilterOperations).reduce((ops, name) => {
353
+ const FilterOperationNames = Object.keys(FilterOperations).reduce(
354
+ (ops, name) => {
304
355
  ops[name] = name;
305
356
  return ops;
306
- }, {});
357
+ },
358
+ {},
359
+ );
307
360
 
308
- const UpdateOperationNames = Object.keys(UpdateOperations).reduce((ops, name) => {
361
+ const UpdateOperationNames = Object.keys(UpdateOperations).reduce(
362
+ (ops, name) => {
309
363
  ops[name] = name;
310
364
  return ops;
311
- }, {});
365
+ },
366
+ {},
367
+ );
312
368
 
313
- module.exports = {UpdateOperations, UpdateOperationNames, FilterOperations, FilterOperationNames, ExpressionState, AttributeOperationProxy};
369
+ module.exports = {
370
+ UpdateOperations,
371
+ UpdateOperationNames,
372
+ FilterOperations,
373
+ FilterOperationNames,
374
+ ExpressionState,
375
+ AttributeOperationProxy,
376
+ };