Plugins-core

This commit is contained in:
lelemm
2025-09-24 08:52:46 -03:00
parent cf8a4b6e6a
commit 2ee1a61689
57 changed files with 2714 additions and 618 deletions

View File

@@ -54,6 +54,22 @@ jobs:
name: actual-crdt
path: packages/crdt/actual-crdt.tgz
plugins-core:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up environment
uses: ./.github/actions/setup
- name: Build Plugins Core
run: yarn workspace @actual-app/plugins-core build
- name: Create package tgz
run: yarn workspace @actual-app/plugins-core pack --filename actual-plugins-core.tgz
- name: Upload Build
uses: actions/upload-artifact@v4
with:
name: actual-plugins-core
path: packages/plugins-core/actual-plugins-core.tgz
web:
runs-on: ubuntu-latest
steps:

View File

@@ -101,8 +101,14 @@ export default defineConfig(
'packages/loot-core/**/proto/*',
'packages/sync-server/build/',
'packages/plugins-service/dist/',
'packages/plugins-core/build/',
'packages/plugins-core/node_modules/',
'.yarn/*',
'.github/*',
'packages/shared-types/dist/',
'packages/shared-types/build/',
'packages/query/dist/',
'packages/query/build/',
],
},
{

View File

@@ -38,6 +38,7 @@
"build:desktop": "./bin/package-electron",
"build:plugins-service": "yarn workspace plugins-service build",
"build:api": "yarn workspace @actual-app/api build",
"build:plugins-core": "yarn workspace @actual-app/plugins-core build",
"generate:i18n": "yarn workspace @actual-app/web generate:i18n",
"generate:release-notes": "ts-node ./bin/release-note-generator.ts",
"test": "lage test --continue",

View File

@@ -11,7 +11,7 @@
"outDir": "dist",
"declarationDir": "@types",
"paths": {
"loot-core/*": ["./@types/loot-core/src/*"]
"loot-core/*": ["./@types/loot-core/loot-core/src/*"]
}
},
"include": ["."],

View File

@@ -19,6 +19,7 @@
"vitest": "^3.2.4"
},
"exports": {
".": "./src/index.ts",
"./hooks/*": "./src/hooks/*.ts",
"./icons/logo": "./src/icons/logo/index.ts",
"./icons/v0": "./src/icons/v0/index.ts",
@@ -49,7 +50,8 @@
"./toggle": "./src/Toggle.tsx",
"./tooltip": "./src/Tooltip.tsx",
"./view": "./src/View.tsx",
"./color-picker": "./src/ColorPicker.tsx"
"./color-picker": "./src/ColorPicker.tsx",
"./props/*": "./src/props/*.ts"
},
"scripts": {
"generate:icons": "rm src/icons/*/*.tsx; cd src/icons && svgr --template template.ts --index-template index-template.ts --typescript --expand-props start -d . .",

View File

@@ -0,0 +1,34 @@
// Components
export { AlignedText } from './AlignedText';
export { Block } from './Block';
export { Button, ButtonWithLoading } from './Button';
export { Card } from './Card';
export { ColorPicker } from './ColorPicker';
export { FormError } from './FormError';
export { InitialFocus } from './InitialFocus';
export { InlineField } from './InlineField';
export { Input } from './Input';
export { Label } from './Label';
export { Menu } from './Menu';
export { Paragraph } from './Paragraph';
export { Popover } from './Popover';
export { Select } from './Select';
export { SpaceBetween } from './SpaceBetween';
export { Stack } from './Stack';
export { Text } from './Text';
export { TextOneLine } from './TextOneLine';
export { Toggle } from './Toggle';
export { Tooltip } from './Tooltip';
export { View } from './View';
export * from './icons/v2';
export * from './icons/logo';
export { AnimatedLoading } from './icons/AnimatedLoading';
export { SvgLoading as Loading } from './icons/Loading';
// Styles, Theme, Tokens
export * from './styles';
export * from './theme';
export * from './tokens';
export type * from './props/modalProps';

View File

@@ -0,0 +1,11 @@
import { type CSSProperties } from 'react';
export type BasicModalProps = {
isLoading?: boolean;
noAnimation?: boolean;
style?: CSSProperties;
onClose?: () => void;
containerProps?: {
style?: CSSProperties;
};
};

View File

@@ -1,12 +1,12 @@
import { q } from 'loot-core/shared/query';
import type { AccountEntity, CategoryEntity } from 'loot-core/types/models';
import {
parametrizedField,
type SheetFields,
type Binding,
type SheetNames,
} from '.';
} from '@actual-app/plugins-core';
import { q } from 'loot-core/shared/query';
import type { AccountEntity, CategoryEntity } from 'loot-core/types/models';
import { uncategorizedTransactions } from '@desktop-client/queries';

View File

@@ -1,119 +1,8 @@
import { type Query } from 'loot-core/shared/query';
export type Spreadsheets = {
account: {
// Common fields
'uncategorized-amount': number;
'uncategorized-balance': number;
// Account fields
balance: number;
[key: `balance-${string}-cleared`]: number | null;
'accounts-balance': number;
'onbudget-accounts-balance': number;
'offbudget-accounts-balance': number;
'closed-accounts-balance': number;
balanceCleared: number;
balanceUncleared: number;
lastReconciled: string | null;
};
category: {
// Common fields
'uncategorized-amount': number;
'uncategorized-balance': number;
balance: number;
balanceCleared: number;
balanceUncleared: number;
};
'envelope-budget': {
// Common fields
'uncategorized-amount': number;
'uncategorized-balance': number;
// Envelope budget fields
'available-funds': number;
'last-month-overspent': number;
buffered: number;
'buffered-auto': number;
'buffered-selected': number;
'to-budget': number | null;
'from-last-month': number;
'total-budgeted': number;
'total-income': number;
'total-spent': number;
'total-leftover': number;
'group-sum-amount': number;
'group-budget': number;
'group-leftover': number;
budget: number;
'sum-amount': number;
leftover: number;
carryover: number;
goal: number;
'long-goal': number;
};
'tracking-budget': {
// Common fields
'uncategorized-amount': number;
'uncategorized-balance': number;
// Tracking budget fields
'total-budgeted': number;
'total-budget-income': number;
'total-saved': number;
'total-income': number;
'total-spent': number;
'real-saved': number;
'total-leftover': number;
'group-sum-amount': number;
'group-budget': number;
'group-leftover': number;
budget: number;
'sum-amount': number;
leftover: number;
carryover: number;
goal: number;
'long-goal': number;
};
[`balance`]: {
// Common fields
'uncategorized-amount': number;
'uncategorized-balance': number;
// Balance fields
[key: `balance-query-${string}`]: number;
[key: `selected-transactions-${string}`]: Array<{ id: string }>;
[key: `selected-balance-${string}`]: number;
};
};
export type SheetNames = keyof Spreadsheets & string;
export type SheetFields<SheetName extends SheetNames> =
keyof Spreadsheets[SheetName] & string;
export type BindingObject<
SheetName extends SheetNames,
SheetFieldName extends SheetFields<SheetName>,
> = {
name: SheetFieldName;
value?: Spreadsheets[SheetName][SheetFieldName] | undefined;
query?: Query | undefined;
};
export type Binding<
SheetName extends SheetNames,
SheetFieldName extends SheetFields<SheetName>,
> =
| SheetFieldName
| {
name: SheetFieldName;
value?: Spreadsheets[SheetName][SheetFieldName] | undefined;
query?: Query | undefined;
};
export const parametrizedField =
<SheetName extends SheetNames>() =>
<SheetFieldName extends SheetFields<SheetName>>(field: SheetFieldName) =>
(id?: string): SheetFieldName =>
`${field}-${id}` as SheetFieldName;
export type {
Spreadsheets,
SheetNames,
SheetFields,
BindingObject,
Binding,
} from '@actual-app/shared-types/spreadsheet';
export { parametrizedField } from '@actual-app/shared-types/spreadsheet';

View File

@@ -1,176 +1,7 @@
import { WithRequired } from '../types/util';
type ObjectExpression = {
[key: string]: ObjectExpression | unknown;
};
export type QueryState = {
get table(): string;
get tableOptions(): Readonly<Record<string, unknown>>;
get filterExpressions(): ReadonlyArray<ObjectExpression>;
get selectExpressions(): ReadonlyArray<ObjectExpression | string | '*'>;
get groupExpressions(): ReadonlyArray<ObjectExpression | string>;
get orderExpressions(): ReadonlyArray<ObjectExpression | string>;
get calculation(): boolean;
get rawMode(): boolean;
get withDead(): boolean;
get validateRefs(): boolean;
get limit(): number | null;
get offset(): number | null;
};
export class Query {
state: QueryState;
constructor(state: WithRequired<Partial<QueryState>, 'table'>) {
this.state = {
tableOptions: state.tableOptions || {},
filterExpressions: state.filterExpressions || [],
selectExpressions: state.selectExpressions || [],
groupExpressions: state.groupExpressions || [],
orderExpressions: state.orderExpressions || [],
calculation: false,
rawMode: false,
withDead: false,
validateRefs: true,
limit: null,
offset: null,
...state,
};
}
filter(expr: ObjectExpression) {
return new Query({
...this.state,
filterExpressions: [...this.state.filterExpressions, expr],
});
}
unfilter(exprs?: Array<keyof ObjectExpression>) {
// Remove all filters if no arguments are passed
if (!exprs) {
return new Query({
...this.state,
filterExpressions: [],
});
}
const exprSet = new Set(exprs);
return new Query({
...this.state,
filterExpressions: this.state.filterExpressions.filter(
expr => !exprSet.has(Object.keys(expr)[0]),
),
});
}
select(
exprs:
| Array<ObjectExpression | string>
| ObjectExpression
| string
| '*'
| ['*'] = [],
) {
if (!Array.isArray(exprs)) {
exprs = [exprs];
}
return new Query({
...this.state,
selectExpressions: exprs,
calculation: false,
});
}
calculate(expr: ObjectExpression | string) {
return new Query({
...this.state,
selectExpressions: [{ result: expr }],
calculation: true,
});
}
groupBy(exprs: ObjectExpression | string | Array<ObjectExpression | string>) {
if (!Array.isArray(exprs)) {
exprs = [exprs];
}
return new Query({
...this.state,
groupExpressions: [...this.state.groupExpressions, ...exprs],
});
}
orderBy(exprs: ObjectExpression | string | Array<ObjectExpression | string>) {
if (!Array.isArray(exprs)) {
exprs = [exprs];
}
return new Query({
...this.state,
orderExpressions: [...this.state.orderExpressions, ...exprs],
});
}
limit(num: number) {
return new Query({ ...this.state, limit: num });
}
offset(num: number) {
return new Query({ ...this.state, offset: num });
}
raw() {
return new Query({ ...this.state, rawMode: true });
}
withDead() {
return new Query({ ...this.state, withDead: true });
}
withoutValidatedRefs() {
return new Query({ ...this.state, validateRefs: false });
}
options(opts: Record<string, unknown>) {
return new Query({ ...this.state, tableOptions: opts });
}
reset() {
return q(this.state.table);
}
serialize() {
return this.state;
}
serializeAsString() {
return JSON.stringify(this.serialize());
}
}
export function getPrimaryOrderBy(
query: Query,
defaultOrderBy: ObjectExpression | null,
) {
const orderExprs = query.serialize().orderExpressions;
if (orderExprs.length === 0) {
if (defaultOrderBy) {
return { order: 'asc', ...defaultOrderBy };
}
return null;
}
const firstOrder = orderExprs[0];
if (typeof firstOrder === 'string') {
return { field: firstOrder, order: 'asc' };
}
// Handle this form: { field: 'desc' }
const [field] = Object.keys(firstOrder);
return { field, order: firstOrder[field] };
}
export function q(table: QueryState['table']) {
return new Query({ table });
}
// Re-export query types and functions from @actual-app/query
export type {
ObjectExpression,
QueryState,
QueryBuilder,
} from '@actual-app/query';
export { Query, q, getPrimaryOrderBy } from '@actual-app/query';

View File

@@ -1,25 +1,5 @@
export type AccountEntity = {
id: string;
name: string;
offbudget: 0 | 1;
closed: 0 | 1;
sort_order: number;
last_reconciled: string | null;
tombstone: 0 | 1;
} & (_SyncFields<true> | _SyncFields<false>);
export type _SyncFields<T> = {
account_id: T extends true ? string : null;
bank: T extends true ? string : null;
bankName: T extends true ? string : null;
bankId: T extends true ? number : null;
mask: T extends true ? string : null; // end of bank account number
official_name: T extends true ? string : null;
balance_current: T extends true ? number : null;
balance_available: T extends true ? number : null;
balance_limit: T extends true ? number : null;
account_sync_source: T extends true ? AccountSyncSource : null;
last_sync: T extends true ? string : null;
};
export type AccountSyncSource = 'simpleFin' | 'goCardless' | 'pluggyai';
export type {
AccountEntity,
_SyncFields,
AccountSyncSource,
} from '@actual-app/shared-types/models/account';

View File

@@ -1,11 +1 @@
import { CategoryEntity } from './category';
export interface CategoryGroupEntity {
id: string;
name: string;
is_income?: boolean;
sort_order?: number;
tombstone?: boolean;
hidden?: boolean;
categories?: CategoryEntity[];
}
export type { CategoryGroupEntity } from '@actual-app/shared-types/models/category-group';

View File

@@ -1,13 +1 @@
import { CategoryGroupEntity } from './category-group';
export interface CategoryEntity {
id: string;
name: string;
is_income?: boolean;
group: CategoryGroupEntity['id'];
goal_def?: string;
template_settings?: { source: 'notes' | 'ui' };
sort_order?: number;
tombstone?: boolean;
hidden?: boolean;
}
export type { CategoryEntity } from '@actual-app/shared-types/models/category';

