json-fs-query 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/README.md +41 -6
  2. package/browser/jsonFsQuery.js +28271 -0
  3. package/browser/jsonFsQuery.min.js +1 -0
  4. package/dist/data/introspect/create.js +6 -4
  5. package/dist/data/introspect/create.min.js +1 -1
  6. package/dist/data/introspect/drop.js +2 -2
  7. package/dist/data/introspect/drop.min.js +1 -1
  8. package/dist/data/introspect/policies/hasDependents.d.ts +13 -0
  9. package/dist/data/introspect/policies/hasDependents.js +52 -0
  10. package/dist/data/introspect/policies/hasDependents.min.js +1 -0
  11. package/dist/data/introspect/policies/keyGenerate.js +1 -1
  12. package/dist/data/introspect/policies/keyGenerate.min.js +1 -1
  13. package/dist/data/introspect/policies/keyUnique.d.ts +9 -1
  14. package/dist/data/introspect/policies/keyUnique.js +19 -9
  15. package/dist/data/introspect/policies/keyUnique.min.js +1 -1
  16. package/dist/data/queries/deleteEntity.js +36 -15
  17. package/dist/data/queries/deleteEntity.min.js +1 -1
  18. package/dist/data/queries/groupBy.js +22 -22
  19. package/dist/data/queries/groupBy.min.js +1 -1
  20. package/dist/data/queries/insertEntity.js +14 -12
  21. package/dist/data/queries/insertEntity.min.js +1 -1
  22. package/dist/data/queries/readEntity.js +1 -1
  23. package/dist/data/queries/readEntity.min.js +1 -1
  24. package/dist/data/queries/updateEntity.js +68 -42
  25. package/dist/data/queries/updateEntity.min.js +1 -1
  26. package/dist/data/queries/where.js +1 -1
  27. package/dist/data/queries/where.min.js +1 -1
  28. package/dist/data/utilities/computeKeyValue.d.ts +13 -0
  29. package/dist/data/utilities/computeKeyValue.js +26 -0
  30. package/dist/data/utilities/computeKeyValue.min.js +1 -0
  31. package/dist/data/utilities/config.d.ts +28 -0
  32. package/dist/data/utilities/config.js +45 -0
  33. package/dist/data/utilities/config.min.js +1 -0
  34. package/dist/data/utilities/deregisterRecord.js +2 -2
  35. package/dist/data/utilities/deregisterRecord.min.js +1 -1
  36. package/dist/data/utilities/getConditionIndexedList.js +1 -1
  37. package/dist/data/utilities/getConditionIndexedList.min.js +1 -1
  38. package/dist/data/utilities/getJoinIndexedList.js +2 -2
  39. package/dist/data/utilities/getJoinIndexedList.min.js +1 -1
  40. package/dist/data/utilities/getMatchingIndexes.js +2 -2
  41. package/dist/data/utilities/getMatchingIndexes.min.js +1 -1
  42. package/dist/data/utilities/matchIndexedJoins.js +2 -2
  43. package/dist/data/utilities/matchIndexedJoins.min.js +1 -1
  44. package/dist/data/utilities/reconcileJoin.js +26 -26
  45. package/dist/data/utilities/reconcileJoin.min.js +1 -1
  46. package/dist/data/utilities/reconcileJoins.js +26 -27
  47. package/dist/data/utilities/reconcileJoins.min.js +1 -1
  48. package/dist/data/utilities/registerRecord.js +2 -2
  49. package/dist/data/utilities/registerRecord.min.js +1 -1
  50. package/dist/data/utilities/removeFromIndex.js +2 -2
  51. package/dist/data/utilities/removeFromIndex.min.js +1 -1
  52. package/dist/data/utilities/restrictIndexedList.js +45 -45
  53. package/dist/data/utilities/restrictIndexedList.min.js +1 -1
  54. package/dist/data/utilities/retrieveFile.js +3 -3
  55. package/dist/data/utilities/retrieveFile.min.js +1 -1
  56. package/dist/data/utilities/retrieveIndexes.js +1 -1
  57. package/dist/data/utilities/retrieveIndexes.min.js +1 -1
  58. package/dist/data/utilities/updateIndex.js +2 -2
  59. package/dist/data/utilities/updateIndex.min.js +1 -1
  60. package/dist/data/utilities/useJoinClause.js +4 -4
  61. package/dist/data/utilities/useJoinClause.min.js +1 -1
  62. package/dist/index.d.ts +1 -0
  63. package/dist/index.js +7 -0
  64. package/dist/index.min.js +1 -1
  65. package/package.json +11 -4
  66. package/.browserslistrc +0 -1
  67. package/.nvmrc +0 -1
  68. package/MAIN.md +0 -15
  69. package/babel.config.js +0 -3
  70. package/build-tools.config.json +0 -30
  71. package/gulpfile.mjs +0 -15
  72. package/src/data/introspect/create.test.js +0 -67
  73. package/src/data/introspect/create.ts +0 -29
  74. package/src/data/introspect/definitions/field.test.js +0 -23
  75. package/src/data/introspect/definitions/field.ts +0 -48
  76. package/src/data/introspect/definitions/key.test.js +0 -100
  77. package/src/data/introspect/definitions/key.ts +0 -64
  78. package/src/data/introspect/definitions/keyReference.test.js +0 -8
  79. package/src/data/introspect/definitions/keyReference.ts +0 -23
  80. package/src/data/introspect/definitions/record.ts +0 -42
  81. package/src/data/introspect/describe.test.js +0 -36
  82. package/src/data/introspect/describe.ts +0 -9
  83. package/src/data/introspect/drop.test.js +0 -77
  84. package/src/data/introspect/drop.ts +0 -20
  85. package/src/data/introspect/index.ts +0 -8
  86. package/src/data/introspect/policies/foreignKeyExists.test.js +0 -44
  87. package/src/data/introspect/policies/foreignKeyExists.ts +0 -15
  88. package/src/data/introspect/policies/keyGenerate.ts +0 -11
  89. package/src/data/introspect/policies/keyUnique.ts +0 -11
  90. package/src/data/introspect/policies/typeCheck.test.js +0 -84
  91. package/src/data/introspect/policies/typeCheck.ts +0 -49
  92. package/src/data/introspect/policies/typeEnum.ts +0 -6
  93. package/src/data/introspect/policies/useDefault.ts +0 -6
  94. package/src/data/queries/deleteEntity.test.js +0 -86
  95. package/src/data/queries/deleteEntity.ts +0 -56
  96. package/src/data/queries/groupBy.test.js +0 -135
  97. package/src/data/queries/groupBy.ts +0 -16
  98. package/src/data/queries/insertEntity.test.js +0 -156
  99. package/src/data/queries/insertEntity.ts +0 -113
  100. package/src/data/queries/joinEntity.test.js +0 -109
  101. package/src/data/queries/joinEntity.ts +0 -20
  102. package/src/data/queries/mergeJoins.test.js +0 -174
  103. package/src/data/queries/mergeJoins.ts +0 -13
  104. package/src/data/queries/parser.test.js +0 -343
  105. package/src/data/queries/parser.ts +0 -24
  106. package/src/data/queries/query.test.js +0 -233
  107. package/src/data/queries/query.ts +0 -12
  108. package/src/data/queries/readEntity.test.js +0 -85
  109. package/src/data/queries/readEntity.ts +0 -8
  110. package/src/data/queries/sortBy.test.js +0 -131
  111. package/src/data/queries/sortBy.ts +0 -18
  112. package/src/data/queries/updateEntity.test.js +0 -228
  113. package/src/data/queries/updateEntity.ts +0 -162
  114. package/src/data/queries/where.test.js +0 -129
  115. package/src/data/queries/where.ts +0 -54
  116. package/src/data/utilities/deregisterRecord.test.js +0 -26
  117. package/src/data/utilities/deregisterRecord.ts +0 -24
  118. package/src/data/utilities/getConditionIndexedList.ts +0 -43
  119. package/src/data/utilities/getJoinIndexedList.ts +0 -53
  120. package/src/data/utilities/getMatchingIndexes.ts +0 -21
  121. package/src/data/utilities/matchIndexedJoins.ts +0 -45
  122. package/src/data/utilities/mergeJoin.test.js +0 -102
  123. package/src/data/utilities/mergeJoin.ts +0 -28
  124. package/src/data/utilities/parsers/findCommand.ts +0 -25
  125. package/src/data/utilities/parsers/findConditions.test.js +0 -38
  126. package/src/data/utilities/parsers/findConditions.ts +0 -32
  127. package/src/data/utilities/parsers/findGroupBy.ts +0 -20
  128. package/src/data/utilities/parsers/findInsertValues.test.js +0 -26
  129. package/src/data/utilities/parsers/findInsertValues.ts +0 -32
  130. package/src/data/utilities/parsers/findJoinClauses.ts +0 -29
  131. package/src/data/utilities/parsers/findJoinEntity.ts +0 -20
  132. package/src/data/utilities/parsers/findLimit.ts +0 -20
  133. package/src/data/utilities/parsers/findMergeJoins.ts +0 -27
  134. package/src/data/utilities/parsers/findOffset.ts +0 -20
  135. package/src/data/utilities/parsers/findSelects.ts +0 -31
  136. package/src/data/utilities/parsers/findSortClauses.ts +0 -31
  137. package/src/data/utilities/parsers/findUpdateValues.test.js +0 -37
  138. package/src/data/utilities/parsers/findUpdateValues.ts +0 -32
  139. package/src/data/utilities/parsers/isEntityProperty.ts +0 -1
  140. package/src/data/utilities/parsers/parseLiteralValue.test.js +0 -41
  141. package/src/data/utilities/parsers/parseLiteralValue.ts +0 -29
  142. package/src/data/utilities/parsers/parseQuery.ts +0 -73
  143. package/src/data/utilities/parsers/removeFromQuery.ts +0 -1
  144. package/src/data/utilities/parsers/splitEntityProperty.test.js +0 -8
  145. package/src/data/utilities/parsers/splitEntityProperty.ts +0 -17
  146. package/src/data/utilities/parsers/splitQueries.test.js +0 -33
  147. package/src/data/utilities/parsers/splitQueries.ts +0 -4
  148. package/src/data/utilities/reconcileJoin.test.js +0 -38
  149. package/src/data/utilities/reconcileJoin.ts +0 -23
  150. package/src/data/utilities/reconcileJoins.test.js +0 -160
  151. package/src/data/utilities/reconcileJoins.ts +0 -15
  152. package/src/data/utilities/registerRecord.test.js +0 -44
  153. package/src/data/utilities/registerRecord.ts +0 -44
  154. package/src/data/utilities/removeFromIndex.test.js +0 -44
  155. package/src/data/utilities/removeFromIndex.ts +0 -34
  156. package/src/data/utilities/restrictIndexedList.ts +0 -38
  157. package/src/data/utilities/retrieveFile.test.js +0 -49
  158. package/src/data/utilities/retrieveFile.ts +0 -19
  159. package/src/data/utilities/retrieveIndexes.ts +0 -17
  160. package/src/data/utilities/retrieveRecord.ts +0 -3
  161. package/src/data/utilities/retrieveRecords.test.js +0 -223
  162. package/src/data/utilities/retrieveRecords.ts +0 -16
  163. package/src/data/utilities/runQuery.test.js +0 -88
  164. package/src/data/utilities/runQuery.ts +0 -75
  165. package/src/data/utilities/updateIndex.test.js +0 -44
  166. package/src/data/utilities/updateIndex.ts +0 -45
  167. package/src/data/utilities/useJoinClause.test.js +0 -94
  168. package/src/data/utilities/useJoinClause.ts +0 -44
  169. package/src/global.d.ts +0 -1
  170. package/src/index.test.js +0 -52
  171. package/src/index.ts +0 -17
  172. package/test-data/__RECORDS.json +0 -32
  173. package/test-data/__indexes/exercise_muscle/fk_exercise_id.json +0 -14
  174. package/test-data/__indexes/exercise_muscle/fk_muscle_id.json +0 -14
  175. package/test-data/__indexes/exercise_muscle/pk__id.json +0 -14
  176. package/test-data/__indexes/exercises/pk__id.json +0 -14
  177. package/test-data/__indexes/muscles/pk__id.json +0 -58
  178. package/test-data/__indexes/workouts/fk_exercise_id.json +0 -23
  179. package/test-data/__indexes/workouts/index_date.json +0 -18
  180. package/test-data/__indexes/workouts/pk__id.json +0 -26
  181. package/test-data/copyDatabase.ts +0 -64
  182. package/test-data/exercise_muscle/bench_press_pectorals.json +0 -6
  183. package/test-data/exercise_muscle/deadlift_hamstrings.json +0 -6
  184. package/test-data/exercise_muscle/squat_quadriceps.json +0 -6
  185. package/test-data/exercise_muscle.json +0 -69
  186. package/test-data/exercises/bench_press.json +0 -5
  187. package/test-data/exercises/deadlift.json +0 -5
  188. package/test-data/exercises/squat.json +0 -5
  189. package/test-data/exercises.json +0 -34
  190. package/test-data/muscles/abdominal.json +0 -6
  191. package/test-data/muscles/biceps.json +0 -6
  192. package/test-data/muscles/calves.json +0 -6
  193. package/test-data/muscles/deltoids.json +0 -6
  194. package/test-data/muscles/erector_spinae.json +0 -6
  195. package/test-data/muscles/forearms.json +0 -6
  196. package/test-data/muscles/gluteus_maximus.json +0 -6
  197. package/test-data/muscles/hamstrings.json +0 -6
  198. package/test-data/muscles/latissimus_dorsi.json +0 -6
  199. package/test-data/muscles/obliques.json +0 -6
  200. package/test-data/muscles/pectorals.json +0 -6
  201. package/test-data/muscles/quadriceps.json +0 -6
  202. package/test-data/muscles/trapezius.json +0 -6
  203. package/test-data/muscles/triceps.json +0 -6
  204. package/test-data/muscles.json +0 -50
  205. package/test-data/workouts/2024-06-02-001.json +0 -10
  206. package/test-data/workouts/2024-06-02-002.json +0 -10
  207. package/test-data/workouts/2024-06-02-003.json +0 -10
  208. package/test-data/workouts/2024-06-05-001.json +0 -10
  209. package/test-data/workouts/2024-06-05-002.json +0 -10
  210. package/test-data/workouts/2024-06-05-003.json +0 -10
  211. package/test-data/workouts.json +0 -84
  212. package/tsconfig.json +0 -15
