json-fs-query 1.1.0 → 2.0.0
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/LICENSE +674 -674
- package/README.md +295 -98
- package/browser/jsonFsQuery.js +12733 -10934
- package/browser/jsonFsQuery.min.js +1 -1
- package/dist/data/introspect/create.d.ts +1 -1
- package/dist/data/introspect/create.js +35 -4
- package/dist/data/introspect/create.min.js +1 -1
- package/dist/data/introspect/definitions/field.d.ts +6 -2
- package/dist/data/introspect/definitions/field.js +7 -1
- package/dist/data/introspect/definitions/field.min.js +1 -1
- package/dist/data/introspect/index.d.ts +6 -3
- package/dist/data/introspect/index.js +10 -19
- package/dist/data/introspect/index.min.js +1 -1
- package/dist/data/introspect/policies/foreignKeyExists.js +28 -28
- package/dist/data/introspect/policies/hasDependents.js +53 -53
- package/dist/data/introspect/policies/keyUnique.js +34 -34
- package/dist/data/introspect/policies/typeCheck.d.ts +9 -1
- package/dist/data/introspect/policies/typeCheck.js +16 -2
- package/dist/data/introspect/policies/typeCheck.min.js +1 -1
- package/dist/data/queries/createRecord.d.ts +11 -0
- package/dist/data/queries/createRecord.js +58 -0
- package/dist/data/queries/createRecord.min.js +1 -0
- package/dist/data/queries/deleteEntity.js +100 -100
- package/dist/data/queries/describeRecord.d.ts +9 -0
- package/dist/data/queries/describeRecord.js +29 -0
- package/dist/data/queries/describeRecord.min.js +1 -0
- package/dist/data/queries/dropRecord.d.ts +9 -0
- package/dist/data/queries/dropRecord.js +61 -0
- package/dist/data/queries/dropRecord.min.js +1 -0
- package/dist/data/queries/insertEntity.js +122 -117
- package/dist/data/queries/insertEntity.min.js +1 -1
- package/dist/data/queries/parser.d.ts +1 -1
- package/dist/data/queries/parser.js +7 -15
- package/dist/data/queries/parser.min.js +1 -1
- package/dist/data/queries/query.d.ts +6 -0
- package/dist/data/queries/query.js +15 -6
- package/dist/data/queries/query.min.js +1 -1
- package/dist/data/queries/queryStatus.d.ts +15 -0
- package/dist/data/queries/queryStatus.js +52 -0
- package/dist/data/queries/queryStatus.min.js +1 -0
- package/dist/data/queries/quote.d.ts +11 -0
- package/dist/data/queries/quote.js +17 -0
- package/dist/data/queries/quote.min.js +1 -0
- package/dist/data/queries/showRecords.d.ts +6 -0
- package/dist/data/queries/showRecords.js +21 -0
- package/dist/data/queries/showRecords.min.js +1 -0
- package/dist/data/queries/sortBy.js +27 -27
- package/dist/data/queries/updateEntity.js +245 -193
- package/dist/data/queries/updateEntity.min.js +1 -1
- package/dist/data/queries/where.d.ts +7 -7
- package/dist/data/queries/where.js +67 -67
- package/dist/data/utilities/compareAs.d.ts +19 -19
- package/dist/data/utilities/compareAs.js +57 -57
- package/dist/data/utilities/config.d.ts +15 -0
- package/dist/data/utilities/config.js +31 -1
- package/dist/data/utilities/config.min.js +1 -1
- package/dist/data/utilities/databaseLock.d.ts +16 -0
- package/dist/data/utilities/databaseLock.js +63 -0
- package/dist/data/utilities/databaseLock.min.js +1 -0
- package/dist/data/utilities/fieldType.d.ts +15 -15
- package/dist/data/utilities/fieldType.js +22 -22
- package/dist/data/utilities/getConditionIndexedList.js +47 -47
- package/dist/data/utilities/matchIndexedJoins.js +62 -62
- package/dist/data/utilities/mergeJoin.js +47 -47
- package/dist/data/utilities/parsers/findConditions.d.ts +18 -15
- package/dist/data/utilities/parsers/findConditions.js +15 -6
- package/dist/data/utilities/parsers/findConditions.min.js +1 -1
- package/dist/data/utilities/parsers/findInsertValues.js +3 -2
- package/dist/data/utilities/parsers/findInsertValues.min.js +1 -1
- package/dist/data/utilities/parsers/findSelects.js +6 -3
- package/dist/data/utilities/parsers/findSelects.min.js +1 -1
- package/dist/data/utilities/parsers/findSortClauses.js +5 -2
- package/dist/data/utilities/parsers/findSortClauses.min.js +1 -1
- package/dist/data/utilities/parsers/findUpdateValues.js +3 -2
- package/dist/data/utilities/parsers/findUpdateValues.min.js +1 -1
- package/dist/data/utilities/parsers/maskStrings.d.ts +39 -0
- package/dist/data/utilities/parsers/maskStrings.js +123 -0
- package/dist/data/utilities/parsers/maskStrings.min.js +1 -0
- package/dist/data/utilities/parsers/parseCreate.d.ts +20 -0
- package/dist/data/utilities/parsers/parseCreate.js +330 -0
- package/dist/data/utilities/parsers/parseCreate.min.js +1 -0
- package/dist/data/utilities/parsers/parseLiteralValue.d.ts +2 -1
- package/dist/data/utilities/parsers/parseLiteralValue.js +6 -3
- package/dist/data/utilities/parsers/parseLiteralValue.min.js +1 -1
- package/dist/data/utilities/parsers/parseQuery.d.ts +3 -0
- package/dist/data/utilities/parsers/parseQuery.js +28 -2
- package/dist/data/utilities/parsers/parseQuery.min.js +1 -1
- package/dist/data/utilities/parsers/parseSchemaStatement.d.ts +13 -0
- package/dist/data/utilities/parsers/parseSchemaStatement.js +82 -0
- package/dist/data/utilities/parsers/parseSchemaStatement.min.js +1 -0
- package/dist/data/utilities/parsers/scanQuery.d.ts +52 -0
- package/dist/data/utilities/parsers/scanQuery.js +158 -0
- package/dist/data/utilities/parsers/scanQuery.min.js +1 -0
- package/dist/data/utilities/parsers/splitQueries.d.ts +6 -0
- package/dist/data/utilities/parsers/splitQueries.js +20 -2
- package/dist/data/utilities/parsers/splitQueries.min.js +1 -1
- package/dist/data/utilities/parsers/startsQuery.d.ts +5 -0
- package/dist/data/utilities/parsers/startsQuery.js +10 -0
- package/dist/data/utilities/parsers/startsQuery.min.js +1 -0
- package/dist/data/utilities/recordExists.d.ts +6 -0
- package/dist/data/utilities/recordExists.js +18 -0
- package/dist/data/utilities/recordExists.min.js +1 -0
- package/dist/data/utilities/removeFromIndex.d.ts +14 -14
- package/dist/data/utilities/removeFromIndex.js +45 -45
- package/dist/data/utilities/runQuery.js +135 -112
- package/dist/data/utilities/runQuery.min.js +1 -1
- package/dist/data/utilities/updateIndex.d.ts +17 -17
- package/dist/data/utilities/updateIndex.js +57 -57
- package/dist/data/utilities/useJoinClause.js +56 -56
- package/dist/index.d.ts +2 -0
- package/dist/index.js +14 -0
- package/dist/index.min.js +1 -1
- package/package.json +68 -60
|
@@ -1,194 +1,246 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.updateEntity = void 0;
|
|
7
|
-
require("core-js/modules/es.json.stringify.js");
|
|
8
|
-
require("core-js/modules/esnext.iterator.constructor.js");
|
|
9
|
-
require("core-js/modules/esnext.iterator.find.js");
|
|
10
|
-
require("core-js/modules/esnext.iterator.map.js");
|
|
11
|
-
require("core-js/modules/esnext.iterator.some.js");
|
|
12
|
-
var _retrieveRecord = require("../utilities/retrieveRecord");
|
|
13
|
-
var _retrieveFile = require("../utilities/retrieveFile");
|
|
14
|
-
var _updateIndex = require("../utilities/updateIndex");
|
|
15
|
-
var _removeFromIndex = require("../utilities/removeFromIndex");
|
|
16
|
-
var _computeKeyValue = require("../utilities/computeKeyValue");
|
|
17
|
-
var _fieldType = require("../utilities/fieldType");
|
|
18
|
-
var _compareAs = require("../utilities/compareAs");
|
|
19
|
-
var _keyUnique = require("../introspect/policies/keyUnique");
|
|
20
|
-
var _typeCheck = require("../introspect/policies/typeCheck");
|
|
21
|
-
var _foreignKeyExists = require("../introspect/policies/foreignKeyExists");
|
|
22
|
-
var _hasDependents = require("../introspect/policies/hasDependents");
|
|
23
|
-
var _splitEntityProperty = require("../utilities/parsers/splitEntityProperty");
|
|
24
|
-
var _config = require("../utilities/config");
|
|
25
|
-
const {
|
|
26
|
-
writeFile
|
|
27
|
-
} = require('fs/promises');
|
|
28
|
-
/**
|
|
29
|
-
* Same reasoning as deleteEntity: a record object carries no memory of which entry file it came
|
|
30
|
-
* from, so re-scan entries[] and match by the record's own primary key value(s) - a single field,
|
|
31
|
-
* or a composite of several for a multi-field primary key.
|
|
32
|
-
* @param record
|
|
33
|
-
* @param primaryKey
|
|
34
|
-
* @param primaryValue
|
|
35
|
-
*/
|
|
36
|
-
const findEntryFile = async (record, primaryKey, primaryValue) => {
|
|
37
|
-
for (const fileName of record.entries) {
|
|
38
|
-
const data = await (0, _retrieveFile.retrieveFile)(`${record.path}/${fileName}`);
|
|
39
|
-
if ((0, _compareAs.sameValue)((0, _fieldType.keyValueType)(record, primaryKey), (0, _computeKeyValue.computeKeyValue)(primaryKey.fields, data), primaryValue)) {
|
|
40
|
-
return fileName;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return null;
|
|
44
|
-
};
|
|
45
|
-
/**
|
|
46
|
-
* True if the update actually changes at least one of this key's fields - a key none of whose
|
|
47
|
-
* fields appear in newValues, or whose given values are all unchanged, doesn't need re-checking.
|
|
48
|
-
* @param record
|
|
49
|
-
* @param key
|
|
50
|
-
* @param oldData
|
|
51
|
-
* @param newValues
|
|
52
|
-
*/
|
|
53
|
-
const keyFieldsChanged = (record, key, oldData, newValues) => key.fields.some(field => Object.prototype.hasOwnProperty.call(newValues, field) && !(0, _compareAs.sameValue)((0, _fieldType.fieldType)(record, field), newValues[field], oldData[field]));
|
|
54
|
-
/**
|
|
55
|
-
* Only check uniqueness for a primary/unique key (single-field, or composite) whose value is
|
|
56
|
-
* actually changing - checking an unchanged value would find the record's own existing entry and
|
|
57
|
-
* wrongly report it as a duplicate of itself.
|
|
58
|
-
*
|
|
59
|
-
* @param
|
|
60
|
-
* @param
|
|
61
|
-
* @param
|
|
62
|
-
* @param
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* @param
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
};
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.updateEntity = void 0;
|
|
7
|
+
require("core-js/modules/es.json.stringify.js");
|
|
8
|
+
require("core-js/modules/esnext.iterator.constructor.js");
|
|
9
|
+
require("core-js/modules/esnext.iterator.find.js");
|
|
10
|
+
require("core-js/modules/esnext.iterator.map.js");
|
|
11
|
+
require("core-js/modules/esnext.iterator.some.js");
|
|
12
|
+
var _retrieveRecord = require("../utilities/retrieveRecord");
|
|
13
|
+
var _retrieveFile = require("../utilities/retrieveFile");
|
|
14
|
+
var _updateIndex = require("../utilities/updateIndex");
|
|
15
|
+
var _removeFromIndex = require("../utilities/removeFromIndex");
|
|
16
|
+
var _computeKeyValue = require("../utilities/computeKeyValue");
|
|
17
|
+
var _fieldType = require("../utilities/fieldType");
|
|
18
|
+
var _compareAs = require("../utilities/compareAs");
|
|
19
|
+
var _keyUnique = require("../introspect/policies/keyUnique");
|
|
20
|
+
var _typeCheck = require("../introspect/policies/typeCheck");
|
|
21
|
+
var _foreignKeyExists = require("../introspect/policies/foreignKeyExists");
|
|
22
|
+
var _hasDependents = require("../introspect/policies/hasDependents");
|
|
23
|
+
var _splitEntityProperty = require("../utilities/parsers/splitEntityProperty");
|
|
24
|
+
var _config = require("../utilities/config");
|
|
25
|
+
const {
|
|
26
|
+
writeFile
|
|
27
|
+
} = require('fs/promises');
|
|
28
|
+
/**
|
|
29
|
+
* Same reasoning as deleteEntity: a record object carries no memory of which entry file it came
|
|
30
|
+
* from, so re-scan entries[] and match by the record's own primary key value(s) - a single field,
|
|
31
|
+
* or a composite of several for a multi-field primary key.
|
|
32
|
+
* @param record
|
|
33
|
+
* @param primaryKey
|
|
34
|
+
* @param primaryValue
|
|
35
|
+
*/
|
|
36
|
+
const findEntryFile = async (record, primaryKey, primaryValue) => {
|
|
37
|
+
for (const fileName of record.entries) {
|
|
38
|
+
const data = await (0, _retrieveFile.retrieveFile)(`${record.path}/${fileName}`);
|
|
39
|
+
if ((0, _compareAs.sameValue)((0, _fieldType.keyValueType)(record, primaryKey), (0, _computeKeyValue.computeKeyValue)(primaryKey.fields, data), primaryValue)) {
|
|
40
|
+
return fileName;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* True if the update actually changes at least one of this key's fields - a key none of whose
|
|
47
|
+
* fields appear in newValues, or whose given values are all unchanged, doesn't need re-checking.
|
|
48
|
+
* @param record
|
|
49
|
+
* @param key
|
|
50
|
+
* @param oldData
|
|
51
|
+
* @param newValues
|
|
52
|
+
*/
|
|
53
|
+
const keyFieldsChanged = (record, key, oldData, newValues) => key.fields.some(field => Object.prototype.hasOwnProperty.call(newValues, field) && !(0, _compareAs.sameValue)((0, _fieldType.fieldType)(record, field), newValues[field], oldData[field]));
|
|
54
|
+
/**
|
|
55
|
+
* Only check uniqueness for a primary/unique key (single-field, or composite) whose value is
|
|
56
|
+
* actually changing - checking an unchanged value would find the record's own existing entry and
|
|
57
|
+
* wrongly report it as a duplicate of itself. A value with a null in it is never a duplicate (as in
|
|
58
|
+
* SQL).
|
|
59
|
+
* @param entity
|
|
60
|
+
* @param record
|
|
61
|
+
* @param oldData
|
|
62
|
+
* @param newData
|
|
63
|
+
* @param newValues
|
|
64
|
+
*/
|
|
65
|
+
const assertUniqueForUpdate = async (entity, record, oldData, newData, newValues) => {
|
|
66
|
+
for (const key of record.keys) {
|
|
67
|
+
if (key.type !== 'primary' && key.type !== 'unique' || !keyFieldsChanged(record, key, oldData, newValues)) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const checkKeys = key.fields.map(field => newData[field]);
|
|
71
|
+
if (checkKeys.some(value => value === null)) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const isUnique = await (0, _keyUnique.keyUnique)(entity, key.fields, checkKeys);
|
|
75
|
+
if (!isUnique) {
|
|
76
|
+
throw new Error(`Duplicate value for ${key.type} key "${key.fields.join(', ')}" on ${entity}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Same "only if it actually changed" scoping as assertUniqueForUpdate: re-checking an unchanged
|
|
82
|
+
* foreign key value would re-validate a reference this update never touches, which could fail an
|
|
83
|
+
* unrelated field's update if that parent record happened to be deleted since. A null references
|
|
84
|
+
* nothing, so it isn't checked.
|
|
85
|
+
* @param entity
|
|
86
|
+
* @param record
|
|
87
|
+
* @param oldData
|
|
88
|
+
* @param newValues
|
|
89
|
+
*/
|
|
90
|
+
const assertForeignKeysForUpdate = async (entity, record, oldData, newValues) => {
|
|
91
|
+
for (const key of record.keys) {
|
|
92
|
+
if (key.type !== 'foreign' || key.fields.length !== 1 || !key.references || key.references.length !== 1) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const field = key.fields[0];
|
|
96
|
+
if (!Object.prototype.hasOwnProperty.call(newValues, field) || newValues[field] === null || (0, _compareAs.sameValue)((0, _fieldType.fieldType)(record, field), newValues[field], oldData[field])) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const {
|
|
100
|
+
entity: refEntity,
|
|
101
|
+
property: refProperty
|
|
102
|
+
} = (0, _splitEntityProperty.splitEntityProperty)(key.references[0].fields[0]);
|
|
103
|
+
const exists = await (0, _foreignKeyExists.foreignKeyExists)(refEntity, refProperty, newValues[field]);
|
|
104
|
+
if (!exists) {
|
|
105
|
+
throw new Error(`Foreign key "${field}" on ${entity} references a nonexistent ${refEntity}.${refProperty} = ${newValues[field]}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* RESTRICT: reject an update that would change a single-field primary key's value while another
|
|
111
|
+
* entity's foreign key still points at the old one - that reference would otherwise be left
|
|
112
|
+
* dangling, since the row itself isn't gone, just re-keyed. Only meaningful for a single-field
|
|
113
|
+
* primary key, matching foreign key support's own single-field-only scope.
|
|
114
|
+
* @param entity
|
|
115
|
+
* @param record
|
|
116
|
+
* @param primaryKey
|
|
117
|
+
* @param oldData
|
|
118
|
+
* @param newValues
|
|
119
|
+
*/
|
|
120
|
+
const assertNoDependentsForUpdate = async (entity, record, primaryKey, oldData, newValues) => {
|
|
121
|
+
if (!(0, _config.getSetting)('enforceForeignKeys', true) || primaryKey.fields.length !== 1 || !keyFieldsChanged(record, primaryKey, oldData, newValues)) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const property = primaryKey.fields[0];
|
|
125
|
+
if (await (0, _hasDependents.hasDependents)(entity, property, oldData[property])) {
|
|
126
|
+
throw new Error(`Cannot update "${entity}" where "${property}" = ${oldData[property]} - other records still reference it`);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Move a key's index entry (single-field, or a composite for a multi-field key) from the old
|
|
131
|
+
* value to the new one wherever the update actually changed one of its fields.
|
|
132
|
+
* @param record
|
|
133
|
+
* @param oldData
|
|
134
|
+
* @param newData
|
|
135
|
+
* @param newValues
|
|
136
|
+
* @param fileName
|
|
137
|
+
*/
|
|
138
|
+
const reconcileIndexes = async (record, oldData, newData, newValues, fileName) => {
|
|
139
|
+
for (const key of record.keys) {
|
|
140
|
+
if (!keyFieldsChanged(record, key, oldData, newValues)) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
await (0, _removeFromIndex.removeFromIndex)(record.path, key, (0, _computeKeyValue.computeKeyValue)(key.fields, oldData), fileName, (0, _fieldType.keyValueType)(record, key));
|
|
144
|
+
await (0, _updateIndex.updateIndex)(record.path, key, (0, _computeKeyValue.computeKeyValue)(key.fields, newData), fileName, (0, _fieldType.keyValueType)(record, key));
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* The rows of one update are checked before any is written, so a check can't see the batch's own new values on disk.
|
|
149
|
+
* Reject the update if two of its rows would end with the same value for a primary or unique key that it changes (a
|
|
150
|
+
* value with a null in it is never a duplicate, as in assertUniqueForUpdate).
|
|
151
|
+
* @param entity
|
|
152
|
+
* @param record
|
|
153
|
+
* @param planned
|
|
154
|
+
* @param newValues
|
|
155
|
+
*/
|
|
156
|
+
const assertUniqueWithinUpdate = (entity, record, planned, newValues) => {
|
|
157
|
+
for (const key of record.keys) {
|
|
158
|
+
if (key.type !== 'primary' && key.type !== 'unique') {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const claimed = [];
|
|
162
|
+
for (const {
|
|
163
|
+
oldData,
|
|
164
|
+
newData
|
|
165
|
+
} of planned) {
|
|
166
|
+
if (!keyFieldsChanged(record, key, oldData, newValues) || key.fields.some(field => newData[field] === null)) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const value = (0, _computeKeyValue.computeKeyValue)(key.fields, newData);
|
|
170
|
+
if (claimed.some(other => (0, _compareAs.sameValue)((0, _fieldType.keyValueType)(record, key), other, value))) {
|
|
171
|
+
throw new Error(`Duplicate value for ${key.type} key "${key.fields.join(', ')}" on ${entity}`);
|
|
172
|
+
}
|
|
173
|
+
claimed.push(value);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
const assertRequiredFields = (entity, record, newData) => {
|
|
178
|
+
for (const field of record.definition) {
|
|
179
|
+
if (!field.optional && typeof newData[field.name] === 'undefined') {
|
|
180
|
+
throw new Error(`Missing required field "${field.name}" for ${entity}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
/**
|
|
185
|
+
* Type-check (and coerce) only the fields actually present in an update's values, never the rest
|
|
186
|
+
* of the merged record - a field whose already-stored value doesn't match its declared type
|
|
187
|
+
* (nothing enforced this before typeCheck() existed) must not block an update that never touches
|
|
188
|
+
* that field.
|
|
189
|
+
* @param record
|
|
190
|
+
* @param values
|
|
191
|
+
*/
|
|
192
|
+
const applyFieldTypes = (record, values) => {
|
|
193
|
+
const typedValues = Object.assign({}, values);
|
|
194
|
+
for (const field of record.definition) {
|
|
195
|
+
if (Object.prototype.hasOwnProperty.call(typedValues, field.name)) {
|
|
196
|
+
typedValues[field.name] = (0, _typeCheck.typeCheck)(field.type, typedValues[field.name], field.nullable);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return typedValues;
|
|
200
|
+
};
|
|
201
|
+
const updateEntity = async (entity = '', values = {}, dataSet = {}) => {
|
|
202
|
+
const databasePath = (0, _config.getSetting)('databasePath', 'database/');
|
|
203
|
+
const record = await (0, _retrieveRecord.retrieveRecord)(entity);
|
|
204
|
+
const primaryKey = record.keys.find(key => key.type === 'primary');
|
|
205
|
+
if (!primaryKey) {
|
|
206
|
+
throw new Error(`Cannot update "${entity}" without a primary key`);
|
|
207
|
+
}
|
|
208
|
+
const typedValues = applyFieldTypes(record, values);
|
|
209
|
+
// Check every row first and write only once all of them pass, so a row that fails leaves the others unchanged too
|
|
210
|
+
const planned = [];
|
|
211
|
+
for (const row of dataSet[entity] || []) {
|
|
212
|
+
const fileName = await findEntryFile(record, primaryKey, (0, _computeKeyValue.computeKeyValue)(primaryKey.fields, row));
|
|
213
|
+
if (!fileName) {
|
|
214
|
+
throw new Error(`Could not find entry for ${entity} where "${primaryKey.fields.join(', ')}" = ${(0, _computeKeyValue.computeKeyValue)(primaryKey.fields, row)}`);
|
|
215
|
+
}
|
|
216
|
+
if (planned.some(other => other.fileName === fileName)) {
|
|
217
|
+
// The same entry given twice is one row: updating it once is enough, and it can't collide with itself
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const oldData = await (0, _retrieveFile.retrieveFile)(`${record.path}/${fileName}`);
|
|
221
|
+
const newData = Object.assign({}, oldData, typedValues);
|
|
222
|
+
await assertUniqueForUpdate(entity, record, oldData, newData, typedValues);
|
|
223
|
+
await assertForeignKeysForUpdate(entity, record, oldData, typedValues);
|
|
224
|
+
await assertNoDependentsForUpdate(entity, record, primaryKey, oldData, typedValues);
|
|
225
|
+
assertRequiredFields(entity, record, newData);
|
|
226
|
+
planned.push({
|
|
227
|
+
fileName,
|
|
228
|
+
oldData,
|
|
229
|
+
newData
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
assertUniqueWithinUpdate(entity, record, planned, typedValues);
|
|
233
|
+
for (const {
|
|
234
|
+
fileName,
|
|
235
|
+
oldData,
|
|
236
|
+
newData
|
|
237
|
+
} of planned) {
|
|
238
|
+
await reconcileIndexes(record, oldData, newData, typedValues, fileName);
|
|
239
|
+
await writeFile(`${databasePath}${record.path}/${fileName}`, JSON.stringify(newData, null, 2));
|
|
240
|
+
}
|
|
241
|
+
dataSet[entity] = planned.map(({
|
|
242
|
+
newData
|
|
243
|
+
}) => newData);
|
|
244
|
+
return dataSet;
|
|
245
|
+
};
|
|
194
246
|
exports.updateEntity = updateEntity;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.updateEntity=void 0,require("core-js/modules/es.json.stringify.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.find.js"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.iterator.some.js");var _retrieveRecord=require("../utilities/retrieveRecord"),_retrieveFile=require("../utilities/retrieveFile"),_updateIndex=require("../utilities/updateIndex"),_removeFromIndex=require("../utilities/removeFromIndex"),_computeKeyValue=require("../utilities/computeKeyValue"),_fieldType=require("../utilities/fieldType"),_compareAs=require("../utilities/compareAs"),_keyUnique=require("../introspect/policies/keyUnique"),_typeCheck=require("../introspect/policies/typeCheck"),_foreignKeyExists=require("../introspect/policies/foreignKeyExists"),_hasDependents=require("../introspect/policies/hasDependents"),_splitEntityProperty=require("../utilities/parsers/splitEntityProperty"),_config=require("../utilities/config");const{writeFile:writeFile}=require("fs/promises"),findEntryFile=async(e,t,i)=>{for(const r of e.entries){const
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.updateEntity=void 0,require("core-js/modules/es.json.stringify.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.find.js"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.iterator.some.js");var _retrieveRecord=require("../utilities/retrieveRecord"),_retrieveFile=require("../utilities/retrieveFile"),_updateIndex=require("../utilities/updateIndex"),_removeFromIndex=require("../utilities/removeFromIndex"),_computeKeyValue=require("../utilities/computeKeyValue"),_fieldType=require("../utilities/fieldType"),_compareAs=require("../utilities/compareAs"),_keyUnique=require("../introspect/policies/keyUnique"),_typeCheck=require("../introspect/policies/typeCheck"),_foreignKeyExists=require("../introspect/policies/foreignKeyExists"),_hasDependents=require("../introspect/policies/hasDependents"),_splitEntityProperty=require("../utilities/parsers/splitEntityProperty"),_config=require("../utilities/config");const{writeFile:writeFile}=require("fs/promises"),findEntryFile=async(e,t,i)=>{for(const r of e.entries){const o=await(0,_retrieveFile.retrieveFile)(`${e.path}/${r}`);if((0,_compareAs.sameValue)((0,_fieldType.keyValueType)(e,t),(0,_computeKeyValue.computeKeyValue)(t.fields,o),i))return r}return null},keyFieldsChanged=(e,t,i,r)=>t.fields.some((t=>Object.prototype.hasOwnProperty.call(r,t)&&!(0,_compareAs.sameValue)((0,_fieldType.fieldType)(e,t),r[t],i[t]))),assertUniqueForUpdate=async(e,t,i,r,o)=>{for(const s of t.keys){if("primary"!==s.type&&"unique"!==s.type||!keyFieldsChanged(t,s,i,o))continue;const n=s.fields.map((e=>r[e]));if(n.some((e=>null===e)))continue;if(!await(0,_keyUnique.keyUnique)(e,s.fields,n))throw new Error(`Duplicate value for ${s.type} key "${s.fields.join(", ")}" on ${e}`)}},assertForeignKeysForUpdate=async(e,t,i,r)=>{for(const o of t.keys){if("foreign"!==o.type||1!==o.fields.length||!o.references||1!==o.references.length)continue;const s=o.fields[0];if(!Object.prototype.hasOwnProperty.call(r,s)||null===r[s]||(0,_compareAs.sameValue)((0,_fieldType.fieldType)(t,s),r[s],i[s]))continue;const{entity:n,property:a}=(0,_splitEntityProperty.splitEntityProperty)(o.references[0].fields[0]);if(!await(0,_foreignKeyExists.foreignKeyExists)(n,a,r[s]))throw new Error(`Foreign key "${s}" on ${e} references a nonexistent ${n}.${a} = ${r[s]}`)}},assertNoDependentsForUpdate=async(e,t,i,r,o)=>{if(!(0,_config.getSetting)("enforceForeignKeys",!0)||1!==i.fields.length||!keyFieldsChanged(t,i,r,o))return;const s=i.fields[0];if(await(0,_hasDependents.hasDependents)(e,s,r[s]))throw new Error(`Cannot update "${e}" where "${s}" = ${r[s]} - other records still reference it`)},reconcileIndexes=async(e,t,i,r,o)=>{for(const s of e.keys)keyFieldsChanged(e,s,t,r)&&(await(0,_removeFromIndex.removeFromIndex)(e.path,s,(0,_computeKeyValue.computeKeyValue)(s.fields,t),o,(0,_fieldType.keyValueType)(e,s)),await(0,_updateIndex.updateIndex)(e.path,s,(0,_computeKeyValue.computeKeyValue)(s.fields,i),o,(0,_fieldType.keyValueType)(e,s)))},assertUniqueWithinUpdate=(e,t,i,r)=>{for(const o of t.keys){if("primary"!==o.type&&"unique"!==o.type)continue;const s=[];for(const{oldData:n,newData:a}of i){if(!keyFieldsChanged(t,o,n,r)||o.fields.some((e=>null===a[e])))continue;const i=(0,_computeKeyValue.computeKeyValue)(o.fields,a);if(s.some((e=>(0,_compareAs.sameValue)((0,_fieldType.keyValueType)(t,o),e,i))))throw new Error(`Duplicate value for ${o.type} key "${o.fields.join(", ")}" on ${e}`);s.push(i)}}},assertRequiredFields=(e,t,i)=>{for(const r of t.definition)if(!r.optional&&void 0===i[r.name])throw new Error(`Missing required field "${r.name}" for ${e}`)},applyFieldTypes=(e,t)=>{const i=Object.assign({},t);for(const t of e.definition)Object.prototype.hasOwnProperty.call(i,t.name)&&(i[t.name]=(0,_typeCheck.typeCheck)(t.type,i[t.name],t.nullable));return i},updateEntity=async(e="",t={},i={})=>{const r=(0,_config.getSetting)("databasePath","database/"),o=await(0,_retrieveRecord.retrieveRecord)(e),s=o.keys.find((e=>"primary"===e.type));if(!s)throw new Error(`Cannot update "${e}" without a primary key`);const n=applyFieldTypes(o,t),a=[];for(const t of i[e]||[]){const i=await findEntryFile(o,s,(0,_computeKeyValue.computeKeyValue)(s.fields,t));if(!i)throw new Error(`Could not find entry for ${e} where "${s.fields.join(", ")}" = ${(0,_computeKeyValue.computeKeyValue)(s.fields,t)}`);if(a.some((e=>e.fileName===i)))continue;const r=await(0,_retrieveFile.retrieveFile)(`${o.path}/${i}`),l=Object.assign({},r,n);await assertUniqueForUpdate(e,o,r,l,n),await assertForeignKeysForUpdate(e,o,r,n),await assertNoDependentsForUpdate(e,o,s,r,n),assertRequiredFields(e,o,l),a.push({fileName:i,oldData:r,newData:l})}assertUniqueWithinUpdate(e,o,a,n);for(const{fileName:e,oldData:t,newData:i}of a)await reconcileIndexes(o,t,i,n,e),await writeFile(`${r}${o.path}/${e}`,JSON.stringify(i,null,2));return i[e]=a.map((({newData:e})=>e)),i};exports.updateEntity=updateEntity;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export declare const where: (dataSet?: any[], { property, joinEntity, comparator, value, type }?: {
|
|
2
|
-
property?: string;
|
|
3
|
-
joinEntity?: any;
|
|
4
|
-
comparator?: string;
|
|
5
|
-
value?: any;
|
|
6
|
-
type?: any;
|
|
7
|
-
}) => any[];
|
|
1
|
+
export declare const where: (dataSet?: any[], { property, joinEntity, comparator, value, type }?: {
|
|
2
|
+
property?: string;
|
|
3
|
+
joinEntity?: any;
|
|
4
|
+
comparator?: string;
|
|
5
|
+
value?: any;
|
|
6
|
+
type?: any;
|
|
7
|
+
}) => any[];
|
|
8
8
|
//# sourceMappingURL=where.d.ts.map
|
|
@@ -1,68 +1,68 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.where = void 0;
|
|
7
|
-
require("core-js/modules/es.array.includes.js");
|
|
8
|
-
require("core-js/modules/esnext.iterator.constructor.js");
|
|
9
|
-
require("core-js/modules/esnext.iterator.filter.js");
|
|
10
|
-
require("core-js/modules/esnext.iterator.some.js");
|
|
11
|
-
var _compareAs = require("../utilities/compareAs");
|
|
12
|
-
const likeMatch = (dataValue, pattern) => {
|
|
13
|
-
if (typeof dataValue !== 'string' || typeof pattern !== 'string') {
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
16
|
-
const regexPattern = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/%/g, '.*').replace(/_/g, '.');
|
|
17
|
-
return new RegExp(`^${regexPattern}$`, 'i').test(dataValue);
|
|
18
|
-
};
|
|
19
|
-
/**
|
|
20
|
-
* Equality (=, !=, <>, in) compares by the field's declared type when given one (see sameValue), so
|
|
21
|
-
* '3' and 3 match in a string or int field. Ordering (>, <, between, like) compares the raw values.
|
|
22
|
-
* @param data
|
|
23
|
-
* @param property
|
|
24
|
-
* @param comparator
|
|
25
|
-
* @param value
|
|
26
|
-
* @param type
|
|
27
|
-
*/
|
|
28
|
-
const makeCondition = (data, property, comparator, value, type = undefined) => {
|
|
29
|
-
if (!Object.prototype.hasOwnProperty.call(data, property)) {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
const dataValue = data[property];
|
|
33
|
-
switch (comparator) {
|
|
34
|
-
case '=':
|
|
35
|
-
return (0, _compareAs.sameValue)(type, dataValue, value);
|
|
36
|
-
case '!=':
|
|
37
|
-
case '<>':
|
|
38
|
-
return !(0, _compareAs.sameValue)(type, dataValue, value);
|
|
39
|
-
case '>':
|
|
40
|
-
return dataValue > value;
|
|
41
|
-
case '>=':
|
|
42
|
-
return dataValue >= value;
|
|
43
|
-
case '<':
|
|
44
|
-
return dataValue < value;
|
|
45
|
-
case '<=':
|
|
46
|
-
return dataValue <= value;
|
|
47
|
-
case 'in':
|
|
48
|
-
return Array.isArray(value) && (type ? value.some(item => (0, _compareAs.sameValue)(type, dataValue, item)) : value.includes(dataValue));
|
|
49
|
-
case 'between':
|
|
50
|
-
return Array.isArray(value) && value.length === 2 && dataValue >= value[0] && dataValue <= value[1];
|
|
51
|
-
case 'like':
|
|
52
|
-
return likeMatch(dataValue, value);
|
|
53
|
-
}
|
|
54
|
-
return false;
|
|
55
|
-
};
|
|
56
|
-
const where = (dataSet = [], {
|
|
57
|
-
property = '',
|
|
58
|
-
joinEntity = undefined,
|
|
59
|
-
comparator = '=',
|
|
60
|
-
value = null,
|
|
61
|
-
type = undefined
|
|
62
|
-
} = {}) => {
|
|
63
|
-
return dataSet.filter(data => {
|
|
64
|
-
const testProperty = joinEntity ? `${joinEntity}.${property}` : property;
|
|
65
|
-
return makeCondition(data, testProperty, comparator, value, type);
|
|
66
|
-
});
|
|
67
|
-
};
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.where = void 0;
|
|
7
|
+
require("core-js/modules/es.array.includes.js");
|
|
8
|
+
require("core-js/modules/esnext.iterator.constructor.js");
|
|
9
|
+
require("core-js/modules/esnext.iterator.filter.js");
|
|
10
|
+
require("core-js/modules/esnext.iterator.some.js");
|
|
11
|
+
var _compareAs = require("../utilities/compareAs");
|
|
12
|
+
const likeMatch = (dataValue, pattern) => {
|
|
13
|
+
if (typeof dataValue !== 'string' || typeof pattern !== 'string') {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const regexPattern = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/%/g, '.*').replace(/_/g, '.');
|
|
17
|
+
return new RegExp(`^${regexPattern}$`, 'i').test(dataValue);
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Equality (=, !=, <>, in) compares by the field's declared type when given one (see sameValue), so
|
|
21
|
+
* '3' and 3 match in a string or int field. Ordering (>, <, between, like) compares the raw values.
|
|
22
|
+
* @param data
|
|
23
|
+
* @param property
|
|
24
|
+
* @param comparator
|
|
25
|
+
* @param value
|
|
26
|
+
* @param type
|
|
27
|
+
*/
|
|
28
|
+
const makeCondition = (data, property, comparator, value, type = undefined) => {
|
|
29
|
+
if (!Object.prototype.hasOwnProperty.call(data, property)) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
const dataValue = data[property];
|
|
33
|
+
switch (comparator) {
|
|
34
|
+
case '=':
|
|
35
|
+
return (0, _compareAs.sameValue)(type, dataValue, value);
|
|
36
|
+
case '!=':
|
|
37
|
+
case '<>':
|
|
38
|
+
return !(0, _compareAs.sameValue)(type, dataValue, value);
|
|
39
|
+
case '>':
|
|
40
|
+
return dataValue > value;
|
|
41
|
+
case '>=':
|
|
42
|
+
return dataValue >= value;
|
|
43
|
+
case '<':
|
|
44
|
+
return dataValue < value;
|
|
45
|
+
case '<=':
|
|
46
|
+
return dataValue <= value;
|
|
47
|
+
case 'in':
|
|
48
|
+
return Array.isArray(value) && (type ? value.some(item => (0, _compareAs.sameValue)(type, dataValue, item)) : value.includes(dataValue));
|
|
49
|
+
case 'between':
|
|
50
|
+
return Array.isArray(value) && value.length === 2 && dataValue >= value[0] && dataValue <= value[1];
|
|
51
|
+
case 'like':
|
|
52
|
+
return likeMatch(dataValue, value);
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
};
|
|
56
|
+
const where = (dataSet = [], {
|
|
57
|
+
property = '',
|
|
58
|
+
joinEntity = undefined,
|
|
59
|
+
comparator = '=',
|
|
60
|
+
value = null,
|
|
61
|
+
type = undefined
|
|
62
|
+
} = {}) => {
|
|
63
|
+
return dataSet.filter(data => {
|
|
64
|
+
const testProperty = joinEntity ? `${joinEntity}.${property}` : property;
|
|
65
|
+
return makeCondition(data, testProperty, comparator, value, type);
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
68
|
exports.where = where;
|