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.
Files changed (32) hide show
  1. package/README.md +41 -6
  2. package/browser/jsonFsQuery.js +28336 -0
  3. package/browser/jsonFsQuery.min.js +1 -0
  4. package/dist/data/introspect/policies/hasDependents.d.ts +13 -0
  5. package/dist/data/introspect/policies/hasDependents.js +52 -0
  6. package/dist/data/introspect/policies/hasDependents.min.js +1 -0
  7. package/dist/data/introspect/policies/keyGenerate.js +1 -1
  8. package/dist/data/introspect/policies/keyGenerate.min.js +1 -1
  9. package/dist/data/introspect/policies/keyUnique.d.ts +9 -1
  10. package/dist/data/introspect/policies/keyUnique.js +19 -9
  11. package/dist/data/introspect/policies/keyUnique.min.js +1 -1
  12. package/dist/data/queries/deleteEntity.js +33 -12
  13. package/dist/data/queries/deleteEntity.min.js +1 -1
  14. package/dist/data/queries/insertEntity.js +9 -7
  15. package/dist/data/queries/insertEntity.min.js +1 -1
  16. package/dist/data/queries/limitOffset.d.ts +8 -0
  17. package/dist/data/queries/limitOffset.js +21 -0
  18. package/dist/data/queries/limitOffset.min.js +1 -0
  19. package/dist/data/queries/selectFields.d.ts +10 -0
  20. package/dist/data/queries/selectFields.js +36 -0
  21. package/dist/data/queries/selectFields.min.js +1 -0
  22. package/dist/data/queries/updateEntity.js +64 -38
  23. package/dist/data/queries/updateEntity.min.js +1 -1
  24. package/dist/data/utilities/computeKeyValue.d.ts +13 -0
  25. package/dist/data/utilities/computeKeyValue.js +26 -0
  26. package/dist/data/utilities/computeKeyValue.min.js +1 -0
  27. package/dist/data/utilities/config.d.ts +6 -0
  28. package/dist/data/utilities/config.js +7 -1
  29. package/dist/data/utilities/config.min.js +1 -1
  30. package/dist/data/utilities/runQuery.js +4 -0
  31. package/dist/data/utilities/runQuery.min.js +1 -1
  32. 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
- export declare const keyUnique: (entity: any, references?: any[], checkKey?: any) => Promise<boolean>;
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
- const keyUnique = async (entity, references = [], checkKey = null) => {
10
- if (references.length !== 1) {
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
- const records = await (0, _retrieveRecords.retrieveRecords)(entity)
14
- const found = (0, _where.where)(records, {
15
- property: references[0],
16
- comparator: '=',
17
- value: checkKey
18
- })
19
- return !found.length
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=null)=>{if(1!==r.length)return!1;const i=await(0,_retrieveRecords.retrieveRecords)(e);return!(0,_where.where)(i,{property:r[0],comparator:"=",value:t}).length};exports.keyUnique=keyUnique;
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 primaryField
64
+ * @param primaryKey
42
65
  * @param deletedValues
43
66
  */
44
- const removeMatchingEntries = async (entity, record, primaryField, deletedValues) => {
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[primaryField])) {
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
- if (key.fields.length === 1) {
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' && key.fields.length === 1)
86
+ const primaryKey = record.keys.find(key => key.type === 'primary')
66
87
  if (!primaryKey) {
67
- throw new Error(`Cannot delete from "${entity}" without a single-field primary key`)
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
- const deletedValues = new Set(toDelete.map(row => row[primaryField]))
75
- record.entries = await removeMatchingEntries(entity, record, primaryField, deletedValues)
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[s])){await rm(`${i}${r.path}/${e}`);for(const s of r.keys)1===s.fields.length&&await(0,_removeFromIndex.removeFromIndex)(r.path,s,n[s.fields[0]],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&&1===e.fields.length));if(!i)throw new Error(`Cannot delete from "${e}" without a single-field primary key`);const o=i.fields[0],n=r[e]||[];if(!n.length)return r;const u=new Set(n.map((e=>e[o])));return t.entries=await removeMatchingEntries(0,t,o,u),await writeFile(`${s}${t.path}.json`,JSON.stringify(t,null,2)),r};exports.deleteEntity=deleteEntity;
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 single-field key value.
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' || key.fields.length !== 1) {
59
+ if (key.type !== 'primary' && key.type !== 'unique') {
57
60
  continue
58
61
  }