@@ -1,86 +0,0 @@
1
- import { create } from '../introspect/create'
2
- import { insertEntity } from './insertEntity'
3
- import { deleteEntity } from './deleteEntity'
4
- import { testHelpers } from 'js-build-tools/testHelpers'
5
-
6
- const { readFile } = require('fs/promises')
7
-
8
- const databasePath = 'delete-entity-database/'
9
- const gulpConfig = testHelpers.gulpConfig
10
- gulpConfig.set('databasePath', databasePath)
11
- testHelpers.setDefaults(databasePath)
12
-
13
- beforeEach(testHelpers.beforeEach)
14
-
15
- afterEach(testHelpers.afterEach)
16
-
17
- const definition = [
18
- { name: '_id', type: 'string', optional: false, autoGenerate: true },
19
- { name: 'bar', type: 'string', optional: true, autoGenerate: false }
20
- ]
21
-
22
- const keys = [
23
- { type: 'primary', fields: ['_id'], lookup: 'pk__id.json' }
24
- ]
25
-
26
- describe('deleteEntity', () => {
27
- test('does nothing when dataSet has no rows to delete', async () => {
28
- await create('foo', definition, keys)
29
- const inserted = await insertEntity('foo', [{ bar: 'keep' }], {})
30
- const result = await deleteEntity('foo', { foo: [] })
31
- expect(result).toEqual({ foo: [] })
32
- const recordFile = JSON.parse(await readFile(`${databasePath}foo.json`, 'utf8'))
33
- expect(recordFile.entries).toEqual([`${inserted.foo[0]._id}.json`])
34
- })
35
-
36
- test('throws when the record has no single-field primary key', async () => {
37
- await create('foo', definition, [])
38
- await expect(deleteEntity('foo', { foo: [{ _id: '1' }] }))
39
- .rejects.toThrow('Cannot delete from "foo" without a single-field primary key')
40
- })
41
-
42
- test('removes the matching entry file, entries[] entry, and index entry', async () => {
43
- await create('foo', definition, keys)
44
- const inserted = await insertEntity('foo', [{ bar: 'one' }, { bar: 'two' }], {})
45
- const [keep, remove] = inserted.foo
46
-
47
- await deleteEntity('foo', { foo: [remove] })
48
-
49
- expect(testHelpers.fileExists(`${databasePath}foo/${remove._id}.json`)).toBeFalsy()
50
- expect(testHelpers.fileExists(`${databasePath}foo/${keep._id}.json`)).toBeTruthy()
51
-
52
- const recordFile = JSON.parse(await readFile(`${databasePath}foo.json`, 'utf8'))
53
- expect(recordFile.entries).toEqual([`${keep._id}.json`])
54
-
55
- const indexFile = JSON.parse(await readFile(`${databasePath}__indexes/foo/pk__id.json`, 'utf8'))
56
- expect(indexFile).toEqual([{ value: keep._id, record: [`${keep._id}.json`] }])
57
- })
58
-
59
- test('finds the right file to delete even when a filename does not match the primary key', async () => {
60
- await create('foo', definition, keys)
61
- const inserted = await insertEntity('foo', [{ bar: 'one' }], {})
62
- const entryFile = `${inserted.foo[0]._id}.json`
63
- const { rename } = require('fs/promises')
64
- await rename(`${databasePath}foo/${entryFile}`, `${databasePath}foo/custom-name.json`)
65
- const recordFile = JSON.parse(await readFile(`${databasePath}foo.json`, 'utf8'))
66
- recordFile.entries = ['custom-name.json']
67
- const { writeFile } = require('fs/promises')
68
- await writeFile(`${databasePath}foo.json`, JSON.stringify(recordFile))
69
-
70
- await deleteEntity('foo', { foo: [inserted.foo[0]] })
71
-
72
- expect(testHelpers.fileExists(`${databasePath}foo/custom-name.json`)).toBeFalsy()
73
- const finalRecordFile = JSON.parse(await readFile(`${databasePath}foo.json`, 'utf8'))
74
- expect(finalRecordFile.entries).toEqual([])
75
- })
76
-
77
- test('deletes multiple rows from one call', async () => {
78
- await create('foo', definition, keys)
79
- const inserted = await insertEntity('foo', [{ bar: 'one' }, { bar: 'two' }, { bar: 'three' }], {})
80
-
81
- await deleteEntity('foo', { foo: [inserted.foo[0], inserted.foo[2]] })
82
-
83
- const recordFile = JSON.parse(await readFile(`${databasePath}foo.json`, 'utf8'))
84
- expect(recordFile.entries).toEqual([`${inserted.foo[1]._id}.json`])
85
- })
86
- })
@@ -1,56 +0,0 @@
1
- import { retrieveRecord } from '../utilities/retrieveRecord'
2
- import { retrieveFile } from '../utilities/retrieveFile'
3
- import { removeFromIndex } from '../utilities/removeFromIndex'
4
- import { recordDefinition } from '../introspect/definitions/record'
5
-
6
- const gulpConfig = require('js-build-tools/gulp.config')
7
- const { rm, writeFile } = require('fs/promises')
8
-
9
- /**
10
- * A record object carries no memory of which entry file it was read from, and files are not
11
- * guaranteed to be named after any field's value, so the only reliable way to find a record's
12
- * file is to re-scan every entry and match it by its primary key value.
13
- * @param entity
14
- * @param record
15
- * @param primaryField
16
- * @param deletedValues
17
- */
18
- const removeMatchingEntries = async (entity: string, record: recordDefinition, primaryField: string, deletedValues: Set<any>): Promise<string[]> => {
19
- const databasePath = gulpConfig.get('databasePath', 'database/')
20
- const remainingEntries = []
21
- for (const fileName of record.entries) {
22
- const data = await retrieveFile(`${record.path}/${fileName}`)
23
- if (!deletedValues.has(data[primaryField])) {
24
- remainingEntries.push(fileName)
25
- continue
26
- }
27
- await rm(`${databasePath}${record.path}/${fileName}`)
28
- for (const key of record.keys) {
29
- if (key.fields.length === 1) {
30
- await removeFromIndex(record.path, key, data[key.fields[0]], fileName)
31
- }
32
- }
33
- }
34
- return remainingEntries
35
- }
36
-
37
- export const deleteEntity = async (entity: string = '', dataSet: Object = {}): Promise<Object> => {
38
- const databasePath = gulpConfig.get('databasePath', 'database/')
39
- const record = await retrieveRecord(entity)
40
- const primaryKey = record.keys.find(key => key.type === 'primary' && key.fields.length === 1)
41
- if (!primaryKey) {
42
- throw new Error(`Cannot delete from "${entity}" without a single-field primary key`)
43
- }
44
- const primaryField = primaryKey.fields[0]
45
-
46
- const toDelete = dataSet[entity] || []
47
- if (!toDelete.length) {
48
- return dataSet
49
- }
50
- const deletedValues = new Set(toDelete.map(row => row[primaryField]))
51
-
52
- record.entries = await removeMatchingEntries(entity, record, primaryField, deletedValues)
53
- await writeFile(`${databasePath}${record.path}.json`, JSON.stringify(record, null, 2))
54
-
55
- return dataSet
56
- }
@@ -1,135 +0,0 @@
1
- import { groupBy } from './groupBy'
2
-
3
- describe('groupBy', () => {
4
- test('it groups by the given property', () => {
5
- const dataSet = [
6
- {
7
- _id: 2,
8
- exercise_id: 2,
9
- weight_effort: '155lbs',
10
- reps: 10,
11
- sets: 3,
12
- note: '',
13
- order: 1,
14
- date: '2024-06-02'
15
- },
16
- {
17
- _id: 1,
18
- exercise_id: 1,
19
- weight_effort: '115lbs',
20
- reps: 10,
21
- sets: 3,
22
- note: '',
23
- order: 0,
24
- date: '2024-06-02'
25
- },
26
- {
27
- _id: 1,
28
- exercise_id: 1,
29
- weight_effort: '115lbs',
30
- reps: 10,
31
- sets: 3,
32
- note: '',
33
- order: 0,
34
- date: '2024-06-05'
35
- },
36
- {
37
- _id: 3,
38
- exercise_id: 3,
39
- weight_effort: '155lbs',
40
- reps: 10,
41
- sets: 3,
42
- note: '',
43
- order: 2,
44
- date: '2024-06-02'
45
- },
46
- {
47
- _id: 2,
48
- exercise_id: 2,
49
- weight_effort: '155lbs',
50
- reps: 10,
51
- sets: 3,
52
- note: '',
53
- order: 1,
54
- date: '2024-06-05'
55
- },
56
- {
57
- _id: 3,
58
- exercise_id: 3,
59
- weight_effort: '155lbs',
60
- reps: 10,
61
- sets: 3,
62
- note: '',
63
- order: 2,
64
- date: '2024-06-05'
65
- }
66
- ]
67
- const result = groupBy(dataSet, 'date')
68
- expect(result).toEqual({
69
- '2024-06-02': [
70
- {
71
- _id: 2,
72
- exercise_id: 2,
73
- weight_effort: '155lbs',
74
- reps: 10,
75
- sets: 3,
76
- note: '',
77
- order: 1,
78
- date: '2024-06-02'
79
- },
80
- {
81
- _id: 1,
82
- exercise_id: 1,
83
- weight_effort: '115lbs',
84
- reps: 10,
85
- sets: 3,
86
- note: '',
87
- order: 0,
88
- date: '2024-06-02'
89
- },
90
- {
91
- _id: 3,
92
- exercise_id: 3,
93
- weight_effort: '155lbs',
94
- reps: 10,
95
- sets: 3,
96
- note: '',
97
- order: 2,
98
- date: '2024-06-02'
99
- }
100
- ],
101
- '2024-06-05': [
102
- {
103
- _id: 1,
104
- exercise_id: 1,
105
- weight_effort: '115lbs',
106
- reps: 10,
107
- sets: 3,
108
- note: '',
109
- order: 0,
110
- date: '2024-06-05'
111
- },
112
- {
113
- _id: 2,
114
- exercise_id: 2,
115
- weight_effort: '155lbs',
116
- reps: 10,
117
- sets: 3,
118
- note: '',
119
- order: 1,
120
- date: '2024-06-05'
121
- },
122
- {
123
- _id: 3,
124
- exercise_id: 3,
125
- weight_effort: '155lbs',
126
- reps: 10,
127
- sets: 3,
128
- note: '',
129
- order: 2,
130
- date: '2024-06-05'
131
- }
132
- ]
133
- })
134
- })
135
- })
@@ -1,16 +0,0 @@
1
- export const groupBy = (dataSet = [], groupProperty = '') => {
2
- if (!groupProperty) {
3
- return dataSet
4
- }
5
- return dataSet.reduce(
6
- (groupedData, data) => {
7
- const property = data[groupProperty]
8
- if (!groupedData.hasOwnProperty(property)) {
9
- groupedData[property] = []
10
- }
11
- groupedData[property].push(data)
12
- return groupedData
13
- },
14
- {}
15
- )
16
- }
@@ -1,156 +0,0 @@
1
- import { create } from '../introspect/create'
2
- import { insertEntity } from './insertEntity'
3
- import { testHelpers } from 'js-build-tools/testHelpers'
4
-
5
- const { readFile } = require('fs/promises')
6
-
7
- const databasePath = 'insert-entity-database/'
8
- const gulpConfig = testHelpers.gulpConfig
9
- gulpConfig.set('databasePath', databasePath)
10
- testHelpers.setDefaults(databasePath)
11
-
12
- beforeEach(testHelpers.beforeEach)
13
-
14
- afterEach(testHelpers.afterEach)
15
-
16
- const definition = [
17
- { name: '_id', type: 'string', optional: false, autoGenerate: true },
18
- { name: 'bar', type: 'string', optional: true, autoGenerate: false }
19
- ]
20
-
21
- const keys = [
22
- { type: 'primary', fields: ['_id'], lookup: 'pk__id.json' }
23
- ]
24
-
25
- describe('insertEntity', () => {
26
- test('inserts a new entity, generating its primary key', async () => {
27
- await create('foo', definition, keys)
28
- const dataSet = await insertEntity('foo', [{ bar: 'baz' }], {})
29
-
30
- expect(dataSet.foo).toHaveLength(1)
31
- const inserted = dataSet.foo[0]
32
- expect(typeof inserted._id).toBe('string')
33
- expect(inserted.bar).toBe('baz')
34
-
35
- expect(testHelpers.fileExists(`${databasePath}foo/${inserted._id}.json`)).toBeTruthy()
36
- const entryFile = JSON.parse(await readFile(`${databasePath}foo/${inserted._id}.json`, 'utf8'))
37
- expect(entryFile).toEqual(inserted)
38
-
39
- const recordFile = JSON.parse(await readFile(`${databasePath}foo.json`, 'utf8'))
40
- expect(recordFile.entries).toEqual([`${inserted._id}.json`])
41
-
42
- const indexFile = JSON.parse(await readFile(`${databasePath}__indexes/foo/pk__id.json`, 'utf8'))
43
- expect(indexFile).toEqual([{ value: inserted._id, record: [`${inserted._id}.json`] }])
44
- })
45
-
46
- test('inserts multiple entities from one call', async () => {
47
- await create('foo', definition, keys)
48
- const dataSet = await insertEntity('foo', [{ bar: 'one' }, { bar: 'two' }], {})
49
-
50
- expect(dataSet.foo).toHaveLength(2)
51
- const recordFile = JSON.parse(await readFile(`${databasePath}foo.json`, 'utf8'))
52
- expect(recordFile.entries).toHaveLength(2)
53
- })
54
-
55
- test('appends onto an existing dataSet entry instead of replacing it', async () => {
56
- await create('foo', definition, keys)
57
- const dataSet = await insertEntity('foo', [{ bar: 'one' }], { foo: [{ bar: 'existing' }] })
58
- expect(dataSet.foo).toHaveLength(2)
59
- expect(dataSet.foo[0]).toEqual({ bar: 'existing' })
60
- })
61
-
62
- test('applies a field default when no value is given', async () => {
63
- const definitionWithDefault = [
64
- { name: '_id', type: 'string', optional: false, autoGenerate: true },
65
- { name: 'status', type: 'string', optional: false, useDefault: true, defaultValue: 'pending' }
66
- ]
67
- await create('foo', definitionWithDefault, keys)
68
- const dataSet = await insertEntity('foo', [{}], {})
69
- expect(dataSet.foo[0].status).toBe('pending')
70
- })
71
-
72
- test('throws when a required field has no value, default, or autoGenerate', async () => {
73
- const definitionRequiringBar = [
74
- { name: '_id', type: 'string', optional: false, autoGenerate: true },
75
- { name: 'bar', type: 'string', optional: false, autoGenerate: false }
76
- ]
77
- await create('foo', definitionRequiringBar, keys)
78
- await expect(insertEntity('foo', [{}], {})).rejects.toThrow('Missing required field "bar" for foo')
79
- })
80
-
81
- test('throws on a duplicate primary key value instead of writing the file', async () => {
82
- const definitionExplicitId = [
83
- { name: '_id', type: 'string', optional: false, autoGenerate: false },
84
- { name: 'bar', type: 'string', optional: true }
85
- ]
86
- await create('foo', definitionExplicitId, keys)
87
- await insertEntity('foo', [{ _id: 'dupe', bar: 'first' }], {})
88
- await expect(insertEntity('foo', [{ _id: 'dupe', bar: 'second' }], {}))
89
- .rejects.toThrow('Duplicate value for primary key "_id" on foo')
90
- })
91
-
92
- test('coerces a value to match its field\'s declared type', async () => {
93
- const definitionWithInt = [
94
- { name: '_id', type: 'string', optional: false, autoGenerate: true },
95
- { name: 'reps', type: 'int', optional: false, autoGenerate: false }
96
- ]
97
- await create('foo', definitionWithInt, keys)
98
- const dataSet = await insertEntity('foo', [{ reps: '10' }], {})
99
- expect(dataSet.foo[0].reps).toBe(10)
100
- })
101
-
102
- test('rejects a value that does not match its field\'s declared type', async () => {
103
- const definitionWithInt = [
104
- { name: '_id', type: 'string', optional: false, autoGenerate: true },
105
- { name: 'reps', type: 'int', optional: false, autoGenerate: false }
106
- ]
107
- await create('foo', definitionWithInt, keys)
108
- await expect(insertEntity('foo', [{ reps: 'ten' }], {}))
109
- .rejects.toThrow('Value "ten" is not a valid int')
110
- })
111
-
112
- describe('foreign keys', () => {
113
- const exerciseDefinition = [
114
- { name: '_id', type: 'string', optional: false, autoGenerate: true },
115
- { name: 'name', type: 'string', optional: true, autoGenerate: false }
116
- ]
117
- const exerciseKeys = [
118
- { type: 'primary', fields: ['_id'], lookup: 'pk__id.json' }
119
- ]
120
- const workoutDefinition = [
121
- { name: '_id', type: 'string', optional: false, autoGenerate: true },
122
- { name: 'exercise_id', type: 'string', optional: true, autoGenerate: false }
123
- ]
124
- const workoutKeys = [
125
- { type: 'primary', fields: ['_id'], lookup: 'pk__id.json' },
126
- {
127
- type: 'foreign',
128
- fields: ['exercise_id'],
129
- lookup: 'fk_exercise_id.json',
130
- references: [{ fields: ['exercises._id'], lookup: 'exercises.json' }]
131
- }
132
- ]
133
-
134
- test('accepts a foreign key value that exists in the referenced record', async () => {
135
- await create('exercises', exerciseDefinition, exerciseKeys)
136
- const exercise = await insertEntity('exercises', [{ name: 'Bench Press' }], {})
137
- await create('workouts', workoutDefinition, workoutKeys)
138
- const result = await insertEntity('workouts', [{ exercise_id: exercise.exercises[0]._id }], {})
139
- expect(result.workouts[0].exercise_id).toBe(exercise.exercises[0]._id)
140
- })
141
-
142
- test('rejects a foreign key value that does not exist in the referenced record', async () => {
143
- await create('exercises', exerciseDefinition, exerciseKeys)
144
- await create('workouts', workoutDefinition, workoutKeys)
145
- await expect(insertEntity('workouts', [{ exercise_id: 'does-not-exist' }], {}))
146
- .rejects.toThrow('Foreign key "exercise_id" on workouts references a nonexistent exercises._id = does-not-exist')
147
- })
148
-
149
- test('skips the check when an optional foreign key field is omitted', async () => {
150
- await create('exercises', exerciseDefinition, exerciseKeys)
151
- await create('workouts', workoutDefinition, workoutKeys)
152
- const result = await insertEntity('workouts', [{}], {})
153
- expect(result.workouts[0].exercise_id).toBeUndefined()
154
- })
155
- })
156
- })
@@ -1,113 +0,0 @@
1
- import { retrieveRecord } from '../utilities/retrieveRecord'
2
- import { updateIndex } from '../utilities/updateIndex'
3
- import { keyGenerate } from '../introspect/policies/keyGenerate'
4
- import { keyUnique } from '../introspect/policies/keyUnique'
5
- import { useDefault } from '../introspect/policies/useDefault'
6
- import { typeCheck } from '../introspect/policies/typeCheck'
7
- import { foreignKeyExists } from '../introspect/policies/foreignKeyExists'
8
- import { splitEntityProperty } from '../utilities/parsers/splitEntityProperty'
9
- import { recordDefinition } from '../introspect/definitions/record'
10
-
11
- const gulpConfig = require('js-build-tools/gulp.config')
12
- const { writeFile } = require('fs/promises')
13
-
14
- /**
15
- * Build one entity's stored values from the raw input values, generating any autoGenerate
16
- * fields and applying field defaults, then throw if a required field still has no value.
17
- * @param entity
18
- * @param record
19
- * @param rowValues
20
- */
21
- const buildEntityValues = async (entity: string, record: recordDefinition, rowValues: Object = {}): Promise<Object> => {
22
- const entityValues = {}
23
- for (const field of record.definition) {
24
- let value = rowValues[field.name]
25
- if (field.autoGenerate) {
26
- value = await keyGenerate(entity, [field.name])
27
- } else if (field.hasOwnProperty('default')) {
28
- value = useDefault(field['default'], value)
29
- }
30
- if (typeof value === 'undefined') {
31
- if (!field.optional) {
32
- throw new Error(`Missing required field "${field.name}" for ${entity}`)
33
- }
34
- continue
35
- }
36
- entityValues[field.name] = typeCheck(field.type, value)
37
- }
38
- return entityValues
39
- }
40
-
41
- /**
42
- * Reject the insert if it collides with an existing primary or unique single-field key value.
43
- * @param entity
44
- * @param record
45
- * @param entityValues
46
- */
47
- const assertUnique = async (entity: string, record: recordDefinition, entityValues: Object): Promise<void> => {
48
- for (const key of record.keys) {
49
- if ((key.type !== 'primary' && key.type !== 'unique') || key.fields.length !== 1) {
50
- continue
51
- }
52
- const isUnique = await keyUnique(entity, key.fields, entityValues[key.fields[0]])
53
- if (!isUnique) {
54
- throw new Error(`Duplicate value for ${key.type} key "${key.fields[0]}" on ${entity}`)
55
- }
56
- }
57
- }
58
-
59
- /**
60
- * Reject the insert if a single-field foreign key's value doesn't exist in the record it
61
- * references.
62
- * @param entity
63
- * @param record
64
- * @param entityValues
65
- */
66
- const assertForeignKeys = async (entity: string, record: recordDefinition, entityValues: Object): Promise<void> => {
67
- for (const key of record.keys) {
68
- if (key.type !== 'foreign' || key.fields.length !== 1 || !key.references || key.references.length !== 1) {
69
- continue
70
- }
71
- const field = key.fields[0]
72
- if (!entityValues.hasOwnProperty(field)) {
73
- continue
74
- }
75
- const { entity: refEntity, property: refProperty } = splitEntityProperty(key.references[0].fields[0])
76
- const exists = await foreignKeyExists(refEntity, refProperty, entityValues[field])
77
- if (!exists) {
78
- throw new Error(`Foreign key "${field}" on ${entity} references a nonexistent ${refEntity}.${refProperty} = ${entityValues[field]}`)
79
- }
80
- }
81
- }
82
-
83
- export const insertEntity = async (entity: string = '', values: Object[] = [], dataSet: Object = {}): Promise<Object> => {
84
- const databasePath = gulpConfig.get('databasePath', 'database/')
85
- const record = await retrieveRecord(entity)
86
- const primaryKey = record.keys.find(key => key.type === 'primary' && key.fields.length === 1)
87
-
88
- if (!dataSet.hasOwnProperty(entity)) {
89
- dataSet[entity] = []
90
- }
91
-
92
- for (const rowValues of values) {
93
- const entityValues = await buildEntityValues(entity, record, rowValues)
94
- await assertUnique(entity, record, entityValues)
95
- await assertForeignKeys(entity, record, entityValues)
96
-
97
- const fileName = `${primaryKey ? entityValues[primaryKey.fields[0]] : crypto.randomUUID()}.json`
98
- await writeFile(`${databasePath}${record.path}/${fileName}`, JSON.stringify(entityValues, null, 2))
99
-
100
- record.entries.push(fileName)
101
- await writeFile(`${databasePath}${record.path}.json`, JSON.stringify(record, null, 2))
102
-
103
- for (const key of record.keys) {
104
- if (key.fields.length === 1) {
105
- await updateIndex(record.path, key, entityValues[key.fields[0]], fileName)
106
- }
107
- }
108
-
109
- dataSet[entity].push(entityValues)
110
- }
111
-
112
- return dataSet
113
- }
@@ -1,109 +0,0 @@
1
- import { joinEntity } from './joinEntity'
2
- import { testHelpers } from 'js-build-tools/testHelpers'
3
- import { copyDatabase } from '../../../test-data/copyDatabase'
4
-
5
- const databasePath = 'join-entity-database/'
6
- const gulpConfig = testHelpers.gulpConfig
7
- gulpConfig.set('databasePath', databasePath)
8
- testHelpers.setDefaults(databasePath)
9
-
10
- beforeEach(
11
- () => testHelpers.beforeEach()
12
- .then(() => copyDatabase(databasePath))
13
- )
14
-
15
- afterEach(testHelpers.afterEach)
16
-
17
- describe('joinEntity', () => {
18
- test('it will find the joins', async () => {
19
- const joinClauses = [
20
- {
21
- and: undefined,
22
- propertyA: 'workouts.exercise_id',
23
- comparator: '=',
24
- propertyB: 'exercises._id'
25
- },
26
- {
27
- and: 'and',
28
- propertyA: 'exercises._id',
29
- comparator: '=',
30
- propertyB: 'exercise_muscle.exercise_id'
31
- },
32
- {
33
- and: 'and',
34
- propertyA: 'exercise_muscle.muscle_id',
35
- comparator: '=',
36
- propertyB: 'muscles._id'
37
- }
38
- ]
39
- const result = await joinEntity('workouts', 'muscles', {}, joinClauses)
40
- expect(result['workouts']).toEqual([
41
- {
42
- _id: 1,
43
- exercise_id: 1,
44
- weight_effort: '115lbs',
45
- reps: 10,
46
- sets: 3,
47
- note: '',
48
- order: 0,
49
- date: '2024-06-02',
50
- muscles: [{ _id: 11, name: 'Pectorals', alias: 'chest', image: '' }]
51
- },
52
- {
53
- _id: 2,
54
- exercise_id: 2,
55
- weight_effort: '155lbs',
56
- reps: 10,
57
- sets: 3,
58
- note: '',
59
- order: 1,
60
- date: '2024-06-02',
61
- muscles: [{ _id: 8, name: 'Hamstrings', alias: 'abs', image: '' }]
62
- },
63
- {
64
- _id: 3,
65
- exercise_id: 3,
66
- weight_effort: '155lbs',
67
- reps: 10,
68
- sets: 3,
69
- note: '',
70
- order: 2,
71
- date: '2024-06-02',
72
- muscles: [{ _id: 12, name: 'Quadriceps', alias: 'quads', image: '' }]
73
- },
74
- {
75
- _id: 4,
76
- exercise_id: 1,
77
- weight_effort: '120lbs',
78
- reps: 10,
79
- sets: 3,
80
- note: '',
81
- order: 0,
82
- date: '2024-06-05',
83
- muscles: [{ _id: 11, name: 'Pectorals', alias: 'chest', image: '' }]
84
- },
85
- {
86
- _id: 5,
87
- exercise_id: 2,
88
- weight_effort: '160lbs',
89
- reps: 10,
90
- sets: 3,
91
- note: '',
92
- order: 1,
93
- date: '2024-06-05',
94
- muscles: [{ _id: 8, name: 'Hamstrings', alias: 'abs', image: '' }]
95
- },
96
- {
97
- _id: 6,
98
- exercise_id: 3,
99
- weight_effort: '160lbs',
100
- reps: 10,
101
- sets: 3,
102
- note: '',
103
- order: 2,
104
- date: '2024-06-05',
105
- muscles: [{ _id: 12, name: 'Quadriceps', alias: 'quads', image: '' }]
106
- }
107
- ])
108
- })
109
- })