json-fs-query 0.1.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.
- package/.browserslistrc +1 -0
- package/.nvmrc +1 -0
- package/LICENSE +674 -0
- package/MAIN.md +15 -0
- package/README.md +63 -0
- package/babel.config.js +3 -0
- package/build-tools.config.json +30 -0
- package/dist/data/introspect/create.d.ts +11 -0
- package/dist/data/introspect/create.js +45 -0
- package/dist/data/introspect/create.min.js +1 -0
- package/dist/data/introspect/definitions/field.d.ts +28 -0
- package/dist/data/introspect/definitions/field.js +36 -0
- package/dist/data/introspect/definitions/field.min.js +1 -0
- package/dist/data/introspect/definitions/key.d.ts +26 -0
- package/dist/data/introspect/definitions/key.js +53 -0
- package/dist/data/introspect/definitions/key.min.js +1 -0
- package/dist/data/introspect/definitions/keyReference.d.ts +18 -0
- package/dist/data/introspect/definitions/keyReference.js +20 -0
- package/dist/data/introspect/definitions/keyReference.min.js +1 -0
- package/dist/data/introspect/definitions/record.d.ts +26 -0
- package/dist/data/introspect/definitions/record.js +32 -0
- package/dist/data/introspect/definitions/record.min.js +1 -0
- package/dist/data/introspect/describe.d.ts +2 -0
- package/dist/data/introspect/describe.js +15 -0
- package/dist/data/introspect/describe.min.js +1 -0
- package/dist/data/introspect/drop.d.ts +2 -0
- package/dist/data/introspect/drop.js +37 -0
- package/dist/data/introspect/drop.min.js +1 -0
- package/dist/data/introspect/index.d.ts +8 -0
- package/dist/data/introspect/index.js +30 -0
- package/dist/data/introspect/index.min.js +1 -0
- package/dist/data/introspect/policies/foreignKeyExists.d.ts +9 -0
- package/dist/data/introspect/policies/foreignKeyExists.js +25 -0
- package/dist/data/introspect/policies/foreignKeyExists.min.js +1 -0
- package/dist/data/introspect/policies/keyGenerate.d.ts +2 -0
- package/dist/data/introspect/policies/keyGenerate.js +17 -0
- package/dist/data/introspect/policies/keyGenerate.min.js +1 -0
- package/dist/data/introspect/policies/keyUnique.d.ts +2 -0
- package/dist/data/introspect/policies/keyUnique.js +21 -0
- package/dist/data/introspect/policies/keyUnique.min.js +1 -0
- package/dist/data/introspect/policies/typeCheck.d.ts +12 -0
- package/dist/data/introspect/policies/typeCheck.js +57 -0
- package/dist/data/introspect/policies/typeCheck.min.js +1 -0
- package/dist/data/introspect/policies/typeEnum.d.ts +2 -0
- package/dist/data/introspect/policies/typeEnum.js +14 -0
- package/dist/data/introspect/policies/typeEnum.min.js +1 -0
- package/dist/data/introspect/policies/useDefault.d.ts +2 -0
- package/dist/data/introspect/policies/useDefault.js +13 -0
- package/dist/data/introspect/policies/useDefault.min.js +1 -0
- package/dist/data/queries/deleteEntity.d.ts +2 -0
- package/dist/data/queries/deleteEntity.js +79 -0
- package/dist/data/queries/deleteEntity.min.js +1 -0
- package/dist/data/queries/groupBy.d.ts +2 -0
- package/dist/data/queries/groupBy.js +22 -0
- package/dist/data/queries/groupBy.min.js +1 -0
- package/dist/data/queries/insertEntity.d.ts +2 -0
- package/dist/data/queries/insertEntity.js +115 -0
- package/dist/data/queries/insertEntity.min.js +1 -0
- package/dist/data/queries/joinEntity.d.ts +2 -0
- package/dist/data/queries/joinEntity.js +27 -0
- package/dist/data/queries/joinEntity.min.js +1 -0
- package/dist/data/queries/mergeJoins.d.ts +2 -0
- package/dist/data/queries/mergeJoins.js +19 -0
- package/dist/data/queries/mergeJoins.min.js +1 -0
- package/dist/data/queries/parser.d.ts +2 -0
- package/dist/data/queries/parser.js +30 -0
- package/dist/data/queries/parser.min.js +1 -0
- package/dist/data/queries/query.d.ts +2 -0
- package/dist/data/queries/query.js +18 -0
- package/dist/data/queries/query.min.js +1 -0
- package/dist/data/queries/readEntity.d.ts +2 -0
- package/dist/data/queries/readEntity.js +14 -0
- package/dist/data/queries/readEntity.min.js +1 -0
- package/dist/data/queries/sortBy.d.ts +2 -0
- package/dist/data/queries/sortBy.js +25 -0
- package/dist/data/queries/sortBy.min.js +1 -0
- package/dist/data/queries/updateEntity.d.ts +2 -0
- package/dist/data/queries/updateEntity.js +164 -0
- package/dist/data/queries/updateEntity.min.js +1 -0
- package/dist/data/queries/where.d.ts +7 -0
- package/dist/data/queries/where.js +56 -0
- package/dist/data/queries/where.min.js +1 -0
- package/dist/data/utilities/deregisterRecord.d.ts +8 -0
- package/dist/data/utilities/deregisterRecord.js +34 -0
- package/dist/data/utilities/deregisterRecord.min.js +1 -0
- package/dist/data/utilities/getConditionIndexedList.d.ts +3 -0
- package/dist/data/utilities/getConditionIndexedList.js +47 -0
- package/dist/data/utilities/getConditionIndexedList.min.js +1 -0
- package/dist/data/utilities/getJoinIndexedList.d.ts +3 -0
- package/dist/data/utilities/getJoinIndexedList.js +59 -0
- package/dist/data/utilities/getJoinIndexedList.min.js +1 -0
- package/dist/data/utilities/getMatchingIndexes.d.ts +2 -0
- package/dist/data/utilities/getMatchingIndexes.js +28 -0
- package/dist/data/utilities/getMatchingIndexes.min.js +1 -0
- package/dist/data/utilities/matchIndexedJoins.d.ts +2 -0
- package/dist/data/utilities/matchIndexedJoins.js +61 -0
- package/dist/data/utilities/matchIndexedJoins.min.js +1 -0
- package/dist/data/utilities/mergeJoin.d.ts +2 -0
- package/dist/data/utilities/mergeJoin.js +44 -0
- package/dist/data/utilities/mergeJoin.min.js +1 -0
- package/dist/data/utilities/parsers/findCommand.d.ts +10 -0
- package/dist/data/utilities/parsers/findCommand.js +25 -0
- package/dist/data/utilities/parsers/findCommand.min.js +1 -0
- package/dist/data/utilities/parsers/findConditions.d.ts +15 -0
- package/dist/data/utilities/parsers/findConditions.js +28 -0
- package/dist/data/utilities/parsers/findConditions.min.js +1 -0
- package/dist/data/utilities/parsers/findGroupBy.d.ts +9 -0
- package/dist/data/utilities/parsers/findGroupBy.js +22 -0
- package/dist/data/utilities/parsers/findGroupBy.min.js +1 -0
- package/dist/data/utilities/parsers/findInsertValues.d.ts +13 -0
- package/dist/data/utilities/parsers/findInsertValues.js +34 -0
- package/dist/data/utilities/parsers/findInsertValues.min.js +1 -0
- package/dist/data/utilities/parsers/findJoinClauses.d.ts +13 -0
- package/dist/data/utilities/parsers/findJoinClauses.js +27 -0
- package/dist/data/utilities/parsers/findJoinClauses.min.js +1 -0
- package/dist/data/utilities/parsers/findJoinEntity.d.ts +9 -0
- package/dist/data/utilities/parsers/findJoinEntity.js +22 -0
- package/dist/data/utilities/parsers/findJoinEntity.min.js +1 -0
- package/dist/data/utilities/parsers/findLimit.d.ts +9 -0
- package/dist/data/utilities/parsers/findLimit.js +22 -0
- package/dist/data/utilities/parsers/findLimit.min.js +1 -0
- package/dist/data/utilities/parsers/findMergeJoins.d.ts +12 -0
- package/dist/data/utilities/parsers/findMergeJoins.js +26 -0
- package/dist/data/utilities/parsers/findMergeJoins.min.js +1 -0
- package/dist/data/utilities/parsers/findOffset.d.ts +9 -0
- package/dist/data/utilities/parsers/findOffset.js +22 -0
- package/dist/data/utilities/parsers/findOffset.min.js +1 -0
- package/dist/data/utilities/parsers/findSelects.d.ts +12 -0
- package/dist/data/utilities/parsers/findSelects.js +30 -0
- package/dist/data/utilities/parsers/findSelects.min.js +1 -0
- package/dist/data/utilities/parsers/findSortClauses.d.ts +12 -0
- package/dist/data/utilities/parsers/findSortClauses.js +30 -0
- package/dist/data/utilities/parsers/findSortClauses.min.js +1 -0
- package/dist/data/utilities/parsers/findUpdateValues.d.ts +15 -0
- package/dist/data/utilities/parsers/findUpdateValues.js +34 -0
- package/dist/data/utilities/parsers/findUpdateValues.min.js +1 -0
- package/dist/data/utilities/parsers/isEntityProperty.d.ts +2 -0
- package/dist/data/utilities/parsers/isEntityProperty.js +8 -0
- package/dist/data/utilities/parsers/isEntityProperty.min.js +1 -0
- package/dist/data/utilities/parsers/parseLiteralValue.d.ts +12 -0
- package/dist/data/utilities/parsers/parseLiteralValue.js +34 -0
- package/dist/data/utilities/parsers/parseLiteralValue.min.js +1 -0
- package/dist/data/utilities/parsers/parseQuery.d.ts +33 -0
- package/dist/data/utilities/parsers/parseQuery.js +51 -0
- package/dist/data/utilities/parsers/parseQuery.min.js +1 -0
- package/dist/data/utilities/parsers/removeFromQuery.d.ts +2 -0
- package/dist/data/utilities/parsers/removeFromQuery.js +8 -0
- package/dist/data/utilities/parsers/removeFromQuery.min.js +1 -0
- package/dist/data/utilities/parsers/splitEntityProperty.d.ts +5 -0
- package/dist/data/utilities/parsers/splitEntityProperty.js +23 -0
- package/dist/data/utilities/parsers/splitEntityProperty.min.js +1 -0
- package/dist/data/utilities/parsers/splitQueries.d.ts +2 -0
- package/dist/data/utilities/parsers/splitQueries.js +11 -0
- package/dist/data/utilities/parsers/splitQueries.min.js +1 -0
- package/dist/data/utilities/reconcileJoin.d.ts +2 -0
- package/dist/data/utilities/reconcileJoin.js +26 -0
- package/dist/data/utilities/reconcileJoin.min.js +1 -0
- package/dist/data/utilities/reconcileJoins.d.ts +2 -0
- package/dist/data/utilities/reconcileJoins.js +27 -0
- package/dist/data/utilities/reconcileJoins.min.js +1 -0
- package/dist/data/utilities/registerRecord.d.ts +8 -0
- package/dist/data/utilities/registerRecord.js +67 -0
- package/dist/data/utilities/registerRecord.min.js +1 -0
- package/dist/data/utilities/removeFromIndex.d.ts +14 -0
- package/dist/data/utilities/removeFromIndex.js +46 -0
- package/dist/data/utilities/removeFromIndex.min.js +1 -0
- package/dist/data/utilities/restrictIndexedList.d.ts +2 -0
- package/dist/data/utilities/restrictIndexedList.js +45 -0
- package/dist/data/utilities/restrictIndexedList.min.js +1 -0
- package/dist/data/utilities/retrieveFile.d.ts +2 -0
- package/dist/data/utilities/retrieveFile.js +28 -0
- package/dist/data/utilities/retrieveFile.min.js +1 -0
- package/dist/data/utilities/retrieveIndexes.d.ts +2 -0
- package/dist/data/utilities/retrieveIndexes.js +24 -0
- package/dist/data/utilities/retrieveIndexes.min.js +1 -0
- package/dist/data/utilities/retrieveRecord.d.ts +2 -0
- package/dist/data/utilities/retrieveRecord.js +9 -0
- package/dist/data/utilities/retrieveRecord.min.js +1 -0
- package/dist/data/utilities/retrieveRecords.d.ts +2 -0
- package/dist/data/utilities/retrieveRecords.js +21 -0
- package/dist/data/utilities/retrieveRecords.min.js +1 -0
- package/dist/data/utilities/runQuery.d.ts +2 -0
- package/dist/data/utilities/runQuery.js +72 -0
- package/dist/data/utilities/runQuery.min.js +1 -0
- package/dist/data/utilities/updateIndex.d.ts +17 -0
- package/dist/data/utilities/updateIndex.js +56 -0
- package/dist/data/utilities/updateIndex.min.js +1 -0
- package/dist/data/utilities/useJoinClause.d.ts +6 -0
- package/dist/data/utilities/useJoinClause.js +51 -0
- package/dist/data/utilities/useJoinClause.min.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +35 -0
- package/dist/index.min.js +1 -0
- package/gulpfile.mjs +15 -0
- package/package.json +44 -0
- package/src/data/introspect/create.test.js +67 -0
- package/src/data/introspect/create.ts +29 -0
- package/src/data/introspect/definitions/field.test.js +23 -0
- package/src/data/introspect/definitions/field.ts +48 -0
- package/src/data/introspect/definitions/key.test.js +100 -0
- package/src/data/introspect/definitions/key.ts +64 -0
- package/src/data/introspect/definitions/keyReference.test.js +8 -0
- package/src/data/introspect/definitions/keyReference.ts +23 -0
- package/src/data/introspect/definitions/record.ts +42 -0
- package/src/data/introspect/describe.test.js +36 -0
- package/src/data/introspect/describe.ts +9 -0
- package/src/data/introspect/drop.test.js +77 -0
- package/src/data/introspect/drop.ts +20 -0
- package/src/data/introspect/index.ts +8 -0
- package/src/data/introspect/policies/foreignKeyExists.test.js +44 -0
- package/src/data/introspect/policies/foreignKeyExists.ts +15 -0
- package/src/data/introspect/policies/keyGenerate.ts +11 -0
- package/src/data/introspect/policies/keyUnique.ts +11 -0
- package/src/data/introspect/policies/typeCheck.test.js +84 -0
- package/src/data/introspect/policies/typeCheck.ts +49 -0
- package/src/data/introspect/policies/typeEnum.ts +6 -0
- package/src/data/introspect/policies/useDefault.ts +6 -0
- package/src/data/queries/deleteEntity.test.js +86 -0
- package/src/data/queries/deleteEntity.ts +56 -0
- package/src/data/queries/groupBy.test.js +135 -0
- package/src/data/queries/groupBy.ts +16 -0
- package/src/data/queries/insertEntity.test.js +156 -0
- package/src/data/queries/insertEntity.ts +113 -0
- package/src/data/queries/joinEntity.test.js +109 -0
- package/src/data/queries/joinEntity.ts +20 -0
- package/src/data/queries/mergeJoins.test.js +174 -0
- package/src/data/queries/mergeJoins.ts +13 -0
- package/src/data/queries/parser.test.js +343 -0
- package/src/data/queries/parser.ts +24 -0
- package/src/data/queries/query.test.js +233 -0
- package/src/data/queries/query.ts +12 -0
- package/src/data/queries/readEntity.test.js +85 -0
- package/src/data/queries/readEntity.ts +8 -0
- package/src/data/queries/sortBy.test.js +131 -0
- package/src/data/queries/sortBy.ts +18 -0
- package/src/data/queries/updateEntity.test.js +228 -0
- package/src/data/queries/updateEntity.ts +162 -0
- package/src/data/queries/where.test.js +129 -0
- package/src/data/queries/where.ts +54 -0
- package/src/data/utilities/deregisterRecord.test.js +26 -0
- package/src/data/utilities/deregisterRecord.ts +24 -0
- package/src/data/utilities/getConditionIndexedList.ts +43 -0
- package/src/data/utilities/getJoinIndexedList.ts +53 -0
- package/src/data/utilities/getMatchingIndexes.ts +21 -0
- package/src/data/utilities/matchIndexedJoins.ts +45 -0
- package/src/data/utilities/mergeJoin.test.js +102 -0
- package/src/data/utilities/mergeJoin.ts +28 -0
- package/src/data/utilities/parsers/findCommand.ts +25 -0
- package/src/data/utilities/parsers/findConditions.test.js +38 -0
- package/src/data/utilities/parsers/findConditions.ts +32 -0
- package/src/data/utilities/parsers/findGroupBy.ts +20 -0
- package/src/data/utilities/parsers/findInsertValues.test.js +26 -0
- package/src/data/utilities/parsers/findInsertValues.ts +32 -0
- package/src/data/utilities/parsers/findJoinClauses.ts +29 -0
- package/src/data/utilities/parsers/findJoinEntity.ts +20 -0
- package/src/data/utilities/parsers/findLimit.ts +20 -0
- package/src/data/utilities/parsers/findMergeJoins.ts +27 -0
- package/src/data/utilities/parsers/findOffset.ts +20 -0
- package/src/data/utilities/parsers/findSelects.ts +31 -0
- package/src/data/utilities/parsers/findSortClauses.ts +31 -0
- package/src/data/utilities/parsers/findUpdateValues.test.js +37 -0
- package/src/data/utilities/parsers/findUpdateValues.ts +32 -0
- package/src/data/utilities/parsers/isEntityProperty.ts +1 -0
- package/src/data/utilities/parsers/parseLiteralValue.test.js +41 -0
- package/src/data/utilities/parsers/parseLiteralValue.ts +29 -0
- package/src/data/utilities/parsers/parseQuery.ts +73 -0
- package/src/data/utilities/parsers/removeFromQuery.ts +1 -0
- package/src/data/utilities/parsers/splitEntityProperty.test.js +8 -0
- package/src/data/utilities/parsers/splitEntityProperty.ts +17 -0
- package/src/data/utilities/parsers/splitQueries.test.js +33 -0
- package/src/data/utilities/parsers/splitQueries.ts +4 -0
- package/src/data/utilities/reconcileJoin.test.js +38 -0
- package/src/data/utilities/reconcileJoin.ts +23 -0
- package/src/data/utilities/reconcileJoins.test.js +160 -0
- package/src/data/utilities/reconcileJoins.ts +15 -0
- package/src/data/utilities/registerRecord.test.js +44 -0
- package/src/data/utilities/registerRecord.ts +44 -0
- package/src/data/utilities/removeFromIndex.test.js +44 -0
- package/src/data/utilities/removeFromIndex.ts +34 -0
- package/src/data/utilities/restrictIndexedList.ts +38 -0
- package/src/data/utilities/retrieveFile.test.js +49 -0
- package/src/data/utilities/retrieveFile.ts +19 -0
- package/src/data/utilities/retrieveIndexes.ts +17 -0
- package/src/data/utilities/retrieveRecord.ts +3 -0
- package/src/data/utilities/retrieveRecords.test.js +223 -0
- package/src/data/utilities/retrieveRecords.ts +16 -0
- package/src/data/utilities/runQuery.test.js +88 -0
- package/src/data/utilities/runQuery.ts +75 -0
- package/src/data/utilities/updateIndex.test.js +44 -0
- package/src/data/utilities/updateIndex.ts +45 -0
- package/src/data/utilities/useJoinClause.test.js +94 -0
- package/src/data/utilities/useJoinClause.ts +44 -0
- package/src/global.d.ts +1 -0
- package/src/index.test.js +52 -0
- package/src/index.ts +17 -0
- package/test-data/__RECORDS.json +32 -0
- package/test-data/__indexes/exercise_muscle/fk_exercise_id.json +14 -0
- package/test-data/__indexes/exercise_muscle/fk_muscle_id.json +14 -0
- package/test-data/__indexes/exercise_muscle/pk__id.json +14 -0
- package/test-data/__indexes/exercises/pk__id.json +14 -0
- package/test-data/__indexes/muscles/pk__id.json +58 -0
- package/test-data/__indexes/workouts/fk_exercise_id.json +23 -0
- package/test-data/__indexes/workouts/index_date.json +18 -0
- package/test-data/__indexes/workouts/pk__id.json +26 -0
- package/test-data/copyDatabase.ts +64 -0
- package/test-data/exercise_muscle/bench_press_pectorals.json +6 -0
- package/test-data/exercise_muscle/deadlift_hamstrings.json +6 -0
- package/test-data/exercise_muscle/squat_quadriceps.json +6 -0
- package/test-data/exercise_muscle.json +69 -0
- package/test-data/exercises/bench_press.json +5 -0
- package/test-data/exercises/deadlift.json +5 -0
- package/test-data/exercises/squat.json +5 -0
- package/test-data/exercises.json +34 -0
- package/test-data/muscles/abdominal.json +6 -0
- package/test-data/muscles/biceps.json +6 -0
- package/test-data/muscles/calves.json +6 -0
- package/test-data/muscles/deltoids.json +6 -0
- package/test-data/muscles/erector_spinae.json +6 -0
- package/test-data/muscles/forearms.json +6 -0
- package/test-data/muscles/gluteus_maximus.json +6 -0
- package/test-data/muscles/hamstrings.json +6 -0
- package/test-data/muscles/latissimus_dorsi.json +6 -0
- package/test-data/muscles/obliques.json +6 -0
- package/test-data/muscles/pectorals.json +6 -0
- package/test-data/muscles/quadriceps.json +6 -0
- package/test-data/muscles/trapezius.json +6 -0
- package/test-data/muscles/triceps.json +6 -0
- package/test-data/muscles.json +50 -0
- package/test-data/workouts/2024-06-02-001.json +10 -0
- package/test-data/workouts/2024-06-02-002.json +10 -0
- package/test-data/workouts/2024-06-02-003.json +10 -0
- package/test-data/workouts/2024-06-05-001.json +10 -0
- package/test-data/workouts/2024-06-05-002.json +10 -0
- package/test-data/workouts/2024-06-05-003.json +10 -0
- package/test-data/workouts.json +84 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { sortBy } from './sortBy'
|
|
2
|
+
|
|
3
|
+
describe('sortBy', () => {
|
|
4
|
+
test('applies sorting', () => {
|
|
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 = sortBy(dataSet, [{ property: 'date', direction: 'desc' }, { property: 'order', direction: 'asc' }])
|
|
68
|
+
expect(result).toEqual([
|
|
69
|
+
{
|
|
70
|
+
_id: 1,
|
|
71
|
+
exercise_id: 1,
|
|
72
|
+
weight_effort: '115lbs',
|
|
73
|
+
reps: 10,
|
|
74
|
+
sets: 3,
|
|
75
|
+
note: '',
|
|
76
|
+
order: 0,
|
|
77
|
+
date: '2024-06-05'
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
_id: 2,
|
|
81
|
+
exercise_id: 2,
|
|
82
|
+
weight_effort: '155lbs',
|
|
83
|
+
reps: 10,
|
|
84
|
+
sets: 3,
|
|
85
|
+
note: '',
|
|
86
|
+
order: 1,
|
|
87
|
+
date: '2024-06-05'
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
_id: 3,
|
|
91
|
+
exercise_id: 3,
|
|
92
|
+
weight_effort: '155lbs',
|
|
93
|
+
reps: 10,
|
|
94
|
+
sets: 3,
|
|
95
|
+
note: '',
|
|
96
|
+
order: 2,
|
|
97
|
+
date: '2024-06-05'
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
_id: 1,
|
|
101
|
+
exercise_id: 1,
|
|
102
|
+
weight_effort: '115lbs',
|
|
103
|
+
reps: 10,
|
|
104
|
+
sets: 3,
|
|
105
|
+
note: '',
|
|
106
|
+
order: 0,
|
|
107
|
+
date: '2024-06-02'
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
_id: 2,
|
|
111
|
+
exercise_id: 2,
|
|
112
|
+
weight_effort: '155lbs',
|
|
113
|
+
reps: 10,
|
|
114
|
+
sets: 3,
|
|
115
|
+
note: '',
|
|
116
|
+
order: 1,
|
|
117
|
+
date: '2024-06-02'
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
_id: 3,
|
|
121
|
+
exercise_id: 3,
|
|
122
|
+
weight_effort: '155lbs',
|
|
123
|
+
reps: 10,
|
|
124
|
+
sets: 3,
|
|
125
|
+
note: '',
|
|
126
|
+
order: 2,
|
|
127
|
+
date: '2024-06-02'
|
|
128
|
+
}
|
|
129
|
+
])
|
|
130
|
+
})
|
|
131
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const sortBy = (dataSet = [], sortClauses = []) => {
|
|
2
|
+
if (!sortClauses.length) {
|
|
3
|
+
return dataSet
|
|
4
|
+
}
|
|
5
|
+
dataSet.sort((a, b) => {
|
|
6
|
+
for (let sortClause of sortClauses) {
|
|
7
|
+
const property = sortClause.property
|
|
8
|
+
const direction = sortClause.direction
|
|
9
|
+
if (a[property] == b[property]) {
|
|
10
|
+
continue
|
|
11
|
+
}
|
|
12
|
+
const result = a[property] < b[property] ? -1 : 1
|
|
13
|
+
return direction === 'asc' ? result : result * -1
|
|
14
|
+
}
|
|
15
|
+
return 0
|
|
16
|
+
})
|
|
17
|
+
return dataSet
|
|
18
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { create } from '../introspect/create'
|
|
2
|
+
import { insertEntity } from './insertEntity'
|
|
3
|
+
import { updateEntity } from './updateEntity'
|
|
4
|
+
import { testHelpers } from 'js-build-tools/testHelpers'
|
|
5
|
+
|
|
6
|
+
const { readFile } = require('fs/promises')
|
|
7
|
+
|
|
8
|
+
const databasePath = 'update-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
|
+
{ name: 'note', type: 'string', optional: true, autoGenerate: false }
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
const keys = [
|
|
24
|
+
{ type: 'primary', fields: ['_id'], lookup: 'pk__id.json' }
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
describe('updateEntity', () => {
|
|
28
|
+
test('does nothing when dataSet has no rows to update', async () => {
|
|
29
|
+
await create('foo', definition, keys)
|
|
30
|
+
const result = await updateEntity('foo', { bar: 'changed' }, { foo: [] })
|
|
31
|
+
expect(result).toEqual({ foo: [] })
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('throws when the record has no single-field primary key', async () => {
|
|
35
|
+
await create('foo', definition, [])
|
|
36
|
+
await expect(updateEntity('foo', { bar: 'x' }, { foo: [{ _id: '1' }] }))
|
|
37
|
+
.rejects.toThrow('Cannot update "foo" without a single-field primary key')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('throws when no entry matches the row being updated', async () => {
|
|
41
|
+
await create('foo', definition, keys)
|
|
42
|
+
await expect(updateEntity('foo', { bar: 'x' }, { foo: [{ _id: 'missing' }] }))
|
|
43
|
+
.rejects.toThrow('Could not find entry for foo where "_id" = missing')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('merges the given values onto the existing entry file, leaving other fields intact', async () => {
|
|
47
|
+
await create('foo', definition, keys)
|
|
48
|
+
const inserted = await insertEntity('foo', [{ bar: 'one', note: 'keep me' }], {})
|
|
49
|
+
const row = inserted.foo[0]
|
|
50
|
+
|
|
51
|
+
const result = await updateEntity('foo', { bar: 'updated' }, { foo: [row] })
|
|
52
|
+
|
|
53
|
+
expect(result.foo).toEqual([{ _id: row._id, bar: 'updated', note: 'keep me' }])
|
|
54
|
+
const onDisk = JSON.parse(await readFile(`${databasePath}foo/${row._id}.json`, 'utf8'))
|
|
55
|
+
expect(onDisk).toEqual({ _id: row._id, bar: 'updated', note: 'keep me' })
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('finds the right file to update even when a filename does not match the primary key', async () => {
|
|
59
|
+
await create('foo', definition, keys)
|
|
60
|
+
const inserted = await insertEntity('foo', [{ bar: 'one' }], {})
|
|
61
|
+
const row = inserted.foo[0]
|
|
62
|
+
const { rename, writeFile } = require('fs/promises')
|
|
63
|
+
await rename(`${databasePath}foo/${row._id}.json`, `${databasePath}foo/custom-name.json`)
|
|
64
|
+
const recordFile = JSON.parse(await readFile(`${databasePath}foo.json`, 'utf8'))
|
|
65
|
+
recordFile.entries = ['custom-name.json']
|
|
66
|
+
await writeFile(`${databasePath}foo.json`, JSON.stringify(recordFile))
|
|
67
|
+
|
|
68
|
+
const result = await updateEntity('foo', { bar: 'moved' }, { foo: [row] })
|
|
69
|
+
expect(result.foo[0].bar).toBe('moved')
|
|
70
|
+
const onDisk = JSON.parse(await readFile(`${databasePath}foo/custom-name.json`, 'utf8'))
|
|
71
|
+
expect(onDisk.bar).toBe('moved')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('throws when the update would remove a required field', async () => {
|
|
75
|
+
const definitionRequiringBar = [
|
|
76
|
+
{ name: '_id', type: 'string', optional: false, autoGenerate: true },
|
|
77
|
+
{ name: 'bar', type: 'string', optional: false, autoGenerate: false }
|
|
78
|
+
]
|
|
79
|
+
await create('foo', definitionRequiringBar, keys)
|
|
80
|
+
const inserted = await insertEntity('foo', [{ bar: 'one' }], {})
|
|
81
|
+
await expect(updateEntity('foo', { bar: undefined }, { foo: [inserted.foo[0]] }))
|
|
82
|
+
.rejects.toThrow('Missing required field "bar" for foo')
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('moves a changed unique key value to a new index entry', async () => {
|
|
86
|
+
const uniqueDefinition = [
|
|
87
|
+
{ name: '_id', type: 'string', optional: false, autoGenerate: true },
|
|
88
|
+
{ name: 'email', type: 'string', optional: false, autoGenerate: false }
|
|
89
|
+
]
|
|
90
|
+
const uniqueKeys = [
|
|
91
|
+
{ type: 'primary', fields: ['_id'], lookup: 'pk__id.json' },
|
|
92
|
+
{ type: 'unique', fields: ['email'], lookup: 'unique_email.json' }
|
|
93
|
+
]
|
|
94
|
+
await create('foo', uniqueDefinition, uniqueKeys)
|
|
95
|
+
const inserted = await insertEntity('foo', [{ email: 'old@example.com' }], {})
|
|
96
|
+
const row = inserted.foo[0]
|
|
97
|
+
|
|
98
|
+
await updateEntity('foo', { email: 'new@example.com' }, { foo: [row] })
|
|
99
|
+
|
|
100
|
+
const emailIndex = JSON.parse(await readFile(`${databasePath}__indexes/foo/unique_email.json`, 'utf8'))
|
|
101
|
+
expect(emailIndex).toEqual([{ value: 'new@example.com', record: [`${row._id}.json`] }])
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('rejects an update that collides with another record\'s unique value', async () => {
|
|
105
|
+
const uniqueDefinition = [
|
|
106
|
+
{ name: '_id', type: 'string', optional: false, autoGenerate: true },
|
|
107
|
+
{ name: 'email', type: 'string', optional: false, autoGenerate: false }
|
|
108
|
+
]
|
|
109
|
+
const uniqueKeys = [
|
|
110
|
+
{ type: 'primary', fields: ['_id'], lookup: 'pk__id.json' },
|
|
111
|
+
{ type: 'unique', fields: ['email'], lookup: 'unique_email.json' }
|
|
112
|
+
]
|
|
113
|
+
await create('foo', uniqueDefinition, uniqueKeys)
|
|
114
|
+
const inserted = await insertEntity('foo', [{ email: 'first@example.com' }, { email: 'second@example.com' }], {})
|
|
115
|
+
|
|
116
|
+
await expect(updateEntity('foo', { email: 'second@example.com' }, { foo: [inserted.foo[0]] }))
|
|
117
|
+
.rejects.toThrow('Duplicate value for unique key "email" on foo')
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('coerces a value to match its field\'s declared type', async () => {
|
|
121
|
+
const definitionWithInt = [
|
|
122
|
+
{ name: '_id', type: 'string', optional: false, autoGenerate: true },
|
|
123
|
+
{ name: 'reps', type: 'int', optional: false, autoGenerate: false }
|
|
124
|
+
]
|
|
125
|
+
await create('foo', definitionWithInt, keys)
|
|
126
|
+
const inserted = await insertEntity('foo', [{ reps: 10 }], {})
|
|
127
|
+
const result = await updateEntity('foo', { reps: '12' }, { foo: [inserted.foo[0]] })
|
|
128
|
+
expect(result.foo[0].reps).toBe(12)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
test('rejects a value that does not match its field\'s declared type', async () => {
|
|
132
|
+
const definitionWithInt = [
|
|
133
|
+
{ name: '_id', type: 'string', optional: false, autoGenerate: true },
|
|
134
|
+
{ name: 'reps', type: 'int', optional: false, autoGenerate: false }
|
|
135
|
+
]
|
|
136
|
+
await create('foo', definitionWithInt, keys)
|
|
137
|
+
const inserted = await insertEntity('foo', [{ reps: 10 }], {})
|
|
138
|
+
await expect(updateEntity('foo', { reps: 'twelve' }, { foo: [inserted.foo[0]] }))
|
|
139
|
+
.rejects.toThrow('Value "twelve" is not a valid int')
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test('does not re-validate a field the update never touches, even if it is already stored with a mismatched type', async () => {
|
|
143
|
+
await create('foo', definition, keys)
|
|
144
|
+
const inserted = await insertEntity('foo', [{ bar: 'one' }], {})
|
|
145
|
+
const row = inserted.foo[0]
|
|
146
|
+
// Simulate data that predates any type checking - _id is declared type "string" but stored
|
|
147
|
+
// as a real number, exactly like the project's own real fixture data.
|
|
148
|
+
const { writeFile } = require('fs/promises')
|
|
149
|
+
await writeFile(`${databasePath}foo/${row._id}.json`, JSON.stringify({ _id: 1, bar: 'one', note: null }))
|
|
150
|
+
|
|
151
|
+
const result = await updateEntity('foo', { bar: 'updated' }, { foo: [{ ...row, _id: 1 }] })
|
|
152
|
+
expect(result.foo[0].bar).toBe('updated')
|
|
153
|
+
expect(result.foo[0]._id).toBe(1)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
describe('foreign keys', () => {
|
|
157
|
+
const exerciseDefinition = [
|
|
158
|
+
{ name: '_id', type: 'string', optional: false, autoGenerate: true },
|
|
159
|
+
{ name: 'name', type: 'string', optional: true, autoGenerate: false }
|
|
160
|
+
]
|
|
161
|
+
const exerciseKeys = [
|
|
162
|
+
{ type: 'primary', fields: ['_id'], lookup: 'pk__id.json' }
|
|
163
|
+
]
|
|
164
|
+
const workoutDefinition = [
|
|
165
|
+
{ name: '_id', type: 'string', optional: false, autoGenerate: true },
|
|
166
|
+
{ name: 'exercise_id', type: 'string', optional: true, autoGenerate: false }
|
|
167
|
+
]
|
|
168
|
+
const workoutKeys = [
|
|
169
|
+
{ type: 'primary', fields: ['_id'], lookup: 'pk__id.json' },
|
|
170
|
+
{
|
|
171
|
+
type: 'foreign',
|
|
172
|
+
fields: ['exercise_id'],
|
|
173
|
+
lookup: 'fk_exercise_id.json',
|
|
174
|
+
references: [{ fields: ['exercises._id'], lookup: 'exercises.json' }]
|
|
175
|
+
}
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
test('accepts changing a foreign key to a value that exists in the referenced record', async () => {
|
|
179
|
+
await create('exercises', exerciseDefinition, exerciseKeys)
|
|
180
|
+
const exercises = await insertEntity('exercises', [{ name: 'Bench Press' }, { name: 'Squat' }], {})
|
|
181
|
+
await create('workouts', workoutDefinition, workoutKeys)
|
|
182
|
+
const inserted = await insertEntity('workouts', [{ exercise_id: exercises.exercises[0]._id }], {})
|
|
183
|
+
|
|
184
|
+
const result = await updateEntity('workouts', { exercise_id: exercises.exercises[1]._id }, { workouts: [inserted.workouts[0]] })
|
|
185
|
+
expect(result.workouts[0].exercise_id).toBe(exercises.exercises[1]._id)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
test('rejects changing a foreign key to a value that does not exist in the referenced record', async () => {
|
|
189
|
+
await create('exercises', exerciseDefinition, exerciseKeys)
|
|
190
|
+
const exercises = await insertEntity('exercises', [{ name: 'Bench Press' }], {})
|
|
191
|
+
await create('workouts', workoutDefinition, workoutKeys)
|
|
192
|
+
const inserted = await insertEntity('workouts', [{ exercise_id: exercises.exercises[0]._id }], {})
|
|
193
|
+
|
|
194
|
+
await expect(updateEntity('workouts', { exercise_id: 'does-not-exist' }, { workouts: [inserted.workouts[0]] }))
|
|
195
|
+
.rejects.toThrow('Foreign key "exercise_id" on workouts references a nonexistent exercises._id = does-not-exist')
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
test('does not re-check an unchanged foreign key value, even if its referenced record was since removed', async () => {
|
|
199
|
+
await create('exercises', exerciseDefinition, exerciseKeys)
|
|
200
|
+
const exercises = await insertEntity('exercises', [{ name: 'Bench Press' }], {})
|
|
201
|
+
await create('workouts', workoutDefinition, workoutKeys)
|
|
202
|
+
const inserted = await insertEntity('workouts', [{ exercise_id: exercises.exercises[0]._id }], {})
|
|
203
|
+
|
|
204
|
+
const { rm } = require('fs/promises')
|
|
205
|
+
await rm(`${databasePath}exercises/${exercises.exercises[0]._id}.json`)
|
|
206
|
+
|
|
207
|
+
const result = await updateEntity('workouts', { exercise_id: exercises.exercises[0]._id }, { workouts: [inserted.workouts[0]] })
|
|
208
|
+
expect(result.workouts[0].exercise_id).toBe(exercises.exercises[0]._id)
|
|
209
|
+
})
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
test('re-updating a row to its own existing unique value does not falsely collide', async () => {
|
|
213
|
+
const uniqueDefinition = [
|
|
214
|
+
{ name: '_id', type: 'string', optional: false, autoGenerate: true },
|
|
215
|
+
{ name: 'email', type: 'string', optional: false, autoGenerate: false }
|
|
216
|
+
]
|
|
217
|
+
const uniqueKeys = [
|
|
218
|
+
{ type: 'primary', fields: ['_id'], lookup: 'pk__id.json' },
|
|
219
|
+
{ type: 'unique', fields: ['email'], lookup: 'unique_email.json' }
|
|
220
|
+
]
|
|
221
|
+
await create('foo', uniqueDefinition, uniqueKeys)
|
|
222
|
+
const inserted = await insertEntity('foo', [{ email: 'same@example.com' }], {})
|
|
223
|
+
const row = inserted.foo[0]
|
|
224
|
+
|
|
225
|
+
const result = await updateEntity('foo', { email: 'same@example.com' }, { foo: [row] })
|
|
226
|
+
expect(result.foo[0].email).toBe('same@example.com')
|
|
227
|
+
})
|
|
228
|
+
})
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { retrieveRecord } from '../utilities/retrieveRecord'
|
|
2
|
+
import { retrieveFile } from '../utilities/retrieveFile'
|
|
3
|
+
import { updateIndex } from '../utilities/updateIndex'
|
|
4
|
+
import { removeFromIndex } from '../utilities/removeFromIndex'
|
|
5
|
+
import { keyUnique } from '../introspect/policies/keyUnique'
|
|
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
|
+
* Same reasoning as deleteEntity: a record object carries no memory of which entry file it came
|
|
16
|
+
* from, so re-scan entries[] and match by the record's own primary key value.
|
|
17
|
+
* @param record
|
|
18
|
+
* @param primaryField
|
|
19
|
+
* @param primaryValue
|
|
20
|
+
*/
|
|
21
|
+
const findEntryFile = async (record: recordDefinition, primaryField: string, primaryValue: any): Promise<string | null> => {
|
|
22
|
+
for (const fileName of record.entries) {
|
|
23
|
+
const data = await retrieveFile(`${record.path}/${fileName}`)
|
|
24
|
+
if (data[primaryField] == primaryValue) {
|
|
25
|
+
return fileName
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Only check uniqueness for a single-field primary/unique key whose value is actually changing -
|
|
33
|
+
* checking an unchanged value would find the record's own existing entry and wrongly report it
|
|
34
|
+
* as a duplicate of itself.
|
|
35
|
+
* @param entity
|
|
36
|
+
* @param record
|
|
37
|
+
* @param oldData
|
|
38
|
+
* @param newValues
|
|
39
|
+
*/
|
|
40
|
+
const assertUniqueForUpdate = async (entity: string, record: recordDefinition, oldData: Object, newValues: Object): Promise<void> => {
|
|
41
|
+
for (const key of record.keys) {
|
|
42
|
+
if ((key.type !== 'primary' && key.type !== 'unique') || key.fields.length !== 1) {
|
|
43
|
+
continue
|
|
44
|
+
}
|
|
45
|
+
const field = key.fields[0]
|
|
46
|
+
if (!newValues.hasOwnProperty(field) || newValues[field] == oldData[field]) {
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
const isUnique = await keyUnique(entity, key.fields, newValues[field])
|
|
50
|
+
if (!isUnique) {
|
|
51
|
+
throw new Error(`Duplicate value for ${key.type} key "${field}" on ${entity}`)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Move a single-field key's index entry from the old value to the new one wherever the update
|
|
58
|
+
* actually changed that field.
|
|
59
|
+
* @param record
|
|
60
|
+
* @param oldData
|
|
61
|
+
* @param newData
|
|
62
|
+
* @param newValues
|
|
63
|
+
* @param fileName
|
|
64
|
+
*/
|
|
65
|
+
/**
|
|
66
|
+
* Same "only if it actually changed" scoping as assertUniqueForUpdate: re-checking an unchanged
|
|
67
|
+
* foreign key value would re-validate a reference this update never touches, which could fail an
|
|
68
|
+
* unrelated field's update if that parent record happened to be deleted since.
|
|
69
|
+
* @param entity
|
|
70
|
+
* @param record
|
|
71
|
+
* @param oldData
|
|
72
|
+
* @param newValues
|
|
73
|
+
*/
|
|
74
|
+
const assertForeignKeysForUpdate = async (entity: string, record: recordDefinition, oldData: Object, newValues: Object): Promise<void> => {
|
|
75
|
+
for (const key of record.keys) {
|
|
76
|
+
if (key.type !== 'foreign' || key.fields.length !== 1 || !key.references || key.references.length !== 1) {
|
|
77
|
+
continue
|
|
78
|
+
}
|
|
79
|
+
const field = key.fields[0]
|
|
80
|
+
if (!newValues.hasOwnProperty(field) || newValues[field] == oldData[field]) {
|
|
81
|
+
continue
|
|
82
|
+
}
|
|
83
|
+
const { entity: refEntity, property: refProperty } = splitEntityProperty(key.references[0].fields[0])
|
|
84
|
+
const exists = await foreignKeyExists(refEntity, refProperty, newValues[field])
|
|
85
|
+
if (!exists) {
|
|
86
|
+
throw new Error(`Foreign key "${field}" on ${entity} references a nonexistent ${refEntity}.${refProperty} = ${newValues[field]}`)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const reconcileIndexes = async (record: recordDefinition, oldData: Object, newData: Object, newValues: Object, fileName: string): Promise<void> => {
|
|
92
|
+
for (const key of record.keys) {
|
|
93
|
+
if (key.fields.length !== 1) {
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
const field = key.fields[0]
|
|
97
|
+
if (!newValues.hasOwnProperty(field) || newValues[field] == oldData[field]) {
|
|
98
|
+
continue
|
|
99
|
+
}
|
|
100
|
+
await removeFromIndex(record.path, key, oldData[field], fileName)
|
|
101
|
+
await updateIndex(record.path, key, newData[field], fileName)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const assertRequiredFields = (entity: string, record: recordDefinition, newData: Object): void => {
|
|
106
|
+
for (const field of record.definition) {
|
|
107
|
+
if (!field.optional && typeof newData[field.name] === 'undefined') {
|
|
108
|
+
throw new Error(`Missing required field "${field.name}" for ${entity}`)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Type-check (and coerce) only the fields actually present in an update's values, never the rest
|
|
115
|
+
* of the merged record - a field whose already-stored value doesn't match its declared type
|
|
116
|
+
* (nothing enforced this before typeCheck() existed) must not block an update that never touches
|
|
117
|
+
* that field.
|
|
118
|
+
* @param record
|
|
119
|
+
* @param values
|
|
120
|
+
*/
|
|
121
|
+
const applyFieldTypes = (record: recordDefinition, values: Object): Object => {
|
|
122
|
+
const typedValues = Object.assign({}, values)
|
|
123
|
+
for (const field of record.definition) {
|
|
124
|
+
if (typedValues.hasOwnProperty(field.name)) {
|
|
125
|
+
typedValues[field.name] = typeCheck(field.type, typedValues[field.name])
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return typedValues
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export const updateEntity = async (entity: string = '', values: Object = {}, dataSet: Object = {}): Promise<Object> => {
|
|
132
|
+
const databasePath = gulpConfig.get('databasePath', 'database/')
|
|
133
|
+
const record = await retrieveRecord(entity)
|
|
134
|
+
const primaryKey = record.keys.find(key => key.type === 'primary' && key.fields.length === 1)
|
|
135
|
+
if (!primaryKey) {
|
|
136
|
+
throw new Error(`Cannot update "${entity}" without a single-field primary key`)
|
|
137
|
+
}
|
|
138
|
+
const primaryField = primaryKey.fields[0]
|
|
139
|
+
const typedValues = applyFieldTypes(record, values)
|
|
140
|
+
|
|
141
|
+
const toUpdate = dataSet[entity] || []
|
|
142
|
+
const updatedRows = []
|
|
143
|
+
for (const row of toUpdate) {
|
|
144
|
+
const fileName = await findEntryFile(record, primaryField, row[primaryField])
|
|
145
|
+
if (!fileName) {
|
|
146
|
+
throw new Error(`Could not find entry for ${entity} where "${primaryField}" = ${row[primaryField]}`)
|
|
147
|
+
}
|
|
148
|
+
const oldData = await retrieveFile(`${record.path}/${fileName}`)
|
|
149
|
+
await assertUniqueForUpdate(entity, record, oldData, typedValues)
|
|
150
|
+
await assertForeignKeysForUpdate(entity, record, oldData, typedValues)
|
|
151
|
+
|
|
152
|
+
const newData = Object.assign({}, oldData, typedValues)
|
|
153
|
+
assertRequiredFields(entity, record, newData)
|
|
154
|
+
|
|
155
|
+
await reconcileIndexes(record, oldData, newData, typedValues, fileName)
|
|
156
|
+
await writeFile(`${databasePath}${record.path}/${fileName}`, JSON.stringify(newData, null, 2))
|
|
157
|
+
updatedRows.push(newData)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
dataSet[entity] = updatedRows
|
|
161
|
+
return dataSet
|
|
162
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { where } from './where'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
const dataSet = [
|
|
5
|
+
{
|
|
6
|
+
_id: 1,
|
|
7
|
+
exercise_id: 1,
|
|
8
|
+
weight_effort: '115lbs',
|
|
9
|
+
reps: 10,
|
|
10
|
+
sets: 3,
|
|
11
|
+
note: '',
|
|
12
|
+
date: '2024-06-02'
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
_id: 2,
|
|
16
|
+
exercise_id: 2,
|
|
17
|
+
weight_effort: '155lbs',
|
|
18
|
+
reps: 10,
|
|
19
|
+
sets: 3,
|
|
20
|
+
note: '',
|
|
21
|
+
date: '2024-06-02'
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
_id: 3,
|
|
25
|
+
exercise_id: 3,
|
|
26
|
+
weight_effort: '155lbs',
|
|
27
|
+
reps: 10,
|
|
28
|
+
sets: 3,
|
|
29
|
+
note: '',
|
|
30
|
+
date: '2024-06-02'
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
describe('where', () => {
|
|
35
|
+
test('will find with given data', () => {
|
|
36
|
+
const result = where(dataSet, {
|
|
37
|
+
property: 'date',
|
|
38
|
+
comparator: '=',
|
|
39
|
+
value: '2024-06-02'
|
|
40
|
+
})
|
|
41
|
+
expect(result).toEqual(dataSet)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('will return no matches', () => {
|
|
45
|
+
const result = where(dataSet, {
|
|
46
|
+
property: 'date',
|
|
47
|
+
comparator: '=',
|
|
48
|
+
value: '2021-06-02'
|
|
49
|
+
})
|
|
50
|
+
expect(result).toEqual([])
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('only one match', () => {
|
|
54
|
+
const result = where(dataSet, {
|
|
55
|
+
property: 'exercise_id',
|
|
56
|
+
comparator: '=',
|
|
57
|
+
value: '2'
|
|
58
|
+
})
|
|
59
|
+
expect(result).toEqual([{
|
|
60
|
+
_id: 2,
|
|
61
|
+
exercise_id: 2,
|
|
62
|
+
weight_effort: '155lbs',
|
|
63
|
+
reps: 10,
|
|
64
|
+
sets: 3,
|
|
65
|
+
note: '',
|
|
66
|
+
date: '2024-06-02'
|
|
67
|
+
}])
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('!= excludes the matching record', () => {
|
|
71
|
+
const result = where(dataSet, { property: 'exercise_id', comparator: '!=', value: 2 })
|
|
72
|
+
expect(result.map(r => r._id)).toEqual([1, 3])
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('<> excludes the matching record', () => {
|
|
76
|
+
const result = where(dataSet, { property: 'exercise_id', comparator: '<>', value: 2 })
|
|
77
|
+
expect(result.map(r => r._id)).toEqual([1, 3])
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('> finds records above the value', () => {
|
|
81
|
+
const result = where(dataSet, { property: 'exercise_id', comparator: '>', value: 1 })
|
|
82
|
+
expect(result.map(r => r._id)).toEqual([2, 3])
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('>= finds records at or above the value', () => {
|
|
86
|
+
const result = where(dataSet, { property: 'exercise_id', comparator: '>=', value: 2 })
|
|
87
|
+
expect(result.map(r => r._id)).toEqual([2, 3])
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('< finds records below the value', () => {
|
|
91
|
+
const result = where(dataSet, { property: 'exercise_id', comparator: '<', value: 3 })
|
|
92
|
+
expect(result.map(r => r._id)).toEqual([1, 2])
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test('<= finds records at or below the value', () => {
|
|
96
|
+
const result = where(dataSet, { property: 'exercise_id', comparator: '<=', value: 2 })
|
|
97
|
+
expect(result.map(r => r._id)).toEqual([1, 2])
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('in finds records whose value is in the given list', () => {
|
|
101
|
+
const result = where(dataSet, { property: 'exercise_id', comparator: 'in', value: [1, 3] })
|
|
102
|
+
expect(result.map(r => r._id)).toEqual([1, 3])
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('in returns no matches when value is not a list', () => {
|
|
106
|
+
const result = where(dataSet, { property: 'exercise_id', comparator: 'in', value: 1 })
|
|
107
|
+
expect(result).toEqual([])
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('between finds records within the inclusive range', () => {
|
|
111
|
+
const result = where(dataSet, { property: 'exercise_id', comparator: 'between', value: [2, 3] })
|
|
112
|
+
expect(result.map(r => r._id)).toEqual([2, 3])
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test('like matches a % wildcard pattern', () => {
|
|
116
|
+
const result = where(dataSet, { property: 'weight_effort', comparator: 'like', value: '15%' })
|
|
117
|
+
expect(result.map(r => r._id)).toEqual([2, 3])
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('like matches a _ single-character wildcard', () => {
|
|
121
|
+
const result = where(dataSet, { property: 'weight_effort', comparator: 'like', value: '1_5lbs' })
|
|
122
|
+
expect(result.map(r => r._id)).toEqual([1, 2, 3])
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('like is case-insensitive and escapes regex-special characters', () => {
|
|
126
|
+
const result = where(dataSet, { property: 'date', comparator: 'like', value: '2024-06-02' })
|
|
127
|
+
expect(result).toEqual(dataSet)
|
|
128
|
+
})
|
|
129
|
+
})
|