59
- const isUnique = await (0, _keyUnique.keyUnique)(entity, key.fields, entityValues[key.fields[0]])
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[0]}" on ${entity}`)
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
- if (key.fields.length === 1) {
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!==r.fields.length)continue;if(!await(0,_keyUnique.keyUnique)(e,r.fields,i[r.fields[0]]))throw new Error(`Duplicate value for ${r.type} key "${r.fields[0]}" 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)1===e.fields.length&&await(0,_updateIndex.updateIndex)(s.path,e,t[e.fields[0]],a);i[e].push(t)}return i};exports.insertEntity=insertEntity;
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 primaryField
31
+ * @param primaryKey
27
32
  * @param primaryValue
28
33
  */
29
- const findEntryFile = async (record, primaryField, primaryValue) => {
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[primaryField] == primaryValue) {
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
- * Only check uniqueness for a single-field primary/unique key whose value is actually changing -
40
- * checking an unchanged value would find the record's own existing entry and wrongly report it
41
- * as a duplicate of itself.
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.fields.length !== 1) {
63
+ if (key.type !== 'primary' && key.type !== 'unique' || !keyFieldsChanged(key, oldData, newValues)) {
50
64
  continue
51
65
  }
52
- const field = key.fields[0]
53
- if (!Object.prototype.hasOwnProperty.call(newValues, field) || newValues[field] == oldData[field]) {
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 "${field}" on ${entity}`)
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.fields.length !== 1) {
131
+ if (!keyFieldsChanged(key, oldData, newValues)) {
102
132
  continue
103
133
  }
104
- const field = key.fields[0]
105
- if (!Object.prototype.hasOwnProperty.call(newValues, field) || newValues[field] == oldData[field]) {
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' && key.fields.length === 1)
165
+ const primaryKey = record.keys.find(key => key.type === 'primary')
140
166
  if (!primaryKey) {
141
- throw new Error(`Cannot update "${entity}" without a single-field primary key`)
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, primaryField, row[primaryField])
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 "${primaryField}" = ${row[primaryField]}`)
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){if((await(0,_retrieveFile.retrieveFile)(`${e.path}/${i}`))[t]==r)return i}return null},assertUniqueForUpdate=async(e,t,r,i)=>{for(const n of t.keys){if("primary"!==n.type&&"unique"!==n.type||1!==n.fields.length)continue;const t=n.fields[0];if(!Object.prototype.hasOwnProperty.call(i,t)||i[t]==r[t])continue;if(!await(0,_keyUnique.keyUnique)(e,n.fields,i[t]))throw new Error(`Duplicate value for ${n.type} key "${t}" on ${e}`)}},assertForeignKeysForUpdate=async(e,t,r,i)=>{for(const n of t.keys){if("foreign"!==n.type||1!==n.fields.length||!n.references||1!==n.references.length)continue;const t=n.fields[0];if(!Object.prototype.hasOwnProperty.call(i,t)||i[t]==r[t])continue;const{entity:o,property:s}=(0,_splitEntityProperty.splitEntityProperty)(n.references[0].fields[0]);if(!await(0,_foreignKeyExists.foreignKeyExists)(o,s,i[t]))throw new Error(`Foreign key "${t}" on ${e} references a nonexistent ${o}.${s} = ${i[t]}`)}},reconcileIndexes=async(e,t,r,i,n)=>{for(const o of e.keys){if(1!==o.fields.length)continue;const s=o.fields[0];Object.prototype.hasOwnProperty.call(i,s)&&i[s]!=t[s]&&(await(0,_removeFromIndex.removeFromIndex)(e.path,o,t[s],n),await(0,_updateIndex.updateIndex)(e.path,o,r[s],n))}},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/"),n=await(0,_retrieveRecord.retrieveRecord)(e),o=n.keys.find((e=>"primary"===e.type&&1===e.fields.length));if(!o)throw new Error(`Cannot update "${e}" without a single-field primary key`);const s=o.fields[0],a=applyFieldTypes(n,t),p=r[e]||[],c=[];for(const t of p){const r=await findEntryFile(n,s,t[s]);if(!r)throw new Error(`Could not find entry for ${e} where "${s}" = ${t[s]}`);const o=await(0,_retrieveFile.retrieveFile)(`${n.path}/${r}`);await assertUniqueForUpdate(e,n,o,a),await assertForeignKeysForUpdate(e,n,o,a);const p=Object.assign({},o,a);assertRequiredFields(e,n,p),await reconcileIndexes(n,o,p,a,r),await writeFile(`${i}${n.path}/${r}`,JSON.stringify(p,null,2)),c.push(p)}return r[e]=c,r};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"),_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