View File

@@ -1,10 +1 @@
import { AccountEntity } from './account';
export interface PayeeEntity {
id: string;
name: string;
transfer_acct?: AccountEntity['id'];
favorite?: boolean;
learn_categories?: boolean;
tombstone?: boolean;
}
export type { PayeeEntity } from '@actual-app/shared-types/models/payee';

View File

@@ -1,186 +1,14 @@
import { type RecurConfig, type ScheduleEntity } from './schedule';
export interface NewRuleEntity {
stage: 'pre' | null | 'post';
conditionsOp: 'or' | 'and';
conditions: RuleConditionEntity[];
actions: RuleActionEntity[];
tombstone?: boolean;
}
export interface RuleEntity extends NewRuleEntity {
id: string;
}
export type RuleConditionOp =
| 'is'
| 'isNot'
| 'oneOf'
| 'notOneOf'
| 'isapprox'
| 'isbetween'
| 'gt'
| 'gte'
| 'lt'
| 'lte'
| 'contains'
| 'doesNotContain'
| 'hasTags'
| 'and'
| 'matches'
| 'onBudget'
| 'offBudget';
export type FieldValueTypes = {
account: string;
amount: number;
category: string;
date: string | RecurConfig;
notes: string;
payee: string;
payee_name: string;
imported_payee: string;
saved: string;
transfer: boolean;
parent: boolean;
cleared: boolean;
reconciled: boolean;
};
type BaseConditionEntity<
Field extends keyof FieldValueTypes,
Op extends RuleConditionOp,
> = {
field: Field;
op: Op;
value: Op extends 'oneOf' | 'notOneOf'
? Array<FieldValueTypes[Field]>
: Op extends 'isbetween'
? { num1: number; num2: number }
: FieldValueTypes[Field];
options?: {
inflow?: boolean;
outflow?: boolean;
month?: boolean;
year?: boolean;
};
conditionsOp?: string;
type?: 'id' | 'boolean' | 'date' | 'number' | 'string';
customName?: string;
queryFilter?: Record<string, { $oneof: string[] }>;
};
export type RuleConditionEntity =
| BaseConditionEntity<
'account',
| 'is'
| 'isNot'
| 'oneOf'
| 'notOneOf'
| 'contains'
| 'doesNotContain'
| 'matches'
| 'onBudget'
| 'offBudget'
>
| BaseConditionEntity<
'category',
| 'is'
| 'isNot'
| 'oneOf'
| 'notOneOf'
| 'contains'
| 'doesNotContain'
| 'matches'
>
| BaseConditionEntity<
'amount',
'is' | 'isapprox' | 'isbetween' | 'gt' | 'gte' | 'lt' | 'lte'
>
| BaseConditionEntity<
'date',
'is' | 'isapprox' | 'isbetween' | 'gt' | 'gte' | 'lt' | 'lte'
>
| BaseConditionEntity<
'notes',
| 'is'
| 'isNot'
| 'oneOf'
| 'notOneOf'
| 'contains'
| 'doesNotContain'
| 'matches'
| 'hasTags'
>
| BaseConditionEntity<
'payee',
| 'is'
| 'isNot'
| 'oneOf'
| 'notOneOf'
| 'contains'
| 'doesNotContain'
| 'matches'
>
| BaseConditionEntity<
'imported_payee',
| 'is'
| 'isNot'
| 'oneOf'
| 'notOneOf'
| 'contains'
| 'doesNotContain'
| 'matches'
>
| BaseConditionEntity<'saved', 'is'>
| BaseConditionEntity<'cleared', 'is'>
| BaseConditionEntity<'reconciled', 'is'>;
export type RuleActionEntity =
| SetRuleActionEntity
| SetSplitAmountRuleActionEntity
| LinkScheduleRuleActionEntity
| PrependNoteRuleActionEntity
| AppendNoteRuleActionEntity
| DeleteTransactionRuleActionEntity;
export interface SetRuleActionEntity {
field: string;
op: 'set';
value: unknown;
options?: {
template?: string;
formula?: string;
splitIndex?: number;
};
type?: string;
}
export interface SetSplitAmountRuleActionEntity {
op: 'set-split-amount';
value: number;
options?: {
splitIndex?: number;
method: 'fixed-amount' | 'fixed-percent' | 'remainder';
};
}
export interface LinkScheduleRuleActionEntity {
op: 'link-schedule';
value: ScheduleEntity;
}
export interface PrependNoteRuleActionEntity {
op: 'prepend-notes';
value: string;
}
export interface AppendNoteRuleActionEntity {
op: 'append-notes';
value: string;
}
export interface DeleteTransactionRuleActionEntity {
op: 'delete-transaction';
value: string;
}
export type {
NewRuleEntity,
RuleEntity,
RuleConditionOp,
FieldValueTypes,
RuleConditionEntity,
RuleActionEntity,
SetRuleActionEntity,
SetSplitAmountRuleActionEntity,
LinkScheduleRuleActionEntity,
PrependNoteRuleActionEntity,
AppendNoteRuleActionEntity,
DeleteTransactionRuleActionEntity,
} from '@actual-app/shared-types/models/rule';

View File

@@ -1,49 +1,6 @@
import type { AccountEntity } from './account';
import type { PayeeEntity } from './payee';
import type { RuleConditionEntity, RuleEntity } from './rule';
export interface RecurPattern {
value: number;
type: 'SU' | 'MO' | 'TU' | 'WE' | 'TH' | 'FR' | 'SA' | 'day';
}
export interface RecurConfig {
frequency: 'daily' | 'weekly' | 'monthly' | 'yearly';
interval?: number;
patterns?: RecurPattern[];
skipWeekend?: boolean;
start: string;
endMode: 'never' | 'after_n_occurrences' | 'on_date';
endOccurrences?: number;
endDate?: string;
weekendSolveMode?: 'before' | 'after';
}
export interface ScheduleEntity {
id: string;
name?: string;
rule: RuleEntity['id'];
next_date: string;
completed: boolean;
posts_transaction: boolean;
tombstone: boolean;
// These are special fields that are actually pulled from the
// underlying rule
_payee: PayeeEntity['id'];
_account: AccountEntity['id'];
_amount: number | { num1: number; num2: number };
_amountOp: string;
_date: RecurConfig;
_conditions: RuleConditionEntity[];
_actions: Array<{ op: unknown }>;
}
export type DiscoverScheduleEntity = {
id: ScheduleEntity['id'];
account: AccountEntity['id'];
payee: PayeeEntity['id'];
date: ScheduleEntity['_date'];
amount: ScheduleEntity['_amount'];
_conditions: ScheduleEntity['_conditions'];
};
export type {
RecurPattern,
RecurConfig,
ScheduleEntity,
DiscoverScheduleEntity,
} from '@actual-app/shared-types/models/schedule';

View File

@@ -8,7 +8,7 @@ export type EverythingButIdOptional<T extends { id: unknown }> = {
id: T['id'];
} & Partial<Omit<T, 'id'>>;
export type WithRequired<T, K extends keyof T> = T & Required<Pick<T, K>>;
export type { WithRequired } from '@actual-app/shared-types';
// Allows use of object literals inside child elements of `Trans` tags
// see https://github.com/i18next/react-i18next/issues/1483

View File

@@ -1,7 +1,6 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": ".",
"declaration": true,
"emitDeclarationOnly": true,
"allowJs": false,

View File

@@ -52,6 +52,14 @@ export default defineConfig(({ mode }) => {
find: /^@actual-app\/crdt(\/.*)?$/,
replacement: path.resolve(crdtDir, 'src') + '$1',
},
{
find: '@actual-app/query',
replacement: path.resolve(__dirname, '../query/src'),
},
{
find: '@actual-app/shared-types',
replacement: path.resolve(__dirname, '../shared-types/src'),
},
],
},
plugins: [

View File

@@ -77,6 +77,14 @@ export default defineConfig(({ mode }) => {
find: /^@actual-app\/crdt(\/.*)?$/,
replacement: path.resolve(crdtDir, 'src') + '$1',
},
{
find: '@actual-app/query',
replacement: path.resolve(__dirname, '../query/src'),
},
{
find: '@actual-app/shared-types',
replacement: path.resolve(__dirname, '../shared-types/src'),
},
],
},
define: {

View File

@@ -50,6 +50,14 @@ export default defineConfig(({ mode }) => {
find: /^@actual-app\/crdt(\/.*)?$/,
replacement: path.resolve(crdtDir, 'src') + '$1',
},
{
find: '@actual-app/query',
replacement: path.resolve(__dirname, '../query/src'),
},
{
find: '@actual-app/shared-types',
replacement: path.resolve(__dirname, '../shared-types/src'),
},
],
},
plugins: [

View File

@@ -0,0 +1,59 @@
{
"name": "@actual-app/plugins-core",
"private": true,
"version": "0.1.0",
"description": "Core plugin system for Actual Budget",
"main": "src/client.ts",
"types": "src/client.ts",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint src/ tests/ --ext .ts,.tsx",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@types/react": "^19.1.4",
"@types/react-dom": "^19.1.4",
"typescript": "^5.5.4",
"typescript-eslint": "^8.18.1",
"typescript-strict-plugin": "^2.4.4",
"vite": "^6.2.0",
"vite-plugin-dts": "^4.5.3"
},
"exports": {
"./server": {
"types": "./src/server.ts",
"development": "./src/server.ts",
"import": "./build/server.js",
"require": "./build/server.cjs"
},
"./client": {
"types": "./src/client.ts",
"development": "./src/client.ts",
"import": "./build/client.js",
"require": "./build/client.cjs"
},
"./BasicModalComponents": {
"types": "./src/BasicModalComponents.tsx",
"import": "./build/BasicModalComponents.js",
"development": "./src/BasicModalComponents.tsx"
},
"./types/*": {
"types": "./src/types/*.ts",
"development": "./src/types/*.ts"
},
"./query": {
"types": "./src/query/index.ts",
"development": "./src/query/index.ts"
},
"./src/*": "./src/*"
},
"peerDependencies": {
"i18next": "^25.2.1",
"react": "19.1.0",
"react-aria-components": "^1.7.1",
"react-dom": "19.1.0",
"react-i18next": "^16.0.0"
}
}

View File

@@ -0,0 +1,228 @@
import React, { ReactNode, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
Button,
CSSProperties,
Input,
styles,
SvgLogo,
View,
} from '@actual-app/components';
import { SvgDelete } from '@actual-app/components/icons/v0';
type ModalButtonsProps = {
style?: CSSProperties;
leftContent?: ReactNode;
focusButton?: boolean;
children: ReactNode;
};
export const ModalButtons = ({
style,
leftContent,
focusButton = false,
children,
}: ModalButtonsProps) => {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (focusButton && containerRef.current) {
const button = containerRef.current.querySelector<HTMLButtonElement>(
'button:not([data-hidden])',
);
if (button) {
button.focus();
}
}
}, [focusButton]);
return (
<View
innerRef={containerRef}
style={{
flexDirection: 'row',
marginTop: 30,
...style,
}}
>
{leftContent}
<View style={{ flex: 1 }} />
{children}
</View>
);
};
type ModalHeaderProps = {
leftContent?: ReactNode;
showLogo?: boolean;
title?: ReactNode;
rightContent?: ReactNode;
};
export function ModalHeader({
leftContent,
showLogo,
title,
rightContent,
}: ModalHeaderProps) {
return (
<View
role="heading"
style={{
justifyContent: 'center',
alignItems: 'center',
position: 'relative',
height: 60,
flex: 'none',
}}
>
<View
style={{
position: 'absolute',
left: 0,
}}
>
{leftContent}
</View>
{(title || showLogo) && (
<View
style={{
textAlign: 'center',
// We need to force a width for the text-overflow
// ellipses to work because we are aligning center.
width: 'calc(100% - 60px)',
}}
>
{showLogo && (
<SvgLogo
width={30}
height={30}
style={{ justifyContent: 'center', alignSelf: 'center' }}
/>
)}
{title &&
(typeof title === 'string' || typeof title === 'number' ? (
<ModalTitle title={`${title}`} />
) : (
title
))}
</View>
)}
{rightContent && (
<View
style={{
position: 'absolute',
right: 0,
}}
>
{rightContent}
</View>
)}
</View>
);
}
type ModalTitleProps = {
title: string;
isEditable?: boolean;
getStyle?: (isEditing: boolean) => CSSProperties;
onEdit?: (isEditing: boolean) => void;
onTitleUpdate?: (newName: string) => void;
};
export function ModalTitle({
title,
isEditable,
getStyle,
onTitleUpdate,
}: ModalTitleProps) {
const [isEditing, setIsEditing] = useState(false);
const onTitleClick = () => {
if (isEditable) {
setIsEditing(true);
}
};
const _onTitleUpdate = (newTitle: string) => {
if (newTitle !== title) {
onTitleUpdate?.(newTitle);
}
setIsEditing(false);
};
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (isEditing) {
if (inputRef.current) {
inputRef.current.scrollLeft = 0;
}
}
}, [isEditing]);
const style = getStyle?.(isEditing);
return isEditing ? (
<Input
ref={inputRef}
style={{
fontSize: 25,
fontWeight: 700,
textAlign: 'center',
...style,
}}
defaultValue={title}
onUpdate={_onTitleUpdate}
onKeyDown={e => {
if (e.key === 'Enter') {
e.preventDefault();
_onTitleUpdate?.(e.currentTarget.value);
}
}}
/>
) : (
<View
style={{
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
}}
>
<span
onClick={onTitleClick}
style={{
fontSize: 25,
fontWeight: 700,
textAlign: 'center',
...(isEditable && styles.underlinedText),
...style,
}}
>
{title}
</span>
</View>
);
}
type ModalCloseButtonProps = {
onPress?: () => void;
style?: CSSProperties;
};
export function ModalCloseButton({ onPress, style }: ModalCloseButtonProps) {
const { t } = useTranslation();
return (
<Button
variant="bare"
onPress={onPress}
aria-label={t('Close')}
style={{ padding: '10px 10px' }}
>
<SvgDelete width={10} style={style} />
</Button>
);
}

