-
Notifications
You must be signed in to change notification settings - Fork 0
β‘ Bolt: Optimize playlist song fetching and fix type errors #56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,7 @@ | ||
| ## 2026-02-10 - Atomic Database Updates | ||
| **Learning:** Read-modify-write patterns for counters (like `playCount`) cause race conditions and extra DB round trips. | ||
| **Action:** Use atomic SQL updates (e.g., `playCount = playCount + 1`) with `returning()` to ensure data integrity and performance. | ||
|
|
||
| ## 2026-02-11 - Drizzle Query Instability | ||
| **Learning:** The `.nullsLast()` method on `desc()` sort operations causes type errors (`Property 'nullsLast' does not exist`) in this environment. | ||
| **Action:** Avoid `.nullsLast()` and rely on default null handling (or explicit `sql` fragments) for sorting. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,7 +10,7 @@ import { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type Playlist, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type InsertPlaylist, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from "@shared/schema"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { eq, desc, and, inArray, sql, getTableColumns } from "drizzle-orm"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { eq, desc, asc, and, inArray, sql, getTableColumns } from "drizzle-orm"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export interface IStorage { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Song CRUD | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -140,7 +140,7 @@ export class DatabaseStorage implements IStorage { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .from(songs) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .innerJoin(songLikes, eq(songs.id, songLikes.songId)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .where(eq(songLikes.userId, userId)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .orderBy(desc(songLikes.createdAt).nullsLast()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .orderBy(desc(songLikes.createdAt)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Changing the sort to Useful? React with πΒ / π.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .orderBy(desc(songLikes.createdAt)); | |
| .orderBy(desc(sql`coalesce(${songLikes.createdAt}, to_timestamp(0))`)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job on optimizing this function to reduce the number of database queries! While this is a solid improvement from 3 queries to 2, we can actually consolidate this into a single query using Drizzle's relational query features. This would make it even more efficient by avoiding the separate getPlaylist call.
By using db.query.playlists.findFirst with the with clause, you can fetch the playlist and all its related songs in one go.
| const playlist = await this.getPlaylist(id); | |
| if (!playlist) return undefined; | |
| const playlistSongRows = await db.select({ songId: playlistSongs.songId }) | |
| .from(playlistSongs) | |
| .where(eq(playlistSongs.playlistId, id)); | |
| const songIds = playlistSongRows.map(r => r.songId).filter(id => typeof id === 'number' && !isNaN(id)); | |
| if (songIds.length === 0) { | |
| return { ...playlist, songs: [] }; | |
| } | |
| const songsResult = await db.select() | |
| // Optimized: Single query with innerJoin to avoid N+1 and ID mapping overhead | |
| const songsList = await db.select(getTableColumns(songs)) | |
| .from(songs) | |
| .where(inArray(songs.id, songIds)); | |
| const songMap = new Map(songsResult.map(s => [s.id, s])); | |
| const songsList = songIds.map(id => songMap.get(id)).filter((s): s is Song => !!s); | |
| .innerJoin(playlistSongs, eq(songs.id, playlistSongs.songId)) | |
| .where(eq(playlistSongs.playlistId, id)) | |
| .orderBy(asc(playlistSongs.addedAt)); | |
| return { ...playlist, songs: songsList }; | |
| const playlistData = await db.query.playlists.findFirst({ | |
| where: eq(playlists.id, id), | |
| with: { | |
| playlistSongs: { | |
| orderBy: asc(playlistSongs.addedAt), | |
| with: { | |
| song: true, | |
| }, | |
| }, | |
| }, | |
| }); | |
| if (!playlistData) { | |
| return undefined; | |
| } | |
| const { playlistSongs, ...playlist } = playlistData; | |
| const songsList = playlistSongs.map((ps) => ps.song).filter((s): s is Song => !!s); | |
| return { ...playlist, songs: songsList }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
inArrayis still imported fromdrizzle-ormbut no longer used in this file after the playlist query rewrite. Please remove it from the import list to keep the module clean and avoid reintroducing unused-query patterns later.