@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,213 @@
1
+ // @ts-strict-ignore
2
+ import { t } from 'i18next';
3
+ import { v4 as uuidv4 } from 'uuid';
4
+
5
+ import { captureBreadcrumb, captureException } from '../../exceptions';
6
+ import * as undo from '../undo';
7
+
8
+ import type * as T from './index-types';
9
+
10
+ const replyHandlers = new Map();
11
+ const listeners = new Map();
12
+ let messageQueue = [];
13
+
14
+ let globalWorker = null;
15
+
16
+ class ReconstructedError extends Error {
17
+ url: string;
18
+ line: string;
19
+ column: string;
20
+
21
+ constructor(message, stack, url, line, column) {
22
+ super(message);
23
+ this.name = this.constructor.name;
24
+ this.message = message;
25
+
26
+ Object.defineProperty(this, 'stack', {
27
+ get: function () {
28
+ return 'extended ' + this._stack;
29
+ },
30
+ set: function (value) {
31
+ this._stack = value;
32
+ },
33
+ });
34
+
35
+ this.stack = stack;
36
+ this.url = url;
37
+ this.line = line;
38
+ this.column = column;
39
+ }
40
+ }
41
+
42
+ function handleMessage(msg) {
43
+ if (msg.type === 'error') {
44
+ // An error happened while handling a message so cleanup the
45
+ // current reply handler and reject the promise. The error will
46
+ // be propagated to the caller through this promise rejection.
47
+ const { id, error } = msg;
48
+ const handler = replyHandlers.get(id);
49
+ if (handler) {
50
+ replyHandlers.delete(id);
51
+ handler.reject(error);
52
+ }
53
+ } else if (msg.type === 'reply') {
54
+ const { id, result, mutated, undoTag } = msg;
55
+
56
+ const handler = replyHandlers.get(id);
57
+ if (handler) {
58
+ replyHandlers.delete(id);
59
+
60
+ if (!mutated) {
61
+ undo.gc(undoTag);
62
+ }
63
+
64
+ handler.resolve(result);
65
+ }
66
+ } else if (msg.type === 'push') {
67
+ const { name, args } = msg;
68
+
69
+ const listens = listeners.get(name);
70
+ if (listens) {
71
+ for (let i = 0; i < listens.length; i++) {
72
+ const stop = listens[i](args);
73
+ if (stop === true) {
74
+ break;
75
+ }
76
+ }
77
+ }
78
+ } else {
79
+ // Ignore internal messages that start with __
80
+ if (!msg.type.startsWith('__')) {
81
+ throw new Error('Unknown message type: ' + JSON.stringify(msg));
82
+ }
83
+ }
84
+ }
85
+
86
+ // Note that this does not support retry. If the worker
87
+ // dies, it will permanently be disconnected. That should be OK since
88
+ // I don't think a worker should ever die due to a system error.
89
+ function connectWorker(worker, onOpen, onError) {
90
+ globalWorker = worker;
91
+
92
+ worker.onmessage = event => {
93
+ const msg = event.data;
94
+
95
+ // The worker implementation implements its own concept of a
96
+ // 'connect' event because the worker is immediately
97
+ // available, but we don't know when the backend is actually
98
+ // ready to handle messages.
99
+ if (msg.type === 'connect') {
100
+ // Send any messages that were queued while closed
101
+ if (messageQueue?.length > 0) {
102
+ messageQueue.forEach(msg => worker.postMessage(msg));
103
+ messageQueue = null;
104
+ }
105
+
106
+ // signal to the backend that we're connected to it
107
+ globalWorker.postMessage({
108
+ name: 'client-connected-to-backend',
109
+ });
110
+ onOpen();
111
+ } else if (msg.type === 'app-init-failure') {
112
+ globalWorker.postMessage({
113
+ name: '__app-init-failure-acknowledged',
114
+ });
115
+ onError(msg);
116
+ } else if (msg.type === 'capture-exception') {
117
+ captureException(
118
+ msg.stack
119
+ ? new ReconstructedError(
120
+ msg.message,
121
+ msg.stack,
122
+ msg.url,
123
+ msg.line,
124
+ msg.column,
125
+ )
126
+ : msg.exc,
127
+ );
128
+
129
+ if (msg.message && msg.message.includes('indexeddb-quota-error')) {
130
+ alert(
131
+ t(
132
+ 'We hit a limit on the local storage available. Edits may not be saved. Please get in touch https://actualbudget.org/contact/ so we can help debug this.',
133
+ ),
134
+ );
135
+ }
136
+ } else if (msg.type === 'capture-breadcrumb') {
137
+ captureBreadcrumb(msg.data);
138
+ } else {
139
+ handleMessage(msg);
140
+ }
141
+ };
142
+
143
+ // In browsers that don't support wasm in workers well (Safari),
144
+ // we run the server on the main process for now. This might not
145
+ // actually be a worker, but instead a message port which we
146
+ // need to start.
147
+ if (worker instanceof MessagePort) {
148
+ worker.start();
149
+ }
150
+ }
151
+
152
+ export const init: T.Init = async function () {
153
+ const worker = await global.Actual.getServerSocket();
154
+ return new Promise((resolve, reject) =>
155
+ connectWorker(worker, resolve, reject),
156
+ );
157
+ };
158
+
159
+ export const send: T.Send = function (
160
+ ...params: Parameters<T.Send>
161
+ ): ReturnType<T.Send> {
162
+ const [name, args, { catchErrors = false } = {}] = params;
163
+ return new Promise((resolve, reject) => {
164
+ const id = uuidv4();
165
+
166
+ replyHandlers.set(id, { resolve, reject });
167
+ const message = {
168
+ id,
169
+ name,
170
+ args,
171
+ undoTag: undo.snapshot(),
172
+ catchErrors,
173
+ };
174
+ if (messageQueue) {
175
+ messageQueue.push(message);
176
+ } else {
177
+ globalWorker.postMessage(message);
178
+ }
179
+ });
180
+ };
181
+
182
+ export const sendCatch: T.SendCatch = function (name, args) {
183
+ return send(name, args, { catchErrors: true });
184
+ };
185
+
186
+ export const listen: T.Listen = function (name, cb) {
187
+ if (!listeners.get(name)) {
188
+ listeners.set(name, []);
189
+ }
190
+ listeners.get(name).push(cb);
191
+
192
+ return () => {
193
+ const arr = listeners.get(name);
194
+ listeners.set(
195
+ name,
196
+ arr.filter(cb_ => cb_ !== cb),
197
+ );
198
+ };
199
+ };
200
+
201
+ export const unlisten: T.Unlisten = function (name) {
202
+ listeners.set(name, []);
203
+ };
204
+
205
+ export const initServer: T.InitServer = async function () {
206
+ // initServer is used in tests to mock the server
207
+ };
208
+ export const serverPush: T.ServerPush = async function () {
209
+ // serverPush is used in tests to mock the server
210
+ };
211
+ export const clearServer: T.ClearServer = async function () {
212
+ // clearServer is used in tests to mock the server
213
+ };
@@ -0,0 +1,155 @@
1
+ // @ts-strict-ignore
2
+ import { v4 as uuidv4 } from 'uuid';
3
+
4
+ import * as undo from '../undo';
5
+
6
+ import type * as T from './index-types';
7
+
8
+ const replyHandlers = new Map();
9
+ const listeners = new Map();
10
+ let messageQueue = [];
11
+ let socketClient = null;
12
+
13
+ function connectSocket(onOpen) {
14
+ global.Actual.ipcConnect(function (client) {
15
+ client.on('message', data => {
16
+ const msg = data;
17
+
18
+ if (msg.type === 'error') {
19
+ // An error happened while handling a message so cleanup the
20
+ // current reply handler and reject the promise. The error will
21
+ // be propagated to the caller through this promise rejection.
22
+ const { id, error } = msg;
23
+ const handler = replyHandlers.get(id);
24
+ if (handler) {
25
+ replyHandlers.delete(id);
26
+ handler.reject(error);
27
+ }
28
+ } else if (msg.type === 'reply') {
29
+ let { result } = msg;
30
+ const { id, mutated, undoTag } = msg;
31
+
32
+ // Check if the result is a serialized buffer, and if so
33
+ // convert it to a Uint8Array. This is only needed when working
34
+ // with node; the web version connection layer automatically
35
+ // supports buffers
36
+ if (result && result.type === 'Buffer' && Array.isArray(result.data)) {
37
+ result = new Uint8Array(result.data);
38
+ }
39
+
40
+ const handler = replyHandlers.get(id);
41
+ if (handler) {
42
+ replyHandlers.delete(id);
43
+
44
+ if (!mutated) {
45
+ undo.gc(undoTag);
46
+ }
47
+
48
+ handler.resolve(result);
49
+ }
50
+ } else if (msg.type === 'push') {
51
+ const { name, args } = msg;
52
+
53
+ const listens = listeners.get(name);
54
+ if (listens) {
55
+ for (let i = 0; i < listens.length; i++) {
56
+ const stop = listens[i](args);
57
+ if (stop === true) {
58
+ break;
59
+ }
60
+ }
61
+ }
62
+ } else {
63
+ throw new Error('Unknown message type: ' + JSON.stringify(msg));
64
+ }
65
+ });
66
+
67
+ socketClient = client;
68
+
69
+ // Send any messages that were queued while closed
70
+ if (messageQueue.length > 0) {
71
+ messageQueue.forEach(msg => client.emit('message', msg));
72
+ messageQueue = [];
73
+ }
74
+
75
+ onOpen();
76
+ });
77
+ }
78
+
79
+ export const init: T.Init = async function () {
80
+ return new Promise(connectSocket);
81
+ };
82
+
83
+ export const send: T.Send = function (
84
+ ...params: Parameters<T.Send>
85
+ ): ReturnType<T.Send> {
86
+ const [name, args, { catchErrors = false } = {}] = params;
87
+ return new Promise((resolve, reject) => {
88
+ const id = uuidv4();
89
+ replyHandlers.set(id, { resolve, reject });
90
+
91
+ if (socketClient) {
92
+ socketClient.emit('message', {
93
+ id,
94
+ name,
95
+ args,
96
+ undoTag: undo.snapshot(),
97
+ catchErrors: !!catchErrors,
98
+ });
99
+ } else {
100
+ messageQueue.push({
101
+ id,
102
+ name,
103
+ args,
104
+ undoTag: undo.snapshot(),
105
+ catchErrors,
106
+ });
107
+ }
108
+ });
109
+ };
110
+
111
+ export const sendCatch: T.SendCatch = function (name, args) {
112
+ return send(name, args, { catchErrors: true });
113
+ };
114
+
115
+ export const listen: T.Listen = function (name, cb) {
116
+ if (!listeners.get(name)) {
117
+ listeners.set(name, []);
118
+ }
119
+ listeners.get(name).push(cb);
120
+
121
+ return () => {
122
+ const arr = listeners.get(name);
123
+ if (arr) {
124
+ listeners.set(
125
+ name,
126
+ arr.filter(cb_ => cb_ !== cb),
127
+ );
128
+ }
129
+ };
130
+ };
131
+
132
+ export const unlisten: T.Unlisten = function (name) {
133
+ listeners.set(name, []);
134
+ };
135
+
136
+ async function closeSocket(onClose) {
137
+ socketClient.onclose = () => {
138
+ socketClient = null;
139
+ onClose();
140
+ };
141
+
142
+ await socketClient.close();
143
+ }
144
+
145
+ export const clearServer: T.ClearServer = async function () {
146
+ if (socketClient != null) {
147
+ return new Promise(closeSocket);
148
+ }
149
+ };
150
+ export const initServer: T.InitServer = async function () {
151
+ // initServer is used in tests to mock the server
152
+ };
153
+ export const serverPush: T.ServerPush = async function () {
154
+ // serverPush is used in tests to mock the server
155
+ };
@@ -0,0 +1,59 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+
3
+ import type { UndoState as ServerUndoState } from '../../../server/undo';
4
+
5
+ // oxlint-disable-next-line @typescript-eslint/no-explicit-any
6
+ type Modal = any; // TODO: fix me
7
+
8
+ type UndoState = {
9
+ url: string | null;
10
+ openModal: Modal | null;
11
+ selectedItems: {
12
+ name: string;
13
+ items: Set<string>;
14
+ } | null;
15
+ undoEvent: ServerUndoState | null;
16
+ };
17
+
18
+ type UndoStateWithId = UndoState & {
19
+ id?: string;
20
+ };
21
+
22
+ // List of recently used states. We don't use a true MRU structure
23
+ // because our needs are simple and we also do some custom reordering.
24
+ const HISTORY_SIZE = 40;
25
+ let UNDO_STATE_MRU: UndoStateWithId[] = [];
26
+
27
+ const currentUndoState: UndoStateWithId = {
28
+ url: null,
29
+ openModal: null,
30
+ selectedItems: null,
31
+ undoEvent: null,
32
+ };
33
+
34
+ export const setUndoState = <K extends keyof Omit<UndoState, 'id'>>(
35
+ name: K,
36
+ value: UndoState[K],
37
+ ) => {
38
+ currentUndoState[name] = value;
39
+ currentUndoState.id = uuidv4();
40
+ };
41
+
42
+ export const getUndoState = <K extends keyof UndoState>(name: K) => {
43
+ return currentUndoState[name];
44
+ };
45
+
46
+ export const getTaggedState = (id: string) => {
47
+ return UNDO_STATE_MRU.find(state => state.id === id);
48
+ };
49
+
50
+ export const snapshot = () => {
51
+ const tagged = { ...currentUndoState, id: uuidv4() };
52
+ UNDO_STATE_MRU.unshift(tagged);
53
+ UNDO_STATE_MRU = UNDO_STATE_MRU.slice(0, HISTORY_SIZE);
54
+ return tagged.id;
55
+ };
56
+
57
+ export const gc = (id: string) => {
58
+ UNDO_STATE_MRU = UNDO_STATE_MRU.filter(state => state.id !== id);
59
+ };
@@ -0,0 +1,7 @@
1
+ export const captureException = function () {
2
+ // Do not capture exceptions in tests
3
+ };
4
+
5
+ export const captureBreadcrumb = function () {
6
+ // Do not capture breadcrumbs in tests
7
+ };
@@ -0,0 +1,9 @@
1
+ import { logger } from '../server/log';
2
+
3
+ export const captureException = function (exc: Error) {
4
+ logger.error('[Exception]', exc);
5
+ };
6
+
7
+ export const captureBreadcrumb = function (crumb: unknown) {
8
+ logger.info('[Breadcrumb]', crumb);
9
+ };
@@ -0,0 +1,50 @@
1
+ // @ts-strict-ignore
2
+ import type { GlobalPrefsJson } from '../../../../types/prefs';
3
+ import type * as T from '../index-types';
4
+
5
+ const store: GlobalPrefsJson = {};
6
+
7
+ export const init: T.Init = function () {
8
+ // No need to initialise in tests
9
+ };
10
+
11
+ export const getItem: T.GetItem = async function (key) {
12
+ return store[key];
13
+ };
14
+
15
+ export const setItem: T.SetItem = async function (key, value) {
16
+ store[key] = value;
17
+ };
18
+
19
+ export const removeItem: T.RemoveItem = async function (key) {
20
+ delete store[key];
21
+ };
22
+
23
+ export async function multiGet<K extends readonly (keyof GlobalPrefsJson)[]>(
24
+ keys: K,
25
+ ): Promise<{ [P in K[number]]: GlobalPrefsJson[P] }> {
26
+ const results = keys.map(key => [key, store[key]]) as {
27
+ [P in keyof K]: [K[P], GlobalPrefsJson[K[P]]];
28
+ };
29
+
30
+ // Convert the array of tuples to an object with properly typed properties
31
+ return results.reduce(
32
+ (acc, [key, value]) => {
33
+ acc[key] = value;
34
+ return acc;
35
+ },
36
+ {} as { [P in K[number]]: GlobalPrefsJson[P] },
37
+ );
38
+ }
39
+
40
+ export const multiSet: T.MultiSet = async function (keyValues) {
41
+ keyValues.forEach(function ([key, value]) {
42
+ store[key] = value;
43
+ });
44
+ };
45
+
46
+ export const multiRemove: T.MultiRemove = async function (keys) {
47
+ keys.forEach(function (key) {
48
+ delete store[key];
49
+ });
50
+ };
@@ -0,0 +1,35 @@
1
+ import type { GlobalPrefsJson } from '../../../types/prefs';
2
+
3
+ export declare function init(opts?: { persist?: boolean }): void;
4
+ export type Init = typeof init;
5
+
6
+ export declare function getItem<K extends keyof GlobalPrefsJson>(
7
+ key: K,
8
+ ): Promise<GlobalPrefsJson[K]>;
9
+ export type GetItem = typeof getItem;
10
+
11
+ export declare function setItem<K extends keyof GlobalPrefsJson>(
12
+ key: K,
13
+ value: GlobalPrefsJson[K],
14
+ ): Promise<void>;
15
+ export type SetItem = typeof setItem;
16
+
17
+ export declare function removeItem(key: keyof GlobalPrefsJson): Promise<void>;
18
+ export type RemoveItem = typeof removeItem;
19
+
20
+ export declare function multiGet<K extends readonly (keyof GlobalPrefsJson)[]>(
21
+ keys: K,
22
+ ): Promise<{ [P in K[number]]: GlobalPrefsJson[P] }>;
23
+
24
+ export type MultiGet = typeof multiGet;
25
+
26
+ export declare function multiSet<K extends keyof GlobalPrefsJson>(
27
+ keyValues: Array<[K, GlobalPrefsJson[K]]>,
28
+ ): Promise<void>;
29
+
30
+ export type MultiSet = typeof multiSet;
31
+
32
+ export declare function multiRemove(
33
+ keys: (keyof GlobalPrefsJson)[],
34
+ ): Promise<void>;
35
+ export type MultiRemove = typeof multiRemove;
@@ -0,0 +1,2 @@
1
+ // oxlint-disable-next-line no-restricted-imports
2
+ export * from './index.electron';
@@ -0,0 +1,88 @@
1
+ // @ts-strict-ignore
2
+ import * as fs from 'fs';
3
+ import { join } from 'path';
4
+
5
+ import type { GlobalPrefsJson } from '../../../types/prefs';
6
+ import * as lootFs from '../fs';
7
+
8
+ import type * as T from './index-types';
9
+
10
+ const getStorePath = () => join(lootFs.getDataDir(), 'global-store.json');
11
+ let store: GlobalPrefsJson;
12
+ let persisted = true;
13
+
14
+ export const init: T.Init = function ({ persist = true } = {}) {
15
+ if (persist) {
16
+ try {
17
+ store = JSON.parse(fs.readFileSync(getStorePath(), 'utf8'));
18
+ } catch {
19
+ store = {};
20
+ }
21
+ } else {
22
+ store = {};
23
+ }
24
+
25
+ persisted = persist;
26
+ };
27
+
28
+ function _saveStore(): Promise<void> {
29
+ if (persisted) {
30
+ return new Promise(function (resolve, reject) {
31
+ fs.writeFile(
32
+ getStorePath(),
33
+ JSON.stringify(store),
34
+ 'utf8',
35
+ function (err) {
36
+ return err ? reject(err) : resolve();
37
+ },
38
+ );
39
+ });
40
+ }
41
+ }
42
+
43
+ export const getItem: T.GetItem = function (key) {
44
+ return new Promise(function (resolve) {
45
+ return resolve(store[key]);
46
+ });
47
+ };
48
+
49
+ export const setItem: T.SetItem = function (key, value) {
50
+ store[key] = value;
51
+ return _saveStore();
52
+ };
53
+
54
+ export const removeItem: T.RemoveItem = function (key) {
55
+ delete store[key];
56
+ return _saveStore();
57
+ };
58
+
59
+ export async function multiGet<K extends readonly (keyof GlobalPrefsJson)[]>(
60
+ keys: K,
61
+ ): Promise<{ [P in K[number]]: GlobalPrefsJson[P] }> {
62
+ const results = keys.map(key => [key, store[key]]) as {
63
+ [P in keyof K]: [K[P], GlobalPrefsJson[K[P]]];
64
+ };
65
+
66
+ // Convert the array of tuples to an object with properly typed properties
67
+ return results.reduce(
68
+ (acc, [key, value]) => {
69
+ acc[key] = value;
70
+ return acc;
71
+ },
72
+ {} as { [P in K[number]]: GlobalPrefsJson[P] },
73
+ );
74
+ }
75
+
76
+ export const multiSet: T.MultiSet = function (keyValues) {
77
+ keyValues.forEach(function ([key, value]) {
78
+ store[key] = value;
79
+ });
80
+ return _saveStore();
81
+ };
82
+
83
+ export const multiRemove: T.MultiRemove = function (keys) {
84
+ keys.forEach(function (key) {
85
+ delete store[key];
86
+ });
87
+ return _saveStore();
88
+ };