@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.
- package/.swcrc +11 -0
- package/bin/build-browser +40 -0
- package/bin/copy-migrations +9 -0
- package/db.sqlite +0 -0
- package/default-db.sqlite +0 -0
- package/migrations/.force-copy-windows +0 -0
- package/migrations/1548957970627_remove-db-version.sql +5 -0
- package/migrations/1550601598648_payees.sql +23 -0
- package/migrations/1555786194328_remove_category_group_unique.sql +25 -0
- package/migrations/1561751833510_indexes.sql +7 -0
- package/migrations/1567699552727_budget.sql +38 -0
- package/migrations/1582384163573_cleared.sql +6 -0
- package/migrations/1597756566448_rules.sql +10 -0
- package/migrations/1608652596043_parent_field.sql +13 -0
- package/migrations/1608652596044_trans_views.sql +56 -0
- package/migrations/1612625548236_optimize.sql +7 -0
- package/migrations/1614782639336_trans_views2.sql +33 -0
- package/migrations/1615745967948_meta.sql +10 -0
- package/migrations/1616167010796_accounts_order.sql +5 -0
- package/migrations/1618975177358_schedules.sql +28 -0
- package/migrations/1632571489012_remove_cache.js +136 -0
- package/migrations/1679728867040_rules_conditions.sql +5 -0
- package/migrations/1681115033845_add_schedule_name.sql +5 -0
- package/migrations/1682974838138_remove_payee_rules.sql +5 -0
- package/migrations/1685007876842_add_category_hidden.sql +6 -0
- package/migrations/1686139660866_remove_account_type.sql +5 -0
- package/migrations/1688749527273_transaction_filters.sql +10 -0
- package/migrations/1688841238000_add_account_type.sql +5 -0
- package/migrations/1691233396000_add_schedule_next_date_tombstone.sql +5 -0
- package/migrations/1694438752000_add_goal_targets.sql +7 -0
- package/migrations/1697046240000_add_reconciled.sql +5 -0
- package/migrations/1704572023730_add_account_sync_source.sql +5 -0
- package/migrations/1704572023731_add_missing_goCardless_sync_source.sql +9 -0
- package/migrations/1707267033000_reports.sql +28 -0
- package/migrations/1712784523000_unhide_input_group.sql +8 -0
- package/migrations/1716359441000_include_current.sql +5 -0
- package/migrations/1720310586000_link_transfer_schedules.sql +19 -0
- package/migrations/1720664867241_add_payee_favorite.sql +5 -0
- package/migrations/1720665000000_goal_context.sql +6 -0
- package/migrations/1722717601000_reports_move_selected_categories.js +55 -0
- package/migrations/1722804019000_create_dashboard_table.js +69 -0
- package/migrations/1723665565000_prefs.js +59 -0
- package/migrations/1730744182000_fix_dashboard_table.sql +7 -0
- package/migrations/1736640000000_custom_report_sorting.sql +7 -0
- package/migrations/1737158400000_add_learn_categories_to_payees.sql +5 -0
- package/migrations/1738491452000_sorting_rename.sql +13 -0
- package/migrations/1739139550000_bank_sync_page.sql +7 -0
- package/migrations/1740506588539_add_last_reconciled_at.sql +5 -0
- package/migrations/1745425408000_update_budgetType_pref.sql +7 -0
- package/migrations/1749799110000_add_tags.sql +10 -0
- package/migrations/1749799110001_tags_tombstone.sql +5 -0
- package/migrations/1754611200000_add_category_template_settings.sql +5 -0
- package/migrations/1759260219000_add_trim_interval_report_setting.sql +6 -0
- package/migrations/1759842823172_add_isGlobal_to_preferences.sql +1 -0
- package/migrations/1762178745667_rename_csv_skip_lines_pref.sql +8 -0
- package/migrations/1765518577215_multiple_dashboards.js +30 -0
- package/migrations/1768872504000_add_payee_locations.sql +21 -0
- package/package.json +128 -0
- package/src/mocks/arbitrary-schema.ts +162 -0
- package/src/mocks/budget.ts +901 -0
- package/src/mocks/files/8859-1.qfx +63 -0
- package/src/mocks/files/best.data-ever$.QFX +124 -0
- package/src/mocks/files/big.data.QiF +91 -0
- package/src/mocks/files/budgets/.commit-to-git +0 -0
- package/src/mocks/files/camt/camt.053.payee-memo.xml +127 -0
- package/src/mocks/files/camt/camt.053.xml +463 -0
- package/src/mocks/files/credit-card.ofx +11 -0
- package/src/mocks/files/data-multi-decimal.ofx +64 -0
- package/src/mocks/files/data-payee-memo.ofx +75 -0
- package/src/mocks/files/data-payee-memo.qif +17 -0
- package/src/mocks/files/data.ofx +124 -0
- package/src/mocks/files/data.qfx +124 -0
- package/src/mocks/files/data.qif +91 -0
- package/src/mocks/files/default-budget-template/db.sqlite +0 -0
- package/src/mocks/files/default-budget-template/metadata.json +6 -0
- package/src/mocks/files/html-vals.qfx +17 -0
- package/src/mocks/index.ts +221 -0
- package/src/mocks/migrations/1508717984291_up_add-poop.sql +13 -0
- package/src/mocks/migrations/1508718036311_up_modify-poop.sql +2 -0
- package/src/mocks/migrations/1508727787513_remove-is_income.sql +15 -0
- package/src/mocks/random.ts +16 -0
- package/src/mocks/setup.ts +180 -0
- package/src/mocks/spreadsheet.ts +101 -0
- package/src/mocks/util.ts +82 -0
- package/src/platform/client/connection/README.md +3 -0
- package/src/platform/client/connection/__mocks__/index.ts +67 -0
- package/src/platform/client/connection/index-types.ts +95 -0
- package/src/platform/client/connection/index.browser.ts +213 -0
- package/src/platform/client/connection/index.ts +155 -0
- package/src/platform/client/undo/index.ts +59 -0
- package/src/platform/exceptions/__mocks__/index.ts +7 -0
- package/src/platform/exceptions/index.ts +9 -0
- package/src/platform/server/asyncStorage/__mocks__/index.ts +50 -0
- package/src/platform/server/asyncStorage/index-types.ts +35 -0
- package/src/platform/server/asyncStorage/index.api.ts +2 -0
- package/src/platform/server/asyncStorage/index.electron.ts +88 -0
- package/src/platform/server/asyncStorage/index.ts +126 -0
- package/src/platform/server/connection/README.md +3 -0
- package/src/platform/server/connection/__mocks__/index.ts +15 -0
- package/src/platform/server/connection/index-types.ts +20 -0
- package/src/platform/server/connection/index.api.ts +13 -0
- package/src/platform/server/connection/index.electron.ts +102 -0
- package/src/platform/server/connection/index.ts +154 -0
- package/src/platform/server/fetch/__mocks__/index.ts +3 -0
- package/src/platform/server/fetch/index.api.ts +1 -0
- package/src/platform/server/fetch/index.electron.ts +18 -0
- package/src/platform/server/fetch/index.ts +20 -0
- package/src/platform/server/fs/index.api.ts +198 -0
- package/src/platform/server/fs/index.electron.ts +208 -0
- package/src/platform/server/fs/index.test.ts +117 -0
- package/src/platform/server/fs/index.ts +416 -0
- package/src/platform/server/fs/path-join.api.ts +1 -0
- package/src/platform/server/fs/path-join.electron.ts +1 -0
- package/src/platform/server/fs/path-join.ts +97 -0
- package/src/platform/server/fs/shared.ts +33 -0
- package/src/platform/server/indexeddb/index.ts +115 -0
- package/src/platform/server/log/index.ts +43 -0
- package/src/platform/server/sqlite/index.api.ts +2 -0
- package/src/platform/server/sqlite/index.electron.ts +134 -0
- package/src/platform/server/sqlite/index.test.ts +108 -0
- package/src/platform/server/sqlite/index.ts +241 -0
- package/src/platform/server/sqlite/normalise.ts +9 -0
- package/src/platform/server/sqlite/unicodeLike.test.ts +58 -0
- package/src/platform/server/sqlite/unicodeLike.ts +31 -0
- package/src/server/__mocks__/post.ts +9 -0
- package/src/server/__snapshots__/main.test.ts.snap +199 -0
- package/src/server/__snapshots__/sheet.test.ts.snap +9 -0
- package/src/server/accounts/__snapshots__/sync.test.ts.snap +136 -0
- package/src/server/accounts/app-bank-sync.test.ts +136 -0
- package/src/server/accounts/app.ts +1294 -0
- package/src/server/accounts/link.ts +25 -0
- package/src/server/accounts/payees.ts +36 -0
- package/src/server/accounts/sync.test.ts +679 -0
- package/src/server/accounts/sync.ts +1168 -0
- package/src/server/accounts/title/index.ts +60 -0
- package/src/server/accounts/title/lower-case.ts +93 -0
- package/src/server/accounts/title/specials.ts +21 -0
- package/src/server/admin/app.ts +241 -0
- package/src/server/api-models.ts +244 -0
- package/src/server/api.test.ts +36 -0
- package/src/server/api.ts +1030 -0
- package/src/server/app.ts +91 -0
- package/src/server/aql/compiler.test.ts +966 -0
- package/src/server/aql/compiler.ts +1222 -0
- package/src/server/aql/exec.test.ts +289 -0
- package/src/server/aql/exec.ts +128 -0
- package/src/server/aql/index.ts +41 -0
- package/src/server/aql/schema/executors.test.ts +420 -0
- package/src/server/aql/schema/executors.ts +345 -0
- package/src/server/aql/schema/index.test.ts +67 -0
- package/src/server/aql/schema/index.ts +409 -0
- package/src/server/aql/schema-helpers.test.ts +242 -0
- package/src/server/aql/schema-helpers.ts +208 -0
- package/src/server/aql/views.test.ts +62 -0
- package/src/server/aql/views.ts +57 -0
- package/src/server/auth/app.ts +387 -0
- package/src/server/bench.ts +29 -0
- package/src/server/budget/actions.ts +686 -0
- package/src/server/budget/app.ts +469 -0
- package/src/server/budget/base.test.ts +340 -0
- package/src/server/budget/base.ts +339 -0
- package/src/server/budget/category-template-context.test.ts +1658 -0
- package/src/server/budget/category-template-context.ts +862 -0
- package/src/server/budget/cleanup-template.pegjs +27 -0
- package/src/server/budget/cleanup-template.ts +408 -0
- package/src/server/budget/envelope.ts +403 -0
- package/src/server/budget/goal-template.pegjs +110 -0
- package/src/server/budget/goal-template.ts +309 -0
- package/src/server/budget/report.ts +308 -0
- package/src/server/budget/schedule-template.test.ts +184 -0
- package/src/server/budget/schedule-template.ts +351 -0
- package/src/server/budget/statements.ts +60 -0
- package/src/server/budget/template-notes.test.ts +393 -0
- package/src/server/budget/template-notes.ts +323 -0
- package/src/server/budget/util.ts +25 -0
- package/src/server/budgetfiles/__snapshots__/backups.test.ts.snap +101 -0
- package/src/server/budgetfiles/app.ts +672 -0
- package/src/server/budgetfiles/backups.test.ts +79 -0
- package/src/server/budgetfiles/backups.ts +251 -0
- package/src/server/cloud-storage.ts +467 -0
- package/src/server/dashboard/app.ts +373 -0
- package/src/server/db/__snapshots__/index.test.ts.snap +271 -0
- package/src/server/db/index.test.ts +300 -0
- package/src/server/db/index.ts +855 -0
- package/src/server/db/mappings.ts +59 -0
- package/src/server/db/sort.ts +58 -0
- package/src/server/db/types/index.ts +342 -0
- package/src/server/db/util.ts +36 -0
- package/src/server/encryption/app.ts +133 -0
- package/src/server/encryption/encryption-internals.api.ts +2 -0
- package/src/server/encryption/encryption-internals.electron.ts +89 -0
- package/src/server/encryption/encryption-internals.ts +109 -0
- package/src/server/encryption/encryption.test.ts +19 -0
- package/src/server/encryption/index.test.ts +19 -0
- package/src/server/encryption/index.ts +89 -0
- package/src/server/errors.ts +110 -0
- package/src/server/filters/app.ts +191 -0
- package/src/server/importers/actual.ts +49 -0
- package/src/server/importers/index.ts +58 -0
- package/src/server/importers/ynab4-types.ts +163 -0
- package/src/server/importers/ynab4.ts +470 -0
- package/src/server/importers/ynab5-types.ts +290 -0
- package/src/server/importers/ynab5.ts +1193 -0
- package/src/server/main-app.ts +25 -0
- package/src/server/main.test.ts +392 -0
- package/src/server/main.ts +336 -0
- package/src/server/migrate/__snapshots__/migrations.test.ts.snap +17 -0
- package/src/server/migrate/cli.ts +100 -0
- package/src/server/migrate/migrations.test.ts +81 -0
- package/src/server/migrate/migrations.ts +192 -0
- package/src/server/models.ts +184 -0
- package/src/server/mutators.ts +139 -0
- package/src/server/notes/app.ts +18 -0
- package/src/server/payees/app.ts +351 -0
- package/src/server/polyfills.ts +26 -0
- package/src/server/post.ts +219 -0
- package/src/server/preferences/app.ts +249 -0
- package/src/server/prefs.ts +91 -0
- package/src/server/reports/app.ts +187 -0
- package/src/server/rules/action.ts +344 -0
- package/src/server/rules/app.ts +193 -0
- package/src/server/rules/condition.ts +436 -0
- package/src/server/rules/customFunctions.ts +61 -0
- package/src/server/rules/formula-action.test.ts +175 -0
- package/src/server/rules/handlebars-helpers.ts +131 -0
- package/src/server/rules/index.test.ts +1095 -0
- package/src/server/rules/index.ts +22 -0
- package/src/server/rules/rule-indexer.ts +89 -0
- package/src/server/rules/rule-utils.ts +274 -0
- package/src/server/rules/rule.ts +193 -0
- package/src/server/schedules/app.test.ts +502 -0
- package/src/server/schedules/app.ts +644 -0
- package/src/server/schedules/find-schedules.ts +391 -0
- package/src/server/server-config.ts +59 -0
- package/src/server/sheet.test.ts +101 -0
- package/src/server/sheet.ts +280 -0
- package/src/server/spreadsheet/__snapshots__/spreadsheet.test.ts.snap +5 -0
- package/src/server/spreadsheet/app.ts +54 -0
- package/src/server/spreadsheet/globals.ts +13 -0
- package/src/server/spreadsheet/graph-data-structure.ts +165 -0
- package/src/server/spreadsheet/scratch +60 -0
- package/src/server/spreadsheet/spreadsheet.test.ts +191 -0
- package/src/server/spreadsheet/spreadsheet.ts +523 -0
- package/src/server/spreadsheet/util.ts +15 -0
- package/src/server/sql/init.sql +88 -0
- package/src/server/sync/__snapshots__/sync.test.ts.snap +31 -0
- package/src/server/sync/app.ts +29 -0
- package/src/server/sync/encoder.ts +129 -0
- package/src/server/sync/index.ts +820 -0
- package/src/server/sync/make-test-message.ts +19 -0
- package/src/server/sync/migrate.test.ts +169 -0
- package/src/server/sync/migrate.ts +48 -0
- package/src/server/sync/repair.ts +39 -0
- package/src/server/sync/reset.ts +91 -0
- package/src/server/sync/sync.property.test.ts +385 -0
- package/src/server/sync/sync.test.ts +349 -0
- package/src/server/sync/utils.ts +3 -0
- package/src/server/tags/app.ts +101 -0
- package/src/server/tests/mockData.json +9352 -0
- package/src/server/tests/mockSyncServer.ts +119 -0
- package/src/server/tools/app.ts +152 -0
- package/src/server/transactions/__snapshots__/transaction-rules.test.ts.snap +173 -0
- package/src/server/transactions/__snapshots__/transfer.test.ts.snap +655 -0
- package/src/server/transactions/app.ts +136 -0
- package/src/server/transactions/export/export-to-csv.ts +132 -0
- package/src/server/transactions/import/__snapshots__/parse-file.test.ts.snap +1582 -0
- package/src/server/transactions/import/ofx2json.test.ts +33 -0
- package/src/server/transactions/import/ofx2json.ts +157 -0
- package/src/server/transactions/import/parse-file.test.ts +224 -0
- package/src/server/transactions/import/parse-file.ts +286 -0
- package/src/server/transactions/import/qif2json.ts +110 -0
- package/src/server/transactions/import/xmlcamt2json.ts +168 -0
- package/src/server/transactions/index.ts +196 -0
- package/src/server/transactions/merge.test.ts +370 -0
- package/src/server/transactions/merge.ts +139 -0
- package/src/server/transactions/transaction-rules.test.ts +994 -0
- package/src/server/transactions/transaction-rules.ts +1038 -0
- package/src/server/transactions/transfer.test.ts +221 -0
- package/src/server/transactions/transfer.ts +173 -0
- package/src/server/undo.ts +271 -0
- package/src/server/update.ts +37 -0
- package/src/server/util/budget-name.ts +61 -0
- package/src/server/util/custom-sync-mapping.ts +48 -0
- package/src/server/util/rschedule.ts +9 -0
- package/src/shared/__mocks__/platform.ts +7 -0
- package/src/shared/__snapshots__/months.test.ts.snap +21 -0
- package/src/shared/arithmetic.test.ts +112 -0
- package/src/shared/arithmetic.ts +170 -0
- package/src/shared/async.test.ts +135 -0
- package/src/shared/async.ts +76 -0
- package/src/shared/constants.ts +5 -0
- package/src/shared/currencies.ts +70 -0
- package/src/shared/dashboard.ts +260 -0
- package/src/shared/environment.ts +18 -0
- package/src/shared/errors.ts +195 -0
- package/src/shared/locale.ts +27 -0
- package/src/shared/location-utils.test.ts +69 -0
- package/src/shared/location-utils.ts +49 -0
- package/src/shared/months.test.ts +5 -0
- package/src/shared/months.ts +485 -0
- package/src/shared/normalisation.ts +6 -0
- package/src/shared/platform.electron.ts +21 -0
- package/src/shared/platform.ts +20 -0
- package/src/shared/query.ts +176 -0
- package/src/shared/rules.test.ts +56 -0
- package/src/shared/rules.ts +371 -0
- package/src/shared/schedules.test.ts +570 -0
- package/src/shared/schedules.ts +560 -0
- package/src/shared/test-helpers.ts +156 -0
- package/src/shared/transactions.test.ts +275 -0
- package/src/shared/transactions.ts +433 -0
- package/src/shared/transfer.test.ts +75 -0
- package/src/shared/transfer.ts +16 -0
- package/src/shared/user.ts +4 -0
- package/src/shared/util.test.ts +240 -0
- package/src/shared/util.ts +633 -0
- package/src/types/api-handlers.ts +287 -0
- package/src/types/budget.ts +8 -0
- package/src/types/file.ts +47 -0
- package/src/types/handlers.ts +46 -0
- package/src/types/models/account.ts +24 -0
- package/src/types/models/bank-sync.ts +23 -0
- package/src/types/models/bank.ts +6 -0
- package/src/types/models/category-group.ts +11 -0
- package/src/types/models/category.ts +13 -0
- package/src/types/models/dashboard.ts +199 -0
- package/src/types/models/gocardless.ts +84 -0
- package/src/types/models/import-transaction.ts +56 -0
- package/src/types/models/index.ts +23 -0
- package/src/types/models/nearby-payee.ts +7 -0
- package/src/types/models/note.ts +4 -0
- package/src/types/models/openid.ts +8 -0
- package/src/types/models/payee-location.ts +8 -0
- package/src/types/models/payee.ts +10 -0
- package/src/types/models/pluggyai.ts +19 -0
- package/src/types/models/reports.ts +144 -0
- package/src/types/models/rule.ts +174 -0
- package/src/types/models/schedule.ts +49 -0
- package/src/types/models/simplefin.ts +28 -0
- package/src/types/models/tags.ts +6 -0
- package/src/types/models/templates.ts +135 -0
- package/src/types/models/transaction-filter.ts +9 -0
- package/src/types/models/transaction.ts +39 -0
- package/src/types/models/user-access.ts +10 -0
- package/src/types/models/user.ts +25 -0
- package/src/types/prefs.ts +167 -0
- package/src/types/server-events.ts +86 -0
- package/src/types/server-handlers.ts +27 -0
- package/src/types/util.ts +26 -0
- package/tsconfig.json +34 -0
- package/typings/pegjs.ts +1 -0
- package/typings/process-worker.ts +12 -0
- package/typings/vite-plugin-peggy-loader.ts +1 -0
- package/typings/window.ts +62 -0
- package/vite.config.ts +109 -0
- package/vite.desktop.config.ts +59 -0
- package/vitest.config.ts +43 -0
- package/vitest.web.config.ts +38 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
// @ts-strict-ignore
|
|
2
|
+
import { getClock, Timestamp } from '@actual-app/crdt';
|
|
3
|
+
|
|
4
|
+
import * as db from '../db';
|
|
5
|
+
import * as prefs from '../prefs';
|
|
6
|
+
import * as sheet from '../sheet';
|
|
7
|
+
import * as mockSyncServer from '../tests/mockSyncServer';
|
|
8
|
+
|
|
9
|
+
import * as encoder from './encoder';
|
|
10
|
+
import { isError } from './utils';
|
|
11
|
+
|
|
12
|
+
import { applyMessages, fullSync, sendMessages, setSyncingMode } from './index';
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
mockSyncServer.reset();
|
|
16
|
+
setSyncingMode('enabled');
|
|
17
|
+
return global.emptyDatabase()();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
global.resetTime();
|
|
22
|
+
setSyncingMode('disabled');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('Sync', () => {
|
|
26
|
+
it('should send messages to the server', async () => {
|
|
27
|
+
void prefs.loadPrefs();
|
|
28
|
+
void prefs.savePrefs({ groupId: 'group' });
|
|
29
|
+
|
|
30
|
+
let timestamp = Timestamp.send();
|
|
31
|
+
await sendMessages([
|
|
32
|
+
{
|
|
33
|
+
dataset: 'transactions',
|
|
34
|
+
row: 'foo',
|
|
35
|
+
column: 'amount',
|
|
36
|
+
value: 3200,
|
|
37
|
+
timestamp,
|
|
38
|
+
},
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
global.stepForwardInTime();
|
|
42
|
+
|
|
43
|
+
timestamp = Timestamp.send();
|
|
44
|
+
await sendMessages([
|
|
45
|
+
{
|
|
46
|
+
dataset: 'transactions',
|
|
47
|
+
row: 'foo',
|
|
48
|
+
column: 'amount',
|
|
49
|
+
value: 4200,
|
|
50
|
+
timestamp,
|
|
51
|
+
},
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
expect(getClock().timestamp.toString()).toEqual(timestamp.toString());
|
|
55
|
+
expect(mockSyncServer.getClock().merkle).toEqual(getClock().merkle);
|
|
56
|
+
|
|
57
|
+
expect(
|
|
58
|
+
await db.all<db.DbCrdtMessage>('SELECT * FROM messages_crdt'),
|
|
59
|
+
).toMatchSnapshot();
|
|
60
|
+
expect(
|
|
61
|
+
await db.all<db.DbClockMessage>('SELECT * FROM messages_clock'),
|
|
62
|
+
).toMatchSnapshot();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should resend old messages to the server', async () => {
|
|
66
|
+
void prefs.loadPrefs();
|
|
67
|
+
void prefs.savePrefs({ groupId: 'group' });
|
|
68
|
+
|
|
69
|
+
global.stepForwardInTime(Date.parse('2018-11-13T13:20:00.000Z'));
|
|
70
|
+
|
|
71
|
+
await applyMessages([
|
|
72
|
+
global.stepForwardInTime() || {
|
|
73
|
+
dataset: 'transactions',
|
|
74
|
+
row: 'foo',
|
|
75
|
+
column: 'amount',
|
|
76
|
+
value: 3200,
|
|
77
|
+
timestamp: Timestamp.send(),
|
|
78
|
+
},
|
|
79
|
+
global.stepForwardInTime() || {
|
|
80
|
+
dataset: 'transactions',
|
|
81
|
+
row: 'foo',
|
|
82
|
+
column: 'amount',
|
|
83
|
+
value: 3200,
|
|
84
|
+
timestamp: Timestamp.send(),
|
|
85
|
+
},
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
// Move the clock forward so that the above 2 messages are not
|
|
89
|
+
// automatically sent out, but will need to be re-sent by way of
|
|
90
|
+
// the merkle tree
|
|
91
|
+
void prefs.savePrefs({
|
|
92
|
+
lastSyncedTimestamp: getClock().timestamp.toString(),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(mockSyncServer.getMessages().length).toBe(0);
|
|
96
|
+
|
|
97
|
+
const result = await fullSync();
|
|
98
|
+
if (isError(result)) throw result.error;
|
|
99
|
+
expect(result.messages.length).toBe(0);
|
|
100
|
+
expect(mockSyncServer.getMessages().length).toBe(2);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should sync multiple clients', async () => {
|
|
104
|
+
void prefs.loadPrefs();
|
|
105
|
+
void prefs.savePrefs({
|
|
106
|
+
groupId: 'group',
|
|
107
|
+
lastSyncedTimestamp: Timestamp.zero.toString(),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await mockSyncServer.handlers['/sync/sync'](
|
|
111
|
+
await encoder.encode(
|
|
112
|
+
'group',
|
|
113
|
+
'client',
|
|
114
|
+
Timestamp.parse('1970-01-01T01:17:37.000Z-0000-0000testinguuid2'),
|
|
115
|
+
[
|
|
116
|
+
{
|
|
117
|
+
dataset: 'transactions',
|
|
118
|
+
row: 'foo',
|
|
119
|
+
column: 'amount',
|
|
120
|
+
value: 'N:3200',
|
|
121
|
+
timestamp: Timestamp.parse(
|
|
122
|
+
'1970-01-02T05:17:36.789Z-0000-0000testinguuid2',
|
|
123
|
+
),
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
dataset: 'transactions',
|
|
127
|
+
row: 'foo',
|
|
128
|
+
column: 'amount',
|
|
129
|
+
value: 'N:4200',
|
|
130
|
+
timestamp: Timestamp.parse(
|
|
131
|
+
'1970-01-02T10:17:36.999Z-0000-0000testinguuid2',
|
|
132
|
+
),
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
),
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
await applyMessages([
|
|
139
|
+
global.stepForwardInTime(Date.parse('1970-01-03T10:17:37.000Z')) || {
|
|
140
|
+
dataset: 'transactions',
|
|
141
|
+
row: 'foo',
|
|
142
|
+
column: 'amount',
|
|
143
|
+
value: 5000,
|
|
144
|
+
timestamp: Timestamp.send(),
|
|
145
|
+
},
|
|
146
|
+
]);
|
|
147
|
+
|
|
148
|
+
const result = await fullSync();
|
|
149
|
+
if (isError(result)) throw result.error;
|
|
150
|
+
expect(result.messages.length).toBe(2);
|
|
151
|
+
expect(mockSyncServer.getMessages().length).toBe(3);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
function registerBudgetMonths(months) {
|
|
156
|
+
const createdMonths = new Set();
|
|
157
|
+
for (const month of months) {
|
|
158
|
+
createdMonths.add(month);
|
|
159
|
+
}
|
|
160
|
+
sheet.get().meta().createdMonths = months;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function asSecondClient(func) {
|
|
164
|
+
void prefs.loadPrefs();
|
|
165
|
+
void prefs.savePrefs({
|
|
166
|
+
groupId: 'group',
|
|
167
|
+
lastSyncedTimestamp: Timestamp.zero.toString(),
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
await func();
|
|
171
|
+
|
|
172
|
+
await global.emptyDatabase()();
|
|
173
|
+
void prefs.savePrefs({
|
|
174
|
+
groupId: 'group',
|
|
175
|
+
lastSyncedTimestamp: Timestamp.zero.toString(),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function expectCellToExist(sheetName, name) {
|
|
180
|
+
const value = sheet.get().getCellValueLoose(sheetName, name);
|
|
181
|
+
expect(value).not.toBe(null);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function expectCellNotToExist(sheetName, name, voided?: boolean) {
|
|
185
|
+
const value = sheet.get().getCellValueLoose(sheetName, name);
|
|
186
|
+
expect(value).toBe(voided ? 0 : null);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
describe('Sync projections', () => {
|
|
190
|
+
test('synced categories should have budgets created', async () => {
|
|
191
|
+
let groupId, fooId, barId;
|
|
192
|
+
await asSecondClient(async () => {
|
|
193
|
+
await sheet.loadSpreadsheet(db);
|
|
194
|
+
groupId = await db.insertCategoryGroup({ id: 'group1', name: 'group1' });
|
|
195
|
+
fooId = await db.insertCategory({ name: 'foo', cat_group: 'group1' });
|
|
196
|
+
barId = await db.insertCategory({ name: 'bar', cat_group: 'group1' });
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
await sheet.loadSpreadsheet(db);
|
|
200
|
+
registerBudgetMonths(['2017-01', '2017-02']);
|
|
201
|
+
expectCellNotToExist('budget201701', 'sum-amount-' + fooId);
|
|
202
|
+
expectCellNotToExist('budget201701', 'sum-amount-' + barId);
|
|
203
|
+
expectCellNotToExist('budget201701', 'group-sum-amount-' + barId);
|
|
204
|
+
|
|
205
|
+
await fullSync();
|
|
206
|
+
|
|
207
|
+
// Make sure the budget cells have been created
|
|
208
|
+
expectCellToExist('budget201701', 'sum-amount-' + fooId);
|
|
209
|
+
expectCellToExist('budget201702', 'sum-amount-' + fooId);
|
|
210
|
+
expectCellToExist('budget201701', 'sum-amount-' + barId);
|
|
211
|
+
expectCellToExist('budget201702', 'sum-amount-' + barId);
|
|
212
|
+
expectCellToExist('budget201701', 'group-sum-amount-' + groupId);
|
|
213
|
+
expectCellToExist('budget201702', 'group-sum-amount-' + groupId);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('creating and deleting categories in same sync', async () => {
|
|
217
|
+
// It should work when the client creates a category and deletes
|
|
218
|
+
// it in the same sync (should do nothing)
|
|
219
|
+
let fooId;
|
|
220
|
+
await asSecondClient(async () => {
|
|
221
|
+
await sheet.loadSpreadsheet(db);
|
|
222
|
+
await db.insertCategoryGroup({ id: 'group1', name: 'group1' });
|
|
223
|
+
fooId = await db.insertCategory({ name: 'foo', cat_group: 'group1' });
|
|
224
|
+
await db.deleteCategory({ id: fooId });
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
await sheet.loadSpreadsheet(db);
|
|
228
|
+
registerBudgetMonths(['2017-01', '2017-02']);
|
|
229
|
+
expectCellNotToExist('budget201701', 'sum-amount-' + fooId);
|
|
230
|
+
await fullSync();
|
|
231
|
+
expectCellNotToExist('budget201701', 'sum-amount-' + fooId);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test('synced categories should have budgets deleted', async () => {
|
|
235
|
+
let fooId;
|
|
236
|
+
await asSecondClient(async () => {
|
|
237
|
+
await sheet.loadSpreadsheet(db);
|
|
238
|
+
await db.insertCategoryGroup({
|
|
239
|
+
id: 'group1',
|
|
240
|
+
name: 'group1',
|
|
241
|
+
});
|
|
242
|
+
fooId = await db.insertCategory({ name: 'foo', cat_group: 'group1' });
|
|
243
|
+
await db.deleteCategory({ id: fooId });
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
await sheet.loadSpreadsheet(db);
|
|
247
|
+
registerBudgetMonths(['2017-01', '2017-02']);
|
|
248
|
+
|
|
249
|
+
// Get all the messages. We'll apply them in two passes
|
|
250
|
+
const messages = mockSyncServer.getMessages();
|
|
251
|
+
|
|
252
|
+
// Apply all but the last message (which deletes the category)
|
|
253
|
+
await applyMessages(messages.slice(0, -1));
|
|
254
|
+
expect((await db.getCategories()).length).toBe(1);
|
|
255
|
+
expectCellToExist('budget201701', 'sum-amount-' + fooId);
|
|
256
|
+
|
|
257
|
+
// Apply the last message and make sure it deleted the appropriate
|
|
258
|
+
// budget cells
|
|
259
|
+
await applyMessages([messages[messages.length - 1]]);
|
|
260
|
+
expect((await db.getCategories()).length).toBe(0);
|
|
261
|
+
expectCellNotToExist('budget201701', 'sum-amount-' + fooId, true);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test('creating and deleting groups in same sync', async () => {
|
|
265
|
+
// It should work when the client creates a category and deletes
|
|
266
|
+
// it in the same sync (should do nothing)
|
|
267
|
+
let groupId;
|
|
268
|
+
await asSecondClient(async () => {
|
|
269
|
+
await sheet.loadSpreadsheet(db);
|
|
270
|
+
groupId = await db.insertCategoryGroup({ id: 'group1', name: 'group1' });
|
|
271
|
+
await db.deleteCategoryGroup({ id: groupId });
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
await sheet.loadSpreadsheet(db);
|
|
275
|
+
registerBudgetMonths(['2017-01', '2017-02']);
|
|
276
|
+
expectCellNotToExist('budget201701', 'group-sum-amount-' + groupId);
|
|
277
|
+
await fullSync();
|
|
278
|
+
expectCellNotToExist('budget201701', 'group-sum-amount-' + groupId);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test('synced groups should have budgets deleted', async () => {
|
|
282
|
+
// Create the message list which creates categories and groups and
|
|
283
|
+
// then deletes them. Go ahead and include a category deletion in
|
|
284
|
+
// the same pass to make sure that works.
|
|
285
|
+
let groupId, fooId;
|
|
286
|
+
await asSecondClient(async () => {
|
|
287
|
+
await sheet.loadSpreadsheet(db);
|
|
288
|
+
groupId = await db.insertCategoryGroup({
|
|
289
|
+
id: 'group1',
|
|
290
|
+
name: 'group1',
|
|
291
|
+
});
|
|
292
|
+
fooId = await db.insertCategory({ name: 'foo', cat_group: 'group1' });
|
|
293
|
+
await db.deleteCategory({ id: fooId });
|
|
294
|
+
await db.deleteCategoryGroup({ id: groupId });
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
await sheet.loadSpreadsheet(db);
|
|
298
|
+
registerBudgetMonths(['2017-01', '2017-02']);
|
|
299
|
+
|
|
300
|
+
// Get all the messages. We'll apply them in two passes
|
|
301
|
+
const messages = mockSyncServer.getMessages();
|
|
302
|
+
|
|
303
|
+
const firstMessages = messages.filter(m => m.column !== 'tombstone');
|
|
304
|
+
const secondMessages = messages.filter(m => m.column === 'tombstone');
|
|
305
|
+
|
|
306
|
+
// Apply all the good messages
|
|
307
|
+
await applyMessages(firstMessages);
|
|
308
|
+
expect((await db.getCategories()).length).toBe(1);
|
|
309
|
+
expect((await db.getCategoriesGrouped()).length).toBe(1);
|
|
310
|
+
expectCellToExist('budget201701', 'sum-amount-' + fooId);
|
|
311
|
+
expectCellToExist('budget201701', 'group-sum-amount-' + groupId);
|
|
312
|
+
|
|
313
|
+
// Apply the messages that deletes it
|
|
314
|
+
await applyMessages(secondMessages);
|
|
315
|
+
expect((await db.getCategories()).length).toBe(0);
|
|
316
|
+
expect((await db.getCategoriesGrouped()).length).toBe(0);
|
|
317
|
+
expectCellNotToExist('budget201701', 'sum-amount-' + fooId, true);
|
|
318
|
+
expectCellNotToExist('budget201701', 'group-sum-amount-' + groupId, true);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test('categories should update the budget when moved', async () => {
|
|
322
|
+
let groupId, fooId;
|
|
323
|
+
await asSecondClient(async () => {
|
|
324
|
+
await sheet.loadSpreadsheet(db);
|
|
325
|
+
groupId = await db.insertCategoryGroup({ id: 'group1', name: 'group1' });
|
|
326
|
+
await db.insertCategoryGroup({ id: 'group2', name: 'group2' });
|
|
327
|
+
fooId = await db.insertCategory({ name: 'foo', cat_group: 'group1' });
|
|
328
|
+
await db.moveCategory(fooId, 'group2', null);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
await sheet.loadSpreadsheet(db);
|
|
332
|
+
registerBudgetMonths(['2017-01', '2017-02']);
|
|
333
|
+
|
|
334
|
+
// Get all the messages. We'll apply them in two passes
|
|
335
|
+
const messages = mockSyncServer.getMessages();
|
|
336
|
+
|
|
337
|
+
const firstMessages = messages.slice(0, -2);
|
|
338
|
+
const secondMessages = messages.slice(-2);
|
|
339
|
+
|
|
340
|
+
// Apply all the good messages
|
|
341
|
+
await applyMessages(firstMessages);
|
|
342
|
+
const [cat] = await db.getCategories();
|
|
343
|
+
expect(cat.cat_group).toBe('group1');
|
|
344
|
+
expectCellToExist('budget201701', 'group-sum-amount-' + groupId);
|
|
345
|
+
|
|
346
|
+
// Apply the messages that deletes it
|
|
347
|
+
await applyMessages(secondMessages);
|
|
348
|
+
});
|
|
349
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { TagEntity } from '../../types/models';
|
|
2
|
+
import { createApp } from '../app';
|
|
3
|
+
import * as db from '../db';
|
|
4
|
+
import { mutator } from '../mutators';
|
|
5
|
+
import { batchMessages } from '../sync';
|
|
6
|
+
import { undoable } from '../undo';
|
|
7
|
+
|
|
8
|
+
export type TagsHandlers = {
|
|
9
|
+
'tags-get': typeof getTags;
|
|
10
|
+
'tags-create': typeof createTag;
|
|
11
|
+
'tags-delete': typeof deleteTag;
|
|
12
|
+
'tags-delete-all': typeof deleteAllTags;
|
|
13
|
+
'tags-update': typeof updateTag;
|
|
14
|
+
'tags-discover': typeof discoverTags;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const app = createApp<TagsHandlers>();
|
|
18
|
+
app.method('tags-get', getTags);
|
|
19
|
+
app.method('tags-create', mutator(undoable(createTag)));
|
|
20
|
+
app.method('tags-delete', mutator(undoable(deleteTag)));
|
|
21
|
+
app.method('tags-delete-all', mutator(deleteAllTags));
|
|
22
|
+
app.method('tags-update', mutator(undoable(updateTag)));
|
|
23
|
+
app.method('tags-discover', mutator(discoverTags));
|
|
24
|
+
|
|
25
|
+
async function getTags(): Promise<TagEntity[]> {
|
|
26
|
+
return await db.getTags();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function createTag({
|
|
30
|
+
tag,
|
|
31
|
+
color = null,
|
|
32
|
+
description = null,
|
|
33
|
+
}: Omit<TagEntity, 'id'>): Promise<TagEntity> {
|
|
34
|
+
const allTags = await db.getAllTags();
|
|
35
|
+
|
|
36
|
+
const { id: tagId = null } = allTags.find(t => t.tag === tag) || {};
|
|
37
|
+
if (tagId) {
|
|
38
|
+
await db.updateTag({
|
|
39
|
+
id: tagId,
|
|
40
|
+
tag,
|
|
41
|
+
color,
|
|
42
|
+
description,
|
|
43
|
+
tombstone: 0,
|
|
44
|
+
});
|
|
45
|
+
return { id: tagId, tag, color, description };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const id = await db.insertTag({
|
|
49
|
+
tag: tag.trim(),
|
|
50
|
+
color: color ? color.trim() : null,
|
|
51
|
+
description,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return { id, tag, color, description };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function deleteTag(tag: Pick<TagEntity, 'id'>): Promise<TagEntity['id']> {
|
|
58
|
+
await db.deleteTag(tag);
|
|
59
|
+
return tag.id;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function deleteAllTags(
|
|
63
|
+
ids: Array<TagEntity['id']>,
|
|
64
|
+
): Promise<Array<TagEntity['id']>> {
|
|
65
|
+
await batchMessages(async () => {
|
|
66
|
+
for (const id of ids) {
|
|
67
|
+
await db.deleteTag({ id });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
return ids;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function updateTag(
|
|
74
|
+
tag: Partial<TagEntity> & Pick<TagEntity, 'id'>,
|
|
75
|
+
): Promise<Partial<TagEntity>> {
|
|
76
|
+
await db.updateTag(tag);
|
|
77
|
+
return tag;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function discoverTags(): Promise<TagEntity[]> {
|
|
81
|
+
const taggedNotes = await db.findTags();
|
|
82
|
+
|
|
83
|
+
const tags = await getTags();
|
|
84
|
+
for (const { notes } of taggedNotes) {
|
|
85
|
+
for (const [_, tag] of notes.matchAll(/(?<!#)#([^#\s]+)/g)) {
|
|
86
|
+
if (!tags.find(t => t.tag === tag)) {
|
|
87
|
+
tags.push(await createTag({ tag }));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return tags.sort(function (a, b) {
|
|
93
|
+
if (a.tag < b.tag) {
|
|
94
|
+
return -1;
|
|
95
|
+
}
|
|
96
|
+
if (a.tag > b.tag) {
|
|
97
|
+
return 1;
|
|
98
|
+
}
|
|
99
|
+
return 0;
|
|
100
|
+
});
|
|
101
|
+
}
|