Commit Graph
6 Commits
Author SHA1 Message Date
4grayandClaude Opus 5.5 94570bc387 refactor(storage): seams for server-backed playlist storage (#1896)
* docs(storage): server storage design and implementation plans

Design for keeping playlists and user data on the self-hosted server
(#305), revised against master, plus three implementation plans: storage
seams, the web-backend storage API, and the PWA server mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(database): share the playlist row codec

Move buildPlaylistRow, parseAppPlaylist and the upsert conflict helpers
from the Electron database operations into libs/shared/database
(@iptvnator/shared/database/app-playlist) so the self-hosted web backend
can write the same rows. The Electron modules re-export them; behavior is
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(database): read the data root from IPTVNATOR_DATA_DIR

Deployments that keep the database on a mounted volume, such as the
self-hosted web backend in Docker, can now set the data root. The E2E
override still wins; without either variable the root stays ~/.iptvnator.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(services): add the playlist storage backend resolver

PlaylistStorageBackend becomes the single place that decides whether
playlists persist through the Electron bridge or IndexedDB, and defines
the PlaylistDocumentStore contract a server store will implement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(services): resolve playlist storage through one backend

PlaylistsService asks PlaylistStorageBackend for its document store
instead of reading window.electron and supportsSqlite directly. Paths that
only make sense on the desktop (legacy IndexedDB import, worker deletion
with progress, the favorites-channel query) check for the Electron kind
explicitly. Behavior is unchanged: the backend still resolves to the
Electron bridge or IndexedDB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(xtream): store PWA user data through USER_DATA_STORAGE

PwaXtreamDataSource and PwaCollectionStorage read and write favorites,
recently viewed items, collection snapshots and playback positions through
the new USER_DATA_STORAGE token (default: localStorage) instead of calling
localStorage directly, so a deployment can substitute a server-synced
storage. Behavior is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(pwa): document the storage seams

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(database): bracket access in the shared playlist codec

libs/shared/database compiles with noPropertyAccessFromIndexSignature,
which the Electron backend did not enforce on the moved code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* build(electron): alias the shared playlist codec in the worker bundle

The database worker is bundled by esbuild with an explicit alias table;
without the new entry, @iptvnator/shared/database/app-playlist resolved
under the library's index file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(services): read localStorage lazily in USER_DATA_STORAGE

When the browser denies storage access, reading localStorage itself
throws. The default factory returned the global, so injecting the token
failed and PwaXtreamDataSource could not be constructed. The adapter now
reads the global inside each call, where the data source already guards
its reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(database): describe /data as an example data root

The Docker image does not set IPTVNATOR_DATA_DIR yet; that arrives with
the server storage backend.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:03:58 +02:00
4grayandClaude Fable 5.1 37df7aceff perf(database): skip the per-launch duplicate scan and keep playlist payloads last (#1883)
deduplicateXtreamCache no longer scans the whole catalog on every launch once both unique indexes exist (7.8 s cold on a 3.9M-title library). The playlists table keeps payload as its last column (playlists-table.ts); ensurePlaylistsPayloadLast rebuilds an existing table once, keeping its indexes and triggers, foreign keys off, committing only if references referencing playlists gain no violations. On a full copy of that profile initDatabase went from 5.2-8.1 s to 39 ms and the playlist metadata read from 1.5-2 s to 22-34 ms; the one-time rebuild costs about 4 s.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 09:02:17 +02:00
1d9a563d1a test(performance): count startup phases and SQL statements for the J1 launch journey (#1715)
* test(performance): count startup phases and SQL statements for the J1 launch journey

Implements plan item A2. With IPTVNATOR_PERF_CAPTURE=1 the main process
keeps named counters and registers a main-only performance:read-counters
IPC handler; without the flag nothing is counted and the handler does not
exist.

- debug-trace.ts owns the registry; traceStartupPhase replaces the
  trace('startup', ...) sites and counts main.startupPhases.
- The database worker counts executed statements through better-sqlite3's
  Statement prototype (the verbose callback expands every statement and
  made bulk inserts 2-4x slower) and posts the count over its message
  port, flushed before every other worker message. The main-thread shared
  connection is counted through a new connection observer in the shared
  database library.
- The first main window freezes main.modulesRegisteredBeforeWindow at
  creation and main.sqlStatementsBeforeReadyToShow at ready-to-show.
- The journey gate drops the ready-to-show that Electron emits for the
  about:blank detour, so the app sees the real document's first paint,
  and taps the counters handler; the J1 record reads both counters after
  the renderer probe completes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(database): require one SQL statement per exec during initialization

The performance capture counts one exec call as one statement, because
SQL cannot be split reliably in the counter (trigger bodies contain
semicolons). The historical-upgrade driver now wraps exec on every
connection initDatabase opens and fails on a batch, so that counting
assumption holds for the fresh profile and all historical schemas.
Documents the definition in the counter and the architecture docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(performance): count SQL statements only for the launch journey

Codex review: the M3U import, refresh-cancellation and Xtream benchmarks
also run with IPTVNATOR_PERF_CAPTURE=1, so the statement hook wrapped
every row of their bulk inserts and changed what they measure.

SQL counting now also needs IPTVNATOR_PERF_COUNT_SQL=1, which only the
launch journey sets; a harness test fails if another source sets it.
Startup phases, the window snapshot and the read handler stay on the
capture flag. Without SQL counting no ready-to-show listener is attached,
so a zero is never reported for statements nobody counted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: 4gray <fourgray@proton.me>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 21:01:26 +02:00
4gray fadcbb4673 test(database): guard upgrades across skipped releases (#1582)
* docs(database): require upgrades across skipped releases

* test(database): cover direct upgrades from 0.19 through 0.23

* test(database): verify upgraded schemas against current contract
2026-09-11 07:08:45 +02:00
4gray d24c77a143 chore(nx): enforce scoped workspace boundaries (#942) 2026-05-15 09:59:06 +02:00
4gray 815279f49f feat(db): add shared database library with sql config
Introduce a shared database schema library for Drizzle ORM and
configure its TypeScript build settings. Move the canonical schema
into libs/shared/database/src/lib/schema.ts and expose strong
typing and table definitions for playlists, categories, content,
recently_viewed and favorites. Add tsconfig.lib.json to set
outDir, declaration output and node types for the shared lib.

Keep apps/electron-backend compatibility by re-exporting the
schema and types from the new shared library (apps/electron-backend
now imports the shared definitions rather than duplicating them).

These changes centralize the database schema as a single source
of truth, prevent duplication, and enable read-only consumers
(e.g. agent-backend) to depend on compiled type declarations.
2025-12-26 00:35:07 +01:00