diff --git a/package.json b/package.json index e2e5242..79e5034 100644 --- a/package.json +++ b/package.json @@ -26,13 +26,11 @@ "@tailwindcss/typography": "^0.5.13", "@testing-library/jest-dom": "^6.4.5", "@testing-library/svelte": "^5.2.0", - "@types/better-sqlite3": "^7.6.11", "@types/lodash": "^4.17.1", "@types/pg": "^8.11.6", "@vitest/browser": "^1.6.0", "@vitest/ui": "^1.6.0", "autoprefixer": "^10.4.19", - "better-sqlite3": "^10.0.0", "drizzle-kit": "^0.24.0", "jsdom": "^24.0.0", "postcss": "^8.4.38", @@ -84,7 +82,6 @@ "msw": "^2.3.1", "nanoid": "^5.0.7", "pg": "^8.12.0", - "sqlocal": "^0.9.0", "svelte-filepond": "^0.2.2", "svelte-french-toast": "^1.2.0", "tailwind-merge": "^2.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3ba91c0..2db36f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -104,9 +104,6 @@ importers: pg: specifier: ^8.12.0 version: 8.12.0 - sqlocal: - specifier: ^0.9.0 - version: 0.9.0(drizzle-orm@0.33.0(@cloudflare/workers-types@4.20240502.0)(@electric-sql/pglite@0.2.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.11)(@types/pg@8.11.6)(@types/sql.js@1.4.9)(better-sqlite3@10.0.0)(kysely@0.27.3)(pg@8.12.0)(react@18.3.1)(sql.js@1.10.3))(kysely@0.27.3) svelte-filepond: specifier: ^0.2.2 version: 0.2.2(filepond@4.31.1) @@ -156,9 +153,6 @@ importers: '@testing-library/svelte': specifier: ^5.2.0 version: 5.2.0(svelte@5.0.0-next.208)(vite@5.2.11(@types/node@20.14.9))(vitest@1.6.0(@types/node@20.14.9)(@vitest/browser@1.6.0)(@vitest/ui@1.6.0)(jsdom@24.0.0)) - '@types/better-sqlite3': - specifier: ^7.6.11 - version: 7.6.11 '@types/lodash': specifier: ^4.17.1 version: 4.17.1 @@ -174,9 +168,6 @@ importers: autoprefixer: specifier: ^10.4.19 version: 10.4.19(postcss@8.4.38) - better-sqlite3: - specifier: ^10.0.0 - version: 10.0.0 drizzle-kit: specifier: ^0.24.0 version: 0.24.0 @@ -1615,10 +1606,6 @@ packages: '@sodaru/yup-to-json-schema@2.0.1': resolution: {integrity: sha512-lWb0Wiz8KZ9ip/dY1eUqt7fhTPmL24p6Hmv5Fd9pzlzAdw/YNcWZr+tiCT4oZ4Zyxzi9+1X4zv82o7jYvcFxYA==} - '@sqlite.org/sqlite-wasm@3.45.3-build1': - resolution: {integrity: sha512-/eLqlKmyPoHpYZkg5VmEZpknisOShPX59Rm//zKHFVqqCk/7/mYT/i5CrooRQDqzfhq0Bm8IDynC+fbL0C1WfQ==} - hasBin: true - '@sveltejs/adapter-auto@3.2.0': resolution: {integrity: sha512-She5nKT47kwHE18v9NMe6pbJcvULr82u0V3yZ0ej3n1laWKGgkgdEABE9/ak5iDPs93LqsBkuIo51kkwCLBjJA==} peerDependencies: @@ -1776,12 +1763,6 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@ungap/structured-clone@1.2.0': - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - - '@ungap/with-resolvers@0.1.0': - resolution: {integrity: sha512-g7f0IkJdPW2xhY7H4iE72DAsIyfuwEFc6JWc2tYFwKDMWWAF699vGjrM348cwQuOXgHpe1gWFe+Eiyjx/ewvvw==} - '@vinejs/compiler@2.5.0': resolution: {integrity: sha512-hg4ekaB5Y2zh+IWzBiC/WCDWrIfpVnKu/ubUvelKlidc/VbulsexoFRw5kJGHZenPVI5YzNnDeTdYSALkTV7jQ==} engines: {node: '>=18.0.0'} @@ -2185,9 +2166,6 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} - coincident@1.2.3: - resolution: {integrity: sha512-Uxz3BMTWIslzeWjuQnizGWVg0j6khbvHUQ8+5BdM7WuJEm4ALXwq3wluYoB+uF68uPBz/oUOeJnYURKyfjexlA==} - color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -2722,9 +2700,6 @@ packages: resolution: {integrity: sha512-DSrkyMTfAnAm4ks9Go20QGOcXEyW/NmZhvTYBU2rb4afBB393WIMQPWPEDMl/k8xqiNN9HYq2zao3oWXsdl2Tg==} engines: {node: '>=14'} - gc-hook@0.3.1: - resolution: {integrity: sha512-E5M+O/h2o7eZzGhzRZGex6hbB3k4NWqO0eA+OzLRLXxhdbYPajZnynPwAtphnh+cRHPwsj5Z80dqZlfI4eK55A==} - gcp-metadata@6.1.0: resolution: {integrity: sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==} engines: {node: '>=14'} @@ -3643,9 +3618,6 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - proxy-target@3.0.2: - resolution: {integrity: sha512-FFE1XNwXX/FNC3/P8HiKaJSy/Qk68RitG/QEcLy/bVnTAPlgTAWPZKh0pARLAnpfXQPKyalBhk009NRTgsk8vQ==} - psl@1.9.0: resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} @@ -3915,17 +3887,6 @@ packages: sql.js@1.10.3: resolution: {integrity: sha512-H46aWtQkdyjZwFQgraUruy5h/DyJBbAK3EA/WEMqiqF6PGPfKBSKBj/er3dVyYqVIoYfRf5TFM/loEjtQIrqJg==} - sqlocal@0.9.0: - resolution: {integrity: sha512-/NiXQFa2+H5zkKr06SkIwjeItz5+GJerZh/rGjiT+X21k/J/29HeYd6a++iIeOtZHBd4qI4eSi8iOYq+yNk1YQ==} - peerDependencies: - drizzle-orm: '*' - kysely: '*' - peerDependenciesMeta: - drizzle-orm: - optional: true - kysely: - optional: true - sswr@2.1.0: resolution: {integrity: sha512-Cqc355SYlTAaUt8iDPaC/4DPPXK925PePLMxyBKuWd5kKc5mwsG3nT9+Mq2tyguL5s7b4Jg+IRMpTRsNTAfpSQ==} peerDependencies: @@ -4762,10 +4723,10 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.600.0(@aws-sdk/client-sts@3.600.0) - '@aws-sdk/client-sts': 3.600.0 + '@aws-sdk/client-sso-oidc': 3.600.0 + '@aws-sdk/client-sts': 3.600.0(@aws-sdk/client-sso-oidc@3.600.0) '@aws-sdk/core': 3.598.0 - '@aws-sdk/credential-provider-node': 3.600.0(@aws-sdk/client-sso-oidc@3.600.0(@aws-sdk/client-sts@3.600.0))(@aws-sdk/client-sts@3.600.0) + '@aws-sdk/credential-provider-node': 3.600.0(@aws-sdk/client-sso-oidc@3.600.0)(@aws-sdk/client-sts@3.600.0(@aws-sdk/client-sso-oidc@3.600.0)) '@aws-sdk/middleware-host-header': 3.598.0 '@aws-sdk/middleware-logger': 3.598.0 '@aws-sdk/middleware-recursion-detection': 3.598.0 @@ -4808,13 +4769,13 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sso-oidc@3.600.0(@aws-sdk/client-sts@3.600.0)': + '@aws-sdk/client-sso-oidc@3.600.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sts': 3.600.0 + '@aws-sdk/client-sts': 3.600.0(@aws-sdk/client-sso-oidc@3.600.0) '@aws-sdk/core': 3.598.0 - '@aws-sdk/credential-provider-node': 3.600.0(@aws-sdk/client-sso-oidc@3.600.0(@aws-sdk/client-sts@3.600.0))(@aws-sdk/client-sts@3.600.0) + '@aws-sdk/credential-provider-node': 3.600.0(@aws-sdk/client-sso-oidc@3.600.0)(@aws-sdk/client-sts@3.600.0(@aws-sdk/client-sso-oidc@3.600.0)) '@aws-sdk/middleware-host-header': 3.598.0 '@aws-sdk/middleware-logger': 3.598.0 '@aws-sdk/middleware-recursion-detection': 3.598.0 @@ -4851,7 +4812,6 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.6.2 transitivePeerDependencies: - - '@aws-sdk/client-sts' - aws-crt '@aws-sdk/client-sso@3.598.0': @@ -4897,13 +4857,13 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sts@3.600.0': + '@aws-sdk/client-sts@3.600.0(@aws-sdk/client-sso-oidc@3.600.0)': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.600.0(@aws-sdk/client-sts@3.600.0) + '@aws-sdk/client-sso-oidc': 3.600.0 '@aws-sdk/core': 3.598.0 - '@aws-sdk/credential-provider-node': 3.600.0(@aws-sdk/client-sso-oidc@3.600.0(@aws-sdk/client-sts@3.600.0))(@aws-sdk/client-sts@3.600.0) + '@aws-sdk/credential-provider-node': 3.600.0(@aws-sdk/client-sso-oidc@3.600.0)(@aws-sdk/client-sts@3.600.0(@aws-sdk/client-sso-oidc@3.600.0)) '@aws-sdk/middleware-host-header': 3.598.0 '@aws-sdk/middleware-logger': 3.598.0 '@aws-sdk/middleware-recursion-detection': 3.598.0 @@ -4940,6 +4900,7 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.6.2 transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' - aws-crt '@aws-sdk/core@3.598.0': @@ -4971,14 +4932,14 @@ snapshots: '@smithy/util-stream': 3.1.3 tslib: 2.6.2 - '@aws-sdk/credential-provider-ini@3.598.0(@aws-sdk/client-sso-oidc@3.600.0(@aws-sdk/client-sts@3.600.0))(@aws-sdk/client-sts@3.600.0)': + '@aws-sdk/credential-provider-ini@3.598.0(@aws-sdk/client-sso-oidc@3.600.0)(@aws-sdk/client-sts@3.600.0(@aws-sdk/client-sso-oidc@3.600.0))': dependencies: - '@aws-sdk/client-sts': 3.600.0 + '@aws-sdk/client-sts': 3.600.0(@aws-sdk/client-sso-oidc@3.600.0) '@aws-sdk/credential-provider-env': 3.598.0 '@aws-sdk/credential-provider-http': 3.598.0 '@aws-sdk/credential-provider-process': 3.598.0 - '@aws-sdk/credential-provider-sso': 3.598.0(@aws-sdk/client-sso-oidc@3.600.0(@aws-sdk/client-sts@3.600.0)) - '@aws-sdk/credential-provider-web-identity': 3.598.0(@aws-sdk/client-sts@3.600.0) + '@aws-sdk/credential-provider-sso': 3.598.0(@aws-sdk/client-sso-oidc@3.600.0) + '@aws-sdk/credential-provider-web-identity': 3.598.0(@aws-sdk/client-sts@3.600.0(@aws-sdk/client-sso-oidc@3.600.0)) '@aws-sdk/types': 3.598.0 '@smithy/credential-provider-imds': 3.2.0 '@smithy/property-provider': 3.1.3 @@ -4989,14 +4950,14 @@ snapshots: - '@aws-sdk/client-sso-oidc' - aws-crt - '@aws-sdk/credential-provider-node@3.600.0(@aws-sdk/client-sso-oidc@3.600.0(@aws-sdk/client-sts@3.600.0))(@aws-sdk/client-sts@3.600.0)': + '@aws-sdk/credential-provider-node@3.600.0(@aws-sdk/client-sso-oidc@3.600.0)(@aws-sdk/client-sts@3.600.0(@aws-sdk/client-sso-oidc@3.600.0))': dependencies: '@aws-sdk/credential-provider-env': 3.598.0 '@aws-sdk/credential-provider-http': 3.598.0 - '@aws-sdk/credential-provider-ini': 3.598.0(@aws-sdk/client-sso-oidc@3.600.0(@aws-sdk/client-sts@3.600.0))(@aws-sdk/client-sts@3.600.0) + '@aws-sdk/credential-provider-ini': 3.598.0(@aws-sdk/client-sso-oidc@3.600.0)(@aws-sdk/client-sts@3.600.0(@aws-sdk/client-sso-oidc@3.600.0)) '@aws-sdk/credential-provider-process': 3.598.0 - '@aws-sdk/credential-provider-sso': 3.598.0(@aws-sdk/client-sso-oidc@3.600.0(@aws-sdk/client-sts@3.600.0)) - '@aws-sdk/credential-provider-web-identity': 3.598.0(@aws-sdk/client-sts@3.600.0) + '@aws-sdk/credential-provider-sso': 3.598.0(@aws-sdk/client-sso-oidc@3.600.0) + '@aws-sdk/credential-provider-web-identity': 3.598.0(@aws-sdk/client-sts@3.600.0(@aws-sdk/client-sso-oidc@3.600.0)) '@aws-sdk/types': 3.598.0 '@smithy/credential-provider-imds': 3.2.0 '@smithy/property-provider': 3.1.3 @@ -5016,10 +4977,10 @@ snapshots: '@smithy/types': 3.3.0 tslib: 2.6.2 - '@aws-sdk/credential-provider-sso@3.598.0(@aws-sdk/client-sso-oidc@3.600.0(@aws-sdk/client-sts@3.600.0))': + '@aws-sdk/credential-provider-sso@3.598.0(@aws-sdk/client-sso-oidc@3.600.0)': dependencies: '@aws-sdk/client-sso': 3.598.0 - '@aws-sdk/token-providers': 3.598.0(@aws-sdk/client-sso-oidc@3.600.0(@aws-sdk/client-sts@3.600.0)) + '@aws-sdk/token-providers': 3.598.0(@aws-sdk/client-sso-oidc@3.600.0) '@aws-sdk/types': 3.598.0 '@smithy/property-provider': 3.1.3 '@smithy/shared-ini-file-loader': 3.1.4 @@ -5029,9 +4990,9 @@ snapshots: - '@aws-sdk/client-sso-oidc' - aws-crt - '@aws-sdk/credential-provider-web-identity@3.598.0(@aws-sdk/client-sts@3.600.0)': + '@aws-sdk/credential-provider-web-identity@3.598.0(@aws-sdk/client-sts@3.600.0(@aws-sdk/client-sso-oidc@3.600.0))': dependencies: - '@aws-sdk/client-sts': 3.600.0 + '@aws-sdk/client-sts': 3.600.0(@aws-sdk/client-sso-oidc@3.600.0) '@aws-sdk/types': 3.598.0 '@smithy/property-provider': 3.1.3 '@smithy/types': 3.3.0 @@ -5074,9 +5035,9 @@ snapshots: '@smithy/util-middleware': 3.0.3 tslib: 2.6.2 - '@aws-sdk/token-providers@3.598.0(@aws-sdk/client-sso-oidc@3.600.0(@aws-sdk/client-sts@3.600.0))': + '@aws-sdk/token-providers@3.598.0(@aws-sdk/client-sso-oidc@3.600.0)': dependencies: - '@aws-sdk/client-sso-oidc': 3.600.0(@aws-sdk/client-sts@3.600.0) + '@aws-sdk/client-sso-oidc': 3.600.0 '@aws-sdk/types': 3.598.0 '@smithy/property-provider': 3.1.3 '@smithy/shared-ini-file-loader': 3.1.4 @@ -6109,8 +6070,6 @@ snapshots: '@sodaru/yup-to-json-schema@2.0.1': optional: true - '@sqlite.org/sqlite-wasm@3.45.3-build1': {} - '@sveltejs/adapter-auto@3.2.0(@sveltejs/kit@2.5.7(@sveltejs/vite-plugin-svelte@3.1.0(svelte@5.0.0-next.208)(vite@5.2.11(@types/node@20.14.9)))(svelte@5.0.0-next.208)(vite@5.2.11(@types/node@20.14.9)))': dependencies: '@sveltejs/kit': 2.5.7(@sveltejs/vite-plugin-svelte@3.1.0(svelte@5.0.0-next.208)(vite@5.2.11(@types/node@20.14.9)))(svelte@5.0.0-next.208)(vite@5.2.11(@types/node@20.14.9)) @@ -6222,6 +6181,7 @@ snapshots: '@types/better-sqlite3@7.6.11': dependencies: '@types/node': 20.14.9 + optional: true '@types/cookie@0.6.0': {} @@ -6292,10 +6252,6 @@ snapshots: '@types/node': 20.14.9 optional: true - '@ungap/structured-clone@1.2.0': {} - - '@ungap/with-resolvers@0.1.0': {} - '@vinejs/compiler@2.5.0': optional: true @@ -6638,6 +6594,7 @@ snapshots: dependencies: bindings: 1.5.0 prebuild-install: 7.1.2 + optional: true big-integer@1.6.52: optional: true @@ -6649,6 +6606,7 @@ snapshots: bindings@1.5.0: dependencies: file-uri-to-path: 1.0.0 + optional: true bits-ui@0.21.7(svelte@5.0.0-next.208): dependencies: @@ -6662,6 +6620,7 @@ snapshots: buffer: 5.7.1 inherits: 2.0.4 readable-stream: 3.6.2 + optional: true blake3-wasm@2.1.5: {} @@ -6703,6 +6662,7 @@ snapshots: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 + optional: true buffer@6.0.3: dependencies: @@ -6786,7 +6746,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - chownr@1.1.4: {} + chownr@1.1.4: + optional: true chromium-bidi@0.4.16(devtools-protocol@0.0.1147663): dependencies: @@ -6814,18 +6775,6 @@ snapshots: clsx@2.1.1: {} - coincident@1.2.3: - dependencies: - '@ungap/structured-clone': 1.2.0 - '@ungap/with-resolvers': 0.1.0 - gc-hook: 0.3.1 - proxy-target: 3.0.2 - optionalDependencies: - ws: 8.17.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - color-convert@1.9.3: dependencies: color-name: 1.1.3 @@ -6935,12 +6884,14 @@ snapshots: decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 + optional: true deep-eql@4.1.3: dependencies: type-detect: 4.0.8 - deep-extend@0.6.0: {} + deep-extend@0.6.0: + optional: true deepmerge-ts@5.1.0: optional: true @@ -6963,7 +6914,8 @@ snapshots: detect-indent@6.1.0: {} - detect-libc@2.0.3: {} + detect-libc@2.0.3: + optional: true devalue@5.0.0: {} @@ -7046,6 +6998,7 @@ snapshots: end-of-stream@1.4.4: dependencies: once: 1.4.0 + optional: true entities@4.5.0: {} @@ -7228,7 +7181,8 @@ snapshots: exit-hook@2.2.1: {} - expand-template@2.0.3: {} + expand-template@2.0.3: + optional: true extend@3.0.2: {} @@ -7278,7 +7232,8 @@ snapshots: fflate@0.8.2: {} - file-uri-to-path@1.0.0: {} + file-uri-to-path@1.0.0: + optional: true filepond@4.31.1: {} @@ -7319,7 +7274,8 @@ snapshots: fraction.js@4.3.7: {} - fs-constants@1.0.0: {} + fs-constants@1.0.0: + optional: true fs-extra@11.2.0: dependencies: @@ -7357,8 +7313,6 @@ snapshots: - encoding - supports-color - gc-hook@0.3.1: {} - gcp-metadata@6.1.0: dependencies: gaxios: 6.7.0 @@ -7417,7 +7371,8 @@ snapshots: - supports-color optional: true - github-from-package@0.0.0: {} + github-from-package@0.0.0: + optional: true glob-parent@5.1.2: dependencies: @@ -7537,7 +7492,8 @@ snapshots: dependencies: safer-buffer: 2.1.2 - ieee754@1.2.1: {} + ieee754@1.2.1: + optional: true import-fresh@3.3.0: dependencies: @@ -7555,7 +7511,8 @@ snapshots: inherits@2.0.4: {} - ini@1.3.8: {} + ini@1.3.8: + optional: true ip-address@9.0.5: dependencies: @@ -7820,7 +7777,8 @@ snapshots: mimic-fn@4.0.0: {} - mimic-response@3.1.0: {} + mimic-response@3.1.0: + optional: true mimic-response@4.0.0: optional: true @@ -7866,7 +7824,8 @@ snapshots: mitt@3.0.0: optional: true - mkdirp-classic@0.5.3: {} + mkdirp-classic@0.5.3: + optional: true mkdirp@0.5.6: dependencies: @@ -7926,7 +7885,8 @@ snapshots: nanoid@5.0.7: {} - napi-build-utils@1.0.2: {} + napi-build-utils@1.0.2: + optional: true netmask@2.0.2: optional: true @@ -7934,6 +7894,7 @@ snapshots: node-abi@3.62.0: dependencies: semver: 7.6.2 + optional: true node-domexception@1.0.0: optional: true @@ -8195,6 +8156,7 @@ snapshots: simple-get: 4.0.1 tar-fs: 2.1.1 tunnel-agent: 0.6.0 + optional: true prettier-plugin-svelte@3.2.3(prettier@3.2.5)(svelte@5.0.0-next.208): dependencies: @@ -8269,14 +8231,13 @@ snapshots: proxy-from-env@1.1.0: optional: true - proxy-target@3.0.2: {} - psl@1.9.0: {} pump@3.0.0: dependencies: end-of-stream: 1.4.4 once: 1.4.0 + optional: true punycode@2.3.1: {} @@ -8316,6 +8277,7 @@ snapshots: ini: 1.3.8 minimist: 1.2.8 strip-json-comments: 2.0.1 + optional: true react-is@17.0.2: {} @@ -8345,6 +8307,7 @@ snapshots: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 + optional: true readable-stream@4.5.2: dependencies: @@ -8485,7 +8448,8 @@ snapshots: '@types/node-forge': 1.3.11 node-forge: 1.3.1 - semver@7.6.2: {} + semver@7.6.2: + optional: true serialize-error@11.0.3: dependencies: @@ -8512,13 +8476,15 @@ snapshots: signal-exit@4.1.0: {} - simple-concat@1.0.1: {} + simple-concat@1.0.1: + optional: true simple-get@4.0.1: dependencies: decompress-response: 6.0.0 once: 1.4.0 simple-concat: 1.0.1 + optional: true sirv@2.0.4: dependencies: @@ -8580,18 +8546,6 @@ snapshots: sql.js@1.10.3: optional: true - sqlocal@0.9.0(drizzle-orm@0.33.0(@cloudflare/workers-types@4.20240502.0)(@electric-sql/pglite@0.2.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.11)(@types/pg@8.11.6)(@types/sql.js@1.4.9)(better-sqlite3@10.0.0)(kysely@0.27.3)(pg@8.12.0)(react@18.3.1)(sql.js@1.10.3))(kysely@0.27.3): - dependencies: - '@sqlite.org/sqlite-wasm': 3.45.3-build1 - coincident: 1.2.3 - nanoid: 5.0.7 - optionalDependencies: - drizzle-orm: 0.33.0(@cloudflare/workers-types@4.20240502.0)(@electric-sql/pglite@0.2.0)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.11)(@types/pg@8.11.6)(@types/sql.js@1.4.9)(better-sqlite3@10.0.0)(kysely@0.27.3)(pg@8.12.0)(react@18.3.1)(sql.js@1.10.3) - kysely: 0.27.3 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - sswr@2.1.0(svelte@5.0.0-next.208): dependencies: svelte: 5.0.0-next.208 @@ -8640,6 +8594,7 @@ snapshots: string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 + optional: true strip-ansi@6.0.1: dependencies: @@ -8655,7 +8610,8 @@ snapshots: dependencies: min-indent: 1.0.1 - strip-json-comments@2.0.1: {} + strip-json-comments@2.0.1: + optional: true strip-literal@2.1.0: dependencies: @@ -8835,6 +8791,7 @@ snapshots: mkdirp-classic: 0.5.3 pump: 3.0.0 tar-stream: 2.2.0 + optional: true tar-fs@3.0.4: dependencies: @@ -8859,6 +8816,7 @@ snapshots: fs-constants: 1.0.0 inherits: 2.0.4 readable-stream: 3.6.2 + optional: true tar-stream@3.1.7: dependencies: @@ -8933,6 +8891,7 @@ snapshots: tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 + optional: true type-detect@4.0.8: {} diff --git a/src/database/migrations.test.ts b/src/database/migrations.test.ts deleted file mode 100644 index 75dd12d..0000000 --- a/src/database/migrations.test.ts +++ /dev/null @@ -1,1019 +0,0 @@ -import { - expect, - describe, - it, - beforeAll, - afterAll, - vi, - onTestFailed, - beforeEach, - afterEach, -} from "vitest"; -import { drizzle } from "drizzle-orm/better-sqlite3"; -import Database from "better-sqlite3"; -import { sql } from "drizzle-orm"; -import { useDb } from "@/database/client"; -import journal from "@/database/migrations/meta/_journal.json"; -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; -import * as schema from "@/database/schema"; - -describe("Migration Tests", () => { - let sqlite: Database.Database; - let db: BetterSQLite3Database; - - /* - * Test data for all tables. This data will be used to populate the database - * after the initial migration and to verify the data integrity after subsequent migrations. - */ - const testData = { - projects: [ - { - id: "project1", - name: "Test Project 1", - prompt: "Test prompt 1", - createdAt: "2023-01-01T00:00:00Z", - }, - { - id: "project2", - name: "Test Project 2", - prompt: "Test prompt 2", - createdAt: "2023-01-02T00:00:00Z", - }, - { - id: "project3", - name: "Test Project 3", - prompt: "Test prompt 3", - createdAt: "2023-01-03T00:00:00Z", - }, - ], - services: [ - { - id: "service1", - name: "Test Service 1", - providerId: "provider1", - baseURL: "http://test1.com", - apiKey: "testkey1", - createdAt: "2023-01-01T00:00:00Z", - }, - { - id: "service2", - name: "Test Service 2", - providerId: "provider2", - baseURL: "http://test2.com", - apiKey: "testkey2", - createdAt: "2023-01-02T00:00:00Z", - }, - ], - models: [ - { - id: "model1", - serviceId: "service1", - name: "Test Model 1", - visible: 1, - createdAt: "2023-01-01T00:00:00Z", - }, - { - id: "model2", - serviceId: "service1", - name: "Test Model 2", - visible: 0, - createdAt: "2023-01-02T00:00:00Z", - }, - { - id: "model3", - serviceId: "service2", - name: "Test Model 3", - visible: 1, - createdAt: "2023-01-03T00:00:00Z", - }, - ], - responses: [ - { - id: "response1", - projectId: "project1", - modelId: "model1", - createdAt: "2023-01-01T00:00:00Z", - }, - { - id: "response2", - projectId: "project1", - modelId: "model2", - createdAt: "2023-01-02T00:00:00Z", - }, - { - id: "response3", - projectId: "project2", - modelId: "model1", - createdAt: "2023-01-03T00:00:00Z", - }, - { - id: "response4", - projectId: "project3", - modelId: "model3", - createdAt: "2023-01-04T00:00:00Z", - }, - ], - responseMessages: [ - { - id: "message1", - index: 0, - responseId: "response1", - role: "user", - content: "Test message 1", - createdAt: "2023-01-01T00:00:00Z", - }, - { - id: "message2", - index: 1, - responseId: "response1", - role: "assistant", - content: "Test reply 1", - createdAt: "2023-01-01T00:00:01Z", - }, - { - id: "message3", - index: 0, - responseId: "response2", - role: "user", - content: "Test message 2", - createdAt: "2023-01-02T00:00:00Z", - }, - { - id: "message4", - index: 0, - responseId: "response3", - role: "user", - content: "Test message 3", - createdAt: "2023-01-03T00:00:00Z", - }, - { - id: "message5", - index: 1, - responseId: "response3", - role: "assistant", - content: "Test reply 3", - createdAt: "2023-01-03T00:00:01Z", - }, - ], - documents: [ - { - id: "doc1", - name: "Document 1", - description: "Test doc", - content: "Test content", - createdAt: "2023-01-01T00:00:00Z", - }, - { - id: "doc2", - name: "Pasted", - description: "Pasted doc", - content: "Pasted content", - createdAt: "2023-01-02T00:00:00Z", - }, - ], - }; - - let initialProjectCount: number; - let initialServiceCount: number; - let initialModelCount: number; - let initialResponseCount: number; - let initialMessageCount: number; - let initialDocumentCount: number; - - function insertTestData() { - for (const project of testData.projects) { - db.run( - sql`INSERT INTO project (id, name, prompt, createdAt) VALUES (${project.id}, ${project.name}, ${project.prompt}, ${project.createdAt})`, - ); - } - for (const service of testData.services) { - db.run( - sql`INSERT INTO service (id, name, providerId, baseURL, apiKey, createdAt) VALUES (${service.id}, ${service.name}, ${service.providerId}, ${service.baseURL}, ${service.apiKey}, ${service.createdAt})`, - ); - } - for (const model of testData.models) { - db.run( - sql`INSERT INTO model (id, serviceId, name, visible, createdAt) VALUES (${model.id}, ${model.serviceId}, ${model.name}, ${model.visible}, ${model.createdAt})`, - ); - } - for (const response of testData.responses) { - db.run( - sql`INSERT INTO response (id, projectId, modelId, createdAt) VALUES (${response.id}, ${response.projectId}, ${response.modelId}, ${response.createdAt})`, - ); - } - for (const message of testData.responseMessages) { - db.run( - sql`INSERT INTO responseMessage (id, "index", responseId, role, content, createdAt) VALUES (${message.id}, ${message.index}, ${message.responseId}, ${message.role}, ${message.content}, ${message.createdAt})`, - ); - } - for (const document of testData.documents) { - db.run( - sql`INSERT INTO document (id, name, description, content, createdAt) VALUES (${document.id}, ${document.name}, ${document.description}, ${document.content}, ${document.createdAt})`, - ); - } - /* - * Verify that the initial data was inserted correctly. - * This establishes a baseline for comparison after migrations. - */ - initialProjectCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM project`, - ).count; - initialServiceCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM service`, - ).count; - initialModelCount = db.get<{ count: number }>(sql`SELECT COUNT(*) as count FROM model`).count; - initialResponseCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM response`, - ).count; - initialMessageCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM responseMessage`, - ).count; - initialDocumentCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM document`, - ).count; - - expect(initialProjectCount).toBe(testData.projects.length); - expect(initialServiceCount).toBe(testData.services.length); - expect(initialModelCount).toBe(testData.models.length); - expect(initialResponseCount).toBe(testData.responses.length); - expect(initialMessageCount).toBe(testData.responseMessages.length); - expect(initialDocumentCount).toBe(testData.documents.length); - } - - async function applyMigrationsUpTo(targetMigration: string) { - let isFirstMigration = true; - for (const entry of journal.entries) { - if (isFirstMigration) { - await runMigration(entry.tag); - insertTestData(); - isFirstMigration = false; - } else { - await runMigration(entry.tag); - } - if (entry.tag === targetMigration) { - break; - } - } - } - - vi.mock("@/database/client"); - - beforeEach(async () => { - sqlite = new Database(":memory:"); - db = drizzle(sqlite); - vi.mocked(useDb).mockReturnValue(db); - }); - - afterEach(() => { - sqlite.close(); - vi.resetAllMocks(); - }); - - async function runMigration(migration: string) { - const migrationSql = (await import(`@/database/migrations/${migration}.sql?raw`)).default; - db.run(sql`PRAGMA foreign_keys = OFF;`); - await Promise.all( - migrationSql.split("--> statement-breakpoint").map((s: string) => db.run(sql.raw(s))), - ); - db.run(sql`PRAGMA foreign_keys = ON;`); - } - - it("0000_careful_smiling_tiger", async () => { - await applyMigrationsUpTo("0000_careful_smiling_tiger"); - - /* - * Check for unique and other indexes after the first migration - * This ensures that the initial schema is set up correctly with all necessary constraints - */ - const documentNameUniqueIndex = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='index' AND name='document_name_unique'`, - ); - expect(documentNameUniqueIndex).toBeTruthy(); - - const serviceNameUniqueIndex = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='index' AND name='serviceName_unique'`, - ); - expect(serviceNameUniqueIndex).toBeTruthy(); - }); - - it("0001_nosy_earthquake", async () => { - await applyMigrationsUpTo("0001_nosy_earthquake"); - - /* - * Verify data integrity after the second migration. - * The record counts should remain the same as no data was added or removed. - */ - const secondMigrationResponseCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM response`, - ).count; - const secondMigrationMessageCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM responseMessage`, - ).count; - const secondMigrationModelCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM model`, - ).count; - - expect(secondMigrationResponseCount).toBe(initialResponseCount); - expect(secondMigrationMessageCount).toBe(initialMessageCount); - expect(secondMigrationModelCount).toBe(initialModelCount); - - /* - * Verify that the new indexes were created. - * This is crucial for ensuring the migration successfully improved query performance. - */ - const responseIndex = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='index' AND name='projectId_idx'`, - ); - const messageIndex = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='index' AND name='responseId_idx'`, - ); - const modelIndex = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='index' AND name='serviceId_idx'`, - ); - - expect(responseIndex).toBeTruthy(); - expect(messageIndex).toBeTruthy(); - expect(modelIndex).toBeTruthy(); - - /* - * Check the structure of the new tables after the second migration - * This verifies that the tables were recreated with the correct structure - */ - const responseColumns = db.all<{ name: string; type: string }>( - sql`PRAGMA table_info(response)`, - ); - expect(responseColumns).toContainEqual( - expect.objectContaining({ name: "error", type: "TEXT" }), - ); - - const responseMessageColumns = db.all<{ name: string; type: string }>( - sql`PRAGMA table_info(responseMessage)`, - ); - expect(responseMessageColumns).toContainEqual( - expect.objectContaining({ name: "index", type: "INTEGER" }), - ); - - const modelColumns = db.all<{ name: string; type: string }>(sql`PRAGMA table_info(model)`); - expect(modelColumns).toContainEqual( - expect.objectContaining({ name: "visible", type: "INTEGER" }), - ); - - /* - * Test the UNIQUE constraint on the model table - * This ensures that the unique constraint on (serviceId, name) is working correctly - */ - expect(() => - db.run(sql` - INSERT INTO model (id, serviceId, name, visible, createdAt) - VALUES ('model4', 'service1', 'Test Model 1', 1, '2023-01-04T00:00:00Z') - `), - ).toThrow(); - }); - - it("0002_overjoyed_mordo", async () => { - await applyMigrationsUpTo("0002_overjoyed_mordo"); - - /* - * Verify that the 'project' table was successfully renamed to 'chat'. - * The count of records in 'chat' should match the original 'project' count. - */ - const thirdMigrationChatCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM chat`, - ).count; - expect(thirdMigrationChatCount).toBe(initialProjectCount); - - /* - * Confirm that the 'project' table no longer exists. - * This ensures the renaming was done correctly and didn't leave behind the old table. - */ - const projectTableExists = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='table' AND name='project'`, - ); - expect(projectTableExists).toBeFalsy(); - - /* - * Verify that the 'response' table wasn't affected by the renaming. - * Its record count should remain the same. - */ - const thirdMigrationResponseCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM response`, - ).count; - expect(thirdMigrationResponseCount).toBe(initialResponseCount); - - /* - * Check that the 'response' table now has a 'chatId' column instead of 'projectId'. - * This verifies that the foreign key was updated correctly. - */ - const chatIdColumn = db.get<{ name: string; type: string } | undefined>( - sql`PRAGMA table_info(response)`, - ); - expect(chatIdColumn).toBeTruthy(); - expect(chatIdColumn?.type).toBe("TEXT"); - - /* - * Verify that the index on 'response' was updated to use 'chatId'. - * This ensures that query performance is maintained after the schema change. - */ - const chatIdIndex = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='index' AND name='chatId_idx'`, - ); - expect(chatIdIndex).toBeTruthy(); - - /* - * Verify that the data in the 'chat' table matches the original 'project' data. - * This ensures that no data was lost or corrupted during the renaming process. - */ - const chatData = db.all<{ id: string; name: string; prompt: string }>( - sql`SELECT id, name, prompt FROM chat ORDER BY id`, - ); - expect(chatData).toEqual( - testData.projects.map(({ id, name, prompt }) => ({ id, name, prompt })), - ); - /* - * Verify that the 'response' table data is correct, with 'chatId' replacing 'projectId'. - * This ensures that the foreign key relationships were properly updated. - */ - const responseData = db.all<{ id: string; chatId: string; modelId: string }>( - sql`SELECT id, chatId, modelId FROM response ORDER BY id`, - ); - expect(responseData).toEqual( - testData.responses.map(({ id, projectId: chatId, modelId }) => ({ id, chatId, modelId })), - ); - - /* - * Verify that the 'responseMessage' table data remains unchanged. - * This table wasn't directly affected by the migrations, so its data should be intact. - */ - const messageData = db.all<{ id: string; responseId: string; role: string; content: string }>( - sql`SELECT id, responseId, role, content FROM responseMessage ORDER BY id`, - ); - expect(messageData).toEqual( - testData.responseMessages.map(({ id, responseId, role, content }) => ({ - id, - responseId, - role, - content, - })), - ); - - /* - * Test the ON DELETE CASCADE behavior introduced in the third migration. - * This ensures that when a chat is deleted, its associated responses are also deleted. - */ - await db.run(sql`DELETE FROM chat WHERE id = 'project1'`); - const responseCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM response WHERE chatId = 'project1'`, - ).count; - expect(responseCount).toBe(0); - - /* - * Verify that the deletion of 'project1' didn't affect other chats' responses - */ - const remainingResponseCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM response`, - ).count; - expect(remainingResponseCount).toBe(initialResponseCount - 2); // 'project1' had 2 responses - - /* - * Check that the responseMessages associated with the deleted responses are also removed - * This tests the cascading delete behavior through multiple levels - */ - const remainingMessageCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM responseMessage`, - ).count; - expect(remainingMessageCount).toBe(initialMessageCount - 3); // 'project1' had 3 messages - }); - - it("0003_huge_nick_fury", async () => { - await applyMigrationsUpTo("0003_huge_nick_fury"); - - // Check for the creation of new tables - const messageTable = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='table' AND name='message'`, - ); - expect(messageTable).toBeTruthy(); - - const revisionTable = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='table' AND name='revision'`, - ); - expect(revisionTable).toBeTruthy(); - - // Verify the creation of new indexes - const messageIndex = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='index' AND name='message_revisionId_idx'`, - ); - expect(messageIndex).toBeTruthy(); - - const revisionIndex = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='index' AND name='revision_chatId_idx'`, - ); - expect(revisionIndex).toBeTruthy(); - - // Check that data has been migrated correctly - const revisionCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM revision`, - ).count; - expect(revisionCount).toBe(initialResponseCount); - - const messageCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM message`, - ).count; - expect(messageCount).toBe(initialMessageCount); - - // Verify the structure of the new tables - const revisionColumns = db.all<{ name: string; type: string }>( - sql`PRAGMA table_info(revision)`, - ); - expect(revisionColumns).toContainEqual( - expect.objectContaining({ name: "version", type: "INTEGER" }), - ); - expect(revisionColumns).toContainEqual( - expect.objectContaining({ name: "chatId", type: "TEXT" }), - ); - - const messageColumns = db.all<{ name: string; type: string }>(sql`PRAGMA table_info(message)`); - expect(messageColumns).toContainEqual( - expect.objectContaining({ name: "revisionId", type: "TEXT" }), - ); - expect(messageColumns).toContainEqual(expect.objectContaining({ name: "role", type: "TEXT" })); - - // Verify the content of migrated data - const revisionData = db.all<{ id: string; version: number; chatId: string }>( - sql`SELECT id, version, chatId FROM revision ORDER BY chatId, createdAt`, - ); - - // Sort testData.responses by createdAt to ensure correct ordering - const sortedResponses = [...testData.responses].sort( - (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(), - ); - - // Calculate expected versions - const chatVersions = new Map(); - const expectedRevisionData = sortedResponses.map(({ id, projectId: chatId }) => { - const currentVersion = (chatVersions.get(chatId) || 0) + 1; - chatVersions.set(chatId, currentVersion); - return { id, version: currentVersion, chatId }; - }); - - expect(revisionData).toEqual(expectedRevisionData); - - const messageData = db.all<{ id: string; revisionId: string; role: string; content: string }>( - sql`SELECT id, revisionId, role, content FROM message ORDER BY id`, - ); - expect(messageData).toEqual( - testData.responseMessages.map(({ id, responseId: revisionId, role, content }) => ({ - id, - revisionId, - role, - content, - })), - ); - - // Test cascading delete behavior - await db.run(sql`DELETE FROM chat WHERE id = 'project2'`); - const remainingRevisionCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM revision WHERE chatId = 'project2'`, - ).count; - expect(remainingRevisionCount).toBe(0); - - const remainingMessageCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM message WHERE revisionId IN (SELECT id FROM revision WHERE chatId = 'project2')`, - ).count; - expect(remainingMessageCount).toBe(0); - - // Verify that the deletion didn't affect other chats' data - const totalRemainingRevisionCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM revision`, - ).count; - expect(totalRemainingRevisionCount).toBe(revisionCount - 1); // 'project2' had 1 response/revision - - const totalRemainingMessageCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM message`, - ).count; - expect(totalRemainingMessageCount).toBe(messageCount - 2); // 'project2' had 2 messages - }); - - it("0004_skinny_machine_man", async () => { - await applyMigrationsUpTo("0004_skinny_machine_man"); - - // Verify that the 'response' table has been dropped - const responseTable = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='table' AND name='response'`, - ); - expect(responseTable).toBeFalsy(); - - // Verify that the 'responseMessage' table has been dropped - const responseMessageTable = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='table' AND name='responseMessage'`, - ); - expect(responseMessageTable).toBeFalsy(); - - // Verify that the 'revision' and 'message' tables still exist and contain the migrated data - const revisionCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM revision`, - ).count; - expect(revisionCount).toBe(initialResponseCount); - - const messageCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM message`, - ).count; - expect(messageCount).toBe(initialMessageCount); - - // Verify that the data in the 'revision' and 'message' tables is still correct - const revisionData = db.all<{ id: string; version: number; chatId: string }>( - sql`SELECT id, version, chatId FROM revision ORDER BY chatId, createdAt`, - ); - - const chatVersions = new Map(); - const expectedRevisionData = testData.responses - .sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()) - .map(({ id, projectId: chatId }) => { - const currentVersion = (chatVersions.get(chatId) || 0) + 1; - chatVersions.set(chatId, currentVersion); - return { id, version: currentVersion, chatId }; - }); - - expect(revisionData).toEqual(expectedRevisionData); - - const messageData = db.all<{ id: string; revisionId: string; role: string; content: string }>( - sql`SELECT id, revisionId, role, content FROM message ORDER BY id`, - ); - - const expectedMessageData = testData.responseMessages.map( - ({ id, responseId: revisionId, role, content }) => ({ - id, - revisionId, - role, - content, - }), - ); - - expect(messageData).toEqual(expectedMessageData); - - // Verify that the foreign key relationships are still intact - await db.run(sql`DELETE FROM chat WHERE id = 'project1'`); - const remainingRevisionCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM revision WHERE chatId = 'project1'`, - ).count; - expect(remainingRevisionCount).toBe(0); - - const remainingMessageCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM message WHERE revisionId IN (SELECT id FROM revision WHERE chatId = 'project1')`, - ).count; - expect(remainingMessageCount).toBe(0); - - // Final check on the total remaining data - const finalRevisionCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM revision`, - ).count; - expect(finalRevisionCount).toBe(revisionCount - 2); // 'project1' had 2 revisions - - const finalMessageCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM message`, - ).count; - expect(finalMessageCount).toBe(messageCount - 3); // 'project1' had 3 messages - }); - - it("0005_careful_the_professor", async () => { - await applyMigrationsUpTo("0005_careful_the_professor"); - - // Verify that the 'attachment' table was created - const attachmentTable = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='table' AND name='attachment'`, - ); - expect(attachmentTable).toBeTruthy(); - - // Check the structure of the 'attachment' table - const attachmentColumns = db.all<{ name: string; type: string }>( - sql`PRAGMA table_info(attachment)`, - ); - expect(attachmentColumns).toContainEqual( - expect.objectContaining({ - name: "id", - type: "TEXT", - }), - ); - expect(attachmentColumns).toContainEqual( - expect.objectContaining({ - name: "messageId", - type: "TEXT", - }), - ); - expect(attachmentColumns).toContainEqual( - expect.objectContaining({ - name: "documentId", - type: "TEXT", - }), - ); - expect(attachmentColumns).toContainEqual( - expect.objectContaining({ - name: "createdAt", - type: "TEXT", - }), - ); - - // Verify that the 'type' column was added to the 'document' table - const documentColumns = db.all<{ name: string; type: string }>( - sql`PRAGMA table_info(document)`, - ); - expect(documentColumns).toContainEqual(expect.objectContaining({ name: "type", type: "TEXT" })); - - // Check if the indexes were created - const attachmentMessageIdIndex = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='index' AND name='attachment_messageId_idx'`, - ); - expect(attachmentMessageIdIndex).toBeTruthy(); - - const messageDocumentUniqueIndex = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='index' AND name='messageDocument_unique'`, - ); - expect(messageDocumentUniqueIndex).toBeTruthy(); - - // Test foreign key constraints - const message = db.get<{ id: string }>(sql`SELECT id FROM message LIMIT 1`); - const document = db.get<{ id: string }>(sql`SELECT id FROM document LIMIT 1`); - - if (message && document) { - // Insert a valid attachment - db.run( - sql`INSERT INTO attachment (id, messageId, documentId) VALUES ('test1', ${message.id}, ${document.id})`, - ); - - // Attempt to insert an attachment with non-existent messageId (should fail) - expect(() => - db.run( - sql`INSERT INTO attachment (id, messageId, documentId) VALUES ('test2', 'non_existent', ${document.id})`, - ), - ).toThrow(); - - // Attempt to insert an attachment with non-existent documentId (should fail) - expect(() => - db.run( - sql`INSERT INTO attachment (id, messageId, documentId) VALUES ('test3', ${message.id}, 'non_existent')`, - ), - ).toThrow(); - - // Test cascade delete - db.run(sql`DELETE FROM message WHERE id = ${message.id}`); - const attachmentAfterDelete = db.get( - sql`SELECT * FROM attachment WHERE messageId = ${message.id}`, - ); - expect(attachmentAfterDelete).toBeFalsy(); - } - }); - - it("0006_opposite_sugar_man", async () => { - await applyMigrationsUpTo("0006_opposite_sugar_man"); - - // Verify that the 'attributes' column was added to the 'document' table - const documentColumns = db.all<{ name: string; type: string }>( - sql`PRAGMA table_info(document)`, - ); - expect(documentColumns).toContainEqual( - expect.objectContaining({ name: "attributes", type: "TEXT" }), - ); - - // Check if the default value is set correctly - const document = db.get<{ attributes: string }>(sql`SELECT attributes FROM document LIMIT 1`); - expect(document?.attributes).toBe("{}"); - - // Test inserting a document with custom attributes - db.run( - sql`INSERT INTO document (id, name, type, description, content, attributes) VALUES ('test_doc', 'Test Document', 'document', 'Test description', 'Test content', '{"key": "value"}')`, - ); - const insertedDocument = db.get<{ attributes: string }>( - sql`SELECT attributes FROM document WHERE id = 'test_doc'`, - ); - expect(insertedDocument?.attributes).toBe('{"key": "value"}'); - - // Verify that existing documents have the default attributes value - const existingDocuments = db.all<{ id: string; attributes: string }>( - sql`SELECT id, attributes FROM document WHERE id != 'test_doc'`, - ); - existingDocuments.forEach((doc) => { - expect(doc.attributes).toBe("{}"); - }); - }); - - it("0007_tidy_lilandra", async () => { - await applyMigrationsUpTo("0007_tidy_lilandra"); - - // Verify that the 'document_name_unique' index was dropped - const documentNameUniqueIndex = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='index' AND name='document_name_unique'`, - ); - expect(documentNameUniqueIndex).toBeFalsy(); - - // Test that we can now insert documents with the same name - db.run( - sql`INSERT INTO document (id, name, type, description, content) VALUES ('doc3', 'Same Name', 'document', 'Test description', 'Test content')`, - ); - db.run( - sql`INSERT INTO document (id, name, type, description, content) VALUES ('doc4', 'Same Name', 'document', 'Test description', 'Test content')`, - ); - - const documentsWithSameName = db.all<{ id: string }>( - sql`SELECT id FROM document WHERE name = 'Same Name'`, - ); - expect(documentsWithSameName.length).toBe(2); - - // Verify that other constraints and data remain intact - const documentCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM document`, - ).count; - expect(documentCount).toBe(initialDocumentCount + 2); // Initial count plus the two we just added - }); - - it("0008_parallel_grandmaster", async () => { - await applyMigrationsUpTo("0008_parallel_grandmaster"); - - // Verify that 'aiService' table was renamed to 'aiAccount' - const aiAccountTable = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='table' AND name='aiAccount'`, - ); - expect(aiAccountTable).toBeTruthy(); - - const aiServiceTable = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='table' AND name='aiService'`, - ); - expect(aiServiceTable).toBeFalsy(); - - // Check that 'aiServiceId' column in 'model' table was renamed to 'aiAccountId' - const modelColumns = db.all<{ name: string; type: string }>(sql`PRAGMA table_info(aiModel)`); - expect(modelColumns).toContainEqual( - expect.objectContaining({ name: "aiAccountId", type: "TEXT" }), - ); - expect(modelColumns).not.toContainEqual(expect.objectContaining({ name: "aiServiceId" })); - - // Verify that 'providerId' column in 'aiAccount' table was renamed to 'aiServiceId' - const aiAccountColumns = db.all<{ name: string; type: string }>( - sql`PRAGMA table_info(aiAccount)`, - ); - expect(aiAccountColumns).toContainEqual( - expect.objectContaining({ name: "aiServiceId", type: "TEXT" }), - ); - expect(aiAccountColumns).not.toContainEqual(expect.objectContaining({ name: "providerId" })); - - // Check that the new indexes were created - const aiAccountIdIdx = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='index' AND name='aiAccountId_idx'`, - ); - expect(aiAccountIdIdx).toBeTruthy(); - - const aiAccountIdUnique = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='index' AND name='aiAccountId_unique'`, - ); - expect(aiAccountIdUnique).toBeTruthy(); - - // Verify that the old indexes were dropped - const aiServiceIdIdx = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='index' AND name='aiServiceId_idx'`, - ); - expect(aiServiceIdIdx).toBeFalsy(); - - const aiServiceNameUnique = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='index' AND name='aiServiceName_unique'`, - ); - expect(aiServiceNameUnique).toBeFalsy(); - - // Verify that the data was migrated correctly - const aiAccountCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM aiAccount`, - ).count; - expect(aiAccountCount).toBe(initialServiceCount); - - const modelCount = db.get<{ count: number }>(sql`SELECT COUNT(*) as count FROM aiModel`).count; - expect(modelCount).toBe(initialModelCount); - - // Verify the content of migrated data - const aiAccountData = db.all<{ id: string; name: string; aiServiceId: string }>( - sql`SELECT id, name, aiServiceId FROM aiAccount ORDER BY id`, - ); - expect(aiAccountData).toEqual( - testData.services.map(({ id, name, providerId: aiServiceId }) => ({ id, name, aiServiceId })), - ); - - const modelData = db.all<{ id: string; aiAccountId: string; name: string; visible: number }>( - sql`SELECT id, aiAccountId, name, visible FROM aiModel ORDER BY id`, - ); - expect(modelData).toEqual( - testData.models.map(({ id, serviceId: aiAccountId, name, visible }) => ({ - id, - aiAccountId, - name, - visible, - })), - ); - - // Test the new foreign key constraint - const aiAccount = db.get<{ id: string }>(sql`SELECT id FROM aiAccount LIMIT 1`); - expect(aiAccount).toBeTruthy(); - - // Insert a valid model - db.run( - sql`INSERT INTO aiModel (id, aiAccountId, name, visible) VALUES ('test_model', ${aiAccount.id}, 'Test Model', 1)`, - ); - - // Attempt to insert a model with non-existent aiAccountId (should fail) - expect(() => - db.run( - sql`INSERT INTO aiModel (id, aiAccountId, name, visible) VALUES ('invalid_model', 'non_existent', 'Invalid Model', 1)`, - ), - ).toThrow(); - - // Test cascade delete - db.run(sql`DELETE FROM aiAccount WHERE id = ${aiAccount.id}`); - const modelAfterDelete = db.get(sql`SELECT * FROM aiModel WHERE aiAccountId = ${aiAccount.id}`); - expect(modelAfterDelete).toBeFalsy(); - }); - - it("0009_ancient_korath", async () => { - await applyMigrationsUpTo("0009_ancient_korath"); - - // Verify that the 'aiSdk' table was created - const aiSdkTable = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='table' AND name='aiSdk'`, - ); - expect(aiSdkTable).toBeTruthy(); - - // Verify that the 'aiService' table was created - const aiServiceTable = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='table' AND name='aiService'`, - ); - expect(aiServiceTable).toBeTruthy(); - - // Check the structure of the 'aiSdk' table - const aiSdkColumns = db.all<{ name: string; type: string }>(sql`PRAGMA table_info(aiSdk)`); - expect(aiSdkColumns).toContainEqual(expect.objectContaining({ name: "id", type: "TEXT" })); - expect(aiSdkColumns).toContainEqual(expect.objectContaining({ name: "slug", type: "TEXT" })); - expect(aiSdkColumns).toContainEqual(expect.objectContaining({ name: "name", type: "TEXT" })); - - // Check the structure of the 'aiService' table - const aiServiceColumns = db.all<{ name: string; type: string }>( - sql`PRAGMA table_info(aiService)`, - ); - expect(aiServiceColumns).toContainEqual(expect.objectContaining({ name: "id", type: "TEXT" })); - expect(aiServiceColumns).toContainEqual( - expect.objectContaining({ name: "name", type: "TEXT" }), - ); - expect(aiServiceColumns).toContainEqual( - expect.objectContaining({ name: "aiSdkId", type: "TEXT" }), - ); - expect(aiServiceColumns).toContainEqual( - expect.objectContaining({ name: "baseURL", type: "TEXT" }), - ); - expect(aiServiceColumns).toContainEqual( - expect.objectContaining({ name: "createdAt", type: "TEXT" }), - ); - - // Verify that the initial data was inserted correctly - const aiSdkCount = db.get<{ count: number }>(sql`SELECT COUNT(*) as count FROM aiSdk`).count; - expect(aiSdkCount).toBe(8); // 11 SDKs were inserted - - const aiServiceCount = db.get<{ count: number }>( - sql`SELECT COUNT(*) as count FROM aiService`, - ).count; - expect(aiServiceCount).toBe(11); // 11 services were inserted - - // Check some specific entries to ensure data integrity - const openaiSdk = db.get<{ id: string; slug: string; name: string }>( - sql`SELECT * FROM aiSdk WHERE id = 'openai'`, - ); - expect(openaiSdk).toEqual({ id: "openai", slug: "openai", name: "OpenAI" }); - - const azureService = db.get(sql`SELECT * FROM aiService WHERE id = 'azure'`); - expect(azureService).toMatchObject({ - id: "azure", - name: "Azure OpenAI", - aiSdkId: "azure", - baseURL: null, - }); - - // // Test unique constraint on aiSdk slug - // expect(() => - // db.run(sql`INSERT INTO aiSdk (id, slug, name) VALUES ('test', 'openai', 'Test SDK')`), - // ).toThrow(); - - // Verify that the aiAccount table still exists and maintains its relationship with aiService - const aiAccountTable = db.get<{ name: string } | undefined>( - sql`SELECT name FROM sqlite_master WHERE type='table' AND name='aiAccount'`, - ); - expect(aiAccountTable).toBeTruthy(); - - const aiAccountColumns = db.all<{ name: string; type: string }>( - sql`PRAGMA table_info(aiAccount)`, - ); - expect(aiAccountColumns).toContainEqual( - expect.objectContaining({ name: "aiServiceId", type: "TEXT" }), - ); - }); - - it("0010_far_wiccan", async () => { - await applyMigrationsUpTo("0010_far_wiccan"); - - // Test unique constraint on aiSdk slug - expect(() => - db.run(sql`INSERT INTO aiSdk (id, slug, name) VALUES ('test', 'openai', 'Test SDK')`), - ).toThrow(); - }); -}); diff --git a/src/database/migrator.ts b/src/database/migrator.ts index 6d4b25b..d28186a 100644 --- a/src/database/migrator.ts +++ b/src/database/migrator.ts @@ -22,16 +22,11 @@ export async function runMigrations(skipSeed = false) { } for (const entry of journal.entries) { - await applyMigration(db, entry.idx, entry.tag); + await applyMigration(db, entry.tag, skipSeed); } - - // if (shouldSeed && !skipSeed) { - // await seed(); - // } - // console.log("Migrations applied"); } -async function applyMigration(db: PgliteDatabase, idx: number, tag: string) { +async function applyMigration(db: PgliteDatabase, tag: string, skipSeed: boolean) { // check if tag is already applied const result = await db.execute<{ name: string }>( sql`SELECT name FROM migrations WHERE name = ${tag}`, @@ -53,7 +48,7 @@ async function applyMigration(db: PgliteDatabase, idx: number, tag: string) await tx.execute(sql`INSERT INTO migrations (name) VALUES (${tag})`); const num = tag.split("_")[0]; - if (num in seed) { + if (num in seed && !skipSeed) { // @ts-expect-error await seed[num](tx); } diff --git a/src/routes/(app)/$data.test.ts b/src/routes/(app)/$data.test.ts index a292e70..5c99499 100644 --- a/src/routes/(app)/$data.test.ts +++ b/src/routes/(app)/$data.test.ts @@ -1,34 +1,34 @@ -import { afterEach, beforeEach, describe, expect, it, vi, onTestFailed } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { duplicateChat, newChat, removeChat } from "./$data"; -import Database from "better-sqlite3"; -import { type BetterSQLite3Database, drizzle } from "drizzle-orm/better-sqlite3"; +import { drizzle } from "drizzle-orm/pglite"; import { nanoid } from "nanoid"; import * as schema from "@/database/schema"; -import { chatTable, revisionTable, messageTable } from "@/database/schema"; +import { chatTable, messageTable, revisionTable } from "@/database/schema"; import { runMigrations } from "@/database/migrator"; import { eq } from "drizzle-orm"; import { invalidate } from "$app/navigation"; import { sql } from "drizzle-orm/sql"; import { useDb } from "@/database"; +import { PGlite } from "@electric-sql/pglite"; +import type { PgliteDatabase } from "drizzle-orm/pglite"; describe("(app)/$data", () => { vi.mock("@/database/client"); vi.mock("$app/navigation"); vi.mock("nanoid"); - let sqlite: Database.Database; - let db: BetterSQLite3Database; + let pglite: PGlite; + let db: PgliteDatabase; - beforeEach(async () => { - // Create an in-memory SQLite database - sqlite = new Database(":memory:"); - db = drizzle(sqlite, { schema }); + beforeAll(async () => { + pglite = new PGlite(); + db = drizzle(pglite, { schema }); + }); + beforeEach(async () => { vi.mocked(useDb).mockReturnValue(db); - db.run(sql.raw("PRAGMA foreign_keys=off;")); await runMigrations(true); - db.run(sql.raw("PRAGMA foreign_keys=on;")); await db.delete(schema.messageTable); await db.delete(schema.revisionTable); @@ -43,17 +43,15 @@ describe("(app)/$data", () => { .values([ { id: "service1", name: "Test Service", sdkId: "sdk1", baseURL: "https://api.test.com" }, ]); - await db - .insert(schema.keyTable) - .values([ - { - id: "key1", - name: "Test Key", - serviceId: "service1", - baseURL: "https://api.test.com", - apiKey: "test-api-key", - }, - ]); + await db.insert(schema.keyTable).values([ + { + id: "key1", + name: "Test Key", + serviceId: "service1", + baseURL: "https://api.test.com", + apiKey: "test-api-key", + }, + ]); await db .insert(schema.modelTable) .values([{ id: "model1", keyId: "key1", name: "Test Model", visible: 1 }]); @@ -87,8 +85,25 @@ describe("(app)/$data", () => { ]); }); - afterEach(() => { - sqlite.close(); + afterEach(async () => { + // pglite is slowish to start for each test + // instead drop all the tables + await db.execute(sql`DO $$ + DECLARE + r RECORD; + BEGIN + -- Disable all triggers + EXECUTE 'SET session_replication_role = replica'; + + -- Drop all tables in the current schema + FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = current_schema()) LOOP + EXECUTE 'DROP TABLE IF EXISTS ' || quote_ident(r.tablename) || ' CASCADE'; + END LOOP; + + -- Re-enable triggers + EXECUTE 'SET session_replication_role = DEFAULT'; + END $$; + `); vi.resetAllMocks(); }); diff --git a/src/routes/(app)/chat/[id]/$data.test.ts b/src/routes/(app)/chat/[id]/$data.test.ts index 064d7c3..58dc3af 100644 --- a/src/routes/(app)/chat/[id]/$data.test.ts +++ b/src/routes/(app)/chat/[id]/$data.test.ts @@ -1,32 +1,35 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { appendMessages, createRevision, + getKeys, getLatestRevision, getModelKey, getRevision, interpolateDocuments, isTab, - getKeys, tabRouteId, updateChat, } from "./$data"; -import Database from "better-sqlite3"; -import { type BetterSQLite3Database, drizzle } from "drizzle-orm/better-sqlite3"; +import type { PgliteDatabase } from "drizzle-orm/pglite"; +import { drizzle } from "drizzle-orm/pglite"; import * as schema from "@/database/schema"; import { runMigrations } from "@/database/migrator"; import { eq } from "drizzle-orm"; import { nanoid } from "nanoid"; import type { ChatMessage } from "$lib/chat-service.svelte"; +import { PGlite } from "@electric-sql/pglite"; +import { sql } from "drizzle-orm/sql"; -let sqlite: Database.Database; -let db: BetterSQLite3Database; +let pglite: PGlite; +let db: PgliteDatabase; -beforeEach(async () => { - // Set up database - sqlite = new Database(":memory:"); - db = drizzle(sqlite, { schema }); +beforeAll(async () => { + pglite = new PGlite(); + db = drizzle(pglite, { schema }); +}); +beforeEach(async () => { // Set up mocks vi.mock("$app/navigation", () => ({ invalidate: vi.fn(), @@ -124,8 +127,26 @@ beforeEach(async () => { ]); }); -afterEach(() => { - sqlite.close(); +afterEach(async () => { + // pglite is slowish to start for each test + // instead drop all the tables + await db.execute(sql`DO $$ + DECLARE + r RECORD; + BEGIN + -- Disable all triggers + EXECUTE 'SET session_replication_role = replica'; + + -- Drop all tables in the current schema + FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = current_schema()) LOOP + EXECUTE 'DROP TABLE IF EXISTS ' || quote_ident(r.tablename) || ' CASCADE'; + END LOOP; + + -- Re-enable triggers + EXECUTE 'SET session_replication_role = DEFAULT'; + END $$; + `); + vi.clearAllMocks(); }); @@ -214,11 +235,12 @@ describe("getModelService", () => { describe("createRevision", () => { it("should create a new revision", async () => { + vi.mocked(nanoid).mockReturnValueOnce("created-revision-id"); const newRevision = await createRevision("chat1"); expect(newRevision).toMatchObject({ chatId: "chat1", version: 3, - id: "mocked-nanoid", + id: "created-revision-id", }); }); }); @@ -252,7 +274,7 @@ describe("appendMessage", () => { describe("newRevision", () => { it("should create a new revision with messages", async () => { vi.mocked(nanoid) - .mockReturnValueOnce("mocked-nanoid") + .mockReturnValueOnce("revision-with-messages-id") .mockReturnValueOnce("message-1") .mockReturnValueOnce("message-2"); const messages: ChatMessage[] = [ @@ -264,11 +286,11 @@ describe("newRevision", () => { expect(revision).toMatchObject({ chatId: "chat1", version: 3, - id: "mocked-nanoid", + id: "revision-with-messages-id", }); const newMessages = await db.query.messageTable.findMany({ - where: eq(schema.messageTable.revisionId, "mocked-nanoid"), + where: eq(schema.messageTable.revisionId, "revision-with-messages-id"), }); expect(newMessages).toHaveLength(2); expect(newMessages[0]).toMatchObject({ diff --git a/test/registry.test.ts b/test/registry.test.ts index 6818c44..7b15cfb 100644 --- a/test/registry.test.ts +++ b/test/registry.test.ts @@ -49,10 +49,10 @@ describe("cache", () => { }); expect(dependencies).toEqual( new Set([ - "model:chat:id-chat", - "model:revision:id-revision", - "model:message:id-message", - "model:message:id-message2", + "model:public.chat:id-chat", + "model:public.revision:id-revision", + "model:public.message:id-message", + "model:public.message:id-message2", ]), ); });