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.
Files changed (336) hide show
  1. package/.browserslistrc +1 -0
  2. package/.nvmrc +1 -0
  3. package/LICENSE +674 -0
  4. package/MAIN.md +15 -0
  5. package/README.md +63 -0
  6. package/babel.config.js +3 -0
  7. package/build-tools.config.json +30 -0
  8. package/dist/data/introspect/create.d.ts +11 -0
  9. package/dist/data/introspect/create.js +45 -0
  10. package/dist/data/introspect/create.min.js +1 -0
  11. package/dist/data/introspect/definitions/field.d.ts +28 -0
  12. package/dist/data/introspect/definitions/field.js +36 -0
  13. package/dist/data/introspect/definitions/field.min.js +1 -0
  14. package/dist/data/introspect/definitions/key.d.ts +26 -0
  15. package/dist/data/introspect/definitions/key.js +53 -0
  16. package/dist/data/introspect/definitions/key.min.js +1 -0
  17. package/dist/data/introspect/definitions/keyReference.d.ts +18 -0
  18. package/dist/data/introspect/definitions/keyReference.js +20 -0
  19. package/dist/data/introspect/definitions/keyReference.min.js +1 -0
  20. package/dist/data/introspect/definitions/record.d.ts +26 -0
  21. package/dist/data/introspect/definitions/record.js +32 -0
  22. package/dist/data/introspect/definitions/record.min.js +1 -0
  23. package/dist/data/introspect/describe.d.ts +2 -0
  24. package/dist/data/introspect/describe.js +15 -0
  25. package/dist/data/introspect/describe.min.js +1 -0
  26. package/dist/data/introspect/drop.d.ts +2 -0
  27. package/dist/data/introspect/drop.js +37 -0
  28. package/dist/data/introspect/drop.min.js +1 -0
  29. package/dist/data/introspect/index.d.ts +8 -0
  30. package/dist/data/introspect/index.js +30 -0
  31. package/dist/data/introspect/index.min.js +1 -0
  32. package/dist/data/introspect/policies/foreignKeyExists.d.ts +9 -0
  33. package/dist/data/introspect/policies/foreignKeyExists.js +25 -0
  34. package/dist/data/introspect/policies/foreignKeyExists.min.js +1 -0
  35. package/dist/data/introspect/policies/keyGenerate.d.ts +2 -0
  36. package/dist/data/introspect/policies/keyGenerate.js +17 -0
  37. package/dist/data/introspect/policies/keyGenerate.min.js +1 -0
  38. package/dist/data/introspect/policies/keyUnique.d.ts +2 -0
  39. package/dist/data/introspect/policies/keyUnique.js +21 -0
  40. package/dist/data/introspect/policies/keyUnique.min.js +1 -0
  41. package/dist/data/introspect/policies/typeCheck.d.ts +12 -0
  42. package/dist/data/introspect/policies/typeCheck.js +57 -0
  43. package/dist/data/introspect/policies/typeCheck.min.js +1 -0
  44. package/dist/data/introspect/policies/typeEnum.d.ts +2 -0
  45. package/dist/data/introspect/policies/typeEnum.js +14 -0
  46. package/dist/data/introspect/policies/typeEnum.min.js +1 -0
  47. package/dist/data/introspect/policies/useDefault.d.ts +2 -0
  48. package/dist/data/introspect/policies/useDefault.js +13 -0
  49. package/dist/data/introspect/policies/useDefault.min.js +1 -0
  50. package/dist/data/queries/deleteEntity.d.ts +2 -0
  51. package/dist/data/queries/deleteEntity.js +79 -0
  52. package/dist/data/queries/deleteEntity.min.js +1 -0
  53. package/dist/data/queries/groupBy.d.ts +2 -0
  54. package/dist/data/queries/groupBy.js +22 -0
  55. package/dist/data/queries/groupBy.min.js +1 -0
  56. package/dist/data/queries/insertEntity.d.ts +2 -0
  57. package/dist/data/queries/insertEntity.js +115 -0
  58. package/dist/data/queries/insertEntity.min.js +1 -0
  59. package/dist/data/queries/joinEntity.d.ts +2 -0
  60. package/dist/data/queries/joinEntity.js +27 -0
  61. package/dist/data/queries/joinEntity.min.js +1 -0
  62. package/dist/data/queries/mergeJoins.d.ts +2 -0
  63. package/dist/data/queries/mergeJoins.js +19 -0
  64. package/dist/data/queries/mergeJoins.min.js +1 -0
  65. package/dist/data/queries/parser.d.ts +2 -0
  66. package/dist/data/queries/parser.js +30 -0
  67. package/dist/data/queries/parser.min.js +1 -0
  68. package/dist/data/queries/query.d.ts +2 -0
  69. package/dist/data/queries/query.js +18 -0
  70. package/dist/data/queries/query.min.js +1 -0
  71. package/dist/data/queries/readEntity.d.ts +2 -0
  72. package/dist/data/queries/readEntity.js +14 -0
  73. package/dist/data/queries/readEntity.min.js +1 -0
  74. package/dist/data/queries/sortBy.d.ts +2 -0
  75. package/dist/data/queries/sortBy.js +25 -0
  76. package/dist/data/queries/sortBy.min.js +1 -0
  77. package/dist/data/queries/updateEntity.d.ts +2 -0
  78. package/dist/data/queries/updateEntity.js +164 -0
  79. package/dist/data/queries/updateEntity.min.js +1 -0
  80. package/dist/data/queries/where.d.ts +7 -0
  81. package/dist/data/queries/where.js +56 -0
  82. package/dist/data/queries/where.min.js +1 -0
  83. package/dist/data/utilities/deregisterRecord.d.ts +8 -0
  84. package/dist/data/utilities/deregisterRecord.js +34 -0
  85. package/dist/data/utilities/deregisterRecord.min.js +1 -0
  86. package/dist/data/utilities/getConditionIndexedList.d.ts +3 -0
  87. package/dist/data/utilities/getConditionIndexedList.js +47 -0
  88. package/dist/data/utilities/getConditionIndexedList.min.js +1 -0
  89. package/dist/data/utilities/getJoinIndexedList.d.ts +3 -0
  90. package/dist/data/utilities/getJoinIndexedList.js +59 -0
  91. package/dist/data/utilities/getJoinIndexedList.min.js +1 -0
  92. package/dist/data/utilities/getMatchingIndexes.d.ts +2 -0
  93. package/dist/data/utilities/getMatchingIndexes.js +28 -0
  94. package/dist/data/utilities/getMatchingIndexes.min.js +1 -0
  95. package/dist/data/utilities/matchIndexedJoins.d.ts +2 -0
  96. package/dist/data/utilities/matchIndexedJoins.js +61 -0
  97. package/dist/data/utilities/matchIndexedJoins.min.js +1 -0
  98. package/dist/data/utilities/mergeJoin.d.ts +2 -0
  99. package/dist/data/utilities/mergeJoin.js +44 -0
  100. package/dist/data/utilities/mergeJoin.min.js +1 -0
  101. package/dist/data/utilities/parsers/findCommand.d.ts +10 -0
  102. package/dist/data/utilities/parsers/findCommand.js +25 -0
  103. package/dist/data/utilities/parsers/findCommand.min.js +1 -0
  104. package/dist/data/utilities/parsers/findConditions.d.ts +15 -0
  105. package/dist/data/utilities/parsers/findConditions.js +28 -0
  106. package/dist/data/utilities/parsers/findConditions.min.js +1 -0
  107. package/dist/data/utilities/parsers/findGroupBy.d.ts +9 -0
  108. package/dist/data/utilities/parsers/findGroupBy.js +22 -0
  109. package/dist/data/utilities/parsers/findGroupBy.min.js +1 -0
  110. package/dist/data/utilities/parsers/findInsertValues.d.ts +13 -0
  111. package/dist/data/utilities/parsers/findInsertValues.js +34 -0
  112. package/dist/data/utilities/parsers/findInsertValues.min.js +1 -0
  113. package/dist/data/utilities/parsers/findJoinClauses.d.ts +13 -0
  114. package/dist/data/utilities/parsers/findJoinClauses.js +27 -0
  115. package/dist/data/utilities/parsers/findJoinClauses.min.js +1 -0
  116. package/dist/data/utilities/parsers/findJoinEntity.d.ts +9 -0
  117. package/dist/data/utilities/parsers/findJoinEntity.js +22 -0
  118. package/dist/data/utilities/parsers/findJoinEntity.min.js +1 -0
  119. package/dist/data/utilities/parsers/findLimit.d.ts +9 -0
  120. package/dist/data/utilities/parsers/findLimit.js +22 -0
  121. package/dist/data/utilities/parsers/findLimit.min.js +1 -0
  122. package/dist/data/utilities/parsers/findMergeJoins.d.ts +12 -0
  123. package/dist/data/utilities/parsers/findMergeJoins.js +26 -0
  124. package/dist/data/utilities/parsers/findMergeJoins.min.js +1 -0
  125. package/dist/data/utilities/parsers/findOffset.d.ts +9 -0
  126. package/dist/data/utilities/parsers/findOffset.js +22 -0
  127. package/dist/data/utilities/parsers/findOffset.min.js +1 -0
  128. package/dist/data/utilities/parsers/findSelects.d.ts +12 -0
  129. package/dist/data/utilities/parsers/findSelects.js +30 -0
  130. package/dist/data/utilities/parsers/findSelects.min.js +1 -0
  131. package/dist/data/utilities/parsers/findSortClauses.d.ts +12 -0
  132. package/dist/data/utilities/parsers/findSortClauses.js +30 -0
  133. package/dist/data/utilities/parsers/findSortClauses.min.js +1 -0
  134. package/dist/data/utilities/parsers/findUpdateValues.d.ts +15 -0
  135. package/dist/data/utilities/parsers/findUpdateValues.js +34 -0
  136. package/dist/data/utilities/parsers/findUpdateValues.min.js +1 -0
  137. package/dist/data/utilities/parsers/isEntityProperty.d.ts +2 -0
  138. package/dist/data/utilities/parsers/isEntityProperty.js +8 -0
  139. package/dist/data/utilities/parsers/isEntityProperty.min.js +1 -0
  140. package/dist/data/utilities/parsers/parseLiteralValue.d.ts +12 -0
  141. package/dist/data/utilities/parsers/parseLiteralValue.js +34 -0
  142. package/dist/data/utilities/parsers/parseLiteralValue.min.js +1 -0
  143. package/dist/data/utilities/parsers/parseQuery.d.ts +33 -0
  144. package/dist/data/utilities/parsers/parseQuery.js +51 -0
  145. package/dist/data/utilities/parsers/parseQuery.min.js +1 -0
  146. package/dist/data/utilities/parsers/removeFromQuery.d.ts +2 -0
  147. package/dist/data/utilities/parsers/removeFromQuery.js +8 -0
  148. package/dist/data/utilities/parsers/removeFromQuery.min.js +1 -0
  149. package/dist/data/utilities/parsers/splitEntityProperty.d.ts +5 -0
  150. package/dist/data/utilities/parsers/splitEntityProperty.js +23 -0
  151. package/dist/data/utilities/parsers/splitEntityProperty.min.js +1 -0
  152. package/dist/data/utilities/parsers/splitQueries.d.ts +2 -0
  153. package/dist/data/utilities/parsers/splitQueries.js +11 -0
  154. package/dist/data/utilities/parsers/splitQueries.min.js +1 -0
  155. package/dist/data/utilities/reconcileJoin.d.ts +2 -0
  156. package/dist/data/utilities/reconcileJoin.js +26 -0
  157. package/dist/data/utilities/reconcileJoin.min.js +1 -0
  158. package/dist/data/utilities/reconcileJoins.d.ts +2 -0
  159. package/dist/data/utilities/reconcileJoins.js +27 -0
  160. package/dist/data/utilities/reconcileJoins.min.js +1 -0
  161. package/dist/data/utilities/registerRecord.d.ts +8 -0
  162. package/dist/data/utilities/registerRecord.js +67 -0
  163. package/dist/data/utilities/registerRecord.min.js +1 -0
  164. package/dist/data/utilities/removeFromIndex.d.ts +14 -0
  165. package/dist/data/utilities/removeFromIndex.js +46 -0
  166. package/dist/data/utilities/removeFromIndex.min.js +1 -0
  167. package/dist/data/utilities/restrictIndexedList.d.ts +2 -0
  168. package/dist/data/utilities/restrictIndexedList.js +45 -0
  169. package/dist/data/utilities/restrictIndexedList.min.js +1 -0
  170. package/dist/data/utilities/retrieveFile.d.ts +2 -0
  171. package/dist/data/utilities/retrieveFile.js +28 -0
  172. package/dist/data/utilities/retrieveFile.min.js +1 -0
  173. package/dist/data/utilities/retrieveIndexes.d.ts +2 -0
  174. package/dist/data/utilities/retrieveIndexes.js +24 -0
  175. package/dist/data/utilities/retrieveIndexes.min.js +1 -0
  176. package/dist/data/utilities/retrieveRecord.d.ts +2 -0
  177. package/dist/data/utilities/retrieveRecord.js +9 -0
  178. package/dist/data/utilities/retrieveRecord.min.js +1 -0
  179. package/dist/data/utilities/retrieveRecords.d.ts +2 -0
  180. package/dist/data/utilities/retrieveRecords.js +21 -0
  181. package/dist/data/utilities/retrieveRecords.min.js +1 -0
  182. package/dist/data/utilities/runQuery.d.ts +2 -0
  183. package/dist/data/utilities/runQuery.js +72 -0
  184. package/dist/data/utilities/runQuery.min.js +1 -0
  185. package/dist/data/utilities/updateIndex.d.ts +17 -0
  186. package/dist/data/utilities/updateIndex.js +56 -0
  187. package/dist/data/utilities/updateIndex.min.js +1 -0
  188. package/dist/data/utilities/useJoinClause.d.ts +6 -0
  189. package/dist/data/utilities/useJoinClause.js +51 -0
  190. package/dist/data/utilities/useJoinClause.min.js +1 -0
  191. package/dist/index.d.ts +4 -0
  192. package/dist/index.js +35 -0
  193. package/dist/index.min.js +1 -0
  194. package/gulpfile.mjs +15 -0
  195. package/package.json +44 -0
  196. package/src/data/introspect/create.test.js +67 -0
  197. package/src/data/introspect/create.ts +29 -0
  198. package/src/data/introspect/definitions/field.test.js +23 -0
  199. package/src/data/introspect/definitions/field.ts +48 -0
  200. package/src/data/introspect/definitions/key.test.js +100 -0
  201. package/src/data/introspect/definitions/key.ts +64 -0
  202. package/src/data/introspect/definitions/keyReference.test.js +8 -0
  203. package/src/data/introspect/definitions/keyReference.ts +23 -0
  204. package/src/data/introspect/definitions/record.ts +42 -0
  205. package/src/data/introspect/describe.test.js +36 -0
  206. package/src/data/introspect/describe.ts +9 -0
  207. package/src/data/introspect/drop.test.js +77 -0
  208. package/src/data/introspect/drop.ts +20 -0
  209. package/src/data/introspect/index.ts +8 -0
  210. package/src/data/introspect/policies/foreignKeyExists.test.js +44 -0
  211. package/src/data/introspect/policies/foreignKeyExists.ts +15 -0
  212. package/src/data/introspect/policies/keyGenerate.ts +11 -0
  213. package/src/data/introspect/policies/keyUnique.ts +11 -0
  214. package/src/data/introspect/policies/typeCheck.test.js +84 -0
  215. package/src/data/introspect/policies/typeCheck.ts +49 -0
  216. package/src/data/introspect/policies/typeEnum.ts +6 -0
  217. package/src/data/introspect/policies/useDefault.ts +6 -0
  218. package/src/data/queries/deleteEntity.test.js +86 -0
  219. package/src/data/queries/deleteEntity.ts +56 -0
  220. package/src/data/queries/groupBy.test.js +135 -0
  221. package/src/data/queries/groupBy.ts +16 -0
  222. package/src/data/queries/insertEntity.test.js +156 -0
  223. package/src/data/queries/insertEntity.ts +113 -0
  224. package/src/data/queries/joinEntity.test.js +109 -0
  225. package/src/data/queries/joinEntity.ts +20 -0
  226. package/src/data/queries/mergeJoins.test.js +174 -0
  227. package/src/data/queries/mergeJoins.ts +13 -0
  228. package/src/data/queries/parser.test.js +343 -0
  229. package/src/data/queries/parser.ts +24 -0
  230. package/src/data/queries/query.test.js +233 -0
  231. package/src/data/queries/query.ts +12 -0
  232. package/src/data/queries/readEntity.test.js +85 -0
  233. package/src/data/queries/readEntity.ts +8 -0
  234. package/src/data/queries/sortBy.test.js +131 -0
  235. package/src/data/queries/sortBy.ts +18 -0
  236. package/src/data/queries/updateEntity.test.js +228 -0
  237. package/src/data/queries/updateEntity.ts +162 -0
  238. package/src/data/queries/where.test.js +129 -0
  239. package/src/data/queries/where.ts +54 -0
  240. package/src/data/utilities/deregisterRecord.test.js +26 -0
  241. package/src/data/utilities/deregisterRecord.ts +24 -0
  242. package/src/data/utilities/getConditionIndexedList.ts +43 -0
  243. package/src/data/utilities/getJoinIndexedList.ts +53 -0
  244. package/src/data/utilities/getMatchingIndexes.ts +21 -0
  245. package/src/data/utilities/matchIndexedJoins.ts +45 -0
  246. package/src/data/utilities/mergeJoin.test.js +102 -0
  247. package/src/data/utilities/mergeJoin.ts +28 -0
  248. package/src/data/utilities/parsers/findCommand.ts +25 -0
  249. package/src/data/utilities/parsers/findConditions.test.js +38 -0
  250. package/src/data/utilities/parsers/findConditions.ts +32 -0
  251. package/src/data/utilities/parsers/findGroupBy.ts +20 -0
  252. package/src/data/utilities/parsers/findInsertValues.test.js +26 -0
  253. package/src/data/utilities/parsers/findInsertValues.ts +32 -0
  254. package/src/data/utilities/parsers/findJoinClauses.ts +29 -0
  255. package/src/data/utilities/parsers/findJoinEntity.ts +20 -0
  256. package/src/data/utilities/parsers/findLimit.ts +20 -0
  257. package/src/data/utilities/parsers/findMergeJoins.ts +27 -0
  258. package/src/data/utilities/parsers/findOffset.ts +20 -0
  259. package/src/data/utilities/parsers/findSelects.ts +31 -0
  260. package/src/data/utilities/parsers/findSortClauses.ts +31 -0
  261. package/src/data/utilities/parsers/findUpdateValues.test.js +37 -0
  262. package/src/data/utilities/parsers/findUpdateValues.ts +32 -0
  263. package/src/data/utilities/parsers/isEntityProperty.ts +1 -0
  264. package/src/data/utilities/parsers/parseLiteralValue.test.js +41 -0
  265. package/src/data/utilities/parsers/parseLiteralValue.ts +29 -0
  266. package/src/data/utilities/parsers/parseQuery.ts +73 -0
  267. package/src/data/utilities/parsers/removeFromQuery.ts +1 -0
  268. package/src/data/utilities/parsers/splitEntityProperty.test.js +8 -0
  269. package/src/data/utilities/parsers/splitEntityProperty.ts +17 -0
  270. package/src/data/utilities/parsers/splitQueries.test.js +33 -0
  271. package/src/data/utilities/parsers/splitQueries.ts +4 -0
  272. package/src/data/utilities/reconcileJoin.test.js +38 -0
  273. package/src/data/utilities/reconcileJoin.ts +23 -0
  274. package/src/data/utilities/reconcileJoins.test.js +160 -0
  275. package/src/data/utilities/reconcileJoins.ts +15 -0
  276. package/src/data/utilities/registerRecord.test.js +44 -0
  277. package/src/data/utilities/registerRecord.ts +44 -0
  278. package/src/data/utilities/removeFromIndex.test.js +44 -0
  279. package/src/data/utilities/removeFromIndex.ts +34 -0
  280. package/src/data/utilities/restrictIndexedList.ts +38 -0
  281. package/src/data/utilities/retrieveFile.test.js +49 -0
  282. package/src/data/utilities/retrieveFile.ts +19 -0
  283. package/src/data/utilities/retrieveIndexes.ts +17 -0
  284. package/src/data/utilities/retrieveRecord.ts +3 -0
  285. package/src/data/utilities/retrieveRecords.test.js +223 -0
  286. package/src/data/utilities/retrieveRecords.ts +16 -0
  287. package/src/data/utilities/runQuery.test.js +88 -0
  288. package/src/data/utilities/runQuery.ts +75 -0
  289. package/src/data/utilities/updateIndex.test.js +44 -0
  290. package/src/data/utilities/updateIndex.ts +45 -0
  291. package/src/data/utilities/useJoinClause.test.js +94 -0
  292. package/src/data/utilities/useJoinClause.ts +44 -0
  293. package/src/global.d.ts +1 -0
  294. package/src/index.test.js +52 -0
  295. package/src/index.ts +17 -0
  296. package/test-data/__RECORDS.json +32 -0
  297. package/test-data/__indexes/exercise_muscle/fk_exercise_id.json +14 -0
  298. package/test-data/__indexes/exercise_muscle/fk_muscle_id.json +14 -0
  299. package/test-data/__indexes/exercise_muscle/pk__id.json +14 -0
  300. package/test-data/__indexes/exercises/pk__id.json +14 -0
  301. package/test-data/__indexes/muscles/pk__id.json +58 -0
  302. package/test-data/__indexes/workouts/fk_exercise_id.json +23 -0
  303. package/test-data/__indexes/workouts/index_date.json +18 -0
  304. package/test-data/__indexes/workouts/pk__id.json +26 -0
  305. package/test-data/copyDatabase.ts +64 -0
  306. package/test-data/exercise_muscle/bench_press_pectorals.json +6 -0
  307. package/test-data/exercise_muscle/deadlift_hamstrings.json +6 -0
  308. package/test-data/exercise_muscle/squat_quadriceps.json +6 -0
  309. package/test-data/exercise_muscle.json +69 -0
  310. package/test-data/exercises/bench_press.json +5 -0
  311. package/test-data/exercises/deadlift.json +5 -0
  312. package/test-data/exercises/squat.json +5 -0
  313. package/test-data/exercises.json +34 -0
  314. package/test-data/muscles/abdominal.json +6 -0
  315. package/test-data/muscles/biceps.json +6 -0
  316. package/test-data/muscles/calves.json +6 -0
  317. package/test-data/muscles/deltoids.json +6 -0
  318. package/test-data/muscles/erector_spinae.json +6 -0
  319. package/test-data/muscles/forearms.json +6 -0
  320. package/test-data/muscles/gluteus_maximus.json +6 -0
  321. package/test-data/muscles/hamstrings.json +6 -0
  322. package/test-data/muscles/latissimus_dorsi.json +6 -0
  323. package/test-data/muscles/obliques.json +6 -0
  324. package/test-data/muscles/pectorals.json +6 -0
  325. package/test-data/muscles/quadriceps.json +6 -0
  326. package/test-data/muscles/trapezius.json +6 -0
  327. package/test-data/muscles/triceps.json +6 -0
  328. package/test-data/muscles.json +50 -0
  329. package/test-data/workouts/2024-06-02-001.json +10 -0
  330. package/test-data/workouts/2024-06-02-002.json +10 -0
  331. package/test-data/workouts/2024-06-02-003.json +10 -0
  332. package/test-data/workouts/2024-06-05-001.json +10 -0
  333. package/test-data/workouts/2024-06-05-002.json +10 -0
  334. package/test-data/workouts/2024-06-05-003.json +10 -0
  335. package/test-data/workouts.json +84 -0
  336. 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
+ })