Skip to content

feat(saved-queries): import and export JSON backups - #756

Merged
cevheri merged 4 commits into
libredb:mainfrom
2160039878-cyber:feat/saved-query-transfer-690
Sep 10, 2026
Merged

feat(saved-queries): import and export JSON backups#756
cevheri merged 4 commits into
libredb:mainfrom
2160039878-cyber:feat/saved-query-transfer-690

Conversation

@2160039878-cyber

@2160039878-cyber 2160039878-cyber commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Description

Saved Queries now offers JSON export and import. Export writes the complete library even while a search or connection-type filter is active. Import validates the entire file before writing, merges new IDs, and reports collisions while preserving existing queries.

Closes #690.

Type of Change

  • New feature (non-breaking)
  • Documentation and test updates

Changes Made

  • Reuse downloadText and jsonText for the JSON array backup. Preserve query text, engine, descriptions, tags and both timestamps.
  • Validate imported fields against the existing shipped-engine registry with Zod, restore Date objects and strip unknown fields. Invalid files make no writes.
  • Configure Zod's JIT-free mode before the browser-loaded schema is constructed, so even its caught eval-capability probe does not violate the app's CSP. The security policy is unchanged.
  • Merge against the latest saved library through the storage facade, retaining the first occurrence of each ID. A successful merge makes one write and emits the existing storage-sync event; empty or all-conflicting imports make no write.
  • Show added counts and skipped IDs, handle file-read and storage failures, and allow selecting the same file again. Import never selects or executes a query.
  • Document saved-query backups in README.

Testing

  • TDD: six new component cases failed against the original source.
  • bun run test:components --pass-with-no-tests -t 'SavedQueries': 18 matching tests passed, 0 failed, including the reviewed file-input reset assertion.
  • The import test observes the input's value setter receiving an empty string. Removing the reset assignment makes that case fail; the production source was restored byte-for-byte afterward.
  • bun run test:unit --isolate --pass-with-no-tests -t 'parseSavedQueries|storage: saved queries': 21 passed, 0 failed.
  • The initial Linux E2E run caught an eval CSP violation introduced by schema construction. A fresh-process regression blocks and counts Function construction during both import and parsing; it sees one attempted evaluation before the fix and zero afterward while preserving the imported fields and Date values.
  • Covers complete-library export despite filters, metadata/date round trips, invalid payloads, duplicate IDs both within a file and in storage, empty imports, quota/read errors, one storage event and unchanged existing queries.
  • Passed locally: format, lint, typecheck, knip, readme:check, chart:check, channels:showcase:check, security:check, production build, build:lib and attw.
  • Full local bun run test / coverage and E2E were not completed: this Windows host lacks Helm/chart dependencies, Docker is unavailable, and existing SQLite cleanup tests encounter Windows file-lock errors. Official Linux CI must verify the full suite and 100% line-coverage gate.

Environment: Windows, Node.js 24.18.1, Bun 1.4.2. Builds used a clean checkout with real local dependencies; the latest follow-up changes only the component test.

Checklist

  • Reviewed the diff and reused the existing export/storage paths.
  • Added regression tests and updated README.
  • Required CI test job passes the 100% line-coverage gate (pending CI for the test follow-up).

Additional Notes

AI-assisted implementation and test execution using Codex. No new dependencies or provider/schema changes. Import is additive: an ID collision is reported and skipped, never overwritten.

@codecov

codecov Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@2160039878-cyber

Copy link
Copy Markdown
Contributor Author

The initial CI run found a real regression in this PR: constructing the newly browser-loaded Zod object probes Function, which emits a CSP violation even when the library catches the exception and falls back successfully. Both Chromium and WebKit reported script-src / eval.

Fixed in 8733479 by enabling Zod's JIT-free mode before constructing the schema. No security-header or E2E assertion was changed. A fresh-process unit regression blocks and counts dynamic evaluation across module import and parsing: the previous commit attempts it once; this commit attempts it zero times and preserves the query metadata and Date values. All 21 targeted importer/storage tests, static checks and both builds pass. The existing browser CSP tests will verify the submitted fix in CI.

@cevheri cevheri left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both of #690's items are met, and the best thing in this PR is the test nobody asked you for.

plan-draft-boundary.test.ts in this repo exists because zod v4's eval probe took a CSP violation on every page rendering an agent run, and its docstring records that nothing caught it until an end-to-end run in a real browser did, with six local gates and twenty CI checks all passing. Your fresh-process test, replacing Function with a proxy that throws the way a strict script-src does and asserting zero probes, turns that into a unit test. I checked it bites: removing the z.config line turns it red.

I verified the fix rather than trusting it, because that docstring says the probe happens at module load, which would put your config too late. It is not true of 4.5.4. Counting new Function constructions: importing zod alone is 0, building the schema is 1, the first parse is 2, and with jitless it stays 0 through all three. zod's own source now short-circuits the probe under jitless with a comment about strict CSPs. So your commit message is right and the repo's comment is stale. That correction is mine, not yours, and I will make it.

Also refuted a concern of my own before raising it: there is no export-then-import gap on connectionType, because SHIPPED_DATABASE_TYPES derives from a Readonly<Record<DatabaseType, true>> and the compiler guarantees it covers every type.

One change. expect(input.value).toBe("") at SavedQueries.test.tsx:98 cannot fail. importFile sets only files, so a jsdom file input reads "" before your component does anything. Measured: delete event.target.value = "" from the source and all 18 cases still pass. The line is guarding something real, since picking the same file twice fires no change event, so a user who edits the JSON after a collision report and re-picks it gets silence. Please make the assertion observe the assignment instead, for example by defining a value setter on the input before firing and asserting it received "". I wrote that version to price it: 18 pass with your line in place, 1 fails without it.

Everything else measures clean: 14 unit, 33 storage and 18 component cases pass, all twenty checks green including E2E, and four of the five mutants I tried are killed, the survivor being the one above.

@2160039878-cyber

Copy link
Copy Markdown
Contributor Author

Updated in 96b264b. The import case now installs a setter spy on the file input and asserts that it receives "", instead of reading the input's initially empty value.

bun run test:components --pass-with-no-tests -t 'SavedQueries': 18 passed, 0 failed. Temporarily removing event.target.value = "" makes the import case fail because the setter is never called. The production source was then restored byte-for-byte; this follow-up changes only the test.

@cevheri

cevheri commented Sep 9, 2026

Copy link
Copy Markdown
Member

This is done. Merging shortly.

The assertion bites now: 18 pass with event.target.value = "" in place, and deleting that line turns the import case red, which is the control I could not get from the old expect(input.value).toBe("").

Two things I checked beyond the ask. Your source is byte-identical to 8733479, so the CSP property is still the one E2E already verified green in a real browser on that head, and the coverage gate cannot move because nothing under src/ changed. And I ran your file alongside all twenty-five files of Group 15/16 in one process: 908 pass, 0 fail. That is not paranoia about your change specifically. Another PR tonight stubbed document.createElement in a test that then threw before restoring it, and the leaked spy broke sixty-nine tests in eight unrelated files of that group. Your setter is defined on an element from that render, so it cannot escape the test, but a stub on a shared global would have.

Thanks for the note in your comment saying you restored the source byte-for-byte after running the control. That is the part of a report I can actually check, and it checked out.

@cevheri
cevheri merged commit 32ef7d8 into libredb:main Sep 10, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Saved Queries has no export or import

2 participants