View File

@@ -0,0 +1,37 @@
// Modal Components (client-side only)
export {
ModalTitle,
ModalButtons,
ModalHeader,
ModalCloseButton,
} from './BasicModalComponents';
// Client-side middleware
export { initializePlugin } from './middleware';
// Icons, styles, theme (client-side only)
export * from '@actual-app/components';
// Client-side hooks (React hooks)
export { useReport } from './utils';
// Query System (also needed on client-side for components)
export * from '@actual-app/query';
// Spreadsheet types and utilities (client-side only)
export * from '@actual-app/shared-types/spreadsheet';
// Client-side plugin types
export type {
ActualPlugin,
ActualPluginInitialized,
ThemeColorOverrides,
HostContext,
} from './types/actualPlugin';
export type * from '@actual-app/components/props/modalProps';
export type {
ActualPluginToolkit,
ActualPluginToolkitFunctions,
} from './types/toolkit';

View File

@@ -0,0 +1,13 @@
/**
* Main Entry Point for @actual-app/plugins-core
*
* Re-exports everything from both server and client exports.
* `server` must be used in `loot-core`
* `client` must be used in `desktop-client`
*/
// Re-export all server-safe exports
export * from './server';
// Re-export all client-only exports
export * from './client';

View File

@@ -0,0 +1,105 @@
import React, { ReactElement } from 'react';
import { initReactI18next } from 'react-i18next';
import type { BasicModalProps } from '@actual-app/components';
import ReactDOM from 'react-dom/client';
import {
ActualPlugin,
ActualPluginInitialized,
SlotLocations,
} from './types/actualPlugin';
const containerRoots = new WeakMap<HTMLElement, ReactDOM.Root>();
function getOrCreateRoot(container: HTMLElement) {
let root = containerRoots.get(container);
if (!root) {
root = ReactDOM.createRoot(container);
containerRoots.set(container, root);
}
return root;
}
export function initializePlugin(
plugin: ActualPlugin,
): ActualPluginInitialized {
const originalActivate = plugin.activate;
const newPlugin: ActualPluginInitialized = {
...plugin,
initialized: true,
activate: context => {
context.i18nInstance.use(initReactI18next);
const wrappedContext = {
...context,
// Database provided by host
db: context.db,
// Query builder passed through directly
q: context.q,
registerSlotContent(position: SlotLocations, element: ReactElement) {
return context.registerSlotContent(position, container => {
const root = getOrCreateRoot(container);
root.render(element);
return () => root.unmount();
});
},
pushModal(element: ReactElement, modalProps?: BasicModalProps) {
context.pushModal(container => {
const root = getOrCreateRoot(container);
root.render(element);
return () => root.unmount();
}, modalProps);
},
registerRoute(path: string, element: ReactElement) {
return context.registerRoute(path, container => {
const root = getOrCreateRoot(container);
root.render(element);
return () => root.unmount();
});
},
registerDashboardWidget(
widgetType: string,
displayName: string,
element: ReactElement,
options?: {
defaultWidth?: number;
defaultHeight?: number;
minWidth?: number;
minHeight?: number;
},
) {
return context.registerDashboardWidget(
widgetType,
displayName,
container => {
const root = getOrCreateRoot(container);
root.render(element);
return () => root.unmount();
},
options,
);
},
// Theme methods - passed through from host context
addTheme: context.addTheme,
overrideTheme: context.overrideTheme,
// Report and spreadsheet utilities - passed through from host context
createSpreadsheet: context.createSpreadsheet,
makeFilters: context.makeFilters,
};
originalActivate(wrappedContext);
},
};
return newPlugin;
}

View File

@@ -0,0 +1,62 @@
/**
* Server-Only Exports for @actual-app/plugins-core
*
* This file contains only types and utilities that can be used in server environments
* (Web Workers, Node.js) without any DOM dependencies or React components.
*/
// Database types
export type {
SqlParameter,
DatabaseQueryResult,
DatabaseRow,
DatabaseSelectResult,
DatabaseResult,
DatabaseOperation,
PluginMetadata,
} from '@actual-app/query/database';
// Plugin file types
export type { PluginFile, PluginFileCollection } from './types/plugin-files';
// AQL query result types
export type {
AQLQueryResult,
AQLQueryOptions,
} from '@actual-app/query/aql-result';
// Model types (server-safe)
export type {
AccountEntity,
CategoryEntity,
CategoryGroupEntity,
PayeeEntity,
ScheduleEntity,
} from '@actual-app/shared-types/models/index';
// Plugin types (server-safe ones)
export type {
PluginDatabase,
PluginSpreadsheet,
PluginBinding,
PluginCellValue,
PluginFilterCondition,
PluginFilterResult,
PluginConditionValue,
PluginMigration,
PluginContext,
ContextEvent,
} from './types/actualPlugin';
export type { ActualPluginEntry } from './types/actualPluginEntry';
export type { ActualPluginManifest } from './types/actualPluginManifest';
// Query System (server-safe)
export {
Query,
q,
getPrimaryOrderBy,
type QueryState,
type QueryBuilder,
type ObjectExpression,
} from '@actual-app/query';

View File

@@ -0,0 +1,521 @@
import type { ReactElement } from 'react';
import type { BasicModalProps } from '@actual-app/components';
import type { Query, QueryBuilder } from '@actual-app/query';
import type {
AccountEntity,
CategoryEntity,
CategoryGroupEntity,
PayeeEntity,
ScheduleEntity,
} from '@actual-app/shared-types';
import type { i18n } from 'i18next';
export type SlotLocations =
| 'main-menu'
| 'more-menu'
| 'before-accounts'
| 'after-accounts'
| 'topbar';
// Define condition value types for filtering
export type PluginConditionValue =
| string
| number
| boolean
| null
| Array<string | number>
| { num1: number; num2: number };
export type PluginFilterCondition = {
field: string;
op: string;
value: PluginConditionValue;
type?: string;
customName?: string;
};
export type PluginFilterResult = {
filters: Record<string, unknown>;
};
// Simple color mapping type for theme methods
export type ThemeColorOverrides = {
// Page colors
pageBackground?: string;
pageBackgroundModalActive?: string;
pageBackgroundTopLeft?: string;
pageBackgroundBottomRight?: string;
pageBackgroundLineTop?: string;
pageBackgroundLineMid?: string;
pageBackgroundLineBottom?: string;
pageText?: string;
pageTextLight?: string;
pageTextSubdued?: string;
pageTextDark?: string;
pageTextPositive?: string;
pageTextLink?: string;
pageTextLinkLight?: string;
// Card colors
cardBackground?: string;
cardBorder?: string;
cardShadow?: string;
// Table colors
tableBackground?: string;
tableRowBackgroundHover?: string;
tableText?: string;
tableTextLight?: string;
tableTextSubdued?: string;
tableTextSelected?: string;
tableTextHover?: string;
tableTextInactive?: string;
tableHeaderText?: string;
tableHeaderBackground?: string;
tableBorder?: string;
tableBorderSelected?: string;
tableBorderHover?: string;
tableBorderSeparator?: string;
tableRowBackgroundHighlight?: string;
tableRowBackgroundHighlightText?: string;
tableRowHeaderBackground?: string;
tableRowHeaderText?: string;
// Sidebar colors
sidebarBackground?: string;
sidebarItemBackgroundPending?: string;
sidebarItemBackgroundPositive?: string;
sidebarItemBackgroundFailed?: string;
sidebarItemBackgroundHover?: string;
sidebarItemAccentSelected?: string;
sidebarItemText?: string;
sidebarItemTextSelected?: string;
// Menu colors
menuBackground?: string;
menuItemBackground?: string;
menuItemBackgroundHover?: string;
menuItemText?: string;
menuItemTextHover?: string;
menuItemTextSelected?: string;
menuItemTextHeader?: string;
menuBorder?: string;
menuBorderHover?: string;
menuKeybindingText?: string;
menuAutoCompleteBackground?: string;
menuAutoCompleteBackgroundHover?: string;
menuAutoCompleteText?: string;
menuAutoCompleteTextHover?: string;
menuAutoCompleteTextHeader?: string;
menuAutoCompleteItemTextHover?: string;
menuAutoCompleteItemText?: string;
// Modal colors
modalBackground?: string;
modalBorder?: string;
// Mobile colors
mobileHeaderBackground?: string;
mobileHeaderText?: string;
mobileHeaderTextSubdued?: string;
mobileHeaderTextHover?: string;
mobilePageBackground?: string;
mobileNavBackground?: string;
mobileNavItem?: string;
mobileNavItemSelected?: string;
mobileAccountShadow?: string;
mobileAccountText?: string;
mobileTransactionSelected?: string;
mobileViewTheme?: string;
mobileConfigServerViewTheme?: string;
// Markdown colors
markdownNormal?: string;
markdownDark?: string;
markdownLight?: string;
// Button colors - Menu buttons
buttonMenuText?: string;
buttonMenuTextHover?: string;
buttonMenuBackground?: string;
buttonMenuBackgroundHover?: string;
buttonMenuBorder?: string;
buttonMenuSelectedText?: string;
buttonMenuSelectedTextHover?: string;
buttonMenuSelectedBackground?: string;
buttonMenuSelectedBackgroundHover?: string;
buttonMenuSelectedBorder?: string;
// Button colors - Primary buttons
buttonPrimaryText?: string;
buttonPrimaryTextHover?: string;
buttonPrimaryBackground?: string;
buttonPrimaryBackgroundHover?: string;
buttonPrimaryBorder?: string;
buttonPrimaryShadow?: string;
buttonPrimaryDisabledText?: string;
buttonPrimaryDisabledBackground?: string;
buttonPrimaryDisabledBorder?: string;
// Button colors - Normal buttons
buttonNormalText?: string;
buttonNormalTextHover?: string;
buttonNormalBackground?: string;
buttonNormalBackgroundHover?: string;
buttonNormalBorder?: string;
buttonNormalShadow?: string;
buttonNormalSelectedText?: string;
buttonNormalSelectedBackground?: string;
buttonNormalDisabledText?: string;
buttonNormalDisabledBackground?: string;
buttonNormalDisabledBorder?: string;
// Button colors - Bare buttons
buttonBareText?: string;
buttonBareTextHover?: string;
buttonBareBackground?: string;
buttonBareBackgroundHover?: string;
buttonBareBackgroundActive?: string;
buttonBareDisabledText?: string;
buttonBareDisabledBackground?: string;
// Calendar colors
calendarText?: string;
calendarBackground?: string;
calendarItemText?: string;
calendarItemBackground?: string;
calendarSelectedBackground?: string;
calendarCellBackground?: string;
// Status colors - Notice
noticeBackground?: string;
noticeBackgroundLight?: string;
noticeBackgroundDark?: string;
noticeText?: string;
noticeTextLight?: string;
noticeTextDark?: string;
noticeTextMenu?: string;
noticeTextMenuHover?: string;
noticeBorder?: string;
// Status colors - Warning
warningBackground?: string;
warningText?: string;
warningTextLight?: string;
warningTextDark?: string;
warningBorder?: string;
// Status colors - Error
errorBackground?: string;
errorText?: string;
errorTextDark?: string;
errorTextDarker?: string;
errorTextMenu?: string;
errorBorder?: string;
// Status colors - Upcoming
upcomingBackground?: string;
upcomingText?: string;
upcomingBorder?: string;
// Form colors
formLabelText?: string;
formLabelBackground?: string;
formInputBackground?: string;
formInputBackgroundSelected?: string;
formInputBackgroundSelection?: string;
formInputBorder?: string;
formInputTextReadOnlySelection?: string;
formInputBorderSelected?: string;
formInputText?: string;
formInputTextSelected?: string;
formInputTextPlaceholder?: string;
formInputTextPlaceholderSelected?: string;
formInputTextSelection?: string;
formInputShadowSelected?: string;
formInputTextHighlight?: string;
// Checkbox colors
checkboxText?: string;
checkboxBackgroundSelected?: string;
checkboxBorderSelected?: string;
checkboxShadowSelected?: string;
checkboxToggleBackground?: string;
checkboxToggleBackgroundSelected?: string;
checkboxToggleDisabled?: string;
// Pill colors
pillBackground?: string;
pillBackgroundLight?: string;
pillText?: string;
pillTextHighlighted?: string;
pillBorder?: string;
pillBorderDark?: string;
pillBackgroundSelected?: string;
pillTextSelected?: string;
pillBorderSelected?: string;
pillTextSubdued?: string;
// Reports colors
reportsRed?: string;
reportsBlue?: string;
reportsGreen?: string;
reportsGray?: string;
reportsLabel?: string;
reportsInnerLabel?: string;
// Note tag colors
noteTagBackground?: string;
noteTagBackgroundHover?: string;
noteTagText?: string;
// Budget colors
budgetCurrentMonth?: string;
budgetOtherMonth?: string;
budgetHeaderCurrentMonth?: string;
budgetHeaderOtherMonth?: string;
// Floating action bar colors
floatingActionBarBackground?: string;
floatingActionBarBorder?: string;
floatingActionBarText?: string;
// Tooltip colors
tooltipText?: string;
tooltipBackground?: string;
tooltipBorder?: string;
// Custom colors (plugin-specific)
[customColor: `custom-${string}`]: string;
};
export interface PluginDatabase {
runQuery<T = unknown>(
sql: string,
params?: (string | number)[],
fetchAll?: boolean,
): Promise<T[] | { changes: number; insertId?: number }>;
execQuery(sql: string): void;
transaction(fn: () => void): void;
getMigrationState(): Promise<string[]>;
setMetadata(key: string, value: string): Promise<void>;
getMetadata(key: string): Promise<string | null>;
/**
* Execute an AQL (Actual Query Language) query.
* This provides a higher-level abstraction over SQL that's consistent with Actual Budget's query system.
*
* @param query - The AQL query (can be a PluginQuery object or serialized PluginQueryState)
* @param options - Optional parameters for the query
* @param options.target - Target database: 'plugin' for plugin tables, 'host' for main app tables. Defaults to 'plugin'
* @param options.params - Named parameters for the query
* @returns Promise that resolves to the query result with data and dependencies
*/
aql(
query: Query,
options?: {
target?: 'plugin' | 'host';
params?: Record<string, unknown>;
},
): Promise<{ data: unknown; dependencies: string[] }>;
}
export type PluginBinding = string | { name: string; query?: Query };
export type PluginCellValue = { name: string; value: unknown | null };
export interface PluginSpreadsheet {
/**
* Bind to a cell and observe changes
* @param sheetName - Name of the sheet (optional, defaults to global)
* @param binding - Cell binding (string name or object with name and optional query)
* @param callback - Function called when cell value changes
* @returns Cleanup function to stop observing
*/
bind(
sheetName: string | undefined,
binding: PluginBinding,
callback: (node: PluginCellValue) => void,
): () => void;
/**
* Get a cell value directly
* @param sheetName - Name of the sheet
* @param name - Cell name
* @returns Promise that resolves to the cell value
*/
get(sheetName: string, name: string): Promise<PluginCellValue>;
/**
* Get all cell names in a sheet
* @param sheetName - Name of the sheet
* @returns Promise that resolves to array of cell names
*/
getCellNames(sheetName: string): Promise<string[]>;
/**
* Create a query in a sheet
* @param sheetName - Name of the sheet
* @param name - Query name
* @param query - The query to create
* @returns Promise that resolves when query is created
*/
createQuery(sheetName: string, name: string, query: Query): Promise<void>;
}
export type PluginMigration = [
timestamp: number,
name: string,
upCommand: string,
downCommand: string,
];
// Plugin context type for easier reuse
export type PluginContext = Omit<
HostContext,
| 'registerSlotContent'
| 'pushModal'
| 'registerRoute'
| 'registerDashboardWidget'
> & {
registerSlotContent: (
location: SlotLocations,
element: ReactElement,
) => string;
pushModal: (element: ReactElement, modalProps?: BasicModalProps) => void;
registerRoute: (path: string, routeElement: ReactElement) => string;
// Dashboard widget registration - wrapped for JSX elements
registerDashboardWidget: (
widgetType: string,
displayName: string,
element: ReactElement,
options?: {
defaultWidth?: number;
defaultHeight?: number;
minWidth?: number;
minHeight?: number;
},
) => string;
// Theme methods - simple and direct
addTheme: (
themeId: string,
displayName: string,
colorOverrides: ThemeColorOverrides,
options?: {
baseTheme?: 'light' | 'dark' | 'midnight';
description?: string;
},
) => void;
overrideTheme: (
themeId: 'light' | 'dark' | 'midnight' | string,
colorOverrides: ThemeColorOverrides,
) => void;
db?: PluginDatabase;
q: QueryBuilder;
// Report and spreadsheet utilities
createSpreadsheet: () => PluginSpreadsheet;
makeFilters: (
conditions: Array<PluginFilterCondition>,
) => Promise<PluginFilterResult>;
};
export interface ActualPlugin {
name: string;
version: string;
uninstall: () => void;
migrations?: () => PluginMigration[];
activate: (context: PluginContext) => void;
}
export type ActualPluginInitialized = Omit<ActualPlugin, 'activate'> & {
initialized: true;
activate: (context: HostContext & { db: PluginDatabase }) => void;
};
export interface ContextEvent {
payees: { payees: PayeeEntity[] };
categories: { categories: CategoryEntity[]; groups: CategoryGroupEntity[] };
accounts: { accounts: AccountEntity[] };
schedules: { schedules: ScheduleEntity[] };
}
export interface HostContext {
navigate: (routePath: string) => void;
pushModal: (
parameter: (container: HTMLDivElement) => void | (() => void),
modalProps?: BasicModalProps,
) => void;
popModal: () => void;
registerRoute: (
path: string,
routeElement: (container: HTMLDivElement) => void | (() => void),
) => string;
unregisterRoute: (id: string) => void;
registerSlotContent: (
location: SlotLocations,
parameter: (container: HTMLDivElement) => void | (() => void),
) => string;
unregisterSlotContent: (id: string) => void;
on: <K extends keyof ContextEvent>(
eventType: K,
callback: (data: ContextEvent[K]) => void,
) => void;
// Dashboard widget methods
registerDashboardWidget: (
widgetType: string,
displayName: string,
renderWidget: (container: HTMLDivElement) => void | (() => void),
options?: {
defaultWidth?: number;
defaultHeight?: number;
minWidth?: number;
minHeight?: number;
},
) => string;
unregisterDashboardWidget: (id: string) => void;
// Theme methods
addTheme: (
themeId: string,
displayName: string,
colorOverrides: ThemeColorOverrides,
options?: {
baseTheme?: 'light' | 'dark' | 'midnight';
description?: string;
},
) => void;
overrideTheme: (
themeId: 'light' | 'dark' | 'midnight' | string,
colorOverrides: ThemeColorOverrides,
) => void;
// Query builder provided by host (loot-core's q function)
q: QueryBuilder;
// Report and spreadsheet utilities for dashboard widgets
createSpreadsheet: () => PluginSpreadsheet;
makeFilters: (
conditions: Array<PluginFilterCondition>,
) => Promise<PluginFilterResult>;
i18nInstance: i18n;
}

