Add core models, setup basic CLI, add internal database and migrations to store models.
This commit is contained in:
parent
e018f3443a
commit
ea4f691864
17 changed files with 409 additions and 3 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -33,3 +33,8 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
|||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
|
||||
|
||||
# infernod
|
||||
|
||||
infernod.db*
|
||||
|
|
31
bun.lock
31
bun.lock
|
@ -3,21 +3,50 @@
|
|||
"workspaces": {
|
||||
"": {
|
||||
"name": "infernod",
|
||||
"dependencies": {
|
||||
"@duckdb/node-api": "^1.3.1-alpha.23",
|
||||
"@sharkitek/core": "^4.1.0",
|
||||
"commander": "^14.0.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"@types/commander": "^2.12.5",
|
||||
"prettier": "^3.6.2",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5",
|
||||
"typescript": "^5.8.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@duckdb/node-api": ["@duckdb/node-api@1.3.1-alpha.23", "", { "dependencies": { "@duckdb/node-bindings": "1.3.1-alpha.23" } }, "sha512-D2dug8GMCC/PNkrB37gik0Wm74J3/JFQxIrEk7c02nK74BmLrZsxTRG5cLc6fqJuqj+AZ31jjgWroUtAaHSN/A=="],
|
||||
|
||||
"@duckdb/node-bindings": ["@duckdb/node-bindings@1.3.1-alpha.23", "", { "optionalDependencies": { "@duckdb/node-bindings-darwin-arm64": "1.3.1-alpha.23", "@duckdb/node-bindings-darwin-x64": "1.3.1-alpha.23", "@duckdb/node-bindings-linux-arm64": "1.3.1-alpha.23", "@duckdb/node-bindings-linux-x64": "1.3.1-alpha.23", "@duckdb/node-bindings-win32-x64": "1.3.1-alpha.23" } }, "sha512-uVFuy0bfV/XHRtMKZMDjaONbZplGXBOep20DZqA8BbWwWBnc37XMZoXZcykLRXdMVvzUll13mwfL1InDa9k1gw=="],
|
||||
|
||||
"@duckdb/node-bindings-darwin-arm64": ["@duckdb/node-bindings-darwin-arm64@1.3.1-alpha.23", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4UbDqkp+vOACjkOp6kQiTtrkzZw9CFb1GIEcDLZ+iySka0fz5sNNldJUNPuCbWm0Racu17OBzoSXQqF5Q0KUmQ=="],
|
||||
|
||||
"@duckdb/node-bindings-darwin-x64": ["@duckdb/node-bindings-darwin-x64@1.3.1-alpha.23", "", { "os": "darwin", "cpu": "x64" }, "sha512-qdBsiFR5dPswAwDiCqE227cP18618uKX5E3U5qEk0bsb9C8f+I+k3aHl8nLjE0vXOCxBUeI6xr3U9KskjyixpA=="],
|
||||
|
||||
"@duckdb/node-bindings-linux-arm64": ["@duckdb/node-bindings-linux-arm64@1.3.1-alpha.23", "", { "os": "linux", "cpu": "arm64" }, "sha512-kqWaYd6AkpLkJbIPd4UqujQE2G/QCriLIgFkpUgWVqP3yyCL/Oq+5oRvRSdUsuFJQqHztj8L3ZOy76xd5q12Ww=="],
|
||||
|
||||
"@duckdb/node-bindings-linux-x64": ["@duckdb/node-bindings-linux-x64@1.3.1-alpha.23", "", { "os": "linux", "cpu": "x64" }, "sha512-lANN+JYoBTqyOF0dGPiXz8r9+GGs6mm5nwB08boWYpQCjdoG2gFpK6XvT56HO+t01wbKIngDNrdThjqdZW9+Aw=="],
|
||||
|
||||
"@duckdb/node-bindings-win32-x64": ["@duckdb/node-bindings-win32-x64@1.3.1-alpha.23", "", { "os": "win32", "cpu": "x64" }, "sha512-bYBLYPAIs4KTSnTtqsRnjBH1Hgu9QVa3Yxio4TYktneRG7rHeX89nKcfszLLIkSUt6xhRNwYoeSh/HbXmx46fA=="],
|
||||
|
||||
"@sharkitek/core": ["@sharkitek/core@4.1.0", "", {}, "sha512-z+4YPJlH3/7EHiGY9WsuPhsWJsUDlf5V2D4nK398rVshYQP+fGUvJsGGtIH+YW3K3iyDft8cseWQrH67cix+lA=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.2.17", "", { "dependencies": { "bun-types": "1.2.17" } }, "sha512-l/BYs/JYt+cXA/0+wUhulYJB6a6p//GTPiJ7nV+QHa8iiId4HZmnu/3J/SowP5g0rTiERY2kfGKXEK5Ehltx4Q=="],
|
||||
|
||||
"@types/commander": ["@types/commander@2.12.5", "", { "dependencies": { "commander": "*" } }, "sha512-YXGZ/rz+s57VbzcvEV9fUoXeJlBt5HaKu5iUheiIWNsJs23bz6AnRuRiZBRVBLYyPnixNvVnuzM5pSaxr8Yp/g=="],
|
||||
|
||||
"@types/node": ["@types/node@24.0.3", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg=="],
|
||||
|
||||
"bun-types": ["bun-types@1.2.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-ElC7ItwT3SCQwYZDYoAH+q6KT4Fxjl8DtZ6qDulUFBmXA8YB4xo+l54J9ZJN+k2pphfn9vk7kfubeSd5QfTVJQ=="],
|
||||
|
||||
"commander": ["commander@14.0.0", "", {}, "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA=="],
|
||||
|
||||
"prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
|
||||
|
||||
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||
|
||||
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
||||
|
|
66
cli/cli.ts
Normal file
66
cli/cli.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
import { Command } from "commander";
|
||||
import { Manager } from "../core/manager.ts";
|
||||
import { listSoftwares } from "./commands/softwares.ts";
|
||||
|
||||
export class Cli {
|
||||
/**
|
||||
* CLI instance.
|
||||
* @protected
|
||||
*/
|
||||
protected command: Command;
|
||||
|
||||
/**
|
||||
* Infernod manager instance.
|
||||
* @protected
|
||||
*/
|
||||
protected manager!: Manager;
|
||||
|
||||
constructor() {
|
||||
this.command = new Command();
|
||||
this.setup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the currently passed command using the defined CLI.
|
||||
*/
|
||||
async run(): Promise<void> {
|
||||
// Parse the provided command.
|
||||
await this.command.parseAsync();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup CLI commands, args and flags.
|
||||
* @protected
|
||||
*/
|
||||
protected setup(): void {
|
||||
this.command
|
||||
.name("infernod")
|
||||
.description("Infernod Manager CLI")
|
||||
.version("0.1.0")
|
||||
|
||||
.option("-d, --database <string>", "set database path")
|
||||
|
||||
// Boot the CLI using provided global options.
|
||||
.hook("preAction", async () => {
|
||||
await this.boot();
|
||||
});
|
||||
|
||||
const softwares = this.command
|
||||
.command("softwares")
|
||||
.description("Softwares management commands.");
|
||||
softwares
|
||||
.command("list")
|
||||
.description("List all registered softwares.")
|
||||
.action(() => listSoftwares(this.manager));
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot the CLI internal requirements.
|
||||
* Mainly, initialize the manager.
|
||||
* @protected
|
||||
*/
|
||||
protected boot(): Promise<void> {
|
||||
this.manager = new Manager();
|
||||
return this.manager.waitForInitialized();
|
||||
}
|
||||
}
|
3
cli/commands/softwares.ts
Normal file
3
cli/commands/softwares.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import type { Manager } from "../../core/manager.ts";
|
||||
|
||||
export function listSoftwares(manager: Manager) {}
|
53
core/manager.ts
Normal file
53
core/manager.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { DuckDBConnection, DuckDBInstance } from "@duckdb/node-api";
|
||||
import { Migrations } from "./migrations/migrations.ts";
|
||||
|
||||
export class Manager {
|
||||
/**
|
||||
* The internal database connection, available when the manager is initialized.
|
||||
* @protected
|
||||
*/
|
||||
protected database?: DuckDBInstance;
|
||||
|
||||
/**
|
||||
* Main initialization promise.
|
||||
* @protected
|
||||
*/
|
||||
protected initialization: Promise<void>;
|
||||
|
||||
constructor(public readonly options: { databasePath?: string } = {}) {
|
||||
this.initialization = this.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the infernod manager.
|
||||
*/
|
||||
async initialize(): Promise<void> {
|
||||
this.database = await DuckDBInstance.create(this.databasePath);
|
||||
|
||||
// Setup database.
|
||||
const migrations = new Migrations(this);
|
||||
await migrations.execute();
|
||||
await migrations.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the promise to wait for initialization.
|
||||
*/
|
||||
async waitForInitialized(): Promise<void> {
|
||||
return this.initialization;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database path option, using default value if undefined.
|
||||
*/
|
||||
get databasePath(): string {
|
||||
return this.options?.databasePath ?? "infernod.db";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new database connection.
|
||||
*/
|
||||
newDatabaseConnection(): Promise<DuckDBConnection> {
|
||||
return this.database!.connect();
|
||||
}
|
||||
}
|
7
core/migrations/migration.ts
Normal file
7
core/migrations/migration.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import type { DuckDBConnection } from "@duckdb/node-api";
|
||||
|
||||
export interface Migration {
|
||||
getIdentifier(): string;
|
||||
|
||||
execute(connection: DuckDBConnection): Promise<void>;
|
||||
}
|
74
core/migrations/migrations.ts
Normal file
74
core/migrations/migrations.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
import type { DuckDBConnection } from "@duckdb/node-api";
|
||||
|
||||
import type { Manager } from "../manager.ts";
|
||||
import type { Migration } from "./migration.ts";
|
||||
|
||||
import { V001_AddAgents } from "./migrations/V001_AddAgents.ts";
|
||||
import { V002_AddApplicationWorkers } from "./migrations/V002_AddApplicationWorkers.ts";
|
||||
import { V003_AddServiceProviders } from "./migrations/V003_AddServiceProviders.ts";
|
||||
import { V004_AddSoftwares } from "./migrations/V004_AddSoftwares.ts";
|
||||
|
||||
export class Migrations {
|
||||
protected connection: Promise<DuckDBConnection>;
|
||||
|
||||
constructor(protected manager: Manager) {
|
||||
this.connection = this.manager.newDatabaseConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create migrations table if it does not exist.
|
||||
* @protected
|
||||
*/
|
||||
protected async setupMigrationsTable() {
|
||||
await (
|
||||
await this.connection
|
||||
).run(`
|
||||
CREATE OR REPLACE TABLE migrations(
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
migrated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the provided migration is executed or not.
|
||||
* @param migrationIdentifier The migration identifier to check for.
|
||||
* @protected
|
||||
*/
|
||||
protected async isExecuted(migrationIdentifier: string): Promise<boolean> {
|
||||
return (
|
||||
(
|
||||
await (
|
||||
await this.connection
|
||||
).runAndRead("SELECT id FROM migrations WHERE id = $id", {
|
||||
id: migrationIdentifier,
|
||||
})
|
||||
).columnCount > 0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the provided migration.
|
||||
* @param MigrationClass Migration class to register.
|
||||
* @protected
|
||||
*/
|
||||
protected async register(MigrationClass: { new (): Migration }) {
|
||||
const migration = new MigrationClass();
|
||||
if (!(await this.isExecuted(migration.getIdentifier()))) {
|
||||
await migration.execute(await this.connection);
|
||||
}
|
||||
}
|
||||
|
||||
async execute() {
|
||||
await this.setupMigrationsTable();
|
||||
|
||||
await this.register(V001_AddAgents);
|
||||
await this.register(V002_AddApplicationWorkers);
|
||||
await this.register(V003_AddServiceProviders);
|
||||
await this.register(V004_AddSoftwares);
|
||||
}
|
||||
|
||||
async close() {
|
||||
(await this.connection).closeSync();
|
||||
}
|
||||
}
|
18
core/migrations/migrations/V001_AddAgents.ts
Normal file
18
core/migrations/migrations/V001_AddAgents.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import type { Migration } from "../migration.ts";
|
||||
import type { DuckDBConnection } from "@duckdb/node-api";
|
||||
|
||||
export class V001_AddAgents implements Migration {
|
||||
getIdentifier(): string {
|
||||
return "V001_AddAgents";
|
||||
}
|
||||
|
||||
async execute(connection: DuckDBConnection): Promise<void> {
|
||||
await connection.run(`
|
||||
CREATE TABLE agents
|
||||
(
|
||||
id uuid default gen_random_uuid() primary key,
|
||||
name varchar(255) not null unique
|
||||
);
|
||||
`);
|
||||
}
|
||||
}
|
17
core/migrations/migrations/V002_AddApplicationWorkers.ts
Normal file
17
core/migrations/migrations/V002_AddApplicationWorkers.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import type { Migration } from "../migration.ts";
|
||||
import type { DuckDBConnection } from "@duckdb/node-api";
|
||||
|
||||
export class V002_AddApplicationWorkers implements Migration {
|
||||
getIdentifier(): string {
|
||||
return "V002_AddApplicationWorkers";
|
||||
}
|
||||
|
||||
async execute(connection: DuckDBConnection): Promise<void> {
|
||||
await connection.run(`
|
||||
CREATE TABLE application_workers
|
||||
(
|
||||
agent_id uuid primary key references agents (id)
|
||||
);
|
||||
`);
|
||||
}
|
||||
}
|
17
core/migrations/migrations/V003_AddServiceProviders.ts
Normal file
17
core/migrations/migrations/V003_AddServiceProviders.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import type { Migration } from "../migration.ts";
|
||||
import type { DuckDBConnection } from "@duckdb/node-api";
|
||||
|
||||
export class V003_AddServiceProviders implements Migration {
|
||||
getIdentifier(): string {
|
||||
return "V003_AddServiceProviders";
|
||||
}
|
||||
|
||||
async execute(connection: DuckDBConnection): Promise<void> {
|
||||
await connection.run(`
|
||||
CREATE TABLE service_providers
|
||||
(
|
||||
agent_id uuid primary key references agents (id)
|
||||
);
|
||||
`);
|
||||
}
|
||||
}
|
19
core/migrations/migrations/V004_AddSoftwares.ts
Normal file
19
core/migrations/migrations/V004_AddSoftwares.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import type { Migration } from "../migration.ts";
|
||||
import type { DuckDBConnection } from "@duckdb/node-api";
|
||||
|
||||
export class V004_AddSoftwares implements Migration {
|
||||
getIdentifier(): string {
|
||||
return "V004_AddSoftwares";
|
||||
}
|
||||
|
||||
async execute(connection: DuckDBConnection): Promise<void> {
|
||||
await connection.run(`
|
||||
CREATE TABLE softwares
|
||||
(
|
||||
id uuid default gen_random_uuid(),
|
||||
name varchar(255) not null unique,
|
||||
variants jsonb not null
|
||||
);
|
||||
`);
|
||||
}
|
||||
}
|
26
core/model/agent.ts
Normal file
26
core/model/agent.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import s from "@sharkitek/core";
|
||||
|
||||
/**
|
||||
* An **Agent** is a generic node object, which can carry out assigned tasks.
|
||||
*/
|
||||
export class Agent {
|
||||
/**
|
||||
* Unique agent UUID.
|
||||
*/
|
||||
id!: string;
|
||||
|
||||
/**
|
||||
* The name of the agent must be unique.
|
||||
* It is a human-understandable identifier.
|
||||
*/
|
||||
name!: string;
|
||||
}
|
||||
|
||||
export const AgentModel = s.defineModel({
|
||||
Class: Agent,
|
||||
identifier: "id",
|
||||
properties: {
|
||||
id: s.property.string(),
|
||||
name: s.property.string(),
|
||||
},
|
||||
});
|
14
core/model/application-worker.ts
Normal file
14
core/model/application-worker.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import s from "@sharkitek/core";
|
||||
|
||||
import { Agent, AgentModel } from "./agent.ts";
|
||||
|
||||
/**
|
||||
* An **ApplicationWorker** agent is responsible for running assigned instances of an application,
|
||||
* using their specific environment.
|
||||
*/
|
||||
export class ApplicationWorker extends Agent {}
|
||||
|
||||
export const ApplicationWorkerModel = s.extend(AgentModel, {
|
||||
Class: ApplicationWorker,
|
||||
properties: {},
|
||||
});
|
14
core/model/service-provider.ts
Normal file
14
core/model/service-provider.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import s from "@sharkitek/core";
|
||||
|
||||
import { Agent, AgentModel } from "./agent.ts";
|
||||
|
||||
/**
|
||||
* The **Service Provider** is responsible for providing a database, a storage solution,
|
||||
* or a way to execute a specific thing when an application needs it.
|
||||
*/
|
||||
export class ServiceProvider extends Agent {}
|
||||
|
||||
export const ServiceProviderModel = s.extend(AgentModel, {
|
||||
Class: ServiceProvider,
|
||||
properties: {},
|
||||
});
|
32
core/model/software.ts
Normal file
32
core/model/software.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import s from "@sharkitek/core";
|
||||
|
||||
/**
|
||||
* A **Software** describes the required *services* and *application environment* to run an application.
|
||||
*/
|
||||
export class Software {
|
||||
/**
|
||||
* Unique software UUID.
|
||||
*/
|
||||
id!: string;
|
||||
|
||||
/**
|
||||
* The name of the software must be unique.
|
||||
* It is a human-understandable identifier.
|
||||
*/
|
||||
name!: string;
|
||||
|
||||
/**
|
||||
* Software variants identifiers, with their associated metadata.
|
||||
*/
|
||||
variants!: Map<string, any>;
|
||||
}
|
||||
|
||||
export const SoftwareModel = s.defineModel({
|
||||
Class: Software,
|
||||
identifier: "id",
|
||||
properties: {
|
||||
id: s.property.string(),
|
||||
name: s.property.string(),
|
||||
variants: s.property.stringMap(s.property.object({})),
|
||||
},
|
||||
});
|
4
infernod.ts
Normal file
4
infernod.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
import { Cli } from "./cli/cli.ts";
|
||||
|
||||
const infernod = new Cli();
|
||||
await infernod.run();
|
12
package.json
12
package.json
|
@ -1,14 +1,22 @@
|
|||
{
|
||||
"name": "infernod",
|
||||
"version": "0.1",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"infernod": "bun run infernod.ts",
|
||||
"format": "prettier . --write"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
"@types/bun": "latest",
|
||||
"@types/commander": "^2.12.5",
|
||||
"prettier": "^3.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@duckdb/node-api": "^1.3.1-alpha.23",
|
||||
"@sharkitek/core": "^4.1.0",
|
||||
"commander": "^14.0.0"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue