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.
Files changed (113) hide show
  1. package/LICENSE +674 -674
  2. package/README.md +295 -98
  3. package/browser/jsonFsQuery.js +12733 -10934
  4. package/browser/jsonFsQuery.min.js +1 -1
  5. package/dist/data/introspect/create.d.ts +1 -1
  6. package/dist/data/introspect/create.js +35 -4
  7. package/dist/data/introspect/create.min.js +1 -1
  8. package/dist/data/introspect/definitions/field.d.ts +6 -2
  9. package/dist/data/introspect/definitions/field.js +7 -1
  10. package/dist/data/introspect/definitions/field.min.js +1 -1
  11. package/dist/data/introspect/index.d.ts +6 -3
  12. package/dist/data/introspect/index.js +10 -19
  13. package/dist/data/introspect/index.min.js +1 -1
  14. package/dist/data/introspect/policies/foreignKeyExists.js +28 -28
  15. package/dist/data/introspect/policies/hasDependents.js +53 -53
  16. package/dist/data/introspect/policies/keyUnique.js +34 -34
  17. package/dist/data/introspect/policies/typeCheck.d.ts +9 -1
  18. package/dist/data/introspect/policies/typeCheck.js +16 -2
  19. package/dist/data/introspect/policies/typeCheck.min.js +1 -1
  20. package/dist/data/queries/createRecord.d.ts +11 -0
  21. package/dist/data/queries/createRecord.js +58 -0
  22. package/dist/data/queries/createRecord.min.js +1 -0
  23. package/dist/data/queries/deleteEntity.js +100 -100
  24. package/dist/data/queries/describeRecord.d.ts +9 -0
  25. package/dist/data/queries/describeRecord.js +29 -0
  26. package/dist/data/queries/describeRecord.min.js +1 -0
  27. package/dist/data/queries/dropRecord.d.ts +9 -0
  28. package/dist/data/queries/dropRecord.js +61 -0
  29. package/dist/data/queries/dropRecord.min.js +1 -0
  30. package/dist/data/queries/insertEntity.js +122 -117
  31. package/dist/data/queries/insertEntity.min.js +1 -1
  32. package/dist/data/queries/parser.d.ts +1 -1
  33. package/dist/data/queries/parser.js +7 -15
  34. package/dist/data/queries/parser.min.js +1 -1
  35. package/dist/data/queries/query.d.ts +6 -0
  36. package/dist/data/queries/query.js +15 -6
  37. package/dist/data/queries/query.min.js +1 -1
  38. package/dist/data/queries/queryStatus.d.ts +15 -0
  39. package/dist/data/queries/queryStatus.js +52 -0
  40. package/dist/data/queries/queryStatus.min.js +1 -0
  41. package/dist/data/queries/quote.d.ts +11 -0
  42. package/dist/data/queries/quote.js +17 -0
  43. package/dist/data/queries/quote.min.js +1 -0
  44. package/dist/data/queries/showRecords.d.ts +6 -0
  45. package/dist/data/queries/showRecords.js +21 -0
  46. package/dist/data/queries/showRecords.min.js +1 -0
  47. package/dist/data/queries/sortBy.js +27 -27
  48. package/dist/data/queries/updateEntity.js +245 -193
  49. package/dist/data/queries/updateEntity.min.js +1 -1
  50. package/dist/data/queries/where.d.ts +7 -7
  51. package/dist/data/queries/where.js +67 -67
  52. package/dist/data/utilities/compareAs.d.ts +19 -19
  53. package/dist/data/utilities/compareAs.js +57 -57
  54. package/dist/data/utilities/config.d.ts +15 -0
  55. package/dist/data/utilities/config.js +31 -1
  56. package/dist/data/utilities/config.min.js +1 -1
  57. package/dist/data/utilities/databaseLock.d.ts +16 -0
  58. package/dist/data/utilities/databaseLock.js +63 -0
  59. package/dist/data/utilities/databaseLock.min.js +1 -0
  60. package/dist/data/utilities/fieldType.d.ts +15 -15
  61. package/dist/data/utilities/fieldType.js +22 -22
  62. package/dist/data/utilities/getConditionIndexedList.js +47 -47
  63. package/dist/data/utilities/matchIndexedJoins.js +62 -62
  64. package/dist/data/utilities/mergeJoin.js +47 -47
  65. package/dist/data/utilities/parsers/findConditions.d.ts +18 -15
  66. package/dist/data/utilities/parsers/findConditions.js +15 -6
  67. package/dist/data/utilities/parsers/findConditions.min.js +1 -1
  68. package/dist/data/utilities/parsers/findInsertValues.js +3 -2
  69. package/dist/data/utilities/parsers/findInsertValues.min.js +1 -1
  70. package/dist/data/utilities/parsers/findSelects.js +6 -3
  71. package/dist/data/utilities/parsers/findSelects.min.js +1 -1
  72. package/dist/data/utilities/parsers/findSortClauses.js +5 -2
  73. package/dist/data/utilities/parsers/findSortClauses.min.js +1 -1
  74. package/dist/data/utilities/parsers/findUpdateValues.js +3 -2
  75. package/dist/data/utilities/parsers/findUpdateValues.min.js +1 -1
  76. package/dist/data/utilities/parsers/maskStrings.d.ts +39 -0
  77. package/dist/data/utilities/parsers/maskStrings.js +123 -0
  78. package/dist/data/utilities/parsers/maskStrings.min.js +1 -0
  79. package/dist/data/utilities/parsers/parseCreate.d.ts +20 -0
  80. package/dist/data/utilities/parsers/parseCreate.js +330 -0
  81. package/dist/data/utilities/parsers/parseCreate.min.js +1 -0
  82. package/dist/data/utilities/parsers/parseLiteralValue.d.ts +2 -1
  83. package/dist/data/utilities/parsers/parseLiteralValue.js +6 -3
  84. package/dist/data/utilities/parsers/parseLiteralValue.min.js +1 -1
  85. package/dist/data/utilities/parsers/parseQuery.d.ts +3 -0
  86. package/dist/data/utilities/parsers/parseQuery.js +28 -2
  87. package/dist/data/utilities/parsers/parseQuery.min.js +1 -1
  88. package/dist/data/utilities/parsers/parseSchemaStatement.d.ts +13 -0
  89. package/dist/data/utilities/parsers/parseSchemaStatement.js +82 -0
  90. package/dist/data/utilities/parsers/parseSchemaStatement.min.js +1 -0
  91. package/dist/data/utilities/parsers/scanQuery.d.ts +52 -0
  92. package/dist/data/utilities/parsers/scanQuery.js +158 -0
  93. package/dist/data/utilities/parsers/scanQuery.min.js +1 -0
  94. package/dist/data/utilities/parsers/splitQueries.d.ts +6 -0
  95. package/dist/data/utilities/parsers/splitQueries.js +20 -2
  96. package/dist/data/utilities/parsers/splitQueries.min.js +1 -1
  97. package/dist/data/utilities/parsers/startsQuery.d.ts +5 -0
  98. package/dist/data/utilities/parsers/startsQuery.js +10 -0
  99. package/dist/data/utilities/parsers/startsQuery.min.js +1 -0
  100. package/dist/data/utilities/recordExists.d.ts +6 -0
  101. package/dist/data/utilities/recordExists.js +18 -0
  102. package/dist/data/utilities/recordExists.min.js +1 -0
  103. package/dist/data/utilities/removeFromIndex.d.ts +14 -14
  104. package/dist/data/utilities/removeFromIndex.js +45 -45
  105. package/dist/data/utilities/runQuery.js +135 -112
  106. package/dist/data/utilities/runQuery.min.js +1 -1
  107. package/dist/data/utilities/updateIndex.d.ts +17 -17
  108. package/dist/data/utilities/updateIndex.js +57 -57
  109. package/dist/data/utilities/useJoinClause.js +56 -56
  110. package/dist/index.d.ts +2 -0
  111. package/dist/index.js +14 -0
  112. package/dist/index.min.js +1 -1
  113. 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
