@actual-app/core 26.3.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 (358) hide show
  1. package/.swcrc +11 -0
  2. package/bin/build-browser +40 -0
  3. package/bin/copy-migrations +9 -0
  4. package/db.sqlite +0 -0
  5. package/default-db.sqlite +0 -0
  6. package/migrations/.force-copy-windows +0 -0
  7. package/migrations/1548957970627_remove-db-version.sql +5 -0
  8. package/migrations/1550601598648_payees.sql +23 -0
  9. package/migrations/1555786194328_remove_category_group_unique.sql +25 -0
  10. package/migrations/1561751833510_indexes.sql +7 -0
  11. package/migrations/1567699552727_budget.sql +38 -0
  12. package/migrations/1582384163573_cleared.sql +6 -0
  13. package/migrations/1597756566448_rules.sql +10 -0
  14. package/migrations/1608652596043_parent_field.sql +13 -0
  15. package/migrations/1608652596044_trans_views.sql +56 -0
  16. package/migrations/1612625548236_optimize.sql +7 -0
  17. package/migrations/1614782639336_trans_views2.sql +33 -0
  18. package/migrations/1615745967948_meta.sql +10 -0
  19. package/migrations/1616167010796_accounts_order.sql +5 -0
  20. package/migrations/1618975177358_schedules.sql +28 -0
  21. package/migrations/1632571489012_remove_cache.js +136 -0
  22. package/migrations/1679728867040_rules_conditions.sql +5 -0
  23. package/migrations/1681115033845_add_schedule_name.sql +5 -0
  24. package/migrations/1682974838138_remove_payee_rules.sql +5 -0
  25. package/migrations/1685007876842_add_category_hidden.sql +6 -0
  26. package/migrations/1686139660866_remove_account_type.sql +5 -0
  27. package/migrations/1688749527273_transaction_filters.sql +10 -0
  28. package/migrations/1688841238000_add_account_type.sql +5 -0
  29. package/migrations/1691233396000_add_schedule_next_date_tombstone.sql +5 -0
  30. package/migrations/1694438752000_add_goal_targets.sql +7 -0
  31. package/migrations/1697046240000_add_reconciled.sql +5 -0
  32. package/migrations/1704572023730_add_account_sync_source.sql +5 -0
  33. package/migrations/1704572023731_add_missing_goCardless_sync_source.sql +9 -0
  34. package/migrations/1707267033000_reports.sql +28 -0
  35. package/migrations/1712784523000_unhide_input_group.sql +8 -0
  36. package/migrations/1716359441000_include_current.sql +5 -0
  37. package/migrations/1720310586000_link_transfer_schedules.sql +19 -0
  38. package/migrations/1720664867241_add_payee_favorite.sql +5 -0
  39. package/migrations/1720665000000_goal_context.sql +6 -0
  40. package/migrations/1722717601000_reports_move_selected_categories.js +55 -0
  41. package/migrations/1722804019000_create_dashboard_table.js +69 -0
  42. package/migrations/1723665565000_prefs.js +59 -0
  43. package/migrations/1730744182000_fix_dashboard_table.sql +7 -0
  44. package/migrations/1736640000000_custom_report_sorting.sql +7 -0
  45. package/migrations/1737158400000_add_learn_categories_to_payees.sql +5 -0
  46. package/migrations/1738491452000_sorting_rename.sql +13 -0
  47. package/migrations/1739139550000_bank_sync_page.sql +7 -0
  48. package/migrations/1740506588539_add_last_reconciled_at.sql +5 -0
  49. package/migrations/1745425408000_update_budgetType_pref.sql +7 -0
  50. package/migrations/1749799110000_add_tags.sql +10 -0
  51. package/migrations/1749799110001_tags_tombstone.sql +5 -0
  52. package/migrations/1754611200000_add_category_template_settings.sql +5 -0
  53. package/migrations/1759260219000_add_trim_interval_report_setting.sql +6 -0
  54. package/migrations/1759842823172_add_isGlobal_to_preferences.sql +1 -0
  55. package/migrations/1762178745667_rename_csv_skip_lines_pref.sql +8 -0
  56. package/migrations/1765518577215_multiple_dashboards.js +30 -0
  57. package/migrations/1768872504000_add_payee_locations.sql +21 -0
  58. package/package.json +128 -0
  59. package/src/mocks/arbitrary-schema.ts +162 -0
  60. package/src/mocks/budget.ts +901 -0
  61. package/src/mocks/files/8859-1.qfx +63 -0
  62. package/src/mocks/files/best.data-ever$.QFX +124 -0
  63. package/src/mocks/files/big.data.QiF +91 -0
  64. package/src/mocks/files/budgets/.commit-to-git +0 -0
  65. package/src/mocks/files/camt/camt.053.payee-memo.xml +127 -0
  66. package/src/mocks/files/camt/camt.053.xml +463 -0
  67. package/src/mocks/files/credit-card.ofx +11 -0
  68. package/src/mocks/files/data-multi-decimal.ofx +64 -0
  69. package/src/mocks/files/data-payee-memo.ofx +75 -0
  70. package/src/mocks/files/data-payee-memo.qif +17 -0
  71. package/src/mocks/files/data.ofx +124 -0
  72. package/src/mocks/files/data.qfx +124 -0
  73. package/src/mocks/files/data.qif +91 -0
  74. package/src/mocks/files/default-budget-template/db.sqlite +0 -0
  75. package/src/mocks/files/default-budget-template/metadata.json +6 -0
  76. package/src/mocks/files/html-vals.qfx +17 -0
  77. package/src/mocks/index.ts +221 -0
  78. package/src/mocks/migrations/1508717984291_up_add-poop.sql +13 -0
  79. package/src/mocks/migrations/1508718036311_up_modify-poop.sql +2 -0
  80. package/src/mocks/migrations/1508727787513_remove-is_income.sql +15 -0
  81. package/src/mocks/random.ts +16 -0
  82. package/src/mocks/setup.ts +180 -0
  83. package/src/mocks/spreadsheet.ts +101 -0
  84. package/src/mocks/util.ts +82 -0
  85. package/src/platform/client/connection/README.md +3 -0
  86. package/src/platform/client/connection/__mocks__/index.ts +67 -0
  87. package/src/platform/client/connection/index-types.ts +95 -0
  88. package/src/platform/client/connection/index.browser.ts +213 -0
  89. package/src/platform/client/connection/index.ts +155 -0
  90. package/src/platform/client/undo/index.ts +59 -0
  91. package/src/platform/exceptions/__mocks__/index.ts +7 -0
  92. package/src/platform/exceptions/index.ts +9 -0
  93. package/src/platform/server/asyncStorage/__mocks__/index.ts +50 -0
  94. package/src/platform/server/asyncStorage/index-types.ts +35 -0
  95. package/src/platform/server/asyncStorage/index.api.ts +2 -0
  96. package/src/platform/server/asyncStorage/index.electron.ts +88 -0
  97. package/src/platform/server/asyncStorage/index.ts +126 -0
  98. package/src/platform/server/connection/README.md +3 -0
  99. package/src/platform/server/connection/__mocks__/index.ts +15 -0
  100. package/src/platform/server/connection/index-types.ts +20 -0
  101. package/src/platform/server/connection/index.api.ts +13 -0
  102. package/src/platform/server/connection/index.electron.ts +102 -0
  103. package/src/platform/server/connection/index.ts +154 -0
  104. package/src/platform/server/fetch/__mocks__/index.ts +3 -0
  105. package/src/platform/server/fetch/index.api.ts +1 -0
  106. package/src/platform/server/fetch/index.electron.ts +18 -0
  107. package/src/platform/server/fetch/index.ts +20 -0
  108. package/src/platform/server/fs/index.api.ts +198 -0
  109. package/src/platform/server/fs/index.electron.ts +208 -0
  110. package/src/platform/server/fs/index.test.ts +117 -0
  111. package/src/platform/server/fs/index.ts +416 -0
  112. package/src/platform/server/fs/path-join.api.ts +1 -0
  113. package/src/platform/server/fs/path-join.electron.ts +1 -0
  114. package/src/platform/server/fs/path-join.ts +97 -0
  115. package/src/platform/server/fs/shared.ts +33 -0
  116. package/src/platform/server/indexeddb/index.ts +115 -0
  117. package/src/platform/server/log/index.ts +43 -0
  118. package/src/platform/server/sqlite/index.api.ts +2 -0
  119. package/src/platform/server/sqlite/index.electron.ts +134 -0
  120. package/src/platform/server/sqlite/index.test.ts +108 -0
  121. package/src/platform/server/sqlite/index.ts +241 -0
  122. package/src/platform/server/sqlite/normalise.ts +9 -0
  123. package/src/platform/server/sqlite/unicodeLike.test.ts +58 -0
  124. package/src/platform/server/sqlite/unicodeLike.ts +31 -0
  125. package/src/server/__mocks__/post.ts +9 -0
  126. package/src/server/__snapshots__/main.test.ts.snap +199 -0
  127. package/src/server/__snapshots__/sheet.test.ts.snap +9 -0
  128. package/src/server/accounts/__snapshots__/sync.test.ts.snap +136 -0
  129. package/src/server/accounts/app-bank-sync.test.ts +136 -0
  130. package/src/server/accounts/app.ts +1294 -0
  131. package/src/server/accounts/link.ts +25 -0
  132. package/src/server/accounts/payees.ts +36 -0
  133. package/src/server/accounts/sync.test.ts +679 -0
  134. package/src/server/accounts/sync.ts +1168 -0
  135. package/src/server/accounts/title/index.ts +60 -0
  136. package/src/server/accounts/title/lower-case.ts +93 -0
  137. package/src/server/accounts/title/specials.ts +21 -0
  138. package/src/server/admin/app.ts +241 -0
  139. package/src/server/api-models.ts +244 -0
  140. package/src/server/api.test.ts +36 -0
  141. package/src/server/api.ts +1030 -0
  142. package/src/server/app.ts +91 -0
  143. package/src/server/aql/compiler.test.ts +966 -0
  144. package/src/server/aql/compiler.ts +1222 -0
  145. package/src/server/aql/exec.test.ts +289 -0
  146. package/src/server/aql/exec.ts +128 -0
  147. package/src/server/aql/index.ts +41 -0
  148. package/src/server/aql/schema/executors.test.ts +420 -0
  149. package/src/server/aql/schema/executors.ts +345 -0
  150. package/src/server/aql/schema/index.test.ts +67 -0
  151. package/src/server/aql/schema/index.ts +409 -0
  152. package/src/server/aql/schema-helpers.test.ts +242 -0
  153. package/src/server/aql/schema-helpers.ts +208 -0
  154. package/src/server/aql/views.test.ts +62 -0
  155. package/src/server/aql/views.ts +57 -0
  156. package/src/server/auth/app.ts +387 -0
  157. package/src/server/bench.ts +29 -0
  158. package/src/server/budget/actions.ts +686 -0
  159. package/src/server/budget/app.ts +469 -0
  160. package/src/server/budget/base.test.ts +340 -0
  161. package/src/server/budget/base.ts +339 -0
  162. package/src/server/budget/category-template-context.test.ts +1658 -0
  163. package/src/server/budget/category-template-context.ts +862 -0
  164. package/src/server/budget/cleanup-template.pegjs +27 -0
  165. package/src/server/budget/cleanup-template.ts +408 -0
  166. package/src/server/budget/envelope.ts +403 -0
  167. package/src/server/budget/goal-template.pegjs +110 -0
  168. package/src/server/budget/goal-template.ts +309 -0
  169. package/src/server/budget/report.ts +308 -0
  170. package/src/server/budget/schedule-template.test.ts +184 -0
  171. package/src/server/budget/schedule-template.ts +351 -0
  172. package/src/server/budget/statements.ts +60 -0
  173. package/src/server/budget/template-notes.test.ts +393 -0
  174. package/src/server/budget/template-notes.ts +323 -0
  175. package/src/server/budget/util.ts +25 -0
  176. package/src/server/budgetfiles/__snapshots__/backups.test.ts.snap +101 -0
  177. package/src/server/budgetfiles/app.ts +672 -0
  178. package/src/server/budgetfiles/backups.test.ts +79 -0
  179. package/src/server/budgetfiles/backups.ts +251 -0
  180. package/src/server/cloud-storage.ts +467 -0
  181. package/src/server/dashboard/app.ts +373 -0
  182. package/src/server/db/__snapshots__/index.test.ts.snap +271 -0
  183. package/src/server/db/index.test.ts +300 -0
  184. package/src/server/db/index.ts +855 -0
  185. package/src/server/db/mappings.ts +59 -0
  186. package/src/server/db/sort.ts +58 -0
  187. package/src/server/db/types/index.ts +342 -0
  188. package/src/server/db/util.ts +36 -0
  189. package/src/server/encryption/app.ts +133 -0
  190. package/src/server/encryption/encryption-internals.api.ts +2 -0
  191. package/src/server/encryption/encryption-internals.electron.ts +89 -0
  192. package/src/server/encryption/encryption-internals.ts +109 -0
  193. package/src/server/encryption/encryption.test.ts +19 -0
  194. package/src/server/encryption/index.test.ts +19 -0
  195. package/src/server/encryption/index.ts +89 -0
  196. package/src/server/errors.ts +110 -0
  197. package/src/server/filters/app.ts +191 -0
  198. package/src/server/importers/actual.ts +49 -0
  199. package/src/server/importers/index.ts +58 -0
  200. package/src/server/importers/ynab4-types.ts +163 -0
  201. package/src/server/importers/ynab4.ts +470 -0
  202. package/src/server/importers/ynab5-types.ts +290 -0
  203. package/src/server/importers/ynab5.ts +1193 -0
  204. package/src/server/main-app.ts +25 -0
  205. package/src/server/main.test.ts +392 -0
  206. package/src/server/main.ts +336 -0
  207. package/src/server/migrate/__snapshots__/migrations.test.ts.snap +17 -0
  208. package/src/server/migrate/cli.ts +100 -0
  209. package/src/server/migrate/migrations.test.ts +81 -0
  210. package/src/server/migrate/migrations.ts +192 -0
  211. package/src/server/models.ts +184 -0
  212. package/src/server/mutators.ts +139 -0
  213. package/src/server/notes/app.ts +18 -0
  214. package/src/server/payees/app.ts +351 -0
  215. package/src/server/polyfills.ts +26 -0
  216. package/src/server/post.ts +219 -0
  217. package/src/server/preferences/app.ts +249 -0
  218. package/src/server/prefs.ts +91 -0
  219. package/src/server/reports/app.ts +187 -0
  220. package/src/server/rules/action.ts +344 -0
  221. package/src/server/rules/app.ts +193 -0
  222. package/src/server/rules/condition.ts +436 -0
  223. package/src/server/rules/customFunctions.ts +61 -0
  224. package/src/server/rules/formula-action.test.ts +175 -0
  225. package/src/server/rules/handlebars-helpers.ts +131 -0
  226. package/src/server/rules/index.test.ts +1095 -0
  227. package/src/server/rules/index.ts +22 -0
  228. package/src/server/rules/rule-indexer.ts +89 -0
  229. package/src/server/rules/rule-utils.ts +274 -0
  230. package/src/server/rules/rule.ts +193 -0
  231. package/src/server/schedules/app.test.ts +502 -0
  232. package/src/server/schedules/app.ts +644 -0
  233. package/src/server/schedules/find-schedules.ts +391 -0
  234. package/src/server/server-config.ts +59 -0
  235. package/src/server/sheet.test.ts +101 -0
  236. package/src/server/sheet.ts +280 -0
  237. package/src/server/spreadsheet/__snapshots__/spreadsheet.test.ts.snap +5 -0
  238. package/src/server/spreadsheet/app.ts +54 -0
  239. package/src/server/spreadsheet/globals.ts +13 -0
  240. package/src/server/spreadsheet/graph-data-structure.ts +165 -0
  241. package/src/server/spreadsheet/scratch +60 -0
  242. package/src/server/spreadsheet/spreadsheet.test.ts +191 -0
  243. package/src/server/spreadsheet/spreadsheet.ts +523 -0
  244. package/src/server/spreadsheet/util.ts +15 -0
  245. package/src/server/sql/init.sql +88 -0
  246. package/src/server/sync/__snapshots__/sync.test.ts.snap +31 -0
  247. package/src/server/sync/app.ts +29 -0
  248. package/src/server/sync/encoder.ts +129 -0
  249. package/src/server/sync/index.ts +820 -0
  250. package/src/server/sync/make-test-message.ts +19 -0
  251. package/src/server/sync/migrate.test.ts +169 -0
  252. package/src/server/sync/migrate.ts +48 -0
  253. package/src/server/sync/repair.ts +39 -0
  254. package/src/server/sync/reset.ts +91 -0
  255. package/src/server/sync/sync.property.test.ts +385 -0
  256. package/src/server/sync/sync.test.ts +349 -0
  257. package/src/server/sync/utils.ts +3 -0
  258. package/src/server/tags/app.ts +101 -0
  259. package/src/server/tests/mockData.json +9352 -0
  260. package/src/server/tests/mockSyncServer.ts +119 -0
  261. package/src/server/tools/app.ts +152 -0
  262. package/src/server/transactions/__snapshots__/transaction-rules.test.ts.snap +173 -0
  263. package/src/server/transactions/__snapshots__/transfer.test.ts.snap +655 -0
  264. package/src/server/transactions/app.ts +136 -0
  265. package/src/server/transactions/export/export-to-csv.ts +132 -0
  266. package/src/server/transactions/import/__snapshots__/parse-file.test.ts.snap +1582 -0
  267. package/src/server/transactions/import/ofx2json.test.ts +33 -0
  268. package/src/server/transactions/import/ofx2json.ts +157 -0
  269. package/src/server/transactions/import/parse-file.test.ts +224 -0
  270. package/src/server/transactions/import/parse-file.ts +286 -0
  271. package/src/server/transactions/import/qif2json.ts +110 -0
  272. package/src/server/transactions/import/xmlcamt2json.ts +168 -0
  273. package/src/server/transactions/index.ts +196 -0
  274. package/src/server/transactions/merge.test.ts +370 -0
  275. package/src/server/transactions/merge.ts +139 -0
  276. package/src/server/transactions/transaction-rules.test.ts +994 -0
  277. package/src/server/transactions/transaction-rules.ts +1038 -0
  278. package/src/server/transactions/transfer.test.ts +221 -0
  279. package/src/server/transactions/transfer.ts +173 -0
  280. package/src/server/undo.ts +271 -0
  281. package/src/server/update.ts +37 -0
  282. package/src/server/util/budget-name.ts +61 -0
  283. package/src/server/util/custom-sync-mapping.ts +48 -0
  284. package/src/server/util/rschedule.ts +9 -0
  285. package/src/shared/__mocks__/platform.ts +7 -0
  286. package/src/shared/__snapshots__/months.test.ts.snap +21 -0
  287. package/src/shared/arithmetic.test.ts +112 -0
  288. package/src/shared/arithmetic.ts +170 -0
  289. package/src/shared/async.test.ts +135 -0
  290. package/src/shared/async.ts +76 -0
  291. package/src/shared/constants.ts +5 -0
  292. package/src/shared/currencies.ts +70 -0
  293. package/src/shared/dashboard.ts +260 -0
  294. package/src/shared/environment.ts +18 -0
  295. package/src/shared/errors.ts +195 -0
  296. package/src/shared/locale.ts +27 -0
  297. package/src/shared/location-utils.test.ts +69 -0
  298. package/src/shared/location-utils.ts +49 -0
  299. package/src/shared/months.test.ts +5 -0
  300. package/src/shared/months.ts +485 -0
  301. package/src/shared/normalisation.ts +6 -0
  302. package/src/shared/platform.electron.ts +21 -0
  303. package/src/shared/platform.ts +20 -0
  304. package/src/shared/query.ts +176 -0
  305. package/src/shared/rules.test.ts +56 -0
  306. package/src/shared/rules.ts +371 -0
  307. package/src/shared/schedules.test.ts +570 -0
  308. package/src/shared/schedules.ts +560 -0
  309. package/src/shared/test-helpers.ts +156 -0
  310. package/src/shared/transactions.test.ts +275 -0
  311. package/src/shared/transactions.ts +433 -0
  312. package/src/shared/transfer.test.ts +75 -0
  313. package/src/shared/transfer.ts +16 -0
  314. package/src/shared/user.ts +4 -0
  315. package/src/shared/util.test.ts +240 -0
  316. package/src/shared/util.ts +633 -0
  317. package/src/types/api-handlers.ts +287 -0
  318. package/src/types/budget.ts +8 -0
  319. package/src/types/file.ts +47 -0
  320. package/src/types/handlers.ts +46 -0
  321. package/src/types/models/account.ts +24 -0
  322. package/src/types/models/bank-sync.ts +23 -0
  323. package/src/types/models/bank.ts +6 -0
  324. package/src/types/models/category-group.ts +11 -0
  325. package/src/types/models/category.ts +13 -0
  326. package/src/types/models/dashboard.ts +199 -0
  327. package/src/types/models/gocardless.ts +84 -0
  328. package/src/types/models/import-transaction.ts +56 -0
  329. package/src/types/models/index.ts +23 -0
  330. package/src/types/models/nearby-payee.ts +7 -0
  331. package/src/types/models/note.ts +4 -0
  332. package/src/types/models/openid.ts +8 -0
  333. package/src/types/models/payee-location.ts +8 -0
  334. package/src/types/models/payee.ts +10 -0
  335. package/src/types/models/pluggyai.ts +19 -0
  336. package/src/types/models/reports.ts +144 -0
  337. package/src/types/models/rule.ts +174 -0
  338. package/src/types/models/schedule.ts +49 -0
  339. package/src/types/models/simplefin.ts +28 -0
  340. package/src/types/models/tags.ts +6 -0
  341. package/src/types/models/templates.ts +135 -0
  342. package/src/types/models/transaction-filter.ts +9 -0
  343. package/src/types/models/transaction.ts +39 -0
  344. package/src/types/models/user-access.ts +10 -0
  345. package/src/types/models/user.ts +25 -0
  346. package/src/types/prefs.ts +167 -0
  347. package/src/types/server-events.ts +86 -0
  348. package/src/types/server-handlers.ts +27 -0
  349. package/src/types/util.ts +26 -0
  350. package/tsconfig.json +34 -0
  351. package/typings/pegjs.ts +1 -0
  352. package/typings/process-worker.ts +12 -0
  353. package/typings/vite-plugin-peggy-loader.ts +1 -0
  354. package/typings/window.ts +62 -0
  355. package/vite.config.ts +109 -0
  356. package/vite.desktop.config.ts +59 -0
  357. package/vitest.config.ts +43 -0
  358. package/vitest.web.config.ts +38 -0