View File

@@ -0,0 +1,5 @@
import React from 'react';
import { ActualPluginInitialized } from './actualPlugin';
export type ActualPluginEntry = () => ActualPluginInitialized;

View File

@@ -0,0 +1,11 @@
export interface ActualPluginManifest {
url: string;
name: string;
version: string;
enabled?: boolean;
description?: string;
pluginType: 'server' | 'client';
minimumActualVersion: string;
author: string;
plugin?: Blob;
}

View File

@@ -0,0 +1,18 @@
/**
* Plugin File Types
*
* Types for plugin file operations and storage.
*/
/**
* Represents a single file within a plugin package
*/
export interface PluginFile {
name: string;
content: string;
}
/**
* Collection of files that make up a plugin
*/
export type PluginFileCollection = PluginFile[];

View File

@@ -0,0 +1,7 @@
export type ActualPluginToolkitFunctions = {
pushModal: (modalName: string, options?: unknown) => void;
};
export type ActualPluginToolkit = {
functions: ActualPluginToolkitFunctions;
};

View File

@@ -0,0 +1,24 @@
import { useState, useEffect } from 'react';
import { PluginSpreadsheet } from './types/actualPlugin';
/**
* useReport hook for plugins to manage spreadsheet data
* This is similar to the useReport hook in desktop-client but adapted for plugins
*/
export function useReport<T>(
sheetName: string,
getData: (
spreadsheet: PluginSpreadsheet,
setData: (results: T) => void,
) => Promise<void>,
spreadsheet: PluginSpreadsheet,
): T | null {
const [results, setResults] = useState<T | null>(null);
useEffect(() => {
getData(spreadsheet, results => setResults(results));
}, [getData, spreadsheet]);
return results;
}

View File

@@ -0,0 +1,25 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"emitDeclarationOnly": true,
"outDir": "dist",
"jsx": "react-jsx",
"module": "esnext",
"moduleResolution": "bundler",
"baseUrl": ".",
"paths": {
"@actual-app/shared-types": ["../shared-types/src/index.ts"],
"@actual-app/shared-types/*": ["../shared-types/src/*"],
"@actual-app/shared-types/models": [
"../shared-types/src/models/index.ts"
],
"@actual-app/query": ["../query/src/index.ts"],
"@actual-app/query/*": ["../query/src/*"],
"@actual-app/components": ["../component-library/src/index.ts"],
"@actual-app/components/*": ["../component-library/src/*"]
}
},
"include": ["src"]
}

View File

@@ -0,0 +1,55 @@
import { resolve } from 'path';
import { defineConfig } from 'vite';
import dts from 'vite-plugin-dts';
export default defineConfig({
resolve: {
alias: {
'@actual-app/shared-types': resolve(__dirname, '../shared-types/src'),
'@actual-app/query': resolve(__dirname, '../query/src'),
'@actual-app/components': resolve(__dirname, '../component-library/src'),
},
},
build: {
outDir: 'build',
lib: {
entry: {
server: 'src/server.ts',
client: 'src/client.ts',
},
name: '@actual-app/plugins-core',
fileName: (format, entryName) =>
format === 'es' ? `${entryName}.js` : `${entryName}.cjs`,
formats: ['es', 'cjs'],
},
rollupOptions: {
external: [
'react',
'react-dom',
'i18next',
'react-i18next',
'react-aria-components',
'@emotion/css',
'usehooks-ts',
],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM',
i18next: 'i18next',
},
},
},
},
plugins: [
dts({
insertTypesEntry: true,
outDir: 'build',
include: ['src/**/*'],
exclude: ['src/**/*.test.ts', 'src/**/*.test.tsx'],
rollupTypes: false,
copyDtsFiles: false,
}),
],
});

View File

@@ -0,0 +1,36 @@
{
"name": "@actual-app/query",
"version": "0.0.1",
"private": true,
"description": "Query system and database utilities for Actual Budget",
"license": "MIT",
"main": "src/index.ts",
"types": "src/index.ts",
"exports": {
".": {
"types": "./src/index.ts",
"development": "./src/index.ts"
},
"./database": {
"types": "./src/database.ts",
"development": "./src/database.ts"
},
"./aql-result": {
"types": "./src/aql-result.ts",
"development": "./src/aql-result.ts"
},
"./spreadsheet": {
"types": "./src/spreadsheet.ts",
"development": "./src/spreadsheet.ts"
}
},
"scripts": {
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"typescript": "^5.5.4"
},
"dependencies": {
"@actual-app/shared-types": "workspace:^"
}
}

View File

@@ -0,0 +1,27 @@
/**
* AQL Query Result Types
*
* Types for AQL (Advanced Query Language) query results.
*/
import { DatabaseRow } from './database';
/**
* Result of an AQL query operation
*/
export interface AQLQueryResult {
/** The actual data returned by the query */
data: DatabaseRow[] | DatabaseRow | null;
/** List of table/field dependencies detected during query execution */
dependencies: string[];
}
/**
* Options for AQL query execution
*/
export interface AQLQueryOptions {
/** Target for the query - 'plugin' uses plugin DB, 'host' uses main DB */
target?: 'plugin' | 'host';
/** Parameters to be passed to the query */
params?: Record<string, string | number | boolean | null>;
}

View File

@@ -0,0 +1,51 @@
/**
* Plugin Database Types
*
* Shared types for database operations between plugins and the host application.
*/
/**
* Parameters that can be passed to SQL queries
*/
export type SqlParameter = string | number | boolean | null | Buffer;
/**
* Result of a database query operation (INSERT, UPDATE, DELETE)
*/
export interface DatabaseQueryResult {
changes: number;
insertId?: number;
}
/**
* Row returned from a database SELECT query
*/
export type DatabaseRow = Record<string, SqlParameter>;
/**
* Result of a database SELECT query
*/
export type DatabaseSelectResult = DatabaseRow[];
/**
* Union type for all possible database query results
*/
export type DatabaseResult = DatabaseQueryResult | DatabaseSelectResult;
/**
* Database transaction operation
*/
export interface DatabaseOperation {
type: 'exec' | 'query';
sql: string;
params?: SqlParameter[];
fetchAll?: boolean; // Only used for query operations
}
/**
* Plugin database metadata key-value pair
*/
export interface PluginMetadata {
key: string;
value: string;
}

179
packages/query/src/index.ts Normal file
View File

