Add shared frontend domain types

This commit is contained in:
Ewald
2026-05-26 23:14:55 +02:00
parent 2fd101cb15
commit 3db3a53b9e
11 changed files with 410 additions and 111 deletions
+5 -5
View File
@@ -16,11 +16,11 @@ Goal: migrate the frontend progressively from permissive TypeScript output to ty
## Phase 1 - Shared Types
- [ ] Add typed API response contracts for project list, project set, template preview, workflow save, and model tools.
- [ ] Add typed runtime state interfaces for persisted state and transient UI callbacks.
- [ ] Add typed graph/node/widget interfaces for the subset used by this extension.
- [ ] Replace broad object assumptions in `state_manager.ts`.
- [ ] Replace broad API payload assumptions in `mjr/api.ts`.
- [x] Add typed API response contracts for project list, project set, template preview, workflow save, and model tools.
- [x] Add typed runtime state interfaces for persisted state and transient UI callbacks.
- [x] Add typed graph/node/widget interfaces for the subset used by this extension.
- [x] Replace broad object assumptions in `state_manager.ts`.
- [x] Replace broad API payload assumptions in `mjr/api.ts`.
## Phase 2 - Low-Risk Strict Modules
+20 -19
View File
@@ -1,19 +1,20 @@
export declare function fetchJSON(url: any, opts: any): Promise<any>;
export declare function tryResolveProjectId(folder: any): Promise<any>;
export declare function listProjects(): Promise<any>;
export declare function setProject(project_name: any, create_base?: boolean): Promise<any>;
export declare function getModels(): Promise<any>;
export declare function createCustomOut(payload: any): Promise<any>;
export declare function previewTemplate(template: any, tokens: any): Promise<any>;
export declare function listExistingNames(project_id: any, media?: string): Promise<any>;
export declare function saveWorkflow(payload: any): Promise<any>;
export declare function scanModelCandidates(missing: any): Promise<any>;
export declare function resolveModelRecipes(missing: any): Promise<any>;
export declare function saveModelRecipes(items: any): Promise<any>;
export declare function downloadModels(items: any): Promise<any>;
export declare function getDownloadStatus(job_id: any): Promise<any>;
export declare function getFingerprintCacheStatus(): Promise<any>;
export declare function buildFingerprintCache(payload: any): Promise<any>;
export declare function resolveFingerprint(payload: any): Promise<any>;
export declare function searchRegistry(query: any, limit?: number): Promise<any>;
export declare function contributeRegistry(payload: any): Promise<any>;
import type { ModelToolResponse, ProjectListResponse, ProjectSetResponse, TemplatePreviewResponse, WorkflowSavePayload, WorkflowSaveResponse } from "../types/domain.js";
export declare function fetchJSON<T = any>(url: string, opts?: RequestInit): Promise<T>;
export declare function tryResolveProjectId(folder: string): Promise<string>;
export declare function listProjects(): Promise<ProjectListResponse>;
export declare function setProject(project_name: string, create_base?: boolean): Promise<ProjectSetResponse>;
export declare function getModels(): Promise<ModelToolResponse>;
export declare function createCustomOut(payload: Record<string, unknown>): Promise<TemplatePreviewResponse>;
export declare function previewTemplate(template: string, tokens: Record<string, unknown>): Promise<TemplatePreviewResponse>;
export declare function listExistingNames(project_id: string, media?: string): Promise<string[]>;
export declare function saveWorkflow(payload: WorkflowSavePayload): Promise<WorkflowSaveResponse>;
export declare function scanModelCandidates(missing: unknown[]): Promise<ModelToolResponse>;
export declare function resolveModelRecipes(missing: unknown[]): Promise<ModelToolResponse>;
export declare function saveModelRecipes(items: unknown[]): Promise<ModelToolResponse>;
export declare function downloadModels(items: unknown[]): Promise<ModelToolResponse>;
export declare function getDownloadStatus(job_id: string): Promise<ModelToolResponse>;
export declare function getFingerprintCacheStatus(): Promise<ModelToolResponse>;
export declare function buildFingerprintCache(payload: Record<string, unknown>): Promise<ModelToolResponse>;
export declare function resolveFingerprint(payload: Record<string, unknown>): Promise<ModelToolResponse>;
export declare function searchRegistry(query: string, limit?: number): Promise<ModelToolResponse>;
export declare function contributeRegistry(payload: Record<string, unknown>): Promise<ModelToolResponse>;
+1 -1
View File
File diff suppressed because one or more lines are too long
+7 -41
View File
@@ -1,41 +1,7 @@
export declare const DEFAULT_STATE: {
projectName: string;
workflowName: string;
workflowAsset: string;
workflowLastBase: string;
workflowNextSuffix: number;
workflowPanelOpen: boolean;
projectId: string;
projectFolder: string;
projectExists: any;
autoPatch: boolean;
autoPatchNewNodes: boolean;
autoSwitchTrusted: boolean;
autoHookSave: boolean;
kind: string;
media: string;
name: string;
modelSelection: string;
detectedModelTag: string;
useCustomModel: boolean;
customModelText: string;
modelUpper: boolean;
pendingProjectName: string;
pendingSelectProjectId: string;
lastRelDir: string;
lastPrefix: string;
pathTemplate: string;
showMoreModels: boolean;
lastError: string;
detected: any;
workflowHasSignature: boolean;
workflowChecked: boolean;
isGraphLoading: boolean;
graphLoadToken: number;
graphIsEmpty: boolean;
};
export declare let runtimeState: any;
export declare function setRuntimeState(state: any): void;
export declare function createRuntimeState(overrides: any): any;
export declare function loadState(): any;
export declare function saveState(state: any): void;
import type { PersistedRuntimeState, RuntimeState } from "./types/domain.js";
export declare const DEFAULT_STATE: RuntimeState;
export declare let runtimeState: RuntimeState | null;
export declare function setRuntimeState(state: RuntimeState | null): void;
export declare function createRuntimeState(overrides?: PersistedRuntimeState | Record<string, unknown>): RuntimeState;
export declare function loadState(): PersistedRuntimeState | null;
export declare function saveState(state: RuntimeState): void;
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"state_manager.js","sourceRoot":"","sources":["../src/state_manager.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"state_manager.js","sourceRoot":"","sources":["../src/state_manager.ts"],"names":[],"mappings":"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"}
+136
View File
@@ -0,0 +1,136 @@
export type ProjectMedia = "images" | "videos" | "other" | string;
export interface ProjectListEntry {
project_id: string;
folder?: string;
exists?: boolean;
archived?: boolean;
[key: string]: unknown;
}
export interface ProjectListResponse {
ok?: boolean;
projects?: ProjectListEntry[];
[key: string]: unknown;
}
export interface ProjectSetResponse {
ok?: boolean;
project_id: string;
project_folder: string;
[key: string]: unknown;
}
export interface ProjectResolveResponse {
ok?: boolean;
project_id?: string;
[key: string]: unknown;
}
export interface TemplatePreviewResponse {
ok?: boolean;
path?: string;
rel_dir?: string;
prefix?: string;
[key: string]: unknown;
}
export interface WorkflowSavePayload {
project_id: string;
workflow_name: string;
workflow: unknown;
overwrite?: boolean;
mirror_to_comfy_workflows?: boolean;
use_project_subfolder_in_workflows?: boolean;
asset_folder?: string;
}
export interface WorkflowSaveResponse {
ok?: boolean;
file?: string;
project_rel_path?: string;
workflow_rel_dir?: string;
comfy_workflow_rel?: string;
mirrored?: boolean;
mirror_error?: string;
[key: string]: unknown;
}
export interface ModelToolResponse {
ok?: boolean;
items?: unknown[];
missing?: unknown[];
results?: unknown[];
entries?: unknown[];
candidates?: unknown[];
recipes?: unknown[];
job_id?: string;
status?: string;
[key: string]: unknown;
}
export interface DetectedProject {
mode: "none" | "signature" | "path" | string;
project_folder: string;
project_id: string;
confidence: number;
}
export interface RuntimeUiCallbacks {
updateStatus?: () => void | Promise<void>;
updateResolve?: () => void | Promise<void>;
updateWorkflowBlock?: () => void | Promise<void>;
updateTargetLabel?: () => void | Promise<void>;
refreshExistingNames?: () => void | Promise<void>;
refreshMissingStatus?: () => void | Promise<void>;
updatePreview?: () => void | Promise<void>;
resetForProjectChange?: () => void;
refreshProjectsList?: (force?: boolean) => void | Promise<void>;
buildWorkflowName?: () => string;
setWorkflowName?: (name: string) => void;
}
export interface RuntimeState {
projectName: string;
workflowName: string;
workflowAsset: string;
workflowLastBase: string;
workflowNextSuffix: number;
workflowPanelOpen: boolean;
projectId: string;
projectFolder: string;
projectExists: boolean | null;
autoPatch: boolean;
autoPatchNewNodes: boolean;
autoSwitchTrusted: boolean;
autoHookSave: boolean;
kind: string;
media: ProjectMedia;
name: string;
modelSelection: string;
detectedModelTag: string;
useCustomModel: boolean;
customModelText: string;
modelUpper: boolean;
pendingProjectName: string;
pendingSelectProjectId: string;
lastRelDir: string;
lastPrefix: string;
pathTemplate: string;
showMoreModels: boolean;
lastError: string;
detected: DetectedProject | null;
workflowHasSignature: boolean;
workflowChecked: boolean;
isGraphLoading: boolean;
graphLoadToken: number;
graphIsEmpty: boolean;
customModel?: string;
_ui?: RuntimeUiCallbacks;
_emptyGraphPromptToken?: number | null;
}
export type PersistedRuntimeState = Partial<Pick<RuntimeState, "projectName" | "workflowName" | "workflowAsset" | "workflowLastBase" | "workflowNextSuffix" | "workflowPanelOpen" | "projectId" | "projectFolder" | "autoPatch" | "autoPatchNewNodes" | "autoSwitchTrusted" | "autoHookSave" | "kind" | "media" | "name" | "modelSelection" | "detectedModelTag" | "useCustomModel" | "customModelText" | "modelUpper" | "pendingProjectName" | "pendingSelectProjectId" | "lastRelDir" | "lastPrefix" | "pathTemplate" | "showMoreModels">>;
export interface ComfyWidget {
name?: string;
value?: unknown;
options?: {
values?: unknown[];
[key: string]: unknown;
};
[key: string]: unknown;
}
export interface ComfyNode {
type?: string;
title?: string;
widgets?: ComfyWidget[];
[key: string]: unknown;
}
+2
View File
@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=domain.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"domain.js","sourceRoot":"","sources":["../../src/types/domain.ts"],"names":[],"mappings":""}
+47 -37
View File
@@ -1,3 +1,13 @@
import type {
ModelToolResponse,
ProjectListResponse,
ProjectResolveResponse,
ProjectSetResponse,
TemplatePreviewResponse,
WorkflowSavePayload,
WorkflowSaveResponse,
} from "../types/domain.js";
let _csrfTokenPromise = null;
function _getApiKey() {
@@ -41,7 +51,7 @@ async function _getCsrfToken(forceRefresh = false) {
return _csrfTokenPromise;
}
export async function fetchJSON(url, opts) {
export async function fetchJSON<T = any>(url: string, opts?: RequestInit): Promise<T> {
const o = opts ? { ...opts } : {};
o.credentials = o.credentials || "same-origin";
o.headers = { ...(o.headers || {}) };
@@ -85,130 +95,130 @@ export async function fetchJSON(url, opts) {
return data;
}
export async function tryResolveProjectId(folder) {
export async function tryResolveProjectId(folder: string): Promise<string> {
try {
const data = await fetchJSON(`/mjr_project/resolve?folder=${encodeURIComponent(folder)}`);
const data = await fetchJSON<ProjectResolveResponse>(`/mjr_project/resolve?folder=${encodeURIComponent(folder)}`);
if (data?.ok) return data.project_id || "";
} catch (_) {}
return "";
}
export async function listProjects() {
return fetchJSON("/mjr_project/list");
export async function listProjects(): Promise<ProjectListResponse> {
return fetchJSON<ProjectListResponse>("/mjr_project/list");
}
export async function setProject(project_name, create_base = true) {
return fetchJSON("/mjr_project/set", {
export async function setProject(project_name: string, create_base = true): Promise<ProjectSetResponse> {
return fetchJSON<ProjectSetResponse>("/mjr_project/set", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ project_name, create_base }),
});
}
export async function getModels() {
return fetchJSON("/mjr_project/models");
export async function getModels(): Promise<ModelToolResponse> {
return fetchJSON<ModelToolResponse>("/mjr_project/models");
}
export async function createCustomOut(payload) {
return fetchJSON("/mjr_project/create_custom_out", {
export async function createCustomOut(payload: Record<string, unknown>): Promise<TemplatePreviewResponse> {
return fetchJSON<TemplatePreviewResponse>("/mjr_project/create_custom_out", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload || {}),
});
}
export async function previewTemplate(template, tokens) {
return fetchJSON("/mjr_project/preview_template", {
export async function previewTemplate(template: string, tokens: Record<string, unknown>): Promise<TemplatePreviewResponse> {
return fetchJSON<TemplatePreviewResponse>("/mjr_project/preview_template", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ template, tokens: tokens || {} }),
});
}
export async function listExistingNames(project_id, media = "images") {
export async function listExistingNames(project_id: string, media = "images"): Promise<string[]> {
const qs = new URLSearchParams({
project_id: project_id || "",
media: media || "images",
});
const data = await fetchJSON(`/mjr_project/assets/list_names?${qs.toString()}`);
const data = await fetchJSON<{ names?: unknown[] }>(`/mjr_project/assets/list_names?${qs.toString()}`);
return Array.isArray(data?.names) ? data.names : [];
}
export async function saveWorkflow(payload) {
return fetchJSON("/mjr_project/workflow/save", {
export async function saveWorkflow(payload: WorkflowSavePayload): Promise<WorkflowSaveResponse> {
return fetchJSON<WorkflowSaveResponse>("/mjr_project/workflow/save", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload || {}),
});
}
export async function scanModelCandidates(missing) {
return fetchJSON("/mjr_models/scan_candidates", {
export async function scanModelCandidates(missing: unknown[]): Promise<ModelToolResponse> {
return fetchJSON<ModelToolResponse>("/mjr_models/scan_candidates", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ missing: missing || [] }),
});
}
export async function resolveModelRecipes(missing) {
return fetchJSON("/mjr_models/resolve_recipes", {
export async function resolveModelRecipes(missing: unknown[]): Promise<ModelToolResponse> {
return fetchJSON<ModelToolResponse>("/mjr_models/resolve_recipes", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ missing: missing || [] }),
});
}
export async function saveModelRecipes(items) {
return fetchJSON("/mjr_models/save_recipes", {
export async function saveModelRecipes(items: unknown[]): Promise<ModelToolResponse> {
return fetchJSON<ModelToolResponse>("/mjr_models/save_recipes", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ items: items || [] }),
});
}
export async function downloadModels(items) {
return fetchJSON("/mjr_models/download", {
export async function downloadModels(items: unknown[]): Promise<ModelToolResponse> {
return fetchJSON<ModelToolResponse>("/mjr_models/download", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ items: items || [] }),
});
}
export async function getDownloadStatus(job_id) {
export async function getDownloadStatus(job_id: string): Promise<ModelToolResponse> {
const qs = new URLSearchParams({ job_id: job_id || "" });
return fetchJSON(`/mjr_models/download_status?${qs.toString()}`);
return fetchJSON<ModelToolResponse>(`/mjr_models/download_status?${qs.toString()}`);
}
export async function getFingerprintCacheStatus() {
return fetchJSON("/mjr_models/fingerprint_cache_status");
export async function getFingerprintCacheStatus(): Promise<ModelToolResponse> {
return fetchJSON<ModelToolResponse>("/mjr_models/fingerprint_cache_status");
}
export async function buildFingerprintCache(payload) {
return fetchJSON("/mjr_models/build_fingerprint_cache", {
export async function buildFingerprintCache(payload: Record<string, unknown>): Promise<ModelToolResponse> {
return fetchJSON<ModelToolResponse>("/mjr_models/build_fingerprint_cache", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload || {}),
});
}
export async function resolveFingerprint(payload) {
return fetchJSON("/mjr_models/resolve_by_fingerprint", {
export async function resolveFingerprint(payload: Record<string, unknown>): Promise<ModelToolResponse> {
return fetchJSON<ModelToolResponse>("/mjr_models/resolve_by_fingerprint", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload || {}),
});
}
export async function searchRegistry(query, limit = 6) {
return fetchJSON("/mjr_models/registry/search", {
export async function searchRegistry(query: string, limit = 6): Promise<ModelToolResponse> {
return fetchJSON<ModelToolResponse>("/mjr_models/registry/search", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: String(query || ""), limit }),
});
}
export async function contributeRegistry(payload) {
return fetchJSON("/mjr_models/registry/contribute", {
export async function contributeRegistry(payload: Record<string, unknown>): Promise<ModelToolResponse> {
return fetchJSON<ModelToolResponse>("/mjr_models/registry/contribute", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload || {}),
+9 -7
View File
@@ -1,6 +1,8 @@
import type { PersistedRuntimeState, RuntimeState } from "./types/domain.js";
const STORAGE_KEY = "mjr_project_settings_state";
export const DEFAULT_STATE = {
export const DEFAULT_STATE: RuntimeState = {
projectName: "",
workflowName: "",
workflowAsset: "",
@@ -37,17 +39,17 @@ export const DEFAULT_STATE = {
graphIsEmpty: false,
};
export let runtimeState = null;
export let runtimeState: RuntimeState | null = null;
export function setRuntimeState(state) {
export function setRuntimeState(state: RuntimeState | null) {
runtimeState = state;
}
export function createRuntimeState(overrides) {
export function createRuntimeState(overrides?: PersistedRuntimeState | Record<string, unknown>): RuntimeState {
return Object.assign({}, DEFAULT_STATE, overrides || {});
}
export function loadState() {
export function loadState(): PersistedRuntimeState | null {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) return null;
@@ -57,8 +59,8 @@ export function loadState() {
}
}
export function saveState(state) {
const data = {
export function saveState(state: RuntimeState) {
const data: PersistedRuntimeState = {
projectName: state.projectName,
workflowName: state.workflowName,
workflowAsset: state.workflowAsset,
+181
View File
@@ -0,0 +1,181 @@
export type ProjectMedia = "images" | "videos" | "other" | string;
export interface ProjectListEntry {
project_id: string;
folder?: string;
exists?: boolean;
archived?: boolean;
[key: string]: unknown;
}
export interface ProjectListResponse {
ok?: boolean;
projects?: ProjectListEntry[];
[key: string]: unknown;
}
export interface ProjectSetResponse {
ok?: boolean;
project_id: string;
project_folder: string;
[key: string]: unknown;
}
export interface ProjectResolveResponse {
ok?: boolean;
project_id?: string;
[key: string]: unknown;
}
export interface TemplatePreviewResponse {
ok?: boolean;
path?: string;
rel_dir?: string;
prefix?: string;
[key: string]: unknown;
}
export interface WorkflowSavePayload {
project_id: string;
workflow_name: string;
workflow: unknown;
overwrite?: boolean;
mirror_to_comfy_workflows?: boolean;
use_project_subfolder_in_workflows?: boolean;
asset_folder?: string;
}
export interface WorkflowSaveResponse {
ok?: boolean;
file?: string;
project_rel_path?: string;
workflow_rel_dir?: string;
comfy_workflow_rel?: string;
mirrored?: boolean;
mirror_error?: string;
[key: string]: unknown;
}
export interface ModelToolResponse {
ok?: boolean;
items?: unknown[];
missing?: unknown[];
results?: unknown[];
entries?: unknown[];
candidates?: unknown[];
recipes?: unknown[];
job_id?: string;
status?: string;
[key: string]: unknown;
}
export interface DetectedProject {
mode: "none" | "signature" | "path" | string;
project_folder: string;
project_id: string;
confidence: number;
}
export interface RuntimeUiCallbacks {
updateStatus?: () => void | Promise<void>;
updateResolve?: () => void | Promise<void>;
updateWorkflowBlock?: () => void | Promise<void>;
updateTargetLabel?: () => void | Promise<void>;
refreshExistingNames?: () => void | Promise<void>;
refreshMissingStatus?: () => void | Promise<void>;
updatePreview?: () => void | Promise<void>;
resetForProjectChange?: () => void;
refreshProjectsList?: (force?: boolean) => void | Promise<void>;
buildWorkflowName?: () => string;
setWorkflowName?: (name: string) => void;
}
export interface RuntimeState {
projectName: string;
workflowName: string;
workflowAsset: string;
workflowLastBase: string;
workflowNextSuffix: number;
workflowPanelOpen: boolean;
projectId: string;
projectFolder: string;
projectExists: boolean | null;
autoPatch: boolean;
autoPatchNewNodes: boolean;
autoSwitchTrusted: boolean;
autoHookSave: boolean;
kind: string;
media: ProjectMedia;
name: string;
modelSelection: string;
detectedModelTag: string;
useCustomModel: boolean;
customModelText: string;
modelUpper: boolean;
pendingProjectName: string;
pendingSelectProjectId: string;
lastRelDir: string;
lastPrefix: string;
pathTemplate: string;
showMoreModels: boolean;
lastError: string;
detected: DetectedProject | null;
workflowHasSignature: boolean;
workflowChecked: boolean;
isGraphLoading: boolean;
graphLoadToken: number;
graphIsEmpty: boolean;
customModel?: string;
_ui?: RuntimeUiCallbacks;
_emptyGraphPromptToken?: number | null;
}
export type PersistedRuntimeState = Partial<
Pick<
RuntimeState,
| "projectName"
| "workflowName"
| "workflowAsset"
| "workflowLastBase"
| "workflowNextSuffix"
| "workflowPanelOpen"
| "projectId"
| "projectFolder"
| "autoPatch"
| "autoPatchNewNodes"
| "autoSwitchTrusted"
| "autoHookSave"
| "kind"
| "media"
| "name"
| "modelSelection"
| "detectedModelTag"
| "useCustomModel"
| "customModelText"
| "modelUpper"
| "pendingProjectName"
| "pendingSelectProjectId"
| "lastRelDir"
| "lastPrefix"
| "pathTemplate"
| "showMoreModels"
>
>;
export interface ComfyWidget {
name?: string;
value?: unknown;
options?: {
values?: unknown[];
[key: string]: unknown;
};
[key: string]: unknown;
}
export interface ComfyNode {
type?: string;
title?: string;
widgets?: ComfyWidget[];
[key: string]: unknown;
}