From 4d8390115d48f5b99654d83ff134ae1a3a444b3e Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Sun, 28 May 2023 16:48:33 -0500 Subject: [PATCH] Config state temp2 --- src/lib/stores/configState.ts | 173 +++++++++++++-------------- src/tests/stores/configStateTests.ts | 60 ++++++++++ src/tests/testSuite.ts | 1 + 3 files changed, 146 insertions(+), 88 deletions(-) create mode 100644 src/tests/stores/configStateTests.ts diff --git a/src/lib/stores/configState.ts b/src/lib/stores/configState.ts index 5da2dab..a05dcd3 100644 --- a/src/lib/stores/configState.ts +++ b/src/lib/stores/configState.ts @@ -1,97 +1,10 @@ import { debounce } from '$lib/utils'; import { get, writable } from 'svelte/store'; import type { Writable } from 'svelte/store'; +import { z, type ZodTypeAny } from "zod" type ConfigDefType = "boolean" | "number" | "string" | "string[]"; -// A simple parameter description interface -interface ConfigDef { - // The `IdType` is necessary to get a stricter type - // parameter instead of a generic `id: string;`. This will be needed - // later when we infer the type of the `config` object. - name: IdType; - - type: TypeType, - - description?: string, - - defaultValue: ValueType, -} - -type ConfigDefBoolean = ConfigDef; -type ConfigDefNumber = ConfigDef; -type ConfigDefString = ConfigDef; -type ConfigDefStringArray = ConfigDef; - -// Configuration parameters ------------------------------------ - -const defComfyUIHostname: ConfigDefString<"comfyUIHostname"> = { - name: "comfyUIHostname", - type: "string", - defaultValue: "localhost", - description: "Backend domain for ComfyUI", -}; - -const defComfyUIPort: ConfigDefNumber<"comfyUIPort"> = { - name: "comfyUIPort", - type: "number", - defaultValue: 8188, - description: "Backend port for ComfyUI", -}; - -const defAlwaysStripUserState: ConfigDefBoolean<"alwaysStripUserState"> = { - name: "alwaysStripUserState", - type: "boolean", - defaultValue: false, - description: "Strip user state even if saving to local storage" -}; - -const defPromptForWorkflowName: ConfigDefBoolean<"promptForWorkflowName"> = { - name: "promptForWorkflowName", - type: "boolean", - defaultValue: false, - description: "When saving, always prompt for a name to save the workflow as", -}; - -const defConfirmWhenUnloadingUnsavedChanges: ConfigDefBoolean<"confirmWhenUnloadingUnsavedChanges"> = { - name: "confirmWhenUnloadingUnsavedChanges", - type: "boolean", - defaultValue: true, - description: "When closing the tab, open the confirmation window if there's unsaved changes" -}; - -const defBuiltInTemplates: ConfigDefStringArray<"builtInTemplates"> = { - name: "builtInTemplates", - type: "string[]", - defaultValue: [], - description: "Basenames of templates that can be loaded from public/templates. Saves LocalStorage space.", -}; - -const defCacheBuiltInResources: ConfigDefBoolean<"cacheBuiltInResources"> = { - name: "cacheBuiltInResources", - type: "boolean", - defaultValue: true, - description: "Cache loading of built-in resources to save network use" -}; - -export const CONFIG_DEFS = [ - defComfyUIHostname, - defComfyUIPort, - defAlwaysStripUserState, - defPromptForWorkflowName, - defConfirmWhenUnloadingUnsavedChanges, - defBuiltInTemplates, - defCacheBuiltInResources, -] as const; - -type Config>>> = { - [K in T[number]["name"]]: Extract["defaultValue"] -} extends infer O - ? { [P in keyof O]: O[P] } - : never; - -type ConfigState = Config - type ConfigStateOps = { getBackendURL: () => string, save: () => void, @@ -110,6 +23,90 @@ const store: Writable = writable( cacheBuiltInResources: true, }) +type Conf2 = { + name: string; + type: ZodTypeAny; + defaultValue: any; + description: string; +}; + +const def2ComfyUIHostname: Conf2 = { + name: 'backend.comfyUIHostname', + type: z.string(), + defaultValue: 'localhost', + description: 'Backend domain for ComfyUI', +}; + +const def2ComfyUIPort: Conf2 = { + name: 'backend.comfyUIPort', + type: z.number(), + defaultValue: 8188, + description: 'Backend port for ComfyUI', +}; + +const def2AlwaysStripUserState: Conf2 = { + name: 'behavior.alwaysStripUserState', + type: z.boolean(), + defaultValue: false, + description: 'Strip user state even if saving to local storage', +}; + +export const allconfs: ReadonlyArray = [ + def2ComfyUIHostname, + def2ComfyUIPort, + def2AlwaysStripUserState, +] as const; + +const confItems: any = {}; +const confDefaults: any = {} + +for (const item of allconfs) { + const trail = item.name.split('.'); + let theI = confItems; + let theDef = confDefaults; + for (const category of trail.slice(0, -1)) { + theI[category] ||= { __category__: true }; + theI = theI[category]; + + theDef[category] ||= {}; + theDef = theDef[category]; + } + const optionName = trail[trail.length - 1]; + theI[optionName] = item; + theDef[optionName] = item.defaultValue; +} + +function recurse(item: Record, trail: string[] = []): ZodTypeAny { + let defaultValue = confDefaults + for (const name of trail) { + defaultValue = defaultValue[name]; + } + + for (const [key, value] of Object.entries(item)) { + if (value.__category__) { + delete value['__category__']; + item[key] = recurse(value, trail.concat(key)); + } else { + const result = value.type.safeParse(value.defaultValue); + if (!result.success) { + throw new Error( + `Default value for config item ${value.name} did not pass type matcher: ${result.error}` + ); + } + item[key] = value.type.catch(value.defaultValue); + } + } + return z.object(item).catch({ ...defaultValue }); +} + +export const Config2 = recurse(confItems); +export type ConfigStore = z.infer; + +const stor: ConfigStore = { + backend: {}, + a: "foo" +} + function getBackendURL(): string { const state = get(store); return `${window.location.protocol}//${state.comfyUIHostname}:${state.comfyUIPort}` diff --git a/src/tests/stores/configStateTests.ts b/src/tests/stores/configStateTests.ts new file mode 100644 index 0000000..0f35323 --- /dev/null +++ b/src/tests/stores/configStateTests.ts @@ -0,0 +1,60 @@ +import { get } from "svelte/store"; +import configState, { Config2 } from "$lib/stores/configState" +import { expect } from 'vitest'; +import UnitTest from "../UnitTest"; +import { Watch } from "@litegraph-ts/nodes-basic"; + +export default class configStateTests extends UnitTest { + test__parse__parsesBasic() { + const testConf = { + backend: { + comfyUIHostname: 'test', + comfyUIPort: 8187, + }, + behavior: { + alwaysStripUserState: false, + }, + }; + + expect(Config2.safeParse(testConf).success).toEqual(true); + } + + test__parse__parsesFallbacks() { + const testConf = { + backend: {}, + behavior: {} + }; + + const result = Config2.safeParse(testConf) + + console.warn(result) + expect(result.success).toEqual(true); + expect(result.data).toEqual({ + backend: { + comfyUIHostname: "localhost", + comfyUIPort: 8188 + }, + behavior: { + alwaysStripUserState: false + } + }); + } + + test__parse__parsesFallbacks2() { + const testConf = "foo"; + + const result = Config2.safeParse(testConf) + + console.warn(result) + expect(result.success).toEqual(true); + expect(result.data).toEqual({ + backend: { + comfyUIHostname: "localhost", + comfyUIPort: 8188 + }, + behavior: { + alwaysStripUserState: false + } + }); + } +} diff --git a/src/tests/testSuite.ts b/src/tests/testSuite.ts index 9e96316..a8d80e2 100644 --- a/src/tests/testSuite.ts +++ b/src/tests/testSuite.ts @@ -3,3 +3,4 @@ export { default as ComfyGraphTests } from "./ComfyGraphTests" export { default as parseA1111Tests } from "./parseA1111Tests" export { default as convertA1111ToStdPromptTests } from "./convertA1111ToStdPromptTests" export { default as convertVanillaWorkflowTest } from "./convertVanillaWorkflowTests" +export { default as configStateTests } from "./stores/configStateTests"