- * @param entity
59
- * @param record
60
- * @param oldData
61
- * @param newData
62
- * @param newValues
63
- */
64
- const assertUniqueForUpdate = async (entity, record, oldData, newData, newValues) => {
65
- for (const key of record.keys) {
66
- if (key.type !== 'primary' && key.type !== 'unique' || !keyFieldsChanged(record, key, oldData, newValues)) {
67
- continue;
68
- }
69
- const checkKeys = key.fields.map(field => newData[field]);
70
- const isUnique = await (0, _keyUnique.keyUnique)(entity, key.fields, checkKeys);
71
- if (!isUnique) {
72
- throw new Error(`Duplicate value for ${key.type} key "${key.fields.join(', ')}" on ${entity}`);
73
- }
74
- }
75
- };
76
- /**
77
- * Same "only if it actually changed" scoping as assertUniqueForUpdate: re-checking an unchanged
78
- * foreign key value would re-validate a reference this update never touches, which could fail an
79
- * unrelated field's update if that parent record happened to be deleted since.
80
- * @param entity
81
- * @param record
82
- * @param oldData
83
- * @param newValues
84
- */
85
- const assertForeignKeysForUpdate = async (entity, record, oldData, newValues) => {
86
- for (const key of record.keys) {
87
- if (key.type !== 'foreign' || key.fields.length !== 1 || !key.references || key.references.length !== 1) {
88
- continue;
89
- }
90
- const field = key.fields[0];
91
- if (!Object.prototype.hasOwnProperty.call(newValues, field) || (0, _compareAs.sameValue)((0, _fieldType.fieldType)(record, field), newValues[field], oldData[field])) {
92
- continue;
93
- }
94
- const {
95
- entity: refEntity,
96
- property: refProperty
97
- } = (0, _splitEntityProperty.splitEntityProperty)(key.references[0].fields[0]);
98
- const exists = await (0, _foreignKeyExists.foreignKeyExists)(refEntity, refProperty, newValues[field]);
99
- if (!exists) {
100
- throw new Error(`Foreign key "${field}" on ${entity} references a nonexistent ${refEntity}.${refProperty} = ${newValues[field]}`);
101
- }
102
- }
103
- };
104
- /**
105
- * RESTRICT: reject an update that would change a single-field primary key's value while another
106
- * entity's foreign key still points at the old one - that reference would otherwise be left
107
- * dangling, since the row itself isn't gone, just re-keyed. Only meaningful for a single-field
108
- * primary key, matching foreign key support's own single-field-only scope.
109
- * @param entity
110
- * @param record
111
- * @param primaryKey
112
- * @param oldData
113
- * @param newValues
114
- */
115
- const assertNoDependentsForUpdate = async (entity, record, primaryKey, oldData, newValues) => {
116
- if (!(0, _config.getSetting)('enforceForeignKeys', true) || primaryKey.fields.length !== 1 || !keyFieldsChanged(record, primaryKey, oldData, newValues)) {
117
- return;
118
- }
119
- const property = primaryKey.fields[0];
120
- if (await (0, _hasDependents.hasDependents)(entity, property, oldData[property])) {
121
- throw new Error(`Cannot update "${entity}" where "${property}" = ${oldData[property]} - other records still reference it`);
122
- }
123
- };
124
- /**
125
- * Move a key's index entry (single-field, or a composite for a multi-field key) from the old
126
- * value to the new one wherever the update actually changed one of its fields.
127
- * @param record
128
- * @param oldData
129
- * @param newData
130
- * @param newValues
131
- * @param fileName
132
- */
133
- const reconcileIndexes = async (record, oldData, newData, newValues, fileName) => {
134
- for (const key of record.keys) {
135
- if (!keyFieldsChanged(record, key, oldData, newValues)) {
136
- continue;
137
- }
138
- await (0, _removeFromIndex.removeFromIndex)(record.path, key, (0, _computeKeyValue.computeKeyValue)(key.fields, oldData), fileName, (0, _fieldType.keyValueType)(record, key));
139
- await (0, _updateIndex.updateIndex)(record.path, key, (0, _computeKeyValue.computeKeyValue)(key.fields, newData), fileName, (0, _fieldType.keyValueType)(record, key));
140
- }
141
- };
142
- const assertRequiredFields = (entity, record, newData) => {
143
- for (const field of record.definition) {
144
- if (!field.optional && typeof newData[field.name] === 'undefined') {
145
- throw new Error(`Missing required field "${field.name}" for ${entity}`);
146
- }
147
- }
148
- };
149
- /**
150
- * Type-check (and coerce) only the fields actually present in an update's values, never the rest
151
- * of the merged record - a field whose already-stored value doesn't match its declared type
152
- * (nothing enforced this before typeCheck() existed) must not block an update that never touches
153
- * that field.
154
- * @param record
155
- * @param values
156
- */
157
- const applyFieldTypes = (record, values) => {
158
- const typedValues = Object.assign({}, values);
159
- for (const field of record.definition) {
160
- if (Object.prototype.hasOwnProperty.call(typedValues, field.name)) {
161
- typedValues[field.name] = (0, _typeCheck.typeCheck)(field.type, typedValues[field.name]);
162
- }
163
- }
164
- return typedValues;
165
- };
166
- const updateEntity = async (entity = '', values = {}, dataSet = {}) => {
167
- const databasePath = (0, _config.getSetting)('databasePath', 'database/');
168
- const record = await (0, _retrieveRecord.retrieveRecord)(entity);
169
- const primaryKey = record.keys.find(key => key.type === 'primary');
170
- if (!primaryKey) {
171
- throw new Error(`Cannot update "${entity}" without a primary key`);
172
- }
173
- const typedValues = applyFieldTypes(record, values);
174
- const toUpdate = dataSet[entity] || [];
175
- const updatedRows = [];
176
- for (const row of toUpdate) {
177
- const fileName = await findEntryFile(record, primaryKey, (0, _computeKeyValue.computeKeyValue)(primaryKey.fields, row));
178
- if (!fileName) {
179
- throw new Error(`Could not find entry for ${entity} where "${primaryKey.fields.join(', ')}" = ${(0, _computeKeyValue.computeKeyValue)(primaryKey.fields, row)}`);
180
- }
181
- const oldData = await (0, _retrieveFile.retrieveFile)(`${record.path}/${fileName}`);
182
- const newData = Object.assign({}, oldData, typedValues);
183
- await assertUniqueForUpdate(entity, record, oldData, newData, typedValues);
184
- await assertForeignKeysForUpdate(entity, record, oldData, typedValues);
185
- await assertNoDependentsForUpdate(entity, record, primaryKey, oldData, typedValues);
186
- assertRequiredFields(entity, record, newData);
187
- await reconcileIndexes(record, oldData, newData, typedValues, fileName);
188
- await writeFile(`${databasePath}${record.path}/${fileName}`, JSON.stringify(newData, null, 2));
189
- updatedRows.push(newData);
190
- }
191
- dataSet[entity] = updatedRows;
192
- return dataSet;
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 s=await(0,_retrieveFile.retrieveFile)(`${e.path}/${r}`);if((0,_compareAs.sameValue)((0,_fieldType.keyValueType)(e,t),(0,_computeKeyValue.computeKeyValue)(t.fields,s),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,s)=>{for(const o of t.keys){if("primary"!==o.type&&"unique"!==o.type||!keyFieldsChanged(t,o,i,s))continue;const n=o.fields.map((e=>r[e]));if(!await(0,_keyUnique.keyUnique)(e,o.fields,n))throw new Error(`Duplicate value for ${o.type} key "${o.fields.join(", ")}" on ${e}`)}},assertForeignKeysForUpdate=async(e,t,i,r)=>{for(const s of t.keys){if("foreign"!==s.type||1!==s.fields.length||!s.references||1!==s.references.length)continue;const o=s.fields[0];if(!Object.prototype.hasOwnProperty.call(r,o)||(0,_compareAs.sameValue)((0,_fieldType.fieldType)(t,o),r[o],i[o]))continue;const{entity:n,property:a}=(0,_splitEntityProperty.splitEntityProperty)(s.references[0].fields[0]);if(!await(0,_foreignKeyExists.foreignKeyExists)(n,a,r[o]))throw new Error(`Foreign key "${o}" on ${e} references a nonexistent ${n}.${a} = ${r[o]}`)}},assertNoDependentsForUpdate=async(e,t,i,r,s)=>{if(!(0,_config.getSetting)("enforceForeignKeys",!0)||1!==i.fields.length||!keyFieldsChanged(t,i,r,s))return;const o=i.fields[0];if(await(0,_hasDependents.hasDependents)(e,o,r[o]))throw new Error(`Cannot update "${e}" where "${o}" = ${r[o]} - other records still reference it`)},reconcileIndexes=async(e,t,i,r,s)=>{for(const o of e.keys)keyFieldsChanged(e,o,t,r)&&(await(0,_removeFromIndex.removeFromIndex)(e.path,o,(0,_computeKeyValue.computeKeyValue)(o.fields,t),s,(0,_fieldType.keyValueType)(e,o)),await(0,_updateIndex.updateIndex)(e.path,o,(0,_computeKeyValue.computeKeyValue)(o.fields,i),s,(0,_fieldType.keyValueType)(e,o)))},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]));return i},updateEntity=async(e="",t={},i={})=>{const r=(0,_config.getSetting)("databasePath","database/"),s=await(0,_retrieveRecord.retrieveRecord)(e),o=s.keys.find((e=>"primary"===e.type));if(!o)throw new Error(`Cannot update "${e}" without a primary key`);const n=applyFieldTypes(s,t),a=i[e]||[],p=[];for(const t of a){const i=await findEntryFile(s,o,(0,_computeKeyValue.computeKeyValue)(o.fields,t));if(!i)throw new Error(`Could not find entry for ${e} where "${o.fields.join(", ")}" = ${(0,_computeKeyValue.computeKeyValue)(o.fields,t)}`);const a=await(0,_retrieveFile.retrieveFile)(`${s.path}/${i}`),l=Object.assign({},a,n);await assertUniqueForUpdate(e,s,a,l,n),await assertForeignKeysForUpdate(e,s,a,n),await assertNoDependentsForUpdate(e,s,o,a,n),assertRequiredFields(e,s,l),await reconcileIndexes(s,a,l,n,i),await writeFile(`${r}${s.path}/${i}`,JSON.stringify(l,null,2)),p.push(l)}return i[e]=p,i};exports.updateEntity=updateEntity;
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;