@actual-app/api 26.1.0-nightly.20251220 → 26.1.0-nightly.20251222
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/@types/loot-core/src/platform/exceptions/index.d.ts +1 -1
- package/@types/loot-core/src/shared/util.d.ts +6 -2
- package/@types/loot-core/typings/window.d.ts +0 -4
- package/dist/app/bundle.api.js +79 -54
- package/dist/migrations/1722804019000_create_dashboard_table.js +0 -1
- package/dist/package.json +2 -2
- package/package.json +2 -2
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export declare const captureException: (exc: Error) => void;
|
|
2
|
-
export declare const captureBreadcrumb: (
|
|
2
|
+
export declare const captureBreadcrumb: (crumb: unknown) => void;
|
|
@@ -55,7 +55,9 @@ export declare function getNumberFormat({ format, hideFraction, decimalPlaces, }
|
|
|
55
55
|
value: "comma-dot" | "dot-comma" | "space-comma" | "apostrophe-dot" | "comma-dot-in";
|
|
56
56
|
thousandsSeparator: any;
|
|
57
57
|
decimalSeparator: any;
|
|
58
|
-
formatter:
|
|
58
|
+
formatter: {
|
|
59
|
+
format: (value: number) => string;
|
|
60
|
+
};
|
|
59
61
|
};
|
|
60
62
|
/**
|
|
61
63
|
* The exact amount.
|
|
@@ -73,7 +75,9 @@ export type CurrencyAmount = string;
|
|
|
73
75
|
export type IntegerAmount = number;
|
|
74
76
|
export declare function safeNumber(value: number): number;
|
|
75
77
|
export declare function toRelaxedNumber(currencyAmount: CurrencyAmount): Amount;
|
|
76
|
-
export declare function integerToCurrency(integerAmount: IntegerAmount, formatter?:
|
|
78
|
+
export declare function integerToCurrency(integerAmount: IntegerAmount, formatter?: {
|
|
79
|
+
format: (value: number) => string;
|
|
80
|
+
}, decimalPlaces?: number): string;
|
|
77
81
|
export declare function integerToCurrencyWithDecimal(integerAmount: IntegerAmount): string;
|
|
78
82
|
export declare function amountToCurrency(amount: Amount): CurrencyAmount;
|
|
79
83
|
export declare function amountToCurrencyNoDecimal(amount: Amount): CurrencyAmount;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type NavigateFunction } from 'react-router';
|
|
2
1
|
export {};
|
|
3
2
|
type FileDialogOptions = {
|
|
4
3
|
properties?: Array<'openFile' | 'openDirectory'>;
|
|
@@ -32,9 +31,6 @@ type Actual = {
|
|
|
32
31
|
startOAuthServer: () => Promise<string>;
|
|
33
32
|
};
|
|
34
33
|
declare global {
|
|
35
|
-
interface Window {
|
|
36
|
-
__navigate?: NavigateFunction;
|
|
37
|
-
}
|
|
38
34
|
var Actual: Actual;
|
|
39
35
|
var IS_TESTING: boolean;
|
|
40
36
|
var currentMonth: string | null;
|
package/dist/app/bundle.api.js
CHANGED
|
@@ -3858,7 +3858,7 @@ const getBudgetDir = (id2) => {
|
|
|
3858
3858
|
throw new Error("getDocumentDir: id is falsy: " + id2);
|
|
3859
3859
|
}
|
|
3860
3860
|
if (id2.match(/[^A-Za-z0-9\-_]/)) {
|
|
3861
|
-
throw new Error(`Invalid budget id
|
|
3861
|
+
throw new Error(`Invalid budget id "${id2}". Check the id of your budget in the Advanced section of the settings page.`);
|
|
3862
3862
|
}
|
|
3863
3863
|
return path.join(getDocumentDir(), id2);
|
|
3864
3864
|
};
|
|
@@ -9324,7 +9324,7 @@ function getNumberFormat({ format: format2 = numberFormatConfig.format, hideFrac
|
|
|
9324
9324
|
break;
|
|
9325
9325
|
case "apostrophe-dot":
|
|
9326
9326
|
locale = "de-CH";
|
|
9327
|
-
thousandsSeparator = "
|
|
9327
|
+
thousandsSeparator = "'";
|
|
9328
9328
|
decimalSeparator = ".";
|
|
9329
9329
|
break;
|
|
9330
9330
|
case "comma-dot-in":
|
|
@@ -9345,11 +9345,18 @@ function getNumberFormat({ format: format2 = numberFormatConfig.format, hideFrac
|
|
|
9345
9345
|
minimumFractionDigits: currentHideFraction ? 0 : 2,
|
|
9346
9346
|
maximumFractionDigits: currentHideFraction ? 0 : 2
|
|
9347
9347
|
};
|
|
9348
|
+
const intlFormatter = new Intl.NumberFormat(locale, fractionDigitsOptions);
|
|
9349
|
+
const formatter = {
|
|
9350
|
+
format: (value) => {
|
|
9351
|
+
const formatted = intlFormatter.format(value);
|
|
9352
|
+
return formatted === "-0" ? "0" : formatted;
|
|
9353
|
+
}
|
|
9354
|
+
};
|
|
9348
9355
|
return {
|
|
9349
9356
|
value: currentFormat,
|
|
9350
9357
|
thousandsSeparator,
|
|
9351
9358
|
decimalSeparator,
|
|
9352
|
-
formatter
|
|
9359
|
+
formatter
|
|
9353
9360
|
};
|
|
9354
9361
|
}
|
|
9355
9362
|
const MAX_SAFE_NUMBER = 2 ** 51 - 1;
|
|
@@ -9359,7 +9366,7 @@ function safeNumber(value) {
|
|
|
9359
9366
|
throw new Error("safeNumber: number is not an integer: " + JSON.stringify(value));
|
|
9360
9367
|
}
|
|
9361
9368
|
if (value > MAX_SAFE_NUMBER || value < MIN_SAFE_NUMBER) {
|
|
9362
|
-
throw new Error("safeNumber: can
|
|
9369
|
+
throw new Error("safeNumber: can't safely perform arithmetic with number: " + value);
|
|
9363
9370
|
}
|
|
9364
9371
|
return value;
|
|
9365
9372
|
}
|
|
@@ -11812,7 +11819,8 @@ instance.loadLanguages;
|
|
|
11812
11819
|
const captureException = function (exc) {
|
|
11813
11820
|
console.error("[Exception]", exc);
|
|
11814
11821
|
};
|
|
11815
|
-
const captureBreadcrumb = function (
|
|
11822
|
+
const captureBreadcrumb = function (crumb) {
|
|
11823
|
+
console.info("[Breadcrumb]", crumb);
|
|
11816
11824
|
};
|
|
11817
11825
|
function isPreviewEnvironment() {
|
|
11818
11826
|
return String(process.env.REACT_APP_NETLIFY) === "true";
|
|
@@ -13057,11 +13065,11 @@ function typed(value, type2, { literal = false } = {}) {
|
|
|
13057
13065
|
}
|
|
13058
13066
|
function getFieldDescription(schema2, tableName, field) {
|
|
13059
13067
|
if (schema2[tableName] == null) {
|
|
13060
|
-
throw new CompileError(`Table
|
|
13068
|
+
throw new CompileError(`Table "${tableName}" does not exist in the schema`);
|
|
13061
13069
|
}
|
|
13062
13070
|
const fieldDesc = schema2[tableName][field];
|
|
13063
13071
|
if (fieldDesc == null) {
|
|
13064
|
-
throw new CompileError(`Field
|
|
13072
|
+
throw new CompileError(`Field "${field}" does not exist in table "${tableName}"`);
|
|
13065
13073
|
}
|
|
13066
13074
|
return fieldDesc;
|
|
13067
13075
|
}
|
|
@@ -13078,7 +13086,7 @@ function makePath(state, path2) {
|
|
|
13078
13086
|
throw new CompileError(`Path error: ${tableName2} table does not exist`);
|
|
13079
13087
|
}
|
|
13080
13088
|
if (!table[field] || table[field].ref == null) {
|
|
13081
|
-
throw new CompileError(`Field not joinable on table ${tableName2}:
|
|
13089
|
+
throw new CompileError(`Field not joinable on table ${tableName2}: "${field}"`);
|
|
13082
13090
|
}
|
|
13083
13091
|
return table[field].ref;
|
|
13084
13092
|
}, initialTable);
|
|
@@ -13185,7 +13193,7 @@ function inferParam(param, type2) {
|
|
|
13185
13193
|
float: ["integer"]
|
|
13186
13194
|
};
|
|
13187
13195
|
if (existingType !== type2 && (!casts[type2] || !casts[type2].includes(existingType))) {
|
|
13188
|
-
throw new Error(`Parameter
|
|
13196
|
+
throw new Error(`Parameter "${param.paramName}" can't convert to ${type2} (already inferred as ${existingType})`);
|
|
13189
13197
|
}
|
|
13190
13198
|
}
|
|
13191
13199
|
else {
|
|
@@ -13202,7 +13210,7 @@ function castInput(state, expr, type2) {
|
|
|
13202
13210
|
}
|
|
13203
13211
|
else if (expr.type === "null") {
|
|
13204
13212
|
if (!expr.literal) {
|
|
13205
|
-
throw new CompileError("A non-literal null doesn
|
|
13213
|
+
throw new CompileError("A non-literal null doesn't make sense");
|
|
13206
13214
|
}
|
|
13207
13215
|
if (type2 === "boolean") {
|
|
13208
13216
|
return typed(0, "boolean", { literal: true });
|
|
@@ -13218,7 +13226,7 @@ function castInput(state, expr, type2) {
|
|
|
13218
13226
|
throw new CompileError("Casting string fields to dates is not supported");
|
|
13219
13227
|
}
|
|
13220
13228
|
}
|
|
13221
|
-
throw new CompileError(`Can
|
|
13229
|
+
throw new CompileError(`Can't cast ${expr.type} to date`);
|
|
13222
13230
|
}
|
|
13223
13231
|
else if (type2 === "date-month") {
|
|
13224
13232
|
let expr2;
|
|
@@ -13229,7 +13237,7 @@ function castInput(state, expr, type2) {
|
|
|
13229
13237
|
expr2 = parseMonth(expr.value) || parseDate(expr.value) || badDateFormat(expr.value, "date-month");
|
|
13230
13238
|
}
|
|
13231
13239
|
else {
|
|
13232
|
-
throw new CompileError(`Can
|
|
13240
|
+
throw new CompileError(`Can't cast ${expr.type} to date-month`);
|
|
13233
13241
|
}
|
|
13234
13242
|
if (expr2.literal) {
|
|
13235
13243
|
return typed(dateToInt(expr2.value.toString().slice(0, 6)), "date-month", { literal: true });
|
|
@@ -13247,7 +13255,7 @@ function castInput(state, expr, type2) {
|
|
|
13247
13255
|
expr2 = parseYear(expr.value) || parseMonth(expr.value) || parseDate(expr.value) || badDateFormat(expr.value, "date-year");
|
|
13248
13256
|
}
|
|
13249
13257
|
else {
|
|
13250
|
-
throw new CompileError(`Can
|
|
13258
|
+
throw new CompileError(`Can't cast ${expr.type} to date-year`);
|
|
13251
13259
|
}
|
|
13252
13260
|
if (expr2.literal) {
|
|
13253
13261
|
return typed(dateToInt(expr2.value.toString().slice(0, 4)), "date-year", {
|
|
@@ -13271,7 +13279,7 @@ function castInput(state, expr, type2) {
|
|
|
13271
13279
|
if (expr.type === "any") {
|
|
13272
13280
|
return typed(expr.value, type2, { literal: expr.literal });
|
|
13273
13281
|
}
|
|
13274
|
-
throw new CompileError(`Can
|
|
13282
|
+
throw new CompileError(`Can't convert ${expr.type} to ${type2}`);
|
|
13275
13283
|
}
|
|
13276
13284
|
function val(state, expr, type2) {
|
|
13277
13285
|
let castedExpr = expr;
|
|
@@ -13425,7 +13433,7 @@ const compileFunction = saveStack("function", (state, func) => {
|
|
|
13425
13433
|
argExprs = [argExprs];
|
|
13426
13434
|
}
|
|
13427
13435
|
if (name[0] !== "$") {
|
|
13428
|
-
throw new CompileError(`Unknown property
|
|
13436
|
+
throw new CompileError(`Unknown property "${name}." Did you mean to call a function? Try prefixing it with $`);
|
|
13429
13437
|
}
|
|
13430
13438
|
let args = argExprs;
|
|
13431
13439
|
if (name !== "$condition") {
|
|
@@ -13687,7 +13695,7 @@ function expandStar(state, expr) {
|
|
|
13687
13695
|
}
|
|
13688
13696
|
const table = state.schema[pathInfo.tableName];
|
|
13689
13697
|
if (table == null) {
|
|
13690
|
-
throw new Error(`Table
|
|
13698
|
+
throw new Error(`Table "${pathInfo.tableName}" does not exist`);
|
|
13691
13699
|
}
|
|
13692
13700
|
return Object.keys(table).map((field) => path2 ? `${path2}.${field}` : field);
|
|
13693
13701
|
}
|
|
@@ -13712,7 +13720,7 @@ const compileSelect = saveStack("select", (state, exprs, isAggregate, orders) =>
|
|
|
13712
13720
|
const [name, value] = Object.entries(expr)[0];
|
|
13713
13721
|
if (name[0] === "$") {
|
|
13714
13722
|
state.compileStack.push({ type: "value", value: expr });
|
|
13715
|
-
throw new CompileError(`Invalid field
|
|
13723
|
+
throw new CompileError(`Invalid field "${name}", are you trying to select a function? You need to name the expression`);
|
|
13716
13724
|
}
|
|
13717
13725
|
if (typeof value === "string") {
|
|
13718
13726
|
const compiled2 = compileExpr(state, "$" + value);
|
|
@@ -14054,7 +14062,7 @@ function convertInputType(value, type2) {
|
|
|
14054
14062
|
return value;
|
|
14055
14063
|
}
|
|
14056
14064
|
else {
|
|
14057
|
-
throw new Error("Can
|
|
14065
|
+
throw new Error("Can't convert to integer: " + JSON.stringify(value));
|
|
14058
14066
|
}
|
|
14059
14067
|
case "json":
|
|
14060
14068
|
return JSON.stringify(value);
|
|
@@ -14091,7 +14099,7 @@ function convertOutputType(value, type2) {
|
|
|
14091
14099
|
function conform(schema2, schemaConfig2, table, obj, { skipNull = false } = {}) {
|
|
14092
14100
|
const tableSchema = schema2[table];
|
|
14093
14101
|
if (tableSchema == null) {
|
|
14094
|
-
throw new Error(`Table
|
|
14102
|
+
throw new Error(`Table "${table}" does not exist`);
|
|
14095
14103
|
}
|
|
14096
14104
|
const views = schemaConfig2.views || {};
|
|
14097
14105
|
const fieldRef = (field) => {
|
|
@@ -14106,10 +14114,10 @@ function conform(schema2, schemaConfig2, table, obj, { skipNull = false } = {})
|
|
|
14106
14114
|
}
|
|
14107
14115
|
const fieldDesc = tableSchema[field];
|
|
14108
14116
|
if (fieldDesc == null) {
|
|
14109
|
-
throw new Error(`Field
|
|
14117
|
+
throw new Error(`Field "${field}" does not exist on table ${table}: ${JSON.stringify(obj)}`);
|
|
14110
14118
|
}
|
|
14111
14119
|
if (isRequired(field, fieldDesc) && obj[field] == null) {
|
|
14112
|
-
throw new Error(
|
|
14120
|
+
throw new Error(`"${field}" is required for table "${table}": ${JSON.stringify(obj)}`);
|
|
14113
14121
|
}
|
|
14114
14122
|
if (skipNull && obj[field] == null) {
|
|
14115
14123
|
return null;
|
|
@@ -14121,7 +14129,7 @@ function convertForInsert(schema2, schemaConfig2, table, rawObj) {
|
|
|
14121
14129
|
const obj = { ...rawObj };
|
|
14122
14130
|
const tableSchema = schema2[table];
|
|
14123
14131
|
if (tableSchema == null) {
|
|
14124
|
-
throw new Error(`Error inserting: table
|
|
14132
|
+
throw new Error(`Error inserting: table "${table}" does not exist`);
|
|
14125
14133
|
}
|
|
14126
14134
|
Object.keys(tableSchema).forEach((field) => {
|
|
14127
14135
|
const fieldDesc = tableSchema[field];
|
|
@@ -14130,7 +14138,7 @@ function convertForInsert(schema2, schemaConfig2, table, rawObj) {
|
|
|
14130
14138
|
obj[field] = typeof fieldDesc.default === "function" ? fieldDesc.default() : fieldDesc.default;
|
|
14131
14139
|
}
|
|
14132
14140
|
else if (isRequired(field, fieldDesc)) {
|
|
14133
|
-
throw new Error(
|
|
14141
|
+
throw new Error(`"${field}" is required for table "${table}": ${JSON.stringify(obj)}`);
|
|
14134
14142
|
}
|
|
14135
14143
|
}
|
|
14136
14144
|
});
|
|
@@ -14140,14 +14148,14 @@ function convertForUpdate(schema2, schemaConfig2, table, rawObj) {
|
|
|
14140
14148
|
const obj = { ...rawObj };
|
|
14141
14149
|
const tableSchema = schema2[table];
|
|
14142
14150
|
if (tableSchema == null) {
|
|
14143
|
-
throw new Error(`Error updating: table
|
|
14151
|
+
throw new Error(`Error updating: table "${table}" does not exist`);
|
|
14144
14152
|
}
|
|
14145
14153
|
return conform(schema2, schemaConfig2, table, obj);
|
|
14146
14154
|
}
|
|
14147
14155
|
function convertFromSelect(schema2, schemaConfig2, table, obj) {
|
|
14148
14156
|
const tableSchema = schema2[table];
|
|
14149
14157
|
if (tableSchema == null) {
|
|
14150
|
-
throw new Error(`Table
|
|
14158
|
+
throw new Error(`Table "${table}" does not exist`);
|
|
14151
14159
|
}
|
|
14152
14160
|
const fields = Object.keys(tableSchema);
|
|
14153
14161
|
const result = {};
|
|
@@ -14565,7 +14573,7 @@ function execTransactions(compilerState, queryState, sqlPieces, params, outputTy
|
|
|
14565
14573
|
const tableOptions = queryState.tableOptions || {};
|
|
14566
14574
|
const splitType = tableOptions.splits ? tableOptions.splits : "inline";
|
|
14567
14575
|
if (!isValidSplitsOption(splitType)) {
|
|
14568
|
-
throw new Error(`Invalid
|
|
14576
|
+
throw new Error(`Invalid "splits" option for transactions: "${splitType}"`);
|
|
14569
14577
|
}
|
|
14570
14578
|
if (splitType === "all" || splitType === "inline" || splitType === "none") {
|
|
14571
14579
|
return execTransactionsBasic(compilerState, queryState, sqlPieces, params, splitType, outputTypes);
|
|
@@ -14690,7 +14698,7 @@ async function execCategoryGroups(compilerState, queryState, sqlPieces, params,
|
|
|
14690
14698
|
const tableOptions = queryState.tableOptions || {};
|
|
14691
14699
|
const categoriesOption = tableOptions.categories ? tableOptions.categories : "all";
|
|
14692
14700
|
if (!isValidCategoriesOption(categoriesOption)) {
|
|
14693
|
-
throw new Error(`Invalid
|
|
14701
|
+
throw new Error(`Invalid "categories" option for category_groups: "${categoriesOption}"`);
|
|
14694
14702
|
}
|
|
14695
14703
|
if (categoriesOption !== "none") {
|
|
14696
14704
|
return execCategoryGroupsWithCategories(compilerState, queryState, sqlPieces, params, categoriesOption, outputTypes);
|
|
@@ -15384,6 +15392,10 @@ async function loadSpreadsheet(db2, onSheetChange2) {
|
|
|
15384
15392
|
else {
|
|
15385
15393
|
sheet = new Spreadsheet();
|
|
15386
15394
|
}
|
|
15395
|
+
captureBreadcrumb({
|
|
15396
|
+
message: "loading spreadsheet",
|
|
15397
|
+
category: "server"
|
|
15398
|
+
});
|
|
15387
15399
|
globalSheet = sheet;
|
|
15388
15400
|
globalOnChange = onSheetChange2;
|
|
15389
15401
|
if (onSheetChange2) {
|
|
@@ -15401,6 +15413,10 @@ async function loadSpreadsheet(db2, onSheetChange2) {
|
|
|
15401
15413
|
logger.log("Loading fresh spreadsheet");
|
|
15402
15414
|
await loadUserBudgets(db2);
|
|
15403
15415
|
}
|
|
15416
|
+
captureBreadcrumb({
|
|
15417
|
+
message: "loaded spreadsheet",
|
|
15418
|
+
category: "server"
|
|
15419
|
+
});
|
|
15404
15420
|
return sheet;
|
|
15405
15421
|
}
|
|
15406
15422
|
function unloadSpreadsheet() {
|
|
@@ -55963,6 +55979,7 @@ function withMutatorContext(context, func) {
|
|
|
55963
55979
|
function getMutatorContext() {
|
|
55964
55980
|
if (currentContext == null) {
|
|
55965
55981
|
captureBreadcrumb({
|
|
55982
|
+
category: "server",
|
|
55966
55983
|
message: "Recent methods: " + _latestHandlerNames.join(", ")
|
|
55967
55984
|
});
|
|
55968
55985
|
return {};
|
|
@@ -60536,7 +60553,7 @@ async function getCategoriesGrouped(ids) {
|
|
|
60536
60553
|
async function insertCategoryGroup(group) {
|
|
60537
60554
|
const existingGroup = await first$2(`SELECT id, name, hidden FROM category_groups WHERE UPPER(name) = ? and tombstone = 0 LIMIT 1`, [group.name.toUpperCase()]);
|
|
60538
60555
|
if (existingGroup) {
|
|
60539
|
-
throw new Error(`A ${existingGroup.hidden ? "hidden " : ""}
|
|
60556
|
+
throw new Error(`A ${existingGroup.hidden ? "hidden " : ""}'${existingGroup.name}' category group already exists.`);
|
|
60540
60557
|
}
|
|
60541
60558
|
const lastGroup = await first$2(`
|
|
60542
60559
|
SELECT sort_order FROM category_groups WHERE tombstone = 0 ORDER BY sort_order DESC, id DESC LIMIT 1
|
|
@@ -60572,7 +60589,7 @@ async function insertCategory(category, { atEnd } = { atEnd: void 0 }) {
|
|
|
60572
60589
|
await batchMessages(async () => {
|
|
60573
60590
|
const existingCatInGroup = await first$2(`SELECT id FROM categories WHERE cat_group = ? and UPPER(name) = ? and tombstone = 0 LIMIT 1`, [category.cat_group, category.name.toUpperCase()]);
|
|
60574
60591
|
if (existingCatInGroup) {
|
|
60575
|
-
throw new Error(`Category
|
|
60592
|
+
throw new Error(`Category '${category.name}' already exists in group '${category.cat_group}'`);
|
|
60576
60593
|
}
|
|
60577
60594
|
if (atEnd) {
|
|
60578
60595
|
const lastCat = await first$2(`
|
|
@@ -105994,7 +106011,7 @@ class Action {
|
|
|
105994
106011
|
case "number": {
|
|
105995
106012
|
const numValue = typeof result === "number" ? result : parseFloat(String(result));
|
|
105996
106013
|
if (isNaN(numValue)) {
|
|
105997
|
-
const error = `Formula for
|
|
106014
|
+
const error = `Formula for "${this.field}" must produce a numeric value. Got: ${JSON.stringify(result)}`;
|
|
105998
106015
|
object._ruleErrors.push(error);
|
|
105999
106016
|
}
|
|
106000
106017
|
else {
|
|
@@ -106008,7 +106025,7 @@ class Action {
|
|
|
106008
106025
|
object[this.field] = format$1(parsed, "yyyy-MM-dd");
|
|
106009
106026
|
}
|
|
106010
106027
|
else {
|
|
106011
|
-
const error = `Formula for
|
|
106028
|
+
const error = `Formula for "${this.field}" must produce a valid date. Got: ${JSON.stringify(result)}`;
|
|
106012
106029
|
object._ruleErrors.push(error);
|
|
106013
106030
|
}
|
|
106014
106031
|
break;
|
|
@@ -106021,10 +106038,13 @@ class Action {
|
|
|
106021
106038
|
object[this.field] = String(result);
|
|
106022
106039
|
break;
|
|
106023
106040
|
}
|
|
106041
|
+
default: {
|
|
106042
|
+
break;
|
|
106043
|
+
}
|
|
106024
106044
|
}
|
|
106025
106045
|
}
|
|
106026
106046
|
catch (err) {
|
|
106027
|
-
const error = `Error executing formula for
|
|
106047
|
+
const error = `Error executing formula for "${this.field}": ${err instanceof Error ? err.message : String(err)}`;
|
|
106028
106048
|
object._ruleErrors.push(error);
|
|
106029
106049
|
break;
|
|
106030
106050
|
}
|
|
@@ -106046,7 +106066,7 @@ class Action {
|
|
|
106046
106066
|
object[this.field] = format$1(parsed, "yyyy-MM-dd");
|
|
106047
106067
|
}
|
|
106048
106068
|
else {
|
|
106049
|
-
logger.error(`rules: invalid date produced by template for field
|
|
106069
|
+
logger.error(`rules: invalid date produced by template for field "${this.field}":`, object[this.field]);
|
|
106050
106070
|
object[this.field] = "9999-12-31";
|
|
106051
106071
|
}
|
|
106052
106072
|
break;
|
|
@@ -106183,10 +106203,10 @@ const CONDITION_TYPES = {
|
|
|
106183
106203
|
const parsed = typeof value === "string" ? parseDateString(value) : value.frequency != null ? parseRecurDate(value) : null;
|
|
106184
106204
|
assert(parsed, "date-format", `Invalid date format (field: ${fieldName})`);
|
|
106185
106205
|
if (op === "isapprox") {
|
|
106186
|
-
assert(parsed.type === "date" || parsed.type === "recur", "date-format", `Invalid date value for
|
|
106206
|
+
assert(parsed.type === "date" || parsed.type === "recur", "date-format", `Invalid date value for "isapprox" (field: ${fieldName})`);
|
|
106187
106207
|
}
|
|
106188
106208
|
else if (op === "gt" || op === "gte" || op === "lt" || op === "lte") {
|
|
106189
|
-
assert(parsed.type === "date", "date-format", `Invalid date value for
|
|
106209
|
+
assert(parsed.type === "date", "date-format", `Invalid date value for "${op}" (field: ${fieldName})`);
|
|
106190
106210
|
}
|
|
106191
106211
|
return parsed;
|
|
106192
106212
|
}
|
|
@@ -106247,10 +106267,10 @@ const CONDITION_TYPES = {
|
|
|
106247
106267
|
const parsed = typeof value === "number" ? { type: "literal", value } : parseBetweenAmount(value);
|
|
106248
106268
|
assert(parsed != null, "not-number", `Value must be a number or between amount: ${JSON.stringify(value)} (field: ${fieldName})`);
|
|
106249
106269
|
if (op === "isbetween") {
|
|
106250
|
-
assert(parsed.type === "between", "number-format", `Invalid between value for
|
|
106270
|
+
assert(parsed.type === "between", "number-format", `Invalid between value for "${op}" (field: ${fieldName})`);
|
|
106251
106271
|
}
|
|
106252
106272
|
else {
|
|
106253
|
-
assert(parsed.type === "literal", "number-format", `Invalid number value for
|
|
106273
|
+
assert(parsed.type === "literal", "number-format", `Invalid number value for "${op}" (field: ${fieldName})`);
|
|
106254
106274
|
}
|
|
106255
106275
|
return parsed;
|
|
106256
106276
|
}
|
|
@@ -107565,9 +107585,7 @@ async function idsWithChildren(ids) {
|
|
|
107565
107585
|
return [...set];
|
|
107566
107586
|
}
|
|
107567
107587
|
async function getTransactionsByIds(ids) {
|
|
107568
|
-
return incrFetch((query, params) => selectWithSchema("transactions", query, params), ids,
|
|
107569
|
-
// eslint-disable-next-line actual/typography
|
|
107570
|
-
(id2) => `id = '${id2}'`, (where) => `SELECT * FROM v_transactions_internal WHERE ${where}`);
|
|
107588
|
+
return incrFetch((query, params) => selectWithSchema("transactions", query, params), ids, (id2) => `id = '${id2}'`, (where) => `SELECT * FROM v_transactions_internal WHERE ${where}`);
|
|
107571
107589
|
}
|
|
107572
107590
|
async function batchUpdateTransactions({ added, deleted, updated, learnCategories = false, detectOrphanPayees = true, runTransfers = true }) {
|
|
107573
107591
|
let addedIds = [];
|
|
@@ -108873,7 +108891,7 @@ function handleSyncError(err, acct) {
|
|
|
108873
108891
|
const syncError = {
|
|
108874
108892
|
type: "SyncError",
|
|
108875
108893
|
accountId: acct.id,
|
|
108876
|
-
message:
|
|
108894
|
+
message: 'Failed syncing account "' + acct.name + '."',
|
|
108877
108895
|
category: error.category,
|
|
108878
108896
|
code: error.code
|
|
108879
108897
|
};
|
|
@@ -108888,7 +108906,7 @@ function handleSyncError(err, acct) {
|
|
|
108888
108906
|
if (err instanceof PostError && err.reason !== "internal") {
|
|
108889
108907
|
return {
|
|
108890
108908
|
accountId: acct.id,
|
|
108891
|
-
message: err.reason ? err.reason : `Account
|
|
108909
|
+
message: err.reason ? err.reason : `Account "${acct.name}" is not linked properly. Please link it again.`
|
|
108892
108910
|
};
|
|
108893
108911
|
}
|
|
108894
108912
|
return {
|
|
@@ -108926,7 +108944,7 @@ async function accountsBankSync({ ids = [] }) {
|
|
|
108926
108944
|
errors2.push(handleSyncError(error, acct));
|
|
108927
108945
|
captureException({
|
|
108928
108946
|
...error,
|
|
108929
|
-
message:
|
|
108947
|
+
message: 'Failed syncing account "' + acct.name + '."'
|
|
108930
108948
|
});
|
|
108931
108949
|
}
|
|
108932
108950
|
finally {
|
|
@@ -108967,7 +108985,7 @@ async function simpleFinBatchSync({ ids = [] }) {
|
|
|
108967
108985
|
if (syncResponse.res.error_code) {
|
|
108968
108986
|
errors2.push(handleSyncError({
|
|
108969
108987
|
type: "BankSyncError",
|
|
108970
|
-
reason:
|
|
108988
|
+
reason: 'Failed syncing account "' + account.name + '."',
|
|
108971
108989
|
category: syncResponse.res.error_type,
|
|
108972
108990
|
code: syncResponse.res.error_code
|
|
108973
108991
|
}, account));
|
|
@@ -109294,10 +109312,10 @@ function getSyncError(error, id2) {
|
|
|
109294
109312
|
return t("This budget cannot be loaded with this version of the app.");
|
|
109295
109313
|
}
|
|
109296
109314
|
else if (error === "budget-not-found") {
|
|
109297
|
-
return t(
|
|
109315
|
+
return t('Budget "{{id}}" not found. Check the ID of your budget in the Advanced section of the settings page.', { id: id2 });
|
|
109298
109316
|
}
|
|
109299
109317
|
else {
|
|
109300
|
-
return t(
|
|
109318
|
+
return t('We had an unknown problem opening "{{id}}".', { id: id2 });
|
|
109301
109319
|
}
|
|
109302
109320
|
}
|
|
109303
109321
|
function getBankSyncError(error) {
|
|
@@ -109480,10 +109498,10 @@ async function validateExpenseCategory(debug, id2) {
|
|
|
109480
109498
|
}
|
|
109481
109499
|
const row = await first$2("SELECT is_income FROM categories WHERE id = ?", [id2]);
|
|
109482
109500
|
if (!row) {
|
|
109483
|
-
throw APIError(`${debug}: category
|
|
109501
|
+
throw APIError(`${debug}: category "${id2}" does not exist`);
|
|
109484
109502
|
}
|
|
109485
109503
|
if (row.is_income !== 0) {
|
|
109486
|
-
throw APIError(`${debug}: category
|
|
109504
|
+
throw APIError(`${debug}: category "${id2}" is not an expense category`);
|
|
109487
109505
|
}
|
|
109488
109506
|
}
|
|
109489
109507
|
function checkFileOpen() {
|
|
@@ -109544,7 +109562,7 @@ handlers["api/download-budget"] = async function ({ syncId, password }) {
|
|
|
109544
109562
|
}
|
|
109545
109563
|
const file = files.find((f2) => f2.groupId === syncId);
|
|
109546
109564
|
if (!file) {
|
|
109547
|
-
throw new Error(`Budget
|
|
109565
|
+
throw new Error(`Budget "${syncId}" not found. Check the sync id of your budget in the Advanced section of the settings page.`);
|
|
109548
109566
|
}
|
|
109549
109567
|
remoteBudget = file;
|
|
109550
109568
|
}
|
|
@@ -115660,6 +115678,7 @@ async function getUpcomingDates({ config: config2, count }) {
|
|
|
115660
115678
|
return schedule.occurrences({ start: startOfDay(/* @__PURE__ */ new Date()), take: count }).toArray().map((date) => config2.skipWeekend ? getDateWithSkippedWeekend(date.date, config2.weekendSolveMode) : date.date).map((date) => dayFromDate(date));
|
|
115661
115679
|
}
|
|
115662
115680
|
catch (err) {
|
|
115681
|
+
captureBreadcrumb(config2);
|
|
115663
115682
|
throw err;
|
|
115664
115683
|
}
|
|
115665
115684
|
}
|
|
@@ -118616,7 +118635,7 @@ Error: ${template.error}`);
|
|
|
118616
118635
|
}
|
|
118617
118636
|
}
|
|
118618
118637
|
else if (template.type === "schedule" && !scheduleNames.includes(template.name)) {
|
|
118619
|
-
errors2.push(`${name}: Schedule
|
|
118638
|
+
errors2.push(`${name}: Schedule "${template.name}" does not exist`);
|
|
118620
118639
|
}
|
|
118621
118640
|
});
|
|
118622
118641
|
});
|
|
@@ -119206,6 +119225,8 @@ class CategoryTemplateContext {
|
|
|
119206
119225
|
case "year":
|
|
119207
119226
|
dateShiftFunction = (date2, numPeriods2) => addMonths(date2, numPeriods2 * 12);
|
|
119208
119227
|
break;
|
|
119228
|
+
default:
|
|
119229
|
+
throw new Error(`Unrecognized periodic period: ${period}`);
|
|
119209
119230
|
}
|
|
119210
119231
|
while (templateContext.month > date) {
|
|
119211
119232
|
date = dateShiftFunction(date, numPeriods);
|
|
@@ -121001,6 +121022,8 @@ async function importTransactions(data, entityIdMap) {
|
|
|
121001
121022
|
case 1:
|
|
121002
121023
|
subtransactionIdx++;
|
|
121003
121024
|
break;
|
|
121025
|
+
default:
|
|
121026
|
+
throw new Error(`Unrecognized orphan transfer comparator result`);
|
|
121004
121027
|
}
|
|
121005
121028
|
} while (transactionIdx < transactions.length && subtransactionIdx < subtransactions.length);
|
|
121006
121029
|
}
|
|
@@ -121841,7 +121864,7 @@ const DEFAULT_DASHBOARD_STATE = [
|
|
|
121841
121864
|
x: 8,
|
|
121842
121865
|
y: 10,
|
|
121843
121866
|
meta: {
|
|
121844
|
-
content:
|
|
121867
|
+
content: '## Dashboard Tips\n\nYou can add new widgets or edit existing widgets by using the buttons at the top of the page. Choose a widget type and customize it to fit your needs.\n\n**Moving cards:** Drag any card by its header to reposition it.\n\n**Deleting cards:** Click the three-dot menu on any card and select "Remove".'
|
|
121845
121868
|
}
|
|
121846
121869
|
}
|
|
121847
121870
|
];
|
|
@@ -122079,7 +122102,7 @@ async function validateBudgetName(name) {
|
|
|
122079
122102
|
message2 = "Budget name is too long (max length 100)";
|
|
122080
122103
|
}
|
|
122081
122104
|
if (uniqueName !== trimmedName) {
|
|
122082
|
-
message2 =
|
|
122105
|
+
message2 = `"${name}" already exists, try "${uniqueName}" instead`;
|
|
122083
122106
|
}
|
|
122084
122107
|
return message2 ? { valid: false, message: message2 } : { valid: true };
|
|
122085
122108
|
}
|
|
@@ -122388,6 +122411,7 @@ async function createDemoBudget() {
|
|
|
122388
122411
|
});
|
|
122389
122412
|
}
|
|
122390
122413
|
async function closeBudget() {
|
|
122414
|
+
captureBreadcrumb({ message: "Closing budget" });
|
|
122391
122415
|
await waitOnSpreadsheet();
|
|
122392
122416
|
unloadSpreadsheet();
|
|
122393
122417
|
clearFullSyncTimeout();
|
|
@@ -122555,6 +122579,7 @@ async function _loadBudget(id2) {
|
|
|
122555
122579
|
captureException(new Error("`getBudgetDir` failed in `loadBudget`: " + e.message));
|
|
122556
122580
|
return { error: "budget-not-found" };
|
|
122557
122581
|
}
|
|
122582
|
+
captureBreadcrumb({ message: "Loading budget " + dir });
|
|
122558
122583
|
if (!await exists(dir)) {
|
|
122559
122584
|
captureException(new Error("budget directory does not exist"));
|
|
122560
122585
|
return { error: "budget-not-found" };
|
|
@@ -122564,6 +122589,7 @@ async function _loadBudget(id2) {
|
|
|
122564
122589
|
await openDatabase(id2);
|
|
122565
122590
|
}
|
|
122566
122591
|
catch (e) {
|
|
122592
|
+
captureBreadcrumb({ message: "Error loading budget " + id2 });
|
|
122567
122593
|
captureException(e);
|
|
122568
122594
|
await closeBudget();
|
|
122569
122595
|
return { error: "opening-budget" };
|
|
@@ -133823,7 +133849,6 @@ async function parseCSV(filepath, options) {
|
|
|
133823
133849
|
columns: options?.hasHeaderRow,
|
|
133824
133850
|
bom: true,
|
|
133825
133851
|
delimiter: options?.delimiter || ",",
|
|
133826
|
-
// eslint-disable-next-line actual/typography
|
|
133827
133852
|
quote: '"',
|
|
133828
133853
|
trim: true,
|
|
133829
133854
|
relax_column_count: true,
|
|
@@ -133848,7 +133873,7 @@ async function parseQIF(filepath, options = {}) {
|
|
|
133848
133873
|
}
|
|
133849
133874
|
catch (err) {
|
|
133850
133875
|
errors2.push({
|
|
133851
|
-
message: "Failed parsing: doesn
|
|
133876
|
+
message: "Failed parsing: doesn't look like a valid QIF file.",
|
|
133852
133877
|
internal: err.stack
|
|
133853
133878
|
});
|
|
133854
133879
|
return { errors: errors2, transactions: [] };
|
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.default = runMigration;
|
|
4
4
|
const uuid_1 = require("uuid");
|
|
5
5
|
const dashboard_1 = require("../src/shared/dashboard");
|
|
6
|
-
/* eslint-disable actual/typography */
|
|
7
6
|
async function runMigration(db) {
|
|
8
7
|
db.transaction(() => {
|
|
9
8
|
const reports = db.runQuery('SELECT id FROM custom_reports WHERE tombstone = 0 ORDER BY name COLLATE NOCASE ASC', [], true);
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actual-app/api",
|
|
3
|
-
"version": "26.1.0-nightly.
|
|
3
|
+
"version": "26.1.0-nightly.20251222",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "An API for Actual",
|
|
6
6
|
"engines": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"build:migrations": "cp migrations/*.sql dist/migrations",
|
|
20
20
|
"build:default-db": "cp default-db.sqlite dist/",
|
|
21
21
|
"build": "yarn run clean && yarn run build:app && yarn run build:node && yarn run build:migrations && yarn run build:default-db",
|
|
22
|
-
"test": "yarn run build:app && yarn run build:crdt && vitest --run",
|
|
22
|
+
"test": "yarn run clean && yarn run build:app && yarn run build:crdt && vitest --run",
|
|
23
23
|
"clean": "rm -rf dist @types"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actual-app/api",
|
|
3
|
-
"version": "26.1.0-nightly.
|
|
3
|
+
"version": "26.1.0-nightly.20251222",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "An API for Actual",
|
|
6
6
|
"engines": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"build:migrations": "cp migrations/*.sql dist/migrations",
|
|
20
20
|
"build:default-db": "cp default-db.sqlite dist/",
|
|
21
21
|
"build": "yarn run clean && yarn run build:app && yarn run build:node && yarn run build:migrations && yarn run build:default-db",
|
|
22
|
-
"test": "yarn run build:app && yarn run build:crdt && vitest --run",
|
|
22
|
+
"test": "yarn run clean && yarn run build:app && yarn run build:crdt && vitest --run",
|
|
23
23
|
"clean": "rm -rf dist @types"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|