Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
116 commits
Select commit Hold shift + click to select a range
c40add7
feat(database): Initial database POC implementation
FoxxMD Apr 3, 2026
83f501d
add FK For parent play
FoxxMD Apr 23, 2026
fec39b2
test(database): Craete pending migration and no-db test cases
FoxxMD Apr 23, 2026
2df8bfe
test(database): More migration tests
FoxxMD Apr 23, 2026
40234e6
refactor(database): Use integer for primary key and make string id as…
FoxxMD Apr 23, 2026
3c51149
feat(database): More database implementation
FoxxMD Apr 23, 2026
d59cb26
feat(database): Add basic repository and transaction support
FoxxMD Apr 23, 2026
e9d1c81
chore: Add ts pretty errors to devcontainer extensions
FoxxMD Apr 23, 2026
5771fbf
feat(database): Implement repository querying
FoxxMD Apr 23, 2026
dda73cd
feat(database): Implement query plays by date range
FoxxMD Apr 23, 2026
cda33ba
test(database): Add component query test
FoxxMD Apr 23, 2026
99ebb53
chore: Remove unusued patch for kysely
FoxxMD Apr 23, 2026
84b43d7
feat(database): Implement play deletes
FoxxMD Apr 24, 2026
b388ca6
feat(database): Cascade parent id to null on play deletes
FoxxMD Apr 24, 2026
92e027b
feat(database): Implement finding purgable play ids
FoxxMD Apr 24, 2026
60a6ddb
feat(database): Implement drizzle logger
FoxxMD Apr 24, 2026
c7361a8
feat(database): Add full backup-and-migrate logic
FoxxMD Apr 24, 2026
3b54684
feat(database): Use asyncstorage for query logging
FoxxMD Apr 24, 2026
cb1ff7d
feat(database): Init and migrate database on startup
FoxxMD Apr 24, 2026
6e66cb0
refactor: Remove duplicated regex functions
FoxxMD Apr 24, 2026
c038431
feat(database): Implement retention options and configuration parsing
FoxxMD Apr 24, 2026
46389fd
feat(database): Add compacting config
FoxxMD Apr 25, 2026
7aa5595
feat(database): Implement component hydration as component init stage
FoxxMD Apr 25, 2026
f38f10d
feat(database): Implement retention cleanup
FoxxMD Apr 25, 2026
561c62b
fix(database): Unique constraint on uid + mode + type, not just uid
FoxxMD Apr 26, 2026
59d1050
feat(database): Implement retention cleanup scheduled task
FoxxMD Apr 26, 2026
adf6b9e
feat(database): Implement basic repository
FoxxMD Apr 26, 2026
8f049a0
feat(database): Implement discovered/scrobble count tracking
FoxxMD Apr 26, 2026
2a52d1e
fix(spotify): Fix device id default should be only device constant, n…
FoxxMD Apr 27, 2026
047de49
feat(database): Initial Souce database usage implementation
FoxxMD Apr 27, 2026
3ef4854
feat(database): Add id awareness to plays and implement basic api GET…
FoxxMD Apr 27, 2026
e9b2990
partial scrobble queue database implementation
FoxxMD Apr 27, 2026
f94538e
first pass on client database implementation
FoxxMD Apr 28, 2026
3c3faf2
more implementation for clients
FoxxMD Apr 28, 2026
47caf8d
more scrobble db implementation
FoxxMD Apr 28, 2026
765b13f
test: Comment out scrobble caching tests
FoxxMD Apr 28, 2026
952ddbd
more client db impl
FoxxMD Apr 28, 2026
439f0e0
fixes based on testing for client db impl
FoxxMD Apr 28, 2026
0f6aa56
add repository api endpoint POC
FoxxMD Apr 28, 2026
4bc460f
feat(database): Add relational model inferred type
FoxxMD Apr 29, 2026
5774be1
Implement api response transform to old structure
FoxxMD Apr 29, 2026
f9f9230
the implementation continues
FoxxMD Apr 29, 2026
b849498
chore: Add sqlite browser vscode extension to devcontainer
FoxxMD Apr 29, 2026
cfb4131
remove scrobbledPlays and prepare queue next query
FoxxMD Apr 30, 2026
54da8b0
remove unused queue package
FoxxMD May 1, 2026
272c3cc
feat(database): Add migration path for cached queued scrobbles
FoxxMD May 1, 2026
8fd870b
Fix failed queueu updates
FoxxMD May 1, 2026
cbe996d
feat(database): Add query params for finding plays by queue
FoxxMD May 1, 2026
22c0916
Fix counts for scrobbles/dead
FoxxMD May 1, 2026
691db92
remove unused variable
FoxxMD May 1, 2026
8de382f
feat(database): Optimize queries for scrobble range ops
FoxxMD May 1, 2026
8882e7a
chore: Update to drizzle rc1
FoxxMD May 1, 2026
12ba994
fix: Add discovered plays with platform based on multiplatform source…
FoxxMD May 4, 2026
524afb6
refactor: Remove platform-awareness for play discovery and database
FoxxMD May 5, 2026
077c3d3
fix(database): regenerate migrations with platformId removed
FoxxMD May 5, 2026
f679995
fix: Fix parsing duration value from string
FoxxMD May 5, 2026
d9a5aa1
fix: Improved retention behavior
FoxxMD May 5, 2026
8be5645
remove debugging statement
FoxxMD May 5, 2026
48b6b55
feat(source): Replace discovered function with existingPlay function
FoxxMD May 5, 2026
d772c8c
test(database): Implement db size stats
FoxxMD May 6, 2026
7a7d7bb
feat(database): Stub out jobs for later use
FoxxMD May 6, 2026
102f979
chore: Bump ts-essentials version
FoxxMD May 6, 2026
0bf9d5e
refactor(database): Replace relation select type with simpler built type
FoxxMD May 6, 2026
5eb967f
test(database): Fix play fixture assignment of updatedAt
FoxxMD May 6, 2026
d0379e1
fix: Simplify and fix getting next dead scrobble
FoxxMD May 6, 2026
f788894
fix(source): Remove superfluous interval logging
FoxxMD May 6, 2026
562b388
feat(now playing): Reduce logging noise
FoxxMD May 6, 2026
092cf90
refactor(client): Move heartbeat responsibility into client class
FoxxMD May 6, 2026
630edd7
feat(client): Make playing now scheduling part of heartbeat
FoxxMD May 6, 2026
75c0230
refactor(source): Move heartbeat responsibility into source class
FoxxMD May 6, 2026
1b53d22
feat: Refactor artists as an object
FoxxMD May 7, 2026
d95f323
feat: Enrich listenbrainz and jellyfin source plays with artist mbids
FoxxMD May 7, 2026
a5926d7
chore: Bump drizzle to lastest beta
FoxxMD May 7, 2026
4d7162a
docs: Add admonition 'important' type
FoxxMD May 7, 2026
f211a4a
refactor(cache)!: Simplify cache config
FoxxMD May 7, 2026
e814cf5
docs: Add upgrade path for 0.14.0
FoxxMD May 7, 2026
0cb20b8
feat: Add ID and name envs for all sources/clients
FoxxMD May 7, 2026
dcc53d1
docs: Fix links and paths
FoxxMD May 7, 2026
d922f37
fix(client): Implement artistcredit conversion during cached scrobble…
FoxxMD May 7, 2026
b8a9623
fix: Add neotraverse as production dependency
FoxxMD May 7, 2026
8cffd0d
fix: Fix resolving config globally
FoxxMD May 8, 2026
c7fce06
fix(listenbrainz): Maybe fix null artist_name
FoxxMD May 8, 2026
640d944
test: Add listenbrainz submit listens example requests for reference
FoxxMD May 8, 2026
90f0bdb
fix(client): Fix dead to queued queueName constraint in the wrong par…
FoxxMD May 8, 2026
957db35
fix(ui): Gaurd against undefined Source in on failed scrobbled page
FoxxMD May 8, 2026
80474a9
feat: Add dead scrobble delay configurable through debug env
FoxxMD May 8, 2026
3cdbfc3
feat: Implement generic play identifier repo method
FoxxMD May 8, 2026
fa1051b
feat(client): Allow processing any dead scrobble that is not complete…
FoxxMD May 8, 2026
28ade6a
fix: More improvements to dead scrobbler handling and rendering
FoxxMD May 8, 2026
f36fd16
chore: Bump with-local-tmp-dir version
FoxxMD May 8, 2026
a1ad250
feat: Improve cache testing and cleanup cache serialization
FoxxMD May 8, 2026
756d9b8
deps: updated to latest across the board
philocalyst May 12, 2026
d2f8717
client: migrated code to new api surface
philocalyst May 12, 2026
53870ac
core: migrated code to new api surface
philocalyst May 12, 2026
d0a490b
deps: fix the last conflict
philocalyst May 12, 2026
eb2159d
docsite: added skip lib check
philocalyst May 12, 2026
fc6ec84
vite: include ansi-to-react
philocalyst May 12, 2026
811615d
fix(docs): Remove skipLibCheck and address React 19 namespace issues …
FoxxMD May 12, 2026
1f199e2
fix(ui): Correct for bad ansi export
FoxxMD May 12, 2026
f93c1fa
revert: Remove vite optimize for ansi-to-react
FoxxMD May 12, 2026
c22867a
Merge pull request #594 from philocalyst/database-latest
FoxxMD May 12, 2026
51b7b3f
test(database): Add 10k size stats
FoxxMD May 9, 2026
af5aec2
port changes from pgllite branch
FoxxMD May 12, 2026
a410721
fix(now playing): Don't update now playing if minimum play informatio…
FoxxMD May 12, 2026
146916b
feat: Enrich artist and albumartist mbids
FoxxMD May 12, 2026
fbf3af8
feat(database): Implement app migrations
FoxxMD May 13, 2026
49be95a
fix(database): Add gitkeep for app migrations empty folder
FoxxMD May 13, 2026
0a594d5
test: Tighter integration with act
FoxxMD May 13, 2026
e6709c4
feat(database): Implement (with patch) migration context
FoxxMD May 13, 2026
5228d48
refactor: Simplify init process by using abort controller to detect i…
FoxxMD May 13, 2026
c86819b
feat(database): Implement cached scrobble migration per-component
FoxxMD May 13, 2026
c5cdc46
fix(lastfm): Remove scrobble duration when less than 30 seconds to by…
FoxxMD May 13, 2026
fe4b947
fix(koito): Use same now playing threshold as listenrbainz
FoxxMD May 14, 2026
7ae727a
feat(cache): Improve scrobble migration
FoxxMD May 14, 2026
83a425a
feat(database): Serialize errors with custom data type
FoxxMD May 14, 2026
38d7a9c
feat: Improve error type guards and ui rendering
FoxxMD May 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
"dbaeumer.vscode-eslint",
"unifiedjs.vscode-mdx",
"bradlc.vscode-tailwindcss",
"TakumiI.markdowntable"
"TakumiI.markdowntable",
"YoavBls.pretty-ts-errors",
"bowlerr.sqlite-intelliview-vscode"
]
}
},
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/testAndSanity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,16 @@ jobs:
name: Tests / Build / Sanity Run
runs-on: ubuntu-latest
steps:
- name: Check out the repo
- name: Check out the repo (gha)
if: ${{ !env.ACT }}
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
with:
ref: ${{ inputs.ref }}
persist-credentials: false

