Commit Graph

72 Commits

Author SHA1 Message Date
fullex
450e35c7af fix(data-ordering): support all DataApi cache shapes in useReorder
Previously the hook hard-coded a `{ items: Array<...> }` cache contract,
so the two resources actually shipping OrderEndpoints — /pins and
/groups — would have thrown `length mismatch` on first drag because
their GET responses are flat arrays (Pin[], Group[]).

Auto-detect flat arrays and `.items`-bearing objects; expose paired
selectItems/updateItems accessors for nested shapes (grouped views,
GraphQL-style connections, envelopes with a different field name).
Half-configured accessors throw at hook construction.

Degradation splits into two tiers: "cache not loaded" (all paths no-op
and warn per call) vs "shape unrecognized" — move/applyBatch let the
PATCH through since id + anchor are self-contained, while
applyReorderedList refuses since blind whole-list reorder without a
client baseline is unsafe. Unrecognized-shape warnings are
de-duplicated per hook instance.

Paginated shapes (OffsetPaginationResponse / CursorPaginationResponse)
now preserve non-items metadata (total, page, nextCursor) on optimistic
overlays.
2026-04-22 02:33:22 -07:00
fullex
168dfffd57 docs(data-ordering): reflect applyScopedMoves and live group/pin partitions
Update the guide to match what's now landed in code:

- §2: drop the `reserved groupId = '__pinned__'` bucket teaching — pin
  is a separate table, not an overloaded scope value. Point readers
  to the schema/service JSDoc for resource-design details.
- §3: add applyScopedMoves to the helper table.
- §3.1 (new): document the scoped-reorder contract (service-side
  scope inference, multi-scope batch rejection, missing-id semantics)
  in one short section.
- §9 "Group Ordering" upgraded from future-extension placeholder to
  spec, trimmed to ordering-relevant points only.
- §10 "Pin Ordering" added, also ordering-only. Polymorphic shape,
  purgeForEntity contract, idempotent pin semantics, and hard-delete
  rationale are deliberately NOT duplicated here — they live in
  pin.ts / PinService.ts JSDoc where the code is.
- Partition-dimensions list and migration checklist updated so
  `group.entityType` / `pin.entityType` show as live rather than
  hypothetical.
2026-04-21 23:28:35 -07:00
fullex
c9abc6e058 docs(data-ordering): add ordering guide and update cross-references 2026-04-21 03:21:05 -07:00
fullex
375478371c refactor(data-schema): tighten createUpdateTimestamps with NOT NULL
Add `.notNull()` to `createdAt` / `updatedAt` in the shared
`createUpdateTimestamps` helper so Drizzle `$inferSelect` produces
`number` instead of the misleading `number | null`. `deletedAt` in
`createUpdateDeleteTimestamps` stays nullable (soft-delete semantics).

Generated migration 0013 rebuilds 26 affected tables via the standard
SQLite table-recreation pattern; FK / CHECK / INDEX constraints are
preserved across rebuild. No backfill is added (project is in the
development phase; null pre-existing rows are accepted as a "wipe DB"
signal rather than engineered around).

Fix upstream in `KnowledgeMappings.toTimestamp` so it returns a
`Date.now()` fallback instead of `undefined` — otherwise future Dexie
-> v2 migrator runs would try to insert undefined into NOT NULL
columns. Three test assertions updated from `undefined` to
`expect.any(Number)`.

Sweep 18 downstream call sites across 9 functions that were carrying a
dead `?? new Date().toISOString()` fallback:

- AssistantService, KnowledgeBaseService, KnowledgeItemService,
  MessageService, TopicService, TranslateHistoryService,
  TranslateLanguageService (the original Pattern A set)
- McpServerService and MiniAppService.rowToMiniApp (reclassified from
  Pattern B: the domain types stay `optional` to accommodate builtin
  literals in the renderer, but `string` assigns legally into
  `string | undefined`, so the switch is safe)

