json-fs-query 3.1.4 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/browser/jsonFsQuery.js +2172 -5305
  2. package/browser/jsonFsQuery.min.js +1 -1
  3. package/dist/data/introspect/create.js +7 -22
  4. package/dist/data/introspect/create.min.js +1 -1
  5. package/dist/data/introspect/drop.js +5 -15
  6. package/dist/data/introspect/drop.min.js +1 -1
  7. package/dist/data/introspect/policies/foreignKeyExists.d.ts +2 -0
  8. package/dist/data/introspect/policies/foreignKeyExists.js +9 -3
  9. package/dist/data/introspect/policies/foreignKeyExists.min.js +1 -1
  10. package/dist/data/introspect/policies/hasDependents.d.ts +3 -3
  11. package/dist/data/introspect/policies/hasDependents.js +11 -3
  12. package/dist/data/introspect/policies/hasDependents.min.js +1 -1
  13. package/dist/data/introspect/policies/keyUnique.d.ts +3 -0
  14. package/dist/data/introspect/policies/keyUnique.js +10 -0
  15. package/dist/data/introspect/policies/keyUnique.min.js +1 -1
  16. package/dist/data/queries/deleteEntity.js +29 -18
  17. package/dist/data/queries/deleteEntity.min.js +1 -1
  18. package/dist/data/queries/insertEntity.js +4 -12
  19. package/dist/data/queries/insertEntity.min.js +1 -1
  20. package/dist/data/queries/joinEntity.d.ts +15 -0
  21. package/dist/data/queries/joinEntity.js +91 -15
  22. package/dist/data/queries/joinEntity.min.js +1 -1
  23. package/dist/data/queries/updateEntity.js +15 -10
  24. package/dist/data/queries/updateEntity.min.js +1 -1
  25. package/dist/data/storage/fileExists.d.ts +6 -0
  26. package/dist/data/storage/fileExists.js +18 -0
  27. package/dist/data/storage/fileExists.min.js +1 -0
  28. package/dist/data/storage/index.d.ts +16 -0
  29. package/dist/data/storage/index.js +61 -0
  30. package/dist/data/storage/index.min.js +1 -0
  31. package/dist/data/storage/makeDirectory.d.ts +6 -0
  32. package/dist/data/storage/makeDirectory.js +18 -0
  33. package/dist/data/storage/makeDirectory.min.js +1 -0
  34. package/dist/data/storage/nodeFiles.d.ts +6 -0
  35. package/dist/data/storage/nodeFiles.js +12 -0
  36. package/dist/data/storage/nodeFiles.min.js +1 -0
  37. package/dist/data/storage/readCache.d.ts +37 -0
  38. package/dist/data/storage/readCache.js +117 -0
  39. package/dist/data/storage/readCache.min.js +1 -0
  40. package/dist/data/storage/readJson.d.ts +8 -0
  41. package/dist/data/storage/readJson.js +25 -0
  42. package/dist/data/storage/readJson.min.js +1 -0
  43. package/dist/data/storage/removeDirectory.d.ts +6 -0
  44. package/dist/data/storage/removeDirectory.js +24 -0
  45. package/dist/data/storage/removeDirectory.min.js +1 -0
  46. package/dist/data/storage/removeFile.d.ts +7 -0
  47. package/dist/data/storage/removeFile.js +22 -0
  48. package/dist/data/storage/removeFile.min.js +1 -0
  49. package/dist/data/storage/storagePath.d.ts +11 -0
  50. package/dist/data/storage/storagePath.js +25 -0
  51. package/dist/data/storage/storagePath.min.js +1 -0
  52. package/dist/data/storage/writeJson.d.ts +8 -0
  53. package/dist/data/storage/writeJson.js +23 -0
  54. package/dist/data/storage/writeJson.min.js +1 -0
  55. package/dist/data/utilities/compareAs.d.ts +1 -0
  56. package/dist/data/utilities/compareAs.js +2 -2
  57. package/dist/data/utilities/compareAs.min.js +1 -1
  58. package/dist/data/utilities/databaseLock.d.ts +2 -0
  59. package/dist/data/utilities/databaseLock.js +5 -2
  60. package/dist/data/utilities/databaseLock.min.js +1 -1
  61. package/dist/data/utilities/deregisterRecord.js +4 -10
  62. package/dist/data/utilities/deregisterRecord.min.js +1 -1
  63. package/dist/data/utilities/indexHas.d.ts +13 -0
  64. package/dist/data/utilities/indexHas.js +48 -0
  65. package/dist/data/utilities/indexHas.min.js +1 -0
  66. package/dist/data/utilities/joinBases.d.ts +29 -0
  67. package/dist/data/utilities/joinBases.js +136 -0
  68. package/dist/data/utilities/joinBases.min.js +1 -0
  69. package/dist/data/utilities/joinKey.d.ts +17 -0
  70. package/dist/data/utilities/joinKey.js +31 -0
  71. package/dist/data/utilities/joinKey.min.js +1 -0
  72. package/dist/data/utilities/keyIndexFiles.d.ts +16 -0
  73. package/dist/data/utilities/keyIndexFiles.js +60 -0
  74. package/dist/data/utilities/keyIndexFiles.min.js +1 -0
  75. package/dist/data/utilities/recordExists.js +2 -7
  76. package/dist/data/utilities/recordExists.min.js +1 -1
  77. package/dist/data/utilities/registerRecord.js +4 -10
  78. package/dist/data/utilities/registerRecord.min.js +1 -1
  79. package/dist/data/utilities/removeFromIndex.js +4 -10
  80. package/dist/data/utilities/removeFromIndex.min.js +1 -1
  81. package/dist/data/utilities/retrieveFile.d.ts +5 -0
  82. package/dist/data/utilities/retrieveFile.js +7 -16
  83. package/dist/data/utilities/retrieveFile.min.js +1 -1
  84. package/dist/data/utilities/rowLookup.d.ts +38 -0
  85. package/dist/data/utilities/rowLookup.js +99 -0
  86. package/dist/data/utilities/rowLookup.min.js +1 -0
  87. package/dist/data/utilities/runQuery.js +1 -2
  88. package/dist/data/utilities/runQuery.min.js +1 -1
  89. package/dist/data/utilities/semiJoin.d.ts +27 -0
  90. package/dist/data/utilities/semiJoin.js +97 -0
  91. package/dist/data/utilities/semiJoin.min.js +1 -0
  92. package/dist/data/utilities/updateIndex.js +5 -14
  93. package/dist/data/utilities/updateIndex.min.js +1 -1
  94. package/package.json +4 -2
  95. package/dist/data/utilities/getJoinIndexedList.d.ts +0 -3
  96. package/dist/data/utilities/getJoinIndexedList.js +0 -76
  97. package/dist/data/utilities/getJoinIndexedList.min.js +0 -1
  98. package/dist/data/utilities/getMatchingIndexes.d.ts +0 -2
  99. package/dist/data/utilities/getMatchingIndexes.js +0 -28
  100. package/dist/data/utilities/getMatchingIndexes.min.js +0 -1
  101. package/dist/data/utilities/matchIndexedJoins.d.ts +0 -2
  102. package/dist/data/utilities/matchIndexedJoins.js +0 -63
  103. package/dist/data/utilities/matchIndexedJoins.min.js +0 -1
  104. package/dist/data/utilities/restrictIndexedList.d.ts +0 -2
  105. package/dist/data/utilities/restrictIndexedList.js +0 -45
  106. package/dist/data/utilities/restrictIndexedList.min.js +0 -1
  107. package/dist/data/utilities/useJoinClause.d.ts +0 -6
  108. package/dist/data/utilities/useJoinClause.js +0 -57
  109. package/dist/data/utilities/useJoinClause.min.js +0 -1
