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/.prettierignore +112 -0
- package/.prettierrc +11 -0
- package/README.md +55 -47
- package/index.d.ts +5298 -2347
- package/index.js +24 -12
- package/package.json +20 -7
- package/src/clauses.js +1500 -1051
- package/src/client.js +255 -235
- package/src/entity.js +4518 -3730
- package/src/errors.js +39 -28
- package/src/events.js +26 -16
- package/src/filterOperations.js +124 -0
- package/src/filters.js +99 -101
- package/src/operations.js +347 -537
- package/src/schema.js +1825 -1473
- package/src/service.js +1081 -852
- package/src/set.js +22 -26
- package/src/transaction.js +179 -153
- package/src/types.js +263 -258
- package/src/update.js +94 -89
- package/src/updateOperations.js +197 -0
- package/src/util.js +45 -32
- package/src/validations.js +337 -325
- package/src/where.js +146 -124
- package/tsconfig.json +11 -11
package/src/operations.js
CHANGED
|
@@ -1,566 +1,376 @@
|
|
|
1
|
-
const {
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
-
|
|
361
|
-
|
|
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
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
-
|
|
157
|
+
}
|
|
500
158
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
361
|
+
const UpdateOperationNames = Object.keys(UpdateOperations).reduce(
|
|
362
|
+
(ops, name) => {
|
|
562
363
|
ops[name] = name;
|
|
563
364
|
return ops;
|
|
564
|
-
},
|
|
565
|
-
|
|
566
|
-
|
|
365
|
+
},
|
|
366
|
+
{},
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
module.exports = {
|
|
370
|
+
UpdateOperations,
|
|
371
|
+
UpdateOperationNames,
|
|
372
|
+
FilterOperations,
|
|
373
|
+
FilterOperationNames,
|
|
374
|
+
ExpressionState,
|
|
375
|
+
AttributeOperationProxy,
|
|
376
|
+
};
|