diff --git a/dist/js/AdeFactory.d.ts b/dist/js/AdeFactory.d.ts new file mode 100644 index 0000000..b3022da --- /dev/null +++ b/dist/js/AdeFactory.d.ts @@ -0,0 +1,58 @@ +import { type ApplicationName } from "@exabyte-io/application-flavors.js"; +import type { ApplicationSchemaBase } from "@mat3ra/esse/dist/js/types"; +import Application from "./application"; +import Executable from "./executable"; +import Flavor from "./flavor"; +import Template from "./template"; +type ApplicationVersion = { + [build: string]: ApplicationSchemaBase; +}; +type ApplicationTreeItem = { + defaultVersion: string; + [version: string]: ApplicationVersion | string; +}; +export type CreateApplicationConfig = { + name: ApplicationName; + version?: string | null; + build?: string; +}; +type ApplicationTree = Partial>; +export default class AdeFactory { + static applicationsTree?: ApplicationTree; + static applicationsArray?: ApplicationSchemaBase[]; + static createApplication({ name, version, build }: CreateApplicationConfig): Application; + static getUniqueAvailableApplicationNames(): ApplicationName[]; + /** + * @summary Return all applications as both a nested object of Applications and an array of config objects + * @returns containing applications and applicationConfigs + */ + static getAllApplications(): { + applicationsTree: Partial>; + applicationsArray: ApplicationSchemaBase[]; + }; + /** + * @summary Get an application from the constructed applications + * @param name name of the application + * @param version version of the application (optional, defaults to defaultVersion) + * @param build the build to use (optional, defaults to Default) + * @return an application + */ + static getApplicationConfig({ name, version, build, }: CreateApplicationConfig): ApplicationSchemaBase | null; + static getExecutables({ name, version }: { + name: ApplicationName; + version?: string; + }): Executable[]; + static getExecutableByName(appName: ApplicationName, execName?: string): Executable; + static getExecutableByConfig(appName: ApplicationName, config?: { + name: string; + }): Executable; + static getExecutableFlavors(executable: Executable): Flavor[]; + static getFlavorByName(executable: Executable, name?: string): Flavor | undefined; + static getFlavorByConfig(executable: Executable, config?: { + name: string; + }): Flavor | undefined; + static getInputAsTemplates(flavor: Flavor): Template[]; + static getInputAsRenderedTemplates(flavor: Flavor, context: Record): import("@mat3ra/esse/dist/js/esse/types").AnyObject[]; + static getAllFlavorsForApplication(appName: ApplicationName, version?: string): Flavor[]; +} +export {}; diff --git a/dist/js/AdeFactory.js b/dist/js/AdeFactory.js new file mode 100644 index 0000000..b266e0e --- /dev/null +++ b/dist/js/AdeFactory.js @@ -0,0 +1,142 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const application_flavors_js_1 = require("@exabyte-io/application-flavors.js"); +const object_1 = require("@mat3ra/code/dist/js/utils/object"); +const application_1 = __importDefault(require("./application")); +const executable_1 = __importDefault(require("./executable")); +const flavor_1 = __importDefault(require("./flavor")); +const template_1 = __importDefault(require("./template")); +class AdeFactory { + static createApplication({ name, version = null, build = "Default" }) { + const staticConfig = AdeFactory.getApplicationConfig({ name, version, build }); + return new application_1.default({ ...staticConfig, name, version, build }); + } + static getUniqueAvailableApplicationNames() { + return application_flavors_js_1.allApplications; + } + /** + * @summary Return all applications as both a nested object of Applications and an array of config objects + * @returns containing applications and applicationConfigs + */ + static getAllApplications() { + if (this.applicationsTree && this.applicationsArray) { + return { + applicationsTree: this.applicationsTree, + applicationsArray: this.applicationsArray, + }; + } + const applicationsTree = {}; + const applicationsArray = []; + application_flavors_js_1.allApplications.forEach((appName) => { + const { versions, defaultVersion, build = "Default", ...appData } = (0, application_flavors_js_1.getAppData)(appName); + const appTreeItem = { defaultVersion }; + versions.forEach((options) => { + const { version } = options; + const appVersion = version in appTreeItem && typeof appTreeItem[version] === "object" + ? appTreeItem[version] + : {}; + appTreeItem[version] = appVersion; + const applicationConfig = { ...appData, build, ...options }; + appVersion[build] = applicationConfig; + applicationsArray.push(applicationConfig); + }); + applicationsTree[appName] = appTreeItem; + }); + this.applicationsTree = applicationsTree; + this.applicationsArray = applicationsArray; + return { + applicationsTree, + applicationsArray: this.applicationsArray, + }; + } + /** + * @summary Get an application from the constructed applications + * @param name name of the application + * @param version version of the application (optional, defaults to defaultVersion) + * @param build the build to use (optional, defaults to Default) + * @return an application + */ + static getApplicationConfig({ name, version = null, build = "Default", }) { + var _a; + const { applicationsTree } = this.getAllApplications(); + const app = applicationsTree[name]; + if (!app) { + throw new Error(`Application ${name} not found`); + } + const version_ = version || app.defaultVersion; + const appVersion = app[version_]; + if (!appVersion || typeof appVersion === "string") { + console.log(`Version ${version_} not available for ${name} !`); + } + if (typeof appVersion === "string") { + return null; + } + return (_a = appVersion[build]) !== null && _a !== void 0 ? _a : null; + } + static getExecutables({ name, version }) { + const tree = (0, application_flavors_js_1.getAppTree)(name); + return Object.keys(tree) + .filter((key) => { + const executable = tree[key]; + const { supportedApplicationVersions } = executable; + return (!supportedApplicationVersions || + (version && supportedApplicationVersions.includes(version))); + }) + .map((key) => new executable_1.default({ ...tree[key], name: key })); + } + static getExecutableByName(appName, execName) { + const appTree = (0, application_flavors_js_1.getAppTree)(appName); + Object.entries(appTree).forEach(([name, exec]) => { + exec.name = name; + }); + const config = execName + ? appTree[execName] + : (0, object_1.getOneMatchFromObject)(appTree, "isDefault", true); + return new executable_1.default(config); + } + // TODO: remove this method and use getApplicationExecutableByName directly + static getExecutableByConfig(appName, config) { + return this.getExecutableByName(appName, config === null || config === void 0 ? void 0 : config.name); + } + static getExecutableFlavors(executable) { + const flavorsTree = executable.prop("flavors", {}); + return Object.keys(flavorsTree).map((key) => { + return new flavor_1.default({ + ...flavorsTree[key], + name: key, + }); + }); + } + static getFlavorByName(executable, name) { + return this.getExecutableFlavors(executable).find((flavor) => name ? flavor.name === name : flavor.isDefault); + } + static getFlavorByConfig(executable, config) { + return this.getFlavorByName(executable, config === null || config === void 0 ? void 0 : config.name); + } + // flavors + static getInputAsTemplates(flavor) { + const appName = flavor.prop("applicationName", ""); + const execName = flavor.prop("executableName", ""); + return flavor.input.map((input) => { + const inputName = input.templateName || input.name; + const filtered = application_flavors_js_1.allTemplates.filter((temp) => temp.applicationName === appName && + temp.executableName === execName && + temp.name === inputName); + if (filtered.length !== 1) { + console.log(`found ${filtered.length} templates for app=${appName} exec=${execName} name=${inputName} expected 1`); + } + return new template_1.default({ ...filtered[0], name: input.name }); + }); + } + static getInputAsRenderedTemplates(flavor, context) { + return this.getInputAsTemplates(flavor).map((template) => template.getRenderedJSON(context)); + } + static getAllFlavorsForApplication(appName, version) { + const allExecutables = this.getExecutables({ name: appName, version }); + return allExecutables.flatMap((executable) => this.getExecutableFlavors(executable)); + } +} +exports.default = AdeFactory; diff --git a/dist/js/ApplicationFlavorsFactory.d.ts b/dist/js/ApplicationFlavorsFactory.d.ts new file mode 100644 index 0000000..956993a --- /dev/null +++ b/dist/js/ApplicationFlavorsFactory.d.ts @@ -0,0 +1,18 @@ +import Application from "./application"; +import Executable from "./executable"; +import Flavor from "./flavor"; +import { type CreateApplicationConfig } from "./tree"; +export declare class AdeFactory { + static createApplication({ name, version, build }: CreateApplicationConfig): Application; + static getApplicationExecutables(application: Application): Executable[]; + static getApplicationExecutableByName(application: Application, name?: string): Executable; + static getApplicationExecutableByConfig(application: Application, config?: { + name: string; + }): Executable; + static getFlavorsByApplicationVersion(executable: Executable, version: string): Flavor[]; + static getExecutableFlavors(executable: Executable): Flavor[]; + static getFlavorByName(executable: Executable, name?: string): Flavor | undefined; + static getFlavorByConfig(executable: Executable, config?: { + name: string; + }): Flavor | undefined; +} diff --git a/dist/js/ApplicationFlavorsFactory.js b/dist/js/ApplicationFlavorsFactory.js new file mode 100644 index 0000000..49a055e --- /dev/null +++ b/dist/js/ApplicationFlavorsFactory.js @@ -0,0 +1,66 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AdeFactory = void 0; +const application_flavors_js_1 = require("@exabyte-io/application-flavors.js"); +const object_1 = require("@mat3ra/code/dist/js/utils/object"); +const application_1 = __importDefault(require("./application")); +const executable_1 = __importDefault(require("./executable")); +const flavor_1 = __importDefault(require("./flavor")); +class AdeFactory { + // applications + static createApplication({ name, version = null, build = "Default" }) { + return new application_1.default({ name, version, build }); + } + static getApplicationExecutables(application) { + const tree = (0, application_flavors_js_1.getAppTree)(application.name); + return Object.keys(tree) + .filter((key) => { + const { supportedApplicationVersions } = tree[key]; + return (!supportedApplicationVersions || + supportedApplicationVersions.includes(application.version)); + }) + .map((key) => new executable_1.default({ ...tree[key], name: key })); + } + static getApplicationExecutableByName(application, name) { + const appTree = (0, application_flavors_js_1.getAppTree)(application.name); + Object.entries(appTree).forEach(([name, exec]) => { + exec.name = name; + }); + const config = name + ? appTree[name] + : (0, object_1.getOneMatchFromObject)(appTree, "isDefault", true); + return new executable_1.default(config); + } + // TODO: remove this method and use getApplicationExecutableByName directly + static getApplicationExecutableByConfig(application, config) { + return this.getApplicationExecutableByName(application, config === null || config === void 0 ? void 0 : config.name); + } + // executables + static getFlavorsByApplicationVersion(executable, version) { + const filteredFlavors = this.getExecutableFlavors(executable).filter((flavor) => { + const supportedApplicationVersions = flavor.prop("supportedApplicationVersions"); + return !supportedApplicationVersions || supportedApplicationVersions.includes(version); + }); + return filteredFlavors; + } + static getExecutableFlavors(executable) { + const flavorsTree = executable.prop("flavors", {}); + return Object.keys(flavorsTree).map((key) => { + return new flavor_1.default({ + ...flavorsTree[key], + name: key, + executable, + }); + }); + } + static getFlavorByName(executable, name) { + return this.getExecutableFlavors(executable).find((flavor) => name ? flavor.name === name : flavor.isDefault); + } + static getFlavorByConfig(executable, config) { + return this.getFlavorByName(executable, config === null || config === void 0 ? void 0 : config.name); + } +} +exports.AdeFactory = AdeFactory; diff --git a/dist/js/application.d.ts b/dist/js/application.d.ts index 1f03143..7830453 100644 --- a/dist/js/application.d.ts +++ b/dist/js/application.d.ts @@ -1,10 +1,8 @@ import { NamedDefaultableInMemoryEntity } from "@mat3ra/code/dist/js/entity"; import type { Constructor } from "@mat3ra/code/dist/js/utils/types"; import { type ApplicationMixin } from "./applicationMixin"; -import { type CreateApplicationConfig } from "./tree"; type Base = typeof NamedDefaultableInMemoryEntity & Constructor; declare const Application_base: Base; export default class Application extends Application_base { - constructor(config: CreateApplicationConfig); } export {}; diff --git a/dist/js/application.js b/dist/js/application.js index 2043743..129810e 100644 --- a/dist/js/application.js +++ b/dist/js/application.js @@ -2,12 +2,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); const entity_1 = require("@mat3ra/code/dist/js/entity"); const applicationMixin_1 = require("./applicationMixin"); -const tree_1 = require("./tree"); class Application extends entity_1.NamedDefaultableInMemoryEntity { - constructor(config) { - const staticConfig = (0, tree_1.getApplicationConfig)(config); - super({ ...staticConfig, ...config }); - } } exports.default = Application; (0, applicationMixin_1.applicationMixin)(Application.prototype); diff --git a/dist/js/applicationMixin.d.ts b/dist/js/applicationMixin.d.ts index 3edc4d4..7b2a8a9 100644 --- a/dist/js/applicationMixin.d.ts +++ b/dist/js/applicationMixin.d.ts @@ -1,45 +1,33 @@ +import { type ApplicationName } from "@exabyte-io/application-flavors.js"; import type { InMemoryEntity } from "@mat3ra/code/dist/js/entity"; import type { DefaultableInMemoryEntity } from "@mat3ra/code/dist/js/entity/mixins/DefaultableMixin"; import type { NamedInMemoryEntity } from "@mat3ra/code/dist/js/entity/mixins/NamedEntityMixin"; import type { Constructor } from "@mat3ra/code/dist/js/utils/types"; import Executable from "./executable"; -import { CreateApplicationConfig } from "./tree"; type Base = InMemoryEntity & NamedInMemoryEntity & DefaultableInMemoryEntity; -export declare function applicationMixin(item: Base): Base; export type BaseConstructor = Constructor & { constructCustomExecutable?: (config: object) => Executable; }; -export declare function applicationStaticMixin(Application: T): ApplicationStaticProperties; -export type ApplicationStaticProperties = { - defaultConfig: { - name: string; - shortName: string; - version: string; - summary: string; - build: string; - }; - create: (config: CreateApplicationConfig) => Base; - createFromNameVersionBuild: (config: CreateApplicationConfig) => Base; - getUniqueAvailableNames: () => string[]; - constructExecutable: (config: object) => Executable; -}; -export type ApplicationConstructor = Constructor & ApplicationStaticProperties; +export type ApplicationConstructor = Constructor & ApplicationStaticMixin; export type ApplicationMixin = { - defaultExecutable: Executable; summary: string | undefined; version: string; build: string | undefined; shortName: string; - executables: Executable[]; + name: ApplicationName; hasAdvancedComputeOptions: boolean; isLicensed: boolean; isUsingMaterial: boolean; - getExecutableByName: (name?: string) => Executable; - getExecutableByConfig: (config?: { +}; +export type ApplicationStaticMixin = { + defaultConfig: { name: string; - }) => Executable; - getExecutables: () => Executable[]; - getBuilds: () => string[]; - getVersions: () => string[]; + shortName: string; + version: string; + summary: string; + build: string; + }; }; +export declare function applicationMixin(item: Base): Base; +export declare function applicationStaticMixin(Application: T): ApplicationStaticMixin; export {}; diff --git a/dist/js/applicationMixin.js b/dist/js/applicationMixin.js index 55f380d..06c8f96 100644 --- a/dist/js/applicationMixin.js +++ b/dist/js/applicationMixin.js @@ -1,20 +1,10 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; Object.defineProperty(exports, "__esModule", { value: true }); exports.applicationMixin = applicationMixin; exports.applicationStaticMixin = applicationStaticMixin; -const application_flavors_js_1 = require("@exabyte-io/application-flavors.js"); -const lodash_1 = __importDefault(require("lodash")); -const executable_1 = __importDefault(require("./executable")); -const tree_1 = require("./tree"); function applicationMixin(item) { - // @ts-ignore + // @ts-expect-error const properties = { - get defaultExecutable() { - return this.getExecutableByName(); - }, get summary() { return this.prop("summary"); }, @@ -27,21 +17,6 @@ function applicationMixin(item) { get shortName() { return this.prop("shortName", this.name); }, - get executables() { - const tree = (0, application_flavors_js_1.getAppTree)(this.name); - return Object.keys(tree) - .filter((key) => { - const { supportedApplicationVersions } = tree[key]; - return (!supportedApplicationVersions || - supportedApplicationVersions.includes(this.version)); - }) - .map((key) => { - return this.constructor.constructExecutable({ - ...tree[key], - name: key, - }); - }); - }, get hasAdvancedComputeOptions() { return this.prop("hasAdvancedComputeOptions", false); }, @@ -52,31 +27,6 @@ function applicationMixin(item) { const materialUsingApplications = ["vasp", "nwchem", "espresso", "exabyteml"]; return materialUsingApplications.includes(this.name); }, - getExecutableByName(name) { - return this.constructor.constructExecutable((0, tree_1.getExecutableConfig)({ - appName: this.name, - execName: name, - })); - }, - getExecutableByConfig(config) { - return config ? this.getExecutableByName(config.name) : this.defaultExecutable; - }, - getExecutables() { - return this.executables; - }, - getBuilds() { - const data = (0, application_flavors_js_1.getAppData)(this.name); - const { versions } = data; - const builds = ["Default"]; - versions.map((v) => v.build && builds.push(v.build)); - return lodash_1.default.uniq(builds); - }, - getVersions() { - const data = (0, application_flavors_js_1.getAppData)(this.name); - const { versions } = data; - const these = versions.map((v) => v.version); - return lodash_1.default.uniq(these); - }, }; Object.defineProperties(item, Object.getOwnPropertyDescriptors(properties)); return item; @@ -92,21 +42,6 @@ function applicationStaticMixin(Application) { build: "Default", }; }, - create(config) { - return this.createFromNameVersionBuild(config); - }, - createFromNameVersionBuild({ name, version = null, build = "Default", }) { - return new Application({ name, version, build }); - }, - getUniqueAvailableNames() { - return application_flavors_js_1.allApplications; - }, - constructExecutable(config) { - if (this.constructCustomExecutable) { - return this.constructCustomExecutable(config); - } - return new executable_1.default(config); - }, }; Object.defineProperties(Application, Object.getOwnPropertyDescriptors(properties)); return properties; diff --git a/dist/js/executable.js b/dist/js/executable.js index 878cfaa..54e8df5 100644 --- a/dist/js/executable.js +++ b/dist/js/executable.js @@ -9,4 +9,3 @@ exports.default = Executable; // Apply mixins (0, RuntimeItemsMixin_1.runtimeItemsMixin)(Executable.prototype); (0, executableMixin_1.executableMixin)(Executable.prototype); -(0, executableMixin_1.executableStaticMixin)(Executable); diff --git a/dist/js/executableMixin.d.ts b/dist/js/executableMixin.d.ts index c14ea35..a6d3d87 100644 --- a/dist/js/executableMixin.d.ts +++ b/dist/js/executableMixin.d.ts @@ -6,27 +6,11 @@ import type { FlavorMixin } from "./flavorMixin"; type BaseFlavor = FlavorMixin & NamedInMemoryEntity & InMemoryEntity; type Base = InMemoryEntity & NamedInMemoryEntity & DefaultableInMemoryEntity; export declare function executableMixin(item: Base): Base; -export type CreateExecutableConfig = { - name: string; - flavors?: Record; -}; export type BaseConstructor = Constructor & { constructCustomFlavor?: (config: object) => BaseFlavor; }; -export declare function executableStaticMixin(Executable: T): ExecutableStaticProperties; -export type ExecutableStaticProperties = { - constructFlavor: (config: object) => BaseFlavor; -}; export type ExecutableMixin = { - defaultFlavor: BaseFlavor; - flavorsTree: Record; - flavors: BaseFlavor[]; - flavorsFromTree: BaseFlavor[]; - getFlavorByName: (name?: string) => BaseFlavor; - getFlavorByConfig: (config?: { - name: string; - }) => BaseFlavor; - getFlavorsByApplicationVersion: (version: string) => BaseFlavor[]; toJSON: (exclude?: string[]) => object; + applicationId: string[]; }; export {}; diff --git a/dist/js/executableMixin.js b/dist/js/executableMixin.js index f42b949..84ecadb 100644 --- a/dist/js/executableMixin.js +++ b/dist/js/executableMixin.js @@ -1,50 +1,14 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; Object.defineProperty(exports, "__esModule", { value: true }); exports.executableMixin = executableMixin; -exports.executableStaticMixin = executableStaticMixin; -const flavor_1 = __importDefault(require("./flavor")); function executableMixin(item) { - // @ts-ignore + // @ts-expect-error const properties = { - get defaultFlavor() { - return this.getFlavorByName(); + get applicationId() { + return this.prop("applicationId", []); }, - get flavorsTree() { - return this.prop("flavors", {}); - }, - get flavors() { - const tree = this.flavorsTree || {}; - return Object.keys(tree).map((key) => { - return this.constructor.constructFlavor({ - ...tree[key], - name: key, - executable: this, - }); - }); - }, - get flavorsFromTree() { - return Object.keys(this.flavorsTree).map((key) => { - return this.constructor.constructFlavor({ - ...this.flavorsTree[key], - name: key, - }); - }); - }, - getFlavorByName(name) { - return this.getEntityByName(this.flavors, "flavor", name || ""); - }, - getFlavorByConfig(config) { - return config ? this.getFlavorByName(config.name) : this.defaultFlavor; - }, - getFlavorsByApplicationVersion(version) { - const filteredFlavors = this.flavors.filter((flavor) => { - const supportedApplicationVersions = flavor.prop("supportedApplicationVersions"); - return (!supportedApplicationVersions || supportedApplicationVersions.includes(version)); - }); - return filteredFlavors; + set applicationId(value) { + this.setProp("applicationId", value); }, toJSON(exclude = []) { const thisProto = Object.getPrototypeOf(this); @@ -56,15 +20,3 @@ function executableMixin(item) { Object.defineProperties(item, Object.getOwnPropertyDescriptors(properties)); return item; } -function executableStaticMixin(Executable) { - const properties = { - constructFlavor(config) { - if (this.constructCustomFlavor) { - return this.constructCustomFlavor(config); - } - return new flavor_1.default(config); - }, - }; - Object.defineProperties(Executable, Object.getOwnPropertyDescriptors(properties)); - return properties; -} diff --git a/dist/js/flavor.d.ts b/dist/js/flavor.d.ts index cd8d346..4134aff 100644 --- a/dist/js/flavor.d.ts +++ b/dist/js/flavor.d.ts @@ -1,7 +1,8 @@ import { NamedDefaultableInMemoryEntity } from "@mat3ra/code/dist/js/entity"; +import { type RuntimeItemsInMemoryEntity } from "@mat3ra/code/dist/js/entity/mixins/RuntimeItemsMixin"; import type { Constructor } from "@mat3ra/code/dist/js/utils/types"; import { type FlavorMixin } from "./flavorMixin"; -type Base = typeof NamedDefaultableInMemoryEntity & Constructor; +type Base = typeof NamedDefaultableInMemoryEntity & Constructor & Constructor; declare const Flavor_base: Base; export default class Flavor extends Flavor_base { } diff --git a/dist/js/flavorMixin.d.ts b/dist/js/flavorMixin.d.ts index e644e13..cea9640 100644 --- a/dist/js/flavorMixin.d.ts +++ b/dist/js/flavorMixin.d.ts @@ -1,14 +1,15 @@ import type { InMemoryEntity } from "@mat3ra/code/dist/js/entity"; import type { NamedInMemoryEntity } from "@mat3ra/code/dist/js/entity/mixins/NamedEntityMixin"; -import type { TemplateBase, TemplateMixin } from "./templateMixin"; +import type { FlavorSchema } from "@mat3ra/esse/dist/js/types"; type Base = InMemoryEntity & NamedInMemoryEntity; +type Input = Required["input"]; export type FlavorMixin = { - input: { - name: string; - templateName?: string; - }[]; - inputAsTemplates: (TemplateMixin & TemplateBase)[]; + input: Input; disableRenderMaterials: boolean; + executableId: string; + executableName: string; + applicationName: string; + supportedApplicationVersions?: string[]; getInputAsRenderedTemplates: (context: Record) => Record[]; }; export declare function flavorMixin(item: Base): FlavorMixin & InMemoryEntity & { diff --git a/dist/js/flavorMixin.js b/dist/js/flavorMixin.js index bb60b82..f196bb9 100644 --- a/dist/js/flavorMixin.js +++ b/dist/js/flavorMixin.js @@ -1,30 +1,28 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; Object.defineProperty(exports, "__esModule", { value: true }); exports.flavorMixin = flavorMixin; -const template_1 = __importDefault(require("./template")); +// TODO: should we add fields from esse schema (executableId, executableName, applicationName)? function flavorMixin(item) { - // @ts-ignore + // @ts-expect-error const properties = { get input() { return this.prop("input", []); }, - // TODO : prevent this from running in client - get inputAsTemplates() { - return this.input.map((input) => { - const template = template_1.default.fromFlavor(this.prop("applicationName", ""), this.prop("executableName", ""), input.templateName || input.name); - template.name = input.name; - return template; - }); - }, - getInputAsRenderedTemplates(context) { - return this.inputAsTemplates.map((t) => t.getRenderedJSON(context)); - }, get disableRenderMaterials() { return this.prop("isMultiMaterial", false); }, + get executableId() { + return this.prop("executableId", ""); + }, + get executableName() { + return this.prop("executableName", ""); + }, + get applicationName() { + return this.prop("applicationName", ""); + }, + get supportedApplicationVersions() { + return this.prop("supportedApplicationVersions"); + }, }; Object.defineProperties(item, Object.getOwnPropertyDescriptors(properties)); return properties; diff --git a/dist/js/index.d.ts b/dist/js/index.d.ts index 90f4198..ad4042e 100644 --- a/dist/js/index.d.ts +++ b/dist/js/index.d.ts @@ -3,5 +3,4 @@ import Application from "./application"; import Executable from "./executable"; import Flavor from "./flavor"; import Template from "./template"; -import { getAllApplications, getApplication } from "./tree"; -export { Application, Executable, Flavor, Template, getAllApplications, getApplication, allApplications, allTemplates, allowedResults, allowedMonitors, }; +export { Application, Executable, Flavor, Template, allApplications, allTemplates, allowedResults, allowedMonitors, }; diff --git a/dist/js/index.js b/dist/js/index.js index 1ff1b34..6a4c0e7 100644 --- a/dist/js/index.js +++ b/dist/js/index.js @@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.allowedMonitors = exports.allowedResults = exports.allTemplates = exports.allApplications = exports.getApplication = exports.getAllApplications = exports.Template = exports.Flavor = exports.Executable = exports.Application = void 0; +exports.allowedMonitors = exports.allowedResults = exports.allTemplates = exports.allApplications = exports.Template = exports.Flavor = exports.Executable = exports.Application = void 0; const application_flavors_js_1 = require("@exabyte-io/application-flavors.js"); Object.defineProperty(exports, "allApplications", { enumerable: true, get: function () { return application_flavors_js_1.allApplications; } }); Object.defineProperty(exports, "allowedMonitors", { enumerable: true, get: function () { return application_flavors_js_1.allowedMonitors; } }); @@ -17,6 +17,3 @@ const flavor_1 = __importDefault(require("./flavor")); exports.Flavor = flavor_1.default; const template_1 = __importDefault(require("./template")); exports.Template = template_1.default; -const tree_1 = require("./tree"); -Object.defineProperty(exports, "getAllApplications", { enumerable: true, get: function () { return tree_1.getAllApplications; } }); -Object.defineProperty(exports, "getApplication", { enumerable: true, get: function () { return tree_1.getApplication; } }); diff --git a/dist/js/templateMixin.d.ts b/dist/js/templateMixin.d.ts index 83ea8c5..7349965 100644 --- a/dist/js/templateMixin.d.ts +++ b/dist/js/templateMixin.d.ts @@ -14,15 +14,15 @@ export type TemplateMixin = { contextProviders: ContextProvider[]; addContextProvider: (provider: ContextProvider) => void; removeContextProvider: (provider: ContextProvider) => void; - render: (externalContext: Record) => void; - getRenderedJSON: (context: Record) => AnyObject; + render: (externalContext?: Record) => void; + getRenderedJSON: (context?: Record) => AnyObject; _cleanRenderingContext: (object: Record) => Record; - getDataFromProvidersForRenderingContext: (context: Record) => Record; + getDataFromProvidersForRenderingContext: (context?: Record) => Record; setContent: (text: string) => void; setRendered: (text: string) => void; - getContextProvidersAsClassInstances: (providerContext: Record) => ContextProvider[]; - getDataFromProvidersForPersistentContext: (providerContext: Record) => Record; - getRenderingContext: (externalContext: Record) => Record; + getContextProvidersAsClassInstances: (providerContext?: Record) => ContextProvider[]; + getDataFromProvidersForPersistentContext: (providerContext?: Record) => Record; + getRenderingContext: (externalContext?: Record) => Record; }; export declare function templateMixin(item: TemplateBase): TemplateMixin & InMemoryEntity & { setName(name: string): void; @@ -34,7 +34,6 @@ export type ContextProviderConfigMapEntry = { }; export type ContextProviderConfigMap = Record; export type TemplateStaticMixin = { - fromFlavor: (appName: string, execName: string, inputName: string) => TemplateMixin & TemplateBase; contextProviderRegistry: ContextProviderRegistryContainer | null; setContextProvidersConfig: (classConfigMap: ContextProviderConfigMap) => void; }; diff --git a/dist/js/templateMixin.js b/dist/js/templateMixin.js index 969e067..0c532e8 100644 --- a/dist/js/templateMixin.js +++ b/dist/js/templateMixin.js @@ -5,7 +5,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) { Object.defineProperty(exports, "__esModule", { value: true }); exports.templateMixin = templateMixin; exports.templateStaticMixin = templateStaticMixin; -const application_flavors_js_1 = require("@exabyte-io/application-flavors.js"); const utils_1 = require("@mat3ra/code/dist/js/utils"); const nunjucks_1 = __importDefault(require("nunjucks")); const ContextProviderRegistryContainer_1 = __importDefault(require("./context/ContextProviderRegistryContainer")); @@ -122,10 +121,10 @@ function templateMixin(item) { return result; }, /* - @summary Combines rendering context (in order of preference): - * - context from templates initialized with external context - * - "external" context and - */ + * @summary Combines rendering context (in order of preference): + * - context from templates initialized with external context + * - "external" context and + */ getRenderingContext(externalContext) { return { ...externalContext, @@ -139,15 +138,6 @@ function templateMixin(item) { function templateStaticMixin(item) { // @ts-ignore const properties = { - fromFlavor(appName, execName, inputName) { - const filtered = application_flavors_js_1.allTemplates.filter((temp) => temp.applicationName === appName && - temp.executableName === execName && - temp.name === inputName); - if (filtered.length !== 1) { - console.log(`found ${filtered.length} templates for app=${appName} exec=${execName} name=${inputName} expected 1`); - } - return new this(filtered[0]); - }, contextProviderRegistry: null, setContextProvidersConfig(classConfigMap) { const contextProviderRegistry = new ContextProviderRegistryContainer_1.default(); diff --git a/dist/js/tree.d.ts b/dist/js/tree.d.ts index 7453195..64b5594 100644 --- a/dist/js/tree.d.ts +++ b/dist/js/tree.d.ts @@ -1,4 +1,4 @@ -import { type ApplicationName, type ApplicationTreeItem } from "@exabyte-io/application-flavors.js"; +import { type ApplicationName } from "@exabyte-io/application-flavors.js"; import type { Constructor } from "@mat3ra/code/dist/js/utils/types"; import type { ApplicationSchemaBase } from "@mat3ra/esse/dist/js/types"; import type { ApplicationMixin } from "./applicationMixin"; @@ -9,7 +9,6 @@ type LocalApplicationTreeItem = { defaultVersion: string; [version: string]: ApplicationVersion | string; }; -type ApplicationTreeStructure = Partial>; /** * @summary Return all applications as both a nested object of Applications and an array of config objects * @param cls optional class to use to create applications @@ -19,20 +18,6 @@ export declare function getAllApplications(cls?: Constructor | applicationsTree: Partial>; applicationsArray: ApplicationSchemaBase[]; }; -/** - * @summary Get an application from the constructed applications - * @param applicationsTree See getAllApplications applicationsTree object structure - * @param name name of the application - * @param version version of the application (optional, defaults to defaultVersion) - * @param build the build to use (optional, defaults to Default) - * @return an application - */ -export declare function getApplication({ applicationsTree, name, version, build, }: { - applicationsTree: ApplicationTreeStructure; - name: ApplicationName; - version?: string | null; - build?: string; -}): ApplicationMixin | ApplicationSchemaBase | null; export type CreateApplicationConfig = { name: ApplicationName; version?: string | null; @@ -42,13 +27,4 @@ export type CreateApplicationConfig = { * @summary Get pre-defined application config from an already generated applicationsTree of configs */ export declare function getApplicationConfig({ name, version, build, }: CreateApplicationConfig): ApplicationMixin | ApplicationSchemaBase | null; -/** - * @summary Get executable config - * @param appName name of application to get executable for - * @param execName if not provided, find the executable with isDefault === true - */ -export declare function getExecutableConfig({ appName, execName, }: { - appName: ApplicationName; - execName?: string | null; -}): ApplicationTreeItem; export {}; diff --git a/dist/js/tree.js b/dist/js/tree.js index f453eea..161dbac 100644 --- a/dist/js/tree.js +++ b/dist/js/tree.js @@ -1,12 +1,9 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getAllApplications = getAllApplications; -exports.getApplication = getApplication; exports.getApplicationConfig = getApplicationConfig; -exports.getExecutableConfig = getExecutableConfig; /* eslint-disable new-cap */ const application_flavors_js_1 = require("@exabyte-io/application-flavors.js"); -const utils_1 = require("@mat3ra/code/dist/js/utils"); /** * @summary Return all applications as both a nested object of Applications and an array of config objects * @param cls optional class to use to create applications @@ -16,20 +13,25 @@ function getAllApplications(cls = null) { const applicationsTree = {}; const applicationsArray = []; application_flavors_js_1.allApplications.forEach((appName) => { - const { versions, defaultVersion, build = "Default", ...appData } = (0, application_flavors_js_1.getAppData)(appName); + const { + versions, + defaultVersion, + build = "Default", + ...appData + } = (0, application_flavors_js_1.getAppData)(appName); const appTreeItem = { defaultVersion }; versions.forEach((options) => { const { version } = options; - const appVersion = version in appTreeItem && typeof appTreeItem[version] === "object" - ? appTreeItem[version] - : {}; + const appVersion = + version in appTreeItem && typeof appTreeItem[version] === "object" + ? appTreeItem[version] + : {}; appTreeItem[version] = appVersion; const config = { ...appData, build, ...options }; if (cls) { appVersion[build] = new cls(config); applicationsArray.push(new cls(config)); - } - else { + } else { appVersion[build] = config; applicationsArray.push(config); } @@ -46,7 +48,7 @@ function getAllApplications(cls = null) { * @param build the build to use (optional, defaults to Default) * @return an application */ -function getApplication({ applicationsTree, name, version = null, build = "Default", }) { +function getApplication({ applicationsTree, name, version = null, build = "Default" }) { var _a; const app = applicationsTree[name]; if (!app) { @@ -66,7 +68,7 @@ const { applicationsTree } = getAllApplications(null); /** * @summary Get pre-defined application config from an already generated applicationsTree of configs */ -function getApplicationConfig({ name, version = null, build = "Default", }) { +function getApplicationConfig({ name, version = null, build = "Default" }) { return getApplication({ applicationsTree, name, @@ -74,18 +76,3 @@ function getApplicationConfig({ name, version = null, build = "Default", }) { build, }); } -/** - * @summary Get executable config - * @param appName name of application to get executable for - * @param execName if not provided, find the executable with isDefault === true - */ -function getExecutableConfig({ appName, execName, }) { - const appTree = (0, application_flavors_js_1.getAppTree)(appName); - Object.entries(appTree).forEach(([name, exec]) => { - exec.name = name; - }); - if (!execName) { - return (0, utils_1.getOneMatchFromObject)(appTree, "isDefault", true); - } - return appTree[execName]; -} diff --git a/package-lock.json b/package-lock.json index 4f79ee1..ebbcdb3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@exabyte-io/application-flavors.js": "2024.9.8-1", "@exabyte-io/eslint-config": "2025.5.13-0", "@mat3ra/code": "git+https://github.com/Exabyte-io/code.git#3fbf30e5318c9f1ab7a3a5b967fa57d2b70cad3e", - "@mat3ra/esse": "2025.4.22-0", + "@mat3ra/esse": "git+https://github.com/Exabyte-io/esse.git#51ebdff7682a9767016cd9af8bc13086386737ec", "@mat3ra/made": "git+https://github.com/Exabyte-io/made.git#84807b83b15dfd632fae9495be301aa7df503b11", "@mat3ra/tsconfig": "2024.6.3-0", "@types/nunjucks": "^3.2.6", @@ -2834,10 +2834,11 @@ } }, "node_modules/@mat3ra/esse": { - "version": "2025.4.22-0", - "resolved": "https://registry.npmjs.org/@mat3ra/esse/-/esse-2025.4.22-0.tgz", - "integrity": "sha512-bhpMHMuf6JTDXN2Pae8L+Zsn/JWBVZCqtLo1LgkvKK9QkjuUcjT8ixSGPYOjYEKe6wak+AUI0CqqgUBcVCS1KQ==", + "version": "0.0.0", + "resolved": "git+ssh://git@github.com/Exabyte-io/esse.git#51ebdff7682a9767016cd9af8bc13086386737ec", + "integrity": "sha512-wqoL9b8C84RJzUJ5RO+B6m6qoI1WyeCelQXcPlPC2EbCq6dVPVL/8DX57qlb6bzJQB5CO8MP4REJLnWH5dXVvw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@babel/cli": "^7.27.0", "@babel/core": "^7.26.10", diff --git a/package.json b/package.json index f747b62..ffb46da 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "@exabyte-io/application-flavors.js": "2024.9.8-1", "@exabyte-io/eslint-config": "2025.5.13-0", "@mat3ra/code": "git+https://github.com/Exabyte-io/code.git#3fbf30e5318c9f1ab7a3a5b967fa57d2b70cad3e", - "@mat3ra/esse": "2025.4.22-0", + "@mat3ra/esse": "git+https://github.com/Exabyte-io/esse.git#51ebdff7682a9767016cd9af8bc13086386737ec", "@mat3ra/made": "git+https://github.com/Exabyte-io/made.git#84807b83b15dfd632fae9495be301aa7df503b11", "@mat3ra/tsconfig": "2024.6.3-0", "@types/nunjucks": "^3.2.6", diff --git a/src/js/AdeFactory.ts b/src/js/AdeFactory.ts new file mode 100644 index 0000000..ef79841 --- /dev/null +++ b/src/js/AdeFactory.ts @@ -0,0 +1,219 @@ +import { + type ApplicationName, + allApplications, + allTemplates, + getAppData, + getAppTree, +} from "@exabyte-io/application-flavors.js"; +import { getOneMatchFromObject } from "@mat3ra/code/dist/js/utils/object"; +import type { ApplicationSchemaBase, ExecutableSchema } from "@mat3ra/esse/dist/js/types"; + +import Application from "./application"; +import Executable from "./executable"; +import Flavor from "./flavor"; +import Template from "./template"; + +type ApplicationVersion = { + [build: string]: ApplicationSchemaBase; +}; + +type ApplicationTreeItem = { + defaultVersion: string; + [version: string]: ApplicationVersion | string; +}; + +export type CreateApplicationConfig = { + name: ApplicationName; + version?: string | null; + build?: string; +}; + +type ApplicationTree = Partial>; + +export default class AdeFactory { + // applications + static applicationsTree?: ApplicationTree; + + static applicationsArray?: ApplicationSchemaBase[]; + + static createApplication({ name, version = null, build = "Default" }: CreateApplicationConfig) { + const staticConfig = AdeFactory.getApplicationConfig({ name, version, build }); + return new Application({ ...staticConfig, name, version, build }); + } + + static getUniqueAvailableApplicationNames() { + return allApplications; + } + + /** + * @summary Return all applications as both a nested object of Applications and an array of config objects + * @returns containing applications and applicationConfigs + */ + static getAllApplications() { + if (this.applicationsTree && this.applicationsArray) { + return { + applicationsTree: this.applicationsTree, + applicationsArray: this.applicationsArray, + }; + } + + const applicationsTree: ApplicationTree = {}; + const applicationsArray: ApplicationSchemaBase[] = []; + + allApplications.forEach((appName) => { + const { versions, defaultVersion, build = "Default", ...appData } = getAppData(appName); + const appTreeItem: ApplicationTreeItem = { defaultVersion }; + + versions.forEach((options) => { + const { version } = options; + + const appVersion = + version in appTreeItem && typeof appTreeItem[version] === "object" + ? appTreeItem[version] + : {}; + + appTreeItem[version] = appVersion; + + const applicationConfig: ApplicationSchemaBase = { ...appData, build, ...options }; + + appVersion[build] = applicationConfig; + applicationsArray.push(applicationConfig); + }); + + applicationsTree[appName] = appTreeItem; + }); + + this.applicationsTree = applicationsTree; + this.applicationsArray = applicationsArray; + + return { + applicationsTree, + applicationsArray: this.applicationsArray, + }; + } + + /** + * @summary Get an application from the constructed applications + * @param name name of the application + * @param version version of the application (optional, defaults to defaultVersion) + * @param build the build to use (optional, defaults to Default) + * @return an application + */ + static getApplicationConfig({ + name, + version = null, + build = "Default", + }: CreateApplicationConfig) { + const { applicationsTree } = this.getAllApplications(); + const app = applicationsTree[name]; + + if (!app) { + throw new Error(`Application ${name} not found`); + } + + const version_ = version || app.defaultVersion; + const appVersion = app[version_]; + + if (!appVersion || typeof appVersion === "string") { + console.log(`Version ${version_} not available for ${name} !`); + } + + if (typeof appVersion === "string") { + return null; + } + + return appVersion[build] ?? null; + } + + static getExecutables({ name, version }: { name: ApplicationName; version?: string }) { + const tree = getAppTree(name); + + return Object.keys(tree) + .filter((key) => { + const executable = tree[key]; + const { supportedApplicationVersions } = executable; + return ( + !supportedApplicationVersions || + (version && supportedApplicationVersions.includes(version)) + ); + }) + .map((key) => new Executable({ ...tree[key], name: key })); + } + + static getExecutableByName(appName: ApplicationName, execName?: string) { + const appTree = getAppTree(appName); + + Object.entries(appTree).forEach(([name, exec]) => { + exec.name = name; + }); + + const config = execName + ? appTree[execName] + : (getOneMatchFromObject(appTree, "isDefault", true) as ExecutableSchema); + + return new Executable(config); + } + + // TODO: remove this method and use getApplicationExecutableByName directly + static getExecutableByConfig(appName: ApplicationName, config?: { name: string }) { + return this.getExecutableByName(appName, config?.name); + } + + static getExecutableFlavors(executable: Executable) { + const flavorsTree = executable.prop("flavors", {}) as Record; + + return Object.keys(flavorsTree).map((key) => { + return new Flavor({ + ...flavorsTree[key], + name: key, + }); + }); + } + + static getFlavorByName(executable: Executable, name?: string) { + return this.getExecutableFlavors(executable).find((flavor) => + name ? flavor.name === name : flavor.isDefault, + ); + } + + static getFlavorByConfig(executable: Executable, config?: { name: string }) { + return this.getFlavorByName(executable, config?.name); + } + + // flavors + static getInputAsTemplates(flavor: Flavor) { + const appName = flavor.prop("applicationName", ""); + const execName = flavor.prop("executableName", ""); + + return flavor.input.map((input) => { + const inputName = input.templateName || input.name; + + const filtered = allTemplates.filter( + (temp) => + temp.applicationName === appName && + temp.executableName === execName && + temp.name === inputName, + ); + + if (filtered.length !== 1) { + console.log( + `found ${filtered.length} templates for app=${appName} exec=${execName} name=${inputName} expected 1`, + ); + } + + return new Template({ ...filtered[0], name: input.name }); + }); + } + + static getInputAsRenderedTemplates(flavor: Flavor, context: Record) { + return this.getInputAsTemplates(flavor).map((template) => + template.getRenderedJSON(context), + ); + } + + static getAllFlavorsForApplication(appName: ApplicationName, version?: string) { + const allExecutables = this.getExecutables({ name: appName, version }); + + return allExecutables.flatMap((executable) => this.getExecutableFlavors(executable)); + } +} diff --git a/src/js/application.ts b/src/js/application.ts index e936697..ad6e7ec 100644 --- a/src/js/application.ts +++ b/src/js/application.ts @@ -6,16 +6,10 @@ import { applicationMixin, applicationStaticMixin, } from "./applicationMixin"; -import { type CreateApplicationConfig, getApplicationConfig } from "./tree"; type Base = typeof NamedDefaultableInMemoryEntity & Constructor; -export default class Application extends (NamedDefaultableInMemoryEntity as Base) { - constructor(config: CreateApplicationConfig) { - const staticConfig = getApplicationConfig(config); - super({ ...staticConfig, ...config }); - } -} +export default class Application extends (NamedDefaultableInMemoryEntity as Base) {} applicationMixin(Application.prototype); applicationStaticMixin(Application); diff --git a/src/js/applicationMixin.ts b/src/js/applicationMixin.ts index 5aec197..c577a36 100644 --- a/src/js/applicationMixin.ts +++ b/src/js/applicationMixin.ts @@ -1,27 +1,43 @@ -import { - type ApplicationName, - allApplications, - getAppData, - getAppTree, -} from "@exabyte-io/application-flavors.js"; +import { type ApplicationName } from "@exabyte-io/application-flavors.js"; import type { InMemoryEntity } from "@mat3ra/code/dist/js/entity"; import type { DefaultableInMemoryEntity } from "@mat3ra/code/dist/js/entity/mixins/DefaultableMixin"; import type { NamedInMemoryEntity } from "@mat3ra/code/dist/js/entity/mixins/NamedEntityMixin"; import type { Constructor } from "@mat3ra/code/dist/js/utils/types"; -import lodash from "lodash"; import Executable from "./executable"; -import { CreateApplicationConfig, getExecutableConfig } from "./tree"; type Base = InMemoryEntity & NamedInMemoryEntity & DefaultableInMemoryEntity; +export type BaseConstructor = Constructor & { + constructCustomExecutable?: (config: object) => Executable; +}; + +export type ApplicationConstructor = Constructor & ApplicationStaticMixin; + +export type ApplicationMixin = { + summary: string | undefined; + version: string; + build: string | undefined; + shortName: string; + name: ApplicationName; + hasAdvancedComputeOptions: boolean; + isLicensed: boolean; + isUsingMaterial: boolean; +}; + +export type ApplicationStaticMixin = { + defaultConfig: { + name: string; + shortName: string; + version: string; + summary: string; + build: string; + }; +}; + export function applicationMixin(item: Base) { - // @ts-ignore + // @ts-expect-error const properties: ApplicationMixin & Base = { - get defaultExecutable() { - return this.getExecutableByName(); - }, - get summary() { return this.prop("summary"); }, @@ -38,26 +54,6 @@ export function applicationMixin(item: Base) { return this.prop("shortName", this.name); }, - get executables() { - const tree = getAppTree(this.name as ApplicationName); - return Object.keys(tree) - .filter((key) => { - const { supportedApplicationVersions } = tree[key]; - return ( - !supportedApplicationVersions || - supportedApplicationVersions.includes(this.version) - ); - }) - .map((key) => { - return ( - this.constructor as unknown as ApplicationStaticProperties - ).constructExecutable({ - ...tree[key], - name: key, - }); - }); - }, - get hasAdvancedComputeOptions() { return this.prop("hasAdvancedComputeOptions", false); }, @@ -70,38 +66,6 @@ export function applicationMixin(item: Base) { const materialUsingApplications = ["vasp", "nwchem", "espresso", "exabyteml"]; return materialUsingApplications.includes(this.name); }, - - getExecutableByName(name?: string) { - return (this.constructor as unknown as ApplicationStaticProperties).constructExecutable( - getExecutableConfig({ - appName: this.name as ApplicationName, - execName: name, - }), - ); - }, - - getExecutableByConfig(config?: { name: string }) { - return config ? this.getExecutableByName(config.name) : this.defaultExecutable; - }, - - getExecutables() { - return this.executables; - }, - - getBuilds() { - const data = getAppData(this.name as ApplicationName); - const { versions } = data; - const builds = ["Default"]; - versions.map((v) => v.build && builds.push(v.build)); - return lodash.uniq(builds); - }, - - getVersions() { - const data = getAppData(this.name as ApplicationName); - const { versions } = data; - const these = versions.map((v) => v.version); - return lodash.uniq(these); - }, }; Object.defineProperties(item, Object.getOwnPropertyDescriptors(properties)); @@ -109,12 +73,8 @@ export function applicationMixin(item: Base) { return item; } -export type BaseConstructor = Constructor & { - constructCustomExecutable?: (config: object) => Executable; -}; - export function applicationStaticMixin(Application: T) { - const properties: ApplicationStaticProperties = { + const properties: ApplicationStaticMixin = { get defaultConfig() { return { name: "espresso", @@ -124,69 +84,9 @@ export function applicationStaticMixin(Application: T build: "Default", }; }, - - create(config: CreateApplicationConfig) { - return this.createFromNameVersionBuild(config); - }, - - createFromNameVersionBuild({ - name, - version = null, - build = "Default", - }: CreateApplicationConfig) { - return new Application({ name, version, build }); - }, - - getUniqueAvailableNames() { - return allApplications as string[]; - }, - - constructExecutable(this: BaseConstructor & typeof properties, config: object) { - if (this.constructCustomExecutable) { - return this.constructCustomExecutable(config); - } - return new Executable(config); - }, }; Object.defineProperties(Application, Object.getOwnPropertyDescriptors(properties)); return properties; } - -export type ApplicationStaticProperties = { - defaultConfig: { - name: string; - shortName: string; - version: string; - summary: string; - build: string; - }; - - create: (config: CreateApplicationConfig) => Base; - - createFromNameVersionBuild: (config: CreateApplicationConfig) => Base; - - getUniqueAvailableNames: () => string[]; - - constructExecutable: (config: object) => Executable; -}; - -export type ApplicationConstructor = Constructor & ApplicationStaticProperties; - -export type ApplicationMixin = { - defaultExecutable: Executable; - summary: string | undefined; - version: string; - build: string | undefined; - shortName: string; - executables: Executable[]; - hasAdvancedComputeOptions: boolean; - isLicensed: boolean; - isUsingMaterial: boolean; - getExecutableByName: (name?: string) => Executable; - getExecutableByConfig: (config?: { name: string }) => Executable; - getExecutables: () => Executable[]; - getBuilds: () => string[]; - getVersions: () => string[]; -}; diff --git a/src/js/executable.ts b/src/js/executable.ts index 7dd12dc..5f611ac 100644 --- a/src/js/executable.ts +++ b/src/js/executable.ts @@ -2,7 +2,7 @@ import { NamedDefaultableInMemoryEntity } from "@mat3ra/code/dist/js/entity"; import { runtimeItemsMixin } from "@mat3ra/code/dist/js/entity/mixins/RuntimeItemsMixin"; import type { Constructor } from "@mat3ra/code/dist/js/utils/types"; -import { type ExecutableMixin, executableMixin, executableStaticMixin } from "./executableMixin"; +import { type ExecutableMixin, executableMixin } from "./executableMixin"; type Base = typeof NamedDefaultableInMemoryEntity & Constructor; @@ -11,4 +11,3 @@ export default class Executable extends (NamedDefaultableInMemoryEntity as Base) // Apply mixins runtimeItemsMixin(Executable.prototype); executableMixin(Executable.prototype); -executableStaticMixin(Executable); diff --git a/src/js/executableMixin.ts b/src/js/executableMixin.ts index 3bba694..6e082c0 100644 --- a/src/js/executableMixin.ts +++ b/src/js/executableMixin.ts @@ -3,65 +3,20 @@ import type { DefaultableInMemoryEntity } from "@mat3ra/code/dist/js/entity/mixi import type { NamedInMemoryEntity } from "@mat3ra/code/dist/js/entity/mixins/NamedEntityMixin"; import type { Constructor } from "@mat3ra/code/dist/js/utils/types"; -import Flavor from "./flavor"; import type { FlavorMixin } from "./flavorMixin"; type BaseFlavor = FlavorMixin & NamedInMemoryEntity & InMemoryEntity; type Base = InMemoryEntity & NamedInMemoryEntity & DefaultableInMemoryEntity; export function executableMixin(item: Base) { - // @ts-ignore + // @ts-expect-error const properties: ExecutableMixin & Base = { - get defaultFlavor() { - return this.getFlavorByName(); + get applicationId() { + return this.prop("applicationId", []); }, - - get flavorsTree() { - return this.prop("flavors", {}) as Record; - }, - - get flavors() { - const tree = this.flavorsTree || {}; - - return Object.keys(tree).map((key) => { - return (this.constructor as unknown as ExecutableStaticProperties).constructFlavor({ - ...tree[key], - name: key, - executable: this, - }); - }); - }, - - get flavorsFromTree() { - return Object.keys(this.flavorsTree).map((key) => { - return (this.constructor as unknown as ExecutableStaticProperties).constructFlavor({ - ...this.flavorsTree[key], - name: key, - }); - }); - }, - - getFlavorByName(name?: string) { - return this.getEntityByName(this.flavors, "flavor", name || "") as BaseFlavor; - }, - - getFlavorByConfig(config?: { name: string }) { - return config ? this.getFlavorByName(config.name) : this.defaultFlavor; - }, - - getFlavorsByApplicationVersion(version: string) { - const filteredFlavors = this.flavors.filter((flavor) => { - const supportedApplicationVersions = flavor.prop( - "supportedApplicationVersions", - ); - return ( - !supportedApplicationVersions || supportedApplicationVersions.includes(version) - ); - }); - - return filteredFlavors; + set applicationId(value: string[]) { + this.setProp("applicationId", value); }, - toJSON(exclude: string[] = []) { const thisProto = Object.getPrototypeOf(this); const superProto = Object.getPrototypeOf(thisProto); @@ -75,41 +30,11 @@ export function executableMixin(item: Base) { return item; } -export type CreateExecutableConfig = { - name: string; - flavors?: Record; -}; - export type BaseConstructor = Constructor & { constructCustomFlavor?: (config: object) => BaseFlavor; }; -export function executableStaticMixin(Executable: T) { - const properties: ExecutableStaticProperties = { - constructFlavor(this: BaseConstructor & typeof properties, config: object): BaseFlavor { - if (this.constructCustomFlavor) { - return this.constructCustomFlavor(config); - } - return new Flavor(config); - }, - }; - - Object.defineProperties(Executable, Object.getOwnPropertyDescriptors(properties)); - - return properties; -} - -export type ExecutableStaticProperties = { - constructFlavor: (config: object) => BaseFlavor; -}; - export type ExecutableMixin = { - defaultFlavor: BaseFlavor; - flavorsTree: Record; - flavors: BaseFlavor[]; - flavorsFromTree: BaseFlavor[]; - getFlavorByName: (name?: string) => BaseFlavor; - getFlavorByConfig: (config?: { name: string }) => BaseFlavor; - getFlavorsByApplicationVersion: (version: string) => BaseFlavor[]; toJSON: (exclude?: string[]) => object; + applicationId: string[]; }; diff --git a/src/js/flavor.ts b/src/js/flavor.ts index d978e35..6799ae0 100644 --- a/src/js/flavor.ts +++ b/src/js/flavor.ts @@ -1,10 +1,15 @@ import { NamedDefaultableInMemoryEntity } from "@mat3ra/code/dist/js/entity"; -import { runtimeItemsMixin } from "@mat3ra/code/dist/js/entity/mixins/RuntimeItemsMixin"; +import { + type RuntimeItemsInMemoryEntity, + runtimeItemsMixin, +} from "@mat3ra/code/dist/js/entity/mixins/RuntimeItemsMixin"; import type { Constructor } from "@mat3ra/code/dist/js/utils/types"; import { type FlavorMixin, flavorMixin } from "./flavorMixin"; -type Base = typeof NamedDefaultableInMemoryEntity & Constructor; +type Base = typeof NamedDefaultableInMemoryEntity & + Constructor & + Constructor; export default class Flavor extends (NamedDefaultableInMemoryEntity as Base) {} diff --git a/src/js/flavorMixin.ts b/src/js/flavorMixin.ts index b4fa6ad..7c2f6b2 100644 --- a/src/js/flavorMixin.ts +++ b/src/js/flavorMixin.ts @@ -1,44 +1,47 @@ import type { InMemoryEntity } from "@mat3ra/code/dist/js/entity"; import type { NamedInMemoryEntity } from "@mat3ra/code/dist/js/entity/mixins/NamedEntityMixin"; - -import Template from "./template"; -import type { TemplateBase, TemplateMixin } from "./templateMixin"; +import type { FlavorSchema } from "@mat3ra/esse/dist/js/types"; type Base = InMemoryEntity & NamedInMemoryEntity; +type Input = Required["input"]; + export type FlavorMixin = { - input: { name: string; templateName?: string }[]; - inputAsTemplates: (TemplateMixin & TemplateBase)[]; + input: Input; disableRenderMaterials: boolean; + executableId: string; + executableName: string; + applicationName: string; + supportedApplicationVersions?: string[]; getInputAsRenderedTemplates: (context: Record) => Record[]; }; +// TODO: should we add fields from esse schema (executableId, executableName, applicationName)? export function flavorMixin(item: Base) { - // @ts-ignore + // @ts-expect-error const properties: FlavorMixin & Base = { get input() { - return this.prop<{ name: string; templateName?: string }[]>("input", []); + return this.prop("input", []); }, - // TODO : prevent this from running in client - get inputAsTemplates() { - return this.input.map((input) => { - const template = Template.fromFlavor( - this.prop("applicationName", ""), - this.prop("executableName", ""), - input.templateName || input.name, - ); - template.name = input.name; - return template; - }); + get disableRenderMaterials() { + return this.prop("isMultiMaterial", false); }, - getInputAsRenderedTemplates(context: Record) { - return this.inputAsTemplates.map((t) => t.getRenderedJSON(context)); + get executableId() { + return this.prop("executableId", ""); }, - get disableRenderMaterials() { - return this.prop("isMultiMaterial", false); + get executableName() { + return this.prop("executableName", ""); + }, + + get applicationName() { + return this.prop("applicationName", ""); + }, + + get supportedApplicationVersions() { + return this.prop("supportedApplicationVersions"); }, }; diff --git a/src/js/index.ts b/src/js/index.ts index b7b45c2..f3cf56d 100644 --- a/src/js/index.ts +++ b/src/js/index.ts @@ -9,15 +9,12 @@ import Application from "./application"; import Executable from "./executable"; import Flavor from "./flavor"; import Template from "./template"; -import { getAllApplications, getApplication } from "./tree"; export { Application, Executable, Flavor, Template, - getAllApplications, - getApplication, allApplications, allTemplates, allowedResults, diff --git a/src/js/templateMixin.ts b/src/js/templateMixin.ts index 2941967..34150f9 100644 --- a/src/js/templateMixin.ts +++ b/src/js/templateMixin.ts @@ -1,4 +1,3 @@ -import { allTemplates } from "@exabyte-io/application-flavors.js"; import type { InMemoryEntity } from "@mat3ra/code/dist/js/entity"; import type { NamedInMemoryEntity } from "@mat3ra/code/dist/js/entity/mixins/NamedEntityMixin"; import { deepClone } from "@mat3ra/code/dist/js/utils"; @@ -23,21 +22,21 @@ export type TemplateMixin = { contextProviders: ContextProvider[]; addContextProvider: (provider: ContextProvider) => void; removeContextProvider: (provider: ContextProvider) => void; - render: (externalContext: Record) => void; - getRenderedJSON: (context: Record) => AnyObject; + render: (externalContext?: Record) => void; + getRenderedJSON: (context?: Record) => AnyObject; _cleanRenderingContext: (object: Record) => Record; getDataFromProvidersForRenderingContext: ( - context: Record, + context?: Record, ) => Record; setContent: (text: string) => void; setRendered: (text: string) => void; getContextProvidersAsClassInstances: ( - providerContext: Record, + providerContext?: Record, ) => ContextProvider[]; getDataFromProvidersForPersistentContext: ( - providerContext: Record, + providerContext?: Record, ) => Record; - getRenderingContext: (externalContext: Record) => Record; + getRenderingContext: (externalContext?: Record) => Record; }; export function templateMixin(item: TemplateBase) { @@ -88,7 +87,7 @@ export function templateMixin(item: TemplateBase) { ); }, - render(externalContext: Record) { + render(externalContext?: Record) { const renderingContext = this.getRenderingContext(externalContext); if (!this.isManuallyChanged) { try { @@ -110,7 +109,7 @@ export function templateMixin(item: TemplateBase) { } }, - getRenderedJSON(context: Record) { + getRenderedJSON(context?: Record) { this.render(context); return this.toJSON(); }, @@ -129,7 +128,7 @@ export function templateMixin(item: TemplateBase) { * previously stored values. That is if data was previously saved in database, the context provider * shall receive it on initialization through providerContext and prioritize this value over the default. */ - getContextProvidersAsClassInstances(providerContext: Record) { + getContextProvidersAsClassInstances(providerContext?: Record) { return this.contextProviders.map((p) => { const providerInstance = ( this.constructor as unknown as TemplateStaticMixin @@ -151,7 +150,7 @@ export function templateMixin(item: TemplateBase) { /* * @summary Extracts the the data from all context providers for further use during render. */ - getDataFromProvidersForRenderingContext(providerContext: Record) { + getDataFromProvidersForRenderingContext(providerContext?: Record) { const result: AnyObject = {}; this.getContextProvidersAsClassInstances(providerContext).forEach((contextProvider) => { const context = contextProvider.yieldDataForRendering(); @@ -171,7 +170,7 @@ export function templateMixin(item: TemplateBase) { * @summary Extracts the the data from all context providers for further save in persistent context. */ // TODO: optimize logic to prevent re-initializing the context provider classes again below, reuse above function - getDataFromProvidersForPersistentContext(providerContext: Record) { + getDataFromProvidersForPersistentContext(providerContext?: Record) { const result = {}; this.getContextProvidersAsClassInstances(providerContext).forEach((contextProvider) => { // only save in the persistent context the data from providers that were edited (or able to be edited) @@ -181,11 +180,11 @@ export function templateMixin(item: TemplateBase) { }, /* - @summary Combines rendering context (in order of preference): - * - context from templates initialized with external context - * - "external" context and - */ - getRenderingContext(externalContext: Record) { + * @summary Combines rendering context (in order of preference): + * - context from templates initialized with external context + * - "external" context and + */ + getRenderingContext(externalContext?: Record) { return { ...externalContext, ...this.getDataFromProvidersForRenderingContext(externalContext), @@ -206,11 +205,6 @@ export type ContextProviderConfigMapEntry = { export type ContextProviderConfigMap = Record; export type TemplateStaticMixin = { - fromFlavor: ( - appName: string, - execName: string, - inputName: string, - ) => TemplateMixin & TemplateBase; contextProviderRegistry: ContextProviderRegistryContainer | null; setContextProvidersConfig: (classConfigMap: ContextProviderConfigMap) => void; }; @@ -218,21 +212,6 @@ export type TemplateStaticMixin = { export function templateStaticMixin(item: Constructor) { // @ts-ignore const properties: TemplateStaticMixin & Constructor = { - fromFlavor(appName: string, execName: string, inputName: string) { - const filtered = allTemplates.filter( - (temp) => - temp.applicationName === appName && - temp.executableName === execName && - temp.name === inputName, - ); - if (filtered.length !== 1) { - console.log( - `found ${filtered.length} templates for app=${appName} exec=${execName} name=${inputName} expected 1`, - ); - } - return new this(filtered[0]); - }, - contextProviderRegistry: null, setContextProvidersConfig(classConfigMap: ContextProviderConfigMap) { diff --git a/src/js/tree.ts b/src/js/tree.ts deleted file mode 100644 index 6e3c60d..0000000 --- a/src/js/tree.ts +++ /dev/null @@ -1,145 +0,0 @@ -/* eslint-disable new-cap */ -import { - type ApplicationName, - type ApplicationTreeItem, - allApplications, - getAppData, - getAppTree, -} from "@exabyte-io/application-flavors.js"; -import { getOneMatchFromObject } from "@mat3ra/code/dist/js/utils"; -import type { Constructor } from "@mat3ra/code/dist/js/utils/types"; -import type { ApplicationSchemaBase } from "@mat3ra/esse/dist/js/types"; - -import type { ApplicationMixin } from "./applicationMixin"; - -type ApplicationVersion = { - [build: string]: ApplicationMixin | ApplicationSchemaBase; -}; - -type LocalApplicationTreeItem = { - defaultVersion: string; - [version: string]: ApplicationVersion | string; -}; - -type ApplicationTreeStructure = Partial>; - -/** - * @summary Return all applications as both a nested object of Applications and an array of config objects - * @param cls optional class to use to create applications - * @returns containing applications and applicationConfigs - */ -export function getAllApplications(cls: Constructor | null = null) { - const applicationsTree: ApplicationTreeStructure = {}; - const applicationsArray: ApplicationMixin | ApplicationSchemaBase[] = []; - allApplications.forEach((appName) => { - const { versions, defaultVersion, build = "Default", ...appData } = getAppData(appName); - const appTreeItem: LocalApplicationTreeItem = { defaultVersion }; - - versions.forEach((options) => { - const { version } = options; - - const appVersion = - version in appTreeItem && typeof appTreeItem[version] === "object" - ? appTreeItem[version] - : {}; - - appTreeItem[version] = appVersion; - - const config = { ...appData, build, ...options }; - - if (cls) { - appVersion[build] = new cls(config); - applicationsArray.push(new cls(config)); - } else { - appVersion[build] = config; - applicationsArray.push(config); - } - }); - - applicationsTree[appName] = appTreeItem; - }); - return { applicationsTree, applicationsArray }; -} - -/** - * @summary Get an application from the constructed applications - * @param applicationsTree See getAllApplications applicationsTree object structure - * @param name name of the application - * @param version version of the application (optional, defaults to defaultVersion) - * @param build the build to use (optional, defaults to Default) - * @return an application - */ -export function getApplication({ - applicationsTree, - name, - version = null, - build = "Default", -}: { - applicationsTree: ApplicationTreeStructure; - name: ApplicationName; - version?: string | null; - build?: string; -}) { - const app = applicationsTree[name]; - if (!app) { - throw new Error(`Application ${name} not found`); - } - const version_ = version || app.defaultVersion; - const appVersion = app[version_]; - if (!appVersion || typeof appVersion === "string") { - console.log(`Version ${version_} not available for ${name} !`); - } - if (typeof appVersion === "string") { - return null; - } - return appVersion[build] ?? null; -} - -const { applicationsTree } = getAllApplications(null); - -export type CreateApplicationConfig = { - name: ApplicationName; - version?: string | null; - build?: string; -}; - -/** - * @summary Get pre-defined application config from an already generated applicationsTree of configs - */ -export function getApplicationConfig({ - name, - version = null, - build = "Default", -}: CreateApplicationConfig) { - return getApplication({ - applicationsTree, - name, - version, - build, - }); -} - -/** - * @summary Get executable config - * @param appName name of application to get executable for - * @param execName if not provided, find the executable with isDefault === true - */ -export function getExecutableConfig({ - appName, - execName, -}: { - appName: ApplicationName; - execName?: string | null; -}): ApplicationTreeItem { - const appTree = getAppTree(appName); - - Object.entries(appTree).forEach(([name, exec]) => { - exec.name = name; - }); - - if (!execName) { - return getOneMatchFromObject(appTree, "isDefault", true) as ApplicationTreeItem; - } - - return appTree[execName]; -} diff --git a/src/js/types/application-flavors.d.ts b/src/js/types/application-flavors.d.ts index b1540e0..fc29413 100644 --- a/src/js/types/application-flavors.d.ts +++ b/src/js/types/application-flavors.d.ts @@ -2,6 +2,8 @@ * TODO: @exabyte-io/application-flavors.js package must be removed and the source code must be moved to the current repo later in the future */ declare module "@exabyte-io/application-flavors.js" { + import type { ExecutableSchema, FlavorSchema } from "@mat3ra/esse/dist/js/types"; + export const allApplications: ApplicationName[]; export interface ApplicationTreeItem { @@ -10,7 +12,9 @@ declare module "@exabyte-io/application-flavors.js" { isDefault?: boolean; } - export function getAppTree(name: ApplicationName): Record; + export function getAppTree( + name: ApplicationName, + ): Record; export interface ApplicationVersion { version: string; diff --git a/tests/js/application.test.ts b/tests/js/application.test.ts index 4fa1bf9..4358c0d 100644 --- a/tests/js/application.test.ts +++ b/tests/js/application.test.ts @@ -1,6 +1,6 @@ import { expect } from "chai"; -import type { CreateApplicationConfig } from "src/js/tree"; +import type { CreateApplicationConfig } from "../../src/js/AdeFactory"; import Application from "../../src/js/application"; describe("Application", () => { diff --git a/tests/js/flavor.test.ts b/tests/js/flavor.test.ts new file mode 100644 index 0000000..8b31971 --- /dev/null +++ b/tests/js/flavor.test.ts @@ -0,0 +1,34 @@ +import { expect } from "chai"; + +import AdeFactory from "../../src/js/AdeFactory"; + +describe("Flavor", () => { + it("results are correct", () => { + const pwscfFlavor = AdeFactory.getAllFlavorsForApplication("espresso").find((flavor) => { + return flavor.name === "pw_scf"; + }); + expect(pwscfFlavor?.results).to.deep.equal([ + { + name: "atomic_forces", + }, + { + name: "fermi_energy", + }, + { + name: "pressure", + }, + { + name: "stress_tensor", + }, + { + name: "total_energy", + }, + { + name: "total_energy_contributions", + }, + { + name: "total_force", + }, + ]); + }); +});