@@ -5,14 +5,13 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.create = void 0;
7
7
  require("core-js/modules/es.array.includes.js");
8
- require("core-js/modules/es.json.stringify.js");
9
8
  require("core-js/modules/esnext.iterator.constructor.js");
10
9
  require("core-js/modules/esnext.iterator.filter.js");
11
10
  require("core-js/modules/esnext.iterator.find.js");
12
11
  require("core-js/modules/esnext.iterator.some.js");
13
12
  var _record = require("./definitions/record");
14
13
  var _registerRecord = require("../utilities/registerRecord");
15
- var _config = require("../utilities/config");
14
+ var _storage = require("../storage");
16
15
  var _requireNode = require("../utilities/requireNode");
17
16
  var _typeCheck = require("./policies/typeCheck");
18
17
  const knownKeyProperties = ['type', 'fields', 'lookup', 'references'];
@@ -76,18 +75,10 @@ const assertValidDefinition = (recordName, recordContent) => {
76
75
  */
77
76
  const create = async (recordName, definition = [], keys = []) => {
78
77
  (0, _requireNode.requireNode)('create()');
79
- const {
80
- writeFile,
81
- mkdir
82
- } = require('fs/promises');
83
- const {
84
- existsSync
85
- } = require('fs');
86
- const databasePath = (0, _config.getSetting)('databasePath', 'database/');
87
78
  if (recordName === '__RECORDS') {
88
79
  return null;
89
80
  }
90
- if (existsSync(`${databasePath}${recordName}.json`)) {
81
+ if ((0, _storage.fileExists)(`${recordName}.json`)) {
91
82
  return null;
92
83
  }
93
84
  assertKnownKeyProperties(recordName, keys);
@@ -97,22 +88,16 @@ const create = async (recordName, definition = [], keys = []) => {
97
88
  keys
98
89
  });
99
90
  assertValidDefinition(recordName, recordContent);
100
- await mkdir(databasePath, {
101
- recursive: true
102
- });
103
- await writeFile(`${databasePath}${recordName}.json`, JSON.stringify(recordContent, null, 2));
104
- await mkdir(`${databasePath}${recordName}`, {
105
- recursive: true
106
- });
91
+ await (0, _storage.makeDirectory)();
92
+ await (0, _storage.writeJson)(`${recordName}.json`, recordContent);
93
+ await (0, _storage.makeDirectory)(recordName);
107
94
  // Each indexed key starts with an empty index, so the new record reads like any other before its first insert
108
95
  // (a where on a key used to fail with "Could not read __indexes/...", since the file only appeared with the first row)
109
96
  const indexedKeys = recordContent.keys.filter(key => key.lookup);
110
97
  if (indexedKeys.length) {
111
- await mkdir(`${databasePath}__indexes/${recordName}`, {
112
- recursive: true
113
- });
98
+ await (0, _storage.makeDirectory)(`__indexes/${recordName}`);
114
99
  for (const key of indexedKeys) {
115
- await writeFile(`${databasePath}__indexes/${recordName}/${key.lookup}`, '[]');
100
+ await (0, _storage.writeJson)(`__indexes/${recordName}/${key.lookup}`, []);
116
101
  }
117
102
  }
118
103
  await (0, _registerRecord.registerRecord)(recordName);
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.create=void 0,require("core-js/modules/es.array.includes.js"),require("core-js/modules/es.json.stringify.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.filter.js"),require("core-js/modules/esnext.iterator.find.js"),require("core-js/modules/esnext.iterator.some.js");var _record=require("./definitions/record"),_registerRecord=require("../utilities/registerRecord"),_config=require("../utilities/config"),_requireNode=require("../utilities/requireNode"),_typeCheck=require("./policies/typeCheck");const knownKeyProperties=["type","fields","lookup","references"],assertKnownKeyProperties=(e,r)=>{for(const i of r)for(const r of Object.keys(i))if(!knownKeyProperties.includes(r))throw new Error(`Unknown property "${r}" on ${i.type??"index"} key of ${e}; must be ${knownKeyProperties.join(", ")}`)},assertValidDefinition=(e,r)=>{for(const i of r.definition)if(Object.prototype.hasOwnProperty.call(i,"default"))try{(0,_typeCheck.typeCheck)(i.type,i.default,i.nullable)}catch(r){throw new Error(`Invalid default for field "${i.name}" of ${e}: ${r.message}`)}for(const i of r.keys){if(i.fields.length<1)throw new Error(`The ${i.type} key of ${e} must have at least one field`);for(const t of i.fields)if(!r.definition.some((e=>e.name===t)))throw new Error(`The ${i.type} key of ${e} names field "${t}", which isn't in the definition`)}const i=r.keys.find((e=>"primary"===e.type));for(const t of i?i.fields:[])if(r.definition.some((e=>e.name===t&&e.nullable)))throw new Error(`Primary key field "${t}" of ${e} can't be nullable`)},create=async(e,r=[],i=[])=>{(0,_requireNode.requireNode)("create()");const{writeFile:t,mkdir:o}=require("fs/promises"),{existsSync:s}=require("fs"),n=(0,_config.getSetting)("databasePath","database/");if("__RECORDS"===e)return null;if(s(`${n}${e}.json`))return null;assertKnownKeyProperties(e,i);const a=(0,_record.record)({path:e,definition:r,keys:i});assertValidDefinition(e,a),await o(n,{recursive:!0}),await t(`${n}${e}.json`,JSON.stringify(a,null,2)),await o(`${n}${e}`,{recursive:!0});const f=a.keys.filter((e=>e.lookup));if(f.length){await o(`${n}__indexes/${e}`,{recursive:!0});for(const r of f)await t(`${n}__indexes/${e}/${r.lookup}`,"[]")}return await(0,_registerRecord.registerRecord)(e),a};exports.create=create;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.create=void 0,require("core-js/modules/es.array.includes.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.filter.js"),require("core-js/modules/esnext.iterator.find.js"),require("core-js/modules/esnext.iterator.some.js");var _record=require("./definitions/record"),_registerRecord=require("../utilities/registerRecord"),_storage=require("../storage"),_requireNode=require("../utilities/requireNode"),_typeCheck=require("./policies/typeCheck");const knownKeyProperties=["type","fields","lookup","references"],assertKnownKeyProperties=(e,r)=>{for(const o of r)for(const r of Object.keys(o))if(!knownKeyProperties.includes(r))throw new Error(`Unknown property "${r}" on ${o.type??"index"} key of ${e}; must be ${knownKeyProperties.join(", ")}`)},assertValidDefinition=(e,r)=>{for(const o of r.definition)if(Object.prototype.hasOwnProperty.call(o,"default"))try{(0,_typeCheck.typeCheck)(o.type,o.default,o.nullable)}catch(r){throw new Error(`Invalid default for field "${o.name}" of ${e}: ${r.message}`)}for(const o of r.keys){if(o.fields.length<1)throw new Error(`The ${o.type} key of ${e} must have at least one field`);for(const t of o.fields)if(!r.definition.some((e=>e.name===t)))throw new Error(`The ${o.type} key of ${e} names field "${t}", which isn't in the definition`)}const o=r.keys.find((e=>"primary"===e.type));for(const t of o?o.fields:[])if(r.definition.some((e=>e.name===t&&e.nullable)))throw new Error(`Primary key field "${t}" of ${e} can't be nullable`)},create=async(e,r=[],o=[])=>{if((0,_requireNode.requireNode)("create()"),"__RECORDS"===e)return null;if((0,_storage.fileExists)(`${e}.json`))return null;assertKnownKeyProperties(e,o);const t=(0,_record.record)({path:e,definition:r,keys:o});assertValidDefinition(e,t),await(0,_storage.makeDirectory)(),await(0,_storage.writeJson)(`${e}.json`,t),await(0,_storage.makeDirectory)(e);const i=t.keys.filter((e=>e.lookup));if(i.length){await(0,_storage.makeDirectory)(`__indexes/${e}`);for(const r of i)await(0,_storage.writeJson)(`__indexes/${e}/${r.lookup}`,[])}return await(0,_registerRecord.registerRecord)(e),t};exports.create=create;
@@ -10,30 +10,20 @@ Object.defineProperty(exports, "__esModule", {
10
10
  exports.drop = void 0;
11
11
  var _retrieveFile = require("../utilities/retrieveFile");
12
12
  var _deregisterRecord = require("../utilities/deregisterRecord");
13
- var _config = require("../utilities/config");
14
13
  var _requireNode = require("../utilities/requireNode");
15
- const {
16
- rm
17
- } = require('fs/promises');
14
+ var _storage = require("../storage");
18
15
  const drop = async record => {
19
16
  (0, _requireNode.requireNode)('drop()');
20
- const databasePath = (0, _config.getSetting)('databasePath', 'database/');
21
17
  if (record === '__RECORDS') {
22
18
  return null;
23
19
  }
24
20
  const recordFile = await (0, _retrieveFile.retrieveFile)(`${record}.json`);
25
21
  for (const file of recordFile.entries) {
26
- await rm(`${databasePath}${recordFile.path}/${file}`);
22
+ await (0, _storage.removeFile)(`${recordFile.path}/${file}`);
27
23
  }
28
- await rm(`${databasePath}${recordFile.path}`, {
29
- recursive: true,
30
- force: true
31
- });
32
- await rm(`${databasePath}__indexes/${recordFile.path}`, {
33
- recursive: true,
34
- force: true
35
- });
36
- await rm(`${databasePath}${record}.json`);
24
+ await (0, _storage.removeDirectory)(recordFile.path);
25
+ await (0, _storage.removeDirectory)(`__indexes/${recordFile.path}`);
26
+ await (0, _storage.removeFile)(`${record}.json`);
37
27
  await (0, _deregisterRecord.deregisterRecord)(record);
38
28
  };
39
29
  exports.drop = drop;
@@ -1 +1 @@
1
- "use strict";require("core-js/modules/esnext.async-iterator.constructor.js"),require("core-js/modules/esnext.async-iterator.drop.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.drop.js"),Object.defineProperty(exports,"__esModule",{value:!0}),exports.drop=void 0;var _retrieveFile=require("../utilities/retrieveFile"),_deregisterRecord=require("../utilities/deregisterRecord"),_config=require("../utilities/config"),_requireNode=require("../utilities/requireNode");const{rm:rm}=require("fs/promises"),drop=async e=>{(0,_requireNode.requireNode)("drop()");const r=(0,_config.getSetting)("databasePath","database/");if("__RECORDS"===e)return null;const i=await(0,_retrieveFile.retrieveFile)(`${e}.json`);for(const e of i.entries)await rm(`${r}${i.path}/${e}`);await rm(`${r}${i.path}`,{recursive:!0,force:!0}),await rm(`${r}__indexes/${i.path}`,{recursive:!0,force:!0}),await rm(`${r}${e}.json`),await(0,_deregisterRecord.deregisterRecord)(e)};exports.drop=drop;
1
+ "use strict";require("core-js/modules/esnext.async-iterator.constructor.js"),require("core-js/modules/esnext.async-iterator.drop.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.drop.js"),Object.defineProperty(exports,"__esModule",{value:!0}),exports.drop=void 0;var _retrieveFile=require("../utilities/retrieveFile"),_deregisterRecord=require("../utilities/deregisterRecord"),_requireNode=require("../utilities/requireNode"),_storage=require("../storage");const drop=async e=>{if((0,_requireNode.requireNode)("drop()"),"__RECORDS"===e)return null;const r=await(0,_retrieveFile.retrieveFile)(`${e}.json`);for(const e of r.entries)await(0,_storage.removeFile)(`${r.path}/${e}`);await(0,_storage.removeDirectory)(r.path),await(0,_storage.removeDirectory)(`__indexes/${r.path}`),await(0,_storage.removeFile)(`${e}.json`),await(0,_deregisterRecord.deregisterRecord)(e)};exports.drop=drop;
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Check whether any record in the referenced entity has the given property set to the given
3
3
  * value - the mirror image of keyUnique(), which checks that no record already has a value.
4
+ * A referenced field with an index (a foreign key usually references a primary key) is answered from it; otherwise
5
+ * every row is read.
4
6
  * @param entity
5
7
  * @param property
6
8
  * @param value
@@ -8,22 +8,28 @@ var _retrieveRecords = require("../../utilities/retrieveRecords");
8
8
  var _where = require("../../queries/where");
9
9
  var _retrieveRecord = require("../../utilities/retrieveRecord");
10
10
  var _fieldType = require("../../utilities/fieldType");
11
+ var _indexHas = require("../../utilities/indexHas");
11
12
  /**
12
13
  * Check whether any record in the referenced entity has the given property set to the given
13
14
  * value - the mirror image of keyUnique(), which checks that no record already has a value.
15
+ * A referenced field with an index (a foreign key usually references a primary key) is answered from it; otherwise
16
+ * every row is read.
14
17
  * @param entity
15
18
  * @param property
16
19
  * @param value
17
20
  */
18
21
  const foreignKeyExists = async (entity, property, value) => {
22
+ const found = await (0, _indexHas.indexHas)(entity, property, value);
23
+ if (typeof found !== 'undefined') {
24
+ return found;
25
+ }
19
26
  const records = await (0, _retrieveRecords.retrieveRecords)(entity);
20
27
  const type = (0, _fieldType.fieldType)(await (0, _retrieveRecord.retrieveRecord)(entity), property);
21
- const found = (0, _where.where)(records, {
28
+ return (0, _where.where)(records, {
22
29
  property,
23
30
  comparator: '=',
24
31
  value,
25
32
  type
26
- });
27
- return found.length > 0;
33
+ }).length > 0;
28
34
  };
29
35
  exports.foreignKeyExists = foreignKeyExists;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.foreignKeyExists=void 0;var _retrieveRecords=require("../../utilities/retrieveRecords"),_where=require("../../queries/where"),_retrieveRecord=require("../../utilities/retrieveRecord"),_fieldType=require("../../utilities/fieldType");const foreignKeyExists=async(e,r,i)=>{const t=await(0,_retrieveRecords.retrieveRecords)(e),s=(0,_fieldType.fieldType)(await(0,_retrieveRecord.retrieveRecord)(e),r);return(0,_where.where)(t,{property:r,comparator:"=",value:i,type:s}).length>0};exports.foreignKeyExists=foreignKeyExists;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.foreignKeyExists=void 0;var _retrieveRecords=require("../../utilities/retrieveRecords"),_where=require("../../queries/where"),_retrieveRecord=require("../../utilities/retrieveRecord"),_fieldType=require("../../utilities/fieldType"),_indexHas=require("../../utilities/indexHas");const foreignKeyExists=async(e,r,i)=>{const t=await(0,_indexHas.indexHas)(e,r,i);if(void 0!==t)return t;const s=await(0,_retrieveRecords.retrieveRecords)(e),o=(0,_fieldType.fieldType)(await(0,_retrieveRecord.retrieveRecord)(e),r);return(0,_where.where)(s,{property:r,comparator:"=",value:i,type:o}).length>0};exports.foreignKeyExists=foreignKeyExists;
@@ -2,9 +2,9 @@
2
2
  * Check whether any other record, in any entity, still has a single-field foreign key pointing
3
3
  * at entity.property = value - the mirror image of foreignKeyExists(), which checks the opposite
4
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.
5
+ * delete or update that would otherwise leave a dangling reference behind. Looks at every record in
6
+ * the database (via the __RECORDS registry) for a foreign key to entity.property. A foreign key field with an index
7
+ * (each one gets one) is answered from it; otherwise that record's rows are read.
8
8
  * @param entity
9
9
  * @param property
10
10
  * @param value
@@ -10,13 +10,14 @@ var _retrieveRecords = require("../../utilities/retrieveRecords");
10
10
  var _where = require("../../queries/where");
11
11
  var _fieldType = require("../../utilities/fieldType");
12
12
  var _splitEntityProperty = require("../../utilities/parsers/splitEntityProperty");
13
+ var _indexHas = require("../../utilities/indexHas");
13
14
  /**
14
15
  * Check whether any other record, in any entity, still has a single-field foreign key pointing
15
16
  * at entity.property = value - the mirror image of foreignKeyExists(), which checks the opposite
16
17
  * direction (that a new/updated foreign key value points at something real). Used to RESTRICT a
17
- * delete or update that would otherwise leave a dangling reference behind. Scans every record in
18
- * the database via the __RECORDS registry, the same "no index needed, just look" approach as
19
- * keyUnique()/foreignKeyExists() - correctness over speed, matching this project's overall scale.
18
+ * delete or update that would otherwise leave a dangling reference behind. Looks at every record in
19
+ * the database (via the __RECORDS registry) for a foreign key to entity.property. A foreign key field with an index
20
+ * (each one gets one) is answered from it; otherwise that record's rows are read.
20
21
  * @param entity
21
22
  * @param property
22
23
  * @param value
@@ -37,6 +38,13 @@ const hasDependents = async (entity, property, value) => {
37
38
  if (refEntity !== entity || refProperty !== property) {
38
39
  continue;
39
40
  }
41
+ const indexed = await (0, _indexHas.indexHas)(otherEntity, key.fields[0], value);
42
+ if (typeof indexed !== 'undefined') {
43
+ if (indexed) {
44
+ return true;
45
+ }
46
+ continue;
47
+ }
40
48
  const records = await (0, _retrieveRecords.retrieveRecords)(otherEntity);
41
49
  const found = (0, _where.where)(records, {
42
50
  property: key.fields[0],
@@ -1 +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"),_fieldType=require("../../utilities/fieldType"),_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:n,property:c}=(0,_splitEntityProperty.splitEntityProperty)(s.references[0].fields[0]);if(n!==e||c!==r)continue;const p=await(0,_retrieveRecords.retrieveRecords)(i);if((0,_where.where)(p,{property:s.fields[0],comparator:"=",value:t,type:(0,_fieldType.fieldType)(o,s.fields[0])}).length)return!0}}return!1};exports.hasDependents=hasDependents;
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"),_fieldType=require("../../utilities/fieldType"),_splitEntityProperty=require("../../utilities/parsers/splitEntityProperty"),_indexHas=require("../../utilities/indexHas");const hasDependents=async(e,r,i)=>{const t=await(0,_describe.describe)();for(const s of t){const t=s.replace(/\.json$/,""),n=await(0,_retrieveRecord.retrieveRecord)(t);for(const s of n.keys){if("foreign"!==s.type||1!==s.fields.length||!s.references||1!==s.references.length)continue;const{entity:o,property:d}=(0,_splitEntityProperty.splitEntityProperty)(s.references[0].fields[0]);if(o!==e||d!==r)continue;const c=await(0,_indexHas.indexHas)(t,s.fields[0],i);if(void 0!==c){if(c)return!0;continue}const p=await(0,_retrieveRecords.retrieveRecords)(t);if((0,_where.where)(p,{property:s.fields[0],comparator:"=",value:i,type:(0,_fieldType.fieldType)(n,s.fields[0])}).length)return!0}}return!1};exports.hasDependents=hasDependents;
@@ -2,6 +2,9 @@
2
2
  * Check that no existing record already has this combination of values across all of the given
3
3
  * fields - a single field for a normal unique/primary key, or several for a composite one.
4
4
  * references and checkKeys are parallel arrays: references[i]'s value must equal checkKeys[i].
5
+ *
6
+ * One field with an index (every primary and unique key has one) is answered from the index; a composite key, or a
7
+ * field without one, reads every row.
5
8
  * @param entity
6
9
  * @param references
7
10
  * @param checkKeys
@@ -8,10 +8,14 @@ var _retrieveRecords = require("../../utilities/retrieveRecords");
8
8
  var _where = require("../../queries/where");
9
9
  var _retrieveRecord = require("../../utilities/retrieveRecord");
10
10
  var _fieldType = require("../../utilities/fieldType");
11
+ var _indexHas = require("../../utilities/indexHas");
11
12
  /**
12
13
  * Check that no existing record already has this combination of values across all of the given
13
14
  * fields - a single field for a normal unique/primary key, or several for a composite one.
14
15
  * references and checkKeys are parallel arrays: references[i]'s value must equal checkKeys[i].
16
+ *
17
+ * One field with an index (every primary and unique key has one) is answered from the index; a composite key, or a
18
+ * field without one, reads every row.
15
19
  * @param entity
16
20
  * @param references
17
21
  * @param checkKeys
@@ -20,6 +24,12 @@ const keyUnique = async (entity, references = [], checkKeys = []) => {
20
24
  if (!references.length || references.length !== checkKeys.length) {
21
25
  return false;
22
26
  }
27
+ if (references.length === 1) {
28
+ const found = await (0, _indexHas.indexHas)(entity, references[0], checkKeys[0]);
29
+ if (typeof found !== 'undefined') {
30
+ return !found;
31
+ }
32
+ }
23
33
  let records = await (0, _retrieveRecords.retrieveRecords)(entity);
24
34
  const record = await (0, _retrieveRecord.retrieveRecord)(entity);
25
35
  for (let i = 0; i < references.length; i++) {
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.keyUnique=void 0;var _retrieveRecords=require("../../utilities/retrieveRecords"),_where=require("../../queries/where"),_retrieveRecord=require("../../utilities/retrieveRecord"),_fieldType=require("../../utilities/fieldType");const keyUnique=async(e,r=[],t=[])=>{if(!r.length||r.length!==t.length)return!1;let i=await(0,_retrieveRecords.retrieveRecords)(e);const o=await(0,_retrieveRecord.retrieveRecord)(e);for(let e=0;e<r.length;e++)i=(0,_where.where)(i,{property:r[e],comparator:"=",value:t[e],type:(0,_fieldType.fieldType)(o,r[e])});return!i.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"),_retrieveRecord=require("../../utilities/retrieveRecord"),_fieldType=require("../../utilities/fieldType"),_indexHas=require("../../utilities/indexHas");const keyUnique=async(e,r=[],i=[])=>{if(!r.length||r.length!==i.length)return!1;if(1===r.length){const t=await(0,_indexHas.indexHas)(e,r[0],i[0]);if(void 0!==t)return!t}let t=await(0,_retrieveRecords.retrieveRecords)(e);const s=await(0,_retrieveRecord.retrieveRecord)(e);for(let e=0;e<r.length;e++)t=(0,_where.where)(t,{property:r[e],comparator:"=",value:i[e],type:(0,_fieldType.fieldType)(s,r[e])});return!t.length};exports.keyUnique=keyUnique;
@@ -4,8 +4,8 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.deleteEntity = void 0;
7
- require("core-js/modules/es.json.stringify.js");
8
7
  require("core-js/modules/esnext.iterator.constructor.js");
8
+ require("core-js/modules/esnext.iterator.filter.js");
9
9
  require("core-js/modules/esnext.iterator.find.js");
10
10
  require("core-js/modules/esnext.iterator.map.js");
11
11
  require("core-js/modules/esnext.set.add-all.js");
@@ -31,10 +31,8 @@ var _computeKeyValue = require("../utilities/computeKeyValue");
31
31
  var _fieldType = require("../utilities/fieldType");
32
32
  var _hasDependents = require("../introspect/policies/hasDependents");
33
33
  var _config = require("../utilities/config");
34
- const {
35
- rm,
36
- writeFile
37
- } = require('fs/promises');
34
+ var _keyIndexFiles = require("../utilities/keyIndexFiles");
35
+ var _storage = require("../storage");
38
36
  /**
39
37
  * RESTRICT: reject the whole delete (before removing anything) if any row being deleted is still
40
38
  * referenced by a single-field foreign key elsewhere. Only meaningful for a single-field primary
@@ -57,32 +55,45 @@ const assertNoDependents = async (entity, primaryKey, toDelete) => {
57
55
  };
58
56
  /**
59
57
  * A record object carries no memory of which entry file it was read from, and files are not
60
- * guaranteed to be named after any field's value, so the only reliable way to find a record's
61
- * file is to re-scan every entry and match it by its primary key value(s) - a single field, or a
62
- * composite of several for a multi-field primary key.
58
+ * guaranteed to be named after any field's value, so a deleted row's file is found by its primary
59
+ * key value(s) - a single field, or a composite of several for a multi-field primary key. The
60
+ * primary key's index says which files those are (each is read to confirm it); any deleted value
61
+ * the index didn't lead to, or a record without one, is found by looking at every entry, as before.
62
+ * Inserts and updates keep primary key values unique, so each value is one row: only a database
63
+ * edited by hand into two rows of one value, with an index listing just one, would keep the other
64
+ * (looking at every entry used to delete both).
63
65
  * @param entity
64
66
  * @param record
65
67
  * @param primaryKey
66
68
  * @param deletedValues
67
69
  */
68
70
  const removeMatchingEntries = async (entity, record, primaryKey, deletedValues) => {
69
- const databasePath = (0, _config.getSetting)('databasePath', 'database/');
70
- const remainingEntries = [];
71
- for (const fileName of record.entries) {
71
+ const removed = new Set();
72
+ const found = new Set();
73
+ const removeIfDeleted = async fileName => {
72
74
  const data = await (0, _retrieveFile.retrieveFile)(`${record.path}/${fileName}`);
73
- if (!deletedValues.has((0, _computeKeyValue.computeKeyValue)(primaryKey.fields, data))) {
74
- remainingEntries.push(fileName);
75
- continue;
75
+ const value = (0, _computeKeyValue.computeKeyValue)(primaryKey.fields, data);
76
+ if (!deletedValues.has(value)) {
77
+ return;
76
78
  }
77
- await rm(`${databasePath}${record.path}/${fileName}`);
79
+ await (0, _storage.removeFile)(`${record.path}/${fileName}`);
78
80
  for (const key of record.keys) {
79
81
  await (0, _removeFromIndex.removeFromIndex)(record.path, key, (0, _computeKeyValue.computeKeyValue)(key.fields, data), fileName, (0, _fieldType.keyValueType)(record, key));
80
82
  }
83
+ removed.add(fileName);
84
+ found.add(value);
85
+ };
86
+ for (const fileName of (await (0, _keyIndexFiles.keyIndexFiles)(record, primaryKey, value => deletedValues.has(value))) ?? []) {
87
+ await removeIfDeleted(fileName);
81
88
  }
82
- return remainingEntries;
89
+ if (found.size < deletedValues.size) {
90
+ for (const fileName of record.entries.filter(entry => !removed.has(entry))) {
91
+ await removeIfDeleted(fileName);
92
+ }
93
+ }
94
+ return record.entries.filter(fileName => !removed.has(fileName));
83
95
  };
84
96
  const deleteEntity = async (entity = '', dataSet = {}) => {
85
- const databasePath = (0, _config.getSetting)('databasePath', 'database/');
86
97
  const record = await (0, _retrieveRecord.retrieveRecord)(entity);
87
98
  const primaryKey = record.keys.find(key => key.type === 'primary');
88
99
  if (!primaryKey) {
@@ -95,7 +106,7 @@ const deleteEntity = async (entity = '', dataSet = {}) => {
95
106
  await assertNoDependents(entity, primaryKey, toDelete);
96
107
  const deletedValues = new Set(toDelete.map(row => (0, _computeKeyValue.computeKeyValue)(primaryKey.fields, row)));
97
108
  record.entries = await removeMatchingEntries(entity, record, primaryKey, deletedValues);
98
- await writeFile(`${databasePath}${record.path}.json`, JSON.stringify(record, null, 2));
109
+ await (0, _storage.writeJson)(`${record.path}.json`, record);
99
110
  return dataSet;
100
111
  };
101
112
  exports.deleteEntity = deleteEntity;
@@ -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"),_computeKeyValue=require("../utilities/computeKeyValue"),_fieldType=require("../utilities/fieldType"),_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,(0,_fieldType.keyValueType)(r,s))}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;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.deleteEntity=void 0,require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.filter.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"),_fieldType=require("../utilities/fieldType"),_hasDependents=require("../introspect/policies/hasDependents"),_config=require("../utilities/config"),_keyIndexFiles=require("../utilities/keyIndexFiles"),_storage=require("../storage");const 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=new Set,o=new Set,n=async e=>{const n=await(0,_retrieveFile.retrieveFile)(`${r.path}/${e}`),u=(0,_computeKeyValue.computeKeyValue)(s.fields,n);if(t.has(u)){await(0,_storage.removeFile)(`${r.path}/${e}`);for(const s of r.keys)await(0,_removeFromIndex.removeFromIndex)(r.path,s,(0,_computeKeyValue.computeKeyValue)(s.fields,n),e,(0,_fieldType.keyValueType)(r,s));i.add(e),o.add(u)}};for(const e of await(0,_keyIndexFiles.keyIndexFiles)(r,s,(e=>t.has(e)))??[])await n(e);if(o.size<t.size)for(const e of r.entries.filter((e=>!i.has(e))))await n(e);return r.entries.filter((e=>!i.has(e)))},deleteEntity=async(e="",r={})=>{const s=await(0,_retrieveRecord.retrieveRecord)(e),t=s.keys.find((e=>"primary"===e.type));if(!t)throw new Error(`Cannot delete from "${e}" without a primary key`);const i=r[e]||[];if(!i.length)return r;await assertNoDependents(e,t,i);const o=new Set(i.map((e=>(0,_computeKeyValue.computeKeyValue)(t.fields,e))));return s.entries=await removeMatchingEntries(0,s,t,o),await(0,_storage.writeJson)(`${s.path}.json`,s),r};exports.deleteEntity=deleteEntity;
@@ -4,7 +4,6 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.insertEntity = void 0;
7
- require("core-js/modules/es.json.stringify.js");
8
7
  require("core-js/modules/esnext.iterator.constructor.js");
9
8
  require("core-js/modules/esnext.iterator.find.js");
10
9
  require("core-js/modules/esnext.iterator.map.js");
@@ -19,11 +18,7 @@ var _foreignKeyExists = require("../introspect/policies/foreignKeyExists");
19
18
  var _splitEntityProperty = require("../utilities/parsers/splitEntityProperty");
20
19
  var _computeKeyValue = require("../utilities/computeKeyValue");
21
20
  var _fieldType = require("../utilities/fieldType");
22
- var _config = require("../utilities/config");
23
- const {
24
- writeFile,
25
- mkdir
26
- } = require('fs/promises');
21
+ var _storage = require("../storage");
27
22
  /**
28
23
  * Build one entity's stored values from the raw input values, generating any autoGenerate
29
24
  * fields and applying field defaults, then throw if a required field still has no value.
@@ -100,7 +95,6 @@ const assertForeignKeys = async (entity, record, entityValues) => {
100
95
  }
101
96
  };
102
97
  const insertEntity = async (entity = '', values = [], dataSet = {}) => {
103
- const databasePath = (0, _config.getSetting)('databasePath', 'database/');
104
98
  const record = await (0, _retrieveRecord.retrieveRecord)(entity);
105
99
  const primaryKey = record.keys.find(key => key.type === 'primary' && key.fields.length === 1);
106
100
  if (!Object.prototype.hasOwnProperty.call(dataSet, entity)) {
@@ -108,17 +102,15 @@ const insertEntity = async (entity = '', values = [], dataSet = {}) => {
108
102
  }
109
103
  // The record's folder can be missing even though the record exists: git doesn't keep empty folders, so a database
110
104
  // committed before its first insert comes back from a fresh clone without it
111
- await mkdir(`${databasePath}${record.path}`, {
112
- recursive: true
113
- });
105
+ await (0, _storage.makeDirectory)(record.path);
114
106
  for (const rowValues of values) {
115
107
  const entityValues = await buildEntityValues(entity, record, rowValues);
116
108
  await assertUnique(entity, record, entityValues);
117
109
  await assertForeignKeys(entity, record, entityValues);
118
110
  const fileName = `${primaryKey ? entityValues[primaryKey.fields[0]] : crypto.randomUUID()}.json`;
119
- await writeFile(`${databasePath}${record.path}/${fileName}`, JSON.stringify(entityValues, null, 2));
111
+ await (0, _storage.writeJson)(`${record.path}/${fileName}`, entityValues);
120
112
  record.entries.push(fileName);
121
- await writeFile(`${databasePath}${record.path}.json`, JSON.stringify(record, null, 2));
113
+ await (0, _storage.writeJson)(`${record.path}.json`, record);
122
114
  for (const key of record.keys) {
123
115
  await (0, _updateIndex.updateIndex)(record.path, key, (0, _computeKeyValue.computeKeyValue)(key.fields, entityValues), fileName, (0, _fieldType.keyValueType)(record, key));
124
116
  }
@@ -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"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.iterator.some.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"),_fieldType=require("../utilities/fieldType"),_config=require("../utilities/config");const{writeFile:writeFile,mkdir:mkdir}=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,s.nullable);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(t.some((e=>null===e)))continue;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)||null===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]=[]),await mkdir(`${r}${s.path}`,{recursive:!0});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,(0,_fieldType.keyValueType)(s,e));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/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"),_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"),_fieldType=require("../utilities/fieldType"),_storage=require("../storage");const buildEntityValues=async(e,t,r={})=>{const i={};for(const s of t.definition){let t=r[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)i[s.name]=(0,_typeCheck.typeCheck)(s.type,t,s.nullable);else if(!s.optional)throw new Error(`Missing required field "${s.name}" for ${e}`)}return i},assertUnique=async(e,t,r)=>{for(const i of t.keys){if("primary"!==i.type&&"unique"!==i.type)continue;const t=i.fields.map((e=>r[e]));if(t.some((e=>null===e)))continue;if(!await(0,_keyUnique.keyUnique)(e,i.fields,t))throw new Error(`Duplicate value for ${i.type} key "${i.fields.join(", ")}" on ${e}`)}},assertForeignKeys=async(e,t,r)=>{for(const i of t.keys){if("foreign"!==i.type||1!==i.fields.length||!i.references||1!==i.references.length)continue;const t=i.fields[0];if(!Object.prototype.hasOwnProperty.call(r,t)||null===r[t])continue;const{entity:s,property:o}=(0,_splitEntityProperty.splitEntityProperty)(i.references[0].fields[0]);if(!await(0,_foreignKeyExists.foreignKeyExists)(s,o,r[t]))throw new Error(`Foreign key "${t}" on ${e} references a nonexistent ${s}.${o} = ${r[t]}`)}},insertEntity=async(e="",t=[],r={})=>{const i=await(0,_retrieveRecord.retrieveRecord)(e),s=i.keys.find((e=>"primary"===e.type&&1===e.fields.length));Object.prototype.hasOwnProperty.call(r,e)||(r[e]=[]),await(0,_storage.makeDirectory)(i.path);for(const o of t){const t=await buildEntityValues(e,i,o);await assertUnique(e,i,t),await assertForeignKeys(e,i,t);const n=`${s?t[s.fields[0]]:crypto.randomUUID()}.json`;await(0,_storage.writeJson)(`${i.path}/${n}`,t),i.entries.push(n),await(0,_storage.writeJson)(`${i.path}.json`,i);for(const e of i.keys)await(0,_updateIndex.updateIndex)(i.path,e,(0,_computeKeyValue.computeKeyValue)(e.fields,t),n,(0,_fieldType.keyValueType)(i,e));r[e].push(t)}return r};exports.insertEntity=insertEntity;
@@ -1,2 +1,17 @@
1
+ /**
2
+ * Attach to each row of `entity` the rows of `joinEntity` its join reaches, through the `on` clauses in order.
3
+ *
4
+ * Each clause narrows both its records to the rows that match the other side: first the rows of the left one that
5
+ * match a right one, then the right ones that match those (see semiJoin for the order). A row's join starts from the
6
+ * row alone and from the rows read for every other record (see joinBases), so each record is read once for the whole
7
+ * join and each lookup is built once, rather than every loaded row being copied for each row joined.
8
+ * @param entity
9
+ * @param joinEntity
10
+ * @param dataSets
11
+ * @param joinClauses
12
+ * @param entityAlreadyResolved - Whether the where clause already narrowed entity's rows (then they aren't read again:
13
+ * an empty list reads as "not loaded yet", see readEntity).
14
+ * @param joinAlreadyResolved - The same for joinEntity.
15
+ */
1
16
  export declare const joinEntity: (entity: any, joinEntity: any, dataSets?: {}, joinClauses?: any[], entityAlreadyResolved?: boolean, joinAlreadyResolved?: boolean) => Promise<{}>;
2
17
  //# sourceMappingURL=joinEntity.d.ts.map
@@ -4,31 +4,107 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.joinEntity = void 0;
7
- var _siFunciona = _interopRequireDefault(require("si-funciona"));
7
+ require("core-js/modules/esnext.iterator.constructor.js");
8
+ require("core-js/modules/esnext.iterator.map.js");
9
+ require("core-js/modules/esnext.map.delete-all.js");
10
+ require("core-js/modules/esnext.map.every.js");
11
+ require("core-js/modules/esnext.map.filter.js");
12
+ require("core-js/modules/esnext.map.find.js");
13
+ require("core-js/modules/esnext.map.find-key.js");
14
+ require("core-js/modules/esnext.map.includes.js");
15
+ require("core-js/modules/esnext.map.key-of.js");
16
+ require("core-js/modules/esnext.map.map-keys.js");
17
+ require("core-js/modules/esnext.map.map-values.js");
18
+ require("core-js/modules/esnext.map.merge.js");
19
+ require("core-js/modules/esnext.map.reduce.js");
20
+ require("core-js/modules/esnext.map.some.js");
21
+ require("core-js/modules/esnext.map.update.js");
8
22
  var _readEntity = require("./readEntity");
9
- var _useJoinClause = require("../utilities/useJoinClause");
10
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
23
+ var _retrieveRecord = require("../utilities/retrieveRecord");
24
+ var _splitEntityProperty = require("../utilities/parsers/splitEntityProperty");
25
+ var _fieldType = require("../utilities/fieldType");
26
+ var _semiJoin = require("../utilities/semiJoin");
27
+ var _joinBases = require("../utilities/joinBases");
28
+ /**
29
+ * Each `on` clause with the types its two sides are compared by. Each record is read here, before its type is looked
30
+ * up, so a record that doesn't exist fails with "Could not read <record>.json", as it always has.
31
+ * @param joinClauses
32
+ */
33
+ const joinSteps = async joinClauses => {
34
+ const steps = [];
35
+ for (const {
36
+ propertyA,
37
+ comparator,
38
+ propertyB
39
+ } of joinClauses) {
40
+ const {
41
+ entity: entityA,
42
+ property: a
43
+ } = (0, _splitEntityProperty.splitEntityProperty)(propertyA);
44
+ const {
45
+ entity: entityB,
46
+ property: b
47
+ } = (0, _splitEntityProperty.splitEntityProperty)(propertyB);
48
+ steps.push({
49
+ entityA,
50
+ propertyA: a,
51
+ typeA: (0, _fieldType.fieldType)(await (0, _retrieveRecord.retrieveRecord)(entityA), a),
52
+ comparator,
53
+ entityB,
54
+ propertyB: b,
55
+ typeB: (0, _fieldType.fieldType)(await (0, _retrieveRecord.retrieveRecord)(entityB), b)
56
+ });
57
+ }
58
+ return steps;
59
+ };
60
+ /**
61
+ * What a row gets for its joined rows. Today every row is kept with the list of rows its join found (none is an empty
62
+ * list), and a condition on the joined record's fields then drops rows with none (whereTree): an inner join. This is
63
+ * the one place a join type (left, right, outer) would decide differently.
64
+ * @param matches - The joined rows the row's join found, shared with other rows' joins until copied here.
65
+ */
66
+ const joinedRows = matches => matches.map(match => structuredClone(match));
67
+ /**
68
+ * Attach to each row of `entity` the rows of `joinEntity` its join reaches, through the `on` clauses in order.
69
+ *
70
+ * Each clause narrows both its records to the rows that match the other side: first the rows of the left one that
71
+ * match a right one, then the right ones that match those (see semiJoin for the order). A row's join starts from the
72
+ * row alone and from the rows read for every other record (see joinBases), so each record is read once for the whole
73
+ * join and each lookup is built once, rather than every loaded row being copied for each row joined.
74
+ * @param entity
75
+ * @param joinEntity
76
+ * @param dataSets
77
+ * @param joinClauses
78
+ * @param entityAlreadyResolved - Whether the where clause already narrowed entity's rows (then they aren't read again:
79
+ * an empty list reads as "not loaded yet", see readEntity).
80
+ * @param joinAlreadyResolved - The same for joinEntity.
81
+ */
11
82
  const joinEntity = async (entity, joinEntity, dataSets = {}, joinClauses = [], entityAlreadyResolved = false, joinAlreadyResolved = false) => {
12
- // Same ambiguity readEntity() always has (can't tell "never loaded" from "loaded, zero rows
13
- // matched") - runQuery already resolved entity's rows via an indexed condition in that second
14
- // case, so entityAlreadyResolved tells this call not to reload and silently discard that result.
15
83
  if (!entityAlreadyResolved) {
16
84
  dataSets = await (0, _readEntity.readEntity)(entity, dataSets);
17
85
  }
18
86
  if (!joinClauses.length) {
19
87
  return dataSets;
20
88
  }
21
- // The same for joinEntity: an indexed condition on its field may have resolved its rows to none
22
- if (!joinAlreadyResolved) {
23
- dataSets = await (0, _readEntity.readEntity)(joinEntity, dataSets);
89
+ const steps = await joinSteps(joinClauses);
90
+ if (!dataSets[entity].length) {
91
+ return dataSets;
24
92
  }
25
- for (const anEntity in dataSets[entity]) {
26
- let filterJoins = _siFunciona.default.cloneObject(dataSets);
27
- filterJoins[entity] = [dataSets[entity][anEntity]];
28
- for (const joinClause of joinClauses) {
29
- filterJoins = await (0, _useJoinClause.useJoinClause)(joinClause, dataSets, filterJoins);
93
+ const bases = await (0, _joinBases.joinBases)(entity, joinEntity, dataSets, steps, joinAlreadyResolved);
94
+ for (const row of dataSets[entity]) {
95
+ const sides = new Map(bases);
96
+ sides.set(entity, {
97
+ rows: [row]
98
+ });
99
+ for (const step of steps) {
100
+ sides.set(step.entityA, {
101
+ rows: (0, _semiJoin.semiJoin)(sides.get(step.entityA), sides.get(step.entityB), step.propertyA, step.propertyB, step.comparator, step.typeA)
102
+ });
103
+ sides.set(step.entityB, {
104
+ rows: (0, _semiJoin.semiJoin)(sides.get(step.entityB), sides.get(step.entityA), step.propertyB, step.propertyA, step.comparator, step.typeB)
105
+ });
30
106
  }
31
- dataSets[entity][anEntity][joinEntity] = filterJoins[joinEntity];
107
+ row[joinEntity] = joinedRows(sides.get(joinEntity).rows);
32
108
  }
33
109
  return dataSets;
34
110
  };
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.joinEntity=void 0;var _siFunciona=_interopRequireDefault(require("si-funciona")),_readEntity=require("./readEntity"),_useJoinClause=require("../utilities/useJoinClause");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}const joinEntity=async(e,t,i={},n=[],r=!1,u=!1)=>{if(r||(i=await(0,_readEntity.readEntity)(e,i)),!n.length)return i;u||(i=await(0,_readEntity.readEntity)(t,i));for(const r in i[e]){let u=_siFunciona.default.cloneObject(i);u[e]=[i[e][r]];for(const e of n)u=await(0,_useJoinClause.useJoinClause)(e,i,u);i[e][r][t]=u[t]}return i};exports.joinEntity=joinEntity;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.joinEntity=void 0,require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.map.delete-all.js"),require("core-js/modules/esnext.map.every.js"),require("core-js/modules/esnext.map.filter.js"),require("core-js/modules/esnext.map.find.js"),require("core-js/modules/esnext.map.find-key.js"),require("core-js/modules/esnext.map.includes.js"),require("core-js/modules/esnext.map.key-of.js"),require("core-js/modules/esnext.map.map-keys.js"),require("core-js/modules/esnext.map.map-values.js"),require("core-js/modules/esnext.map.merge.js"),require("core-js/modules/esnext.map.reduce.js"),require("core-js/modules/esnext.map.some.js"),require("core-js/modules/esnext.map.update.js");var _readEntity=require("./readEntity"),_retrieveRecord=require("../utilities/retrieveRecord"),_splitEntityProperty=require("../utilities/parsers/splitEntityProperty"),_fieldType=require("../utilities/fieldType"),_semiJoin=require("../utilities/semiJoin"),_joinBases=require("../utilities/joinBases");const joinSteps=async e=>{const t=[];for(const{propertyA:r,comparator:s,propertyB:i}of e){const{entity:e,property:o}=(0,_splitEntityProperty.splitEntityProperty)(r),{entity:n,property:p}=(0,_splitEntityProperty.splitEntityProperty)(i);t.push({entityA:e,propertyA:o,typeA:(0,_fieldType.fieldType)(await(0,_retrieveRecord.retrieveRecord)(e),o),comparator:s,entityB:n,propertyB:p,typeB:(0,_fieldType.fieldType)(await(0,_retrieveRecord.retrieveRecord)(n),p)})}return t},joinedRows=e=>e.map((e=>structuredClone(e))),joinEntity=async(e,t,r={},s=[],i=!1,o=!1)=>{if(i||(r=await(0,_readEntity.readEntity)(e,r)),!s.length)return r;const n=await joinSteps(s);if(!r[e].length)return r;const p=await(0,_joinBases.joinBases)(e,t,r,n,o);for(const s of r[e]){const r=new Map(p);r.set(e,{rows:[s]});for(const e of n)r.set(e.entityA,{rows:(0,_semiJoin.semiJoin)(r.get(e.entityA),r.get(e.entityB),e.propertyA,e.propertyB,e.comparator,e.typeA)}),r.set(e.entityB,{rows:(0,_semiJoin.semiJoin)(r.get(e.entityB),r.get(e.entityA),e.propertyB,e.propertyA,e.comparator,e.typeB)});s[t]=joinedRows(r.get(t).rows)}return r};exports.joinEntity=joinEntity;