@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,351 @@
1
+ import { DEFAULT_MAX_DISTANCE_METERS } from '#shared/constants';
2
+ import type { Diff } from '../../shared/util';
3
+ import type {
4
+ NearbyPayeeEntity,
5
+ PayeeEntity,
6
+ PayeeLocationEntity,
7
+ RuleEntity,
8
+ } from '../../types/models';
9
+ import { createApp } from '../app';
10
+ import * as db from '../db';
11
+ import { payeeModel } from '../models';
12
+ import { mutator } from '../mutators';
13
+ import { batchMessages } from '../sync';
14
+ import * as rules from '../transactions/transaction-rules';
15
+ import { undoable } from '../undo';
16
+
17
+ export type PayeesHandlers = {
18
+ 'payee-create': typeof createPayee;
19
+ 'common-payees-get': typeof getCommonPayees;
20
+ 'payees-get': typeof getPayees;
21
+ 'payees-get-orphaned': typeof getOrphanedPayees;
22
+ 'payees-get-rule-counts': typeof getPayeeRuleCounts;
23
+ 'payees-merge': typeof mergePayees;
24
+ 'payees-batch-change': typeof batchChangePayees;
25
+ 'payees-check-orphaned': typeof checkOrphanedPayees;
26
+ 'payees-get-rules': typeof getPayeeRules;
27
+ 'payee-location-create': typeof createPayeeLocation;
28
+ 'payee-locations-get': typeof getPayeeLocations;
29
+ 'payee-location-delete': typeof deletePayeeLocation;
30
+ 'payees-get-nearby': typeof getNearbyPayees;
31
+ };
32
+
33
+ export const app = createApp<PayeesHandlers>();
34
+ app.method('payee-create', mutator(undoable(createPayee)));
35
+ app.method('common-payees-get', getCommonPayees);
36
+ app.method('payees-get', getPayees);
37
+ app.method('payees-get-orphaned', getOrphanedPayees);
38
+ app.method('payees-get-rule-counts', getPayeeRuleCounts);
39
+ app.method(
40
+ 'payees-merge',
41
+ mutator(
42
+ undoable(mergePayees, args => ({
43
+ mergeIds: args.mergeIds,
44
+ targetId: args.targetId,
45
+ })),
46
+ ),
47
+ );
48
+ app.method('payees-batch-change', mutator(undoable(batchChangePayees)));
49
+ app.method('payees-check-orphaned', checkOrphanedPayees);
50
+ app.method('payees-get-rules', getPayeeRules);
51
+ app.method('payee-location-create', mutator(createPayeeLocation));
52
+ app.method('payee-locations-get', getPayeeLocations);
53
+ app.method('payee-location-delete', mutator(deletePayeeLocation));
54
+ app.method('payees-get-nearby', getNearbyPayees);
55
+
56
+ async function createPayee({ name }: { name: PayeeEntity['name'] }) {
57
+ return db.insertPayee({ name });
58
+ }
59
+
60
+ async function getCommonPayees(): Promise<PayeeEntity[]> {
61
+ // TODO: Update to an AQL query. Server must return AQL entities not the raw DB data.
62
+ return (await db.getCommonPayees()).map(p => payeeModel.fromDb(p));
63
+ }
64
+
65
+ async function getPayees(): Promise<PayeeEntity[]> {
66
+ // TODO: Update to an AQL query. Server must return AQL entities not the raw DB data.
67
+ return (await db.getPayees()).map(p => payeeModel.fromDb(p));
68
+ }
69
+
70
+ async function getOrphanedPayees(): Promise<Array<Pick<PayeeEntity, 'id'>>> {
71
+ return await db.syncGetOrphanedPayees();
72
+ }
73
+
74
+ async function getPayeeRuleCounts() {
75
+ const payeeCounts: Record<PayeeEntity['id'], number> = {};
76
+
77
+ rules.iterateIds(rules.getRules(), 'payee', (rule, id) => {
78
+ if (payeeCounts[id] == null) {
79
+ payeeCounts[id] = 0;
80
+ }
81
+ payeeCounts[id]++;
82
+ });
83
+
84
+ return payeeCounts;
85
+ }
86
+
87
+ async function mergePayees({
88
+ targetId,
89
+ mergeIds,
90
+ }: {
91
+ targetId: PayeeEntity['id'];
92
+ mergeIds: Array<PayeeEntity['id']>;
93
+ }) {
94
+ await db.mergePayees(targetId, mergeIds);
95
+ }
96
+
97
+ async function batchChangePayees({
98
+ added,
99
+ deleted,
100
+ updated,
101
+ }: Partial<Diff<PayeeEntity>>): Promise<void> {
102
+ await batchMessages(async () => {
103
+ if (deleted) {
104
+ await Promise.all(
105
+ deleted.map(p => ({ id: p.id })).map(p => db.deletePayee(p)),
106
+ );
107
+ }
108
+
109
+ if (added) {
110
+ await Promise.all(
111
+ added.map(p => payeeModel.toDb(p)).map(p => db.insertPayee(p)),
112
+ );
113
+ }
114
+
115
+ if (updated) {
116
+ await Promise.all(
117
+ updated
118
+ .map(p => payeeModel.toDb(p as PayeeEntity, { update: true }))
119
+ .map(p => db.updatePayee(p)),
120
+ );
121
+ }
122
+ });
123
+ }
124
+
125
+ async function checkOrphanedPayees({
126
+ ids,
127
+ }: {
128
+ ids: Array<PayeeEntity['id']>;
129
+ }): Promise<Array<PayeeEntity['id']>> {
130
+ const orphaned = new Set(await db.getOrphanedPayees());
131
+ return ids.filter(id => orphaned.has(id));
132
+ }
133
+
134
+ async function getPayeeRules({
135
+ id,
136
+ }: {
137
+ id: PayeeEntity['id'];
138
+ }): Promise<RuleEntity[]> {
139
+ return rules.getRulesForPayee(id).map(rule => rule.serialize());
140
+ }
141
+
142
+ async function createPayeeLocation({
143
+ payeeId,
144
+ latitude,
145
+ longitude,
146
+ }: {
147
+ payeeId: PayeeEntity['id'];
148
+ latitude: number;
149
+ longitude: number;
150
+ }): Promise<PayeeLocationEntity['id']> {
151
+ const created_at = Date.now();
152
+
153
+ if (
154
+ !Number.isFinite(latitude) ||
155
+ !Number.isFinite(longitude) ||
156
+ latitude < -90 ||
157
+ latitude > 90 ||
158
+ longitude < -180 ||
159
+ longitude > 180
160
+ ) {
161
+ throw new Error(
162
+ 'Invalid coordinates: latitude must be between -90 and 90, longitude must be between -180 and 180',
163
+ );
164
+ }
165
+
166
+ return await db.insertWithUUID('payee_locations', {
167
+ payee_id: payeeId,
168
+ latitude,
169
+ longitude,
170
+ created_at,
171
+ });
172
+ }
173
+
174
+ async function getPayeeLocations({
175
+ payeeId,
176
+ }: {
177
+ payeeId?: PayeeEntity['id'];
178
+ } = {}): Promise<PayeeLocationEntity[]> {
179
+ let query = 'SELECT * FROM payee_locations WHERE tombstone IS NOT 1';
180
+ let params: string[] = [];
181
+
182
+ if (payeeId) {
183
+ query += ' AND payee_id = ?';
184
+ params = [payeeId];
185
+ }
186
+
187
+ query += ' ORDER BY created_at DESC';
188
+
189
+ return db.runQuery<PayeeLocationEntity>(query, params, true);
190
+ }
191
+
192
+ async function deletePayeeLocation({
193
+ id,
194
+ }: {
195
+ id: PayeeLocationEntity['id'];
196
+ }): Promise<void> {
197
+ await db.delete_('payee_locations', id);
198
+ }
199
+
200
+ // Type for the raw query result that combines PayeeEntity and PayeeLocationEntity fields
201
+ type NearbyPayeeQueryResult = Pick<
202
+ db.DbPayee,
203
+ | 'id'
204
+ | 'name'
205
+ | 'transfer_acct'
206
+ | 'favorite'
207
+ | 'learn_categories'
208
+ | 'tombstone'
209
+ > &
210
+ Omit<PayeeLocationEntity, 'id'> & {
211
+ // PayeeLocationEntity's id renamed to location_id
212
+ location_id: PayeeLocationEntity['id'];
213
+ // Calculated distance from SQL
214
+ distance: number;
215
+ };
216
+ async function getNearbyPayees({
217
+ latitude,
218
+ longitude,
219
+ maxDistance = DEFAULT_MAX_DISTANCE_METERS,
220
+ }: {
221
+ latitude: number;
222
+ longitude: number;
223
+ maxDistance?: number;
224
+ }): Promise<NearbyPayeeEntity[]> {
225
+ if (
226
+ !Number.isFinite(latitude) ||
227
+ !Number.isFinite(longitude) ||
228
+ latitude < -90 ||
229
+ latitude > 90 ||
230
+ longitude < -180 ||
231
+ longitude > 180
232
+ ) {
233
+ throw new Error(
234
+ 'Invalid coordinates: latitude must be between -90 and 90, longitude must be between -180 and 180',
235
+ );
236
+ }
237
+
238
+ if (!Number.isFinite(maxDistance) || maxDistance <= 0) {
239
+ throw new Error(
240
+ 'Invalid maxDistance: must be a finite positive number greater than 0',
241
+ );
242
+ }
243
+
244
+ // Get the closest location for each payee within maxDistance using window functions
245
+ const query = `
246
+ WITH payee_distances AS (
247
+ SELECT
248
+ pl.id as location_id,
249
+ pl.payee_id,
250
+ pl.latitude,
251
+ pl.longitude,
252
+ pl.created_at,
253
+ p.id,
254
+ p.name,
255
+ p.transfer_acct,
256
+ p.favorite,
257
+ p.learn_categories,
258
+ p.tombstone,
259
+ -- Haversine formula to calculate distance
260
+ ((6371 * acos(
261
+ MIN(1, MAX(-1,
262
+ cos(radians(?)) * cos(radians(pl.latitude)) *
263
+ cos(radians(pl.longitude) - radians(?)) +
264
+ sin(radians(?)) * sin(radians(pl.latitude))
265
+ ))
266
+ ))) * 1000 as distance,
267
+ -- Rank locations by distance for each payee
268
+ ROW_NUMBER() OVER (PARTITION BY pl.payee_id ORDER BY (
269
+ (6371 * acos(
270
+ MIN(1, MAX(-1,
271
+ cos(radians(?)) * cos(radians(pl.latitude)) *
272
+ cos(radians(pl.longitude) - radians(?)) +
273
+ sin(radians(?)) * sin(radians(pl.latitude))
274
+ ))
275
+ )) * 1000
276
+ )) as distance_rank
277
+ FROM payee_locations pl
278
+ JOIN payees p ON pl.payee_id = p.id
279
+ WHERE p.tombstone IS NOT 1
280
+ AND pl.tombstone IS NOT 1
281
+ -- Filter by distance using Haversine formula
282
+ AND (6371 * acos(
283
+ MIN(1, MAX(-1,
284
+ cos(radians(?)) * cos(radians(pl.latitude)) *
285
+ cos(radians(pl.longitude) - radians(?)) +
286
+ sin(radians(?)) * sin(radians(pl.latitude))
287
+ ))
288
+ )) * 1000 <= ?
289
+ )
290
+ SELECT
291
+ location_id,
292
+ payee_id,
293
+ latitude,
294
+ longitude,
295
+ created_at,
296
+ id,
297
+ name,
298
+ transfer_acct,
299
+ favorite,
300
+ learn_categories,
301
+ tombstone,
302
+ distance
303
+ FROM payee_distances
304
+ WHERE distance_rank = 1
305
+ ORDER BY distance ASC
306
+ LIMIT 10
307
+ `;
308
+
309
+ const results = db.runQuery<NearbyPayeeQueryResult>(
310
+ query,
311
+ [
312
+ latitude,
313
+ longitude,
314
+ latitude, // For first distance calculation in SELECT
315
+ latitude,
316
+ longitude,
317
+ latitude, // For ROW_NUMBER() ordering
318
+ latitude,
319
+ longitude,
320
+ latitude, // For WHERE distance filter
321
+ maxDistance,
322
+ ],
323
+ true,
324
+ );
325
+
326
+ // Transform results to expected format
327
+ const nearbyPayees: NearbyPayeeEntity[] = results.map(row => {
328
+ const payee = payeeModel.fromDb({
329
+ id: row.id,
330
+ name: row.name,
331
+ transfer_acct: row.transfer_acct,
332
+ favorite: row.favorite,
333
+ learn_categories: row.learn_categories,
334
+ tombstone: row.tombstone,
335
+ });
336
+
337
+ return {
338
+ payee,
339
+ location: {
340
+ id: row.location_id,
341
+ payee_id: row.payee_id,
342
+ latitude: row.latitude,
343
+ longitude: row.longitude,
344
+ created_at: row.created_at,
345
+ distance: row.distance,
346
+ },
347
+ };
348
+ });
349
+
350
+ return nearbyPayees;
351
+ }
@@ -0,0 +1,26 @@
1
+ // Polyfills for browser/web worker environment
2
+ import * as jspb from 'google-protobuf';
3
+
4
+ if (typeof globalThis !== 'undefined') {
5
+ // Add a basic require polyfill for CommonJS modules
6
+ if (typeof globalThis.require === 'undefined') {
7
+ // @ts-expect-error - we're creating a minimal require implementation
8
+ globalThis.require = (moduleId: string) => {
9
+ switch (moduleId) {
10
+ case 'google-protobuf':
11
+ return jspb;
12
+ default:
13
+ throw new Error(
14
+ `Module not found: ${moduleId}. Add to polyfills if needed.`,
15
+ );
16
+ }
17
+ };
18
+ }
19
+ }
20
+
21
+ // Also set on global for compatibility
22
+ if (typeof global !== 'undefined') {
23
+ if (typeof global.require === 'undefined') {
24
+ global.require = globalThis.require;
25
+ }
26
+ }
@@ -0,0 +1,219 @@
1
+ // @ts-strict-ignore
2
+ import { fetch } from '../platform/server/fetch';
3
+ import { logger } from '../platform/server/log';
4
+ import * as Platform from '../shared/platform';
5
+
6
+ import { PostError } from './errors';
7
+
8
+ function throwIfNot200(res: Response, text: string) {
9
+ if (res.status !== 200) {
10
+ if (res.status === 500) {
11
+ throw new PostError(res.status === 500 ? 'internal' : text);
12
+ }
13
+
14
+ const contentType = res.headers.get('Content-Type');
15
+ if (contentType.toLowerCase().indexOf('application/json') !== -1) {
16
+ const json = JSON.parse(text);
17
+ throw new PostError(json.reason);
18
+ }
19
+
20
+ // Actual Sync Server may be exposed via a tunnel (e.g. ngrok). Tunnel errors should be treated as network errors.
21
+ const tunnelErrorHeaders = ['ngrok-error-code'];
22
+ const tunnelError = tunnelErrorHeaders.some(header =>
23
+ res.headers.has(header),
24
+ );
25
+
26
+ if (tunnelError) {
27
+ // Tunnel errors are present when the tunnel is active and the server is not reachable e.g. server is offline
28
+ // When we experience a tunnel error we treat it as a network failure
29
+ throw new PostError('network-failure');
30
+ }
31
+
32
+ throw new PostError(text);
33
+ }
34
+ }
35
+
36
+ export async function post(
37
+ url: RequestInfo,
38
+ data: unknown,
39
+ headers = {},
40
+ timeout: number | null = null,
41
+ ) {
42
+ let text: string;
43
+ let res: Response;
44
+
45
+ try {
46
+ const controller = new AbortController();
47
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
48
+ const signal = timeout ? controller.signal : null;
49
+ res = await fetch(url, {
50
+ method: 'POST',
51
+ body: JSON.stringify(data),
52
+ signal,
53
+ headers: {
54
+ ...headers,
55
+ 'Content-Type': 'application/json',
56
+ },
57
+ });
58
+ clearTimeout(timeoutId);
59
+ text = await res.text();
60
+ } catch {
61
+ throw new PostError('network-failure');
62
+ }
63
+
64
+ throwIfNot200(res, text);
65
+
66
+ let responseData;
67
+
68
+ try {
69
+ responseData = JSON.parse(text);
70
+ } catch {
71
+ // Something seriously went wrong. TODO handle errors
72
+ throw new PostError('parse-json', { meta: text });
73
+ }
74
+
75
+ if (responseData.status !== 'ok') {
76
+ logger.log(
77
+ 'API call failed: ' +
78
+ url +
79
+ '\nData: ' +
80
+ JSON.stringify(data, null, 2) +
81
+ '\nResponse: ' +
82
+ JSON.stringify(res, null, 2),
83
+ );
84
+
85
+ throw new PostError(
86
+ responseData.description || responseData.reason || 'unknown',
87
+ );
88
+ }
89
+
90
+ return responseData.data;
91
+ }
92
+
93
+ export async function del(url, data, headers = {}, timeout = null) {
94
+ let text;
95
+ let res;
96
+
97
+ try {
98
+ const controller = new AbortController();
99
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
100
+ const signal = timeout ? controller.signal : null;
101
+ res = await fetch(url, {
102
+ method: 'DELETE',
103
+ body: JSON.stringify(data),
104
+ signal,
105
+ headers: {
106
+ ...headers,
107
+ 'Content-Type': 'application/json',
108
+ },
109
+ });
110
+ clearTimeout(timeoutId);
111
+ text = await res.text();
112
+ } catch {
113
+ throw new PostError('network-failure');
114
+ }
115
+
116
+ throwIfNot200(res, text);
117
+
118
+ try {
119
+ res = JSON.parse(text);
120
+ } catch {
121
+ // Something seriously went wrong. TODO handle errors
122
+ throw new PostError('parse-json', { meta: text });
123
+ }
124
+
125
+ if (res.status !== 'ok') {
126
+ logger.log(
127
+ 'API call failed: ' +
128
+ url +
129
+ '\nData: ' +
130
+ JSON.stringify(data, null, 2) +
131
+ '\nResponse: ' +
132
+ JSON.stringify(res, null, 2),
133
+ );
134
+
135
+ throw new PostError(res.description || res.reason || 'unknown');
136
+ }
137
+
138
+ return res.data;
139
+ }
140
+
141
+ export async function patch(url, data, headers = {}, timeout = null) {
142
+ let text;
143
+ let res;
144
+
145
+ try {
146
+ const controller = new AbortController();
147
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
148
+ const signal = timeout ? controller.signal : null;
149
+ res = await fetch(url, {
150
+ method: 'PATCH',
151
+ body: JSON.stringify(data),
152
+ signal,
153
+ headers: {
154
+ ...headers,
155
+ 'Content-Type': 'application/json',
156
+ },
157
+ });
158
+ clearTimeout(timeoutId);
159
+ text = await res.text();
160
+ } catch {
161
+ throw new PostError('network-failure');
162
+ }
163
+
164
+ throwIfNot200(res, text);
165
+
166
+ try {
167
+ res = JSON.parse(text);
168
+ } catch {
169
+ // Something seriously went wrong. TODO handle errors
170
+ throw new PostError('parse-json', { meta: text });
171
+ }
172
+
173
+ if (res.status !== 'ok') {
174
+ logger.log(
175
+ 'API call failed: ' +
176
+ url +
177
+ '\nData: ' +
178
+ JSON.stringify(data, null, 2) +
179
+ '\nResponse: ' +
180
+ JSON.stringify(res, null, 2),
181
+ );
182
+
183
+ throw new PostError(res.description || res.reason || 'unknown');
184
+ }
185
+
186
+ return res.data;
187
+ }
188
+
189
+ export async function postBinary(url, data, headers) {
190
+ let res;
191
+ try {
192
+ res = await fetch(url, {
193
+ method: 'POST',
194
+ body: Platform.isBrowser ? data : Buffer.from(data),
195
+ headers: {
196
+ 'Content-Length': data.length,
197
+ 'Content-Type': 'application/actual-sync',
198
+ ...headers,
199
+ },
200
+ });
201
+ } catch {
202
+ throw new PostError('network-failure');
203
+ }
204
+
205
+ let buffer;
206
+ if (res.arrayBuffer) {
207
+ buffer = Buffer.from(await res.arrayBuffer());
208
+ } else {
209
+ buffer = await res.buffer();
210
+ }
211
+
212
+ throwIfNot200(res, buffer.toString());
213
+
214
+ return buffer;
215
+ }
216
+
217
+ export function get(url, opts?) {
218
+ return fetch(url, opts).then(res => res.text());
219
+ }