Skip to main content
Glama
asset.store.ts27.8 kB
import { defineStore } from "pinia"; import * as _ from "lodash-es"; import { addStoreHooks, ApiRequest } from "@si/vue-lib/pinia"; import { useWorkspacesStore } from "@/store/workspaces.store"; import { FuncId, FuncKind } from "@/api/sdf/dal/func"; import { SchemaId, SchemaVariant, SchemaVariantId, UninstalledVariant, } from "@/api/sdf/dal/schema"; import { Visibility } from "@/api/sdf/dal/visibility"; import keyedDebouncer from "@/utils/keyedDebouncer"; import router from "@/router"; import { PropKind } from "@/api/sdf/dal/prop"; import { nonNullable } from "@/utils/typescriptLinter"; import { ChangeSetId } from "@/api/sdf/dal/change_set"; import { useFuncStore } from "./func/funcs.store"; import { useChangeSetsStore } from "./change_sets.store"; import { useModuleStore } from "./module.store"; import { useRealtimeStore } from "./realtime/realtime.store"; import handleStoreError from "./errors"; import { useRouterStore } from "./router.store"; import { useAuthStore } from "./auth.store"; export interface InstalledPkgAssetView { assetId: string; assetHash: string; assetKind: string; } export type DetachedAttributePrototypeKind = | { type: "OutputSocketSocket"; data: { name: string; kind: "ConfigurationInput" | "ConfigurationOutput"; }; } | { type: "InputSocketSocket"; data: { name: string; kind: "ConfigurationInput" | "ConfigurationOutput"; }; } | { type: "InputSocketProp"; data: { path: string; kind: PropKind } } | { type: "Prop"; data: { path: string; kind: PropKind } }; export interface DetachedAttributePrototype { id: string; funcId: FuncId; funcName: string; key: string | null; kind: FuncKind; context: DetachedAttributePrototypeKind; } export interface DetachedValidationPrototype { id: string; funcId: FuncId; funcName: string; args: unknown; link: string | null; propPath: string; propKind: PropKind; } export type SchemaVariantSaveRequest = Visibility & { code?: string } & { variant: Omit<SchemaVariant, "created_at" | "updated_at">; }; export type SchemaVariantCreateRequest = { name: string; color: string }; export type SchemaVariantCloneRequest = Visibility & { id: SchemaVariantId; name: string; }; export const schemaVariantDisplayName = (schemaVariant: SchemaVariant) => (schemaVariant.displayName ?? "").length === 0 ? schemaVariant.schemaName : schemaVariant.displayName; export const useAssetStore = (forceChangeSetId?: ChangeSetId) => { const changeSetStore = useChangeSetsStore(); let changeSetId: ChangeSetId | undefined; if (forceChangeSetId) { changeSetId = forceChangeSetId; } else { changeSetId = changeSetStore.selectedChangeSetId; } const visibility = { // changeSetId should not be empty if we are actually using this store // so we can give it a bad value and let it throw an error visibility_change_set_pk: changeSetId || "XXX", }; const workspaceStore = useWorkspacesStore(); const workspaceId = workspaceStore.selectedWorkspacePk; const changeSetsStore = useChangeSetsStore(); const selectedChangeSetId = changeSetsStore.selectedChangeSet?.id; const funcStore = useFuncStore(); const moduleStore = useModuleStore(); const routerStore = useRouterStore(); const authStore = useAuthStore(); const realtimeStore = useRealtimeStore(); let assetSaveDebouncer: ReturnType<typeof keyedDebouncer> | undefined; const API_PREFIX = [ "v2", "workspaces", { workspaceId }, "change-sets", { selectedChangeSetId }, "schema-variants", ]; return addStoreHooks( workspaceId, selectedChangeSetId, defineStore(`ws${workspaceId || "NONE"}/cs${changeSetId || "NONE"}/asset`, { state: () => ({ variantList: [] as SchemaVariant[], uninstalledVariantList: [] as UninstalledVariant[], executeSchemaVariantTaskId: undefined as string | undefined, executeSchemaVariantTaskRunning: false as boolean, executeSchemaVariantTaskError: undefined as string | undefined, editingFuncLatestCode: {} as Record<SchemaVariantId, string>, codeSaveIsDebouncing: false, // represents state of the left rail lists and all open editor tabs selectedSchemaVariants: [] as SchemaVariantId[], selectedFuncs: [] as FuncId[], detachmentWarnings: [] as { message: string; funcId: FuncId; kind?: FuncKind; }[], }), getters: { variantFromListById: (state) => _.keyBy(state.variantList, (a) => a.schemaVariantId), schemaVariants: (state) => state.variantList, selectedVariantId(state): SchemaVariantId | undefined { if (state.selectedSchemaVariants.length === 1) return state.selectedSchemaVariants[0]; else return undefined; }, selectedSchemaVariant(): SchemaVariant | undefined { if (this.selectedVariantId) return this.variantFromListById[this.selectedVariantId]; }, selectedSchemaVariantRecords(): SchemaVariant[] { return this.selectedSchemaVariants .map((id) => this.variantFromListById[id]) .filter(nonNullable); }, selectedFuncId(state): FuncId | undefined { if (state.selectedFuncs.length === 1) return state.selectedFuncs[0]; else return undefined; }, unlockedVariantIdForId: (state) => { type VariantsBySchemaId = Record<SchemaId, SchemaVariantId>; const unlockedVariantsBySchema: VariantsBySchemaId = state.variantList .filter((v) => !v.isLocked) .reduce((obj, v) => { obj[v.schemaId] = v.schemaVariantId; return obj; }, {} as VariantsBySchemaId); return state.variantList.reduce((obj, v) => { obj[v.schemaVariantId] = unlockedVariantsBySchema[v.schemaId]; return obj; }, {} as Record<SchemaVariantId, SchemaVariantId | undefined>); }, schemaVariantOptionsUnlocked: ( state, ): { label: string; value: string }[] => { return state.variantList .filter((v) => !v.isLocked) .map((sv) => ({ label: sv.displayName || sv.schemaName, value: sv.schemaVariantId, })); }, schemaVariantOptions: (state): { label: string; value: string }[] => { return state.variantList.map((sv) => ({ label: sv.displayName || sv.schemaName, value: sv.schemaVariantId, })); }, }, actions: { addSchemaVariantSelection(id: SchemaVariantId) { if (!this.selectedSchemaVariants.includes(id)) { this.selectedSchemaVariants.push(id); // we don't load schema variant here, because we aren't showing the editor this.syncSelectionIntoUrl(); this.selectedFuncs = []; } }, removeSchemaVariantSelection(id: SchemaVariantId) { const thisAssetIdx = this.selectedSchemaVariants.findIndex( (thisId) => thisId === id, ); if (thisAssetIdx !== -1) { this.selectedSchemaVariants.splice(thisAssetIdx, 1); this.syncSelectionIntoUrl(); } }, clearSchemaVariantSelection() { this.setFuncSelection(undefined); this.selectedSchemaVariants = []; this.syncSelectionIntoUrl(); }, setSchemaVariantSelection(id: SchemaVariantId) { this.setFuncSelection(undefined); if ( this.selectedSchemaVariants.length === 1 && this.selectedSchemaVariants[0] === id ) { return; } if (!this.selectedSchemaVariants.includes(id)) { this.selectedFuncs = []; } this.selectedSchemaVariants = [id]; this.syncSelectionIntoUrl(); const variant = this.variantFromListById[id]; if (variant?.assetFuncId) funcStore.FETCH_CODE(variant.assetFuncId); }, async setFuncSelection(id?: FuncId) { // ignore the old func selections and replace with one func or no funcs funcStore.selectedFuncId = id; if (id) { await funcStore.FETCH_CODE(id); this.selectedFuncs = [id]; } else { this.selectedFuncs = []; } this.syncSelectionIntoUrl(); }, async addFuncSelection(id: FuncId) { if (!this.selectedFuncs.includes(id)) this.selectedFuncs.push(id); await funcStore.FETCH_CODE(id); funcStore.selectedFuncId = id; this.syncSelectionIntoUrl(); }, removeFuncSelection(id: FuncId) { const idx = this.selectedFuncs.indexOf(id); if (idx !== -1) this.selectedFuncs.splice(idx, 1); this.syncSelectionIntoUrl(); }, syncSelectionIntoUrl(returnQuery?: boolean) { let selectedIds: string[] = []; selectedIds = _.map(this.selectedSchemaVariants, (id) => `a_${id}`); selectedIds = selectedIds.concat( _.map(this.selectedFuncs, (id) => `f_${id}`), ); const newQueryObj = { ...(selectedIds.length && { s: selectedIds.join("|") }), }; if (returnQuery) return newQueryObj; if (!_.isEqual(routerStore.currentRoute?.query, newQueryObj)) { routerStore.replace(changeSetId, { params: { ...routerStore.currentRoute?.params }, query: newQueryObj, }); } }, async syncUrlIntoSelection() { this.selectedSchemaVariants = []; this.selectedFuncs = []; funcStore.selectedFuncId = undefined; const ids = ((router.currentRoute.value.query?.s as string) || "") .split("|") .filter(Boolean); if (ids.length > 0) { /* eslint-disable @typescript-eslint/no-explicit-any */ const promises: Promise<any>[] = []; const fnIds = [] as FuncId[]; ids.sort().forEach((id) => { if (id.startsWith("a_")) { id = id.substring(2); this.selectedSchemaVariants.push(id); const variant = this.variantFromListById[id]; if (variant?.assetFuncId) promises.push(funcStore.FETCH_CODE(variant.assetFuncId)); } else if (id.startsWith("f_")) { id = id.substring(2); this.selectedFuncs.push(id); promises.push(funcStore.FETCH_CODE(id)); fnIds.push(id); } }); await Promise.all(promises); funcStore.selectedFuncId = fnIds[fnIds.length - 1]; } }, // MOCK DATA GENERATION generateMockColor() { return `#${_.sample([ "FF0000", "FFFF00", "FF00FF", "00FFFF", "FFAA00", "AAFF00", "00FFAA", "00AAFF", ])}`; }, replaceFuncForVariant( schemaVariantId: SchemaVariantId, oldFuncId: FuncId, newFuncId: FuncId, ) { // TODO assets should be represented in a single state variable, we shouldn't be doing this operation twice here // Copy bindings for list variant const listVariantIdx = this.variantList.findIndex( (v) => v.schemaVariantId === schemaVariantId, ); const listVariant = this.variantList[listVariantIdx]; if (!listVariant) { // eslint-disable-next-line no-console console.warn( `could not find variant ${schemaVariantId}, for binding with ${newFuncId} (previously ${oldFuncId})`, ); return; } const funcIdxForListVariant = listVariant.funcIds.findIndex( (f) => f === oldFuncId, ); if (funcIdxForListVariant === -1) { listVariant.funcIds.push(newFuncId); } else { listVariant.funcIds[funcIdxForListVariant] = newFuncId; } }, async CREATE_VARIANT(name: string) { if (changeSetStore.creatingChangeSet) throw new Error("race, wait until the change set is created"); if (changeSetId === changeSetStore.headChangeSetId) changeSetStore.creatingChangeSet = true; return new ApiRequest<SchemaVariant, SchemaVariantCreateRequest>({ method: "post", url: "/variant/create_variant", params: { ...visibility, name, color: this.generateMockColor(), }, onSuccess: (variant) => { const savedAssetIdx = this.variantList.findIndex( (a) => a.schemaVariantId === variant.schemaVariantId, ); if (savedAssetIdx === -1) this.variantList.push(variant); else this.variantList.splice(savedAssetIdx, 1, variant); }, }); }, async CLONE_VARIANT(schemaVariantId: SchemaVariantId, name: string) { if (changeSetStore.creatingChangeSet) throw new Error("race, wait until the change set is created"); if (changeSetStore.headSelected) changeSetStore.creatingChangeSet = true; return new ApiRequest<SchemaVariant, SchemaVariantCloneRequest>({ method: "post", keyRequestStatusBy: schemaVariantId, url: "/variant/clone_variant", params: { ...visibility, id: schemaVariantId, name, }, }); }, enqueueVariantSave( schemaVariant: SchemaVariant, code: string, debounce: boolean, ) { if (!debounce) { return this.SAVE_SCHEMA_VARIANT(schemaVariant, code); } this.codeSaveIsDebouncing = true; this.editingFuncLatestCode[schemaVariant.schemaVariantId] = code; // don't see how this should ever happen /* if (changeSetsStore.headSelected) return this.SAVE_SCHEMA_VARIANT(schemaVariant, code); */ if (!assetSaveDebouncer) { assetSaveDebouncer = keyedDebouncer((id: SchemaVariantId) => { const variant = this.variantFromListById[id]; if (!variant) return; const code = this.editingFuncLatestCode[variant.schemaVariantId]; if (!code) throw Error( `No asset code for variant ${variant.schemaVariantId}`, ); this.SAVE_SCHEMA_VARIANT(variant, code); this.codeSaveIsDebouncing = false; }, 1000); } const assetSaveFunc = assetSaveDebouncer( schemaVariant.schemaVariantId, ); if (assetSaveFunc) { assetSaveFunc(schemaVariant.schemaVariantId); } }, async SAVE_SCHEMA_VARIANT(schemaVariant: SchemaVariant, code?: string) { if (changeSetStore.creatingChangeSet) throw new Error("race, wait until the change set is created"); if (changeSetStore.headSelected) changeSetStore.creatingChangeSet = true; if (schemaVariant.isLocked) throw new Error( `cant save locked schema variant (${schemaVariant.displayName},${schemaVariant.schemaVariantId})`, ); return new ApiRequest< { success: boolean; assetFuncId: FuncId }, SchemaVariantSaveRequest >({ method: "post", keyRequestStatusBy: schemaVariant.schemaVariantId, url: "/variant/save_variant", params: { ...visibility, code, variant: schemaVariant, }, onSuccess: () => { if (code) { const f = funcStore.funcCodeById[schemaVariant.assetFuncId]; if (f) f.code = code; } }, }); }, async REGENERATE_VARIANT(schemaVariantId: SchemaVariantId) { if (changeSetStore.creatingChangeSet) throw new Error("race, wait until the change set is created"); if (changeSetStore.headSelected) changeSetStore.creatingChangeSet = true; this.detachmentWarnings = []; const variant = this.variantFromListById[schemaVariantId]; if (!variant) throw new Error(`${schemaVariantId} Variant does not exist`); return new ApiRequest<{ schemaVariantId: SchemaVariantId; }>({ method: "post", url: "/variant/regenerate_variant", keyRequestStatusBy: schemaVariantId, params: { ...visibility, variant, }, }); }, async LOAD_SCHEMA_VARIANT_LIST() { return new ApiRequest< { installed: SchemaVariant[]; uninstalled: UninstalledVariant[] }, Visibility >({ url: API_PREFIX, params: { ...visibility }, onSuccess: (response) => { this.variantList = response.installed; this.uninstalledVariantList = response.uninstalled; }, }); }, async CREATE_UNLOCKED_COPY(id: SchemaVariantId) { if (changeSetStore.creatingChangeSet) throw new Error("race, wait until the change set is created"); if (changeSetStore.headSelected) changeSetStore.creatingChangeSet = true; this.detachmentWarnings = []; return new ApiRequest<SchemaVariant>({ method: "post", url: API_PREFIX.concat([id]), keyRequestStatusBy: id, }); }, async DELETE_UNLOCKED_VARIANT(id: SchemaVariantId) { if (changeSetStore.creatingChangeSet) throw new Error("race, wait until the change set is created"); if (changeSetStore.headSelected) changeSetStore.creatingChangeSet = true; return new ApiRequest<SchemaVariant>({ method: "delete", url: API_PREFIX.concat([id]), keyRequestStatusBy: id, params: { // ...visibility, }, onSuccess: (variant) => { const deletedVariantIdx = this.variantList.findIndex( (a) => a.schemaVariantId === variant.schemaVariantId, ); if (deletedVariantIdx !== -1) this.variantList.splice(deletedVariantIdx, 1, variant); }, }); }, registerRequestsBegin(requestUlid: string, actionName: string) { realtimeStore.inflightRequests.set(requestUlid, actionName); }, registerRequestsEnd(requestUlid: string) { realtimeStore.inflightRequests.delete(requestUlid); }, }, async onActivated() { realtimeStore.subscribe(this.$id, `changeset/${changeSetId}`, [ { eventType: "SchemaVariantCreated", callback: (variant, metadata) => { if (metadata.change_set_id !== changeSetId) return; const savedAssetIdx = this.variantList.findIndex( (a) => a.schemaVariantId === variant.schemaVariantId, ); if (savedAssetIdx === -1) this.variantList.push(variant); else this.variantList.splice(savedAssetIdx, 1, variant); const actionWhichCreatedView = realtimeStore.inflightRequests.get( metadata.request_ulid, ); if ( metadata.actor !== "System" && metadata.actor.User === authStore.userPk && actionWhichCreatedView !== "CREATE_TEMPLATE_FUNC_FROM_COMPONENTS" ) { this.setSchemaVariantSelection(variant.schemaVariantId); } }, }, { eventType: "SchemaVariantDeleted", callback: (data) => { if (data.changeSetId !== changeSetId) return; const savedAssetIdx = this.variantList.findIndex( (a) => a.schemaVariantId === data.schemaVariantId, ); this.variantList.splice(savedAssetIdx, 1); }, }, { eventType: "SchemaVariantCloned", callback: async (data, metadata) => { // TODO: ship the actual variant if (data.changeSetId !== changeSetId) return; await Promise.all([ this.LOAD_SCHEMA_VARIANT_LIST(), moduleStore.SYNC(), ]); if ( metadata.actor !== "System" && metadata.actor.User === authStore.userPk ) { this.setSchemaVariantSelection(data.schemaVariantId); } }, }, { eventType: "SchemaVariantSaved", callback: (data) => { if (data.changeSetId !== changeSetId) return; const savedAssetIdx = this.variantList.findIndex( (a) => a.schemaVariantId === data.schemaVariantId, ); const savedAsset = this.variantList[savedAssetIdx]; if (savedAsset) { savedAsset.schemaName = data.name; savedAsset.category = data.category; savedAsset.color = data.color; savedAsset.componentType = data.componentType; savedAsset.displayName = data.displayName || null; this.variantList.splice(savedAssetIdx, 1, savedAsset); } const existingAsset = this.variantFromListById[data.schemaVariantId]; if (existingAsset) { existingAsset.schemaName = data.name; existingAsset.category = data.category; existingAsset.color = data.color; existingAsset.componentType = data.componentType; existingAsset.displayName = data.displayName || null; existingAsset.description = data.description || ""; existingAsset.link = data.link || null; } }, }, { eventType: "SchemaVariantUpdateFinished", callback: async (data) => { if (data.changeSetId !== changeSetId) return; this.LOAD_SCHEMA_VARIANT_LIST(); moduleStore.SYNC(); }, }, { eventType: "ModulesUpdated", callback: async (data) => { if (data.changeSetId !== changeSetId) return; this.LOAD_SCHEMA_VARIANT_LIST(); }, }, { eventType: "SchemaVariantUpdated", callback: (variant, metadata) => { if (metadata.change_set_id !== changeSetId) return; const savedAssetIdx = this.variantList.findIndex( (a) => a.schemaVariantId === variant.schemaVariantId, ); if (variant?.assetFuncId) funcStore.FETCH_CODE(variant.assetFuncId); if (savedAssetIdx !== -1) { this.variantList.splice(savedAssetIdx, 1, variant); } else this.variantList.push(variant); }, }, { eventType: "SchemaVariantReplaced", callback: ( { newSchemaVariant: newVariant, oldSchemaVariantId: oldVariantId, }, metadata, ) => { if (metadata.change_set_id !== changeSetId) return; const oldAssetIdx = this.variantList.findIndex( (a) => a.schemaVariantId === oldVariantId, ); if (oldAssetIdx !== -1) { this.variantList.splice(oldAssetIdx, 1, newVariant); } else this.variantList.push(newVariant); if (this.selectedSchemaVariants.includes(oldVariantId)) { if (this.selectedSchemaVariants.length === 1) { const selectedFuncs = _.clone(this.selectedFuncs); this.setSchemaVariantSelection(newVariant.schemaVariantId); // Right now you can't multi select functions but they're in an array, // so we just set the values in the array, which will leave the last one selected for (const funcId of selectedFuncs) { this.setFuncSelection(funcId); } } else { this.removeSchemaVariantSelection(oldVariantId); this.addSchemaVariantSelection(newVariant.schemaVariantId); } } }, }, { eventType: "ModuleImported", callback: (schemaVariants, metadata) => { if (metadata.change_set_id !== changeSetId) return; for (const variant of schemaVariants) { this.uninstalledVariantList = this.uninstalledVariantList.filter( (uninstalledVariant) => uninstalledVariant.schemaId !== variant.schemaId, ); const savedAssetIdx = this.variantList.findIndex( (a) => a.schemaId === variant.schemaId, ); if (savedAssetIdx !== -1) { this.variantList.splice(savedAssetIdx, 1, variant); } else this.variantList.push(variant); if ( metadata.actor !== "System" && metadata.actor.User === authStore.userPk ) { this.setSchemaVariantSelection(variant.schemaVariantId); } } }, }, { eventType: "ChangeSetApplied", callback: () => { this.LOAD_SCHEMA_VARIANT_LIST(); moduleStore.SYNC(); }, }, // For the async api endpoints { eventType: "AsyncError", callback: ({ id, error }) => { if (id === this.executeSchemaVariantTaskId) { this.executeSchemaVariantTaskRunning = false; this.executeSchemaVariantTaskId = undefined; let errorMessage = error; { const match = error.match( "function execution result failure:.*message=(.*?),", )?.[1]; if (match) { errorMessage = match; } } { const match = error.match( "func execution failure error: (.*)", )?.[1]; if (match) { errorMessage = match; } } this.executeSchemaVariantTaskError = errorMessage; } }, }, ]); const actionUnsub = this.$onAction(handleStoreError); return () => { actionUnsub(); realtimeStore.unsubscribe(this.$id); }; }, }), )(); };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server