@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.
@@ -1,2 +1,2 @@
1
1
  export declare const captureException: (exc: Error) => void;
2
- export declare const captureBreadcrumb: (_crumb: unknown) => void;
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: Intl.NumberFormat;
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?: Intl.NumberFormat, decimalPlaces?: number): string;
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;
@@ -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 “${id2}”. Check the id of your budget in the Advanced section of the settings page.`);
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: new Intl.NumberFormat(locale, fractionDigitsOptions)
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: cant safely perform arithmetic with number: " + value);
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 (_crumb) {
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 “${tableName} does not exist in the schema`);
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 “${field} does not exist in table “${tableName}”`);
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}: “${field}”`);
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 “${param.paramName} cant convert to ${type2} (already inferred as ${existingType})`);
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 doesnt make sense");
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(`Cant cast ${expr.type} to date`);
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(`Cant cast ${expr.type} to date-month`);
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(`Cant cast ${expr.type} to date-year`);
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(`Cant convert ${expr.type} to ${type2}`);
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 “${name}.” Did you mean to call a function? Try prefixing it with $`);
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 “${pathInfo.tableName} does not exist`);
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 “${name}”, are you trying to select a function? You need to name the expression`);
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("Cant convert to integer: " + JSON.stringify(value));
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 “${table} does not exist`);
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 “${field} does not exist on table ${table}: ${JSON.stringify(obj)}`);
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(`“${field} is required for table “${table}”: ${JSON.stringify(obj)}`);
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 “${table} does not exist`);
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(`“${field} is required for table “${table}”: ${JSON.stringify(obj)}`);
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 “${table} does not exist`);
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 “${table} does not exist`);
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 splits option for transactions: “${splitType}”`);
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 categories option for category_groups: “${categoriesOption}”`);
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 " : ""}’${existingGroup.name} category group already exists.`);
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 ‘${category.name} already exists in group ‘${category.cat_group}’`);
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 “${this.field} must produce a numeric value. Got: ${JSON.stringify(result)}`;
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 “${this.field} must produce a valid date. Got: ${JSON.stringify(result)}`;
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 “${this.field}”: ${err instanceof Error ? err.message : String(err)}`;
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 “${this.field}”:`, object[this.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 isapprox (field: ${fieldName})`);
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 “${op} (field: ${fieldName})`);
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 “${op} (field: ${fieldName})`);
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 “${op} (field: ${fieldName})`);
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: "Failed syncing account " + acct.name + ".”",
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 “${acct.name} is not linked properly. Please link it again.`
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: "Failed syncing account " + acct.name + ".”"
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: "Failed syncing account " + account.name + ".”",
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("Budget {{id}} not found. Check the ID of your budget in the Advanced section of the settings page.", { id: id2 });
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("We had an unknown problem opening {{id}}”.", { id: id2 });
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 “${id2} does not exist`);
109501
+ throw APIError(`${debug}: category "${id2}" does not exist`);
109484
109502
  }
109485
109503
  if (row.is_income !== 0) {
109486
- throw APIError(`${debug}: category “${id2} is not an expense 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 “${syncId} not found. Check the sync id of your budget in the Advanced section of the settings page.`);
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 “${template.name} does not exist`);
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: "## 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”."
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 = `“${name} already exists, try “${uniqueName} instead`;
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: doesnt look like a valid QIF file.",
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.20251220",
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.20251220",
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": {