diff --git a/.nvmrc b/.nvmrc index 6f7f377b..3f430af8 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v16 +v18 diff --git a/package.json b/package.json index 4561bb4a..2bda369a 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "strip-ansi": "^7.1.0", "threads": "1.7.0", "type-fest": "3.13.0", + "typeorm": "^0.3.17", "typescript-styled-is": "^2.1.0", "v8-compile-cache-lib": "^3.0.1", "winston": "3.10.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1c9f833b..d6ac3ae2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -229,6 +229,9 @@ dependencies: type-fest: specifier: 3.13.0 version: 3.13.0 + typeorm: + specifier: ^0.3.17 + version: 0.3.17(better-sqlite3@8.5.1)(ts-node@10.9.1) typescript-styled-is: specifier: ^2.1.0 version: 2.1.0(react-dom@18.2.0)(react@18.2.0)(styled-components@6.0.7) @@ -1931,7 +1934,6 @@ packages: engines: {node: '>=12'} dependencies: '@jridgewell/trace-mapping': 0.3.9 - dev: true /@cucumber/ci-environment@9.2.0: resolution: {integrity: sha512-jLzRtVwdtNt+uAmTwvXwW9iGYLEOJFpDSmnx/dgoMGKXUWRx1UHT86Q696CLdgXO8kyTwsgJY0c6n5SW9VitAA==} @@ -3385,7 +3387,6 @@ packages: /@jridgewell/resolve-uri@3.1.1: resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} engines: {node: '>=6.0.0'} - dev: true /@jridgewell/set-array@1.1.2: resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} @@ -3415,7 +3416,6 @@ packages: dependencies: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 - dev: true /@leichtgewicht/ip-codec@2.0.4: resolution: {integrity: sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==} @@ -3970,6 +3970,10 @@ packages: resolution: {integrity: sha512-aywhxHNb6l7COooF3m439eT/6QN8E/RSl5IVboSKthMHcp0GlZYMSoS7546rqDLmFRxTD8f1tu/NIS9vtDwYAg==} dev: true + /@sqltools/formatter@1.2.5: + resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} + dev: false + /@szmarczak/http-timer@4.0.6: resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} engines: {node: '>=10'} @@ -3999,19 +4003,15 @@ packages: /@tsconfig/node10@1.0.9: resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} - dev: true /@tsconfig/node12@1.0.11: resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} - dev: true /@tsconfig/node14@1.0.3: resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} - dev: true /@tsconfig/node16@1.0.4: resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - dev: true /@types/better-sqlite3@7.6.4: resolution: {integrity: sha512-dzrRZCYPXIXfSR1/surNbJ/grU3scTaygS0OMzjlGf71i9sc2fGyHPXXiXmEvNIoE0cGwsanEFMVJxPXmco9Eg==} @@ -4851,7 +4851,6 @@ packages: /acorn-walk@8.2.0: resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} engines: {node: '>=0.4.0'} - dev: true /acorn@8.9.0: resolution: {integrity: sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==} @@ -4988,7 +4987,6 @@ packages: /any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - dev: true /anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} @@ -5004,6 +5002,11 @@ packages: execa: 5.1.1 dev: false + /app-root-path@3.1.0: + resolution: {integrity: sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==} + engines: {node: '>= 6.0.0'} + dev: false + /aproba@1.2.0: resolution: {integrity: sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==} dev: false @@ -5029,7 +5032,6 @@ packages: /arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - dev: true /argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -5397,7 +5399,6 @@ packages: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} dependencies: balanced-match: 1.0.2 - dev: true /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} @@ -5511,7 +5512,6 @@ packages: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 - dev: true /bufferutil@4.0.7: resolution: {integrity: sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==} @@ -5655,7 +5655,6 @@ packages: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - dev: true /chalk@5.3.0: resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} @@ -5776,6 +5775,19 @@ packages: restore-cursor: 3.1.0 dev: true + /cli-highlight@2.1.11: + resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true + dependencies: + chalk: 4.1.2 + highlight.js: 10.7.3 + mz: 2.7.0 + parse5: 5.1.1 + parse5-htmlparser2-tree-adapter: 6.0.1 + yargs: 16.2.0 + dev: false + /cli-spinners@2.9.0: resolution: {integrity: sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==} engines: {node: '>=6'} @@ -5813,8 +5825,6 @@ packages: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - dev: true - optional: true /cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} @@ -6144,7 +6154,6 @@ packages: /create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - dev: true /cross-env@7.0.3: resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} @@ -6325,7 +6334,6 @@ packages: engines: {node: '>=0.11'} dependencies: '@babel/runtime': 7.22.6 - dev: true /debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} @@ -6520,7 +6528,6 @@ packages: /diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} - dev: true /diffie-hellman@5.0.3: resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==} @@ -6646,6 +6653,11 @@ packages: tslib: 2.6.0 dev: true + /dotenv@16.3.1: + resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==} + engines: {node: '>=12'} + dev: false + /dprint@0.37.1: resolution: {integrity: sha512-MVKIVOvk5FSiZQFFZlLv1KiB4zZXAirlZ/m8vtyKu2PTFnkD5d6jVv4aNHvd7M6f1ToganNZ/CqhSwpgadn8Ug==} hasBin: true @@ -8528,7 +8540,6 @@ packages: inherits: 2.0.4 minimatch: 5.1.6 once: 1.4.0 - dev: true /global-agent@3.0.0: resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} @@ -8760,6 +8771,10 @@ packages: hasBin: true dev: true + /highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + dev: false + /hmac-drbg@1.0.1: resolution: {integrity: sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=} dependencies: @@ -10052,7 +10067,6 @@ packages: /make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - dev: true /make-fetch-happen@11.1.1: resolution: {integrity: sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==} @@ -10263,7 +10277,6 @@ packages: engines: {node: '>=10'} dependencies: brace-expansion: 2.0.1 - dev: true /minimatch@9.0.2: resolution: {integrity: sha512-PZOT9g5v2ojiTL7r1xF6plNHLtOeTpSlDI007As2NlA2aYBMfVom17yqa6QzhmDP8QOhn7LjHTg7DFCVSSa6yg==} @@ -10373,7 +10386,6 @@ packages: resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==} engines: {node: '>=10'} hasBin: true - dev: true /moment@2.29.4: resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} @@ -10407,7 +10419,6 @@ packages: any-promise: 1.3.0 object-assign: 4.1.1 thenify-all: 1.6.0 - dev: true /nanoclone@0.2.1: resolution: {integrity: sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==} @@ -11307,6 +11318,12 @@ packages: engines: {node: '>=0.10.0'} dev: true + /parse5-htmlparser2-tree-adapter@6.0.1: + resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} + dependencies: + parse5: 6.0.1 + dev: false + /parse5-htmlparser2-tree-adapter@7.0.0: resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==} dependencies: @@ -11314,6 +11331,14 @@ packages: parse5: 7.1.2 dev: true + /parse5@5.1.1: + resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==} + dev: false + + /parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + dev: false + /parse5@7.1.2: resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} dependencies: @@ -12087,7 +12112,7 @@ packages: dev: true /require-directory@2.1.1: - resolution: {integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I=} + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} /require-from-string@2.0.2: @@ -13088,7 +13113,6 @@ packages: engines: {node: '>=8'} dependencies: has-flag: 4.0.0 - dev: true /supports-color@8.1.1: resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} @@ -13224,13 +13248,11 @@ packages: engines: {node: '>=0.8'} dependencies: thenify: 3.3.1 - dev: true /thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} dependencies: any-promise: 1.3.0 - dev: true /threads-plugin@1.4.0(@babel/types@7.22.5)(webpack@5.88.1): resolution: {integrity: sha512-lQENPueZLsD+6Cvxvj/QaQyUskwnFZO+2ZGDMnPIvtytSeywWvYzete8paZ9L+5IR4v8jnSYNZPlIQrEhSK1EA==} @@ -13414,7 +13436,6 @@ packages: typescript: 5.1.6 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - dev: true /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} @@ -13538,6 +13559,85 @@ packages: is-typedarray: 1.0.0 dev: false + /typeorm@0.3.17(better-sqlite3@8.5.1)(ts-node@10.9.1): + resolution: {integrity: sha512-UDjUEwIQalO9tWw9O2A4GU+sT3oyoUXheHJy4ft+RFdnRdQctdQ34L9SqE2p7LdwzafHx1maxT+bqXON+Qnmig==} + engines: {node: '>= 12.9.0'} + hasBin: true + peerDependencies: + '@google-cloud/spanner': ^5.18.0 + '@sap/hana-client': ^2.12.25 + better-sqlite3: ^7.1.2 || ^8.0.0 + hdb-pool: ^0.1.6 + ioredis: ^5.0.4 + mongodb: ^5.2.0 + mssql: ^9.1.1 + mysql2: ^2.2.5 || ^3.0.1 + oracledb: ^5.1.0 + pg: ^8.5.1 + pg-native: ^3.0.0 + pg-query-stream: ^4.0.0 + redis: ^3.1.1 || ^4.0.0 + sql.js: ^1.4.0 + sqlite3: ^5.0.3 + ts-node: ^10.7.0 + typeorm-aurora-data-api-driver: ^2.0.0 + peerDependenciesMeta: + '@google-cloud/spanner': + optional: true + '@sap/hana-client': + optional: true + better-sqlite3: + optional: true + hdb-pool: + optional: true + ioredis: + optional: true + mongodb: + optional: true + mssql: + optional: true + mysql2: + optional: true + oracledb: + optional: true + pg: + optional: true + pg-native: + optional: true + pg-query-stream: + optional: true + redis: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + ts-node: + optional: true + typeorm-aurora-data-api-driver: + optional: true + dependencies: + '@sqltools/formatter': 1.2.5 + app-root-path: 3.1.0 + better-sqlite3: 8.5.1 + buffer: 6.0.3 + chalk: 4.1.2 + cli-highlight: 2.1.11 + date-fns: 2.30.0 + debug: 4.3.4(supports-color@8.1.1) + dotenv: 16.3.1 + glob: 8.1.0 + mkdirp: 2.1.6 + reflect-metadata: 0.1.13 + sha.js: 2.4.11 + ts-node: 10.9.1(@types/node@20.5.1)(typescript@5.1.6) + tslib: 2.6.0 + uuid: 9.0.0 + yargs: 17.7.2 + transitivePeerDependencies: + - supports-color + dev: false + /typescript-plugin-styled-components@3.0.0(typescript@5.1.6): resolution: {integrity: sha512-QWlhTl6NqsFxtJyxn7pJjm3RhgzXSByUftZ3AoQClrMMpa4yAaHuJKTN1gFpH3Ti+Rwm56fNUfG9pXSBU+WW3A==} peerDependencies: @@ -13564,7 +13664,6 @@ packages: resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==} engines: {node: '>=14.17'} hasBin: true - dev: true /typesync@0.11.1: resolution: {integrity: sha512-sMoD2oBqrmUZPX1jAmRd75N07qPG8gTSocfJSfe09otfuoVx4rFNcOreOriUW+hp6Fh01dBuh42yD2NCgZD2dA==} @@ -13759,7 +13858,6 @@ packages: /uuid@9.0.0: resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} hasBin: true - dev: true /v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -14346,8 +14444,6 @@ packages: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} requiresBuild: true - dev: true - optional: true /yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} @@ -14388,8 +14484,6 @@ packages: string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 20.2.9 - dev: true - optional: true /yargs@17.7.2: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} @@ -14421,7 +14515,6 @@ packages: /yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} - dev: true /yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} diff --git a/src/constants/channels.ts b/src/constants/channels.ts index dc24f26d..13ad9e3d 100644 --- a/src/constants/channels.ts +++ b/src/constants/channels.ts @@ -147,6 +147,10 @@ export enum MetaDataChannel { name = 'MetaDataChannel', } +export enum WorkflowChannel { + name = 'WorkflowChannel', +} + export type Channels = | MainChannel | AuthenticationChannel diff --git a/src/main.ts b/src/main.ts index d5634200..8b26bbaf 100755 --- a/src/main.ts +++ b/src/main.ts @@ -22,6 +22,7 @@ import { bindServiceAndProxy } from '@services/libs/bindServiceAndProxy'; import serviceIdentifier from '@services/serviceIdentifier'; import { WindowNames } from '@services/windows/WindowProperties'; +import { IDatabaseService } from '@services/database/interface'; import { reportErrorToGithubWithTemplates } from '@services/native/reportError'; import type { IUpdaterService } from '@services/updater/interface'; import { IWikiService } from '@services/wiki/interface'; @@ -58,6 +59,7 @@ const wikiGitWorkspaceService = container.get(serviceI const wikiService = container.get(serviceIdentifier.Wiki); const windowService = container.get(serviceIdentifier.Window); const workspaceViewService = container.get(serviceIdentifier.WorkspaceView); +const databaseService = container.get(serviceIdentifier.Database); app.on('second-instance', () => { // Someone tried to run a second instance, we should focus our window. const mainWindow = windowService.get(WindowNames.main); @@ -95,6 +97,7 @@ const commonInit = async (): Promise => { preferenceService.get('attachToMenubar').then(async (attachToMenubar) => { attachToMenubar && await windowService.open(WindowNames.menuBar); }), + databaseService.initializeForApp(), ]); // perform wiki startup and git sync for each workspace await workspaceViewService.initializeAllWorkspaceView(); diff --git a/src/services/database/Readme.md b/src/services/database/Readme.md new file mode 100644 index 00000000..7ed4023c --- /dev/null +++ b/src/services/database/Readme.md @@ -0,0 +1,9 @@ +# Database Service + +We have workspace level db, work as cache for tiddlers, and change logs (to record deletion, for sync deletion with mobile clients). + +And we have app level db, to store things for pages like workflow pages. They are also regarded as temporary cache, and user can toggle a switch to store something inside cache to a wiki. + +## App level DB + +`src/services/database/entity` and `src/services/database/migration` are for app level db. diff --git a/src/services/database/entity/User.ts b/src/services/database/entity/User.ts new file mode 100644 index 00000000..2df67e9e --- /dev/null +++ b/src/services/database/entity/User.ts @@ -0,0 +1,14 @@ +import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; +import { WorkflowNetwork } from './WorkflowNetwork'; + +@Entity() +export class User { + @PrimaryGeneratedColumn() + id!: number; + + @Column('text') + username!: string; + + @OneToMany(() => WorkflowNetwork, workflowNetwork => workflowNetwork.user) + workflowNetworks!: WorkflowNetwork[]; +} diff --git a/src/services/database/entity/WorkflowNetwork.ts b/src/services/database/entity/WorkflowNetwork.ts new file mode 100644 index 00000000..e91570de --- /dev/null +++ b/src/services/database/entity/WorkflowNetwork.ts @@ -0,0 +1,26 @@ +import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; +import { User } from './User'; + +export enum WorkflowRunningState { + Idle = 'idle', + Running = 'running', + Stopped = 'stopped', +} + +@Entity() +export class WorkflowNetwork { + @PrimaryGeneratedColumn() + id!: number; + + @Column({ + type: 'varchar', + default: WorkflowRunningState.Idle, + }) + runningState!: WorkflowRunningState; + + @Column('text') // use text for large JSON string + serializedState!: string; + + @ManyToOne(() => User, user => user.workflowNetworks) + user!: User; +} diff --git a/src/services/database/entity/index.ts b/src/services/database/entity/index.ts new file mode 100644 index 00000000..75d1a35f --- /dev/null +++ b/src/services/database/entity/index.ts @@ -0,0 +1,4 @@ +import { User } from './User'; +import { WorkflowNetwork } from './WorkflowNetwork'; + +export const entities = [User, WorkflowNetwork]; diff --git a/src/services/database/index.ts b/src/services/database/index.ts index f29ccb5d..9eaca72d 100644 --- a/src/services/database/index.ts +++ b/src/services/database/index.ts @@ -11,6 +11,9 @@ import { lazyInject } from '@services/container'; import { logger } from '@services/libs/log'; import fs from 'fs-extra'; import path from 'path'; +import { DataSource } from 'typeorm'; +import { entities } from './entity'; +import { migrations } from './migration'; import { loadSqliteVss } from './sqlite-vss'; @injectable() @@ -23,9 +26,9 @@ export class DatabaseService implements IDatabaseService { // private readonly dbWorker?: ModuleThread; async initializeForWorkspace(workspaceID: string): Promise { - const destinationFilePath = this.getDataBasePath(workspaceID); + const destinationFilePath = this.getWorkspaceDataBasePath(workspaceID); // only create db file for this workspace's wiki if it doesn't exist - if (await fs.exists(this.getDataBasePath(workspaceID))) { + if (await fs.exists(this.getWorkspaceDataBasePath(workspaceID))) { logger.debug(`DatabaseService.initializeForWorkspace skip, there already has sqlite database for workspace ${workspaceID} in ${destinationFilePath}`); return; } @@ -74,7 +77,130 @@ export class DatabaseService implements IDatabaseService { } } - getDataBasePath(workspaceID: string): string { + async initializeForApp(): Promise { + const destinationFilePath = this.getAppDataBasePath(); + // only create db file for app if it doesn't exist + if (await fs.exists(destinationFilePath)) { + logger.debug(`DatabaseService.initializeForApp skip, there already has sqlite database for app in ${destinationFilePath}`); + return; + } + await fs.ensureDir(CACHE_DATABASE_FOLDER); + + try { + logger.debug(`DatabaseService.initializeForApp create a sqlite database for app`, { SQLITE_BINARY_PATH }); + + // Initialize TypeORM Connection using DataSource + const appDataSource = new DataSource({ + type: 'better-sqlite3', + nativeBinding: SQLITE_BINARY_PATH, + database: destinationFilePath, + entities, + synchronize: false, + migrationsRun: true, + logging: true, + migrations, + }); + + await appDataSource.initialize(); + await appDataSource.runMigrations(); + await appDataSource.destroy(); + logger.info(`DatabaseService.initializeForApp TypeORM connection initialized and migrations ran for app`); + } catch (error) { + logger.error(`DatabaseService.initializeForApp error when initializing TypeORM connection and running migrations for app: ${(error as Error).message}`); + } + } + + private readonly dataSources = new Map(); + + async getAppDatabase(isRetry = false): Promise { + const name = 'app-tidgi'; + if (!this.dataSources.has(name)) { + try { + const dataSource = new DataSource({ + type: 'sqlite', + database: this.getAppDataBasePath(), + entities, + synchronize: false, + migrationsRun: false, + logging: true, + migrations, + }); + /** + * Error `TypeError: Cannot read property 'transaction' of undefined` will show if run any query without initialize. + */ + await dataSource.initialize(); + + this.dataSources.set(name, dataSource); + return dataSource; + } catch (error) { + console.error(`Failed to getDatabase ${name}: ${(error as Error).message} ${(error as Error).stack ?? ''}`); + if (!isRetry) { + try { + await this.#fixAppDbLock(); + return await this.getAppDatabase(true); + } catch (error) { + console.error(`Failed to retry getDatabase ${name}: ${(error as Error).message} ${(error as Error).stack ?? ''}`); + } + } + try { + await this.dataSources.get(name)?.destroy(); + } catch (error) { + console.error(`Failed to destroy in getDatabase ${name}: ${(error as Error).message} ${(error as Error).stack ?? ''}`); + } + throw error; + } + } + + return this.dataSources.get(name)!; + } + + async closeAppDatabase(drop?: boolean) { + const name = 'app-tidgi'; + if (this.dataSources.has(name)) { + try { + const dataSource = this.dataSources.get(name)!; + this.dataSources.delete(name); + if (drop === true) { + await dataSource.dropDatabase(); + // need to delete the file. May encounter SQLITE_BUSY error if not deleted. + await fs.unlink(this.getAppDataBasePath()); + } else { + await dataSource.destroy(); + console.log(`closeDatabase ${name}`); + } + } catch (error) { + console.error(`Failed to closeDatabase ${name}: ${(error as Error).message} ${(error as Error).stack ?? ''}`); + throw error; + } + } + } + + /** + * Fix SQLite busy by move the file. + * @url https://stackoverflow.com/a/1226850 + * + * Fixes this: + * + * ```error + * [Error: Error getting skinny tiddlers list from SQLite: Call to function 'ExpoSQLite.exec' has been rejected. + * → Caused by: android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5 SQLITE_BUSY): , while compiling: PRAGMA journal_mode] Error: Error getting skinny tiddlers list from SQLite: Call to function 'ExpoSQLite.exec' has been rejected. + * → Caused by: android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5 SQLITE_BUSY): , while compiling: PRAGMA journal_mode + * ``` + */ + async #fixAppDbLock() { + const oldSqlitePath = this.getAppDataBasePath(); + const temporarySqlitePath = `${oldSqlitePath}.temp`; + await fs.copy(oldSqlitePath, temporarySqlitePath); + await fs.unlink(oldSqlitePath); + await fs.copy(temporarySqlitePath, oldSqlitePath); + await fs.unlink(temporarySqlitePath); + } + + getWorkspaceDataBasePath(workspaceID: string): string { return path.resolve(CACHE_DATABASE_FOLDER, `${workspaceID}-sqlite3-cache.db`); } + + getAppDataBasePath(): string { + return path.resolve(CACHE_DATABASE_FOLDER, `app-tidgi-sqlite3-cache.db`); + } } diff --git a/src/services/database/interface.ts b/src/services/database/interface.ts index 9eb8fbf6..da10e462 100644 --- a/src/services/database/interface.ts +++ b/src/services/database/interface.ts @@ -1,11 +1,19 @@ import { DatabaseChannel } from '@/constants/channels'; import { ProxyPropertyType } from 'electron-ipc-cat/common'; +import { DataSource } from 'typeorm'; /** * Allow wiki or external app to save/search tiddlers cache from database like sqlite+sqlite-vss (vector storage) */ export interface IDatabaseService { - getDataBasePath(workspaceID: string): string; + closeAppDatabase(drop?: boolean): void; + getAppDataBasePath(): string; + /** + * Get a database connection for the app db, which is a sqlite manages by TypeORM for all app level data + */ + getAppDatabase(isRetry?: boolean): Promise; + getWorkspaceDataBasePath(workspaceID: string): string; + initializeForApp(): Promise; /** * Create a database file for a workspace, store it in the appData folder, and load it in a worker_thread to execute SQL. * * (not store `.db` file in the workspace wiki's folder, because this cache file shouldn't not by Database committed) @@ -15,7 +23,9 @@ export interface IDatabaseService { export const DatabaseServiceIPCDescriptor = { channel: DatabaseChannel.name, properties: { - initializeForWorkspace: ProxyPropertyType.Function, + getAppDataBasePath: ProxyPropertyType.Function, getDataBasePath: ProxyPropertyType.Function, + initializeForApp: ProxyPropertyType.Function, + initializeForWorkspace: ProxyPropertyType.Function, }, }; diff --git a/src/services/database/migration/1695276132349-init.ts b/src/services/database/migration/1695276132349-init.ts new file mode 100644 index 00000000..c9581e2e --- /dev/null +++ b/src/services/database/migration/1695276132349-init.ts @@ -0,0 +1,73 @@ +import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm'; + +export class Init1695276132349 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + // User table + await queryRunner.createTable( + new Table({ + name: 'user', + columns: [ + { + name: 'id', + type: 'integer', + isPrimary: true, + isGenerated: true, + generationStrategy: 'increment', + }, + { + name: 'username', + type: 'varchar', + }, + ], + }), + true, + ); + + // WorkflowNetwork table + await queryRunner.createTable( + new Table({ + name: 'workflowNetwork', + columns: [ + { + name: 'id', + type: 'integer', + isPrimary: true, + isGenerated: true, + generationStrategy: 'increment', + }, + { + name: 'runningState', + type: 'varchar', + // SQLite does not natively support the ENUM data type, and it seems like better-sqlite3 also does not support it. Error: Data type \"enum\" in \"WorkflowNetwork.runningState\" is not supported by \"better-sqlite3\" database.", + // enum: ['idle', 'running', 'stopped'], + default: "'idle'", + }, + { + name: 'serializedState', + type: 'text', + }, + { + name: 'userId', + type: 'integer', + }, + ], + }), + true, + ); + + await queryRunner.createForeignKey( + 'workflowNetwork', + new TableForeignKey({ + columnNames: ['userId'], + referencedColumnNames: ['id'], + referencedTableName: 'user', + onDelete: 'CASCADE', + }), + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropTable('workflowNetwork'); + await queryRunner.dropTable('user'); + } +} diff --git a/src/services/database/migration/index.ts b/src/services/database/migration/index.ts new file mode 100644 index 00000000..759717d5 --- /dev/null +++ b/src/services/database/migration/index.ts @@ -0,0 +1,3 @@ +import { Init1695276132349 } from './1695276132349-init'; + +export const migrations = [Init1695276132349]; diff --git a/src/services/wiki/index.ts b/src/services/wiki/index.ts index 9ac55345..2a9cd4ad 100644 --- a/src/services/wiki/index.ts +++ b/src/services/wiki/index.ts @@ -176,7 +176,7 @@ export class Wiki implements IWikiService { logger.debug('startWiki calling initCacheDatabase in the main process', { function: 'wikiWorker.initCacheDatabase' }); worker.initCacheDatabase({ - databaseFile: this.databaseService.getDataBasePath(workspaceID), + databaseFile: this.databaseService.getWorkspaceDataBasePath(workspaceID), sqliteBinary: SQLITE_BINARY_PATH, packagePathBase: PACKAGE_PATH_BASE, }).subscribe(async (message) => {