@@ -0,0 +1,179 @@
/**
* Query System - Single Source of Truth
*
* This is the main query implementation used by both loot-core and plugins.
* No more conversion functions needed!
*/
import {
ObjectExpression,
IQuery,
QueryState,
WithRequired,
} from '@actual-app/shared-types';
export type { ObjectExpression, IQuery, QueryState };
export class Query implements IQuery {
state: QueryState;
constructor(state: WithRequired<Partial<QueryState>, 'table'>) {
this.state = {
tableOptions: state.tableOptions || {},
filterExpressions: state.filterExpressions || [],
selectExpressions: state.selectExpressions || [],
groupExpressions: state.groupExpressions || [],
orderExpressions: state.orderExpressions || [],
calculation: false,
rawMode: false,
withDead: false,
validateRefs: true,
limit: null,
offset: null,
...state,
};
}
filter(expr: ObjectExpression) {
return new Query({
...this.state,
filterExpressions: [...this.state.filterExpressions, expr],
});
}
unfilter(exprs?: Array<keyof ObjectExpression>) {
// Remove all filters if no arguments are passed
if (!exprs) {
return new Query({
...this.state,
filterExpressions: [],
});
}
const exprSet = new Set(exprs);
return new Query({
...this.state,
filterExpressions: this.state.filterExpressions.filter(
expr => !exprSet.has(Object.keys(expr)[0]),
),
});
}
select(
exprs:
| Array<ObjectExpression | string>
| ObjectExpression
| string
| '*'
| ['*'] = [],
) {
if (!Array.isArray(exprs)) {
exprs = [exprs];
}
return new Query({
...this.state,
selectExpressions: exprs,
calculation: false,
});
}
calculate(expr: ObjectExpression | string) {
return new Query({
...this.state,
selectExpressions: [{ result: expr }],
calculation: true,
});
}
groupBy(exprs: ObjectExpression | string | Array<ObjectExpression | string>) {
if (!Array.isArray(exprs)) {
exprs = [exprs];
}
return new Query({
...this.state,
groupExpressions: [...this.state.groupExpressions, ...exprs],
});
}
orderBy(exprs: ObjectExpression | string | Array<ObjectExpression | string>) {
if (!Array.isArray(exprs)) {
exprs = [exprs];
}
return new Query({
...this.state,
orderExpressions: [...this.state.orderExpressions, ...exprs],
});
}
limit(num: number) {
return new Query({ ...this.state, limit: num });
}
offset(num: number) {
return new Query({ ...this.state, offset: num });
}
raw() {
return new Query({ ...this.state, rawMode: true });
}
withDead() {
return new Query({ ...this.state, withDead: true });
}
withoutValidatedRefs() {
return new Query({ ...this.state, validateRefs: false });
}
options(opts: Record<string, unknown>) {
return new Query({ ...this.state, tableOptions: opts });
}
reset() {
return q(this.state.table);
}
serialize() {
return this.state;
}
serializeAsString() {
return JSON.stringify(this.serialize());
}
}
/**
* Query builder function - creates a new Query for the given table
*/
export function q(table: QueryState['table']) {
return new Query({ table });
}
export type { QueryBuilder } from '@actual-app/shared-types';
/**
* Helper function to get primary order by clause
*/
export function getPrimaryOrderBy(
query: Query,
defaultOrderBy: ObjectExpression | null,
) {
const orderExprs = query.serialize().orderExpressions;
if (orderExprs.length === 0) {
if (defaultOrderBy) {
return { order: 'asc', ...defaultOrderBy };
}
return null;
}
const firstOrder = orderExprs[0];
if (typeof firstOrder === 'string') {
return { field: firstOrder, order: 'asc' };
}
// Handle this form: { field: 'desc' }
const [field] = Object.keys(firstOrder);
return { field, order: firstOrder[field] };
}

View File

@@ -0,0 +1,17 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"target": "ES2021",
"noEmit": false,
"declaration": true,
"outDir": "dist",
"declarationDir": "@types",
"paths": {
"@actual-app/shared-types": ["../shared-types/src/index.ts"],
"@actual-app/shared-types/*": ["../shared-types/src/*"],
"@actual-app/shared-types/models": ["../shared-types/src/models/index.ts"]
}
},
"include": ["."],
"exclude": ["**/node_modules/*", "dist", "@types", "*.test.ts"]
}

View File

@@ -0,0 +1,29 @@
{
"name": "@actual-app/shared-types",
"version": "0.0.1",
"private": true,
"description": "Shared type definitions for Actual Budget",
"license": "MIT",
"main": "src/index.ts",
"types": "src/index.ts",
"exports": {
".": {
"types": "./src/index.ts",
"development": "./src/index.ts"
},
"./models/*": {
"types": "./src/models/*.ts",
"development": "./src/models/*.ts"
},
"./modalProps": {
"types": "./src/modalProps.ts",
"development": "./src/modalProps.ts"
}
},
"scripts": {
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"typescript": "^5.5.4"
}
}

View File

@@ -0,0 +1,8 @@
// Re-export all model types
export type * from './models';
// Export query types
export type * from './query';
// Export utility types
export type * from './util';

View File

@@ -0,0 +1,25 @@
export type AccountEntity = {
id: string;
name: string;
offbudget: 0 | 1;
closed: 0 | 1;
sort_order: number;
last_reconciled: string | null;
tombstone: 0 | 1;
} & (_SyncFields<true> | _SyncFields<false>);
export type _SyncFields<T> = {
account_id: T extends true ? string : null;
bank: T extends true ? string : null;
bankName: T extends true ? string : null;
bankId: T extends true ? number : null;
mask: T extends true ? string : null; // end of bank account number
official_name: T extends true ? string : null;
balance_current: T extends true ? number : null;
balance_available: T extends true ? number : null;
balance_limit: T extends true ? number : null;
account_sync_source: T extends true ? AccountSyncSource : null;
last_sync: T extends true ? string : null;
};
export type AccountSyncSource = 'simpleFin' | 'goCardless' | 'pluggyai';

View File

@@ -0,0 +1,11 @@
import { CategoryEntity } from './category';
export interface CategoryGroupEntity {
id: string;
name: string;
is_income?: boolean;
sort_order?: number;
tombstone?: boolean;
hidden?: boolean;
categories?: CategoryEntity[];
}

View File

@@ -0,0 +1,7 @@
import { CategoryEntity } from './category';
import { CategoryGroupEntity } from './category-group';
export interface CategoryViews {
grouped: CategoryGroupEntity[];
list: CategoryEntity[];
}

View File

@@ -0,0 +1,13 @@
import { CategoryGroupEntity } from './category-group';
export interface CategoryEntity {
id: string;
name: string;
is_income?: boolean;
group: CategoryGroupEntity['id'];
goal_def?: string;
template_settings?: { source: 'notes' | 'ui' };
sort_order?: number;
tombstone?: boolean;
hidden?: boolean;
}

View File

@@ -0,0 +1,7 @@
export type * from './account';
export type * from './payee';
export type * from './category';
export type * from './category-group';
export type * from './category-views';
export type * from './rule';
export type * from './schedule';

View File

@@ -0,0 +1,10 @@
import { AccountEntity } from './account';
export interface PayeeEntity {
id: string;
name: string;
transfer_acct?: AccountEntity['id'];
favorite?: boolean;
learn_categories?: boolean;
tombstone?: boolean;
}

View File

@@ -0,0 +1,186 @@
import type { RecurConfig, ScheduleEntity } from './schedule';
export interface NewRuleEntity {
stage: 'pre' | null | 'post';
conditionsOp: 'or' | 'and';
conditions: RuleConditionEntity[];
actions: RuleActionEntity[];
tombstone?: boolean;
}
export interface RuleEntity extends NewRuleEntity {
id: string;
}
export type RuleConditionOp =
| 'is'
| 'isNot'
| 'oneOf'
| 'notOneOf'
| 'isapprox'
| 'isbetween'
| 'gt'
| 'gte'
| 'lt'
| 'lte'
| 'contains'
| 'doesNotContain'
| 'hasTags'
| 'and'
| 'matches'
| 'onBudget'
| 'offBudget';
export type FieldValueTypes = {
account: string;
amount: number;
category: string;
date: string | RecurConfig;
notes: string;
payee: string;
payee_name: string;
imported_payee: string;
saved: string;
transfer: boolean;
parent: boolean;
cleared: boolean;
reconciled: boolean;
};
type BaseConditionEntity<
Field extends keyof FieldValueTypes,
Op extends RuleConditionOp,
> = {
field: Field;
op: Op;
value: Op extends 'oneOf' | 'notOneOf'
? Array<FieldValueTypes[Field]>
: Op extends 'isbetween'
? { num1: number; num2: number }
: FieldValueTypes[Field];
options?: {
inflow?: boolean;
outflow?: boolean;
month?: boolean;
year?: boolean;
};
conditionsOp?: string;
type?: 'id' | 'boolean' | 'date' | 'number' | 'string';
customName?: string;
queryFilter?: Record<string, { $oneof: string[] }>;
};
export type RuleConditionEntity =
| BaseConditionEntity<
'account',
| 'is'
| 'isNot'
| 'oneOf'
| 'notOneOf'
| 'contains'
| 'doesNotContain'
| 'matches'
| 'onBudget'
| 'offBudget'
>
| BaseConditionEntity<
'category',
| 'is'
| 'isNot'
| 'oneOf'
| 'notOneOf'
| 'contains'
| 'doesNotContain'
| 'matches'
>
| BaseConditionEntity<
'amount',
'is' | 'isapprox' | 'isbetween' | 'gt' | 'gte' | 'lt' | 'lte'
>
| BaseConditionEntity<
'date',
'is' | 'isapprox' | 'isbetween' | 'gt' | 'gte' | 'lt' | 'lte'
>
| BaseConditionEntity<
'notes',
| 'is'
| 'isNot'
| 'oneOf'
| 'notOneOf'
| 'contains'
| 'doesNotContain'
| 'matches'
| 'hasTags'
>
| BaseConditionEntity<
'payee',
| 'is'
| 'isNot'
| 'oneOf'
| 'notOneOf'
| 'contains'
| 'doesNotContain'
| 'matches'
>
| BaseConditionEntity<
'imported_payee',
| 'is'
| 'isNot'
| 'oneOf'
| 'notOneOf'
| 'contains'
| 'doesNotContain'
| 'matches'
>
| BaseConditionEntity<'saved', 'is'>
| BaseConditionEntity<'cleared', 'is'>
| BaseConditionEntity<'reconciled', 'is'>;
export type RuleActionEntity =
| SetRuleActionEntity
| SetSplitAmountRuleActionEntity
| LinkScheduleRuleActionEntity
| PrependNoteRuleActionEntity
| AppendNoteRuleActionEntity
| DeleteTransactionRuleActionEntity;
export interface SetRuleActionEntity {
field: string;
op: 'set';
value: unknown;
options?: {
template?: string;
formula?: string;
splitIndex?: number;
};
type?: string;
}
export interface SetSplitAmountRuleActionEntity {
op: 'set-split-amount';
value: number;
options?: {
splitIndex?: number;
method: 'fixed-amount' | 'fixed-percent' | 'remainder';
};
}
export interface LinkScheduleRuleActionEntity {
op: 'link-schedule';
value: ScheduleEntity;
}
export interface PrependNoteRuleActionEntity {
op: 'prepend-notes';
value: string;
}
export interface AppendNoteRuleActionEntity {
op: 'append-notes';
value: string;
}
export interface DeleteTransactionRuleActionEntity {
op: 'delete-transaction';
value: string;
}

View File

@@ -0,0 +1,49 @@
import type { AccountEntity } from './account';
import type { PayeeEntity } from './payee';
import type { RuleConditionEntity, RuleEntity } from './rule';
export interface RecurPattern {
value: number;
type: 'SU' | 'MO' | 'TU' | 'WE' | 'TH' | 'FR' | 'SA' | 'day';
}
export interface RecurConfig {
frequency: 'daily' | 'weekly' | 'monthly' | 'yearly';
interval?: number;
patterns?: RecurPattern[];
skipWeekend?: boolean;
start: string;
endMode: 'never' | 'after_n_occurrences' | 'on_date';
endOccurrences?: number;
endDate?: string;
weekendSolveMode?: 'before' | 'after';
}
export interface ScheduleEntity {
id: string;
name?: string;
rule: RuleEntity['id'];
next_date: string;
completed: boolean;
posts_transaction: boolean;
tombstone: boolean;
// These are special fields that are actually pulled from the
// underlying rule
_payee: PayeeEntity['id'];
_account: AccountEntity['id'];
_amount: number | { num1: number; num2: number };
_amountOp: string;
_date: RecurConfig;
_conditions: RuleConditionEntity[];
_actions: Array<{ op: unknown }>;
}
export type DiscoverScheduleEntity = {
id: ScheduleEntity['id'];
account: AccountEntity['id'];
payee: PayeeEntity['id'];
date: ScheduleEntity['_date'];
amount: ScheduleEntity['_amount'];
_conditions: ScheduleEntity['_conditions'];
};

View File

@@ -0,0 +1,54 @@
/**
* Query System Types
*/
export type ObjectExpression = {
[key: string]: ObjectExpression | unknown;
};
export type QueryState = {
get table(): string;
get tableOptions(): Readonly<Record<string, unknown>>;
get filterExpressions(): ReadonlyArray<ObjectExpression>;
get selectExpressions(): ReadonlyArray<ObjectExpression | string | '*'>;
get groupExpressions(): ReadonlyArray<ObjectExpression | string>;
get orderExpressions(): ReadonlyArray<ObjectExpression | string>;
get calculation(): boolean;
get rawMode(): boolean;
get withDead(): boolean;
get validateRefs(): boolean;
get limit(): number | null;
get offset(): number | null;
};
export type IQuery = {
state: QueryState;
filter(expr: ObjectExpression): IQuery;
unfilter(exprs?: Array<keyof ObjectExpression>): IQuery;
select(
exprs:
| Array<ObjectExpression | string>
| ObjectExpression
| string
| '*'
| ['*'],
): IQuery;
calculate(expr: ObjectExpression | string): IQuery;
groupBy(
exprs: ObjectExpression | string | Array<ObjectExpression | string>,
): IQuery;
orderBy(
exprs: ObjectExpression | string | Array<ObjectExpression | string>,
): IQuery;
limit(num: number): IQuery;
offset(num: number): IQuery;
raw(): IQuery;
withDead(): IQuery;
withoutValidatedRefs(): IQuery;
options(opts: Record<string, unknown>): IQuery;
reset(): IQuery;
serialize(): QueryState;
serializeAsString(): string;
};
export type QueryBuilder = (table: string) => IQuery;

View File

