import { z } from "zod"; export const HOUSEKEEPING_PREFERENCE_MAX_IDENTIFIER_LENGTH = 128; export const HOUSEKEEPING_PREFERENCE_MAX_ITEMS_PER_ARRAY = 64; export const HOUSEKEEPING_PREFERENCE_MAX_SERIALIZED_BYTES = 4096; export interface HousekeepingPreferences { schemaVersion: 1; pinnedRouteIds: string[]; pinnedCommandIds: string[]; shortcutOrder: string[]; widgetOrder: string[]; enabledOptionalWidgetIds: string[]; } const uniqueIdentifiers = z .array( z.string().trim().min(1).max(HOUSEKEEPING_PREFERENCE_MAX_IDENTIFIER_LENGTH), ) .max(HOUSEKEEPING_PREFERENCE_MAX_ITEMS_PER_ARRAY) .superRefine((values, context) => { const seen = new Set(); for (const [index, value] of values.entries()) { if (seen.has(value)) { context.addIssue({ code: "custom", message: "duplicate preference identifier", path: [index], }); } seen.add(value); } }); export const housekeepingPreferencesSchema: z.ZodType = z .object({ schemaVersion: z.literal(1), pinnedRouteIds: uniqueIdentifiers, pinnedCommandIds: uniqueIdentifiers, shortcutOrder: uniqueIdentifiers, widgetOrder: uniqueIdentifiers, enabledOptionalWidgetIds: uniqueIdentifiers, }) .strict() .superRefine((preferences, context) => { if ( new TextEncoder().encode(JSON.stringify(preferences)).byteLength > HOUSEKEEPING_PREFERENCE_MAX_SERIALIZED_BYTES ) { context.addIssue({ code: "custom", message: "housekeeping preferences payload is too large", }); } }); export function defaultHousekeepingPreferences(): HousekeepingPreferences { return { schemaVersion: 1, pinnedRouteIds: [], pinnedCommandIds: [], shortcutOrder: [], widgetOrder: [], enabledOptionalWidgetIds: [], }; } export function parseHousekeepingPreferences( serialized: string, ): HousekeepingPreferences { let value: unknown; try { value = JSON.parse(serialized); } catch { throw new Error("invalid housekeeping preferences JSON"); } return housekeepingPreferencesSchema.parse(value); }