import { notEmpty } from "@milaboratories/helpers"; import type { PlatformaExtended, PlatformaV3, PlatformaV1, PlatformaV2, BlockCodeKnownFeatureFlags, UiServices as AllUiServices, } from "@platforma-sdk/model"; import { getPlatformaApiVersion, unwrapResult, type BlockOutputsBase, type BlockModelInfo, } from "@platforma-sdk/model"; import type { App as VueApp, Component, Reactive } from "vue"; import { inject, markRaw, reactive } from "vue"; import { createAppV1, type BaseAppV1 } from "./internal/createAppV1"; import { createAppV2, type BaseAppV2 } from "./internal/createAppV2"; import { createAppV3, type BaseAppV3 } from "./internal/createAppV3"; import type { AppSettings, ExtendSettings, Routes } from "./types"; const pluginKey = Symbol("sdk-vue"); export const pluginDataKey = Symbol("plugin-data-access"); export function useSdkPlugin(): SdkPlugin { return inject(pluginKey)!; } export function useFeatureFlags() { const sdk = useSdkPlugin(); return sdk.featureFlags; } export function defineApp< Args = unknown, Outputs extends BlockOutputsBase = BlockOutputsBase, UiState = unknown, Href extends `/${string}` = `/${string}`, Extend extends ExtendSettings = ExtendSettings, >( platforma: PlatformaV1 & { blockModelInfo: BlockModelInfo; }, extendApp: (app: BaseAppV1) => Extend, settings?: AppSettings, ): SdkPluginV1; export function defineApp< Args = unknown, Outputs extends BlockOutputsBase = BlockOutputsBase, UiState = unknown, Href extends `/${string}` = `/${string}`, Extend extends ExtendSettings = ExtendSettings, >( platforma: PlatformaV2 & { blockModelInfo: BlockModelInfo; }, extendApp: (app: BaseAppV2) => Extend, settings?: AppSettings, ): SdkPluginV2; export function defineApp< Args = unknown, Outputs extends BlockOutputsBase = BlockOutputsBase, UiState = unknown, Href extends `/${string}` = `/${string}`, Extend extends ExtendSettings = ExtendSettings, >( platforma: PlatformaExtended< PlatformaV1 | PlatformaV2 >, // extendApp: (app: any) => Extend, settings: AppSettings = {}, ): | SdkPluginV1 | SdkPluginV2 { let app: | AppV1 | AppV2 | undefined = undefined; const runtimeApiVersion = platforma.apiVersion ?? 1; // undefined means 1 (backward compatibility) const blockRequestedApiVersion = getPlatformaApiVersion(); const loadApp = async () => { if (blockRequestedApiVersion !== runtimeApiVersion) { throw new Error(`Block requested API version ${blockRequestedApiVersion} but runtime API version is ${runtimeApiVersion}. Please update the desktop app to use the latest API version.`); } if (platforma.apiVersion === undefined || platforma.apiVersion === 1) { await platforma.loadBlockState().then((state) => { plugin.loaded = true; const baseApp = createAppV1(state, platforma, settings); const localState = extendApp(baseApp); const routes = Object.fromEntries( Object.entries(localState.routes as Routes).map(([href, component]) => { const c = typeof component === "function" ? component() : component; return [href, markRaw(c as Component)]; }), ); app = Object.assign(baseApp, { ...localState, getRoute(href: Href): Component | undefined { return routes[href]; }, } as unknown as AppV1); }); } else if (platforma.apiVersion === 2) { await platforma.loadBlockState().then((stateOrError) => { const state = unwrapResult(stateOrError); plugin.loaded = true; const baseApp = createAppV2(state, platforma, settings); const localState = extendApp(baseApp); const routes = Object.fromEntries( Object.entries(localState.routes as Routes).map(([href, component]) => { const c = typeof component === "function" ? component() : component; return [href, markRaw(c as Component)]; }), ); app = Object.assign(baseApp, { ...localState, getRoute(href: Href): Component | undefined { return routes[href]; }, } as unknown as AppV2); }); } }; const plugin = reactive({ apiVersion: platforma.apiVersion ?? 1, featureFlags: platforma.blockModelInfo.featureFlags, loaded: false, error: undefined as unknown, useApp() { return notEmpty(app, "App is not loaded") as | AppV1 | AppV2; }, install(app: VueApp) { app.provide(pluginKey, this); loadApp().catch((err) => { console.error("load initial state error", err); plugin.error = err; }); }, }); return plugin as | SdkPluginV1 | SdkPluginV2; } export function defineAppV3< Data = unknown, Args = unknown, Outputs extends BlockOutputsBase = BlockOutputsBase, Href extends `/${string}` = `/${string}`, Plugins extends Record = Record, UiServices extends Partial = Partial, Extend extends ExtendSettings = ExtendSettings, >( platforma: PlatformaV3 & { blockModelInfo: BlockModelInfo; }, extendApp: (app: BaseAppV3) => Extend, settings: AppSettings = {}, ): SdkPluginV3 { let app: AppV3 | undefined = undefined; // Captured during install() so V3 can provide plugin data access after async load let vueAppInstance: VueApp | undefined; const runtimeApiVersion = 3; const blockRequestedApiVersion = getPlatformaApiVersion(); const loadApp = async () => { if (blockRequestedApiVersion !== runtimeApiVersion) { throw new Error(`Block requested API version ${blockRequestedApiVersion} but runtime API version is ${runtimeApiVersion}. Please update the desktop app to use the latest API version.`); } await platforma.loadBlockState().then((stateOrError) => { const state = unwrapResult(stateOrError); plugin.loaded = true; const { app: baseApp, pluginAccess } = createAppV3< Data, Args, Outputs, Href, Plugins, UiServices >(state, platforma, settings); if (!vueAppInstance) { throw new Error( "Plugin data injection failed: Vue app instance not captured during install()", ); } vueAppInstance.provide(pluginDataKey, pluginAccess); const localState = extendApp(baseApp); const routes = Object.fromEntries( Object.entries(localState.routes as Routes).map(([href, component]) => { const c = typeof component === "function" ? component() : component; return [href, markRaw(c as Component)]; }), ); app = Object.assign(baseApp, { ...localState, getRoute(href: Href): Component | undefined { return routes[href]; }, } as AppV3); }); }; const plugin = reactive({ apiVersion: 3, featureFlags: platforma.blockModelInfo.featureFlags, loaded: false, error: undefined, useApp() { return notEmpty(app, "App is not loaded") as AppV3< Data, Args, Outputs, PageHref, Plugins, Extend, UiServices >; }, install(app: VueApp) { vueAppInstance = app; app.provide(pluginKey, this); loadApp().catch((err) => { console.error("load initial state error", err); plugin.error = err; }); }, }); return plugin as SdkPluginV3; } export type AppV1< Args = unknown, Outputs extends BlockOutputsBase = BlockOutputsBase, UiState = unknown, Href extends `/${string}` = `/${string}`, Local extends ExtendSettings = ExtendSettings, > = BaseAppV1 & Reactive> & { getRoute(href: Href): Component | undefined }; export type AppV2< Args = unknown, Outputs extends BlockOutputsBase = BlockOutputsBase, UiState = unknown, Href extends `/${string}` = `/${string}`, Local extends ExtendSettings = ExtendSettings, > = BaseAppV2 & Reactive> & { getRoute(href: Href): Component | undefined }; export type AppV3< Data = unknown, Args = unknown, Outputs extends BlockOutputsBase = NonNullable, Href extends `/${string}` = `/${string}`, Plugins extends Record = Record, Local extends ExtendSettings = ExtendSettings, UiServices extends Partial = Partial, > = BaseAppV3 & Reactive> & { getRoute(href: Href): Component | undefined }; // --------------------------------------------------------------------------- // SdkPlugin types // --------------------------------------------------------------------------- export type SdkPluginV1< Args = unknown, Outputs extends BlockOutputsBase = BlockOutputsBase, UiState = unknown, Href extends `/${string}` = `/${string}`, Local extends ExtendSettings = ExtendSettings, > = { apiVersion: 1; featureFlags: BlockCodeKnownFeatureFlags; loaded: boolean; error: unknown; useApp(): AppV1; install(app: VueApp): void; }; export type SdkPluginV2< Args = unknown, Outputs extends BlockOutputsBase = BlockOutputsBase, UiState = unknown, Href extends `/${string}` = `/${string}`, Local extends ExtendSettings = ExtendSettings, > = { apiVersion: 2; featureFlags: BlockCodeKnownFeatureFlags; loaded: boolean; error: unknown; useApp(): AppV2; install(app: VueApp): void; }; export type SdkPluginV3< Data = unknown, Args = unknown, Outputs extends BlockOutputsBase = BlockOutputsBase, Href extends `/${string}` = `/${string}`, Plugins extends Record = Record, Local extends ExtendSettings = ExtendSettings, UiServices extends Partial = Partial, > = { apiVersion: 3; featureFlags: BlockCodeKnownFeatureFlags; loaded: boolean; error: unknown; useApp(): AppV3< Data, Args, Outputs, PageHref, Plugins, Local, UiServices >; install(app: VueApp): void; }; export type SdkPlugin = SdkPluginV1 | SdkPluginV2 | SdkPluginV3;