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