Refactor to unify scripts/categories as Executable
This commit consolidates scripts and categories under a unified 'Executable' concept. This simplifies the architecture and improves code readability. - Introduce subfolders within `src/domain` to segregate domain elements. - Update class and interface names by removing the 'I' prefix in alignment with new coding standards. - Replace 'Node' with 'Executable' to clarify usage; reserve 'Node' exclusively for the UI's tree component.
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import { getEnumValues, assertInRange } from '@/application/Common/Enum';
|
||||
import { RecommendationLevel } from './RecommendationLevel';
|
||||
import { RecommendationLevel } from './Executables/Script/RecommendationLevel';
|
||||
import { OperatingSystem } from './OperatingSystem';
|
||||
import type { IEntity } from '../infrastructure/Entity/IEntity';
|
||||
import type { ICategory } from './ICategory';
|
||||
import type { IScript } from './IScript';
|
||||
import type { Category } from './Executables/Category/Category';
|
||||
import type { Script } from './Executables/Script/Script';
|
||||
import type { IScriptingDefinition } from './IScriptingDefinition';
|
||||
import type { ICategoryCollection } from './ICategoryCollection';
|
||||
|
||||
@@ -16,7 +16,7 @@ export class CategoryCollection implements ICategoryCollection {
|
||||
|
||||
constructor(
|
||||
public readonly os: OperatingSystem,
|
||||
public readonly actions: ReadonlyArray<ICategory>,
|
||||
public readonly actions: ReadonlyArray<Category>,
|
||||
public readonly scripting: IScriptingDefinition,
|
||||
) {
|
||||
this.queryable = makeQueryable(actions);
|
||||
@@ -26,7 +26,7 @@ export class CategoryCollection implements ICategoryCollection {
|
||||
ensureNoDuplicates(this.queryable.allScripts);
|
||||
}
|
||||
|
||||
public getCategory(categoryId: number): ICategory {
|
||||
public getCategory(categoryId: number): Category {
|
||||
const category = this.queryable.allCategories.find((c) => c.id === categoryId);
|
||||
if (!category) {
|
||||
throw new Error(`Missing category with ID: "${categoryId}"`);
|
||||
@@ -34,13 +34,13 @@ export class CategoryCollection implements ICategoryCollection {
|
||||
return category;
|
||||
}
|
||||
|
||||
public getScriptsByLevel(level: RecommendationLevel): readonly IScript[] {
|
||||
public getScriptsByLevel(level: RecommendationLevel): readonly Script[] {
|
||||
assertInRange(level, RecommendationLevel);
|
||||
const scripts = this.queryable.scriptsByLevel.get(level);
|
||||
return scripts ?? [];
|
||||
}
|
||||
|
||||
public getScript(scriptId: string): IScript {
|
||||
public getScript(scriptId: string): Script {
|
||||
const script = this.queryable.allScripts.find((s) => s.id === scriptId);
|
||||
if (!script) {
|
||||
throw new Error(`missing script: ${scriptId}`);
|
||||
@@ -48,11 +48,11 @@ export class CategoryCollection implements ICategoryCollection {
|
||||
return script;
|
||||
}
|
||||
|
||||
public getAllScripts(): IScript[] {
|
||||
public getAllScripts(): Script[] {
|
||||
return this.queryable.allScripts;
|
||||
}
|
||||
|
||||
public getAllCategories(): ICategory[] {
|
||||
public getAllCategories(): Category[] {
|
||||
return this.queryable.allCategories;
|
||||
}
|
||||
}
|
||||
@@ -73,9 +73,9 @@ function ensureNoDuplicates<TKey>(entities: ReadonlyArray<IEntity<TKey>>) {
|
||||
}
|
||||
|
||||
interface IQueryableCollection {
|
||||
allCategories: ICategory[];
|
||||
allScripts: IScript[];
|
||||
scriptsByLevel: Map<RecommendationLevel, readonly IScript[]>;
|
||||
allCategories: Category[];
|
||||
allScripts: Script[];
|
||||
scriptsByLevel: Map<RecommendationLevel, readonly Script[]>;
|
||||
}
|
||||
|
||||
function ensureValid(application: IQueryableCollection) {
|
||||
@@ -83,13 +83,13 @@ function ensureValid(application: IQueryableCollection) {
|
||||
ensureValidScripts(application.allScripts);
|
||||
}
|
||||
|
||||
function ensureValidCategories(allCategories: readonly ICategory[]) {
|
||||
function ensureValidCategories(allCategories: readonly Category[]) {
|
||||
if (!allCategories.length) {
|
||||
throw new Error('must consist of at least one category');
|
||||
}
|
||||
}
|
||||
|
||||
function ensureValidScripts(allScripts: readonly IScript[]) {
|
||||
function ensureValidScripts(allScripts: readonly Script[]) {
|
||||
if (!allScripts.length) {
|
||||
throw new Error('must consist of at least one script');
|
||||
}
|
||||
@@ -102,8 +102,8 @@ function ensureValidScripts(allScripts: readonly IScript[]) {
|
||||
}
|
||||
|
||||
function flattenApplication(
|
||||
categories: ReadonlyArray<ICategory>,
|
||||
): [ICategory[], IScript[]] {
|
||||
categories: ReadonlyArray<Category>,
|
||||
): [Category[], Script[]] {
|
||||
const [subCategories, subScripts] = (categories || [])
|
||||
// Parse children
|
||||
.map((category) => flattenApplication(category.subCategories))
|
||||
@@ -113,7 +113,7 @@ function flattenApplication(
|
||||
[...previousCategories, ...currentCategories],
|
||||
[...previousScripts, ...currentScripts],
|
||||
];
|
||||
}, [new Array<ICategory>(), new Array<IScript>()]);
|
||||
}, [new Array<Category>(), new Array<Script>()]);
|
||||
return [
|
||||
[
|
||||
...(categories || []),
|
||||
@@ -127,7 +127,7 @@ function flattenApplication(
|
||||
}
|
||||
|
||||
function makeQueryable(
|
||||
actions: ReadonlyArray<ICategory>,
|
||||
actions: ReadonlyArray<Category>,
|
||||
): IQueryableCollection {
|
||||
const flattened = flattenApplication(actions);
|
||||
return {
|
||||
@@ -138,8 +138,8 @@ function makeQueryable(
|
||||
}
|
||||
|
||||
function groupByLevel(
|
||||
allScripts: readonly IScript[],
|
||||
): Map<RecommendationLevel, readonly IScript[]> {
|
||||
allScripts: readonly Script[],
|
||||
): Map<RecommendationLevel, readonly Script[]> {
|
||||
return getEnumValues(RecommendationLevel)
|
||||
.map((level) => ({
|
||||
level,
|
||||
@@ -150,5 +150,5 @@ function groupByLevel(
|
||||
.reduce((map, group) => {
|
||||
map.set(group.level, group.scripts);
|
||||
return map;
|
||||
}, new Map<RecommendationLevel, readonly IScript[]>());
|
||||
}, new Map<RecommendationLevel, readonly Script[]>());
|
||||
}
|
||||
|
||||
11
src/domain/Executables/Category/Category.ts
Normal file
11
src/domain/Executables/Category/Category.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { Script } from '../Script/Script';
|
||||
import type { Executable } from '../Executable';
|
||||
|
||||
export interface Category extends Executable<number> {
|
||||
readonly id: number;
|
||||
readonly name: string;
|
||||
readonly subCategories: ReadonlyArray<Category>;
|
||||
readonly scripts: ReadonlyArray<Script>;
|
||||
includes(script: Script): boolean;
|
||||
getAllScriptsRecursively(): ReadonlyArray<Script>;
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
import { BaseEntity } from '../infrastructure/Entity/BaseEntity';
|
||||
import type { ICategory } from './ICategory';
|
||||
import type { IScript } from './IScript';
|
||||
import { BaseEntity } from '@/infrastructure/Entity/BaseEntity';
|
||||
import type { Category } from './Category';
|
||||
import type { Script } from '../Script/Script';
|
||||
|
||||
export class Category extends BaseEntity<number> implements ICategory {
|
||||
private allSubScripts?: ReadonlyArray<IScript> = undefined;
|
||||
export class CollectionCategory extends BaseEntity<number> implements Category {
|
||||
private allSubScripts?: ReadonlyArray<Script> = undefined;
|
||||
|
||||
public readonly name: string;
|
||||
|
||||
public readonly docs: ReadonlyArray<string>;
|
||||
|
||||
public readonly subCategories: ReadonlyArray<ICategory>;
|
||||
public readonly subCategories: ReadonlyArray<Category>;
|
||||
|
||||
public readonly scripts: ReadonlyArray<IScript>;
|
||||
public readonly scripts: ReadonlyArray<Script>;
|
||||
|
||||
constructor(parameters: CategoryInitParameters) {
|
||||
super(parameters.id);
|
||||
@@ -22,11 +22,11 @@ export class Category extends BaseEntity<number> implements ICategory {
|
||||
this.scripts = parameters.scripts;
|
||||
}
|
||||
|
||||
public includes(script: IScript): boolean {
|
||||
public includes(script: Script): boolean {
|
||||
return this.getAllScriptsRecursively().some((childScript) => childScript.id === script.id);
|
||||
}
|
||||
|
||||
public getAllScriptsRecursively(): readonly IScript[] {
|
||||
public getAllScriptsRecursively(): readonly Script[] {
|
||||
if (!this.allSubScripts) {
|
||||
this.allSubScripts = parseScriptsRecursively(this);
|
||||
}
|
||||
@@ -38,11 +38,11 @@ export interface CategoryInitParameters {
|
||||
readonly id: number;
|
||||
readonly name: string;
|
||||
readonly docs: ReadonlyArray<string>;
|
||||
readonly subcategories: ReadonlyArray<ICategory>;
|
||||
readonly scripts: ReadonlyArray<IScript>;
|
||||
readonly subcategories: ReadonlyArray<Category>;
|
||||
readonly scripts: ReadonlyArray<Script>;
|
||||
}
|
||||
|
||||
function parseScriptsRecursively(category: ICategory): ReadonlyArray<IScript> {
|
||||
function parseScriptsRecursively(category: Category): ReadonlyArray<Script> {
|
||||
return [
|
||||
...category.scripts,
|
||||
...category.subCategories.flatMap((c) => c.getAllScriptsRecursively()),
|
||||
@@ -1,3 +1,3 @@
|
||||
export interface IDocumentable {
|
||||
export interface Documentable {
|
||||
readonly docs: ReadonlyArray<string>;
|
||||
}
|
||||
6
src/domain/Executables/Executable.ts
Normal file
6
src/domain/Executables/Executable.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { IEntity } from '@/infrastructure/Entity/IEntity';
|
||||
import type { Documentable } from './Documentable';
|
||||
|
||||
export interface Executable<TExecutableKey>
|
||||
extends Documentable, IEntity<TExecutableKey> {
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { IScriptCode } from './IScriptCode';
|
||||
import type { ScriptCode } from './ScriptCode';
|
||||
|
||||
export class ScriptCode implements IScriptCode {
|
||||
export class DistinctReversibleScriptCode implements ScriptCode {
|
||||
constructor(
|
||||
public readonly execute: string,
|
||||
public readonly revert: string | undefined,
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface IScriptCode {
|
||||
export interface ScriptCode {
|
||||
readonly execute: string;
|
||||
readonly revert?: string;
|
||||
}
|
||||
12
src/domain/Executables/Script/Code/ScriptCodeFactory.ts
Normal file
12
src/domain/Executables/Script/Code/ScriptCodeFactory.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { DistinctReversibleScriptCode } from './DistinctReversibleScriptCode';
|
||||
import type { ScriptCode } from './ScriptCode';
|
||||
|
||||
export interface ScriptCodeFactory {
|
||||
(
|
||||
...args: ConstructorParameters<typeof DistinctReversibleScriptCode>
|
||||
): ScriptCode;
|
||||
}
|
||||
|
||||
export const createScriptCode: ScriptCodeFactory = (
|
||||
...args
|
||||
) => new DistinctReversibleScriptCode(...args);
|
||||
@@ -1,12 +1,12 @@
|
||||
import { BaseEntity } from '@/infrastructure/Entity/BaseEntity';
|
||||
import { RecommendationLevel } from './RecommendationLevel';
|
||||
import type { IScript } from './IScript';
|
||||
import type { IScriptCode } from './IScriptCode';
|
||||
import type { Script } from './Script';
|
||||
import type { ScriptCode } from './Code/ScriptCode';
|
||||
|
||||
export class Script extends BaseEntity<string> implements IScript {
|
||||
export class CollectionScript extends BaseEntity<string> implements Script {
|
||||
public readonly name: string;
|
||||
|
||||
public readonly code: IScriptCode;
|
||||
public readonly code: ScriptCode;
|
||||
|
||||
public readonly docs: ReadonlyArray<string>;
|
||||
|
||||
@@ -28,7 +28,7 @@ export class Script extends BaseEntity<string> implements IScript {
|
||||
|
||||
export interface ScriptInitParameters {
|
||||
readonly name: string;
|
||||
readonly code: IScriptCode;
|
||||
readonly code: ScriptCode;
|
||||
readonly docs: ReadonlyArray<string>;
|
||||
readonly level?: RecommendationLevel;
|
||||
}
|
||||
11
src/domain/Executables/Script/Script.ts
Normal file
11
src/domain/Executables/Script/Script.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { RecommendationLevel } from './RecommendationLevel';
|
||||
import type { Executable } from '../Executable';
|
||||
import type { Documentable } from '../Documentable';
|
||||
import type { ScriptCode } from './Code/ScriptCode';
|
||||
|
||||
export interface Script extends Executable<string>, Documentable {
|
||||
readonly name: string;
|
||||
readonly level?: RecommendationLevel;
|
||||
readonly code: ScriptCode;
|
||||
canRevert(): boolean;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { IEntity } from '../infrastructure/Entity/IEntity';
|
||||
import type { IScript } from './IScript';
|
||||
import type { IDocumentable } from './IDocumentable';
|
||||
|
||||
export interface ICategory extends IEntity<number>, IDocumentable {
|
||||
readonly id: number;
|
||||
readonly name: string;
|
||||
readonly subCategories: ReadonlyArray<ICategory>;
|
||||
readonly scripts: ReadonlyArray<IScript>;
|
||||
includes(script: IScript): boolean;
|
||||
getAllScriptsRecursively(): ReadonlyArray<IScript>;
|
||||
}
|
||||
|
||||
export type { IEntity } from '../infrastructure/Entity/IEntity';
|
||||
export type { IScript } from './IScript';
|
||||
@@ -1,19 +1,19 @@
|
||||
import type { IScriptingDefinition } from '@/domain/IScriptingDefinition';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||
import type { IScript } from '@/domain/IScript';
|
||||
import type { ICategory } from '@/domain/ICategory';
|
||||
import { RecommendationLevel } from '@/domain/Executables/Script/RecommendationLevel';
|
||||
import type { Script } from '@/domain/Executables/Script/Script';
|
||||
import type { Category } from '@/domain/Executables/Category/Category';
|
||||
|
||||
export interface ICategoryCollection {
|
||||
readonly scripting: IScriptingDefinition;
|
||||
readonly os: OperatingSystem;
|
||||
readonly totalScripts: number;
|
||||
readonly totalCategories: number;
|
||||
readonly actions: ReadonlyArray<ICategory>;
|
||||
readonly actions: ReadonlyArray<Category>;
|
||||
|
||||
getScriptsByLevel(level: RecommendationLevel): ReadonlyArray<IScript>;
|
||||
getCategory(categoryId: number): ICategory;
|
||||
getScript(scriptId: string): IScript;
|
||||
getAllScripts(): ReadonlyArray<IScript>;
|
||||
getAllCategories(): ReadonlyArray<ICategory>;
|
||||
getScriptsByLevel(level: RecommendationLevel): ReadonlyArray<Script>;
|
||||
getCategory(categoryId: number): Category;
|
||||
getScript(scriptId: string): Script;
|
||||
getAllScripts(): ReadonlyArray<Script>;
|
||||
getAllCategories(): ReadonlyArray<Category>;
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import { RecommendationLevel } from './RecommendationLevel';
|
||||
import type { IEntity } from '../infrastructure/Entity/IEntity';
|
||||
import type { IDocumentable } from './IDocumentable';
|
||||
import type { IScriptCode } from './IScriptCode';
|
||||
|
||||
export interface IScript extends IEntity<string>, IDocumentable {
|
||||
readonly name: string;
|
||||
readonly level?: RecommendationLevel;
|
||||
readonly docs: ReadonlyArray<string>;
|
||||
readonly code: IScriptCode;
|
||||
canRevert(): boolean;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import { ScriptCode } from './ScriptCode';
|
||||
import type { IScriptCode } from './IScriptCode';
|
||||
|
||||
export interface ScriptCodeFactory {
|
||||
(
|
||||
...args: ConstructorParameters<typeof ScriptCode>
|
||||
): IScriptCode;
|
||||
}
|
||||
|
||||
export const createScriptCode: ScriptCodeFactory = (...args) => new ScriptCode(...args);
|
||||
Reference in New Issue
Block a user