@@ -0,0 +1,5 @@
1
+ import * as monthUtils from './months';
2
+
3
+ test('range returns a full range', () => {
4
+ expect(monthUtils.range('2016-10', '2018-01')).toMatchSnapshot();
5
+ });
@@ -0,0 +1,485 @@
1
+ // @ts-strict-ignore
2
+ import * as d from 'date-fns';
3
+ import type { Locale } from 'date-fns';
4
+ import memoizeOne from 'memoize-one';
5
+
6
+ import type { SyncedPrefs } from '../types/prefs';
7
+
8
+ import * as Platform from './platform';
9
+
10
+ type DateLike = string | Date;
11
+ type Day = 0 | 1 | 2 | 3 | 4 | 5 | 6;
12
+
13
+ export function _parse(value: DateLike): Date {
14
+ if (typeof value === 'string') {
15
+ // Dates are hard. We just want to deal with months in the format
16
+ // 2020-01 and days in the format 2020-01-01, but life is never
17
+ // simple. We want to rely on native dates for date logic because
18
+ // days are complicated (leap years, etc). But relying on native
19
+ // dates mean we're exposed to craziness.
20
+ //
21
+ // The biggest problem is that JS dates work with local time by
22
+ // default. We could try to only work with UTC, but there's not an
23
+ // easy way to make `format` avoid local time, and not sure if we
24
+ // want that anyway (`currentMonth` should surely print the local
25
+ // time). We need to embrace local time, and as long as inputs to
26
+ // date logic and outputs from format are local time, it should
27
+ // work.
28
+ //
29
+ // To make sure we're in local time, always give Date integer
30
+ // values. If you pass in a string to parse, different string
31
+ // formats produce different results.
32
+ //
33
+ // A big problem is daylight savings, however. Usually, when
34
+ // giving the time to the Date constructor, you get back a date
35
+ // specifically for that time in your local timezone. However, if
36
+ // daylight savings occurs on that exact time, you will get back
37
+ // something different:
38
+ //
39
+ // This is fine:
40
+ // > new Date(2017, 2, 12, 1).toString()
41
+ // > 'Sun Mar 12 2017 01:00:00 GMT-0500 (Eastern Standard Time)'
42
+ //
43
+ // But wait, we got back a different time (3AM instead of 2AM):
44
+ // > new Date(2017, 2, 12, 2).toString()
45
+ // > 'Sun Mar 12 2017 03:00:00 GMT-0400 (Eastern Daylight Time)'
46
+ //
47
+ // The time is "correctly" adjusted via DST, but we _really_
48
+ // wanted 2AM. The problem is that time simply doesn't exist.
49
+ //
50
+ // Why is this a problem? Well, consider a case where the DST
51
+ // shift happens *at midnight* and it goes back an hour. You think
52
+ // you have a date object for the next day, but when formatted it
53
+ // actually shows the previous day. A more likely scenario: buggy
54
+ // timezone data makes JS dates do this shift when it shouldn't,
55
+ // so using midnight at the time for date logic gives back the
56
+ // last day. See the time range of Sep 30 15:00 - Oct 1 1:00 for
57
+ // the AEST timezone when nodejs-mobile incorrectly gives you back
58
+ // a time an hour *before* you specified. Since this happens on
59
+ // Oct 1, doing `addMonths(September, 1)` still gives you back
60
+ // September. Issue here:
61
+ // https://github.com/JaneaSystems/nodejs-mobile/issues/251
62
+ //
63
+ // The fix is simple once you understand this. Always use the 12th
64
+ // hour of the day. That's it. There is no DST that shifts more
65
+ // than 12 hours (god let's hope not) so no matter how far DST has
66
+ // shifted backwards or forwards, doing date logic will stay
67
+ // within the day we want.
68
+
69
+ const [year, month, day] = value.split('-');
70
+ if (day != null) {
71
+ return new Date(parseInt(year), parseInt(month) - 1, parseInt(day), 12);
72
+ } else if (month != null) {
73
+ return new Date(parseInt(year), parseInt(month) - 1, 1, 12);
74
+ } else {
75
+ return new Date(parseInt(year), 0, 1, 12);
76
+ }
77
+ }
78
+ if (typeof value === 'number') {
79
+ return new Date(value);
80
+ }
81
+ return value;
82
+ }
83
+
84
+ export const parseDate = _parse;
85
+
86
+ export function yearFromDate(date: DateLike): string {
87
+ return d.format(_parse(date), 'yyyy');
88
+ }
89
+
90
+ export function monthFromDate(date: DateLike): string {
91
+ return d.format(_parse(date), 'yyyy-MM');
92
+ }
93
+
94
+ export function weekFromDate(
95
+ date: DateLike,
96
+ firstDayOfWeekIdx: SyncedPrefs['firstDayOfWeekIdx'],
97
+ ): string {
98
+ const converted = parseInt(firstDayOfWeekIdx || '0') as Day;
99
+ return d.format(
100
+ _parse(d.startOfWeek(_parse(date), { weekStartsOn: converted })),
101
+ 'yyyy-MM-dd',
102
+ );
103
+ }
104
+
105
+ export function firstDayOfMonth(date: DateLike): string {
106
+ return dayFromDate(d.startOfMonth(_parse(date)));
107
+ }
108
+
109
+ export function lastDayOfMonth(date: DateLike): string {
110
+ return dayFromDate(d.endOfMonth(_parse(date)));
111
+ }
112
+
113
+ export function dayFromDate(date: DateLike): string {
114
+ return d.format(_parse(date), 'yyyy-MM-dd');
115
+ }
116
+
117
+ export function currentMonth(): string {
118
+ if (global.IS_TESTING || Platform.isPlaywright) {
119
+ return global.currentMonth || '2017-01';
120
+ } else {
121
+ return d.format(new Date(), 'yyyy-MM');
122
+ }
123
+ }
124
+
125
+ export function currentWeek(
126
+ firstDayOfWeekIdx?: SyncedPrefs['firstDayOfWeekIdx'],
127
+ ): string {
128
+ if (global.IS_TESTING || Platform.isPlaywright) {
129
+ return global.currentWeek || '2017-01-01';
130
+ } else {
131
+ const converted = parseInt(firstDayOfWeekIdx || '0') as Day;
132
+ return d.format(
133
+ _parse(d.startOfWeek(new Date(), { weekStartsOn: converted })),
134
+ 'yyyy-MM-dd',
135
+ );
136
+ }
137
+ }
138
+
139
+ export function currentYear(): string {
140
+ if (global.IS_TESTING || Platform.isPlaywright) {
141
+ return global.currentMonth || '2017';
142
+ } else {
143
+ return d.format(new Date(), 'yyyy');
144
+ }
145
+ }
146
+
147
+ export function currentDate(): Date {
148
+ if (global.IS_TESTING || Platform.isPlaywright) {
149
+ return d.parse(currentDay(), 'yyyy-MM-dd', new Date());
150
+ }
151
+
152
+ return new Date();
153
+ }
154
+
155
+ export function currentDay(): string {
156
+ if (global.IS_TESTING || Platform.isPlaywright) {
157
+ return '2017-01-01';
158
+ } else {
159
+ return d.format(new Date(), 'yyyy-MM-dd');
160
+ }
161
+ }
162
+
163
+ export function nextMonth(month: DateLike): string {
164
+ return d.format(d.addMonths(_parse(month), 1), 'yyyy-MM');
165
+ }
166
+
167
+ export function prevYear(month: DateLike, format = 'yyyy-MM'): string {
168
+ return d.format(d.subMonths(_parse(month), 12), format);
169
+ }
170
+
171
+ export function prevMonth(month: DateLike): string {
172
+ return d.format(d.subMonths(_parse(month), 1), 'yyyy-MM');
173
+ }
174
+
175
+ export function addYears(year: DateLike, n: number): string {
176
+ return d.format(d.addYears(_parse(year), n), 'yyyy');
177
+ }
178
+
179
+ export function addMonths(month: DateLike, n: number): string {
180
+ return d.format(d.addMonths(_parse(month), n), 'yyyy-MM');
181
+ }
182
+
183
+ export function addWeeks(date: DateLike, n: number): string {
184
+ return d.format(d.addWeeks(_parse(date), n), 'yyyy-MM-dd');
185
+ }
186
+
187
+ export function differenceInCalendarMonths(
188
+ month1: DateLike,
189
+ month2: DateLike,
190
+ ): number {
191
+ return d.differenceInCalendarMonths(_parse(month1), _parse(month2));
192
+ }
193
+
194
+ export function differenceInCalendarDays(
195
+ month1: DateLike,
196
+ month2: DateLike,
197
+ ): number {
198
+ return d.differenceInCalendarDays(_parse(month1), _parse(month2));
199
+ }
200
+
201
+ export function subMonths(month: string | Date, n: number) {
202
+ return d.format(d.subMonths(_parse(month), n), 'yyyy-MM');
203
+ }
204
+
205
+ export function subWeeks(date: DateLike, n: number): string {
206
+ return d.format(d.subWeeks(_parse(date), n), 'yyyy-MM-dd');
207
+ }
208
+
209
+ export function subYears(year: string | Date, n: number) {
210
+ return d.format(d.subYears(_parse(year), n), 'yyyy');
211
+ }
212
+
213
+ export function addDays(day: DateLike, n: number): string {
214
+ return d.format(d.addDays(_parse(day), n), 'yyyy-MM-dd');
215
+ }
216
+
217
+ export function subDays(day: DateLike, n: number): string {
218
+ return d.format(d.subDays(_parse(day), n), 'yyyy-MM-dd');
219
+ }
220
+
221
+ export function isBefore(month1: DateLike, month2: DateLike): boolean {
222
+ return d.isBefore(_parse(month1), _parse(month2));
223
+ }
224
+
225
+ export function isAfter(month1: DateLike, month2: DateLike): boolean {
226
+ return d.isAfter(_parse(month1), _parse(month2));
227
+ }
228
+
229
+ export function isCurrentMonth(month: DateLike): boolean {
230
+ return month === currentMonth();
231
+ }
232
+
233
+ export function isCurrentDay(day: DateLike): boolean {
234
+ return day === currentDay();
235
+ }
236
+
237
+ // TODO: This doesn't really fit in this module anymore, should
238
+ // probably live elsewhere
239
+ export function bounds(month: DateLike): { start: number; end: number } {
240
+ return {
241
+ start: parseInt(d.format(d.startOfMonth(_parse(month)), 'yyyyMMdd')),
242
+ end: parseInt(d.format(d.endOfMonth(_parse(month)), 'yyyyMMdd')),
243
+ };
244
+ }
245
+
246
+ export function _yearRange(
247
+ start: DateLike,
248
+ end: DateLike,
249
+ inclusive = false,
250
+ ): string[] {
251
+ const years: string[] = [];
252
+ let year = yearFromDate(start);
253
+ const endYear = yearFromDate(end);
254
+ while (d.isBefore(_parse(year), _parse(endYear))) {
255
+ years.push(year);
256
+ year = addYears(year, 1);
257
+ }
258
+
259
+ if (inclusive) {
260
+ years.push(year);
261
+ }
262
+
263
+ return years;
264
+ }
265
+
266
+ export function yearRangeInclusive(start: DateLike, end: DateLike): string[] {
267
+ return _yearRange(start, end, true);
268
+ }
269
+
270
+ export function _weekRange(
271
+ start: DateLike,
272
+ end: DateLike,
273
+ inclusive = false,
274
+ firstDayOfWeekIdx?: SyncedPrefs['firstDayOfWeekIdx'],
275
+ ): string[] {
276
+ const weeks: string[] = [];
277
+ let week = weekFromDate(start, firstDayOfWeekIdx);
278
+ const endWeek = weekFromDate(end, firstDayOfWeekIdx);
279
+ while (d.isBefore(_parse(week), _parse(endWeek))) {
280
+ weeks.push(week);
281
+ week = addWeeks(week, 1);
282
+ }
283
+
284
+ if (inclusive) {
285
+ weeks.push(week);
286
+ }
287
+
288
+ return weeks;
289
+ }
290
+
291
+ export function weekRangeInclusive(
292
+ start: DateLike,
293
+ end: DateLike,
294
+ firstDayOfWeekIdx?: SyncedPrefs['firstDayOfWeekIdx'],
295
+ ): string[] {
296
+ return _weekRange(start, end, true, firstDayOfWeekIdx);
297
+ }
298
+
299
+ export function _range(
300
+ start: DateLike,
301
+ end: DateLike,
302
+ inclusive = false,
303
+ ): string[] {
304
+ const months: string[] = [];
305
+ let month = monthFromDate(start);
306
+ const endMonth = monthFromDate(end);
307
+ while (d.isBefore(_parse(month), _parse(endMonth))) {
308
+ months.push(month);
309
+ month = addMonths(month, 1);
310
+ }
311
+
312
+ if (inclusive) {
313
+ months.push(month);
314
+ }
315
+
316
+ return months;
317
+ }
318
+
319
+ export function range(start: DateLike, end: DateLike): string[] {
320
+ return _range(start, end);
321
+ }
322
+
323
+ export function rangeInclusive(start: DateLike, end: DateLike): string[] {
324
+ return _range(start, end, true);
325
+ }
326
+
327
+ export function _dayRange(
328
+ start: DateLike,
329
+ end: DateLike,
330
+ inclusive = false,
331
+ ): string[] {
332
+ const days: string[] = [];
333
+ let day = start;
334
+ while (d.isBefore(_parse(day), _parse(end))) {
335
+ days.push(dayFromDate(day));
336
+ day = addDays(day, 1);
337
+ }
338
+
339
+ if (inclusive) {
340
+ days.push(dayFromDate(day));
341
+ }
342
+
343
+ return days;
344
+ }
345
+
346
+ export function dayRange(start: DateLike, end: DateLike) {
347
+ return _dayRange(start, end);
348
+ }
349
+
350
+ export function dayRangeInclusive(start: DateLike, end: DateLike) {
351
+ return _dayRange(start, end, true);
352
+ }
353
+
354
+ export function getMonthFromIndex(year: string, monthIndex: number) {
355
+ const formatMonth = `${monthIndex + 1}`.padStart(2, '0');
356
+ return `${year}-${formatMonth}`;
357
+ }
358
+
359
+ export function getMonthIndex(month: string): number {
360
+ return parseInt(month.slice(5, 7)) - 1;
361
+ }
362
+
363
+ export function getYear(month: string): string {
364
+ return month.slice(0, 4);
365
+ }
366
+
367
+ export function getMonth(day: string): string {
368
+ return day.slice(0, 7);
369
+ }
370
+
371
+ export function getDay(day: string): number {
372
+ return Number(d.format(_parse(day), 'dd'));
373
+ }
374
+
375
+ export function getMonthEnd(day: string): string {
376
+ return subDays(nextMonth(day.slice(0, 7)) + '-01', 1);
377
+ }
378
+
379
+ export function getWeekEnd(
380
+ date: DateLike,
381
+ firstDayOfWeekIdx?: SyncedPrefs['firstDayOfWeekIdx'],
382
+ ): string {
383
+ const converted = parseInt(firstDayOfWeekIdx || '0') as Day;
384
+ return d.format(
385
+ _parse(d.endOfWeek(_parse(date), { weekStartsOn: converted })),
386
+ 'yyyy-MM-dd',
387
+ );
388
+ }
389
+
390
+ export function getYearStart(month: string): string {
391
+ return getYear(month) + '-01';
392
+ }
393
+
394
+ export function getYearEnd(month: string): string {
395
+ return getYear(month) + '-12';
396
+ }
397
+
398
+ export function sheetForMonth(month: string): string {
399
+ return 'budget' + month.replace('-', '');
400
+ }
401
+
402
+ export function nameForMonth(month: DateLike, locale?: Locale): string {
403
+ return d.format(_parse(month), "MMMM ''yy", { locale });
404
+ }
405
+
406
+ export function format(
407
+ month: DateLike,
408
+ format: string,
409
+ locale?: Locale,
410
+ ): string {
411
+ return d.format(_parse(month), format, { locale });
412
+ }
413
+
414
+ export function formatDistance(
415
+ date1: DateLike,
416
+ date2: DateLike,
417
+ locale?: Locale,
418
+ options?: { addSuffix?: boolean; includeSeconds?: boolean },
419
+ ): string {
420
+ return d.formatDistance(_parse(date1), _parse(date2), {
421
+ locale,
422
+ ...options,
423
+ });
424
+ }
425
+
426
+ export const getDateFormatRegex = memoizeOne((format: string) => {
427
+ return new RegExp(
428
+ format
429
+ .replace(/d+/g, '\\d{1,2}')
430
+ .replace(/M+/g, '\\d{1,2}')
431
+ .replace(/y+/g, '\\d{4}'),
432
+ );
433
+ });
434
+
435
+ export const getDayMonthFormat = memoizeOne((format: string) => {
436
+ return format
437
+ .replace(/y+/g, '')
438
+ .replace(/[^\w]$/, '')
439
+ .replace(/^[^\w]/, '');
440
+ });
441
+
442
+ export const getDayMonthRegex = memoizeOne((format: string) => {
443
+ const regex = format
444
+ .replace(/y+/g, '')
445
+ .replace(/[^\w]$/, '')
446
+ .replace(/^[^\w]/, '')
447
+ .replace(/d+/g, '\\d{1,2}')
448
+ .replace(/M+/g, '\\d{1,2}');
449
+ return new RegExp('^' + regex + '$');
450
+ });
451
+
452
+ export const getMonthYearFormat = memoizeOne((format: string) => {
453
+ return format
454
+ .replace(/d+/g, '')
455
+ .replace(/[^\w]$/, '')
456
+ .replace(/^[^\w]/, '')
457
+ .replace(/\/\//, '/')
458
+ .replace(/\.\./, '.')
459
+ .replace(/--/, '-');
460
+ });
461
+
462
+ export const getMonthYearRegex = memoizeOne((format: string) => {
463
+ const regex = format
464
+ .replace(/d+/g, '')
465
+ .replace(/[^\w]$/, '')
466
+ .replace(/^[^\w]/, '')
467
+ .replace(/\/\//, '/')
468
+ .replace(/M+/g, '\\d{1,2}')
469
+ .replace(/y+/g, '\\d{2,4}');
470
+ return new RegExp('^' + regex + '$');
471
+ });
472
+
473
+ export const getShortYearFormat = memoizeOne((format: string) => {
474
+ return format.replace(/y+/g, 'yy');
475
+ });
476
+
477
+ export const getShortYearRegex = memoizeOne((format: string) => {
478
+ const regex = format
479
+ .replace(/[^\w]$/, '')
480
+ .replace(/^[^\w]/, '')
481
+ .replace(/d+/g, '\\d{1,2}')
482
+ .replace(/M+/g, '\\d{1,2}')
483
+ .replace(/y+/g, '\\d{2}');
484
+ return new RegExp('^' + regex + '$');
485
+ });
@@ -0,0 +1,6 @@
1
+ export function getNormalisedString(value: string) {
2
+ return value
3
+ .toLowerCase()
4
+ .normalize('NFD')
5
+ .replace(/\p{Diacritic}/gu, '');
6
+ }
@@ -0,0 +1,21 @@
1
+ import os from 'os';
2
+
3
+ import type * as T from './platform';
4
+
5
+ const isWindows = os.platform() === 'win32';
6
+ const isMac = os.platform() === 'darwin';
7
+ const isLinux = os.platform() === 'linux';
8
+
9
+ export const isPlaywright = false;
10
+
11
+ export const OS: typeof T.OS = isWindows
12
+ ? 'windows'
13
+ : isMac
14
+ ? 'mac'
15
+ : isLinux
16
+ ? 'linux'
17
+ : 'unknown';
18
+ export const env: typeof T.env = 'unknown';
19
+ export const isBrowser: typeof T.isBrowser = false;
20
+
21
+ export const isIOSAgent: typeof T.isIOSAgent = false;
@@ -0,0 +1,20 @@
1
+ import { UAParser } from 'ua-parser-js';
2
+
3
+ const isWindows =
4
+ navigator.platform && navigator.platform.toLowerCase() === 'win32';
5
+
6
+ const isMac =
7
+ navigator.platform && navigator.platform.toUpperCase().indexOf('MAC') >= 0;
8
+
9
+ export const isPlaywright = navigator.userAgent === 'playwright';
10
+
11
+ export const OS: 'windows' | 'mac' | 'linux' | 'unknown' = isWindows
12
+ ? 'windows'
13
+ : isMac
14
+ ? 'mac'
15
+ : 'linux';
16
+ export const env: 'web' | 'mobile' | 'unknown' = 'web';
17
+ export const isBrowser: boolean = true;
18
+
19
+ const agent = UAParser(navigator.userAgent);
20
+ export const isIOSAgent = agent.browser.name === 'Mobile Safari';
@@ -0,0 +1,176 @@
1
+ import type { WithRequired } from '../types/util';
2
+
3
+ type ObjectExpression = {
4
+ [key: string]: ObjectExpression | unknown;
5
+ };
6
+
7
+ export type QueryState = {
8
+ get table(): string;
9
+ get tableOptions(): Readonly<Record<string, unknown>>;
10
+ get filterExpressions(): ReadonlyArray<ObjectExpression>;
11
+ get selectExpressions(): ReadonlyArray<ObjectExpression | string | '*'>;
12
+ get groupExpressions(): ReadonlyArray<ObjectExpression | string>;
13
+ get orderExpressions(): ReadonlyArray<ObjectExpression | string>;
14
+ get calculation(): boolean;
15
+ get rawMode(): boolean;
16
+ get withDead(): boolean;
17
+ get validateRefs(): boolean;
18
+ get limit(): number | null;
19
+ get offset(): number | null;
20
+ };
21
+
22
+ export class Query {
23
+ state: QueryState;
24
+
25
+ constructor(state: WithRequired<Partial<QueryState>, 'table'>) {
26
+ this.state = {
27
+ tableOptions: state.tableOptions || {},
28
+ filterExpressions: state.filterExpressions || [],
29
+ selectExpressions: state.selectExpressions || [],
30
+ groupExpressions: state.groupExpressions || [],
31
+ orderExpressions: state.orderExpressions || [],
32
+ calculation: false,
33
+ rawMode: false,
34
+ withDead: false,
35
+ validateRefs: true,
36
+ limit: null,
37
+ offset: null,
38
+ ...state,
39
+ };
40
+ }
41
+
42
+ filter(expr: ObjectExpression) {
43
+ return new Query({
44
+ ...this.state,
45
+ filterExpressions: [...this.state.filterExpressions, expr],
46
+ });
47
+ }
48
+
49
+ unfilter(exprs?: Array<keyof ObjectExpression>) {
50
+ // Remove all filters if no arguments are passed
51
+ if (!exprs) {
52
+ return new Query({
53
+ ...this.state,
54
+ filterExpressions: [],
55
+ });
56
+ }
57
+
58
+ const exprSet = new Set(exprs);
59
+ return new Query({
60
+ ...this.state,
61
+ filterExpressions: this.state.filterExpressions.filter(
62
+ expr => !exprSet.has(Object.keys(expr)[0]),
63
+ ),
64
+ });
65
+ }
66
+
67
+ select(
68
+ exprs:
69
+ | Array<ObjectExpression | string>
70
+ | ObjectExpression
71
+ | string
72
+ | '*'
73
+ | ['*'] = [],
74
+ ) {
75
+ if (!Array.isArray(exprs)) {
76
+ exprs = [exprs];
77
+ }
78
+
79
+ return new Query({
80
+ ...this.state,
81
+ selectExpressions: exprs,
82
+ calculation: false,
83
+ });
84
+ }
85
+
86
+ calculate(expr: ObjectExpression | string) {
87
+ return new Query({
88
+ ...this.state,
89
+ selectExpressions: [{ result: expr }],
90
+ calculation: true,
91
+ });
92
+ }
93
+
94
+ groupBy(exprs: ObjectExpression | string | Array<ObjectExpression | string>) {
95
+ if (!Array.isArray(exprs)) {
96
+ exprs = [exprs];
97
+ }
98
+
99
+ return new Query({
100
+ ...this.state,
101
+ groupExpressions: [...this.state.groupExpressions, ...exprs],
102
+ });
103
+ }
104
+
105
+ orderBy(exprs: ObjectExpression | string | Array<ObjectExpression | string>) {
106
+ if (!Array.isArray(exprs)) {
107
+ exprs = [exprs];
108
+ }
109
+
110
+ return new Query({
111
+ ...this.state,
112
+ orderExpressions: [...this.state.orderExpressions, ...exprs],
113
+ });
114
+ }
115
+
116
+ limit(num: number) {
117
+ return new Query({ ...this.state, limit: num });
118
+ }
119
+
120
+ offset(num: number) {
121
+ return new Query({ ...this.state, offset: num });
122
+ }
123
+
124
+ raw() {
125
+ return new Query({ ...this.state, rawMode: true });
126
+ }
127
+
128
+ withDead() {
129
+ return new Query({ ...this.state, withDead: true });
130
+ }
131
+
132
+ withoutValidatedRefs() {
133
+ return new Query({ ...this.state, validateRefs: false });
134
+ }
135
+
136
+ options(opts: Record<string, unknown>) {
137
+ return new Query({ ...this.state, tableOptions: opts });
138
+ }
139
+
140
+ reset() {
141
+ return q(this.state.table);
142
+ }
143
+
144
+ serialize() {
145
+ return this.state;
146
+ }
147
+
148
+ serializeAsString() {
149
+ return JSON.stringify(this.serialize());
150
+ }
151
+ }
152
+
153
+ export function getPrimaryOrderBy(
154
+ query: Query,
155
+ defaultOrderBy: ObjectExpression | null,
156
+ ) {
157
+ const orderExprs = query.serialize().orderExpressions;
158
+ if (orderExprs.length === 0) {
159
+ if (defaultOrderBy) {
160
+ return { order: 'asc', ...defaultOrderBy };
161
+ }
162
+ return null;
163
+ }
164
+
165
+ const firstOrder = orderExprs[0];
166
+ if (typeof firstOrder === 'string') {
167
+ return { field: firstOrder, order: 'asc' };
168
+ }
169
+ // Handle this form: { field: 'desc' }
170
+ const [field] = Object.keys(firstOrder);
171
+ return { field, order: firstOrder[field] };
172
+ }
173
+
174
+ export function q(table: QueryState['table']) {
175
+ return new Query({ table });
176
+ }