json-fs-query 0.1.1 → 1.0.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/README.md +41 -6
- package/browser/jsonFsQuery.js +28336 -0
- package/browser/jsonFsQuery.min.js +1 -0
- package/dist/data/introspect/policies/hasDependents.d.ts +13 -0
- package/dist/data/introspect/policies/hasDependents.js +52 -0
- package/dist/data/introspect/policies/hasDependents.min.js +1 -0
- package/dist/data/introspect/policies/keyGenerate.js +1 -1
- package/dist/data/introspect/policies/keyGenerate.min.js +1 -1
- package/dist/data/introspect/policies/keyUnique.d.ts +9 -1
- package/dist/data/introspect/policies/keyUnique.js +19 -9
- package/dist/data/introspect/policies/keyUnique.min.js +1 -1
- package/dist/data/queries/deleteEntity.js +33 -12
- package/dist/data/queries/deleteEntity.min.js +1 -1
- package/dist/data/queries/insertEntity.js +9 -7
- package/dist/data/queries/insertEntity.min.js +1 -1
- package/dist/data/queries/limitOffset.d.ts +8 -0
- package/dist/data/queries/limitOffset.js +21 -0
- package/dist/data/queries/limitOffset.min.js +1 -0
- package/dist/data/queries/selectFields.d.ts +10 -0
- package/dist/data/queries/selectFields.js +36 -0
- package/dist/data/queries/selectFields.min.js +1 -0
- package/dist/data/queries/updateEntity.js +64 -38
- package/dist/data/queries/updateEntity.min.js +1 -1
- package/dist/data/utilities/computeKeyValue.d.ts +13 -0
- package/dist/data/utilities/computeKeyValue.js +26 -0
- package/dist/data/utilities/computeKeyValue.min.js +1 -0
- package/dist/data/utilities/config.d.ts +6 -0
- package/dist/data/utilities/config.js +7 -1
- package/dist/data/utilities/config.min.js +1 -1
- package/dist/data/utilities/runQuery.js +4 -0
- package/dist/data/utilities/runQuery.min.js +1 -1
- package/package.json +8 -5
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check whether any other record, in any entity, still has a single-field foreign key pointing
|
|
3
|
+
* at entity.property = value - the mirror image of foreignKeyExists(), which checks the opposite
|
|
4
|
+
* direction (that a new/updated foreign key value points at something real). Used to RESTRICT a
|
|
5
|
+
* delete or update that would otherwise leave a dangling reference behind. Scans every record in
|
|
6
|
+
* the database via the __RECORDS registry, the same "no index needed, just look" approach as
|
|
7
|
+
* keyUnique()/foreignKeyExists() - correctness over speed, matching this project's overall scale.
|
|
8
|
+
* @param entity
|
|
9
|
+
* @param property
|
|
10
|
+
* @param value
|
|
11
|
+
*/
|
|
12
|
+
export declare const hasDependents: (entity: string, property: string, value: any) => Promise<boolean>;
|
|
13
|
+
//# sourceMappingURL=hasDependents.d.ts.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', {
|
|
4
|
+
value: true
|
|
5
|
+
})
|
|
6
|
+
exports.hasDependents = void 0
|
|
7
|
+
const _describe = require('../describe')
|
|
8
|
+
const _retrieveRecord = require('../../utilities/retrieveRecord')
|
|
9
|
+
const _retrieveRecords = require('../../utilities/retrieveRecords')
|
|
10
|
+
const _where = require('../../queries/where')
|
|
11
|
+
const _splitEntityProperty = require('../../utilities/parsers/splitEntityProperty')
|
|
12
|
+
/**
|
|
13
|
+
* Check whether any other record, in any entity, still has a single-field foreign key pointing
|
|
14
|
+
* at entity.property = value - the mirror image of foreignKeyExists(), which checks the opposite
|
|
15
|
+
* direction (that a new/updated foreign key value points at something real). Used to RESTRICT a
|
|
16
|
+
* delete or update that would otherwise leave a dangling reference behind. Scans every record in
|
|
17
|
+
* the database via the __RECORDS registry, the same "no index needed, just look" approach as
|
|
18
|
+
* keyUnique()/foreignKeyExists() - correctness over speed, matching this project's overall scale.
|
|
19
|
+
* @param entity
|
|
20
|
+
* @param property
|
|
21
|
+
* @param value
|
|
22
|
+
*/
|
|
23
|
+
const hasDependents = async (entity, property, value) => {
|
|
24
|
+
const entityFiles = await (0, _describe.describe)()
|
|
25
|
+
for (const entityFile of entityFiles) {
|
|
26
|
+
const otherEntity = entityFile.replace(/\.json$/, '')
|
|
27
|
+
const otherRecord = await (0, _retrieveRecord.retrieveRecord)(otherEntity)
|
|
28
|
+
for (const key of otherRecord.keys) {
|
|
29
|
+
if (key.type !== 'foreign' || key.fields.length !== 1 || !key.references || key.references.length !== 1) {
|
|
30
|
+
continue
|
|
31
|
+
}
|
|
32
|
+
const {
|
|
33
|
+
entity: refEntity,
|
|
34
|
+
property: refProperty
|
|
35
|
+
} = (0, _splitEntityProperty.splitEntityProperty)(key.references[0].fields[0])
|
|
36
|
+
if (refEntity !== entity || refProperty !== property) {
|
|
37
|
+
continue
|
|
38
|
+
}
|
|
39
|
+
const records = await (0, _retrieveRecords.retrieveRecords)(otherEntity)
|
|
40
|
+
const found = (0, _where.where)(records, {
|
|
41
|
+
property: key.fields[0],
|
|
42
|
+
comparator: '=',
|
|
43
|
+
value
|
|
44
|
+
})
|
|
45
|
+
if (found.length) {
|
|
46
|
+
return true
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
exports.hasDependents = hasDependents
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.hasDependents=void 0;var _describe=require("../describe"),_retrieveRecord=require("../../utilities/retrieveRecord"),_retrieveRecords=require("../../utilities/retrieveRecords"),_where=require("../../queries/where"),_splitEntityProperty=require("../../utilities/parsers/splitEntityProperty");const hasDependents=async(e,r,t)=>{const i=await(0,_describe.describe)();for(const s of i){const i=s.replace(/\.json$/,""),o=await(0,_retrieveRecord.retrieveRecord)(i);for(const s of o.keys){if("foreign"!==s.type||1!==s.fields.length||!s.references||1!==s.references.length)continue;const{entity:o,property:n}=(0,_splitEntityProperty.splitEntityProperty)(s.references[0].fields[0]);if(o!==e||n!==r)continue;const c=await(0,_retrieveRecords.retrieveRecords)(i);if((0,_where.where)(c,{property:s.fields[0],comparator:"=",value:t}).length)return!0}}return!1};exports.hasDependents=hasDependents;
|
|
@@ -10,7 +10,7 @@ const keyGenerate = async (entity, references = []) => {
|
|
|
10
10
|
let isUnique = false
|
|
11
11
|
while (!isUnique) {
|
|
12
12
|
uuid = crypto.randomUUID()
|
|
13
|
-
isUnique = await (0, _keyUnique.keyUnique)(entity, references, uuid)
|
|
13
|
+
isUnique = await (0, _keyUnique.keyUnique)(entity, references, [uuid])
|
|
14
14
|
}
|
|
15
15
|
return uuid
|
|
16
16
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.keyGenerate=void 0;var _keyUnique=require("./keyUnique");const keyGenerate=async(e,r=[])=>{let t=null,n=!1;for(;!n;)t=crypto.randomUUID(),n=await(0,_keyUnique.keyUnique)(e,r,t);return t};exports.keyGenerate=keyGenerate;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.keyGenerate=void 0;var _keyUnique=require("./keyUnique");const keyGenerate=async(e,r=[])=>{let t=null,n=!1;for(;!n;)t=crypto.randomUUID(),n=await(0,_keyUnique.keyUnique)(e,r,[t]);return t};exports.keyGenerate=keyGenerate;
|
|
@@ -1,2 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Check that no existing record already has this combination of values across all of the given
|
|
3
|
+
* fields - a single field for a normal unique/primary key, or several for a composite one.
|
|
4
|
+
* references and checkKeys are parallel arrays: references[i]'s value must equal checkKeys[i].
|
|
5
|
+
* @param entity
|
|
6
|
+
* @param references
|
|
7
|
+
* @param checkKeys
|
|
8
|
+
*/
|
|
9
|
+
export declare const keyUnique: (entity: any, references?: any[], checkKeys?: any[]) => Promise<boolean>;
|
|
2
10
|
//# sourceMappingURL=keyUnique.d.ts.map
|
|
@@ -6,16 +6,26 @@ Object.defineProperty(exports, '__esModule', {
|
|
|
6
6
|
exports.keyUnique = void 0
|
|
7
7
|
const _retrieveRecords = require('../../utilities/retrieveRecords')
|
|
8
8
|
const _where = require('../../queries/where')
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Check that no existing record already has this combination of values across all of the given
|
|
11
|
+
* fields - a single field for a normal unique/primary key, or several for a composite one.
|
|
12
|
+
* references and checkKeys are parallel arrays: references[i]'s value must equal checkKeys[i].
|
|
13
|
+
* @param entity
|
|
14
|
+
* @param references
|
|
15
|
+
* @param checkKeys
|
|
16
|
+
*/
|
|
17
|
+
const keyUnique = async (entity, references = [], checkKeys = []) => {
|
|
18
|
+
if (!references.length || references.length !== checkKeys.length) {
|
|
11
19
|
return false
|
|
12
20
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
let records = await (0, _retrieveRecords.retrieveRecords)(entity)
|
|
22
|
+
for (let i = 0; i < references.length; i++) {
|
|
23
|
+
records = (0, _where.where)(records, {
|
|
24
|
+
property: references[i],
|
|
25
|
+
comparator: '=',
|
|
26
|
+
value: checkKeys[i]
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
return !records.length
|
|
20
30
|
}
|
|
21
31
|
exports.keyUnique = keyUnique
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.keyUnique=void 0;var _retrieveRecords=require("../../utilities/retrieveRecords"),_where=require("../../queries/where");const keyUnique=async(e,r=[],t=
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.keyUnique=void 0;var _retrieveRecords=require("../../utilities/retrieveRecords"),_where=require("../../queries/where");const keyUnique=async(e,r=[],t=[])=>{if(!r.length||r.length!==t.length)return!1;let i=await(0,_retrieveRecords.retrieveRecords)(e);for(let e=0;e<r.length;e++)i=(0,_where.where)(i,{property:r[e],comparator:"=",value:t[e]});return!i.length};exports.keyUnique=keyUnique;
|
|
@@ -27,34 +27,55 @@ require('core-js/modules/esnext.set.union.js')
|
|
|
27
27
|
const _retrieveRecord = require('../utilities/retrieveRecord')
|
|
28
28
|
const _retrieveFile = require('../utilities/retrieveFile')
|
|
29
29
|
const _removeFromIndex = require('../utilities/removeFromIndex')
|
|
30
|
+
const _computeKeyValue = require('../utilities/computeKeyValue')
|
|
31
|
+
const _hasDependents = require('../introspect/policies/hasDependents')
|
|
30
32
|
const _config = require('../utilities/config')
|
|
31
33
|
const {
|
|
32
34
|
rm,
|
|
33
35
|
writeFile
|
|
34
36
|
} = require('fs/promises')
|
|
37
|
+
/**
|
|
38
|
+
* RESTRICT: reject the whole delete (before removing anything) if any row being deleted is still
|
|
39
|
+
* referenced by a single-field foreign key elsewhere. Only meaningful for a single-field primary
|
|
40
|
+
* key, since foreign key support itself is single-field only - a composite primary key can't be
|
|
41
|
+
* the target of one yet, so there's nothing to check.
|
|
42
|
+
* @param entity
|
|
43
|
+
* @param primaryKey
|
|
44
|
+
* @param toDelete
|
|
45
|
+
*/
|
|
46
|
+
const assertNoDependents = async (entity, primaryKey, toDelete) => {
|
|
47
|
+
if (!(0, _config.getSetting)('enforceForeignKeys', true) || primaryKey.fields.length !== 1) {
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
const property = primaryKey.fields[0]
|
|
51
|
+
for (const row of toDelete) {
|
|
52
|
+
if (await (0, _hasDependents.hasDependents)(entity, property, row[property])) {
|
|
53
|
+
throw new Error(`Cannot delete from "${entity}" where "${property}" = ${row[property]} - other records still reference it`)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
35
57
|
/**
|
|
36
58
|
* A record object carries no memory of which entry file it was read from, and files are not
|
|
37
59
|
* guaranteed to be named after any field's value, so the only reliable way to find a record's
|
|
38
|
-
* file is to re-scan every entry and match it by its primary key value
|
|
60
|
+
* file is to re-scan every entry and match it by its primary key value(s) - a single field, or a
|
|
61
|
+
* composite of several for a multi-field primary key.
|
|
39
62
|
* @param entity
|
|
40
63
|
* @param record
|
|
41
|
-
* @param
|
|
64
|
+
* @param primaryKey
|
|
42
65
|
* @param deletedValues
|
|
43
66
|
*/
|
|
44
|
-
const removeMatchingEntries = async (entity, record,
|
|
67
|
+
const removeMatchingEntries = async (entity, record, primaryKey, deletedValues) => {
|
|
45
68
|
const databasePath = (0, _config.getSetting)('databasePath', 'database/')
|
|
46
69
|
const remainingEntries = []
|
|
47
70
|
for (const fileName of record.entries) {
|
|
48
71
|
const data = await (0, _retrieveFile.retrieveFile)(`${record.path}/${fileName}`)
|
|
49
|
-
if (!deletedValues.has(data
|
|
72
|
+
if (!deletedValues.has((0, _computeKeyValue.computeKeyValue)(primaryKey.fields, data))) {
|
|
50
73
|
remainingEntries.push(fileName)
|
|
51
74
|
continue
|
|
52
75
|
}
|
|
53
76
|
await rm(`${databasePath}${record.path}/${fileName}`)
|
|
54
77
|
for (const key of record.keys) {
|
|
55
|
-
|
|
56
|
-
await (0, _removeFromIndex.removeFromIndex)(record.path, key, data[key.fields[0]], fileName)
|
|
57
|
-
}
|
|
78
|
+
await (0, _removeFromIndex.removeFromIndex)(record.path, key, (0, _computeKeyValue.computeKeyValue)(key.fields, data), fileName)
|
|
58
79
|
}
|
|
59
80
|
}
|
|
60
81
|
return remainingEntries
|
|
@@ -62,17 +83,17 @@ const removeMatchingEntries = async (entity, record, primaryField, deletedValues
|
|
|
62
83
|
const deleteEntity = async (entity = '', dataSet = {}) => {
|
|
63
84
|
const databasePath = (0, _config.getSetting)('databasePath', 'database/')
|
|
64
85
|
const record = await (0, _retrieveRecord.retrieveRecord)(entity)
|
|
65
|
-
const primaryKey = record.keys.find(key => key.type === 'primary'
|
|
86
|
+
const primaryKey = record.keys.find(key => key.type === 'primary')
|
|
66
87
|
if (!primaryKey) {
|
|
67
|
-
throw new Error(`Cannot delete from "${entity}" without a
|
|
88
|
+
throw new Error(`Cannot delete from "${entity}" without a primary key`)
|
|
68
89
|
}
|
|
69
|
-
const primaryField = primaryKey.fields[0]
|
|
70
90
|
const toDelete = dataSet[entity] || []
|
|
71
91
|
if (!toDelete.length) {
|
|
72
92
|
return dataSet
|
|
73
93
|
}
|
|
74
|
-
|
|
75
|
-
|
|
94
|
+
await assertNoDependents(entity, primaryKey, toDelete)
|
|
95
|
+
const deletedValues = new Set(toDelete.map(row => (0, _computeKeyValue.computeKeyValue)(primaryKey.fields, row)))
|
|
96
|
+
record.entries = await removeMatchingEntries(entity, record, primaryKey, deletedValues)
|
|
76
97
|
await writeFile(`${databasePath}${record.path}.json`, JSON.stringify(record, null, 2))
|
|
77
98
|
return dataSet
|
|
78
99
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.deleteEntity=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.set.add-all.js"),require("core-js/modules/esnext.set.delete-all.js"),require("core-js/modules/esnext.set.difference.js"),require("core-js/modules/esnext.set.every.js"),require("core-js/modules/esnext.set.filter.js"),require("core-js/modules/esnext.set.find.js"),require("core-js/modules/esnext.set.intersection.js"),require("core-js/modules/esnext.set.is-disjoint-from.js"),require("core-js/modules/esnext.set.is-subset-of.js"),require("core-js/modules/esnext.set.is-superset-of.js"),require("core-js/modules/esnext.set.join.js"),require("core-js/modules/esnext.set.map.js"),require("core-js/modules/esnext.set.reduce.js"),require("core-js/modules/esnext.set.some.js"),require("core-js/modules/esnext.set.symmetric-difference.js"),require("core-js/modules/esnext.set.union.js");var _retrieveRecord=require("../utilities/retrieveRecord"),_retrieveFile=require("../utilities/retrieveFile"),_removeFromIndex=require("../utilities/removeFromIndex"),_config=require("../utilities/config");const{rm:rm,writeFile:writeFile}=require("fs/promises"),removeMatchingEntries=async(e,r,s,t)=>{const i=(0,_config.getSetting)("databasePath","database/"),o=[];for(const e of r.entries){const n=await(0,_retrieveFile.retrieveFile)(`${r.path}/${e}`);if(t.has(n
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.deleteEntity=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.set.add-all.js"),require("core-js/modules/esnext.set.delete-all.js"),require("core-js/modules/esnext.set.difference.js"),require("core-js/modules/esnext.set.every.js"),require("core-js/modules/esnext.set.filter.js"),require("core-js/modules/esnext.set.find.js"),require("core-js/modules/esnext.set.intersection.js"),require("core-js/modules/esnext.set.is-disjoint-from.js"),require("core-js/modules/esnext.set.is-subset-of.js"),require("core-js/modules/esnext.set.is-superset-of.js"),require("core-js/modules/esnext.set.join.js"),require("core-js/modules/esnext.set.map.js"),require("core-js/modules/esnext.set.reduce.js"),require("core-js/modules/esnext.set.some.js"),require("core-js/modules/esnext.set.symmetric-difference.js"),require("core-js/modules/esnext.set.union.js");var _retrieveRecord=require("../utilities/retrieveRecord"),_retrieveFile=require("../utilities/retrieveFile"),_removeFromIndex=require("../utilities/removeFromIndex"),_computeKeyValue=require("../utilities/computeKeyValue"),_hasDependents=require("../introspect/policies/hasDependents"),_config=require("../utilities/config");const{rm:rm,writeFile:writeFile}=require("fs/promises"),assertNoDependents=async(e,r,s)=>{if(!(0,_config.getSetting)("enforceForeignKeys",!0)||1!==r.fields.length)return;const t=r.fields[0];for(const r of s)if(await(0,_hasDependents.hasDependents)(e,t,r[t]))throw new Error(`Cannot delete from "${e}" where "${t}" = ${r[t]} - other records still reference it`)},removeMatchingEntries=async(e,r,s,t)=>{const i=(0,_config.getSetting)("databasePath","database/"),o=[];for(const e of r.entries){const n=await(0,_retrieveFile.retrieveFile)(`${r.path}/${e}`);if(t.has((0,_computeKeyValue.computeKeyValue)(s.fields,n))){await rm(`${i}${r.path}/${e}`);for(const s of r.keys)await(0,_removeFromIndex.removeFromIndex)(r.path,s,(0,_computeKeyValue.computeKeyValue)(s.fields,n),e)}else o.push(e)}return o},deleteEntity=async(e="",r={})=>{const s=(0,_config.getSetting)("databasePath","database/"),t=await(0,_retrieveRecord.retrieveRecord)(e),i=t.keys.find((e=>"primary"===e.type));if(!i)throw new Error(`Cannot delete from "${e}" without a primary key`);const o=r[e]||[];if(!o.length)return r;await assertNoDependents(e,i,o);const n=new Set(o.map((e=>(0,_computeKeyValue.computeKeyValue)(i.fields,e))));return t.entries=await removeMatchingEntries(0,t,i,n),await writeFile(`${s}${t.path}.json`,JSON.stringify(t,null,2)),r};exports.deleteEntity=deleteEntity;
|
|
@@ -7,6 +7,7 @@ exports.insertEntity = void 0
|
|
|
7
7
|
require('core-js/modules/es.json.stringify.js')
|
|
8
8
|
require('core-js/modules/esnext.iterator.constructor.js')
|
|
9
9
|
require('core-js/modules/esnext.iterator.find.js')
|
|
10
|
+
require('core-js/modules/esnext.iterator.map.js')
|
|
10
11
|
const _retrieveRecord = require('../utilities/retrieveRecord')
|
|
11
12
|
const _updateIndex = require('../utilities/updateIndex')
|
|
12
13
|
const _keyGenerate = require('../introspect/policies/keyGenerate')
|
|
@@ -15,6 +16,7 @@ const _useDefault = require('../introspect/policies/useDefault')
|
|
|
15
16
|
const _typeCheck = require('../introspect/policies/typeCheck')
|
|
16
17
|
const _foreignKeyExists = require('../introspect/policies/foreignKeyExists')
|
|
17
18
|
const _splitEntityProperty = require('../utilities/parsers/splitEntityProperty')
|
|
19
|
+
const _computeKeyValue = require('../utilities/computeKeyValue')
|
|
18
20
|
const _config = require('../utilities/config')
|
|
19
21
|
const {
|
|
20
22
|
writeFile
|
|
@@ -46,19 +48,21 @@ const buildEntityValues = async (entity, record, rowValues = {}) => {
|
|
|
46
48
|
return entityValues
|
|
47
49
|
}
|
|
48
50
|
/**
|
|
49
|
-
* Reject the insert if it collides with an existing primary or unique
|
|
51
|
+
* Reject the insert if it collides with an existing primary or unique key value - a single field,
|
|
52
|
+
* or a composite of several for a multi-field key.
|
|
50
53
|
* @param entity
|
|
51
54
|
* @param record
|
|
52
55
|
* @param entityValues
|
|
53
56
|
*/
|
|
54
57
|
const assertUnique = async (entity, record, entityValues) => {
|
|
55
58
|
for (const key of record.keys) {
|
|
56
|
-
if (key.type !== 'primary' && key.type !== 'unique'
|
|
59
|
+
if (key.type !== 'primary' && key.type !== 'unique') {
|
|
57
60
|
continue
|
|
58
61
|
}
|
|
59
|
-
const
|
|
62
|
+
const checkKeys = key.fields.map(field => entityValues[field])
|
|
63
|
+
const isUnique = await (0, _keyUnique.keyUnique)(entity, key.fields, checkKeys)
|
|
60
64
|
if (!isUnique) {
|
|
61
|
-
throw new Error(`Duplicate value for ${key.type} key "${key.fields
|
|
65
|
+
throw new Error(`Duplicate value for ${key.type} key "${key.fields.join(', ')}" on ${entity}`)
|
|
62
66
|
}
|
|
63
67
|
}
|
|
64
68
|
}
|
|
@@ -104,9 +108,7 @@ const insertEntity = async (entity = '', values = [], dataSet = {}) => {
|
|
|
104
108
|
record.entries.push(fileName)
|
|
105
109
|
await writeFile(`${databasePath}${record.path}.json`, JSON.stringify(record, null, 2))
|
|
106
110
|
for (const key of record.keys) {
|
|
107
|
-
|
|
108
|
-
await (0, _updateIndex.updateIndex)(record.path, key, entityValues[key.fields[0]], fileName)
|
|
109
|
-
}
|
|
111
|
+
await (0, _updateIndex.updateIndex)(record.path, key, (0, _computeKeyValue.computeKeyValue)(key.fields, entityValues), fileName)
|
|
110
112
|
}
|
|
111
113
|
dataSet[entity].push(entityValues)
|
|
112
114
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.insertEntity=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");var _retrieveRecord=require("../utilities/retrieveRecord"),_updateIndex=require("../utilities/updateIndex"),_keyGenerate=require("../introspect/policies/keyGenerate"),_keyUnique=require("../introspect/policies/keyUnique"),_useDefault=require("../introspect/policies/useDefault"),_typeCheck=require("../introspect/policies/typeCheck"),_foreignKeyExists=require("../introspect/policies/foreignKeyExists"),_splitEntityProperty=require("../utilities/parsers/splitEntityProperty"),_config=require("../utilities/config");const{writeFile:writeFile}=require("fs/promises"),buildEntityValues=async(e,t,i={})=>{const r={};for(const s of t.definition){let t=i[s.name];if(s.autoGenerate?t=await(0,_keyGenerate.keyGenerate)(e,[s.name]):Object.prototype.hasOwnProperty.call(s,"default")&&(t=(0,_useDefault.useDefault)(s.default,t)),void 0!==t)r[s.name]=(0,_typeCheck.typeCheck)(s.type,t);else if(!s.optional)throw new Error(`Missing required field "${s.name}" for ${e}`)}return r},assertUnique=async(e,t,i)=>{for(const r of t.keys){if("primary"!==r.type&&"unique"!==r.type
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.insertEntity=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");var _retrieveRecord=require("../utilities/retrieveRecord"),_updateIndex=require("../utilities/updateIndex"),_keyGenerate=require("../introspect/policies/keyGenerate"),_keyUnique=require("../introspect/policies/keyUnique"),_useDefault=require("../introspect/policies/useDefault"),_typeCheck=require("../introspect/policies/typeCheck"),_foreignKeyExists=require("../introspect/policies/foreignKeyExists"),_splitEntityProperty=require("../utilities/parsers/splitEntityProperty"),_computeKeyValue=require("../utilities/computeKeyValue"),_config=require("../utilities/config");const{writeFile:writeFile}=require("fs/promises"),buildEntityValues=async(e,t,i={})=>{const r={};for(const s of t.definition){let t=i[s.name];if(s.autoGenerate?t=await(0,_keyGenerate.keyGenerate)(e,[s.name]):Object.prototype.hasOwnProperty.call(s,"default")&&(t=(0,_useDefault.useDefault)(s.default,t)),void 0!==t)r[s.name]=(0,_typeCheck.typeCheck)(s.type,t);else if(!s.optional)throw new Error(`Missing required field "${s.name}" for ${e}`)}return r},assertUnique=async(e,t,i)=>{for(const r of t.keys){if("primary"!==r.type&&"unique"!==r.type)continue;const t=r.fields.map((e=>i[e]));if(!await(0,_keyUnique.keyUnique)(e,r.fields,t))throw new Error(`Duplicate value for ${r.type} key "${r.fields.join(", ")}" on ${e}`)}},assertForeignKeys=async(e,t,i)=>{for(const r of t.keys){if("foreign"!==r.type||1!==r.fields.length||!r.references||1!==r.references.length)continue;const t=r.fields[0];if(!Object.prototype.hasOwnProperty.call(i,t))continue;const{entity:s,property:n}=(0,_splitEntityProperty.splitEntityProperty)(r.references[0].fields[0]);if(!await(0,_foreignKeyExists.foreignKeyExists)(s,n,i[t]))throw new Error(`Foreign key "${t}" on ${e} references a nonexistent ${s}.${n} = ${i[t]}`)}},insertEntity=async(e="",t=[],i={})=>{const r=(0,_config.getSetting)("databasePath","database/"),s=await(0,_retrieveRecord.retrieveRecord)(e),n=s.keys.find((e=>"primary"===e.type&&1===e.fields.length));Object.prototype.hasOwnProperty.call(i,e)||(i[e]=[]);for(const o of t){const t=await buildEntityValues(e,s,o);await assertUnique(e,s,t),await assertForeignKeys(e,s,t);const a=`${n?t[n.fields[0]]:crypto.randomUUID()}.json`;await writeFile(`${r}${s.path}/${a}`,JSON.stringify(t,null,2)),s.entries.push(a),await writeFile(`${r}${s.path}.json`,JSON.stringify(s,null,2));for(const e of s.keys)await(0,_updateIndex.updateIndex)(s.path,e,(0,_computeKeyValue.computeKeyValue)(e.fields,t),a);i[e].push(t)}return i};exports.insertEntity=insertEntity;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slice a dataSet down to at most `limit` rows, starting after the first `offset` of them.
|
|
3
|
+
* @param dataSet
|
|
4
|
+
* @param offset
|
|
5
|
+
* @param limit
|
|
6
|
+
*/
|
|
7
|
+
export declare const limitOffset: (dataSet: Object[], offset: number | undefined, limit: number | undefined) => Object[];
|
|
8
|
+
//# sourceMappingURL=limitOffset.d.ts.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', {
|
|
4
|
+
value: true
|
|
5
|
+
})
|
|
6
|
+
exports.limitOffset = void 0
|
|
7
|
+
/**
|
|
8
|
+
* Slice a dataSet down to at most `limit` rows, starting after the first `offset` of them.
|
|
9
|
+
* @param dataSet
|
|
10
|
+
* @param offset
|
|
11
|
+
* @param limit
|
|
12
|
+
*/
|
|
13
|
+
const limitOffset = (dataSet = [], offset, limit) => {
|
|
14
|
+
if (typeof offset === 'undefined' && typeof limit === 'undefined') {
|
|
15
|
+
return dataSet
|
|
16
|
+
}
|
|
17
|
+
const start = offset ?? 0
|
|
18
|
+
const end = typeof limit === 'number' ? start + limit : undefined
|
|
19
|
+
return dataSet.slice(start, end)
|
|
20
|
+
}
|
|
21
|
+
exports.limitOffset = limitOffset
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.limitOffset=void 0;const limitOffset=(e=[],t,i)=>{if(void 0===t&&void 0===i)return e;const s=t??0,o="number"==typeof i?s+i:void 0;return e.slice(s,o)};exports.limitOffset=limitOffset;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ParsedSelect } from '../utilities/parsers/findSelects';
|
|
2
|
+
/**
|
|
3
|
+
* Shape each row down to only the selected properties (aliased, when given). dataSet is either a plain array of
|
|
4
|
+
* rows, or (once groupBy has run) a plain object keyed by group value, each value an array of rows - both are
|
|
5
|
+
* shaped the same way, row by row.
|
|
6
|
+
* @param dataSet
|
|
7
|
+
* @param selectClauses
|
|
8
|
+
*/
|
|
9
|
+
export declare const selectFields: (dataSet?: Object[] | Object, selectClauses?: ParsedSelect[]) => Object[] | Object;
|
|
10
|
+
//# sourceMappingURL=selectFields.d.ts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', {
|
|
4
|
+
value: true
|
|
5
|
+
})
|
|
6
|
+
exports.selectFields = void 0
|
|
7
|
+
require('core-js/modules/esnext.iterator.constructor.js')
|
|
8
|
+
require('core-js/modules/esnext.iterator.map.js')
|
|
9
|
+
require('core-js/modules/esnext.iterator.reduce.js')
|
|
10
|
+
const shapeRow = (row, selectClauses) => selectClauses.reduce((shaped, {
|
|
11
|
+
property,
|
|
12
|
+
alias
|
|
13
|
+
}) => {
|
|
14
|
+
shaped[alias || property] = row[property]
|
|
15
|
+
return shaped
|
|
16
|
+
}, {})
|
|
17
|
+
/**
|
|
18
|
+
* Shape each row down to only the selected properties (aliased, when given). dataSet is either a plain array of
|
|
19
|
+
* rows, or (once groupBy has run) a plain object keyed by group value, each value an array of rows - both are
|
|
20
|
+
* shaped the same way, row by row.
|
|
21
|
+
* @param dataSet
|
|
22
|
+
* @param selectClauses
|
|
23
|
+
*/
|
|
24
|
+
const selectFields = (dataSet = [], selectClauses = []) => {
|
|
25
|
+
if (!selectClauses.length) {
|
|
26
|
+
return dataSet
|
|
27
|
+
}
|
|
28
|
+
if (Array.isArray(dataSet)) {
|
|
29
|
+
return dataSet.map(row => shapeRow(row, selectClauses))
|
|
30
|
+
}
|
|
31
|
+
return Object.keys(dataSet).reduce((shaped, key) => {
|
|
32
|
+
shaped[key] = dataSet[key].map(row => shapeRow(row, selectClauses))
|
|
33
|
+
return shaped
|
|
34
|
+
}, {})
|
|
35
|
+
}
|
|
36
|
+
exports.selectFields = selectFields
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.selectFields=void 0,require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.iterator.reduce.js");const shapeRow=(e,s)=>s.reduce(((s,{property:r,alias:t})=>(s[t||r]=e[r],s)),{}),selectFields=(e=[],s=[])=>s.length?Array.isArray(e)?e.map((e=>shapeRow(e,s))):Object.keys(e).reduce(((r,t)=>(r[t]=e[t].map((e=>shapeRow(e,s))),r)),{}):e;exports.selectFields=selectFields;
|
|
@@ -7,13 +7,17 @@ exports.updateEntity = void 0
|
|
|
7
7
|
require('core-js/modules/es.json.stringify.js')
|
|
8
8
|
require('core-js/modules/esnext.iterator.constructor.js')
|
|
9
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')
|
|
10
12
|
const _retrieveRecord = require('../utilities/retrieveRecord')
|
|
11
13
|
const _retrieveFile = require('../utilities/retrieveFile')
|
|
12
14
|
const _updateIndex = require('../utilities/updateIndex')
|
|
13
15
|
const _removeFromIndex = require('../utilities/removeFromIndex')
|
|
16
|
+
const _computeKeyValue = require('../utilities/computeKeyValue')
|
|
14
17
|
const _keyUnique = require('../introspect/policies/keyUnique')
|
|
15
18
|
const _typeCheck = require('../introspect/policies/typeCheck')
|
|
16
19
|
const _foreignKeyExists = require('../introspect/policies/foreignKeyExists')
|
|
20
|
+
const _hasDependents = require('../introspect/policies/hasDependents')
|
|
17
21
|
const _splitEntityProperty = require('../utilities/parsers/splitEntityProperty')
|
|
18
22
|
const _config = require('../utilities/config')
|
|
19
23
|
const {
|
|
@@ -21,53 +25,51 @@ const {
|
|
|
21
25
|
} = require('fs/promises')
|
|
22
26
|
/**
|
|
23
27
|
* Same reasoning as deleteEntity: a record object carries no memory of which entry file it came
|
|
24
|
-
* from, so re-scan entries[] and match by the record's own primary key value
|
|
28
|
+
* from, so re-scan entries[] and match by the record's own primary key value(s) - a single field,
|
|
29
|
+
* or a composite of several for a multi-field primary key.
|
|
25
30
|
* @param record
|
|
26
|
-
* @param
|
|
31
|
+
* @param primaryKey
|
|
27
32
|
* @param primaryValue
|
|
28
33
|
*/
|
|
29
|
-
const findEntryFile = async (record,
|
|
34
|
+
const findEntryFile = async (record, primaryKey, primaryValue) => {
|
|
30
35
|
for (const fileName of record.entries) {
|
|
31
36
|
const data = await (0, _retrieveFile.retrieveFile)(`${record.path}/${fileName}`)
|
|
32
|
-
if (data
|
|
37
|
+
if ((0, _computeKeyValue.computeKeyValue)(primaryKey.fields, data) == primaryValue) {
|
|
33
38
|
return fileName
|
|
34
39
|
}
|
|
35
40
|
}
|
|
36
41
|
return null
|
|
37
42
|
}
|
|
38
43
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
44
|
+
* True if the update actually changes at least one of this key's fields - a key none of whose
|
|
45
|
+
* fields appear in newValues, or whose given values are all unchanged, doesn't need re-checking.
|
|
46
|
+
* @param key
|
|
47
|
+
* @param oldData
|
|
48
|
+
* @param newValues
|
|
49
|
+
*/
|
|
50
|
+
const keyFieldsChanged = (key, oldData, newValues) => key.fields.some(field => Object.prototype.hasOwnProperty.call(newValues, field) && newValues[field] != oldData[field])
|
|
51
|
+
/**
|
|
52
|
+
* Only check uniqueness for a primary/unique key (single-field, or composite) whose value is
|
|
53
|
+
* actually changing - checking an unchanged value would find the record's own existing entry and
|
|
54
|
+
* wrongly report it as a duplicate of itself.
|
|
42
55
|
* @param entity
|
|
43
56
|
* @param record
|
|
44
57
|
* @param oldData
|
|
58
|
+
* @param newData
|
|
45
59
|
* @param newValues
|
|
46
60
|
*/
|
|
47
|
-
const assertUniqueForUpdate = async (entity, record, oldData, newValues) => {
|
|
61
|
+
const assertUniqueForUpdate = async (entity, record, oldData, newData, newValues) => {
|
|
48
62
|
for (const key of record.keys) {
|
|
49
|
-
if (key.type !== 'primary' && key.type !== 'unique' || key
|
|
63
|
+
if (key.type !== 'primary' && key.type !== 'unique' || !keyFieldsChanged(key, oldData, newValues)) {
|
|
50
64
|
continue
|
|
51
65
|
}
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
continue
|
|
55
|
-
}
|
|
56
|
-
const isUnique = await (0, _keyUnique.keyUnique)(entity, key.fields, newValues[field])
|
|
66
|
+
const checkKeys = key.fields.map(field => newData[field])
|
|
67
|
+
const isUnique = await (0, _keyUnique.keyUnique)(entity, key.fields, checkKeys)
|
|
57
68
|
if (!isUnique) {
|
|
58
|
-
throw new Error(`Duplicate value for ${key.type} key "${
|
|
69
|
+
throw new Error(`Duplicate value for ${key.type} key "${key.fields.join(', ')}" on ${entity}`)
|
|
59
70
|
}
|
|
60
71
|
}
|
|
61
72
|
}
|
|
62
|
-
/**
|
|
63
|
-
* Move a single-field key's index entry from the old value to the new one wherever the update
|
|
64
|
-
* actually changed that field.
|
|
65
|
-
* @param record
|
|
66
|
-
* @param oldData
|
|
67
|
-
* @param newData
|
|
68
|
-
* @param newValues
|
|
69
|
-
* @param fileName
|
|
70
|
-
*/
|
|
71
73
|
/**
|
|
72
74
|
* Same "only if it actually changed" scoping as assertUniqueForUpdate: re-checking an unchanged
|
|
73
75
|
* foreign key value would re-validate a reference this update never touches, which could fail an
|
|
@@ -96,17 +98,41 @@ const assertForeignKeysForUpdate = async (entity, record, oldData, newValues) =>
|
|
|
96
98
|
}
|
|
97
99
|
}
|
|
98
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* RESTRICT: reject an update that would change a single-field primary key's value while another
|
|
103
|
+
* entity's foreign key still points at the old one - that reference would otherwise be left
|
|
104
|
+
* dangling, since the row itself isn't gone, just re-keyed. Only meaningful for a single-field
|
|
105
|
+
* primary key, matching foreign key support's own single-field-only scope.
|
|
106
|
+
* @param entity
|
|
107
|
+
* @param primaryKey
|
|
108
|
+
* @param oldData
|
|
109
|
+
* @param newValues
|
|
110
|
+
*/
|
|
111
|
+
const assertNoDependentsForUpdate = async (entity, primaryKey, oldData, newValues) => {
|
|
112
|
+
if (!(0, _config.getSetting)('enforceForeignKeys', true) || primaryKey.fields.length !== 1 || !keyFieldsChanged(primaryKey, oldData, newValues)) {
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
const property = primaryKey.fields[0]
|
|
116
|
+
if (await (0, _hasDependents.hasDependents)(entity, property, oldData[property])) {
|
|
117
|
+
throw new Error(`Cannot update "${entity}" where "${property}" = ${oldData[property]} - other records still reference it`)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Move a key's index entry (single-field, or a composite for a multi-field key) from the old
|
|
122
|
+
* value to the new one wherever the update actually changed one of its fields.
|
|
123
|
+
* @param record
|
|
124
|
+
* @param oldData
|
|
125
|
+
* @param newData
|
|
126
|
+
* @param newValues
|
|
127
|
+
* @param fileName
|
|
128
|
+
*/
|
|
99
129
|
const reconcileIndexes = async (record, oldData, newData, newValues, fileName) => {
|
|
100
130
|
for (const key of record.keys) {
|
|
101
|
-
if (key
|
|
131
|
+
if (!keyFieldsChanged(key, oldData, newValues)) {
|
|
102
132
|
continue
|
|
103
133
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
continue
|
|
107
|
-
}
|
|
108
|
-
await (0, _removeFromIndex.removeFromIndex)(record.path, key, oldData[field], fileName)
|
|
109
|
-
await (0, _updateIndex.updateIndex)(record.path, key, newData[field], fileName)
|
|
134
|
+
await (0, _removeFromIndex.removeFromIndex)(record.path, key, (0, _computeKeyValue.computeKeyValue)(key.fields, oldData), fileName)
|
|
135
|
+
await (0, _updateIndex.updateIndex)(record.path, key, (0, _computeKeyValue.computeKeyValue)(key.fields, newData), fileName)
|
|
110
136
|
}
|
|
111
137
|
}
|
|
112
138
|
const assertRequiredFields = (entity, record, newData) => {
|
|
@@ -136,23 +162,23 @@ const applyFieldTypes = (record, values) => {
|
|
|
136
162
|
const updateEntity = async (entity = '', values = {}, dataSet = {}) => {
|
|
137
163
|
const databasePath = (0, _config.getSetting)('databasePath', 'database/')
|
|
138
164
|
const record = await (0, _retrieveRecord.retrieveRecord)(entity)
|
|
139
|
-
const primaryKey = record.keys.find(key => key.type === 'primary'
|
|
165
|
+
const primaryKey = record.keys.find(key => key.type === 'primary')
|
|
140
166
|
if (!primaryKey) {
|
|
141
|
-
throw new Error(`Cannot update "${entity}" without a
|
|
167
|
+
throw new Error(`Cannot update "${entity}" without a primary key`)
|
|
142
168
|
}
|
|
143
|
-
const primaryField = primaryKey.fields[0]
|
|
144
169
|
const typedValues = applyFieldTypes(record, values)
|
|
145
170
|
const toUpdate = dataSet[entity] || []
|
|
146
171
|
const updatedRows = []
|
|
147
172
|
for (const row of toUpdate) {
|
|
148
|
-
const fileName = await findEntryFile(record,
|
|
173
|
+
const fileName = await findEntryFile(record, primaryKey, (0, _computeKeyValue.computeKeyValue)(primaryKey.fields, row))
|
|
149
174
|
if (!fileName) {
|
|
150
|
-
throw new Error(`Could not find entry for ${entity} where "${
|
|
175
|
+
throw new Error(`Could not find entry for ${entity} where "${primaryKey.fields.join(', ')}" = ${(0, _computeKeyValue.computeKeyValue)(primaryKey.fields, row)}`)
|
|
151
176
|
}
|
|
152
177
|
const oldData = await (0, _retrieveFile.retrieveFile)(`${record.path}/${fileName}`)
|
|
153
|
-
await assertUniqueForUpdate(entity, record, oldData, typedValues)
|
|
154
|
-
await assertForeignKeysForUpdate(entity, record, oldData, typedValues)
|
|
155
178
|
const newData = Object.assign({}, oldData, typedValues)
|
|
179
|
+
await assertUniqueForUpdate(entity, record, oldData, newData, typedValues)
|
|
180
|
+
await assertForeignKeysForUpdate(entity, record, oldData, typedValues)
|
|
181
|
+
await assertNoDependentsForUpdate(entity, primaryKey, oldData, typedValues)
|
|
156
182
|
assertRequiredFields(entity, record, newData)
|
|
157
183
|
await reconcileIndexes(record, oldData, newData, typedValues, fileName)
|
|
158
184
|
await writeFile(`${databasePath}${record.path}/${fileName}`, JSON.stringify(newData, null, 2))
|
|
@@ -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");var _retrieveRecord=require("../utilities/retrieveRecord"),_retrieveFile=require("../utilities/retrieveFile"),_updateIndex=require("../utilities/updateIndex"),_removeFromIndex=require("../utilities/removeFromIndex"),_keyUnique=require("../introspect/policies/keyUnique"),_typeCheck=require("../introspect/policies/typeCheck"),_foreignKeyExists=require("../introspect/policies/foreignKeyExists"),_splitEntityProperty=require("../utilities/parsers/splitEntityProperty"),_config=require("../utilities/config");const{writeFile:writeFile}=require("fs/promises"),findEntryFile=async(e,t,r)=>{for(const i of e.entries){
|
|
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"),_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,r)=>{for(const i of e.entries){const s=await(0,_retrieveFile.retrieveFile)(`${e.path}/${i}`);if((0,_computeKeyValue.computeKeyValue)(t.fields,s)==r)return i}return null},keyFieldsChanged=(e,t,r)=>e.fields.some((e=>Object.prototype.hasOwnProperty.call(r,e)&&r[e]!=t[e])),assertUniqueForUpdate=async(e,t,r,i,s)=>{for(const o of t.keys){if("primary"!==o.type&&"unique"!==o.type||!keyFieldsChanged(o,r,s))continue;const t=o.fields.map((e=>i[e]));if(!await(0,_keyUnique.keyUnique)(e,o.fields,t))throw new Error(`Duplicate value for ${o.type} key "${o.fields.join(", ")}" on ${e}`)}},assertForeignKeysForUpdate=async(e,t,r,i)=>{for(const s of t.keys){if("foreign"!==s.type||1!==s.fields.length||!s.references||1!==s.references.length)continue;const t=s.fields[0];if(!Object.prototype.hasOwnProperty.call(i,t)||i[t]==r[t])continue;const{entity:o,property:n}=(0,_splitEntityProperty.splitEntityProperty)(s.references[0].fields[0]);if(!await(0,_foreignKeyExists.foreignKeyExists)(o,n,i[t]))throw new Error(`Foreign key "${t}" on ${e} references a nonexistent ${o}.${n} = ${i[t]}`)}},assertNoDependentsForUpdate=async(e,t,r,i)=>{if(!(0,_config.getSetting)("enforceForeignKeys",!0)||1!==t.fields.length||!keyFieldsChanged(t,r,i))return;const s=t.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,r,i,s)=>{for(const o of e.keys)keyFieldsChanged(o,t,i)&&(await(0,_removeFromIndex.removeFromIndex)(e.path,o,(0,_computeKeyValue.computeKeyValue)(o.fields,t),s),await(0,_updateIndex.updateIndex)(e.path,o,(0,_computeKeyValue.computeKeyValue)(o.fields,r),s))},assertRequiredFields=(e,t,r)=>{for(const i of t.definition)if(!i.optional&&void 0===r[i.name])throw new Error(`Missing required field "${i.name}" for ${e}`)},applyFieldTypes=(e,t)=>{const r=Object.assign({},t);for(const t of e.definition)Object.prototype.hasOwnProperty.call(r,t.name)&&(r[t.name]=(0,_typeCheck.typeCheck)(t.type,r[t.name]));return r},updateEntity=async(e="",t={},r={})=>{const i=(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=r[e]||[],p=[];for(const t of a){const r=await findEntryFile(s,o,(0,_computeKeyValue.computeKeyValue)(o.fields,t));if(!r)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}/${r}`),u=Object.assign({},a,n);await assertUniqueForUpdate(e,s,a,u,n),await assertForeignKeysForUpdate(e,s,a,n),await assertNoDependentsForUpdate(e,o,a,n),assertRequiredFields(e,s,u),await reconcileIndexes(s,a,u,n,r),await writeFile(`${i}${s.path}/${r}`,JSON.stringify(u,null,2)),p.push(u)}return r[e]=p,r};exports.updateEntity=updateEntity;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { fieldName } from '../introspect/definitions/field';
|
|
2
|
+
/**
|
|
3
|
+
* The value a key (of any field count) indexes and checks uniqueness by. A single-field key uses
|
|
4
|
+
* the field's own raw value directly - this is the pre-existing, already-shipped index file
|
|
5
|
+
* format (e.g. a plain "2024-06-02" or 1), and stays exactly as it was for backward compatibility
|
|
6
|
+
* with real fixture data. A multi-field key has no single value to use, so its fields' values are
|
|
7
|
+
* combined into one deterministic composite (a JSON array), in the same field order as key.fields
|
|
8
|
+
* - two records only share a composite value if every field matches.
|
|
9
|
+
* @param fields
|
|
10
|
+
* @param values
|
|
11
|
+
*/
|
|
12
|
+
export declare const computeKeyValue: (fields: fieldName[], values: Object) => any;
|
|
13
|
+
//# sourceMappingURL=computeKeyValue.d.ts.map
|