@@ -0,0 +1,127 @@
/**
* Spreadsheet Types and Utilities
*
* Core spreadsheet schema definitions and binding utilities used across
* the application for managing financial data in sheet-like structures.
*/
import { IQuery } from './query';
export type Spreadsheets = {
account: {
// Common fields
'uncategorized-amount': number;
'uncategorized-balance': number;
// Account fields
balance: number;
[key: `balance-${string}-cleared`]: number | null;
'accounts-balance': number;
'onbudget-accounts-balance': number;
'offbudget-accounts-balance': number;
'closed-accounts-balance': number;
balanceCleared: number;
balanceUncleared: number;
lastReconciled: string | null;
};
category: {
// Common fields
'uncategorized-amount': number;
'uncategorized-balance': number;
balance: number;
balanceCleared: number;
balanceUncleared: number;
};
'envelope-budget': {
// Common fields
'uncategorized-amount': number;
'uncategorized-balance': number;
// Envelope budget fields
'available-funds': number;
'last-month-overspent': number;
buffered: number;
'buffered-auto': number;
'buffered-selected': number;
'to-budget': number | null;
'from-last-month': number;
'total-budgeted': number;
'total-income': number;
'total-spent': number;
'total-leftover': number;
'group-sum-amount': number;
'group-budget': number;
'group-leftover': number;
budget: number;
'sum-amount': number;
leftover: number;
carryover: number;
goal: number;
'long-goal': number;
};
'tracking-budget': {
// Common fields
'uncategorized-amount': number;
'uncategorized-balance': number;
// Tracking budget fields
'total-budgeted': number;
'total-budget-income': number;
'total-saved': number;
'total-income': number;
'total-spent': number;
'real-saved': number;
'total-leftover': number;
'group-sum-amount': number;
'group-budget': number;
'group-leftover': number;
budget: number;
'sum-amount': number;
leftover: number;
carryover: number;
goal: number;
'long-goal': number;
};
[`balance`]: {
// Common fields
'uncategorized-amount': number;
'uncategorized-balance': number;
// Balance fields
[key: `balance-query-${string}`]: number;
[key: `selected-transactions-${string}`]: Array<{ id: string }>;
[key: `selected-balance-${string}`]: number;
};
};
export type SheetNames = keyof Spreadsheets & string;
export type SheetFields<SheetName extends SheetNames> =
keyof Spreadsheets[SheetName] & string;
export type BindingObject<
SheetName extends SheetNames,
SheetFieldName extends SheetFields<SheetName>,
> = {
name: SheetFieldName;
value?: Spreadsheets[SheetName][SheetFieldName] | undefined;
query?: IQuery | undefined;
};
export type Binding<
SheetName extends SheetNames,
SheetFieldName extends SheetFields<SheetName>,
> =
| SheetFieldName
| {
name: SheetFieldName;
value?: Spreadsheets[SheetName][SheetFieldName] | undefined;
query?: IQuery | undefined;
};
export const parametrizedField =
<SheetName extends SheetNames>() =>
<SheetFieldName extends SheetFields<SheetName>>(field: SheetFieldName) =>
(id?: string): SheetFieldName =>
`${field}-${id}` as SheetFieldName;

View File

@@ -0,0 +1,5 @@
/**
* Utility types for plugins-core
*/
export type WithRequired<T, K extends keyof T> = T & Required<Pick<T, K>>;

View File

@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "build",
"composite": true
},
"include": ["src/**/*"]
}

View File

