A decentralized music tracking and discovery platform built on AT Protocol 🎵

Revert "Merge pull request #8 from tsirysndr/chore/migrate-to-bun"

This reverts commit 4ec6bb5f70b047edafe1f60e3dfb5995829dbe76, reversing
changes made to 1d902f131e5fe056b9e506e36ce86f44631e085c.

+125 -108
+15 -15
apps/api/package.json
··· 6 6 "module": "dist/index.js", 7 7 "scripts": { 8 8 "lexgen": "lex gen-server ./src/lexicon ./src/tealfm/lexicons/teal/**/* ./lexicons/**/* ./lexicons/* ./src/tealfm/lexicons/**/*", 9 - "dev": "concurrently 'bun --watch ./src/index.ts' 'bun --watch ./src/server.ts'", 10 - "prod": "bun ./src/index.ts", 9 + "dev": "concurrently 'tsx --watch ./src/index.ts' 'tsx --watch ./src/server.ts'", 10 + "prod": "tsx ./src/index.ts", 11 11 "build": "pkgroll", 12 - "sync": "bun ./src/scripts/sync.ts", 13 - "meili:sync": "bun ./src/scripts/meili.ts", 14 - "sync:library": "bun ./src/scripts/sync-library.ts", 15 - "avatar": "bun ./src/scripts/avatar.ts", 16 - "genres": "bun ./src/scripts/genres.ts", 12 + "sync": "tsx ./src/scripts/sync.ts", 13 + "meili:sync": "tsx ./src/scripts/meili.ts", 14 + "sync:library": "tsx ./src/scripts/sync-library.ts", 15 + "avatar": "tsx ./src/scripts/avatar.ts", 16 + "genres": "tsx ./src/scripts/genres.ts", 17 17 "pkl:eval": "pkl eval -f json", 18 - "pkl:gen": "bun ./scripts/pkl.ts", 19 - "dev:xrpc": "bun --watch ./src/server.ts", 20 - "prod:xrpc": "bun ./src/server.ts", 18 + "pkl:gen": "tsx ./scripts/pkl.ts", 19 + "dev:xrpc": "tsx --watch ./src/server.ts", 20 + "prod:xrpc": "tsx ./src/server.ts", 21 21 "db:migrate": "drizzle-kit migrate", 22 22 "db:gen-migration": "drizzle-kit generate", 23 - "prod:all": "concurrently 'bun ./src/index.ts' 'bun ./src/server.ts'", 23 + "prod:all": "concurrently 'tsx ./src/index.ts' 'tsx ./src/server.ts'", 24 24 "format": "biome format src", 25 25 "lint": "biome lint src" 26 26 }, ··· 28 28 "@atproto/api": "^0.13.31", 29 29 "@atproto/common": "^0.4.6", 30 30 "@atproto/identity": "^0.4.5", 31 - "@atproto/jwk-jose": "0.1.8", 32 31 "@atproto/lex-cli": "^0.5.6", 33 32 "@atproto/lexicon": "^0.4.5", 34 33 "@atproto/oauth-client-node": "^0.2.14", ··· 45 44 "@opentelemetry/sdk-metrics": "^2.0.0", 46 45 "@opentelemetry/sdk-node": "^0.200.0", 47 46 "@opentelemetry/semantic-conventions": "^1.32.0", 47 + "@pyroscope/nodejs": "^0.4.5", 48 48 "assert": "^2.1.0", 49 49 "axios": "^1.7.9", 50 + "better-sqlite3": "^11.8.1", 50 51 "chalk": "^5.4.1", 51 52 "chanfana": "^2.0.2", 52 53 "cors": "^2.8.5", ··· 62 63 "http-proxy-middleware": "^3.0.5", 63 64 "iron-session": "^8.0.4", 64 65 "jsonwebtoken": "^9.0.2", 65 - "kysely": "^0.28.8", 66 - "kysely-bun-sqlite": "^0.4.0", 66 + "kysely": "^0.27.5", 67 67 "lodash": "^4.17.21", 68 68 "nats": "^2.29.2", 69 69 "pg": "^8.13.3", ··· 78 78 "devDependencies": { 79 79 "@biomejs/biome": "^2.2.3", 80 80 "@pkl-community/pkl": "^0.28.2", 81 - "@types/bun": "^1.3.0", 82 81 "@types/express": "^5.0.2", 83 82 "@types/lodash": "^4.17.17", 84 83 "@types/node": "^22.13.0", ··· 87 86 "concurrently": "^9.2.0", 88 87 "drizzle-kit": "^0.31.1", 89 88 "pkgroll": "^2.6.1", 89 + "tsx": "^4.19.2", 90 90 "zx": "^8.5.4" 91 91 }, 92 92 "exports": {
+3 -3
apps/api/src/db.ts
··· 1 - import { Database as SqliteDb } from "bun:sqlite"; 1 + import SqliteDb from "better-sqlite3"; 2 2 import { 3 3 Kysely, 4 4 type Migration, 5 5 type MigrationProvider, 6 6 Migrator, 7 + SqliteDialect, 7 8 } from "kysely"; 8 - import { BunSqliteDialect } from "kysely-bun-sqlite"; 9 9 10 10 // Types 11 11 ··· 79 79 80 80 export const createDb = (location: string): Database => { 81 81 return new Kysely<DatabaseSchema>({ 82 - dialect: new BunSqliteDialect({ 82 + dialect: new SqliteDialect({ 83 83 database: new SqliteDb(location), 84 84 }), 85 85 });
+1
apps/api/src/index.ts
··· 24 24 import googledrive from "./googledrive/app"; 25 25 import { env } from "./lib/env"; 26 26 import { requestCounter, requestDuration } from "./metrics"; 27 + import "./profiling"; 27 28 import albumTracks from "./schema/album-tracks"; 28 29 import albums from "./schema/albums"; 29 30 import artistTracks from "./schema/artist-tracks";
+1 -1
apps/api/src/lexicon/types/app/rocksky/apikey/createApikey.ts
··· 9 9 import type { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 10 import type * as AppRockskyApikeyDefs from "./defs"; 11 11 12 - export type QueryParams = {}; 12 + export type QueryParams = {} 13 13 14 14 export interface InputSchema { 15 15 /** The name of the API key. */
+1 -1
apps/api/src/lexicon/types/app/rocksky/apikey/updateApikey.ts
··· 9 9 import type { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 10 import type * as AppRockskyApikeyDefs from "./defs"; 11 11 12 - export type QueryParams = {}; 12 + export type QueryParams = {} 13 13 14 14 export interface InputSchema { 15 15 /** The ID of the API key to update. */
+1 -1
apps/api/src/lexicon/types/app/rocksky/like/dislikeShout.ts
··· 9 9 import type { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 10 import type * as AppRockskyShoutDefs from "../shout/defs"; 11 11 12 - export type QueryParams = {}; 12 + export type QueryParams = {} 13 13 14 14 export interface InputSchema { 15 15 /** The unique identifier of the shout to dislike */
+1 -1
apps/api/src/lexicon/types/app/rocksky/like/dislikeSong.ts
··· 9 9 import type { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 10 import type * as AppRockskySongDefs from "../song/defs"; 11 11 12 - export type QueryParams = {}; 12 + export type QueryParams = {} 13 13 14 14 export interface InputSchema { 15 15 /** The unique identifier of the song to dislike */
+1 -1
apps/api/src/lexicon/types/app/rocksky/like/likeShout.ts
··· 9 9 import type { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 10 import type * as AppRockskyShoutDefs from "../shout/defs"; 11 11 12 - export type QueryParams = {}; 12 + export type QueryParams = {} 13 13 14 14 export interface InputSchema { 15 15 /** The unique identifier of the shout to like */
+1 -1
apps/api/src/lexicon/types/app/rocksky/like/likeSong.ts
··· 9 9 import type { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 10 import type * as AppRockskySongDefs from "../song/defs"; 11 11 12 - export type QueryParams = {}; 12 + export type QueryParams = {} 13 13 14 14 export interface InputSchema { 15 15 /** The unique identifier of the song to like */
+1 -1
apps/api/src/lexicon/types/app/rocksky/scrobble/createScrobble.ts
··· 9 9 import type { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 10 import type * as AppRockskyScrobbleDefs from "./defs"; 11 11 12 - export type QueryParams = {}; 12 + export type QueryParams = {} 13 13 14 14 export interface InputSchema { 15 15 /** The title of the track being scrobbled */
+1 -1
apps/api/src/lexicon/types/app/rocksky/shout/createShout.ts
··· 9 9 import type { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 10 import type * as AppRockskyShoutDefs from "./defs"; 11 11 12 - export type QueryParams = {}; 12 + export type QueryParams = {} 13 13 14 14 export interface InputSchema { 15 15 /** The content of the shout */
+1 -1
apps/api/src/lexicon/types/app/rocksky/shout/replyShout.ts
··· 9 9 import type { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 10 import type * as AppRockskyShoutDefs from "./defs"; 11 11 12 - export type QueryParams = {}; 12 + export type QueryParams = {} 13 13 14 14 export interface InputSchema { 15 15 /** The unique identifier of the shout to reply to */
+1 -1
apps/api/src/lexicon/types/app/rocksky/shout/reportShout.ts
··· 9 9 import type { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 10 import type * as AppRockskyShoutDefs from "./defs"; 11 11 12 - export type QueryParams = {}; 12 + export type QueryParams = {} 13 13 14 14 export interface InputSchema { 15 15 /** The unique identifier of the shout to report */
+1 -1
apps/api/src/lexicon/types/app/rocksky/song/createSong.ts
··· 9 9 import type { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 10 import type * as AppRockskySongDefs from "./defs"; 11 11 12 - export type QueryParams = {}; 12 + export type QueryParams = {} 13 13 14 14 export interface InputSchema { 15 15 /** The title of the song */
+1 -1
apps/api/src/lexicon/types/app/rocksky/spotify/next.ts
··· 8 8 import { CID } from "multiformats/cid"; 9 9 import { type HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 10 11 - export type QueryParams = {}; 11 + export type QueryParams = {} 12 12 13 13 export type InputSchema = undefined; 14 14 export type HandlerInput = undefined;
+1 -1
apps/api/src/lexicon/types/app/rocksky/spotify/pause.ts
··· 8 8 import { CID } from "multiformats/cid"; 9 9 import { type HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 10 11 - export type QueryParams = {}; 11 + export type QueryParams = {} 12 12 13 13 export type InputSchema = undefined; 14 14 export type HandlerInput = undefined;
+1 -1
apps/api/src/lexicon/types/app/rocksky/spotify/play.ts
··· 8 8 import { CID } from "multiformats/cid"; 9 9 import { type HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 10 11 - export type QueryParams = {}; 11 + export type QueryParams = {} 12 12 13 13 export type InputSchema = undefined; 14 14 export type HandlerInput = undefined;
+1 -1
apps/api/src/lexicon/types/app/rocksky/spotify/previous.ts
··· 8 8 import { CID } from "multiformats/cid"; 9 9 import { type HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server"; 10 10 11 - export type QueryParams = {}; 11 + export type QueryParams = {} 12 12 13 13 export type InputSchema = undefined; 14 14 export type HandlerInput = undefined;
+46 -46
apps/api/src/nowplaying/nowplaying.service.ts
··· 26 26 27 27 export async function putArtistRecord( 28 28 track: Track, 29 - agent: Agent, 29 + agent: Agent 30 30 ): Promise<string | null> { 31 31 const rkey = TID.nextStr(); 32 32 const record: Artist.Record = { ··· 62 62 63 63 export async function putAlbumRecord( 64 64 track: Track, 65 - agent: Agent, 65 + agent: Agent 66 66 ): Promise<string | null> { 67 67 const rkey = TID.nextStr(); 68 68 ··· 103 103 104 104 export async function putSongRecord( 105 105 track: Track, 106 - agent: Agent, 106 + agent: Agent 107 107 ): Promise<string | null> { 108 108 const rkey = TID.nextStr(); 109 109 ··· 158 158 159 159 async function putScrobbleRecord( 160 160 track: Track, 161 - agent: Agent, 161 + agent: Agent 162 162 ): Promise<string | null> { 163 163 const rkey = TID.nextStr(); 164 164 ··· 276 276 .where( 277 277 and( 278 278 eq(artistAlbums.albumId, scrobble.album.id), 279 - eq(artistAlbums.artistId, scrobble.artist.id), 280 - ), 279 + eq(artistAlbums.artistId, scrobble.artist.id) 280 + ) 281 281 ) 282 282 .limit(1) 283 283 .then((rows) => rows[0]), ··· 440 440 }, 441 441 }), 442 442 null, 443 - 2, 443 + 2 444 444 ); 445 445 446 446 ctx.nc.publish( 447 447 "rocksky.scrobble", 448 - Buffer.from(message.replaceAll("sha_256", "sha256")), 448 + Buffer.from(message.replaceAll("sha_256", "sha256")) 449 449 ); 450 450 451 451 const trackMessage = JSON.stringify( ··· 492 492 xata_createdat: artist_album.createdAt.toISOString(), 493 493 xata_updatedat: artist_album.updatedAt.toISOString(), 494 494 }, 495 - }), 495 + }) 496 496 ); 497 497 498 498 ctx.nc.publish( 499 499 "rocksky.track", 500 - Buffer.from(trackMessage.replaceAll("sha_256", "sha256")), 500 + Buffer.from(trackMessage.replaceAll("sha_256", "sha256")) 501 501 ); 502 502 } 503 503 ··· 505 505 ctx: Context, 506 506 track: Track, 507 507 agent: Agent, 508 - userDid: string, 508 + userDid: string 509 509 ): Promise<void> { 510 510 // check if scrobble already exists (user did + timestamp) 511 511 const scrobbleTime = dayjs.unix(track.timestamp || dayjs().unix()); ··· 524 524 eq(tracks.title, track.title), 525 525 eq(tracks.artist, track.artist), 526 526 gte(scrobbles.timestamp, scrobbleTime.subtract(60, "seconds").toDate()), 527 - lte(scrobbles.timestamp, scrobbleTime.add(60, "seconds").toDate()), 528 - ), 527 + lte(scrobbles.timestamp, scrobbleTime.add(60, "seconds").toDate()) 528 + ) 529 529 ) 530 530 .limit(1) 531 531 .then((rows) => rows[0]); ··· 533 533 if (existingScrobble) { 534 534 console.log( 535 535 `Scrobble already exists for ${chalk.cyan(track.title)} at ${chalk.cyan( 536 - scrobbleTime.format("YYYY-MM-DD HH:mm:ss"), 537 - )}`, 536 + scrobbleTime.format("YYYY-MM-DD HH:mm:ss") 537 + )}` 538 538 ); 539 539 return; 540 540 } ··· 547 547 tracks.sha256, 548 548 createHash("sha256") 549 549 .update( 550 - `${track.title} - ${track.artist} - ${track.album}`.toLowerCase(), 550 + `${track.title} - ${track.artist} - ${track.album}`.toLowerCase() 551 551 ) 552 - .digest("hex"), 553 - ), 552 + .digest("hex") 553 + ) 554 554 ) 555 555 .limit(1) 556 556 .then((rows) => rows[0]); ··· 564 564 albums.sha256, 565 565 createHash("sha256") 566 566 .update(`${track.album} - ${track.albumArtist}`.toLowerCase()) 567 - .digest("hex"), 568 - ), 567 + .digest("hex") 568 + ) 569 569 ) 570 570 .limit(1) 571 571 .then((rows) => rows[0]); ··· 586 586 artists.sha256, 587 587 createHash("sha256") 588 588 .update(track.albumArtist.toLowerCase()) 589 - .digest("hex"), 590 - ), 589 + .digest("hex") 590 + ) 591 591 ) 592 592 .limit(1) 593 593 .then((rows) => rows[0]); ··· 618 618 artist: track.artist.split(",").map((a) => ({ name: a.trim() })), 619 619 name: track.title, 620 620 album: track.album, 621 - }, 621 + } 622 622 ); 623 623 624 624 if (!mbTrack?.trackMBID) { ··· 647 647 albums.sha256, 648 648 createHash("sha256") 649 649 .update(`${track.album} - ${track.albumArtist}`.toLowerCase()) 650 - .digest("hex"), 651 - ), 650 + .digest("hex") 651 + ) 652 652 ) 653 653 .limit(1) 654 654 .then((rows) => rows[0]); ··· 664 664 tracks.sha256, 665 665 createHash("sha256") 666 666 .update( 667 - `${track.title} - ${track.artist} - ${track.album}`.toLowerCase(), 667 + `${track.title} - ${track.artist} - ${track.album}`.toLowerCase() 668 668 ) 669 - .digest("hex"), 670 - ), 669 + .digest("hex") 670 + ) 671 671 ) 672 672 .limit(1) 673 673 .then((rows) => rows[0]); ··· 681 681 682 682 if (existingTrack) { 683 683 console.log( 684 - `Song found: ${chalk.cyan(existingTrack.id)} - ${track.title}, after ${chalk.magenta(tries)} tries`, 684 + `Song found: ${chalk.cyan(existingTrack.id)} - ${track.title}, after ${chalk.magenta(tries)} tries` 685 685 ); 686 686 } 687 687 ··· 694 694 artists.sha256, 695 695 createHash("sha256") 696 696 .update(track.albumArtist.toLowerCase()) 697 - .digest("hex"), 697 + .digest("hex") 698 698 ), 699 699 eq( 700 700 artists.sha256, 701 - createHash("sha256").update(track.artist.toLowerCase()).digest("hex"), 702 - ), 703 - ), 701 + createHash("sha256").update(track.artist.toLowerCase()).digest("hex") 702 + ) 703 + ) 704 704 ) 705 705 .limit(1) 706 706 .then((rows) => rows[0]); ··· 715 715 .innerJoin(artists, eq(userArtists.artistId, artists.id)) 716 716 .innerJoin(users, eq(userArtists.userId, users.id)) 717 717 .where( 718 - and(eq(artists.id, existingArtist?.id || ""), eq(users.did, userDid)), 718 + and(eq(artists.id, existingArtist?.id || ""), eq(users.did, userDid)) 719 719 ) 720 720 .limit(1) 721 721 .then((rows) => rows[0]); ··· 750 750 tracks.sha256, 751 751 createHash("sha256") 752 752 .update( 753 - `${track.title} - ${track.artist} - ${track.album}`.toLowerCase(), 753 + `${track.title} - ${track.artist} - ${track.album}`.toLowerCase() 754 754 ) 755 - .digest("hex"), 756 - ), 755 + .digest("hex") 756 + ) 757 757 ) 758 758 .limit(1) 759 759 .then((rows) => rows[0]); 760 760 761 761 while (!existingTrack?.artistUri && !existingTrack?.albumUri && tries < 30) { 762 762 console.log( 763 - `Artist uri not ready, trying again: ${chalk.magenta(tries + 1)}`, 763 + `Artist uri not ready, trying again: ${chalk.magenta(tries + 1)}` 764 764 ); 765 765 existingTrack = await ctx.db 766 766 .select() ··· 770 770 tracks.sha256, 771 771 createHash("sha256") 772 772 .update( 773 - `${track.title} - ${track.artist} - ${track.album}`.toLowerCase(), 773 + `${track.title} - ${track.artist} - ${track.album}`.toLowerCase() 774 774 ) 775 - .digest("hex"), 776 - ), 775 + .digest("hex") 776 + ) 777 777 ) 778 778 .limit(1) 779 779 .then((rows) => rows[0]); ··· 788 788 artists.sha256, 789 789 createHash("sha256") 790 790 .update(track.albumArtist.toLowerCase()) 791 - .digest("hex"), 792 - ), 791 + .digest("hex") 792 + ) 793 793 ) 794 794 .limit(1) 795 795 .then((rows) => rows[0]); ··· 812 812 albums.sha256, 813 813 createHash("sha256") 814 814 .update(`${track.album} - ${track.albumArtist}`.toLowerCase()) 815 - .digest("hex"), 816 - ), 815 + .digest("hex") 816 + ) 817 817 ) 818 818 .limit(1) 819 819 .then((rows) => rows[0]); ··· 843 843 844 844 if (existingTrack?.artistUri) { 845 845 console.log( 846 - `Artist uri ready: ${chalk.cyan(existingTrack.id)} - ${track.title}, after ${chalk.magenta(tries)} tries`, 846 + `Artist uri ready: ${chalk.cyan(existingTrack.id)} - ${track.title}, after ${chalk.magenta(tries)} tries` 847 847 ); 848 848 } 849 849
+13
apps/api/src/profiling.ts
··· 1 + import Pyroscope from "@pyroscope/nodejs"; 2 + 3 + Pyroscope.init({ 4 + serverAddress: "http://localhost:4040", 5 + appName: "rocksky-api", 6 + // Enable CPU time collection for wall profiles 7 + // This is required for CPU profiling functionality 8 + // wall: { 9 + // collectCpuTime: true 10 + // } 11 + }); 12 + 13 + Pyroscope.start();
+5 -6
apps/api/src/sqliteKv.ts
··· 1 - import { Database } from "bun:sqlite"; 2 - import { Kysely } from "kysely"; 3 - import { BunSqliteDialect } from "kysely-bun-sqlite"; 1 + import Database from "better-sqlite3"; 2 + import { Kysely, SqliteDialect } from "kysely"; 4 3 import { defineDriver } from "unstorage"; 5 4 6 5 interface TableSchema { ··· 39 38 throw new Error("SQLite location is required"); 40 39 } 41 40 42 - const sqlite = new Database(location); 41 + const sqlite = new Database(location, { fileMustExist: false }); 43 42 44 43 // Enable WAL mode 45 - sqlite.run("PRAGMA journal_mode = WAL;"); 44 + sqlite.pragma("journal_mode = WAL"); 46 45 47 46 _db = new Kysely<TableSchema>({ 48 - dialect: new BunSqliteDialect({ 47 + dialect: new SqliteDialect({ 49 48 database: sqlite, 50 49 }), 51 50 });
+5 -5
apps/api/src/tealfm/index.ts
··· 20 20 async function publishPlayingNow( 21 21 agent: Agent, 22 22 track: MusicbrainzTrack, 23 - duration: number, 23 + duration: number 24 24 ) { 25 25 try { 26 26 // wait 60 seconds to ensure the track is actually being played ··· 34 34 // diff in seconds less than 60 35 35 Math.abs( 36 36 new Date(record.playedTime).getTime() - 37 - new Date(track.timestamp).getTime(), 37 + new Date(track.timestamp).getTime() 38 38 ) < 60000 39 39 ); 40 40 }); 41 41 if (alreadyPlayed) { 42 42 console.log( 43 43 `Track ${chalk.cyan(track.name)} by ${chalk.cyan( 44 - track.artist.map((a) => a.name).join(", "), 45 - )} already played recently. Skipping...`, 44 + track.artist.map((a) => a.name).join(", ") 45 + )} already played recently. Skipping...` 46 46 ); 47 47 return; 48 48 } ··· 88 88 async function publishStatus( 89 89 agent: Agent, 90 90 track: MusicbrainzTrack, 91 - duration: number, 91 + duration: number 92 92 ) { 93 93 const item: PlayView = { 94 94 trackName: track.name,
+1 -1
apps/api/tsconfig.json
··· 22 22 "target": "esnext", 23 23 "module": "esnext", 24 24 "types": [ 25 - "@types/bun", 25 + "@types/node", 26 26 "@types/service-worker-mock", 27 27 "@cloudflare/workers-types/2023-07-01" 28 28 ]
+21 -16
bun.lock
··· 5 5 "name": "rocksky", 6 6 "devDependencies": { 7 7 "@biomejs/biome": "^2.2.3", 8 - "@types/bun": "^1.3.0", 9 8 "prettier": "^3.5.3", 10 9 "turbo": "^2.5.8", 11 10 }, ··· 17 16 "@atproto/api": "^0.13.31", 18 17 "@atproto/common": "^0.4.6", 19 18 "@atproto/identity": "^0.4.5", 20 - "@atproto/jwk-jose": "0.1.8", 21 19 "@atproto/lex-cli": "^0.5.6", 22 20 "@atproto/lexicon": "^0.4.5", 23 21 "@atproto/oauth-client-node": "^0.2.14", ··· 34 32 "@opentelemetry/sdk-metrics": "^2.0.0", 35 33 "@opentelemetry/sdk-node": "^0.200.0", 36 34 "@opentelemetry/semantic-conventions": "^1.32.0", 35 + "@pyroscope/nodejs": "^0.4.5", 37 36 "assert": "^2.1.0", 38 37 "axios": "^1.7.9", 38 + "better-sqlite3": "^11.8.1", 39 39 "chalk": "^5.4.1", 40 40 "chanfana": "^2.0.2", 41 41 "cors": "^2.8.5", ··· 51 51 "http-proxy-middleware": "^3.0.5", 52 52 "iron-session": "^8.0.4", 53 53 "jsonwebtoken": "^9.0.2", 54 - "kysely": "^0.28.8", 55 - "kysely-bun-sqlite": "^0.4.0", 54 + "kysely": "^0.27.5", 56 55 "lodash": "^4.17.21", 57 56 "nats": "^2.29.2", 58 57 "pg": "^8.13.3", ··· 67 66 "devDependencies": { 68 67 "@biomejs/biome": "^2.2.3", 69 68 "@pkl-community/pkl": "^0.28.2", 70 - "@types/bun": "^1.3.0", 71 69 "@types/express": "^5.0.2", 72 70 "@types/lodash": "^4.17.17", 73 71 "@types/node": "^22.13.0", ··· 76 74 "concurrently": "^9.2.0", 77 75 "drizzle-kit": "^0.31.1", 78 76 "pkgroll": "^2.6.1", 77 + "tsx": "^4.19.2", 79 78 "zx": "^8.5.4", 80 79 }, 81 80 }, ··· 502 501 503 502 "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], 504 503 504 + "@datadog/pprof": ["@datadog/pprof@5.8.2", "", { "dependencies": { "delay": "^5.0.0", "node-gyp-build": "<4.0", "p-limit": "^3.1.0", "pprof-format": "^2.1.0", "source-map": "^0.7.4" } }, "sha512-M+bO4v4TaxYK6k2qJBxnhf7Vh25Wode64y4LfxzGs5kLTFr8eFTp6HJai3/af7U5gjTHX/4s+CHv2bxja97pbw=="], 505 + 505 506 "@date-io/core": ["@date-io/core@2.17.0", "", {}, "sha512-+EQE8xZhRM/hsY0CDTVyayMDDY5ihc4MqXCrPxooKw19yAzUIC6uUqsZeaOFNL9YKTNxYKrJP5DFgE8o5xRCOw=="], 506 507 507 508 "@date-io/date-fns": ["@date-io/date-fns@2.17.0", "", { "dependencies": { "@date-io/core": "^2.17.0" }, "peerDependencies": { "date-fns": "^2.0.0" }, "optionalPeers": ["date-fns"] }, "sha512-L0hWZ/mTpy3Gx/xXJ5tq5CzHo0L7ry6KEO9/w/JWiFWFLZgiNVo3ex92gOl3zmzjHqY/3Ev+5sehAr8UnGLEng=="], ··· 918 919 919 920 "@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="], 920 921 922 + "@pyroscope/nodejs": ["@pyroscope/nodejs@0.4.5", "", { "dependencies": { "@datadog/pprof": "^5.4.1", "debug": "^4.3.3", "p-limit": "^3.1.0", "regenerator-runtime": "^0.13.11", "source-map": "^0.7.3" } }, "sha512-39YVwmLA2QhmLEd/yZjkjvAdU16kH0CiH6KY4wd1O4Lz7CszLW3/q2b33RAegdpnFmu8pPMhDcrPl/79mkjI0g=="], 923 + 921 924 "@redis/bloom": ["@redis/bloom@1.2.0", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg=="], 922 925 923 926 "@redis/client": ["@redis/client@1.6.1", "", { "dependencies": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", "yallist": "4.0.0" } }, "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw=="], ··· 1213 1216 "@types/babel__traverse": ["@types/babel__traverse@7.20.7", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng=="], 1214 1217 1215 1218 "@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="], 1216 - 1217 - "@types/bun": ["@types/bun@1.3.0", "", { "dependencies": { "bun-types": "1.3.0" } }, "sha512-+lAGCYjXjip2qY375xX/scJeVRmZ5cY0wyHYyCYxNcdEXrQ4AOe3gACgd4iQ8ksOslJtW4VNxBJ8llUwc3a6AA=="], 1218 1219 1219 1220 "@types/bunyan": ["@types/bunyan@1.8.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-758fRH7umIMk5qt5ELmRMff4mLDlN+xyYzC+dkPTdKwbSkJFvz6xwyScrytPU0QIBbRRwbiE8/BIg8bpajerNQ=="], 1220 1221 ··· 1410 1411 1411 1412 "better-opn": ["better-opn@3.0.2", "", { "dependencies": { "open": "^8.0.4" } }, "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ=="], 1412 1413 1413 - "better-sqlite3": ["better-sqlite3@12.4.1", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-3yVdyZhklTiNrtg+4WqHpJpFDd+WHTg2oM7UcR80GqL05AOV0xEJzc6qNvFYoEtE+hRp1n9MpN6/+4yhlGkDXQ=="], 1414 + "better-sqlite3": ["better-sqlite3@11.10.0", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ=="], 1414 1415 1415 1416 "bignumber.js": ["bignumber.js@9.3.0", "", {}, "sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA=="], 1416 1417 ··· 1439 1440 "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], 1440 1441 1441 1442 "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], 1442 - 1443 - "bun-types": ["bun-types@1.3.0", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ=="], 1444 1443 1445 1444 "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], 1446 1445 ··· 1480 1479 1481 1480 "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], 1482 1481 1483 - "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], 1482 + "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], 1484 1483 1485 1484 "chromatic": ["chromatic@11.29.0", "", { "peerDependencies": { "@chromatic-com/cypress": "^0.*.* || ^1.0.0", "@chromatic-com/playwright": "^0.*.* || ^1.0.0" }, "optionalPeers": ["@chromatic-com/cypress", "@chromatic-com/playwright"], "bin": { "chroma": "dist/bin.js", "chromatic": "dist/bin.js", "chromatic-cli": "dist/bin.js" } }, "sha512-yisBlntp9hHVj19lIQdpTlcYIXuU9H/DbFuu6tyWHmj6hWT2EtukCCcxYXL78XdQt1vm2GfIrtgtKpj/Rzmo4A=="], 1486 1485 ··· 1651 1650 "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], 1652 1651 1653 1652 "delaunator": ["delaunator@4.0.1", "", {}, "sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag=="], 1653 + 1654 + "delay": ["delay@5.0.0", "", {}, "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw=="], 1654 1655 1655 1656 "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], 1656 1657 ··· 2058 2059 2059 2060 "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], 2060 2061 2061 - "kysely": ["kysely@0.28.8", "", {}, "sha512-QUOgl5ZrS9IRuhq5FvOKFSsD/3+IA6MLE81/bOOTRA/YQpKDza2sFdN5g6JCB9BOpqMJDGefLCQ9F12hRS13TA=="], 2062 - 2063 - "kysely-bun-sqlite": ["kysely-bun-sqlite@0.4.0", "", { "dependencies": { "bun-types": "^1.1.31" }, "peerDependencies": { "kysely": "^0.28.2" } }, "sha512-2EkQE5sT4ewiw7IWfJsAkpxJ/QPVKXKO5sRYI/xjjJIJlECuOdtG+ssYM0twZJySrdrmuildNPFYVreyu1EdZg=="], 2062 + "kysely": ["kysely@0.27.6", "", {}, "sha512-FIyV/64EkKhJmjgC0g2hygpBv5RNWVPyNCqSAD7eTCv6eFWNIi4PN1UvdSJGicN/o35bnevgis4Y0UDC0qi8jQ=="], 2064 2063 2065 2064 "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], 2066 2065 ··· 2208 2207 2209 2208 "node-fetch-native": ["node-fetch-native@1.6.6", "", {}, "sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ=="], 2210 2209 2210 + "node-gyp-build": ["node-gyp-build@3.9.0", "", { "bin": { "node-gyp-build": "./bin.js", "node-gyp-build-test": "./build-test.js", "node-gyp-build-optional": "./optional.js" } }, "sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A=="], 2211 + 2211 2212 "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.1.1", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-test": "build-test.js", "node-gyp-build-optional-packages-optional": "optional.js" } }, "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw=="], 2212 2213 2213 2214 "node-mock-http": ["node-mock-http@1.0.0", "", {}, "sha512-0uGYQ1WQL1M5kKvGRXWQ3uZCHtLTO8hln3oBjIusM75WoesZ909uQJs/Hb946i2SS+Gsrhkaa6iAO17jRIv6DQ=="], ··· 2336 2337 2337 2338 "potpack": ["potpack@1.0.2", "", {}, "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ=="], 2338 2339 2340 + "pprof-format": ["pprof-format@2.1.0", "", {}, "sha512-0+G5bHH0RNr8E5hoZo/zJYsL92MhkZjwrHp3O2IxmY8RJL9ooKeuZ8Tm0ZNBw5sGZ9TiM71sthTjWoR2Vf5/xw=="], 2341 + 2339 2342 "preact": ["preact@10.26.9", "", {}, "sha512-SSjF9vcnF27mJK1XyFMNJzFd5u3pQiATFqoaDy03XuN00u4ziveVVEGt5RKJrDR8MHE/wJo9Nnad56RLzS2RMA=="], 2340 2343 2341 2344 "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], ··· 2457 2460 "redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="], 2458 2461 2459 2462 "redis": ["redis@4.7.1", "", { "dependencies": { "@redis/bloom": "1.2.0", "@redis/client": "1.6.1", "@redis/graph": "1.1.1", "@redis/json": "1.0.7", "@redis/search": "1.2.0", "@redis/time-series": "1.1.0" } }, "sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ=="], 2463 + 2464 + "regenerator-runtime": ["regenerator-runtime@0.13.11", "", {}, "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="], 2460 2465 2461 2466 "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], 2462 2467 ··· 3200 3205 3201 3206 "table/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], 3202 3207 3208 + "tar/chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], 3209 + 3203 3210 "tar/mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], 3204 3211 3205 3212 "tar/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], 3206 - 3207 - "tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], 3208 3213 3209 3214 "tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], 3210 3215
-1
package.json
··· 3 3 "version": "0.0.0", 4 4 "devDependencies": { 5 5 "@biomejs/biome": "^2.2.3", 6 - "@types/bun": "^1.3.0", 7 6 "prettier": "^3.5.3", 8 7 "turbo": "^2.5.8" 9 8 },