`config` — added this morning in 2.1.0 — is gone, and `storeRepositoryUrl` with it, along with the two store repositories they pointed at. 2.1.0 had the registry say how each store behaves. Wrong shape: how a store behaves is fixed per installed client, and this application is the only thing that can see the machine it runs on. A copy of that on a server was a second authority over decisions this side had already made correctly — including which directories the store may delete from — and two authorities are a way to disagree. Keeping two stores on one machine apart needs none of it. It is a subfolder, derived here: the store id is a slug of the catalog host, the home is `<id>-desktop`, the games folder is `<id>`, and that folder is the only subtree the store will ever delete from. Derived from the *catalog* on purpose — the catalog is what a store is, so two records naming the same one are the same store and land in the same place, which makes installing twice idempotent instead of a way to orphan what is already there. Existing installations keep their identity: a store home is recognised by its own `config.json`, so one installed as `ttg` stays `ttg` in `ttg-desktop` with its games where they are. Only a new install derives its id. `StoreProvisioningService` no longer re-reads the registry before installing. That existed to keep the renderer from supplying a config, and with no config in the record there is nothing left to protect: a name and a catalog have no paths in them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
40 lines
1.5 KiB
TypeScript
40 lines
1.5 KiB
TypeScript
import { asRecord, readString } from '../../infrastructure/json/JsonRecord'
|
|
import type { RegistryStoreDto } from '../../shared/contracts/dto/RegistryStoreDto'
|
|
|
|
/**
|
|
* Reading what came over the bridge.
|
|
*
|
|
* The window is ours, but the channel is an interface: a payload is checked here
|
|
* once, so no service below has to wonder whether a string is really a string.
|
|
*/
|
|
export function requireString (value: unknown, name: string): string {
|
|
if (typeof value !== 'string' || value.length === 0) {
|
|
throw new TypeError(`${name} must be a non-empty string`)
|
|
}
|
|
return value
|
|
}
|
|
|
|
export function requireBoolean (value: unknown, name: string): boolean {
|
|
if (typeof value !== 'boolean') throw new TypeError(`${name} must be a boolean`)
|
|
return value
|
|
}
|
|
|
|
export function requireStringArray (value: unknown, name: string): readonly string[] {
|
|
if (!Array.isArray(value)) throw new TypeError(`${name} must be an array of strings`)
|
|
return value.map((item: unknown, index: number): string => requireString(item, `${name}[${String(index)}]`))
|
|
}
|
|
|
|
export function requireRegistryStore (value: unknown): RegistryStoreDto {
|
|
const record = asRecord(value)
|
|
if (record === null) throw new TypeError('a store record is required')
|
|
const store: RegistryStoreDto = {
|
|
name: readString(record, 'name'),
|
|
catalogUrl: readString(record, 'catalogUrl'),
|
|
storeId: readString(record, 'storeId')
|
|
}
|
|
if (store.name.length === 0 || store.catalogUrl.length === 0) {
|
|
throw new TypeError('a store record needs a name and a catalog URL')
|
|
}
|
|
return store
|
|
}
|