Bundle loot-core types into the API (#2053)

* Bundle loot-core types into the API

So we can have loot-core be the source of truth
for some types that get passed through

- Improves downstream development with API by including types
- Use path aliases for dist vs dev tsconfigs
- Convert api index to typescript as example
- Permit ts-ignore for issues with our version of typescript

---------

Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
This commit is contained in:
DJ Mountney
2024-01-20 10:30:23 -08:00
committed by GitHub
parent dd254c6c23
commit 0045d9212e
19 changed files with 190 additions and 62 deletions

View File

@@ -1,5 +1,6 @@
packages/api/app/bundle.api.js
packages/api/dist
packages/api/@types
packages/api/migrations
packages/crdt/dist

View File

@@ -165,6 +165,11 @@ module.exports = {
{ patterns: [...restrictedImportPatterns, ...restrictedImportColors] },
],
'@typescript-eslint/ban-ts-comment': [
'error',
{ 'ts-ignore': 'allow-with-description' },
],
// Rules disable during TS migration
'@typescript-eslint/no-var-requires': 'off',
'prefer-const': 'warn',

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@
!data/.gitkeep
/data2
packages/api/dist
packages/api/@types
packages/crdt/dist
packages/desktop-electron/client-build
packages/desktop-electron/.electron-symbols

View File

@@ -1,38 +0,0 @@
// eslint-disable-next-line import/extensions
import * as bundle from './app/bundle.api.js';
import * as injected from './injected';
import { validateNodeVersion } from './validateNodeVersion';
let actualApp;
export const internal = bundle.lib;
// DEPRECATED: remove the next line in @actual-app/api v7
export * as methods from './methods';
export * from './methods';
export * as utils from './utils';
export async function init(config = {}) {
if (actualApp) {
return;
}
validateNodeVersion();
global.fetch = (...args) =>
import('node-fetch').then(({ default: fetch }) => fetch(...args));
await bundle.init(config);
actualApp = bundle.lib;
injected.override(bundle.lib.send);
return bundle.lib;
}
export async function shutdown() {
if (actualApp) {
await actualApp.send('sync');
await actualApp.send('close-budget');
actualApp = null;
}
}

53
packages/api/index.ts Normal file
View File

@@ -0,0 +1,53 @@
import type {
RequestInfo as FetchInfo,
RequestInit as FetchInit,
// @ts-ignore: false-positive commonjs module error on build until typescript 5.3
} from 'node-fetch'; // with { 'resolution-mode': 'import' };
// loot-core types
import type { InitConfig } from 'loot-core/server/main';
// @ts-ignore: bundle not available until we build it
// eslint-disable-next-line import/extensions
import * as bundle from './app/bundle.api.js';
import * as injected from './injected';
import { validateNodeVersion } from './validateNodeVersion';
let actualApp: null | typeof bundle.lib;
export const internal = bundle.lib;
// DEPRECATED: remove the next line in @actual-app/api v7
export * as methods from './methods';
export * from './methods';
export * as utils from './utils';
export async function init(config: InitConfig = {}) {
if (actualApp) {
return;
}
validateNodeVersion();
if (!globalThis.fetch) {
globalThis.fetch = (url: URL | RequestInfo, init?: RequestInit) => {
return import('node-fetch').then(({ default: fetch }) =>
fetch(url as unknown as FetchInfo, init as unknown as FetchInit),
) as unknown as Promise<Response>;
};
}
await bundle.init(config);
actualApp = bundle.lib;
injected.override(bundle.lib.send);
return bundle.lib;
}
export async function shutdown() {
if (actualApp) {
await actualApp.send('sync');
await actualApp.send('close-budget');
actualApp = null;
}
}

View File

@@ -1,8 +1,14 @@
// @ts-strict-ignore
import type { Handlers } from 'loot-core/src/types/handlers';
import * as injected from './injected';
export { q } from './app/query';
function send(name, args) {
function send<K extends keyof Handlers, T extends Handlers[K]>(
name: K,
args?: Parameters<T>[0],
): Promise<Awaited<ReturnType<T>>> {
return injected.send(name, args);
}
@@ -21,7 +27,7 @@ export async function loadBudget(budgetId) {
return send('api/load-budget', { id: budgetId });
}
export async function downloadBudget(syncId, { password } = {}) {
export async function downloadBudget(syncId, { password }: { password? } = {}) {
return send('api/download-budget', { syncId, password });
}
@@ -91,7 +97,7 @@ export function getAccounts() {
return send('api/accounts-get');
}
export function createAccount(account, initialBalance) {
export function createAccount(account, initialBalance?) {
return send('api/account-create', { account, initialBalance });
}
@@ -99,7 +105,7 @@ export function updateAccount(id, fields) {
return send('api/account-update', { id, fields });
}
export function closeAccount(id, transferAccountId, transferCategoryId) {
export function closeAccount(id, transferAccountId?, transferCategoryId?) {
return send('api/account-close', {
id,
transferAccountId,
@@ -123,7 +129,7 @@ export function updateCategoryGroup(id, fields) {
return send('api/category-group-update', { id, fields });
}
export function deleteCategoryGroup(id, transferCategoryId) {
export function deleteCategoryGroup(id, transferCategoryId?) {
return send('api/category-group-delete', { id, transferCategoryId });
}
@@ -139,7 +145,7 @@ export function updateCategory(id, fields) {
return send('api/category-update', { id, fields });
}
export function deleteCategory(id, transferCategoryId) {
export function deleteCategory(id, transferCategoryId?) {
return send('api/category-delete', { id, transferCategoryId });
}

View File

@@ -7,17 +7,18 @@
"node": ">=18.12.0"
},
"main": "dist/index.js",
"types": "dist/index.d.ts",
"types": "@types/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build:app": "yarn workspace loot-core build:api",
"build:node": "tsc --p tsconfig.dist.json",
"build:node": "tsc --p tsconfig.dist.json && tsc-alias -p tsconfig.dist.json",
"build:migrations": "cp migrations/*.sql dist/migrations",
"build:default-db": "cp default-db.sqlite dist/",
"build": "rm -rf dist && yarn run build:app && yarn run build:node && yarn run build:migrations && yarn run build:default-db",
"test": "yarn run build:app && jest -c jest.config.js"
"build": "yarn run clean && yarn run build:app && yarn run build:node && yarn run build:migrations && yarn run build:default-db",
"test": "yarn run build:app && jest -c jest.config.js",
"clean": "rm -rf dist @types"
},
"dependencies": {
"better-sqlite3": "^9.2.2",
@@ -31,6 +32,7 @@
"@types/jest": "^27.5.0",
"@types/uuid": "^9.0.2",
"jest": "^27.0.0",
"tsc-alias": "^1.8.8",
"typescript": "^5.0.2"
}
}

View File

@@ -8,8 +8,12 @@
"moduleResolution": "Node16",
"noEmit": false,
"declaration": true,
"outDir": "dist"
"outDir": "dist",
"declarationDir": "@types",
"paths": {
"loot-core/*": ["./@types/loot-core/*"],
}
},
"include": ["."],
"exclude": ["dist"]
"exclude": ["**/node_modules/*", "dist", "@types"]
}

View File

@@ -0,0 +1,21 @@
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")/.." || exit 1
ROOT="$(pwd -P)"
yarn tsc -p tsconfig.api.json --outDir ../api/@types/loot-core/
# Copy existing handwritten .d.ts files, as tsc doesn't move them for us
dest="../../api/@types/loot-core"
cd src
find . -type f -name "*.d.ts" | while read -r f
do
d=$(dirname "${f}")
d="${dest}/${d}"
mkdir -p "${d}"
cp "${f}" "${d}"
done
cd "$ROOT"
yarn webpack --config ./webpack/webpack.api.config.js;
./bin/copy-migrations ../api

View File

@@ -6,7 +6,7 @@
"scripts": {
"build:node": "cross-env NODE_ENV=production webpack --config ./webpack/webpack.desktop.config.js",
"watch:node": "cross-env NODE_ENV=development webpack --config ./webpack/webpack.desktop.config.js --watch",
"build:api": "cross-env NODE_ENV=development webpack --config ./webpack/webpack.api.config.js; ./bin/copy-migrations ../api",
"build:api": "cross-env NODE_ENV=development ./bin/build-api",
"build:browser": "cross-env NODE_ENV=production ./bin/build-browser",
"watch:browser": "cross-env NODE_ENV=development ./bin/build-browser",
"test": "npm-run-all -cp 'test:*'",

View File

@@ -146,8 +146,8 @@ async function importTransactions(
);
const payeesByTransferAcct = payees
.filter((payee: YNAB5.Payee) => payee?.transfer_acct)
.map((payee: YNAB5.Payee) => [payee.transfer_acct, payee]);
.filter(payee => payee?.transfer_acct)
.map(payee => [payee.transfer_acct, payee] as [string, YNAB5.Payee]);
const payeeTransferAcctHashMap = new Map<string, YNAB5.Payee>(
payeesByTransferAcct,
);

View File

@@ -2260,8 +2260,14 @@ export async function initApp(isDev, socketName) {
}
}
export type InitConfig = {
dataDir?: string;
serverURL?: string;
password?: string;
};
// eslint-disable-next-line import/no-unused-modules
export async function init(config) {
export async function init(config: InitConfig) {
// Get from build
let dataDir, serverURL;

View File

@@ -51,7 +51,9 @@ export async function runHandler<T extends Handlers[keyof Handlers]>(
}
if (mutatingMethods.has(handler)) {
return runMutator(() => handler(args), { undoTag });
return runMutator(() => handler(args), { undoTag }) as Promise<
ReturnType<T>
>;
}
// When closing a file, it clears out all global state for the file. That
@@ -67,7 +69,7 @@ export async function runHandler<T extends Handlers[keyof Handlers]>(
promise.then(() => {
runningMethods.delete(promise);
});
return promise;
return promise as Promise<ReturnType<T>>;
}
// These are useful for tests. Only use them in tests.

View File

@@ -189,11 +189,13 @@ export function getRecurringDescription(config, dateFormat) {
export function recurConfigToRSchedule(config) {
const base: IRuleOptions = {
start: monthUtils.parseDate(config.start),
// @ts-ignore: issues with https://gitlab.com/john.carroll.p/rschedule/-/issues/86
frequency: config.frequency.toUpperCase(),
byHourOfDay: [12],
};
if (config.interval) {
// @ts-ignore: issues with https://gitlab.com/john.carroll.p/rschedule/-/issues/86
base.interval = config.interval;
}

View File

@@ -54,10 +54,11 @@ export interface ApiHandlers {
payees;
}) => Promise<unknown>;
'api/transactions-import': (arg: {
accountId;
transactions;
}) => Promise<unknown>;
'api/transactions-import': (arg: { accountId; transactions }) => Promise<{
errors?: { message: string }[];
added;
updated;
}>;
'api/transactions-add': (arg: {
accountId;

View File

@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"declaration": true,
"emitDeclarationOnly": true,
"allowJs": false,
"noEmit": false,
},
"include": ["./typings", "./src/server/*"],
"exclude": ["**/node_modules/*", "**/build/*", "**/lib-dist/*", "./src/server/bench.ts"],
}

View File

@@ -29,6 +29,10 @@
"module": "ES2022",
// Until/if we build using tsc
"noEmit": true,
"paths": {
// until we turn on composite/references
"loot-core/*": ["./packages/loot-core/src/*"],
},
"plugins": [
{
"name": "typescript-strict-plugin",

View File

@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [twk3]
---
Bundle loot-core types into the API

View File

@@ -31,6 +31,7 @@ __metadata:
compare-versions: "npm:^6.1.0"
jest: "npm:^27.0.0"
node-fetch: "npm:^3.3.2"
tsc-alias: "npm:^1.8.8"
typescript: "npm:^5.0.2"
uuid: "npm:^9.0.0"
languageName: unknown
@@ -6336,7 +6337,7 @@ __metadata:
languageName: node
linkType: hard
"commander@npm:^9.4.1":
"commander@npm:^9.0.0, commander@npm:^9.4.1":
version: 9.5.0
resolution: "commander@npm:9.5.0"
checksum: 41c49b3d0f94a1fbeb0463c85b13f15aa15a9e0b4d5e10a49c0a1d58d4489b549d62262b052ae0aa6cfda53299bee487bfe337825df15e342114dde543f82906
@@ -9216,7 +9217,7 @@ __metadata:
languageName: node
linkType: hard
"globby@npm:^11.1.0":
"globby@npm:^11.0.4, globby@npm:^11.1.0":
version: 11.1.0
resolution: "globby@npm:11.1.0"
dependencies:
@@ -12639,6 +12640,13 @@ __metadata:
languageName: node
linkType: hard
"mylas@npm:^2.1.9":
version: 2.1.13
resolution: "mylas@npm:2.1.13"
checksum: 37f335424463c422f48d50317aa0a34fe410fabb146cbf27b453a0aa743732b5626f56deaa190bca2ce29836f809d88759007976dc78d5d22b75918a00586577
languageName: node
linkType: hard
"nanoid@npm:^3.3.7":
version: 3.3.7
resolution: "nanoid@npm:3.3.7"
@@ -13593,6 +13601,15 @@ __metadata:
languageName: node
linkType: hard
"plimit-lit@npm:^1.2.6":
version: 1.6.1
resolution: "plimit-lit@npm:1.6.1"
dependencies:
queue-lit: "npm:^1.5.1"
checksum: e4eaf018dc311fd4d452954c10992cd8a9eb72d168ec2274bb831d86558422703e1405a8978ffdd5c418654e6a25e10a0765a39bf3ce3a84dc799fe6268e0ea4
languageName: node
linkType: hard
"plist@npm:^3.0.4, plist@npm:^3.0.5":
version: 3.1.0
resolution: "plist@npm:3.1.0"
@@ -13841,6 +13858,13 @@ __metadata:
languageName: node
linkType: hard
"queue-lit@npm:^1.5.1":
version: 1.5.2
resolution: "queue-lit@npm:1.5.2"
checksum: 8dd45c79bd25b33b0c7d587391eb0b4acc4deb797bf92fef62b2d8e7c03b64083f5304f09d52a18267d34d020cc67ccde97a88185b67590eeccb194938ff1f98
languageName: node
linkType: hard
"queue-microtask@npm:^1.2.2":
version: 1.2.3
resolution: "queue-microtask@npm:1.2.3"
@@ -16108,6 +16132,22 @@ __metadata:
languageName: node
linkType: hard
"tsc-alias@npm:^1.8.8":
version: 1.8.8
resolution: "tsc-alias@npm:1.8.8"
dependencies:
chokidar: "npm:^3.5.3"
commander: "npm:^9.0.0"
globby: "npm:^11.0.4"
mylas: "npm:^2.1.9"
normalize-path: "npm:^3.0.0"
plimit-lit: "npm:^1.2.6"
bin:
tsc-alias: dist/bin/index.js
checksum: 145d7bb23a618e1136c8addd4b4ed23a1d503a37d3fc5b3698a993fea9331180a68853b0e78ff50fb3fb7ed95d4996a2d82f77395814bbd1c40adee8a9151d90
languageName: node
linkType: hard
"tsconfck@npm:^2.1.0":
version: 2.1.2
resolution: "tsconfck@npm:2.1.2"