feat(app): improve desktop multi-server support (#30678)
Co-authored-by: Brendan Allan <git@brendonovich.dev>
This commit is contained in:
parent
7ae856a9e9
commit
7f33576f46
62 changed files with 1523 additions and 841 deletions
70
packages/app/src/utils/server-scope.ts
Normal file
70
packages/app/src/utils/server-scope.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import type { ServerConnection } from "@/context/server"
|
||||
|
||||
export type ServerScope = string & { readonly __brand: "ServerScope" }
|
||||
export type SessionRouteKey = string & { readonly __brand: "SessionRouteKey" }
|
||||
export type SessionStateKey = string & { readonly __brand: "SessionStateKey" }
|
||||
export type ScopedKey = string & { readonly __brand: "ScopedKey" }
|
||||
|
||||
const separator = "\u0000"
|
||||
|
||||
function fragment(label: string, value: string) {
|
||||
if (value.includes(separator)) throw new Error(`${label} cannot contain null bytes`)
|
||||
return value
|
||||
}
|
||||
|
||||
function compose(scope: ServerScope, parts: string[]) {
|
||||
return [fragment("Server scope", scope), ...parts.map((part) => fragment("Scoped key part", part))].join(separator)
|
||||
}
|
||||
|
||||
export const ServerScope = {
|
||||
local: "local" as ServerScope,
|
||||
fromServerKey(key: ServerConnection.Key, canonicalLocalServer?: ServerConnection.Key) {
|
||||
return fragment("Server scope", key === "sidecar" || key === canonicalLocalServer ? ServerScope.local : key) as ServerScope
|
||||
},
|
||||
}
|
||||
|
||||
export const SessionRouteKey = {
|
||||
fromRoute(dir: string | undefined, sessionID?: string) {
|
||||
return fragment("Session route", `${dir ?? ""}${sessionID ? "/" + sessionID : ""}`) as SessionRouteKey
|
||||
},
|
||||
fromLegacy(key: string) {
|
||||
return fragment("Legacy session route", key) as SessionRouteKey
|
||||
},
|
||||
}
|
||||
|
||||
export const SessionStateKey = {
|
||||
from(scope: ServerScope, route: SessionRouteKey) {
|
||||
return compose(scope, [route]) as SessionStateKey
|
||||
},
|
||||
route(key: string) {
|
||||
const split = key.lastIndexOf(separator)
|
||||
return SessionRouteKey.fromLegacy(split === -1 ? key : key.slice(split + 1))
|
||||
},
|
||||
scope(key: string) {
|
||||
const split = key.indexOf(separator)
|
||||
if (split === -1) return ServerScope.local
|
||||
return fragment("Stored server scope", key.slice(0, split)) as ServerScope
|
||||
},
|
||||
}
|
||||
|
||||
export const ScopedKey = {
|
||||
from(scope: ServerScope, ...parts: string[]) {
|
||||
return compose(scope, parts) as ScopedKey
|
||||
},
|
||||
prefix(scope: ServerScope, ...parts: string[]) {
|
||||
return `${ScopedKey.from(scope, ...parts)}${separator}`
|
||||
},
|
||||
}
|
||||
|
||||
export function migrateLegacySessionStateKeys(value: unknown) {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) return value
|
||||
const entries = Object.entries(value)
|
||||
if (entries.every(([key]) => key.includes(separator))) return value
|
||||
const scoped = Object.fromEntries(entries.filter(([key]) => key.includes(separator)))
|
||||
for (const [key, item] of entries) {
|
||||
if (key.includes(separator)) continue
|
||||
const next = SessionStateKey.from(ServerScope.local, SessionRouteKey.fromLegacy(key))
|
||||
if (!(next in scoped)) scoped[next] = item
|
||||
}
|
||||
return scoped
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue