[READ-ONLY] Mirror of https://github.com/FoxxMD/multi-scrobbler. Scrobble plays from multiple sources to multiple clients docs.multi-scrobbler.app
deezer docker jellyfin koito lastfm listenbrainz maloja mopidy mpris music music-assistant plex scrobble self-hosted spotify subsonic tautulli youtube-music
0

Configure Feed

Select the types of activity you want to include in your feed.

feat(database): Initial database POC implementation

Using drizzle

FoxxMD (May 6, 2026, 2:56 AM UTC) c40add7d 569c5a86

+1866 -100
+4 -1
.gitignore
··· 151 151 152 152 *storybook.log 153 153 storybook-static 154 - lib 154 + 155 + *.db 156 + *.db.* 157 + *.db-*lib 155 158 *.db 156 159 *.db.* 157 160 *.db-*lib
+13
drizzle.config.ts
··· 1 + import 'dotenv/config'; 2 + import { defineConfig } from 'drizzle-kit'; 3 + import { configDir, projectDir } from './src/backend/common/index.js'; 4 + import * as path from 'path'; 5 + 6 + export default defineConfig({ 7 + schema: path.resolve(projectDir, 'src/backend/common/database/drizzle/schema'), 8 + out: path.resolve(projectDir, 'src/backend/common/database/drizzle/migrations'), 9 + dialect: 'sqlite', 10 + dbCredentials: { 11 + url: path.resolve(configDir, process.env.DB_FILE_NAME! ?? 'ms.db'), 12 + }, 13 + });
+947 -7
package-lock.json
··· 26 26 "@kenyip/backoff-strategies": "^1.0.4", 27 27 "@keyv/valkey": "^1.0.8", 28 28 "@lukehagar/plexjs": "^0.39.0", 29 + "@platformatic/job-queue": "^0.5.0", 29 30 "@supercharge/promise-pool": "^3.0.0", 30 31 "@svrooij/sonos": "^2.5.0", 31 32 "@xhayper/discord-rpc": "^1.3.0", ··· 47 48 "dbus-ts": "^0.0.7", 48 49 "discord.js": "^14.26.0", 49 50 "dotenv": "^10.0.0", 51 + "drizzle-kit": "^1.0.0-beta.22", 52 + "drizzle-orm": "^1.0.0-beta.22", 50 53 "express": "^5.2.1", 51 54 "express-session": "^1.19.0", 52 55 "fast-equals": "^6.0.0", ··· 129 132 "@types/jest": "^27.5.2", 130 133 "@types/jscodeshift": "^0.11.6", 131 134 "@types/mocha": "^9.1.0", 132 - "@types/node": "^20.19.2", 135 + "@types/node": "^24.12.2", 133 136 "@types/object-hash": "^3.0.0", 134 137 "@types/passport": "^1.0.12", 135 138 "@types/react": "^18.2.18", ··· 1055 1058 "version": "1.1.3", 1056 1059 "license": "ISC" 1057 1060 }, 1061 + "node_modules/@drizzle-team/brocli": { 1062 + "version": "0.11.0", 1063 + "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.11.0.tgz", 1064 + "integrity": "sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg==", 1065 + "license": "Apache-2.0" 1066 + }, 1058 1067 "node_modules/@dword-design/chdir": { 1059 1068 "version": "4.0.0", 1060 1069 "dev": true, ··· 2018 2027 "@swc/helpers": "^0.5.0" 2019 2028 } 2020 2029 }, 2030 + "node_modules/@ioredis/commands": { 2031 + "version": "1.5.1", 2032 + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.1.tgz", 2033 + "integrity": "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==", 2034 + "license": "MIT", 2035 + "optional": true 2036 + }, 2021 2037 "node_modules/@iovalkey/commands": { 2022 2038 "version": "0.1.0", 2023 2039 "license": "MIT" ··· 2172 2188 "dependencies": { 2173 2189 "@jridgewell/resolve-uri": "^3.1.0", 2174 2190 "@jridgewell/sourcemap-codec": "^1.4.14" 2191 + } 2192 + }, 2193 + "node_modules/@js-temporal/polyfill": { 2194 + "version": "0.5.1", 2195 + "resolved": "https://registry.npmjs.org/@js-temporal/polyfill/-/polyfill-0.5.1.tgz", 2196 + "integrity": "sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ==", 2197 + "license": "ISC", 2198 + "dependencies": { 2199 + "jsbi": "^4.3.0" 2200 + }, 2201 + "engines": { 2202 + "node": ">=12" 2175 2203 } 2176 2204 }, 2177 2205 "node_modules/@kenyip/backoff-strategies": { ··· 2605 2633 "url": "https://opencollective.com/parcel" 2606 2634 } 2607 2635 }, 2636 + "node_modules/@pinojs/redact": { 2637 + "version": "0.4.0", 2638 + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", 2639 + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", 2640 + "license": "MIT" 2641 + }, 2608 2642 "node_modules/@pkgjs/parseargs": { 2609 2643 "version": "0.11.0", 2610 2644 "license": "MIT", ··· 2613 2647 "node": ">=14" 2614 2648 } 2615 2649 }, 2650 + "node_modules/@platformatic/job-queue": { 2651 + "version": "0.5.0", 2652 + "resolved": "https://registry.npmjs.org/@platformatic/job-queue/-/job-queue-0.5.0.tgz", 2653 + "integrity": "sha512-vAD4HmVy3IdDhv74+282j6YS9N8wbziWT/T1IPBQNsQO+ObKiZNfDQFeakAIEUp0w80vFJyrgy42knxJ97ACgQ==", 2654 + "license": "Apache-2.0", 2655 + "dependencies": { 2656 + "pino": "^10.3.1" 2657 + }, 2658 + "engines": { 2659 + "node": ">=22.19.0" 2660 + }, 2661 + "optionalDependencies": { 2662 + "fast-write-atomic": "^0.4.0", 2663 + "iovalkey": "^0.2.0", 2664 + "pg": "^8.20.0" 2665 + } 2666 + }, 2667 + "node_modules/@platformatic/job-queue/node_modules/iovalkey": { 2668 + "version": "0.2.2", 2669 + "resolved": "https://registry.npmjs.org/iovalkey/-/iovalkey-0.2.2.tgz", 2670 + "integrity": "sha512-7eVmLOYV2UamZ/YPXuUwTu/4zBDxXcfjj/wmOwlKBBhU2qjg60Th0Y/cqfED3OxNAhc6hUV2Ft4eQMCKi2EMpQ==", 2671 + "license": "MIT", 2672 + "optional": true, 2673 + "dependencies": { 2674 + "@ioredis/commands": "^1.1.1", 2675 + "cluster-key-slot": "^1.1.0", 2676 + "debug": "^4.3.4", 2677 + "denque": "^2.1.0", 2678 + "lodash.defaults": "^4.2.0", 2679 + "lodash.isarguments": "^3.1.0", 2680 + "redis-errors": "^1.2.0", 2681 + "redis-parser": "^3.0.0", 2682 + "standard-as-callback": "^2.1.0" 2683 + }, 2684 + "engines": { 2685 + "node": ">=18.12.0" 2686 + } 2687 + }, 2688 + "node_modules/@platformatic/job-queue/node_modules/pino": { 2689 + "version": "10.3.1", 2690 + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", 2691 + "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", 2692 + "license": "MIT", 2693 + "dependencies": { 2694 + "@pinojs/redact": "^0.4.0", 2695 + "atomic-sleep": "^1.0.0", 2696 + "on-exit-leak-free": "^2.1.0", 2697 + "pino-abstract-transport": "^3.0.0", 2698 + "pino-std-serializers": "^7.0.0", 2699 + "process-warning": "^5.0.0", 2700 + "quick-format-unescaped": "^4.0.3", 2701 + "real-require": "^0.2.0", 2702 + "safe-stable-stringify": "^2.3.1", 2703 + "sonic-boom": "^4.0.1", 2704 + "thread-stream": "^4.0.0" 2705 + }, 2706 + "bin": { 2707 + "pino": "bin.js" 2708 + } 2709 + }, 2710 + "node_modules/@platformatic/job-queue/node_modules/pino-abstract-transport": { 2711 + "version": "3.0.0", 2712 + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", 2713 + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", 2714 + "license": "MIT", 2715 + "dependencies": { 2716 + "split2": "^4.0.0" 2717 + } 2718 + }, 2719 + "node_modules/@platformatic/job-queue/node_modules/process-warning": { 2720 + "version": "5.0.0", 2721 + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", 2722 + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", 2723 + "funding": [ 2724 + { 2725 + "type": "github", 2726 + "url": "https://github.com/sponsors/fastify" 2727 + }, 2728 + { 2729 + "type": "opencollective", 2730 + "url": "https://opencollective.com/fastify" 2731 + } 2732 + ], 2733 + "license": "MIT" 2734 + }, 2735 + "node_modules/@platformatic/job-queue/node_modules/thread-stream": { 2736 + "version": "4.0.0", 2737 + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz", 2738 + "integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==", 2739 + "license": "MIT", 2740 + "dependencies": { 2741 + "real-require": "^0.2.0" 2742 + }, 2743 + "engines": { 2744 + "node": ">=20" 2745 + } 2746 + }, 2616 2747 "node_modules/@protobufjs/aspromise": { 2617 2748 "version": "1.1.2", 2618 2749 "license": "BSD-3-Clause" ··· 4288 4419 "license": "MIT" 4289 4420 }, 4290 4421 "node_modules/@types/node": { 4291 - "version": "20.19.37", 4422 + "version": "24.12.2", 4423 + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", 4424 + "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", 4292 4425 "license": "MIT", 4293 4426 "dependencies": { 4294 - "undici-types": "~6.21.0" 4427 + "undici-types": "~7.16.0" 4295 4428 } 4296 4429 }, 4297 4430 "node_modules/@types/object-hash": { ··· 7241 7374 "node": ">=10" 7242 7375 } 7243 7376 }, 7377 + "node_modules/drizzle-kit": { 7378 + "version": "1.0.0-beta.22", 7379 + "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-1.0.0-beta.22.tgz", 7380 + "integrity": "sha512-9HTZuQRljQKTgCx4UhiGn8KYYfHGk4+B/bRR1714W67kz0qgJvdrG527i8rQD8uUyET9UTGR1u8syySJD4znGw==", 7381 + "license": "MIT", 7382 + "dependencies": { 7383 + "@drizzle-team/brocli": "^0.11.0", 7384 + "@js-temporal/polyfill": "^0.5.1", 7385 + "esbuild": "^0.25.10", 7386 + "get-tsconfig": "^4.13.6", 7387 + "jiti": "^2.6.1" 7388 + }, 7389 + "bin": { 7390 + "drizzle-kit": "bin.cjs" 7391 + } 7392 + }, 7393 + "node_modules/drizzle-kit/node_modules/@esbuild/aix-ppc64": { 7394 + "version": "0.25.12", 7395 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", 7396 + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", 7397 + "cpu": [ 7398 + "ppc64" 7399 + ], 7400 + "license": "MIT", 7401 + "optional": true, 7402 + "os": [ 7403 + "aix" 7404 + ], 7405 + "engines": { 7406 + "node": ">=18" 7407 + } 7408 + }, 7409 + "node_modules/drizzle-kit/node_modules/@esbuild/android-arm": { 7410 + "version": "0.25.12", 7411 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", 7412 + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", 7413 + "cpu": [ 7414 + "arm" 7415 + ], 7416 + "license": "MIT", 7417 + "optional": true, 7418 + "os": [ 7419 + "android" 7420 + ], 7421 + "engines": { 7422 + "node": ">=18" 7423 + } 7424 + }, 7425 + "node_modules/drizzle-kit/node_modules/@esbuild/android-arm64": { 7426 + "version": "0.25.12", 7427 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", 7428 + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", 7429 + "cpu": [ 7430 + "arm64" 7431 + ], 7432 + "license": "MIT", 7433 + "optional": true, 7434 + "os": [ 7435 + "android" 7436 + ], 7437 + "engines": { 7438 + "node": ">=18" 7439 + } 7440 + }, 7441 + "node_modules/drizzle-kit/node_modules/@esbuild/android-x64": { 7442 + "version": "0.25.12", 7443 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", 7444 + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", 7445 + "cpu": [ 7446 + "x64" 7447 + ], 7448 + "license": "MIT", 7449 + "optional": true, 7450 + "os": [ 7451 + "android" 7452 + ], 7453 + "engines": { 7454 + "node": ">=18" 7455 + } 7456 + }, 7457 + "node_modules/drizzle-kit/node_modules/@esbuild/darwin-arm64": { 7458 + "version": "0.25.12", 7459 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", 7460 + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", 7461 + "cpu": [ 7462 + "arm64" 7463 + ], 7464 + "license": "MIT", 7465 + "optional": true, 7466 + "os": [ 7467 + "darwin" 7468 + ], 7469 + "engines": { 7470 + "node": ">=18" 7471 + } 7472 + }, 7473 + "node_modules/drizzle-kit/node_modules/@esbuild/darwin-x64": { 7474 + "version": "0.25.12", 7475 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", 7476 + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", 7477 + "cpu": [ 7478 + "x64" 7479 + ], 7480 + "license": "MIT", 7481 + "optional": true, 7482 + "os": [ 7483 + "darwin" 7484 + ], 7485 + "engines": { 7486 + "node": ">=18" 7487 + } 7488 + }, 7489 + "node_modules/drizzle-kit/node_modules/@esbuild/freebsd-arm64": { 7490 + "version": "0.25.12", 7491 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", 7492 + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", 7493 + "cpu": [ 7494 + "arm64" 7495 + ], 7496 + "license": "MIT", 7497 + "optional": true, 7498 + "os": [ 7499 + "freebsd" 7500 + ], 7501 + "engines": { 7502 + "node": ">=18" 7503 + } 7504 + }, 7505 + "node_modules/drizzle-kit/node_modules/@esbuild/freebsd-x64": { 7506 + "version": "0.25.12", 7507 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", 7508 + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", 7509 + "cpu": [ 7510 + "x64" 7511 + ], 7512 + "license": "MIT", 7513 + "optional": true, 7514 + "os": [ 7515 + "freebsd" 7516 + ], 7517 + "engines": { 7518 + "node": ">=18" 7519 + } 7520 + }, 7521 + "node_modules/drizzle-kit/node_modules/@esbuild/linux-arm": { 7522 + "version": "0.25.12", 7523 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", 7524 + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", 7525 + "cpu": [ 7526 + "arm" 7527 + ], 7528 + "license": "MIT", 7529 + "optional": true, 7530 + "os": [ 7531 + "linux" 7532 + ], 7533 + "engines": { 7534 + "node": ">=18" 7535 + } 7536 + }, 7537 + "node_modules/drizzle-kit/node_modules/@esbuild/linux-arm64": { 7538 + "version": "0.25.12", 7539 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", 7540 + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", 7541 + "cpu": [ 7542 + "arm64" 7543 + ], 7544 + "license": "MIT", 7545 + "optional": true, 7546 + "os": [ 7547 + "linux" 7548 + ], 7549 + "engines": { 7550 + "node": ">=18" 7551 + } 7552 + }, 7553 + "node_modules/drizzle-kit/node_modules/@esbuild/linux-ia32": { 7554 + "version": "0.25.12", 7555 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", 7556 + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", 7557 + "cpu": [ 7558 + "ia32" 7559 + ], 7560 + "license": "MIT", 7561 + "optional": true, 7562 + "os": [ 7563 + "linux" 7564 + ], 7565 + "engines": { 7566 + "node": ">=18" 7567 + } 7568 + }, 7569 + "node_modules/drizzle-kit/node_modules/@esbuild/linux-loong64": { 7570 + "version": "0.25.12", 7571 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", 7572 + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", 7573 + "cpu": [ 7574 + "loong64" 7575 + ], 7576 + "license": "MIT", 7577 + "optional": true, 7578 + "os": [ 7579 + "linux" 7580 + ], 7581 + "engines": { 7582 + "node": ">=18" 7583 + } 7584 + }, 7585 + "node_modules/drizzle-kit/node_modules/@esbuild/linux-mips64el": { 7586 + "version": "0.25.12", 7587 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", 7588 + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", 7589 + "cpu": [ 7590 + "mips64el" 7591 + ], 7592 + "license": "MIT", 7593 + "optional": true, 7594 + "os": [ 7595 + "linux" 7596 + ], 7597 + "engines": { 7598 + "node": ">=18" 7599 + } 7600 + }, 7601 + "node_modules/drizzle-kit/node_modules/@esbuild/linux-ppc64": { 7602 + "version": "0.25.12", 7603 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", 7604 + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", 7605 + "cpu": [ 7606 + "ppc64" 7607 + ], 7608 + "license": "MIT", 7609 + "optional": true, 7610 + "os": [ 7611 + "linux" 7612 + ], 7613 + "engines": { 7614 + "node": ">=18" 7615 + } 7616 + }, 7617 + "node_modules/drizzle-kit/node_modules/@esbuild/linux-riscv64": { 7618 + "version": "0.25.12", 7619 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", 7620 + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", 7621 + "cpu": [ 7622 + "riscv64" 7623 + ], 7624 + "license": "MIT", 7625 + "optional": true, 7626 + "os": [ 7627 + "linux" 7628 + ], 7629 + "engines": { 7630 + "node": ">=18" 7631 + } 7632 + }, 7633 + "node_modules/drizzle-kit/node_modules/@esbuild/linux-s390x": { 7634 + "version": "0.25.12", 7635 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", 7636 + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", 7637 + "cpu": [ 7638 + "s390x" 7639 + ], 7640 + "license": "MIT", 7641 + "optional": true, 7642 + "os": [ 7643 + "linux" 7644 + ], 7645 + "engines": { 7646 + "node": ">=18" 7647 + } 7648 + }, 7649 + "node_modules/drizzle-kit/node_modules/@esbuild/linux-x64": { 7650 + "version": "0.25.12", 7651 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", 7652 + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", 7653 + "cpu": [ 7654 + "x64" 7655 + ], 7656 + "license": "MIT", 7657 + "optional": true, 7658 + "os": [ 7659 + "linux" 7660 + ], 7661 + "engines": { 7662 + "node": ">=18" 7663 + } 7664 + }, 7665 + "node_modules/drizzle-kit/node_modules/@esbuild/netbsd-arm64": { 7666 + "version": "0.25.12", 7667 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", 7668 + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", 7669 + "cpu": [ 7670 + "arm64" 7671 + ], 7672 + "license": "MIT", 7673 + "optional": true, 7674 + "os": [ 7675 + "netbsd" 7676 + ], 7677 + "engines": { 7678 + "node": ">=18" 7679 + } 7680 + }, 7681 + "node_modules/drizzle-kit/node_modules/@esbuild/netbsd-x64": { 7682 + "version": "0.25.12", 7683 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", 7684 + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", 7685 + "cpu": [ 7686 + "x64" 7687 + ], 7688 + "license": "MIT", 7689 + "optional": true, 7690 + "os": [ 7691 + "netbsd" 7692 + ], 7693 + "engines": { 7694 + "node": ">=18" 7695 + } 7696 + }, 7697 + "node_modules/drizzle-kit/node_modules/@esbuild/openbsd-arm64": { 7698 + "version": "0.25.12", 7699 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", 7700 + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", 7701 + "cpu": [ 7702 + "arm64" 7703 + ], 7704 + "license": "MIT", 7705 + "optional": true, 7706 + "os": [ 7707 + "openbsd" 7708 + ], 7709 + "engines": { 7710 + "node": ">=18" 7711 + } 7712 + }, 7713 + "node_modules/drizzle-kit/node_modules/@esbuild/openbsd-x64": { 7714 + "version": "0.25.12", 7715 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", 7716 + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", 7717 + "cpu": [ 7718 + "x64" 7719 + ], 7720 + "license": "MIT", 7721 + "optional": true, 7722 + "os": [ 7723 + "openbsd" 7724 + ], 7725 + "engines": { 7726 + "node": ">=18" 7727 + } 7728 + }, 7729 + "node_modules/drizzle-kit/node_modules/@esbuild/openharmony-arm64": { 7730 + "version": "0.25.12", 7731 + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", 7732 + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", 7733 + "cpu": [ 7734 + "arm64" 7735 + ], 7736 + "license": "MIT", 7737 + "optional": true, 7738 + "os": [ 7739 + "openharmony" 7740 + ], 7741 + "engines": { 7742 + "node": ">=18" 7743 + } 7744 + }, 7745 + "node_modules/drizzle-kit/node_modules/@esbuild/sunos-x64": { 7746 + "version": "0.25.12", 7747 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", 7748 + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", 7749 + "cpu": [ 7750 + "x64" 7751 + ], 7752 + "license": "MIT", 7753 + "optional": true, 7754 + "os": [ 7755 + "sunos" 7756 + ], 7757 + "engines": { 7758 + "node": ">=18" 7759 + } 7760 + }, 7761 + "node_modules/drizzle-kit/node_modules/@esbuild/win32-arm64": { 7762 + "version": "0.25.12", 7763 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", 7764 + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", 7765 + "cpu": [ 7766 + "arm64" 7767 + ], 7768 + "license": "MIT", 7769 + "optional": true, 7770 + "os": [ 7771 + "win32" 7772 + ], 7773 + "engines": { 7774 + "node": ">=18" 7775 + } 7776 + }, 7777 + "node_modules/drizzle-kit/node_modules/@esbuild/win32-ia32": { 7778 + "version": "0.25.12", 7779 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", 7780 + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", 7781 + "cpu": [ 7782 + "ia32" 7783 + ], 7784 + "license": "MIT", 7785 + "optional": true, 7786 + "os": [ 7787 + "win32" 7788 + ], 7789 + "engines": { 7790 + "node": ">=18" 7791 + } 7792 + }, 7793 + "node_modules/drizzle-kit/node_modules/@esbuild/win32-x64": { 7794 + "version": "0.25.12", 7795 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", 7796 + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", 7797 + "cpu": [ 7798 + "x64" 7799 + ], 7800 + "license": "MIT", 7801 + "optional": true, 7802 + "os": [ 7803 + "win32" 7804 + ], 7805 + "engines": { 7806 + "node": ">=18" 7807 + } 7808 + }, 7809 + "node_modules/drizzle-kit/node_modules/esbuild": { 7810 + "version": "0.25.12", 7811 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", 7812 + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", 7813 + "hasInstallScript": true, 7814 + "license": "MIT", 7815 + "bin": { 7816 + "esbuild": "bin/esbuild" 7817 + }, 7818 + "engines": { 7819 + "node": ">=18" 7820 + }, 7821 + "optionalDependencies": { 7822 + "@esbuild/aix-ppc64": "0.25.12", 7823 + "@esbuild/android-arm": "0.25.12", 7824 + "@esbuild/android-arm64": "0.25.12", 7825 + "@esbuild/android-x64": "0.25.12", 7826 + "@esbuild/darwin-arm64": "0.25.12", 7827 + "@esbuild/darwin-x64": "0.25.12", 7828 + "@esbuild/freebsd-arm64": "0.25.12", 7829 + "@esbuild/freebsd-x64": "0.25.12", 7830 + "@esbuild/linux-arm": "0.25.12", 7831 + "@esbuild/linux-arm64": "0.25.12", 7832 + "@esbuild/linux-ia32": "0.25.12", 7833 + "@esbuild/linux-loong64": "0.25.12", 7834 + "@esbuild/linux-mips64el": "0.25.12", 7835 + "@esbuild/linux-ppc64": "0.25.12", 7836 + "@esbuild/linux-riscv64": "0.25.12", 7837 + "@esbuild/linux-s390x": "0.25.12", 7838 + "@esbuild/linux-x64": "0.25.12", 7839 + "@esbuild/netbsd-arm64": "0.25.12", 7840 + "@esbuild/netbsd-x64": "0.25.12", 7841 + "@esbuild/openbsd-arm64": "0.25.12", 7842 + "@esbuild/openbsd-x64": "0.25.12", 7843 + "@esbuild/openharmony-arm64": "0.25.12", 7844 + "@esbuild/sunos-x64": "0.25.12", 7845 + "@esbuild/win32-arm64": "0.25.12", 7846 + "@esbuild/win32-ia32": "0.25.12", 7847 + "@esbuild/win32-x64": "0.25.12" 7848 + } 7849 + }, 7850 + "node_modules/drizzle-orm": { 7851 + "version": "1.0.0-beta.22", 7852 + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-1.0.0-beta.22.tgz", 7853 + "integrity": "sha512-F+DZyVIvH0oVKa/w08Cle1xfoH+pc+htIXHG/frnMLG72aby9NYYr9oc+9XvghnoO4umxFItduz0OMmQJMnenw==", 7854 + "license": "Apache-2.0", 7855 + "peerDependencies": { 7856 + "@aws-sdk/client-rds-data": ">=3", 7857 + "@cloudflare/workers-types": ">=4", 7858 + "@effect/sql": "^0.48.5", 7859 + "@effect/sql-pg": "^0.49.7", 7860 + "@electric-sql/pglite": ">=0.2.0", 7861 + "@libsql/client": ">=0.10.0", 7862 + "@libsql/client-wasm": ">=0.10.0", 7863 + "@neondatabase/serverless": ">=0.10.0", 7864 + "@op-engineering/op-sqlite": ">=2", 7865 + "@opentelemetry/api": "^1.4.1", 7866 + "@planetscale/database": ">=1.13", 7867 + "@sinclair/typebox": ">=0.34.8", 7868 + "@sqlitecloud/drivers": ">=1.0.653", 7869 + "@tidbcloud/serverless": "*", 7870 + "@tursodatabase/database": ">=0.2.1", 7871 + "@tursodatabase/database-common": ">=0.2.1", 7872 + "@tursodatabase/database-wasm": ">=0.2.1", 7873 + "@types/better-sqlite3": "*", 7874 + "@types/mssql": "^9.1.4", 7875 + "@types/pg": "*", 7876 + "@types/sql.js": "*", 7877 + "@upstash/redis": ">=1.34.7", 7878 + "@vercel/postgres": ">=0.8.0", 7879 + "@xata.io/client": "*", 7880 + "arktype": ">=2.0.0", 7881 + "better-sqlite3": ">=9.3.0", 7882 + "bun-types": "*", 7883 + "expo-sqlite": ">=14.0.0", 7884 + "gel": ">=2", 7885 + "mssql": "^11.0.1", 7886 + "mysql2": ">=2", 7887 + "pg": ">=8", 7888 + "postgres": ">=3", 7889 + "sql.js": ">=1", 7890 + "sqlite3": ">=5", 7891 + "typebox": ">=1.0.0", 7892 + "valibot": ">=1.0.0-beta.7", 7893 + "zod": "^3.25.0 || ^4.0.0" 7894 + }, 7895 + "peerDependenciesMeta": { 7896 + "@aws-sdk/client-rds-data": { 7897 + "optional": true 7898 + }, 7899 + "@cloudflare/workers-types": { 7900 + "optional": true 7901 + }, 7902 + "@effect/sql": { 7903 + "optional": true 7904 + }, 7905 + "@effect/sql-pg": { 7906 + "optional": true 7907 + }, 7908 + "@electric-sql/pglite": { 7909 + "optional": true 7910 + }, 7911 + "@libsql/client": { 7912 + "optional": true 7913 + }, 7914 + "@libsql/client-wasm": { 7915 + "optional": true 7916 + }, 7917 + "@neondatabase/serverless": { 7918 + "optional": true 7919 + }, 7920 + "@op-engineering/op-sqlite": { 7921 + "optional": true 7922 + }, 7923 + "@opentelemetry/api": { 7924 + "optional": true 7925 + }, 7926 + "@planetscale/database": { 7927 + "optional": true 7928 + }, 7929 + "@sinclair/typebox": { 7930 + "optional": true 7931 + }, 7932 + "@sqlitecloud/drivers": { 7933 + "optional": true 7934 + }, 7935 + "@tidbcloud/serverless": { 7936 + "optional": true 7937 + }, 7938 + "@tursodatabase/database": { 7939 + "optional": true 7940 + }, 7941 + "@tursodatabase/database-common": { 7942 + "optional": true 7943 + }, 7944 + "@tursodatabase/database-wasm": { 7945 + "optional": true 7946 + }, 7947 + "@types/better-sqlite3": { 7948 + "optional": true 7949 + }, 7950 + "@types/mssql": { 7951 + "optional": true 7952 + }, 7953 + "@types/pg": { 7954 + "optional": true 7955 + }, 7956 + "@types/sql.js": { 7957 + "optional": true 7958 + }, 7959 + "@upstash/redis": { 7960 + "optional": true 7961 + }, 7962 + "@vercel/postgres": { 7963 + "optional": true 7964 + }, 7965 + "@xata.io/client": { 7966 + "optional": true 7967 + }, 7968 + "arktype": { 7969 + "optional": true 7970 + }, 7971 + "better-sqlite3": { 7972 + "optional": true 7973 + }, 7974 + "bun-types": { 7975 + "optional": true 7976 + }, 7977 + "expo-sqlite": { 7978 + "optional": true 7979 + }, 7980 + "gel": { 7981 + "optional": true 7982 + }, 7983 + "mssql": { 7984 + "optional": true 7985 + }, 7986 + "mysql2": { 7987 + "optional": true 7988 + }, 7989 + "pg": { 7990 + "optional": true 7991 + }, 7992 + "postgres": { 7993 + "optional": true 7994 + }, 7995 + "sql.js": { 7996 + "optional": true 7997 + }, 7998 + "sqlite3": { 7999 + "optional": true 8000 + }, 8001 + "typebox": { 8002 + "optional": true 8003 + }, 8004 + "valibot": { 8005 + "optional": true 8006 + }, 8007 + "zod": { 8008 + "optional": true 8009 + } 8010 + } 8011 + }, 7244 8012 "node_modules/dunder-proto": { 7245 8013 "version": "1.0.1", 7246 8014 "license": "MIT", ··· 8160 8928 "version": "3.0.3", 8161 8929 "license": "BSD-3-Clause" 8162 8930 }, 8931 + "node_modules/fast-write-atomic": { 8932 + "version": "0.4.0", 8933 + "resolved": "https://registry.npmjs.org/fast-write-atomic/-/fast-write-atomic-0.4.0.tgz", 8934 + "integrity": "sha512-O0AGqOsuOgsVXFiF/vIm7c+NHRZB6c6IDMBVPWc8rLGWVW7FYaBBfzhqxKlKyGKVF5tPR4drUOxE02lDiWjbSg==", 8935 + "license": "MIT", 8936 + "optional": true, 8937 + "engines": { 8938 + "node": ">=20" 8939 + } 8940 + }, 8163 8941 "node_modules/fast-xml-parser": { 8164 8942 "version": "3.19.0", 8165 8943 "license": "MIT", ··· 8564 9342 } 8565 9343 }, 8566 9344 "node_modules/get-tsconfig": { 8567 - "version": "4.8.1", 9345 + "version": "4.14.0", 9346 + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", 9347 + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", 8568 9348 "license": "MIT", 8569 9349 "dependencies": { 8570 9350 "resolve-pkg-maps": "^1.0.0" ··· 9561 10341 "version": "2.6.1", 9562 10342 "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", 9563 10343 "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", 9564 - "dev": true, 9565 10344 "license": "MIT", 9566 10345 "bin": { 9567 10346 "jiti": "lib/jiti-cli.mjs" ··· 9596 10375 "bin": { 9597 10376 "js-yaml": "bin/js-yaml.js" 9598 10377 } 10378 + }, 10379 + "node_modules/jsbi": { 10380 + "version": "4.3.2", 10381 + "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-4.3.2.tgz", 10382 + "integrity": "sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew==", 10383 + "license": "Apache-2.0" 9599 10384 }, 9600 10385 "node_modules/jsesc": { 9601 10386 "version": "3.1.0", ··· 11532 12317 "dev": true, 11533 12318 "license": "MIT" 11534 12319 }, 12320 + "node_modules/pg": { 12321 + "version": "8.20.0", 12322 + "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz", 12323 + "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==", 12324 + "license": "MIT", 12325 + "optional": true, 12326 + "dependencies": { 12327 + "pg-connection-string": "^2.12.0", 12328 + "pg-pool": "^3.13.0", 12329 + "pg-protocol": "^1.13.0", 12330 + "pg-types": "2.2.0", 12331 + "pgpass": "1.0.5" 12332 + }, 12333 + "engines": { 12334 + "node": ">= 16.0.0" 12335 + }, 12336 + "optionalDependencies": { 12337 + "pg-cloudflare": "^1.3.0" 12338 + }, 12339 + "peerDependencies": { 12340 + "pg-native": ">=3.0.1" 12341 + }, 12342 + "peerDependenciesMeta": { 12343 + "pg-native": { 12344 + "optional": true 12345 + } 12346 + } 12347 + }, 12348 + "node_modules/pg-cloudflare": { 12349 + "version": "1.3.0", 12350 + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz", 12351 + "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==", 12352 + "license": "MIT", 12353 + "optional": true 12354 + }, 12355 + "node_modules/pg-connection-string": { 12356 + "version": "2.12.0", 12357 + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz", 12358 + "integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==", 12359 + "license": "MIT", 12360 + "optional": true 12361 + }, 12362 + "node_modules/pg-int8": { 12363 + "version": "1.0.1", 12364 + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", 12365 + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", 12366 + "license": "ISC", 12367 + "optional": true, 12368 + "engines": { 12369 + "node": ">=4.0.0" 12370 + } 12371 + }, 12372 + "node_modules/pg-pool": { 12373 + "version": "3.13.0", 12374 + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz", 12375 + "integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==", 12376 + "license": "MIT", 12377 + "optional": true, 12378 + "peerDependencies": { 12379 + "pg": ">=8.0" 12380 + } 12381 + }, 12382 + "node_modules/pg-protocol": { 12383 + "version": "1.13.0", 12384 + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz", 12385 + "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==", 12386 + "license": "MIT", 12387 + "optional": true 12388 + }, 12389 + "node_modules/pg-types": { 12390 + "version": "2.2.0", 12391 + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", 12392 + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", 12393 + "license": "MIT", 12394 + "optional": true, 12395 + "dependencies": { 12396 + "pg-int8": "1.0.1", 12397 + "postgres-array": "~2.0.0", 12398 + "postgres-bytea": "~1.0.0", 12399 + "postgres-date": "~1.0.4", 12400 + "postgres-interval": "^1.1.0" 12401 + }, 12402 + "engines": { 12403 + "node": ">=4" 12404 + } 12405 + }, 12406 + "node_modules/pgpass": { 12407 + "version": "1.0.5", 12408 + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", 12409 + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", 12410 + "license": "MIT", 12411 + "optional": true, 12412 + "dependencies": { 12413 + "split2": "^4.1.0" 12414 + } 12415 + }, 11535 12416 "node_modules/phin": { 11536 12417 "version": "3.7.1", 11537 12418 "license": "MIT", ··· 11735 12616 "version": "4.2.0", 11736 12617 "dev": true, 11737 12618 "license": "MIT" 12619 + }, 12620 + "node_modules/postgres-array": { 12621 + "version": "2.0.0", 12622 + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", 12623 + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", 12624 + "license": "MIT", 12625 + "optional": true, 12626 + "engines": { 12627 + "node": ">=4" 12628 + } 12629 + }, 12630 + "node_modules/postgres-bytea": { 12631 + "version": "1.0.1", 12632 + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", 12633 + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", 12634 + "license": "MIT", 12635 + "optional": true, 12636 + "engines": { 12637 + "node": ">=0.10.0" 12638 + } 12639 + }, 12640 + "node_modules/postgres-date": { 12641 + "version": "1.0.7", 12642 + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", 12643 + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", 12644 + "license": "MIT", 12645 + "optional": true, 12646 + "engines": { 12647 + "node": ">=0.10.0" 12648 + } 12649 + }, 12650 + "node_modules/postgres-interval": { 12651 + "version": "1.2.0", 12652 + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", 12653 + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", 12654 + "license": "MIT", 12655 + "optional": true, 12656 + "dependencies": { 12657 + "xtend": "^4.0.0" 12658 + }, 12659 + "engines": { 12660 + "node": ">=0.10.0" 12661 + } 11738 12662 }, 11739 12663 "node_modules/prelude-ls": { 11740 12664 "version": "1.2.1", ··· 13947 14871 } 13948 14872 }, 13949 14873 "node_modules/undici-types": { 13950 - "version": "6.21.0", 14874 + "version": "7.16.0", 14875 + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", 14876 + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", 13951 14877 "license": "MIT" 13952 14878 }, 13953 14879 "node_modules/unicorn-magic": { ··· 14927 15853 "node": ">=4.0" 14928 15854 } 14929 15855 }, 15856 + "node_modules/xtend": { 15857 + "version": "4.0.2", 15858 + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 15859 + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", 15860 + "license": "MIT", 15861 + "optional": true, 15862 + "engines": { 15863 + "node": ">=0.4" 15864 + } 15865 + }, 14930 15866 "node_modules/y18n": { 14931 15867 "version": "5.0.8", 14932 15868 "dev": true, ··· 15089 16025 } 15090 16026 }, 15091 16027 "node_modules/zod": { 15092 - "version": "3.23.8", 16028 + "version": "3.25.76", 16029 + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", 16030 + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", 15093 16031 "license": "MIT", 15094 16032 "funding": { 15095 16033 "url": "https://github.com/sponsors/colinhacks" ··· 15097 16035 }, 15098 16036 "node_modules/zod-validation-error": { 15099 16037 "version": "2.1.0", 16038 + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-2.1.0.tgz", 16039 + "integrity": "sha512-VJh93e2wb4c3tWtGgTa0OF/dTt/zoPCPzXq4V11ZjxmEAFaPi/Zss1xIZdEB5RD8GD00U0/iVXgqkF77RV7pdQ==", 15100 16040 "license": "MIT", 15101 16041 "engines": { 15102 16042 "node": ">=18.0.0"
+4 -1
package.json
··· 64 64 "@kenyip/backoff-strategies": "^1.0.4", 65 65 "@keyv/valkey": "^1.0.8", 66 66 "@lukehagar/plexjs": "^0.39.0", 67 + "@platformatic/job-queue": "^0.5.0", 67 68 "@supercharge/promise-pool": "^3.0.0", 68 69 "@svrooij/sonos": "^2.5.0", 69 70 "@xhayper/discord-rpc": "^1.3.0", ··· 85 86 "dbus-ts": "^0.0.7", 86 87 "discord.js": "^14.26.0", 87 88 "dotenv": "^10.0.0", 89 + "drizzle-kit": "^1.0.0-beta.22", 90 + "drizzle-orm": "^1.0.0-beta.22", 88 91 "express": "^5.2.1", 89 92 "express-session": "^1.19.0", 90 93 "fast-equals": "^6.0.0", ··· 167 170 "@types/jest": "^27.5.2", 168 171 "@types/jscodeshift": "^0.11.6", 169 172 "@types/mocha": "^9.1.0", 170 - "@types/node": "^20.19.2", 173 + "@types/node": "^24.12.2", 171 174 "@types/object-hash": "^3.0.0", 172 175 "@types/passport": "^1.0.12", 173 176 "@types/react": "^18.2.18",
+60
patches/kysely-node-native-sqlite+1.1.0.patch
··· 1 + diff --git a/node_modules/kysely-node-native-sqlite/dist/index.cjs b/node_modules/kysely-node-native-sqlite/dist/index.cjs 2 + index 1015da7..0679a95 100644 3 + --- a/node_modules/kysely-node-native-sqlite/dist/index.cjs 4 + +++ b/node_modules/kysely-node-native-sqlite/dist/index.cjs 5 + @@ -55,7 +55,11 @@ var import_node_sqlite = require("node:sqlite"); 6 + var NodeNativeSqliteConnection = class { 7 + #db; 8 + constructor(...args) { 9 + - this.#db = new import_node_sqlite.DatabaseSync(...args); 10 + + if (args[0] instanceof import_node_sqlite.DatabaseSync) { 11 + + this.#db = args[0]; 12 + + } else { 13 + + this.#db = new import_node_sqlite.DatabaseSync(...args); 14 + + } 15 + } 16 + [Symbol.dispose]() { 17 + this.#db.close(); 18 + diff --git a/node_modules/kysely-node-native-sqlite/dist/index.d.cts b/node_modules/kysely-node-native-sqlite/dist/index.d.cts 19 + index 72366bb..32e8464 100644 20 + --- a/node_modules/kysely-node-native-sqlite/dist/index.d.cts 21 + +++ b/node_modules/kysely-node-native-sqlite/dist/index.d.cts 22 + @@ -3,7 +3,7 @@ import { Dialect, SqliteAdapter, Driver, Kysely, DatabaseIntrospector, SqliteQue 23 + 24 + declare class NodeNativeSqliteDialect implements Dialect { 25 + #private; 26 + - constructor(...args: ConstructorParameters<typeof DatabaseSync>); 27 + + constructor(...args: ConstructorParameters<typeof DatabaseSync> | [DatabaseSync]); 28 + createAdapter(): SqliteAdapter; 29 + createDriver(): Driver; 30 + createIntrospector(db: Kysely<unknown>): DatabaseIntrospector; 31 + diff --git a/node_modules/kysely-node-native-sqlite/dist/index.d.ts b/node_modules/kysely-node-native-sqlite/dist/index.d.ts 32 + index 72366bb..32e8464 100644 33 + --- a/node_modules/kysely-node-native-sqlite/dist/index.d.ts 34 + +++ b/node_modules/kysely-node-native-sqlite/dist/index.d.ts 35 + @@ -3,7 +3,7 @@ import { Dialect, SqliteAdapter, Driver, Kysely, DatabaseIntrospector, SqliteQue 36 + 37 + declare class NodeNativeSqliteDialect implements Dialect { 38 + #private; 39 + - constructor(...args: ConstructorParameters<typeof DatabaseSync>); 40 + + constructor(...args: ConstructorParameters<typeof DatabaseSync> | [DatabaseSync]); 41 + createAdapter(): SqliteAdapter; 42 + createDriver(): Driver; 43 + createIntrospector(db: Kysely<unknown>): DatabaseIntrospector; 44 + diff --git a/node_modules/kysely-node-native-sqlite/dist/index.js b/node_modules/kysely-node-native-sqlite/dist/index.js 45 + index 8c2b8cf..17b7eb1 100644 46 + --- a/node_modules/kysely-node-native-sqlite/dist/index.js 47 + +++ b/node_modules/kysely-node-native-sqlite/dist/index.js 48 + @@ -32,7 +32,11 @@ import { DatabaseSync } from "node:sqlite"; 49 + var NodeNativeSqliteConnection = class { 50 + #db; 51 + constructor(...args) { 52 + - this.#db = new DatabaseSync(...args); 53 + + if (args[0] instanceof DatabaseSync) { 54 + + this.#db = args[0]; 55 + + } else { 56 + + this.#db = new DatabaseSync(...args); 57 + + } 58 + } 59 + [Symbol.dispose]() { 60 + this.#db.close();
+44
src/backend/common/database/Database.ts
··· 1 + import { DatabaseSync } from 'node:sqlite'; 2 + import { configDir } from '../index.js'; 3 + import * as path from 'path'; 4 + import { promises as fs } from 'fs' 5 + import { childLogger, Logger } from '@foxxmd/logging'; 6 + import { loggerNoop } from '../MaybeLogger.js'; 7 + import { fileExists, fileOrDirectoryIsWriteable } from '../../utils/FSUtils.js'; 8 + 9 + export const MEMORY_DB_NAME = ':memory:'; 10 + export const isMemoryDb = (name: string): boolean => name === MEMORY_DB_NAME; 11 + 12 + export const getDbPath = (name: string = 'ms', workingDirectory?: string): string => { 13 + if(isMemoryDb(name)) { 14 + return MEMORY_DB_NAME; 15 + } 16 + return path.resolve(workingDirectory ?? configDir, `${name}.db`); 17 + } 18 + 19 + export const backupDb = async (dbName: string, parentLogger: Logger = loggerNoop): Promise<void> => { 20 + 21 + const logger = childLogger(parentLogger, 'Migrations'); 22 + 23 + const dbPath = getDbPath(dbName); 24 + let newDb = false; 25 + 26 + if(dbPath !== MEMORY_DB_NAME) { 27 + if(!fileExists(dbPath)) { 28 + logger.info(`Database at ${dbPath} does not exist, will create it.`); 29 + newDb = true; 30 + } 31 + try { 32 + fileOrDirectoryIsWriteable(dbPath); 33 + } catch (e) { 34 + throw new Error('Cannot access database path for migrations', {cause: e}); 35 + } 36 + } 37 + 38 + if(dbPath !== MEMORY_DB_NAME && !newDb) { 39 + const backupPath = `${getDbPath(`${Date.now()}-${dbName}`)}.bak`; 40 + logger.info(`Backing up database before migrating => ${backupPath}`); 41 + await fs.copyFile(dbPath, backupPath) 42 + logger.info('Backed up!'); 43 + } 44 + }
+81
src/backend/common/database/drizzle/drizzleUtils.ts
··· 1 + import { drizzle } from 'drizzle-orm/node-sqlite'; 2 + import { migrate } from 'drizzle-orm/node-sqlite/migrator'; 3 + import { sql as dsl } from 'drizzle-orm'; 4 + import * as fs from 'fs/promises'; 5 + import * as path from 'path'; 6 + import { getDbPath } from '../Database.js'; 7 + import { fileExists } from '../../../utils/FSUtils.js'; 8 + import { childLogger, Logger } from '@foxxmd/logging'; 9 + import { loggerNoop } from '../../MaybeLogger.js'; 10 + import { projectDir } from '../../index.js'; 11 + import { relations } from './schema/drizzlePlaysTable.js'; 12 + 13 + export async function shouldBackupDb(dbPath: string, parentLogger: Logger = loggerNoop): Promise<boolean> { 14 + 15 + const logger = childLogger(parentLogger, 'Migrations'); 16 + 17 + logger.info(`Checking database at ${dbPath}`); 18 + if (!fileExists(dbPath)) { 19 + logger.info(`No database exists!`); 20 + return false; 21 + } 22 + 23 + const db = drizzle(dbPath); 24 + 25 + try { 26 + // Ensure the migrations table exists 27 + // https://github.com/drizzle-team/drizzle-orm/issues/1953 28 + const res = db.all(dsl` 29 + SELECT count(*) FROM sqlite_master WHERE type='table' AND name='__drizzle_migrations'; 30 + `); 31 + 32 + if (res[0]['count(*)'] === 0) { 33 + logger.info(`Database exists but there is no __drizzle_migrations table??`); 34 + return false; 35 + } 36 + 37 + const dbMigrations = await db.all(dsl`SELECT id, hash, created_at, name, applied_at FROM "__drizzle_migrations" ORDER BY created_at DESC`); 38 + const appliedMigrations = new Set(dbMigrations.map((m: any) => m.name)); 39 + 40 + const allFiles = await fs.readdir(path.resolve(projectDir, 'src/backend/common/database/drizzleMigrations')); 41 + const migrationFiles = allFiles 42 + .sort(); 43 + 44 + const pendingMigrations = migrationFiles.filter(file => { 45 + return !appliedMigrations.has(file); 46 + }); 47 + 48 + //console.log('Applied migrations:', Array.from(appliedMigrations)); 49 + if (pendingMigrations.length > 0) { 50 + logger.info(`${pendingMigrations.length} pending migrations:\n${pendingMigrations.join('\n')}`); 51 + return true; 52 + } else { 53 + logger.info('No pending migrations.'); 54 + return false; 55 + } 56 + } catch (error) { 57 + logger.error(new Error('Failed to get pending migrations', { cause: error })); 58 + return true; 59 + } 60 + } 61 + 62 + export const getDb = (dbName: string = 'ms', opts: { logger?: Logger, workingDirectory?: string } = {}) => { 63 + const { 64 + workingDirectory, 65 + logger = loggerNoop 66 + } = opts; 67 + const dbPath = getDbPath(dbName, workingDirectory); 68 + logger.info(`Using database at ${dbPath}`); 69 + return drizzle(dbPath, {relations: relations}); 70 + } 71 + 72 + export const migrateDb = async (db: ReturnType<typeof drizzle>, parentLogger: Logger = loggerNoop) => { 73 + const logger = childLogger(parentLogger, 'Migrations'); 74 + 75 + try { 76 + await migrate(db, { migrationsFolder: path.resolve(projectDir, 'src/backend/common/database/drizzle/migrations') }); 77 + logger.info('Migrations complete'); 78 + } catch (e) { 79 + throw new Error('Failed to migrate database', { cause: e }); 80 + } 81 + }
+35
src/backend/common/database/drizzle/migrations/20260422185819_white_wilson_fisk/migration.sql
··· 1 + CREATE TABLE `play_inputs` ( 2 + `id` integer PRIMARY KEY, 3 + `playId` text(30), 4 + `data` text, 5 + `play` text, 6 + `createdAt` integer, 7 + CONSTRAINT `fk_play_inputs_playId_plays_id_fk` FOREIGN KEY (`playId`) REFERENCES `plays`(`id`) ON UPDATE CASCADE ON DELETE CASCADE 8 + ); 9 + --> statement-breakpoint 10 + CREATE TABLE `plays` ( 11 + `id` text(30) PRIMARY KEY, 12 + `componentType` text(50) NOT NULL, 13 + `componentName` text(200) NOT NULL, 14 + `error` text, 15 + `playedAt` integer, 16 + `seenAt` integer, 17 + `play` text NOT NULL, 18 + `parentId` text(30) 19 + ); 20 + --> statement-breakpoint 21 + CREATE TABLE `play_queue_state` ( 22 + `id` integer PRIMARY KEY, 23 + `playId` text, 24 + `queueName` text(200), 25 + `queueStatus` text(30), 26 + `error` text, 27 + `createdAt` integer, 28 + CONSTRAINT `fk_play_queue_state_playId_plays_id_fk` FOREIGN KEY (`playId`) REFERENCES `plays`(`id`) ON UPDATE CASCADE ON DELETE CASCADE 29 + ); 30 + --> statement-breakpoint 31 + CREATE INDEX `play_input_id_idx` ON `play_inputs` (`playId`);--> statement-breakpoint 32 + CREATE INDEX `play_parent_id_idx` ON `plays` (`parentId`);--> statement-breakpoint 33 + CREATE INDEX `play_playedAt_idx` ON `plays` (`playedAt`);--> statement-breakpoint 34 + CREATE INDEX `play_seenAt_idx` ON `plays` (`seenAt`);--> statement-breakpoint 35 + CREATE INDEX `play_queue_state_id_idx` ON `play_queue_state` (`playId`);
+340
src/backend/common/database/drizzle/migrations/20260422185819_white_wilson_fisk/snapshot.json
··· 1 + { 2 + "version": "7", 3 + "dialect": "sqlite", 4 + "id": "96a2050a-7d06-4db1-b989-3082b6374e1f", 5 + "prevIds": [ 6 + "00000000-0000-0000-0000-000000000000" 7 + ], 8 + "ddl": [ 9 + { 10 + "name": "play_inputs", 11 + "entityType": "tables" 12 + }, 13 + { 14 + "name": "plays", 15 + "entityType": "tables" 16 + }, 17 + { 18 + "name": "play_queue_state", 19 + "entityType": "tables" 20 + }, 21 + { 22 + "type": "integer", 23 + "notNull": false, 24 + "autoincrement": false, 25 + "default": null, 26 + "generated": null, 27 + "name": "id", 28 + "entityType": "columns", 29 + "table": "play_inputs" 30 + }, 31 + { 32 + "type": "text(30)", 33 + "notNull": false, 34 + "autoincrement": false, 35 + "default": null, 36 + "generated": null, 37 + "name": "playId", 38 + "entityType": "columns", 39 + "table": "play_inputs" 40 + }, 41 + { 42 + "type": "text", 43 + "notNull": false, 44 + "autoincrement": false, 45 + "default": null, 46 + "generated": null, 47 + "name": "data", 48 + "entityType": "columns", 49 + "table": "play_inputs" 50 + }, 51 + { 52 + "type": "text", 53 + "notNull": false, 54 + "autoincrement": false, 55 + "default": null, 56 + "generated": null, 57 + "name": "play", 58 + "entityType": "columns", 59 + "table": "play_inputs" 60 + }, 61 + { 62 + "type": "integer", 63 + "notNull": false, 64 + "autoincrement": false, 65 + "default": null, 66 + "generated": null, 67 + "name": "createdAt", 68 + "entityType": "columns", 69 + "table": "play_inputs" 70 + }, 71 + { 72 + "type": "text(30)", 73 + "notNull": false, 74 + "autoincrement": false, 75 + "default": null, 76 + "generated": null, 77 + "name": "id", 78 + "entityType": "columns", 79 + "table": "plays" 80 + }, 81 + { 82 + "type": "text(50)", 83 + "notNull": true, 84 + "autoincrement": false, 85 + "default": null, 86 + "generated": null, 87 + "name": "componentType", 88 + "entityType": "columns", 89 + "table": "plays" 90 + }, 91 + { 92 + "type": "text(200)", 93 + "notNull": true, 94 + "autoincrement": false, 95 + "default": null, 96 + "generated": null, 97 + "name": "componentName", 98 + "entityType": "columns", 99 + "table": "plays" 100 + }, 101 + { 102 + "type": "text", 103 + "notNull": false, 104 + "autoincrement": false, 105 + "default": null, 106 + "generated": null, 107 + "name": "error", 108 + "entityType": "columns", 109 + "table": "plays" 110 + }, 111 + { 112 + "type": "integer", 113 + "notNull": false, 114 + "autoincrement": false, 115 + "default": null, 116 + "generated": null, 117 + "name": "playedAt", 118 + "entityType": "columns", 119 + "table": "plays" 120 + }, 121 + { 122 + "type": "integer", 123 + "notNull": false, 124 + "autoincrement": false, 125 + "default": null, 126 + "generated": null, 127 + "name": "seenAt", 128 + "entityType": "columns", 129 + "table": "plays" 130 + }, 131 + { 132 + "type": "text", 133 + "notNull": true, 134 + "autoincrement": false, 135 + "default": null, 136 + "generated": null, 137 + "name": "play", 138 + "entityType": "columns", 139 + "table": "plays" 140 + }, 141 + { 142 + "type": "text(30)", 143 + "notNull": false, 144 + "autoincrement": false, 145 + "default": null, 146 + "generated": null, 147 + "name": "parentId", 148 + "entityType": "columns", 149 + "table": "plays" 150 + }, 151 + { 152 + "type": "integer", 153 + "notNull": false, 154 + "autoincrement": false, 155 + "default": null, 156 + "generated": null, 157 + "name": "id", 158 + "entityType": "columns", 159 + "table": "play_queue_state" 160 + }, 161 + { 162 + "type": "text", 163 + "notNull": false, 164 + "autoincrement": false, 165 + "default": null, 166 + "generated": null, 167 + "name": "playId", 168 + "entityType": "columns", 169 + "table": "play_queue_state" 170 + }, 171 + { 172 + "type": "text(200)", 173 + "notNull": false, 174 + "autoincrement": false, 175 + "default": null, 176 + "generated": null, 177 + "name": "queueName", 178 + "entityType": "columns", 179 + "table": "play_queue_state" 180 + }, 181 + { 182 + "type": "text(30)", 183 + "notNull": false, 184 + "autoincrement": false, 185 + "default": null, 186 + "generated": null, 187 + "name": "queueStatus", 188 + "entityType": "columns", 189 + "table": "play_queue_state" 190 + }, 191 + { 192 + "type": "text", 193 + "notNull": false, 194 + "autoincrement": false, 195 + "default": null, 196 + "generated": null, 197 + "name": "error", 198 + "entityType": "columns", 199 + "table": "play_queue_state" 200 + }, 201 + { 202 + "type": "integer", 203 + "notNull": false, 204 + "autoincrement": false, 205 + "default": null, 206 + "generated": null, 207 + "name": "createdAt", 208 + "entityType": "columns", 209 + "table": "play_queue_state" 210 + }, 211 + { 212 + "columns": [ 213 + "playId" 214 + ], 215 + "tableTo": "plays", 216 + "columnsTo": [ 217 + "id" 218 + ], 219 + "onUpdate": "CASCADE", 220 + "onDelete": "CASCADE", 221 + "nameExplicit": false, 222 + "name": "fk_play_inputs_playId_plays_id_fk", 223 + "entityType": "fks", 224 + "table": "play_inputs" 225 + }, 226 + { 227 + "columns": [ 228 + "playId" 229 + ], 230 + "tableTo": "plays", 231 + "columnsTo": [ 232 + "id" 233 + ], 234 + "onUpdate": "CASCADE", 235 + "onDelete": "CASCADE", 236 + "nameExplicit": false, 237 + "name": "fk_play_queue_state_playId_plays_id_fk", 238 + "entityType": "fks", 239 + "table": "play_queue_state" 240 + }, 241 + { 242 + "columns": [ 243 + "id" 244 + ], 245 + "nameExplicit": false, 246 + "name": "play_inputs_pk", 247 + "table": "play_inputs", 248 + "entityType": "pks" 249 + }, 250 + { 251 + "columns": [ 252 + "id" 253 + ], 254 + "nameExplicit": false, 255 + "name": "plays_pk", 256 + "table": "plays", 257 + "entityType": "pks" 258 + }, 259 + { 260 + "columns": [ 261 + "id" 262 + ], 263 + "nameExplicit": false, 264 + "name": "play_queue_state_pk", 265 + "table": "play_queue_state", 266 + "entityType": "pks" 267 + }, 268 + { 269 + "columns": [ 270 + { 271 + "value": "playId", 272 + "isExpression": false 273 + } 274 + ], 275 + "isUnique": false, 276 + "where": null, 277 + "origin": "manual", 278 + "name": "play_input_id_idx", 279 + "entityType": "indexes", 280 + "table": "play_inputs" 281 + }, 282 + { 283 + "columns": [ 284 + { 285 + "value": "parentId", 286 + "isExpression": false 287 + } 288 + ], 289 + "isUnique": false, 290 + "where": null, 291 + "origin": "manual", 292 + "name": "play_parent_id_idx", 293 + "entityType": "indexes", 294 + "table": "plays" 295 + }, 296 + { 297 + "columns": [ 298 + { 299 + "value": "playedAt", 300 + "isExpression": false 301 + } 302 + ], 303 + "isUnique": false, 304 + "where": null, 305 + "origin": "manual", 306 + "name": "play_playedAt_idx", 307 + "entityType": "indexes", 308 + "table": "plays" 309 + }, 310 + { 311 + "columns": [ 312 + { 313 + "value": "seenAt", 314 + "isExpression": false 315 + } 316 + ], 317 + "isUnique": false, 318 + "where": null, 319 + "origin": "manual", 320 + "name": "play_seenAt_idx", 321 + "entityType": "indexes", 322 + "table": "plays" 323 + }, 324 + { 325 + "columns": [ 326 + { 327 + "value": "playId", 328 + "isExpression": false 329 + } 330 + ], 331 + "isUnique": false, 332 + "where": null, 333 + "origin": "manual", 334 + "name": "play_queue_state_id_idx", 335 + "entityType": "indexes", 336 + "table": "play_queue_state" 337 + } 338 + ], 339 + "renames": [] 340 + }
+117
src/backend/common/database/drizzle/schema/drizzlePlaysTable.ts
··· 1 + import { integer, sqliteTable, text, index, customType } from "drizzle-orm/sqlite-core"; 2 + import { defineRelations } from 'drizzle-orm'; 3 + import dayjs, { Dayjs } from "dayjs"; 4 + 5 + const DayjsTimestamp = customType< 6 + { 7 + data: Dayjs; 8 + driverData: number; 9 + } 10 + >({ 11 + dataType() { 12 + return 'number' 13 + }, 14 + toDriver(value: Dayjs): number { 15 + return value.valueOf(); 16 + }, 17 + fromDriver(value: number): Dayjs { 18 + return dayjs(value); 19 + }, 20 + }); 21 + 22 + export const plays = sqliteTable("plays", { 23 + id: text({ length: 30 }).primaryKey(), 24 + componentType: text({ length: 50 }).notNull(), 25 + componentName: text({ length: 200 }).notNull(), 26 + error: text({ mode: 'json' }), 27 + playedAt: DayjsTimestamp('playedAt'), // integer({ mode: 'timestamp_ms' }), 28 + seenAt: DayjsTimestamp('seenAt'), // integer({ mode: 'timestamp_ms' }), 29 + play: text({ mode: 'json' }).notNull(), 30 + // TODO can this have a reference with cascade? 31 + parentId: text({ length: 30 }) 32 + }, (table) => [ 33 + index("play_parent_id_idx").on(table.parentId), 34 + index("play_playedAt_idx").on(table.playedAt), 35 + index("play_seenAt_idx").on(table.seenAt), 36 + ]); 37 + 38 + export type NewPlay = typeof plays.$inferInsert; 39 + export type SelectPlay = typeof plays.$inferSelect; 40 + 41 + export const playInputs = sqliteTable("play_inputs", { 42 + id: integer({ mode: 'number' }).primaryKey(), 43 + playId: text({ length: 30 }).references(() => plays.id, {onDelete: 'cascade', onUpdate: 'cascade'}), 44 + data: text({ mode: 'json' }), 45 + play: text({ mode: 'json' }), 46 + createdAt: DayjsTimestamp('createdAt').$defaultFn(() => dayjs()) // integer({ mode: 'timestamp_ms' }) 47 + }, (table) => [ 48 + index('play_input_id_idx').on(table.playId) 49 + ]); 50 + 51 + // export const playParentRelations = defineRelations({plays}, (r) => ({ 52 + // plays: { 53 + // parent: r.one.plays({ 54 + // from: r.plays.parentId, 55 + // to: r.plays.id 56 + // }), 57 + // children: r.many.plays() 58 + // } 59 + // })) 60 + 61 + 62 + // export const playInputRelations = defineRelations({ plays, playInputs }, (r) => ({ 63 + // plays: { 64 + // input: r.one.playInputs({ 65 + // from: r.plays.id, 66 + // to: r.playInputs.playId, 67 + // optional: false, 68 + // }) 69 + // } 70 + // })); 71 + 72 + export const queueStates = sqliteTable("play_queue_state", { 73 + id: integer({ mode: 'number' }).primaryKey(), 74 + playId: text().references(() => plays.id, {onDelete: 'cascade', onUpdate: 'cascade'}), 75 + queueName: text({length: 200}), 76 + queueStatus: text({length: 30}), 77 + error: text({ mode: 'json' }), 78 + createdAt: DayjsTimestamp('createdAt').$defaultFn(() => dayjs()) // integer({ mode: 'timestamp_ms' }) 79 + }, (table) => [ 80 + index('play_queue_state_id_idx').on(table.playId) 81 + ]); 82 + 83 + // export const playQueueRelations = defineRelations({ plays, queueStates }, (r) => ({ 84 + // plays: { 85 + // queueStates: r.many.queueStates() 86 + // }, 87 + // queueStates: { 88 + // play: r.one.plays({ 89 + // from: r.queueStates.playId, 90 + // to: r.plays.id 91 + // }) 92 + // } 93 + // })); 94 + 95 + export const playRelations = defineRelations({ plays, queueStates, playInputs }, (r) => ({ 96 + plays: { 97 + queueStates: r.many.queueStates(), 98 + input: r.one.playInputs({ 99 + from: r.plays.id, 100 + to: r.playInputs.playId, 101 + optional: false, 102 + }), 103 + parent: r.one.plays({ 104 + from: r.plays.parentId, 105 + to: r.plays.id 106 + }), 107 + children: r.many.plays() 108 + }, 109 + queueStates: { 110 + play: r.one.plays({ 111 + from: r.queueStates.playId, 112 + to: r.plays.id 113 + }) 114 + } 115 + })); 116 + 117 + export const relations = playRelations;
+8 -6
src/backend/common/errors/MSErrors.ts
··· 43 43 stackShortened: boolean = false; 44 44 45 45 shortenStack() { 46 - const atIndex = parseRegexSingle(STACK_AT_REGEX,this.stack); 47 - if(atIndex !== undefined) { 48 - const firstn = this.stack.indexOf('\n', atIndex.index + atIndex.match.length); 49 - if(firstn !== -1) { 50 - this.stack = this.stack.slice(0, firstn); 51 - this.stackShortened = true; 46 + if(this.stack !== undefined) { 47 + const atIndex = parseRegexSingle(STACK_AT_REGEX, this.stack); 48 + if(atIndex !== undefined) { 49 + const firstn = this.stack.indexOf('\n', atIndex.index + atIndex.match.length); 50 + if(firstn !== -1) { 51 + this.stack = this.stack.slice(0, firstn); 52 + this.stackShortened = true; 53 + } 52 54 } 53 55 } 54 56 }
+8
src/backend/scrobblers/AbstractScrobbleClient.ts
··· 68 68 import {serializeError} from 'serialize-error'; 69 69 import { DEFAULT_NEW_PADDING, groupPlaysToTimeRanges } from "../utils/ListenFetchUtils.js"; 70 70 import { spawn, catchAbortError, isAbortError, rethrowAbortError, delay, forever, AbortError, throwIfAborted } from 'abort-controller-x'; 71 + import { Queue, MemoryStorage } from '@platformatic/job-queue' 71 72 72 73 type PlatformMappedPlays = Map<string, {player: SourcePlayerObj, source: SourceIdentifier}>; 73 74 type NowPlayingQueue = Map<string, PlatformMappedPlays>; 74 75 75 76 const platformTruncate = truncateStringToLength(10); 77 + 76 78 77 79 export default abstract class AbstractScrobbleClient extends AbstractComponent implements Authenticatable { 78 80 ··· 116 118 dupeLogger: Logger; 117 119 deadLogger: Logger; 118 120 121 + scrobbleQueue: Queue<{payload: QueuedScrobble<PlayObject>}, {scrobbled: ScrobbledPlayObject}>; 122 + 119 123 existingScrobble: (playObjPre: PlayObject, existingScrobbles: PlayObject[], log?: boolean) => Promise<PlayMatchResult> 120 124 121 125 declare config: CommonClientConfig; ··· 145 149 this.notifier = notifier; 146 150 this.emitter = emitter; 147 151 this.scrobbledPlayObjs = new FixedSizeList<ScrobbledPlayObject>(this.MAX_STORED_SCROBBLES); 152 + this.scrobbleQueue = new Queue<{payload: QueuedScrobble<PlayObject>}, {scrobbled: ScrobbledPlayObject}>({ 153 + storage: new MemoryStorage(), 154 + concurrency: 1 155 + }); 148 156 149 157 const { 150 158 options: {
+94
src/backend/tests/database/drizzle.test.ts
··· 1 + import chai, { assert, expect } from 'chai'; 2 + import asPromised from 'chai-as-promised'; 3 + import { getDb, migrateDb, shouldBackupDb } from '../../common/database/drizzle/drizzleUtils.js'; 4 + import withLocalTmpDir from 'with-local-tmp-dir'; 5 + import { playInputs, plays, queueStates } from '../../common/database/drizzle/schema/drizzlePlaysTable.js'; 6 + import { nanoid } from 'nanoid'; 7 + import dayjs from 'dayjs'; 8 + import { generatePlay } from '../../../core/PlayTestUtils.js'; 9 + import { getDbPath } from '../../common/database/Database.js'; 10 + 11 + // it('Detects pending migrations', async function () { 12 + 13 + // const res = await shouldBackupDb(getDbPath(undefined, process.cwd())); 14 + 15 + // expect(res).to.be.undefined; 16 + 17 + // }); 18 + 19 + describe('Basic DB Operations', function () { 20 + 21 + it('Should create a play', async function () { 22 + 23 + withLocalTmpDir(async () => { 24 + 25 + const db = getDb(':memory:', { workingDirectory: process.cwd() }); 26 + await migrateDb(db); 27 + 28 + const playRow = await db.insert(plays).values({ 29 + id: nanoid(), 30 + componentName: 'mySpot', 31 + componentType: 'spotify', 32 + playedAt: dayjs(), 33 + seenAt: dayjs(), 34 + play: generatePlay() 35 + }); 36 + 37 + expect(playRow.changes).eq(1); 38 + 39 + }, { unsafeCleanup: true }); 40 + }); 41 + 42 + it('Should create a play with relations', async function () { 43 + 44 + withLocalTmpDir(async () => { 45 + 46 + const db = getDb(':memory:', { workingDirectory: process.cwd() }); 47 + await migrateDb(db); 48 + 49 + const id = nanoid(); 50 + const playRow = await db.insert(plays).values({ 51 + id, 52 + componentName: 'mySpot', 53 + componentType: 'spotify', 54 + playedAt: dayjs(), 55 + seenAt: dayjs(), 56 + play: generatePlay() 57 + }).returning(); 58 + 59 + const input = await db.insert(playInputs).values({ 60 + playId: playRow[0].id, 61 + play: playRow[0].play, 62 + data: { anything: 'foo' } 63 + }).returning(); 64 + 65 + const twoQueues = await db.insert(queueStates).values([ 66 + { 67 + playId: id, 68 + queueName: 'foo', 69 + queueStatus: 'queued' 70 + }, 71 + { 72 + playId: id, 73 + queueName: 'bar', 74 + queueStatus: 'completed' 75 + } 76 + ]); 77 + 78 + const fullPlay = await db.query.plays.findFirst({ 79 + with: { 80 + input: true, 81 + queueStates: true, 82 + }, 83 + }); 84 + 85 + expect(fullPlay.queueStates).to.not.be.undefined; 86 + expect(fullPlay.queueStates).length(2); 87 + 88 + expect(fullPlay.input).to.not.be.undefined; 89 + 90 + }, { unsafeCleanup: true }); 91 + }); 92 + 93 + }); 94 +
+1 -1
src/backend/tests/scrobbler/scrobblers.test.ts
··· 20 20 import { defaultLifecycle } from '../../utils/PlayTransformUtils.js'; 21 21 import { shuffleArray } from '../../utils/DataUtils.js'; 22 22 import { DEFAULT_CONSOLIDATE_DURATION, DEFAULT_GROUP_DURATION, groupPlaysToTimeRanges } from '../../utils/ListenFetchUtils.js'; 23 - import { asPlay } from '../../../core/tests/utils/fixtures.js'; 23 + import { asPlay } from '../../../core/PlayMarshalUtils.js'; 24 24 import { nanoid } from 'nanoid'; 25 25 import { getRoot } from '../../ioc.js'; 26 26 import { transientCache } from '../utils/CacheTestUtils.js';
+18
src/backend/utils/FSUtils.ts
··· 33 33 // }); 34 34 // }); 35 35 } 36 + 36 37 export const fileOrDirectoryIsWriteable = (location: string) => { 37 38 const pathInfo = pathUtil.parse(location); 38 39 const isDir = pathInfo.ext === ''; ··· 63 64 } 64 65 }; 65 66 67 + export const fileExists = (location: string) => { 68 + const pathInfo = pathUtil.parse(location); 69 + const isDir = pathInfo.ext === ''; 70 + try { 71 + accessSync(location, constants.R_OK); 72 + return true; 73 + } catch (err: any) { 74 + const { code } = err; 75 + if (code === 'ENOENT') { 76 + return false; 77 + } else if (code === 'EACCES') { 78 + throw new Error(`${isDir ? 'Directory' : 'File'} exists at ${location} but application does not have permission to write to it.`); 79 + } else { 80 + throw new Error(`${isDir ? 'Directory' : 'File'} exists at ${location} but application is unable to access it due to a system error`, { cause: err }); 81 + } 82 + } 83 + };
+82
src/core/PlayMarshalUtils.ts
··· 1 + import clone from 'clone'; 2 + import dayjs from 'dayjs'; 3 + import { Traverse, TraverseContext } from 'neotraverse/modern'; 4 + import { ListenRange } from '../backend/sources/PlayerState/ListenRange.js'; 5 + import { AmbPlayObject, JsonPlayObject, PlayObject, PlayProgressAmb, REGEX_ISO8601_LOOSE } from './Atomic.js'; 6 + import { ListenProgressPositional, ListenProgressTS } from '../backend/sources/PlayerState/ListenProgress.js'; 7 + 8 + interface BlockPath { key: string, parent: string }; 9 + type BlockPaths = BlockPath[]; 10 + 11 + /** We know some nodes will never have data that needs to be transformed 12 + * and these nodes can have lots of data so we can optimize them away by not (recursively) traversing them 13 + */ 14 + const blockedKeys: PropertyKey[] = ['patch', 'inputs', 'payload', 'response', 'error']; 15 + /** We know some paths/nodes will never have data that needs to be transformed 16 + * and these nodes can have lots of data so we can optimize them away by not (recursively) traversing them 17 + */ 18 + const blockedPaths: BlockPaths = [ 19 + { 20 + parent: 'data', 21 + key: 'meta' 22 + }, 23 + { 24 + parent: 'lifecycle', 25 + key: 'input' 26 + } 27 + ]; 28 + 29 + export const shouldBlock = (ctx: TraverseContext): boolean => { 30 + if (blockedKeys.includes(ctx.key)) { 31 + return true; 32 + } 33 + return blockedPaths.some((x) => { 34 + let blocked = x.key === ctx.key; 35 + if (blocked && x.parent !== undefined) { 36 + blocked = ctx.parent !== undefined && ctx.parent.key === x.parent; 37 + } 38 + return blocked; 39 + }); 40 + }; 41 + 42 + export const asJsonPlayObject = (play: AmbPlayObject): JsonPlayObject => { 43 + const cloned = clone(play); 44 + new Traverse(cloned).forEach((ctx, x) => { 45 + if (shouldBlock(ctx)) { 46 + ctx.block(); 47 + return; 48 + } 49 + 50 + if (dayjs.isDayjs(x)) { 51 + ctx.update(x.toISOString()); 52 + } else if (x instanceof ListenRange) { 53 + ctx.update(x.toJSON(), true); 54 + } 55 + }); 56 + return cloned as unknown as JsonPlayObject; 57 + }; 58 + 59 + export const asPlay = (data: JsonPlayObject): PlayObject => { 60 + const cloned = clone(data); 61 + new Traverse(cloned).forEach((ctx, x) => { 62 + if (shouldBlock(ctx)) { 63 + ctx.block(); 64 + return; 65 + } 66 + 67 + if (typeof x === 'string' && REGEX_ISO8601_LOOSE.test(x)) { 68 + ctx.update(dayjs(x), true); 69 + } else if (ctx.key === 'listenRanges') { 70 + const ranges = x[0].map((y: PlayProgressAmb<string>) => { 71 + if (y.positionPercent === undefined) { 72 + return new ListenProgressPositional({ timestamp: dayjs(y.timestamp), position: y.position }); 73 + } else { 74 + return new ListenProgressTS({ timestamp: dayjs(y.timestamp), positionPercent: y.positionPercent }); 75 + } 76 + }); 77 + ctx.update(ranges, true); 78 + } 79 + }); 80 + return cloned as unknown as PlayObject; 81 + }; 82 +
+1 -79
src/core/tests/utils/fixtures.ts
··· 1 1 import { Traverse, TraverseContext } from 'neotraverse/modern'; 2 2 import { faker } from '@faker-js/faker'; 3 - import dayjs from 'dayjs'; 4 - import { AmbPlayObject, JsonPlayObject, LifecycleInput, LifecycleStep, ObjectPlayData, PlayMeta, PlayObject, PlayProgressAmb, REGEX_ISO8601_LOOSE, ScrobbleResult } from '../../Atomic.js'; 5 - import { ListenRange } from '../../../backend/sources/PlayerState/ListenRange.js'; 6 - import { ListenProgressPositional, ListenProgressTS } from '../../../backend/sources/PlayerState/ListenProgress.js'; 3 + import { LifecycleInput, LifecycleStep, ObjectPlayData, PlayMeta, PlayObject, ScrobbleResult } from '../../Atomic.js'; 7 4 import { MarkOptional } from 'ts-essentials'; 8 5 import { generateBrainz, generateMbid, generatePlay, GeneratePlayOpts, generatePlays } from '../../PlayTestUtils.js'; 9 6 import { lifecyclelessInvariantTransform } from '../../PlayUtils.js'; ··· 13 10 import { UpstreamError } from '../../../backend/common/errors/UpstreamError.js'; 14 11 import { playToListenPayload } from '../../../backend/common/vendor/listenbrainz/lzUtils.js'; 15 12 import { mergeSimpleError, SimpleError, SkipTransformStageError, StagePrerequisiteError } from '../../../backend/common/errors/MSErrors.js'; 16 - 17 - interface BlockPath { key: string, parent: string }; 18 - type BlockPaths = BlockPath[]; 19 - 20 - /** We know some nodes will never have data that needs to be transformed 21 - * and these nodes can have lots of data so we can optimize them away by not (recursively) traversing them 22 - */ 23 - const blockedKeys: PropertyKey[] = ['patch', 'inputs', 'payload', 'response', 'error']; 24 - /** We know some paths/nodes will never have data that needs to be transformed 25 - * and these nodes can have lots of data so we can optimize them away by not (recursively) traversing them 26 - */ 27 - const blockedPaths: BlockPaths = [ 28 - { 29 - parent: 'data', 30 - key: 'meta' 31 - }, 32 - { 33 - parent: 'lifecycle', 34 - key: 'input' 35 - } 36 - ] 37 - 38 - const shouldBlock = (ctx: TraverseContext): boolean => { 39 - if (blockedKeys.includes(ctx.key)) { 40 - return true; 41 - } 42 - return blockedPaths.some((x) => { 43 - let blocked = x.key === ctx.key; 44 - if (blocked && x.parent !== undefined) { 45 - blocked = ctx.parent !== undefined && ctx.parent.key === x.parent; 46 - } 47 - return blocked; 48 - }) 49 - } 50 - 51 - export const asJsonPlayObject = (play: AmbPlayObject): JsonPlayObject => { 52 - const cloned = clone(play); 53 - new Traverse(cloned).forEach((ctx, x) => { 54 - if (shouldBlock(ctx)) { 55 - ctx.block(); 56 - return; 57 - } 58 - 59 - if (dayjs.isDayjs(x)) { 60 - ctx.update(x.toISOString()); 61 - } else if (x instanceof ListenRange) { 62 - ctx.update(x.toJSON(), true); 63 - } 64 - }); 65 - return cloned as unknown as JsonPlayObject; 66 - } 67 - 68 - export const asPlay = (data: JsonPlayObject): PlayObject => { 69 - const cloned = clone(data); 70 - new Traverse(cloned).forEach((ctx, x) => { 71 - if (shouldBlock(ctx)) { 72 - ctx.block(); 73 - return; 74 - } 75 - 76 - if (typeof x === 'string' && REGEX_ISO8601_LOOSE.test(x)) { 77 - ctx.update(dayjs(x), true); 78 - } else if (ctx.key === 'listenRanges') { 79 - const ranges = x[0].map((y: PlayProgressAmb<string>) => { 80 - if (y.positionPercent === undefined) { 81 - return new ListenProgressPositional({ timestamp: dayjs(y.timestamp), position: y.position }); 82 - } else { 83 - return new ListenProgressTS({ timestamp: dayjs(y.timestamp), positionPercent: y.positionPercent }); 84 - } 85 - }) 86 - ctx.update(ranges, true); 87 - } 88 - }); 89 - return cloned as unknown as PlayObject; 90 - } 91 13 92 14 export interface ScrobbleMatchOptions { 93 15 match?: boolean
+2 -1
src/stories/ActivityTimeline.stories.tsx
··· 8 8 import { generateJsonPlays } from "../core/PlayTestUtils.js"; 9 9 import { ErrorLike, JsonPlayObject, PlayLifecycle } from "../core/Atomic.js"; 10 10 import { examplePlay, lastfmErrorExample } from "./storyUtils.js"; 11 - import { asJsonPlayObject, generatePlayWithLifecycle, playWithLifecycleScrobble } from "../core/tests/utils/fixtures.js"; 11 + import { generatePlayWithLifecycle, playWithLifecycleScrobble } from "../core/tests/utils/fixtures.js"; 12 + import { asJsonPlayObject } from '../core/PlayMarshalUtils.js'; 12 13 13 14 // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export 14 15 const meta = preview.meta({
+2 -1
src/stories/List.stories.tsx
··· 8 8 import { generateJsonPlays, normalizePlays } from "../core/PlayTestUtils.js"; 9 9 import { ErrorLike, JsonPlayObject } from "../core/Atomic.js"; 10 10 import {examplePlay, lastfmErrorExample} from './storyUtils.js'; 11 - import {playWithLifecycleScrobble, generatePlayWithLifecycle, asJsonPlayObject} from '../core/tests/utils/fixtures' 11 + import {playWithLifecycleScrobble, generatePlayWithLifecycle} from '../core/tests/utils/fixtures' 12 12 import { generateArray } from "../core/DataUtils.js"; 13 13 import dayjs from "dayjs"; 14 + import { asJsonPlayObject } from "../core/PlayMarshalUtils.js"; 14 15 15 16 const stack = "Scrobble Submit Error: Failed to submit to Listenbrainz (listen_type single)\n at ListenbrainzApiClient.submitListen (/app/src/backend/common/vendor/ListenbrainzApiClient.ts:246:19)\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async ListenbrainzScrobbler.doScrobble (/app/src/backend/scrobblers/ListenbrainzScrobbler.ts:87:28)\n at async ListenbrainzScrobbler.scrobble (/app/src/backend/scrobblers/AbstractScrobbleClient.ts:679:28)\n at async ListenbrainzScrobbler.processDeadLetterScrobble (/app/src/backend/scrobblers/AbstractScrobbleClient.ts:920:39)\n at async ListenbrainzScrobbler.processDeadLetterQueue (/app/src/backend/scrobblers/AbstractScrobbleClient.ts:894:43)\n at async PromisePoolExecutor.handler (/app/src/backend/tasks/heartbeatClients.ts:35:21)\n at async PromisePoolExecutor.waitForActiveTaskToFinish (/app/node_modules/@supercharge/promise-pool/dist/promise-pool-executor.js:375:9)\n at async PromisePoolExecutor.waitForProcessingSlot (/app/node_modules/@supercharge/promise-pool/dist/promise-pool-executor.js:368:13)\n at async PromisePoolExecutor.process (/app/node_modules/@supercharge/promise-pool/dist/promise-pool-executor.js:354:13)"; 16 17
+1 -1
src/stories/PlayInfo.stories.tsx
··· 7 7 import { Container } from '@chakra-ui/react'; 8 8 import { generateArtists, generateJsonPlay, generatePlay, withBrainz } from "../core/PlayTestUtils.js" 9 9 import clone from "clone"; 10 - import { asJsonPlayObject } from "../core/tests/utils/fixtures.js"; 10 + import { asJsonPlayObject } from '../core/PlayMarshalUtils.js'; 11 11 12 12 type PropsAndCustomArgs = React.ComponentProps<typeof PlayData> & { 13 13 includeAlbumArtists?: boolean;
+2 -1
src/stories/TransformSteps.stories.tsx
··· 8 8 import { generateJsonPlays } from "../core/PlayTestUtils.js"; 9 9 import { ErrorLike, JsonPlayObject, PlayLifecycle } from "../core/Atomic.js"; 10 10 import { examplePlay, lastfmErrorExample } from "./storyUtils.js"; 11 - import {asJsonPlayObject, generatePlayWithLifecycle} from '../core/tests/utils/fixtures' 11 + import {generatePlayWithLifecycle} from '../core/tests/utils/fixtures' 12 + import { asJsonPlayObject } from "../core/PlayMarshalUtils.js"; 12 13 13 14 // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export 14 15 const meta = preview.meta({
+2 -1
tsconfig.json
··· 19 19 "sourceMap": false, 20 20 }, 21 21 "include": [ 22 - "src" 22 + "src", 23 + "drizzle.config.ts" 23 24 ], 24 25 }