@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,280 @@
|
|
|
1
|
+
// @ts-strict-ignore
|
|
2
|
+
import type { Database } from '@jlongster/sql.js';
|
|
3
|
+
|
|
4
|
+
import { captureBreadcrumb } from '../platform/exceptions';
|
|
5
|
+
import { logger } from '../platform/server/log';
|
|
6
|
+
import * as sqlite from '../platform/server/sqlite';
|
|
7
|
+
import { sheetForMonth } from '../shared/months';
|
|
8
|
+
import * as Platform from '../shared/platform';
|
|
9
|
+
|
|
10
|
+
import type * as DbModule from './db';
|
|
11
|
+
import type {
|
|
12
|
+
DbPreference,
|
|
13
|
+
DbReflectBudget,
|
|
14
|
+
DbZeroBudget,
|
|
15
|
+
DbZeroBudgetMonth,
|
|
16
|
+
} from './db';
|
|
17
|
+
import { Spreadsheet } from './spreadsheet/spreadsheet';
|
|
18
|
+
import { resolveName } from './spreadsheet/util';
|
|
19
|
+
|
|
20
|
+
let globalSheet: Spreadsheet;
|
|
21
|
+
let globalOnChange;
|
|
22
|
+
let globalCacheDb;
|
|
23
|
+
|
|
24
|
+
export function get(): Spreadsheet {
|
|
25
|
+
return globalSheet;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function updateSpreadsheetCache(rawDb, names: string[]) {
|
|
29
|
+
sqlite.transaction(rawDb, () => {
|
|
30
|
+
names.forEach(name => {
|
|
31
|
+
const node = globalSheet._getNode(name);
|
|
32
|
+
|
|
33
|
+
// Don't cache query nodes yet
|
|
34
|
+
if (node.sql == null) {
|
|
35
|
+
sqlite.runQuery(
|
|
36
|
+
rawDb,
|
|
37
|
+
'INSERT OR REPLACE INTO kvcache (key, value) VALUES (?, ?)',
|
|
38
|
+
[name, JSON.stringify(node.value)],
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function setCacheStatus(
|
|
46
|
+
mainDb: Database,
|
|
47
|
+
cacheDb: Database,
|
|
48
|
+
{ clean }: { clean: boolean },
|
|
49
|
+
) {
|
|
50
|
+
if (clean) {
|
|
51
|
+
// Generate random number and stick in both places
|
|
52
|
+
const num = Math.random() * 10000000;
|
|
53
|
+
sqlite.runQuery(
|
|
54
|
+
cacheDb,
|
|
55
|
+
'INSERT OR REPLACE INTO kvcache_key (id, key) VALUES (1, ?)',
|
|
56
|
+
[num],
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
if (mainDb) {
|
|
60
|
+
sqlite.runQuery(
|
|
61
|
+
mainDb,
|
|
62
|
+
'INSERT OR REPLACE INTO kvcache_key (id, key) VALUES (1, ?)',
|
|
63
|
+
[num],
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
sqlite.runQuery(cacheDb, 'DELETE FROM kvcache_key');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isCacheDirty(mainDb: Database, cacheDb: Database): boolean {
|
|
72
|
+
let rows = sqlite.runQuery<{ key?: number }>(
|
|
73
|
+
cacheDb,
|
|
74
|
+
'SELECT key FROM kvcache_key WHERE id = 1',
|
|
75
|
+
[],
|
|
76
|
+
true,
|
|
77
|
+
);
|
|
78
|
+
const num = rows.length === 0 ? null : rows[0].key;
|
|
79
|
+
|
|
80
|
+
if (num == null) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (mainDb) {
|
|
85
|
+
const rows = sqlite.runQuery<{ key?: number }>(
|
|
86
|
+
mainDb,
|
|
87
|
+
'SELECT key FROM kvcache_key WHERE id = 1',
|
|
88
|
+
[],
|
|
89
|
+
true,
|
|
90
|
+
);
|
|
91
|
+
if (rows.length === 0 || rows[0].key !== num) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Always also check if there is anything in `kvcache`. We ask for one item;
|
|
97
|
+
// if we didn't get back anything it's empty so there is no cache
|
|
98
|
+
rows = sqlite.runQuery(cacheDb, 'SELECT * FROM kvcache LIMIT 1', [], true);
|
|
99
|
+
return rows.length === 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function loadSpreadsheet(
|
|
103
|
+
db,
|
|
104
|
+
onSheetChange?,
|
|
105
|
+
): Promise<Spreadsheet> {
|
|
106
|
+
const cacheEnabled = process.env.NODE_ENV !== 'test';
|
|
107
|
+
const mainDb = db.getDatabase();
|
|
108
|
+
let cacheDb;
|
|
109
|
+
|
|
110
|
+
if (!Platform.isBrowser && cacheEnabled) {
|
|
111
|
+
// Desktop apps use a separate database for the cache. This is because it is
|
|
112
|
+
// much more likely to directly work with files on desktop, and this makes
|
|
113
|
+
// it a lot clearer what the true filesize of the main db is (and avoid
|
|
114
|
+
// copying the cache data around).
|
|
115
|
+
const cachePath = db
|
|
116
|
+
.getDatabasePath()
|
|
117
|
+
.replace(/db\.sqlite$/, 'cache.sqlite');
|
|
118
|
+
globalCacheDb = cacheDb = await sqlite.openDatabase(cachePath);
|
|
119
|
+
|
|
120
|
+
sqlite.execQuery(
|
|
121
|
+
cacheDb,
|
|
122
|
+
`
|
|
123
|
+
CREATE TABLE IF NOT EXISTS kvcache (key TEXT PRIMARY KEY, value TEXT);
|
|
124
|
+
CREATE TABLE IF NOT EXISTS kvcache_key (id INTEGER PRIMARY KEY, key REAL)
|
|
125
|
+
`,
|
|
126
|
+
);
|
|
127
|
+
} else {
|
|
128
|
+
// All other platforms use the same database for cache
|
|
129
|
+
cacheDb = mainDb;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let sheet;
|
|
133
|
+
if (cacheEnabled) {
|
|
134
|
+
sheet = new Spreadsheet(
|
|
135
|
+
updateSpreadsheetCache.bind(null, cacheDb),
|
|
136
|
+
setCacheStatus.bind(null, mainDb, cacheDb),
|
|
137
|
+
);
|
|
138
|
+
} else {
|
|
139
|
+
sheet = new Spreadsheet();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
captureBreadcrumb({
|
|
143
|
+
message: 'loading spreadsheet',
|
|
144
|
+
category: 'server',
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
globalSheet = sheet;
|
|
148
|
+
globalOnChange = onSheetChange;
|
|
149
|
+
|
|
150
|
+
if (onSheetChange) {
|
|
151
|
+
sheet.addEventListener('change', onSheetChange);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (cacheEnabled && !isCacheDirty(mainDb, cacheDb)) {
|
|
155
|
+
const cachedRows = sqlite.runQuery<{ key?: number; value: string }>(
|
|
156
|
+
cacheDb,
|
|
157
|
+
'SELECT * FROM kvcache',
|
|
158
|
+
[],
|
|
159
|
+
true,
|
|
160
|
+
);
|
|
161
|
+
logger.log(`Loaded spreadsheet from cache (${cachedRows.length} items)`);
|
|
162
|
+
|
|
163
|
+
for (const row of cachedRows) {
|
|
164
|
+
const parsed = JSON.parse(row.value);
|
|
165
|
+
sheet.load(row.key, parsed);
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
logger.log('Loading fresh spreadsheet');
|
|
169
|
+
await loadUserBudgets(db);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
captureBreadcrumb({
|
|
173
|
+
message: 'loaded spreadsheet',
|
|
174
|
+
category: 'server',
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return sheet;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function unloadSpreadsheet(): void {
|
|
181
|
+
if (globalSheet) {
|
|
182
|
+
// TODO: Should wait for the sheet to finish
|
|
183
|
+
globalSheet.unload();
|
|
184
|
+
globalSheet = null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (globalCacheDb) {
|
|
188
|
+
sqlite.closeDatabase(globalCacheDb);
|
|
189
|
+
globalCacheDb = null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export async function reloadSpreadsheet(db): Promise<Spreadsheet> {
|
|
194
|
+
if (globalSheet) {
|
|
195
|
+
unloadSpreadsheet();
|
|
196
|
+
return loadSpreadsheet(db, globalOnChange);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export async function loadUserBudgets(db: typeof DbModule): Promise<void> {
|
|
201
|
+
const sheet = globalSheet;
|
|
202
|
+
|
|
203
|
+
// TODO: Clear out the cache here so make sure future loads of the app
|
|
204
|
+
// don't load any extra values that aren't set here
|
|
205
|
+
|
|
206
|
+
const { value: budgetType = 'envelope' } =
|
|
207
|
+
(await db.first<Pick<DbPreference, 'value'>>(
|
|
208
|
+
'SELECT value from preferences WHERE id = ?',
|
|
209
|
+
['budgetType'],
|
|
210
|
+
)) ?? {};
|
|
211
|
+
|
|
212
|
+
const table = budgetType === 'tracking' ? 'reflect_budgets' : 'zero_budgets';
|
|
213
|
+
const budgets = await db.all<DbReflectBudget | DbZeroBudget>(`
|
|
214
|
+
SELECT * FROM ${table} b
|
|
215
|
+
LEFT JOIN categories c ON c.id = b.category
|
|
216
|
+
WHERE c.tombstone = 0
|
|
217
|
+
`);
|
|
218
|
+
|
|
219
|
+
sheet.startTransaction();
|
|
220
|
+
|
|
221
|
+
// Load all the budget amounts and carryover values
|
|
222
|
+
for (const budget of budgets) {
|
|
223
|
+
if (budget.month && budget.category) {
|
|
224
|
+
const sheetName = `budget${budget.month}`;
|
|
225
|
+
sheet.set(`${sheetName}!budget-${budget.category}`, budget.amount);
|
|
226
|
+
sheet.set(
|
|
227
|
+
`${sheetName}!carryover-${budget.category}`,
|
|
228
|
+
budget.carryover === 1 ? true : false,
|
|
229
|
+
);
|
|
230
|
+
sheet.set(`${sheetName}!goal-${budget.category}`, budget.goal);
|
|
231
|
+
sheet.set(`${sheetName}!long-goal-${budget.category}`, budget.long_goal);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// For zero-based budgets, load the buffered amounts
|
|
236
|
+
if (budgetType !== 'tracking') {
|
|
237
|
+
const budgetMonths = await db.all<DbZeroBudgetMonth>(
|
|
238
|
+
'SELECT * FROM zero_budget_months',
|
|
239
|
+
);
|
|
240
|
+
for (const budgetMonth of budgetMonths) {
|
|
241
|
+
const sheetName = sheetForMonth(budgetMonth.id);
|
|
242
|
+
sheet.set(`${sheetName}!buffered`, budgetMonth.buffered);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
sheet.endTransaction();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function getCell(sheet: string, name: string) {
|
|
250
|
+
return globalSheet._getNode(resolveName(sheet, name));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function getCellValue(
|
|
254
|
+
sheet: string,
|
|
255
|
+
name: string,
|
|
256
|
+
): string | number | boolean {
|
|
257
|
+
return globalSheet.getValue(resolveName(sheet, name));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function startTransaction(): void {
|
|
261
|
+
if (globalSheet) {
|
|
262
|
+
globalSheet.startTransaction();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function endTransaction(): void {
|
|
267
|
+
if (globalSheet) {
|
|
268
|
+
globalSheet.endTransaction();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function waitOnSpreadsheet(): Promise<void> {
|
|
273
|
+
return new Promise(resolve => {
|
|
274
|
+
if (globalSheet) {
|
|
275
|
+
globalSheet.onFinish(resolve);
|
|
276
|
+
} else {
|
|
277
|
+
resolve(undefined);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`Spreadsheet > querying deep join works 1`] = `"=from transactions where acct.offbudget = 0 and (description.transfer_acct.offbudget = null or description.transfer_acct.offbudget = 1) select { acct.offbudget, description.transfer_acct.offbudget as foo, amount }"`;
|
|
4
|
+
|
|
5
|
+
exports[`Spreadsheet > querying transactions works 1`] = `"=from transactions select { amount, category }"`;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { QueryState } from '../../shared/query';
|
|
2
|
+
import { createApp } from '../app';
|
|
3
|
+
import * as sheet from '../sheet';
|
|
4
|
+
|
|
5
|
+
import { resolveName, unresolveName } from './util';
|
|
6
|
+
|
|
7
|
+
export type SpreadsheetHandlers = {
|
|
8
|
+
'get-cell': typeof getCell;
|
|
9
|
+
'get-cell-names': typeof getCellNames;
|
|
10
|
+
'create-query': typeof createQuery;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Expose functions to the client
|
|
14
|
+
export const app = createApp<SpreadsheetHandlers>();
|
|
15
|
+
app.method('get-cell', getCell);
|
|
16
|
+
app.method('get-cell-names', getCellNames);
|
|
17
|
+
app.method('create-query', createQuery);
|
|
18
|
+
|
|
19
|
+
async function getCell({
|
|
20
|
+
sheetName,
|
|
21
|
+
name,
|
|
22
|
+
}: {
|
|
23
|
+
sheetName: string;
|
|
24
|
+
name: string;
|
|
25
|
+
}) {
|
|
26
|
+
const node = sheet.get()._getNode(resolveName(sheetName, name));
|
|
27
|
+
return { name: node.name, value: node.value };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function getCellNames({ sheetName }: { sheetName: string }) {
|
|
31
|
+
const names = [];
|
|
32
|
+
for (const name of sheet.get().getNodes().keys()) {
|
|
33
|
+
const { sheet: nodeSheet, name: nodeName } = unresolveName(name);
|
|
34
|
+
if (nodeSheet === sheetName) {
|
|
35
|
+
names.push(nodeName);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return names;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function createQuery({
|
|
42
|
+
sheetName,
|
|
43
|
+
name,
|
|
44
|
+
query,
|
|
45
|
+
}: {
|
|
46
|
+
sheetName: string;
|
|
47
|
+
name: string;
|
|
48
|
+
query: QueryState;
|
|
49
|
+
}) {
|
|
50
|
+
// Always run it regardless of cache. We don't know anything has changed
|
|
51
|
+
// between the cache value being saved and now
|
|
52
|
+
sheet.get().createQuery(sheetName, name, query);
|
|
53
|
+
return 'ok';
|
|
54
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// @ts-strict-ignore
|
|
2
|
+
export function Graph() {
|
|
3
|
+
const graph = {
|
|
4
|
+
addNode,
|
|
5
|
+
removeNode,
|
|
6
|
+
adjacent,
|
|
7
|
+
adjacentIncoming,
|
|
8
|
+
addEdge,
|
|
9
|
+
removeEdge,
|
|
10
|
+
removeIncomingEdges,
|
|
11
|
+
topologicalSort,
|
|
12
|
+
generateDOT,
|
|
13
|
+
getEdges,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const edges = new Map();
|
|
17
|
+
const incomingEdges = new Map();
|
|
18
|
+
|
|
19
|
+
function getEdges() {
|
|
20
|
+
return { edges, incomingEdges };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function addNode(node) {
|
|
24
|
+
edges.set(node, adjacent(node));
|
|
25
|
+
incomingEdges.set(node, adjacentIncoming(node));
|
|
26
|
+
return graph;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function removeIncomingEdges(node) {
|
|
30
|
+
const incoming = adjacentIncoming(node);
|
|
31
|
+
incomingEdges.set(node, new Set());
|
|
32
|
+
|
|
33
|
+
const iter = incoming.values();
|
|
34
|
+
let cur = iter.next();
|
|
35
|
+
while (!cur.done) {
|
|
36
|
+
removeEdge(cur.value, node);
|
|
37
|
+
cur = iter.next();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function removeNode(node) {
|
|
42
|
+
removeIncomingEdges(node);
|
|
43
|
+
edges.delete(node);
|
|
44
|
+
incomingEdges.delete(node);
|
|
45
|
+
return graph;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function adjacent(node) {
|
|
49
|
+
return edges.get(node) || new Set();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function adjacentIncoming(node) {
|
|
53
|
+
return incomingEdges.get(node) || new Set();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Adds an edge from node u to node v.
|
|
57
|
+
// Implicitly adds the nodes if they were not already added.
|
|
58
|
+
function addEdge(node1, node2) {
|
|
59
|
+
addNode(node1);
|
|
60
|
+
addNode(node2);
|
|
61
|
+
adjacent(node1).add(node2);
|
|
62
|
+
adjacentIncoming(node2).add(node1);
|
|
63
|
+
return graph;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Removes the edge from node u to node v.
|
|
67
|
+
// Does not remove the nodes.
|
|
68
|
+
// Does nothing if the edge does not exist.
|
|
69
|
+
function removeEdge(node1, node2) {
|
|
70
|
+
if (edges.has(node1)) {
|
|
71
|
+
adjacent(node1).delete(node2);
|
|
72
|
+
}
|
|
73
|
+
if (incomingEdges.has(node2)) {
|
|
74
|
+
adjacentIncoming(node2).delete(node1);
|
|
75
|
+
}
|
|
76
|
+
return graph;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function topologicalSort(sourceNodes) {
|
|
80
|
+
const visited = new Set();
|
|
81
|
+
const sorted = [];
|
|
82
|
+
|
|
83
|
+
sourceNodes.forEach(name => {
|
|
84
|
+
if (!visited.has(name)) {
|
|
85
|
+
topologicalSortIterable(name, visited, sorted);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return sorted;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function topologicalSortIterable(name, visited, sorted) {
|
|
93
|
+
const stackTrace: StackItem[] = [];
|
|
94
|
+
|
|
95
|
+
stackTrace.push({
|
|
96
|
+
count: -1,
|
|
97
|
+
value: name,
|
|
98
|
+
parent: '',
|
|
99
|
+
level: 0,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
while (stackTrace.length > 0) {
|
|
103
|
+
const current = stackTrace.slice(-1)[0];
|
|
104
|
+
|
|
105
|
+
const adjacents = adjacent(current.value);
|
|
106
|
+
if (current.count === -1) {
|
|
107
|
+
current.count = adjacents.size;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (current.count > 0) {
|
|
111
|
+
const iter = adjacents.values();
|
|
112
|
+
let cur = iter.next();
|
|
113
|
+
while (!cur.done) {
|
|
114
|
+
if (!visited.has(cur.value)) {
|
|
115
|
+
stackTrace.push({
|
|
116
|
+
count: -1,
|
|
117
|
+
parent: current.value,
|
|
118
|
+
value: cur.value,
|
|
119
|
+
level: current.level + 1,
|
|
120
|
+
});
|
|
121
|
+
} else {
|
|
122
|
+
current.count--;
|
|
123
|
+
}
|
|
124
|
+
cur = iter.next();
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
if (!visited.has(current.value)) {
|
|
128
|
+
visited.add(current.value);
|
|
129
|
+
sorted.unshift(current.value);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const removed = stackTrace.pop();
|
|
133
|
+
for (let i = 0; i < stackTrace.length; i++) {
|
|
134
|
+
if (stackTrace[i].value === removed.parent) {
|
|
135
|
+
stackTrace[i].count--;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function generateDOT() {
|
|
143
|
+
const edgeStrings = [];
|
|
144
|
+
edges.forEach(function (adj, edge) {
|
|
145
|
+
if (adj.length !== 0) {
|
|
146
|
+
edgeStrings.push(`${edge} -> {${adj.join(',')}}`);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return `
|
|
151
|
+
digraph G {
|
|
152
|
+
${edgeStrings.join('\n').replace(/!/g, '_')}
|
|
153
|
+
}
|
|
154
|
+
`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return graph;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
type StackItem = {
|
|
161
|
+
count: number;
|
|
162
|
+
value: string;
|
|
163
|
+
parent: string;
|
|
164
|
+
level: number;
|
|
165
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const query = (
|
|
2
|
+
from t in transactions
|
|
3
|
+
where t.amount == 700
|
|
4
|
+
select { t.amount, t.description }
|
|
5
|
+
);
|
|
6
|
+
|
|
7
|
+
const sqlgen = require("./sqlgen");
|
|
8
|
+
const sqlinterp = require("./sqlinterp");
|
|
9
|
+
const { Database } = require("sqlite3");
|
|
10
|
+
const db = new Database("db.sqlite");
|
|
11
|
+
|
|
12
|
+
const shemas = {};
|
|
13
|
+
|
|
14
|
+
function makeRow(columns, data) {
|
|
15
|
+
return t.toObj(columns.map((name, i) => {
|
|
16
|
+
return [name, data[i]];
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function run(query, cb) {
|
|
21
|
+
console.log("running")
|
|
22
|
+
db.each(sqlgen(query) + " LIMIT 10", function(err, row) {
|
|
23
|
+
console.log(row);
|
|
24
|
+
}, cb);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
db.addListener("preupdate", function(type, db, table, oldValues, newValues) {
|
|
28
|
+
let existed, exists;
|
|
29
|
+
debugger;
|
|
30
|
+
if(oldValues) {
|
|
31
|
+
const row = makeRow(schemas[table], oldValues);
|
|
32
|
+
existed = sqlinterp(query, row);
|
|
33
|
+
}
|
|
34
|
+
if(newValues) {
|
|
35
|
+
const row = makeRow(schemas[table], newValues);
|
|
36
|
+
exists = sqlinterp(query, row);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if(existed || exists) {
|
|
40
|
+
console.log("Running query...");
|
|
41
|
+
run(query, function() {});
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
db.all("PRAGMA table_info(transactions)", function(err, rows) {
|
|
46
|
+
schemas["transactions"] = rows.map(r => r.name);
|
|
47
|
+
|
|
48
|
+
run(query, function() {
|
|
49
|
+
console.log("\nCHANGING\n");
|
|
50
|
+
|
|
51
|
+
db.run("INSERT INTO transactions (acct, category, amount, description, date)" +
|
|
52
|
+
' VALUES (1, 1, 3300, "foobar", 1456808400000)');
|
|
53
|
+
db.run("INSERT INTO transactions (acct, category, amount, description, date)" +
|
|
54
|
+
' VALUES (1, 1, 700, "foobar", 1456808400000)');
|
|
55
|
+
db.run("INSERT INTO transactions (acct, category, amount, description, date)" +
|
|
56
|
+
' VALUES (1, 1, -500, "foobar", 1456808400000)');
|
|
57
|
+
db.run("INSERT INTO transactions (acct, category, amount, description, date)" +
|
|
58
|
+
' VALUES (1, 1, 200, "foobar", 1456808400000)');
|
|
59
|
+
});
|
|
60
|
+
});
|