@@ -32,8 +32,24 @@
"paths": {
// TEMPORARY: Until we can fix the "exports" in the loot-core package.json
"loot-core/*": ["./packages/loot-core/src/*"],
"plugins-core/*": ["./packages/plugins-core/src/*"],
"@actual-app/plugins-core": ["./packages/plugins-core/src/index.ts"],
"@actual-app/plugins-core/server": [
"./packages/plugins-core/src/server.ts"
],
"@actual-app/plugins-core/client": [
"./packages/plugins-core/src/client.ts"
],
"@actual-app/plugins-core/*": ["./packages/plugins-core/src/*"],
"@desktop-client/*": ["./packages/desktop-client/src/*"],
"@desktop-client/e2e/*": ["./packages/desktop-client/e2e/*"]
"@desktop-client/e2e/*": ["./packages/desktop-client/e2e/*"],
"@actual-app/query": ["./packages/query/src/index.ts"],
"@actual-app/query/*": ["./packages/query/src/*"],
"@actual-app/shared-types": ["./packages/shared-types/src/index.ts"],
"@actual-app/shared-types/*": ["./packages/shared-types/src/*"],
"@actual-app/shared-types/models": [
"./packages/shared-types/src/models/index.ts"
]
},
"plugins": [
{
@@ -53,7 +69,8 @@
"**/lib-dist/*",
"**/test-results/*",
"**/playwright-report/*",
"**/service-worker/*"
"**/service-worker/*",
"packages/test-plugin/*"
],
"ts-node": {
"compilerOptions": {

View File

@@ -0,0 +1,7 @@
---
category: Features
authors: [lelemm]
---
Add support for frontend plugins with a new core package for enhanced functionality.

543
yarn.lock
View File

@@ -79,6 +79,43 @@ __metadata:
languageName: unknown
linkType: soft
"@actual-app/plugins-core@workspace:packages/plugins-core":
version: 0.0.0-use.local
resolution: "@actual-app/plugins-core@workspace:packages/plugins-core"
dependencies:
"@types/react": "npm:^19.1.4"
"@types/react-dom": "npm:^19.1.4"
typescript: "npm:^5.5.4"
typescript-eslint: "npm:^8.18.1"
typescript-strict-plugin: "npm:^2.4.4"
vite: "npm:^6.2.0"
vite-plugin-dts: "npm:^4.5.3"
peerDependencies:
i18next: ^25.2.1
react: 19.1.0
react-aria-components: ^1.7.1
react-dom: 19.1.0
react-i18next: ^16.0.0
languageName: unknown
linkType: soft
"@actual-app/query@workspace:packages/query":
version: 0.0.0-use.local
resolution: "@actual-app/query@workspace:packages/query"
dependencies:
"@actual-app/shared-types": "workspace:^"
typescript: "npm:^5.5.4"
languageName: unknown
linkType: soft
"@actual-app/shared-types@workspace:^, @actual-app/shared-types@workspace:packages/shared-types":
version: 0.0.0-use.local
resolution: "@actual-app/shared-types@workspace:packages/shared-types"
dependencies:
typescript: "npm:^5.5.4"
languageName: unknown
linkType: soft
"@actual-app/sync-server@workspace:*, @actual-app/sync-server@workspace:packages/sync-server":
version: 0.0.0-use.local
resolution: "@actual-app/sync-server@workspace:packages/sync-server"
@@ -5266,7 +5303,7 @@ __metadata:
languageName: node
linkType: hard
"@rollup/pluginutils@npm:^5.0.1":
"@rollup/pluginutils@npm:^5.0.1, @rollup/pluginutils@npm:^5.1.4":
version: 5.3.0
resolution: "@rollup/pluginutils@npm:5.3.0"
dependencies:
@@ -5454,6 +5491,77 @@ __metadata:
languageName: node
linkType: hard
"@rushstack/node-core-library@npm:5.17.0":
version: 5.17.0
resolution: "@rushstack/node-core-library@npm:5.17.0"
dependencies:
ajv: "npm:~8.13.0"
ajv-draft-04: "npm:~1.0.0"
ajv-formats: "npm:~3.0.1"
fs-extra: "npm:~11.3.0"
import-lazy: "npm:~4.0.0"
jju: "npm:~1.4.0"
resolve: "npm:~1.22.1"
semver: "npm:~7.5.4"
peerDependencies:
"@types/node": "*"
peerDependenciesMeta:
"@types/node":
optional: true
checksum: 10/a4aae2502248ea7b150295cad83f0c3bba4281817f16dc80dc627452197be145d8548c7f57047d0ecfdf0a37b646e32fcea8fc2a3dc1a650c6bd95cb26f31794
languageName: node
linkType: hard
"@rushstack/problem-matcher@npm:0.1.1":
version: 0.1.1
resolution: "@rushstack/problem-matcher@npm:0.1.1"
peerDependencies:
"@types/node": "*"
peerDependenciesMeta:
"@types/node":
optional: true
checksum: 10/a47c2d5fd0e3bbe7336f06c29ef91061e36ab8dafd04c861392806e60a3366fd8c3921be217adc71d039c8749f6b70a06c874ff314501eed5b7f8fb7b42c7a39
languageName: node
linkType: hard
"@rushstack/rig-package@npm:0.6.0":
version: 0.6.0
resolution: "@rushstack/rig-package@npm:0.6.0"
dependencies:
resolve: "npm:~1.22.1"
strip-json-comments: "npm:~3.1.1"
checksum: 10/6ca5d6615365dfe4d78fdc52a1a145bec92bba79d8692db91d05c774b4ec4d9dc6c41b31949708d0312896b9c1c205a0f0eaa32f51ac7b1780415ac51c76af71
languageName: node
linkType: hard
"@rushstack/terminal@npm:0.19.1":
version: 0.19.1
resolution: "@rushstack/terminal@npm:0.19.1"
dependencies:
"@rushstack/node-core-library": "npm:5.17.0"
"@rushstack/problem-matcher": "npm:0.1.1"
supports-color: "npm:~8.1.1"
peerDependencies:
"@types/node": "*"
peerDependenciesMeta:
"@types/node":
optional: true
checksum: 10/57f39076b0a8ffcb4631bb670ac7bbc748e3ecb339cdfcf3611ea51436b6235d0a6a7da0f457e0f7fd52787e71ff449d1026bbabc51d446c8e6f941eb0ba5572
languageName: node
linkType: hard
"@rushstack/ts-command-line@npm:5.1.1":
version: 5.1.1
resolution: "@rushstack/ts-command-line@npm:5.1.1"
dependencies:
"@rushstack/terminal": "npm:0.19.1"
"@types/argparse": "npm:1.0.38"
argparse: "npm:~1.0.9"
string-argv: "npm:~0.3.1"
checksum: 10/53daad744574dec994be46ea7eba519753b4e05001f55d4cfb4715d5f821e830695ea04fe9bb4104dd411dd837e7e7f34706453cbc6ca0056acdcab314bc6fdc
languageName: node
linkType: hard
"@sinclair/typebox@npm:^0.34.0":
version: 0.34.41
resolution: "@sinclair/typebox@npm:0.34.41"
@@ -5945,6 +6053,13 @@ __metadata:
languageName: node
linkType: hard
"@types/argparse@npm:1.0.38":
version: 1.0.38
resolution: "@types/argparse@npm:1.0.38"
checksum: 10/26ed7e3f1e3595efdb883a852f5205f971b798e4c28b7e30a32c5298eee596e8b45834ce831f014d250b9730819ab05acff5b31229666d3af4ba465b4697d0eb
languageName: node
linkType: hard
"@types/aria-query@npm:^5.0.1":
version: 5.0.4
resolution: "@types/aria-query@npm:5.0.4"
@@ -6508,6 +6623,15 @@ __metadata:
languageName: node
linkType: hard
"@types/react@npm:^19.1.4":
version: 19.1.13
resolution: "@types/react@npm:19.1.13"
dependencies:
csstype: "npm:^3.0.2"
checksum: 10/a4e12df335ded76e931cc2ba2a4c8a61872ed840081eca83612fbdadc4afbf0cbd0ae31fdedc7fae7f0e02c90dac98dda517dfa73bec653dd4b1de2755431a62
languageName: node
linkType: hard
"@types/resolve@npm:1.20.2":
version: 1.20.2
resolution: "@types/resolve@npm:1.20.2"
@@ -7202,6 +7326,94 @@ __metadata:
languageName: node
linkType: hard
"@volar/language-core@npm:2.4.23, @volar/language-core@npm:~2.4.11":
version: 2.4.23
resolution: "@volar/language-core@npm:2.4.23"
dependencies:
"@volar/source-map": "npm:2.4.23"
checksum: 10/5c7a330be253580fff0ae4308fdd385680de3888b676fdc032bfc4a69f1014db5167e48347b2f85232503b09373deb495206322d665b995dcd99bd1da5eff2f1
languageName: node
linkType: hard
"@volar/source-map@npm:2.4.23":
version: 2.4.23
resolution: "@volar/source-map@npm:2.4.23"
checksum: 10/511aeb03fb3715232e125cad24c011a11740b5572bac29fbf4ad34fe3374671178b67b7730ba3bb926568a715858fe433e1ff7661deeea4838095e4b4758613c
languageName: node
linkType: hard
"@volar/typescript@npm:^2.4.11":
version: 2.4.23
resolution: "@volar/typescript@npm:2.4.23"
dependencies:
"@volar/language-core": "npm:2.4.23"
path-browserify: "npm:^1.0.1"
vscode-uri: "npm:^3.0.8"
checksum: 10/8dcf522ea5e479a90b37389b852e38372080a2077d6e2ef8f6468bc640e89523dae99e5be85f579fe61ef2951a8d5bfeb656bb2e48ca1695dbd43e1e6749fb39
languageName: node
linkType: hard
"@vue/compiler-core@npm:3.5.21":
version: 3.5.21
resolution: "@vue/compiler-core@npm:3.5.21"
dependencies:
"@babel/parser": "npm:^7.28.3"
"@vue/shared": "npm:3.5.21"
entities: "npm:^4.5.0"
estree-walker: "npm:^2.0.2"
source-map-js: "npm:^1.2.1"
checksum: 10/a50ccf92a9560490be40fed324b43d76c75d3df024fe4d52ac1630b1027b39035f9af8dca84f6c75e6063c0812fde8de36b43feb77b7af01a3ed7ca689f1680c
languageName: node
linkType: hard
"@vue/compiler-dom@npm:^3.5.0":
version: 3.5.21
resolution: "@vue/compiler-dom@npm:3.5.21"
dependencies:
"@vue/compiler-core": "npm:3.5.21"
"@vue/shared": "npm:3.5.21"
checksum: 10/371f8a88c1d3906feb39d8e06ccebc1a4459f1ebbb39f8b2081c424b6fc69a0f9f791a1e9a64ce825c54368bd0b6c518ab8a4d75a969ae3320824c68e747fdf7
languageName: node
linkType: hard
"@vue/compiler-vue2@npm:^2.7.16":
version: 2.7.16
resolution: "@vue/compiler-vue2@npm:2.7.16"
dependencies:
de-indent: "npm:^1.0.2"
he: "npm:^1.2.0"
checksum: 10/739ad06be19206b2715707c226a070509bcf28c31b539a6fc932d220eb7b0c09109d71fded573ed0c4073429793a3513ca4a4e69ad4f7afc0c5bc3c28639e871
languageName: node
linkType: hard
"@vue/language-core@npm:2.2.0":
version: 2.2.0
resolution: "@vue/language-core@npm:2.2.0"
dependencies:
"@volar/language-core": "npm:~2.4.11"
"@vue/compiler-dom": "npm:^3.5.0"
"@vue/compiler-vue2": "npm:^2.7.16"
"@vue/shared": "npm:^3.5.0"
alien-signals: "npm:^0.4.9"
minimatch: "npm:^9.0.3"
muggle-string: "npm:^0.4.1"
path-browserify: "npm:^1.0.1"
peerDependencies:
typescript: "*"
peerDependenciesMeta:
typescript:
optional: true
checksum: 10/dc22d038509a58e5a5569fe19f67e7373067cde3531b1e405270dcbe026a8cdbb1de8a7a93d6aaa76a3c5b71393f5c6ec5d9125f0b050898cb96a5c62b4a8d88
languageName: node
linkType: hard
"@vue/shared@npm:3.5.21, @vue/shared@npm:^3.5.0":
version: 3.5.21
resolution: "@vue/shared@npm:3.5.21"
checksum: 10/2bb8b884e88383d24ccbd31246d3079f53bc14ee3766c20364b3e2fff4f6d21d9900d8c88dc72acbad9f8a3981356297593b013562e51f3b85e78206e90a2e70
languageName: node
linkType: hard
"@xmldom/xmldom@npm:^0.8.8":
version: 0.8.11
resolution: "@xmldom/xmldom@npm:0.8.11"
@@ -7361,6 +7573,32 @@ __metadata:
languageName: node
linkType: hard
"ajv-draft-04@npm:~1.0.0":
version: 1.0.0
resolution: "ajv-draft-04@npm:1.0.0"
peerDependencies:
ajv: ^8.5.0
peerDependenciesMeta:
ajv:
optional: true
checksum: 10/3f11fa0e7f7359bef6608657f02ab78e9cc62b1fb7bdd860db0d00351b3863a1189c1a23b72466d2d82726cab4eb20725c76f5e7c134a89865e2bfd0e6828137
languageName: node
linkType: hard
"ajv-formats@npm:~3.0.1":
version: 3.0.1
resolution: "ajv-formats@npm:3.0.1"
dependencies:
ajv: "npm:^8.0.0"
peerDependencies:
ajv: ^8.0.0
peerDependenciesMeta:
ajv:
optional: true
checksum: 10/5679b9f9ced9d0213a202a37f3aa91efcffe59a6de1a6e3da5c873344d3c161820a1f11cc29899661fee36271fd2895dd3851b6461c902a752ad661d1c1e8722
languageName: node
linkType: hard
"ajv-keywords@npm:^3.4.1":
version: 3.5.2
resolution: "ajv-keywords@npm:3.5.2"
@@ -7382,7 +7620,7 @@ __metadata:
languageName: node
linkType: hard
"ajv@npm:^8.6.0":
"ajv@npm:^8.0.0, ajv@npm:^8.6.0":
version: 8.17.1
resolution: "ajv@npm:8.17.1"
dependencies:
@@ -7394,6 +7632,37 @@ __metadata:
languageName: node
linkType: hard
"ajv@npm:~8.12.0":
version: 8.12.0
resolution: "ajv@npm:8.12.0"
dependencies:
fast-deep-equal: "npm:^3.1.1"
json-schema-traverse: "npm:^1.0.0"
require-from-string: "npm:^2.0.2"
uri-js: "npm:^4.2.2"
checksum: 10/b406f3b79b5756ac53bfe2c20852471b08e122bc1ee4cde08ae4d6a800574d9cd78d60c81c69c63ff81e4da7cd0b638fafbb2303ae580d49cf1600b9059efb85
languageName: node
linkType: hard
"ajv@npm:~8.13.0":
version: 8.13.0
resolution: "ajv@npm:8.13.0"
dependencies:
fast-deep-equal: "npm:^3.1.3"
json-schema-traverse: "npm:^1.0.0"
require-from-string: "npm:^2.0.2"
uri-js: "npm:^4.4.1"
checksum: 10/4ada268c9a6e44be87fd295df0f0a91267a7bae8dbc8a67a2d5799c3cb459232839c99d18b035597bb6e3ffe88af6979f7daece854f590a81ebbbc2dfa80002c
languageName: node
linkType: hard
"alien-signals@npm:^0.4.9":
version: 0.4.14
resolution: "alien-signals@npm:0.4.14"
checksum: 10/306a7f4a88a982d1619ff313ed078bdfe52bb9d1381590ef4c1c245812ce7274b9b645a6233214e764a1adbef21863bdf74d10aa4fb30917456b7dd7779df5fc
languageName: node
linkType: hard
"ansi-escapes@npm:^7.0.0":
version: 7.1.1
resolution: "ansi-escapes@npm:7.1.1"
@@ -7524,6 +7793,15 @@ __metadata:
languageName: node
linkType: hard
"argparse@npm:~1.0.9":
version: 1.0.10
resolution: "argparse@npm:1.0.10"
dependencies:
sprintf-js: "npm:~1.0.2"
checksum: 10/c6a621343a553ff3779390bb5ee9c2263d6643ebcd7843227bdde6cc7adbed796eb5540ca98db19e3fd7b4714e1faa51551f8849b268bb62df27ddb15cbcd91e
languageName: node
linkType: hard
"aria-hidden@npm:^1.2.4":
version: 1.2.6
resolution: "aria-hidden@npm:1.2.6"
@@ -8984,6 +9262,20 @@ __metadata:
languageName: node
linkType: hard
"confbox@npm:^0.1.8":
version: 0.1.8
resolution: "confbox@npm:0.1.8"
checksum: 10/4ebcfb1c6a3b25276734ec5722e88768eb61fc02f98e11960b845c5c62bc27fd05f493d2a8244d9675b24ef95afe4c0d511cdcad02c72f5eeea463cc26687999
languageName: node
linkType: hard
"confbox@npm:^0.2.2":
version: 0.2.2
resolution: "confbox@npm:0.2.2"
checksum: 10/988c7216f9b5aee5d8a8f32153a9164e1b58d92d8335c5daa323fd3fdee91f742ffc25f6c28b059474b6319204085eca985ab14c5a246988dc7ef1fe29414108
languageName: node
linkType: hard
"config-file-ts@npm:0.2.8-rc1":
version: 0.2.8-rc1
resolution: "config-file-ts@npm:0.2.8-rc1"
@@ -9566,6 +9858,13 @@ __metadata:
languageName: node
linkType: hard
"de-indent@npm:^1.0.2":
version: 1.0.2
resolution: "de-indent@npm:1.0.2"
checksum: 10/30bf43744dca005f9252dbb34ed95dcb3c30dfe52bfed84973b89c29eccff04e27769f222a34c61a93354acf47457785e9032e6184be390ed1d324fb9ab3f427
languageName: node
linkType: hard
"debug@npm:4, debug@npm:^4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.3.7, debug@npm:^4.4.0, debug@npm:^4.4.1, debug@npm:^4.4.3":
version: 4.4.3
resolution: "debug@npm:4.4.3"
@@ -10222,7 +10521,7 @@ __metadata:
languageName: node
linkType: hard
"entities@npm:^4.2.0, entities@npm:^4.4.0":
"entities@npm:^4.2.0, entities@npm:^4.4.0, entities@npm:^4.5.0":
version: 4.5.0
resolution: "entities@npm:4.5.0"
checksum: 10/ede2a35c9bce1aeccd055a1b445d41c75a14a2bb1cd22e242f20cf04d236cdcd7f9c859eb83f76885327bfae0c25bf03303665ee1ce3d47c5927b98b0e3e3d48
@@ -11143,6 +11442,13 @@ __metadata:
languageName: node
linkType: hard
"exsolve@npm:^1.0.7":
version: 1.0.7
resolution: "exsolve@npm:1.0.7"
checksum: 10/0c9fc0964da0154f38b55e612ed29bf5040f753d5d2db3a63559762237d0a86290e2f18997973343bb9900c07ab1e48596321de9d9d338e373b1f3f1a015e4c9
languageName: node
linkType: hard
"extend@npm:^3.0.0":
version: 3.0.2
resolution: "extend@npm:3.0.2"
@@ -11277,7 +11583,7 @@ __metadata:
languageName: node
linkType: hard
"fdir@npm:^6.5.0":
"fdir@npm:^6.4.4, fdir@npm:^6.5.0":
version: 6.5.0
resolution: "fdir@npm:6.5.0"
peerDependencies:
@@ -11489,7 +11795,7 @@ __metadata:
languageName: node
linkType: hard
"fs-extra@npm:^11.1.1, fs-extra@npm:^11.2.0, fs-extra@npm:^11.3.2":
"fs-extra@npm:^11.1.1, fs-extra@npm:^11.2.0, fs-extra@npm:^11.3.2, fs-extra@npm:~11.3.0":
version: 11.3.2
resolution: "fs-extra@npm:11.3.2"
dependencies:
@@ -12165,6 +12471,15 @@ __metadata:
languageName: node
linkType: hard
"he@npm:^1.2.0":
version: 1.2.0
resolution: "he@npm:1.2.0"
bin:
he: bin/he
checksum: 10/d09b2243da4e23f53336e8de3093e5c43d2c39f8d0d18817abfa32ce3e9355391b2edb4bb5edc376aea5d4b0b59d6a0482aab4c52bc02ef95751e4b818e847f1
languageName: node
linkType: hard
"heimdalljs-logger@npm:^0.1.10, heimdalljs-logger@npm:^0.1.7":
version: 0.1.10
resolution: "heimdalljs-logger@npm:0.1.10"
@@ -12576,6 +12891,13 @@ __metadata:
languageName: node
linkType: hard
"import-lazy@npm:~4.0.0":
version: 4.0.0
resolution: "import-lazy@npm:4.0.0"
checksum: 10/943309cc8eb01ada12700448c288b0384f77a1bc33c7e00fa4cb223c665f467a13ce9aaceb8d2e4cf586b07c1d2828040263dcc069873ce63cfc2ac6fd087971
languageName: node
linkType: hard
"imurmurhash@npm:^0.1.4":
version: 0.1.4
resolution: "imurmurhash@npm:0.1.4"
@@ -13340,6 +13662,13 @@ __metadata:
languageName: node
linkType: hard
"jju@npm:~1.4.0":
version: 1.4.0
resolution: "jju@npm:1.4.0"
checksum: 10/1067ff8ce02221faac5a842116ed0ec79a53312a111d0bf8342a80bd02c0a3fdf0b8449694a65947db0a3e8420e8b326dffb489c7dd5866efc380c0d1708a707
languageName: node
linkType: hard
"jose@npm:^4.15.9":
version: 4.15.9
resolution: "jose@npm:4.15.9"
@@ -13629,6 +13958,13 @@ __metadata:
languageName: node
linkType: hard
"kolorist@npm:^1.8.0":
version: 1.8.0
resolution: "kolorist@npm:1.8.0"
checksum: 10/71d5d122951cc65f2f14c3e1d7f8fd91694b374647d4f6deec3816d018cd04a44edd9578d93e00c82c2053b925e5d30a0565746c4171f4ca9fce1a13bd5f3315
languageName: node
linkType: hard
"kuler@npm:^2.0.0":
version: 2.0.0
resolution: "kuler@npm:2.0.0"
@@ -13763,6 +14099,17 @@ __metadata:
languageName: node
linkType: hard
"local-pkg@npm:^1.0.0":
version: 1.1.2
resolution: "local-pkg@npm:1.1.2"
dependencies:
mlly: "npm:^1.7.4"
pkg-types: "npm:^2.3.0"
quansync: "npm:^0.2.11"
checksum: 10/761d82f40849e4721fa50d86782cf75bc2befb0696f32ac99869fb6f3033b904e4018f4bb8cdfde994d710816480dc1aba8e462c67ec20fe89d4700a245d17f8
languageName: node
linkType: hard
"locate-path@npm:^6.0.0":
version: 6.0.0
resolution: "locate-path@npm:6.0.0"
@@ -13849,7 +14196,7 @@ __metadata:
languageName: node
linkType: hard
"lodash@npm:^4.17.15, lodash@npm:^4.17.20, lodash@npm:^4.17.21":
"lodash@npm:^4.17.15, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:~4.17.15":
version: 4.17.21
resolution: "lodash@npm:4.17.21"
checksum: 10/c08619c038846ea6ac754abd6dd29d2568aa705feb69339e836dfa8d8b09abbb2f859371e86863eda41848221f9af43714491467b5b0299122431e202bb0c532
@@ -14938,7 +15285,7 @@ __metadata:
languageName: node
linkType: hard
"minimatch@npm:^10.0.0, minimatch@npm:^10.0.3":
"minimatch@npm:10.0.3, minimatch@npm:^10.0.0, minimatch@npm:^10.0.3":
version: 10.0.3
resolution: "minimatch@npm:10.0.3"
dependencies:
@@ -15137,6 +15484,18 @@ __metadata:
languageName: node
linkType: hard
"mlly@npm:^1.7.4":
version: 1.8.0
resolution: "mlly@npm:1.8.0"
dependencies:
acorn: "npm:^8.15.0"
pathe: "npm:^2.0.3"
pkg-types: "npm:^1.3.1"
ufo: "npm:^1.6.1"
checksum: 10/4db690a421076d5fe88331679f702b77a4bfc9fe3f324bc6150270fb0b69ecd4b5e43570b8e4573dde341515b3eac4daa720a6ac9f2715c210b670852641ab1c
languageName: node
linkType: hard
"mockdate@npm:^3.0.5":
version: 3.0.5
resolution: "mockdate@npm:3.0.5"
@@ -15158,6 +15517,13 @@ __metadata:
languageName: node
linkType: hard
"muggle-string@npm:^0.4.1":
version: 0.4.1
resolution: "muggle-string@npm:0.4.1"
checksum: 10/8fa2ea08f497c04069718bd3fd1909b382114dacbad832d10967ca72690de43f5f8492d8ccfbf827d6be63868ed5fc10395e7b7c082aa95997eea498586c6620
languageName: node
linkType: hard
"murmurhash@npm:^2.0.1":
version: 2.0.1
resolution: "murmurhash@npm:2.0.1"
@@ -16008,7 +16374,7 @@ __metadata:
languageName: node
linkType: hard
"pathe@npm:^2.0.3":
"pathe@npm:^2.0.1, pathe@npm:^2.0.3":
version: 2.0.3
resolution: "pathe@npm:2.0.3"
checksum: 10/01e9a69928f39087d96e1751ce7d6d50da8c39abf9a12e0ac2389c42c83bc76f78c45a475bd9026a02e6a6f79be63acc75667df855862fe567d99a00a540d23d
@@ -16198,7 +16564,7 @@ __metadata:
languageName: node
linkType: hard
"postcss@npm:^8.5.6":
"postcss@npm:^8.5.3, postcss@npm:^8.5.6":
version: 8.5.6
resolution: "postcss@npm:8.5.6"
dependencies:
@@ -16468,6 +16834,13 @@ __metadata:
languageName: node
linkType: hard
"quansync@npm:^0.2.11":
version: 0.2.11
resolution: "quansync@npm:0.2.11"
checksum: 10/d4f0cc21a25052a8a6183f17752a6221829c4795b40641de67c06945b356841ff00296d3700d0332dfe8e86100fdcc02f4be7559f3f1774a753b05adb7800d01
languageName: node
linkType: hard
"querystring-es3@npm:^0.2.1":
version: 0.2.1
resolution: "querystring-es3@npm:0.2.1"
@@ -17437,7 +17810,7 @@ __metadata:
languageName: node
linkType: hard
"resolve@npm:^1.10.0, resolve@npm:^1.10.1, resolve@npm:^1.17.0, resolve@npm:^1.19.0, resolve@npm:^1.22.1, resolve@npm:^1.22.10, resolve@npm:^1.22.4":
"resolve@npm:^1.10.0, resolve@npm:^1.10.1, resolve@npm:^1.17.0, resolve@npm:^1.19.0, resolve@npm:^1.22.1, resolve@npm:^1.22.10, resolve@npm:^1.22.4, resolve@npm:~1.22.1, resolve@npm:~1.22.2":
version: 1.22.10
resolution: "resolve@npm:1.22.10"
dependencies:
@@ -17463,7 +17836,7 @@ __metadata:
languageName: node
linkType: hard
"resolve@patch:resolve@npm%3A^1.10.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.10.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.17.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.10#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin<compat/resolve>":
"resolve@patch:resolve@npm%3A^1.10.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.10.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.17.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.10#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A~1.22.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A~1.22.2#optional!builtin<compat/resolve>":
version: 1.22.10
resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin<compat/resolve>::version=1.22.10&hash=c3c19d"
dependencies:
@@ -17886,6 +18259,17 @@ __metadata:
languageName: node
linkType: hard
"semver@npm:~7.5.4":
version: 7.5.4
resolution: "semver@npm:7.5.4"
dependencies:
lru-cache: "npm:^6.0.0"
bin:
semver: bin/semver.js
checksum: 10/985dec0d372370229a262c737063860fabd4a1c730662c1ea3200a2f649117761a42184c96df62a0e885e76fbd5dace41087d6c1ac0351b13c0df5d6bcb1b5ac
languageName: node
linkType: hard
"send@npm:^1.1.0, send@npm:^1.2.0":
version: 1.2.0
resolution: "send@npm:1.2.0"
@@ -18288,7 +18672,7 @@ __metadata:
languageName: node
linkType: hard
"source-map@npm:^0.6.0, source-map@npm:^0.6.1":
"source-map@npm:^0.6.0, source-map@npm:^0.6.1, source-map@npm:~0.6.1":
version: 0.6.1
resolution: "source-map@npm:0.6.1"
checksum: 10/59ef7462f1c29d502b3057e822cdbdae0b0e565302c4dd1a95e11e793d8d9d62006cdc10e0fd99163ca33ff2071360cf50ee13f90440806e7ed57d81cba2f7ff
@@ -18366,6 +18750,13 @@ __metadata:
languageName: node
linkType: hard
"sprintf-js@npm:~1.0.2":
version: 1.0.3
resolution: "sprintf-js@npm:1.0.3"
checksum: 10/c34828732ab8509c2741e5fd1af6b767c3daf2c642f267788f933a65b1614943c282e74c4284f4fa749c264b18ee016a0d37a3e5b73aee446da46277d3a85daa
languageName: node
linkType: hard
"ssri@npm:^12.0.0":
version: 12.0.0
resolution: "ssri@npm:12.0.0"
@@ -18485,7 +18876,7 @@ __metadata:
languageName: node
linkType: hard
"string-argv@npm:^0.3.2":
"string-argv@npm:^0.3.2, string-argv@npm:~0.3.1":
version: 0.3.2
resolution: "string-argv@npm:0.3.2"
checksum: 10/f9d3addf887026b4b5f997a271149e93bf71efc8692e7dc0816e8807f960b18bcb9787b45beedf0f97ff459575ee389af3f189d8b649834cac602f2e857e75af
@@ -18721,7 +19112,7 @@ __metadata:
languageName: node
linkType: hard
"strip-json-comments@npm:^3.1.1":
"strip-json-comments@npm:^3.1.1, strip-json-comments@npm:~3.1.1":
version: 3.1.1
resolution: "strip-json-comments@npm:3.1.1"
checksum: 10/492f73e27268f9b1c122733f28ecb0e7e8d8a531a6662efbd08e22cccb3f9475e90a1b82cab06a392f6afae6d2de636f977e231296400d0ec5304ba70f166443
@@ -18830,6 +19221,15 @@ __metadata:
languageName: node
linkType: hard
"supports-color@npm:~8.1.1":
version: 8.1.1
resolution: "supports-color@npm:8.1.1"
dependencies:
has-flag: "npm:^4.0.0"
checksum: 10/157b534df88e39c5518c5e78c35580c1eca848d7dbaf31bbe06cdfc048e22c7ff1a9d046ae17b25691128f631a51d9ec373c1b740c12ae4f0de6e292037e4282
languageName: node
linkType: hard
"supports-preserve-symlinks-flag@npm:^1.0.0":
version: 1.0.0
resolution: "supports-preserve-symlinks-flag@npm:1.0.0"
@@ -19081,7 +19481,7 @@ __metadata:
languageName: node
linkType: hard
"tinyglobby@npm:^0.2.10, tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.14, tinyglobby@npm:^0.2.15":
"tinyglobby@npm:^0.2.10, tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.13, tinyglobby@npm:^0.2.14, tinyglobby@npm:^0.2.15":
version: 0.2.15
resolution: "tinyglobby@npm:0.2.15"
dependencies:
@@ -19480,7 +19880,7 @@ __metadata:
languageName: node
linkType: hard
"typescript-eslint@npm:^8.46.0":
"typescript-eslint@npm:^8.18.1, typescript-eslint@npm:^8.46.0":
version: 8.46.0
resolution: "typescript-eslint@npm:8.46.0"
dependencies:
@@ -19511,7 +19911,17 @@ __metadata:
languageName: node
linkType: hard
"typescript@npm:^5.0.4, typescript@npm:^5.4.3, typescript@npm:^5.9.3":
"typescript@npm:5.8.2":
version: 5.8.2
resolution: "typescript@npm:5.8.2"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 10/dbc2168a55d56771f4d581997be52bab5cbc09734fec976cfbaabd787e61fb4c6cf9125fd48c6f98054ce549c77ecedefc7f64252a830dd8e9c3381f61fbeb78
languageName: node
linkType: hard
"typescript@npm:^5.0.4, typescript@npm:^5.4.3, typescript@npm:^5.5.4, typescript@npm:^5.9.3":
version: 5.9.3
resolution: "typescript@npm:5.9.3"
bin:
@@ -19521,7 +19931,17 @@ __metadata:
languageName: node
linkType: hard
"typescript@patch:typescript@npm%3A^5.0.4#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.4.3#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.9.3#optional!builtin<compat/typescript>":
"typescript@patch:typescript@npm%3A5.8.2#optional!builtin<compat/typescript>":
version: 5.8.2
resolution: "typescript@patch:typescript@npm%3A5.8.2#optional!builtin<compat/typescript>::version=5.8.2&hash=5786d5"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 10/97920a082ffc57583b1cb6bc4faa502acc156358e03f54c7fc7fdf0b61c439a717f4c9070c449ee9ee683d4cfc3bb203127c2b9794b2950f66d9d307a4ff262c
languageName: node
linkType: hard
"typescript@patch:typescript@npm%3A^5.0.4#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.4.3#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.5.4#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.9.3#optional!builtin<compat/typescript>":
version: 5.9.3
resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin<compat/typescript>::version=5.9.3&hash=5786d5"
bin:
@@ -19558,6 +19978,13 @@ __metadata:
languageName: node
linkType: hard
"ufo@npm:^1.6.1":
version: 1.6.1
resolution: "ufo@npm:1.6.1"
checksum: 10/088a68133b93af183b093e5a8730a40fe7fd675d3dc0656ea7512f180af45c92300c294f14d4d46d4b2b553e3e52d3b13d4856b9885e620e7001edf85531234e
languageName: node
linkType: hard
"uglify-js@npm:^3.1.4":
version: 3.19.3
resolution: "uglify-js@npm:3.19.3"
@@ -19888,7 +20315,7 @@ __metadata:
languageName: node
linkType: hard
"uri-js@npm:^4.2.2":
"uri-js@npm:^4.2.2, uri-js@npm:^4.4.1":
version: 4.4.1
resolution: "uri-js@npm:4.4.1"
dependencies:
@@ -20160,6 +20587,29 @@ __metadata:
languageName: node
linkType: hard
"vite-plugin-dts@npm:^4.5.3":
version: 4.5.4
resolution: "vite-plugin-dts@npm:4.5.4"
dependencies:
"@microsoft/api-extractor": "npm:^7.50.1"
"@rollup/pluginutils": "npm:^5.1.4"
"@volar/typescript": "npm:^2.4.11"
"@vue/language-core": "npm:2.2.0"
compare-versions: "npm:^6.1.1"
debug: "npm:^4.4.0"
kolorist: "npm:^1.8.0"
local-pkg: "npm:^1.0.0"
magic-string: "npm:^0.30.17"
peerDependencies:
typescript: "*"
vite: "*"
peerDependenciesMeta:
vite:
optional: true
checksum: 10/1504a8da02f8015c3138ef57a690179ad9b9d4369a054ae48b52a1c8cbc7ad05474551eab309cd90cf758800e17b67ccd13317aa5f8e28efde957547c7a339fb
languageName: node
linkType: hard
"vite-plugin-node-polyfills@npm:^0.24.0":
version: 0.24.0
resolution: "vite-plugin-node-polyfills@npm:0.24.0"
@@ -20273,6 +20723,61 @@ __metadata:
languageName: node
linkType: hard
"vite@npm:^6.2.0":
version: 6.3.6
resolution: "vite@npm:6.3.6"
dependencies:
esbuild: "npm:^0.25.0"
fdir: "npm:^6.4.4"
fsevents: "npm:~2.3.3"
picomatch: "npm:^4.0.2"
postcss: "npm:^8.5.3"
rollup: "npm:^4.34.9"
tinyglobby: "npm:^0.2.13"
peerDependencies:
"@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0
jiti: ">=1.21.0"
less: "*"
lightningcss: ^1.21.0
sass: "*"
sass-embedded: "*"
stylus: "*"
sugarss: "*"
terser: ^5.16.0
tsx: ^4.8.1
yaml: ^2.4.2
dependenciesMeta:
fsevents:
optional: true
peerDependenciesMeta:
"@types/node":
optional: true
jiti:
optional: true
less:
optional: true
lightningcss:
optional: true
sass:
optional: true
sass-embedded:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
tsx:
optional: true
yaml:
optional: true
bin:
vite: bin/vite.js
checksum: 10/8b8b6fe12318ca457396bf2053df7056cf4810f1d4a43b36b6afe59860e32b749c0685a290fe8a973b0d3da179ceec4c30cebbd3c91d0c47fbcf6436b17bdeef
languageName: node
linkType: hard
"vitest@npm:^3.2.4":
version: 3.2.4
resolution: "vitest@npm:3.2.4"