- name: Check out the repo (act)
if: ${{ env.ACT }}
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
- name: Use Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f #6.3.0
with:
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,10 @@ tmp-*

*storybook.log
storybook-static
lib

*.db
*.db.*
*.db-*lib
*.db
*.db.*
*.db-*lib
6 changes: 6 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"recommendations": [
"sanjulaganepola.github-local-actions",
"github.vscode-github-actions"
]
}
233 changes: 73 additions & 160 deletions docsite/docs/configuration/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -245,11 +245,8 @@ WARN : [App] [Sources] [spotify Secrets] Matched: None | Unmatched: SPOTIFY_SECR

</DetailsAdmo>

## Application Options

These options affect multi-scrobbler's behavior and are not specific to any source/client.

### Base URL
## Base URL

Defines the URL that is used to generate default redirect URLs for authentication on [spotify](/configuration/sources/spotify) and [lastfm](/configuration/clients/lastfm) -- as well as some logging hints.

Expand Down Expand Up @@ -280,197 +277,96 @@ Useful when running with [docker](../installation/installation.mdx#docker) so th

</details>

### Caching

Multi-scrobbler caches some activities to persist important data across restarts, reduce external API calls, and make some actions faster.

All of the activities below are **always** cached **in-memory** with an optional, configurable [**secondary** store](#secondary-caching-configuration) for persistence.

<Tabs groupId="cachedThings" queryString>
<TabItem value="scrobbles" label="Scrobbles">

**Queued** and **Failed** Scrobbles are cached so that any un-scrobbled data you have is persisted across restarts of multi-scrobbler.

:::tip

By default, this data use a [Secondary](#secondary-caching-configuration) [File](./?cacheType=file#secondary-caching-configuration) store, configured for you automatically.

If you have configured a [persisted volume/bind mount](/installation?dockerSetting=storage#recommended-settings) for configuration (`/config` is mounted in [docker compose](/quickstart#create-docker-compose-file)) then you are already done. If you are not persisting this directory then you should consider setting up [Valkey Cache](./?cacheType=valkey#secondary-caching-configuration) for this.

:::

##### Configuration

Use any [Secondary Cache](#secondary-caching-configuration), the config examples below show the default values:

<Tabs>
<TabItem value="env" label="ENV">

| Environmental Variable | Required? | Default | Description |
| :--------------------- | --------- | --------- | --------------------- |
| `CACHE_SCROBBLE` | No | `file` | The cache type to use |
| `CACHE_SCROBBLE_CONN` | No | `/config` | |
## Caching

</TabItem>
Multi-scrobbler implements caching to persist important data across restarts, reduce external API calls, and make some actions faster.

<TabItem value="aio" label="AIO">
A default **in-memory** cache store is used so that you always benefit from some caching. An optional, [**secondary** store](#secondary-caching) can be configured for greater caching capabilities.

```json5 title="config.json"
{
"cache": {
"scrobble": {
"provider": "file",
"connection": "/config"
}
},
// ...
}
```
</TabItem>
</Tabs>
<DetailsAdmo type="note" summary="What is Cached?">

</TabItem>
<Tabs groupId="cachedThings">
<TabItem value="auth" label="Auth Data">

Authentication sessions/tokens/etc... are cached for quicker requests and for persistence across restarts.

:::tip

By default, this data use a [Secondary](#secondary-caching-configuration) [File](./?cacheType=file#secondary-caching-configuration) store, configured for you automatically.

If you have configured a [persisted volume/bind mount](/installation?dockerSetting=storage#recommended-settings) for configuration (`/config` is mounted in [docker compose](/quickstart#create-docker-compose-file)) then you are already done. If you are not persisting this directory then you should consider setting up [Valkey Cache](./?cacheType=valkey#secondary-caching-configuration) for this.

:::

##### Configuration

Use any [Secondary Cache](#secondary-caching-configuration), the config examples below show the default values:

<Tabs>
<TabItem value="env" label="ENV">

| Environmental Variable | Required? | Default | Description |
| :--------------------- | --------- | --------- | --------------------- |
| `CACHE_AUTH` | No | `file` | The cache type to use |
| `CACHE_AUTH_CONN` | No | `/config` | |

</TabItem>

<TabItem value="aio" label="AIO">

```json5 title="config.json"
{
"cache": {
"auth": {
"provider": "file",
"connection": "/config"
}
},
// ...
}
```
</TabItem>
</Tabs>

<TabItem value="transform" label="Scrobble Transforms">
The results of [transform rules](/configuration/transforms) are cached so that if a scrobble with identical data (track/artists/album) is identified and it has the same set of transform rules then the cached transform results can be applied.
</TabItem>
<TabItem value="metadata" label="Transform API Calls">

API Calls to external (metadata) services used to [Enhance Scrobbles](/configuration/transforms), like calls to [Musicbrainz](/configuration/transforms/musicbrainz), can be cached to avoid duplicate calls and speed up scrobble transformations.

By default, these calls are only cached in memory. If you wish for cached calls to be persisted across restarts then setup [Valkey Cache](./?cacheType=valkey#secondary-caching-configuration).

##### Configuration

Use any [Secondary Cache](#secondary-caching-configuration), the config examples below show the default values:

<Tabs>
<TabItem value="env" label="ENV">

| Environmental Variable | Required? | Default | Description |
| :--------------------- | --------- | ------- | --------------------- |
| `CACHE_METADATA` | No | | The cache type to use |
| `CACHE_METADATA_CONN` | No | | |

</TabItem>

<TabItem value="aio" label="AIO">

```json5 title="config.json"
{
"cache": {
"metadata": {
"provider": "valkey",
"connection": "yourConnectionStringHere"
}
},
// ...
}
```
</TabItem>
</Tabs>

</TabItem>
</Tabs>
</DetailsAdmo>

#### Secondary Caching Configuration
<DetailsAdmo type="important" summary="Auth Cache Configuration">

The type of cache used, and its connection properties, can be configured through **ENV** or **AIO** config.
Auth caching defaults to a **file** that is stored in the `CONFIG_DIR` directory using the pre-defined file name `ms-auth.cache`.

<Tabs groupId="cacheType" queryString>
This provides automatic persistence across restarts for long-lived auth data/credentials if you have configured a [persisted volume/bind mount](/installation?dockerSetting=storage#recommended-settings) for configuration (`/config` is mounted in [docker compose](/quickstart#create-docker-compose-file)).

<TabItem value="file" label="File">
If you wish to use the [secondary store](#secondary-caching) for caching Auth you must explicitly configure it. This is because valkey can potentially be *ephemeral* if you do not provide a volume for its data directory.

**File** cache is stored in the `CONFIG_DIR` directory using the pre-defined file name `ms-[cacheName].cache`.
To explicitly configure auth to use the secondary store:

<Tabs groupId="configType" queryString>

<TabItem value="env" label="ENV">

Example

| Environmental Variable | Required? | Default | Description |
| :--------------------- | --------- | --------- | ------------------------------------------------------------ |
| `CACHE_SCROBBLE` | No | `file` | The cache type to use |
| `CACHE_SCROBBLE_CONN` | No | `/config` | The directory, within the container, to store the cache file |
```yaml title="compose.yaml"
services:
multi-scrobbler:
# ...
environment:
// highlight-start
- CACHE_AUTH=valkey
// highlight-end
# ...
```

</TabItem>

<TabItem value="aio" label="AIO">

Example

```json5 title="config.json"
{
"cache": {
"scrobble": {
"provider": "file",
"connection": "/config"
"auth": {
"provider": "valkey"
}
},
// ...
}
```

</TabItem>

</Tabs>
</TabItem>

<TabItem value="valkey" label="Valkey">
</DetailsAdmo>

### Secondary Caching Store {#secondary-caching}

Using a secondary store enables:

[**Valkey**](https://valkey.io/) is an open-source fork of Redis.
* persistence of cached data across restarts
* a larger store (more data is saved)
* a longer time-to-live in the store (cached data is fetchable for a longer period)

These benefits are particularly beneficial when using [transforms](/configuration/transforms) like Musicbrainz and it is **strongly recommended** for these scenarios.

Currently, Multi-scrobbler only supports [**Valkey**](https://valkey.io/), an open-source fork of Redis, as a secondary store.

<details>

<summary>Adding Valkey container to multi-scrobbler</summary>

A valkey container can be added to the [multi-scrobbler docker compose stack](/installation?runType=docker-compose#docker):

<Tabs>
<TabItem value="named" label="Named Volume">

```yaml title="docker-compose.yml"
services:
multi-scrobbler:
# ...
# adding everything below
// highlight-start
valkey:
image: valkey/valkey
Expand All @@ -483,6 +379,25 @@ volumes:
// highlight-end
```

</TabItem>
<TabItem value="bind" label="Bind Mount">

```yaml title="docker-compose.yml"
services:
multi-scrobbler:
# ...
// highlight-start
valkey:
image: valkey/valkey
volumes:
- ./valkeyData:/data
// highlight-end
```

</TabItem>

</Tabs>

Use `redis://valkey:6379` as the connection string in the configurations below.

</details>
Expand All @@ -497,12 +412,16 @@ redis://HOST_IP:HOST_PORT

<TabItem value="env" label="ENV">

Example

| Environmental Variable | Required? | Default | Description |
| :--------------------- | --------- | -------- | ------------------------------------------------------------------- |
| `CACHE_METADATA` | Yes | `valkey` | The cache type to use |
| `CACHE_METADATA_CONN` | Yes | | The host/IP and port to connect to EX: `redis://192.168.0.120:6379` |
```yaml title="compose.yaml"
services:
multi-scrobbler:
# ...
environment:
// highlight-start
- CACHE_VALKEY=redis://192.168.0.120:6379
// highlight-end
# ...
```

</TabItem>

Expand All @@ -513,23 +432,17 @@ Example
```json5 title="config.json"
{
"cache": {
"metadata": {
"provider": "valkey",
"connection": "redis://192.168.0.120:6379"
}
"valkey": "redis://192.168.0.120:6379"
},
// ...
}
```

</TabItem>

</Tabs>
</TabItem>

</Tabs>

### Debug Mode
## Debug Mode

Turning on Debug Mode will

Expand All @@ -550,7 +463,7 @@ To set debug mode either add it to [AIO `config.json`](./?configType=aio#configu

or set the [ENV](./?configType=env#configuration-types) `DEBUG_MODE=true`

### Disable Web
## Disable Web

If you do not need the dashboard and/or ingress sources, or have security concerns about ingress and cannot control your hosting environment, the web server and API can be disabled.

Expand Down
2 changes: 1 addition & 1 deletion docsite/docs/configuration/sources/lastfm-endpoint.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,6 @@ http://localhost:9078/api/lastfm/mySlug
<Config config="LastFMEndpointSourceConfig" fileContent={LastfmEndpointConfig} name="endpointlfm">
| Environmental Variable | Required? | Default | Description |
| :--------------------- | :-------- | ------- | ------------------------------------------------------------------------------------------------------------------ |
| `LFMENDPOINT_ENABLE` | No | | Use LFM Endpoint as a Source without any other configuration. Only required if slug/token are not provided as ENVs |
| `LFM_ENABLE` | No | | Use LFM Endpoint as a Source without any other configuration. Only required if slug/token are not provided as ENVs |
| `LFM_SLUG` | No | | (Optional) The URL suffix to use for accepting LFM scrobbles |
</Config>
Loading
Loading