Keep `MiniAppService.builtinToMiniApp` on `timestampToISOOrUndefined`
— its `dbRow?: MiniAppSelect` semantics ("the preference row may not
exist at all") is genuinely optional, not a disguised "nullable column".

Also remove a Pattern C that neither the plan nor the grep audit
caught: `TagService.ensureTagTimestamp` was a self-rolled defense
layer that threw INTERNAL_SERVER_ERROR on null timestamps. The DB
now refuses to produce such rows, so the defense — and the test
named "should surface timestamp anomalies instead of masking them" —
are dead code. Removed both.

Update three docs to reflect the new defaults:

- `services/utils/README.md` — drop the "DB still nullable" table row
  and the predictive paragraph; reframe Pattern B around
  "whole row may not exist"
- `services/utils/rowMappers.ts` JSDoc — same reframing
- `docs/references/data/data-api-in-main.md` — delete the fallback
  code samples and simplify Convention §3
2026-04-21 03:21:05 -07:00
fullex
f8f0e4b19a refactor(data-services): consolidate row-to-entity mapping utilities
Introduce shared `services/utils/rowMappers.ts` exporting three helpers:
`nullsToUndefined` (renamed from the previous `stripNulls`, with a
corrected type signature that preserves `notNull()` columns unchanged),
`timestampToISO` for guaranteed-present timestamps, and
`timestampToISOOrUndefined` for nullable ones.

Migrate 9 services off hand-rolled null/date handling:

- Eliminate the `stripNulls` duplicate in MiniAppService
- Replace 18 repetitive createdAt/updatedAt ternaries with helper calls
- Fix McpServerService misuse where an optional domain field was being
  forced to a synthesized "now" value; restore honest undefined semantics
- Simplify KnowledgeBaseService.rowToKnowledgeBase via the spread idiom,
  bypassing `clean` for the `T | null` embeddingModelId field

Document the paradigm in docs/references/data/data-api-in-main.md with
standard and advanced skeletons, plus an explicit "when NOT to spread"
list covering services with field renaming, custom fallbacks, computed
fields, or sensitive-data sanitization. Per-helper design decisions
(shallow vs. recursive, rejected alternatives) live in
services/utils/README.md.
2026-04-21 03:21:05 -07:00
fullex
686bd15290 feat(data-api): support template paths, function refresh, and /* prefix matching
Extend the renderer data hooks to cover three mutation shapes that
previously had to drop to imperative dataApiService calls:

- Template paths (e.g. `/providers/:providerId`) with a runtime `params`
  option on useQuery / useMutation / useInfiniteQuery / usePaginatedQuery,
  so a single hook instance can operate on ids chosen at call time.
  ParamsForPath derives types directly from the schema's existing
  `params: {...}` declarations (no template-string parsing).
- Function-form `refresh: ({ args, result }) => ConcreteApiPaths[]` for
  invalidation keys that depend on trigger input or server response. Args
  are closure-captured at trigger entry to avoid races between concurrent
  calls.
- Explicit `/*` suffix for path-segment prefix matching on refresh and
  invalidate patterns, preserving the trailing slash so `/providers/*`
  doesn't match siblings like `/providers-archived`.

A single `resolveTemplate` function is the canonical path-replacement
point, so `useQuery('/providers/:id', { params: { id: 'abc' } })` and
`useQuery('/providers/abc')` produce byte-for-byte identical cache keys.

Dev-mode assertions flag invalid `/*` patterns (bare wildcard or missing
trailing slash) and warn on concurrent triggers against the same template
hook instance, which would share SWR mutation state.

Fully backward compatible: existing `refresh: ['/topics']` and
concrete-path hook calls compile and behave identically.
2026-04-20 09:02:57 -07:00
fullex
b40d863b0b refactor(quick-assistant): rename mini window to quick assistant
Unify the feature under its canonical name "Quick Assistant":

- Rename miniWindow.html → quickAssistant.html and
  windows/mini/ → windows/quickAssistant/ (via git mv to preserve history).
  The bounds file miniWindow-state.json is not migrated; users see default
  bounds once after upgrade.
- Collapse half-renamed QuickWindow/MiniWindow identifiers in
  QuickAssistantService and its callers (ShortcutService, TrayService,
  ShortcutService.test) onto QuickAssistant.
- Rename the Inputbar scope literal 'mini-window' → 'quick-assistant'.
  The Redux DEFAULT_TOOL_ORDER_BY_SCOPE keeps the legacy key behind a
  transitional Record<InputbarScope | 'mini-window', ToolOrder> annotation;
  both entries will be collapsed when Redux is removed.
- Rename i18n keys miniwindow.*, mini_window, show_mini_window to
  quickAssistant.*, quick_assistant, show_quick_assistant across all
  locales; synced via i18n:sync.
- Update lingering "mini window" wording in docs and comments.

Legacy 'mini_window' literals intentionally retained in the v2
ShortcutMappings migrator (identifies Redux-persisted old keys) and in
v1 Redux store files (to be removed with Redux).
2026-04-18 10:36:27 -07:00
fullex
a2adc8fcce feat(data-api): support greedy path params for IDs containing slashes
Extend the DataApi router's `extractPathParams` to recognize `:name*` segments
as greedy captures that consume one or more path segments joined with `/`.
Greedy params may appear as the last segment or in the middle anchored by
trailing static / plain-param segments; at most one greedy is allowed per
pattern. No decoding is performed, consistent with the existing raw-string
policy.

This unblocks routing for composite identifiers whose values contain `/`
(e.g. OpenRouter-style `qwen/qwen3-vl`, Fireworks-style
`accounts/fireworks/models/deepseek-v3p2`) without resorting to URL encoding,
which the project does not use.

- Add 21 unit tests covering plain params, tail greedy, middle greedy,
  multi-greedy rejection, and edge cases.
- Document the feature under "Greedy Path Parameters" in the API design
  guidelines with tail/middle/mixed examples.
2026-04-17 07:29:27 -07:00
fullex
544a8373f8 refactor(data): translate SQLite constraint errors via withSqliteErrors
Introduce src/main/data/db/sqliteErrors.ts with three APIs:

- classifySqliteError(e): walks the .cause chain and classifies UNIQUE /
  FOREIGN KEY / CHECK / NOT NULL violations, including PRIMARYKEY and
  ROWID codes which SQLite reports as semantically UNIQUE.
- withSqliteErrors(op, handlers): runs op and routes recognized
  violations through a sparse handlers map. Constraint kinds without a
  handler and non-SQLite errors are rethrown unchanged by construction,
  so "forgot to rethrow" is not representable in client code.
- defaultHandlersFor(resource, identifier): complete default handler set
  for the common CRUD case. Spread to override specific kinds.

Migrate TranslateLanguageService.create and MiniAppService.create to the
new API. The MiniAppService migration also removes a select-then-insert
TOCTOU race: two concurrent create requests with the same appId now
produce one 201 and one 409 instead of one 201 and one 500.

Delete the now-unused errorUtils.ts. Document the module in
src/main/data/db/README.md and api-design-guidelines.md with a note that
withSqliteErrors handlers are a TOCTOU fallback, not a replacement for
application-level pre-validation.
2026-04-16 09:31:36 -07:00
亢奋猫
ea99c82115 refactor: remove memory module from v2 (#14251)
Co-authored-by: SuYao <sy20010504@gmail.com>
Fixes #14250
2026-04-16 13:22:30 +08:00
亢奋猫
4197117b51 refactor: migrate shortcut system to Preference architecture (#14086)
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
2026-04-16 11:38:21 +08:00
fullex
40719bd77c fix(MessageService): map snake_case columns from raw SQL recursive CTEs
Drizzle's `casing: 'snake_case'` config only applies to the ORM channel.
Raw SQL via `db.all(sql`...`)` returns SQLite's native snake_case columns
with no runtime mapping — the TypeScript generic on `db.all<T>()` is a
compile-time assertion only. The recursive CTEs in `getTree`,
`getBranchMessages`, and `getPathToNode` used `SELECT *` and asserted
results to `messageTable.$inferSelect` (camelCase), so downstream code
silently read `undefined` from `parentId` and `siblingsGroupId`. This
broke multi-model message grouping whenever the renderer rebuilt
branches from the database (page refresh, topic switch).

Switch to the "CTE for IDs, ORM for rows" pattern: the recursive CTE
collects IDs only (single-word columns are casing-safe), then full rows
are fetched via `db.select().from(messageTable).where(inArray(...))`
where Drizzle applies camelCase mapping automatically. CTE order is
restored via a Map since SQL IN-list does not preserve order. Rename
`tree_depth` to `treeDepth` for naming consistency.

Document the pattern as the project standard in
`docs/references/data/database-patterns.md` and add regression tests
covering all three methods plus the multi-model siblings scenario.
2026-04-14 22:49:06 -07:00
fullex
01484ab6cb refactor(data-api): make IpcAdapter implement Disposable for automatic lifecycle cleanup
IpcAdapter is a transport adapter bridging Electron IPC to the
transport-agnostic ApiServer. Previously it managed its own lifecycle
via manual setupHandlers()/removeHandlers() calls, bypassing
BaseService's Disposable tracking.

Now IpcAdapter implements Disposable (setup/dispose), and
DataApiService registers it via registerDisposable() — eliminating
manual onStop() cleanup and gaining exception-safe teardown via
BaseService's finally block. This also paves the way for future
HttpAdapter to follow the same pattern.

Signed-off-by: fullex <0xfullex@gmail.com>
2026-04-11 22:51:45 -07:00
fullex
9b451b87a9 refactor(paths): add @application path alias for main/core/application
Align with the existing @logger alias convention by introducing
@application as a short alias for src/main/core/application. This
reduces import verbosity across ~130 main-process files while keeping
4 intentional sub-path imports (@main/core/application/Application)
unchanged to preserve the vi.mock bypass mechanism in tests.

Configured in tsconfig.node.json and electron.vite.config.ts; Vitest
inherits the alias automatically.

Signed-off-by: fullex <0xfullex@gmail.com>
2026-04-11 22:06:00 -07:00
fullex
3c140fc3be refactor(data): redesign database seeding architecture with SeedRunner and versioned seeders
Replace the ad-hoc seeding system with a journal-based architecture that
tracks seed versions via app_state table and skips unchanged seeds on startup.

- Introduce ISeeder interface with name/version/description/run() contract
- Add SeedRunner orchestrator with journal-based version tracking
- Rename ISeed -> ISeeder, migrate() -> run() (align with industry conventions)
- Rename *Seed -> *Seeder classes, *Seeding.ts -> *Seeder.ts files
- Move seeders into seeding/seeders/ subdirectory for better organization
- Add hashObject utility for auto-computing version from static data sources
- PreferenceSeeder/TranslateLanguageSeeder: auto checksum via hashObject()
- PresetProviderSeeder: lazy getter using RegistryLoader.getProvidersVersion()
- Simplify DbService.onInit() to single SeedRunner.runAll() call
- Add SeedRunner tests and PreferenceSeeder tests
- Add database-seeding-guide.md with version strategy documentation

Signed-off-by: fullex <0xfullex@gmail.com>
2026-04-10 06:29:11 -07:00
SuYao
04de165a35 docs: move provider-registry.md to provider-model directory (#14167)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 20:16:55 +08:00
jidan745le
780a884c67 feat(data): provider/model data migration and registry service (backend only) (#14115)
### What this PR does

Before this PR:
- v2 migration did not include provider/model data migration from legacy
`llm` state.
- Provider/model data APIs and handlers were incomplete.
- `@cherrystudio/provider-registry` (formerly provider-catalog) package
was not integrated into the data layer.

After this PR:
- Add provider/model migration path (`ProviderModelMigrator` + mappings)
and register it in v2 migrator flow.
- Add `@cherrystudio/provider-registry` package with JSON-based registry
data, Zod-validated schemas, and lifecycle-managed
`ProviderRegistryService`.
- Complete provider/model schemas, services, handlers, shared API
schemas/types, and model merger utility.
- Complete provider API endpoints (`registry-models`, `auth-config`,
`api-keys`) aligned with lifecycle DI patterns.

**Note:** This PR is intentionally scoped to backend/data-layer only.
Renderer consumer migration will be submitted in a separate PR to
maintain domain separation.

Fixes #

N/A

### Why we need it and why it was done in this way

The following tradeoffs were made:
- Kept migration and data API implementation within current v2
architecture (handler -> service -> db schema) instead of adding
temporary compatibility layers.
- Replaced protobuf toolchain with JSON + Zod validation for simpler
data pipeline and better debuggability.
- Converted all numeric enums to string-valued `as-const` objects
(EndpointType, ModelCapability, Modality, etc.) for runtime
debuggability.
- Unified separate `baseUrls`, `modelsApiUrls`, `reasoningFormatTypes`
fields into a single `endpointConfigs` map keyed by EndpointType.

The following alternatives were considered:
- Keep protobuf-based registry data; rejected due to complexity of proto
toolchain and poor debuggability of binary data.
- Include renderer consumer migration in same PR; deferred to separate
PR for cleaner domain boundaries.

Links to places where the discussion took place:
- Original combined PR: #14034

### Breaking changes

None.

### Special notes for your reviewer

- This is a backend-only extraction from #14034, which contained both
backend and renderer consumer code. The renderer migration will follow
in a separate PR.
- Please focus review on migration flow (`ProviderModelMigrator`),
provider/model service contracts, and the registry package design.
- The `@cherrystudio/provider-registry` package was renamed from
`provider-catalog` and uses JSON data files instead of protobuf.

### Checklist

- [x] PR: The PR description is expressive enough and will help future
contributors
- [x] Code: Write code that humans can understand and Keep it simple
- [x] Refactor: You have left the code cleaner than you found it (Boy
Scout Rule)
- [ ] Upgrade: Impact of this change on upgrade flows was considered and
addressed if required
- [ ] Documentation: Not required (internal data layer, no user-facing
changes)
- [x] Self-review: I have reviewed my own code

### Release note

```release-note
NONE
```

---------

Signed-off-by: jidan745le <420511176@qq.com>
Signed-off-by: suyao <sy20010504@gmail.com>
Co-authored-by: suyao <sy20010504@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
2026-04-10 19:12:33 +08:00
fullex
e8e13d7be3 docs: add Registry Service supplementary notes to DataApi documentation
Signed-off-by: fullex <0xfullex@gmail.com>
2026-04-10 02:55:25 -07:00
fullex
209b8aa6c7 docs: remove Repository layer from data architecture, default to Service-only pattern
Services now handle both business logic and data access directly via Drizzle ORM.
Repository pattern is strongly discouraged unless absolutely necessary.

Signed-off-by: fullex <0xfullex@gmail.com>
2026-04-10 01:18:32 -07:00
fullex
493fd11aa2 feat(migration): add version compatibility gate to v2 migration
Enforce a linear upgrade path (v1.last → v2.0.0 → v2.x) before
allowing data migration to proceed. Users who manually install v2
bypassing the auto-updater are now blocked at startup if their
previous version is incompatible.

- Add versionPolicy.ts with pure check function and version.log reader
- Add versionLogFile to MigrationPaths (uses resolved userData path)
- Wire version check into v2MigrationGate after needsMigration()
- Block on: no version.log, v1 too old (<1.9.0), v2.0.0 gateway skipped
- Treat v2.0.0 pre-releases as "before v2.0.0" per semver ordering
- Update migration docs with version upgrade requirements

Signed-off-by: fullex <0xfullex@gmail.com>
2026-04-09 22:40:48 -07:00
fullex
2be42e40e2 fix(migration): centralize path resolution to fix v1 custom userData detection
v1 users who configured a custom userData directory via
~/.cherrystudio/config/config.json had their data silently skipped
during v2 migration, because resolveUserDataLocation() reads only
boot-config.json (empty on first v2 launch) and the migration
pipeline ran at the wrong default path.

Introduce MigrationPaths — a frozen, pre-computed path object
resolved once at the migration gate entry. All 10 scattered
app.getPath('userData') and path.join calls in migration/v2 are
replaced with centralized constants. Legacy config.json detection
runs before engine initialization, redirecting both the internal
paths and Chromium-level storage (via app.setPath) so the migration
window's renderer can access the correct Dexie/localStorage data.

When the detected custom path is inaccessible (e.g. unmounted
external drive), a dialog prompts the user instead of silently
falling back to an empty default directory.

Signed-off-by: fullex <0xfullex@gmail.com>
2026-04-09 22:40:48 -07:00
亢奋猫
a83f98fd24 docs: consolidate bilingual docs, add link checker and architecture overview (#14138)
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
2026-04-09 16:01:40 +08:00