├── .eslintignore ├── src ├── common │ ├── types │ │ ├── clouded-object.ts │ │ └── json-types.ts │ ├── logger │ │ ├── logging-utils.ts │ │ ├── logger-types.ts │ │ ├── get-system-info.ts │ │ ├── hook-console-to-file.ts │ │ ├── logger.ts │ │ └── default-styler-functions.ts │ ├── helpers │ │ ├── parsers.ts │ │ ├── import-json-and-parse.ts │ │ └── get-object-property.ts │ ├── constants.ts │ ├── meta-file-type.ts │ ├── environment-start.ts │ ├── assertions │ │ └── is-valid-type.ts │ └── execution-env.ts ├── configuration │ ├── README.md │ ├── addon-type.ts │ ├── schemas │ │ ├── schemas-type.ts │ │ └── schema.ts │ ├── runtime-config │ │ └── defaults.ts │ ├── diff │ │ ├── configuration-diff-type.ts │ │ ├── check-entity-diff.ts │ │ └── diff-manager.ts │ ├── configuration-type.ts │ ├── business-operations │ │ ├── business-operation.ts │ │ ├── business-operations-type.ts │ │ ├── cyclic-dependency-check.ts │ │ └── validate-bops-pipeline-flow.ts │ ├── configuration.ts │ ├── configuration-definition.ts │ ├── path-alias-utils.ts │ └── de-serialize-configuration.ts ├── bops-functions │ ├── prebuilt-functions │ │ ├── non-bops-utils │ │ │ ├── any-is-nan.ts │ │ │ ├── get-decimal-places.ts │ │ │ └── get-largest-decimal-places.ts │ │ ├── Readme.md │ │ ├── date │ │ │ └── date-now.ts │ │ ├── logic │ │ │ ├── not.ts │ │ │ ├── and.ts │ │ │ ├── lower-than.ts │ │ │ ├── higher-than.ts │ │ │ ├── or.ts │ │ │ ├── lower-or-equal-to.ts │ │ │ ├── higher-or-equal-to.ts │ │ │ └── equal.ts │ │ ├── array │ │ │ ├── length.ts │ │ │ ├── join.ts │ │ │ ├── array-at.ts │ │ │ ├── array-map.ts │ │ │ ├── push.ts │ │ │ ├── array-filter.ts │ │ │ ├── inlcudes.ts │ │ │ ├── array-sort.ts │ │ │ ├── remove.ts │ │ │ ├── array-find.ts │ │ │ └── find-index.ts │ │ ├── boolean │ │ │ ├── bool-to-number.ts │ │ │ └── bool-to-string.ts │ │ ├── number │ │ │ ├── random.ts │ │ │ ├── to-string.ts │ │ │ └── to-exponential.ts │ │ ├── string │ │ │ ├── index-of.ts │ │ │ ├── concat.ts │ │ │ ├── to-number.ts │ │ │ ├── replace.ts │ │ │ ├── char-at.ts │ │ │ ├── count.ts │ │ │ └── template.ts │ │ ├── object │ │ │ ├── to-string.ts │ │ │ ├── create.ts │ │ │ ├── values.ts │ │ │ ├── keys.ts │ │ │ ├── get-value.ts │ │ │ └── combine.ts │ │ ├── assertion │ │ │ └── is-nill.ts │ │ ├── flux-control │ │ │ ├── try-catch.ts │ │ │ ├── if.ts │ │ │ └── forLoop.ts │ │ ├── math │ │ │ ├── absolute.ts │ │ │ ├── subtract.ts │ │ │ ├── exponential.ts │ │ │ ├── square-root.ts │ │ │ ├── add.ts │ │ │ ├── divide.ts │ │ │ ├── multipy.ts │ │ │ ├── modulus.ts │ │ │ └── round.ts │ │ └── system │ │ │ ├── execute-with-args.ts │ │ │ └── get-system-function.ts │ ├── bops-engine │ │ ├── engine-errors │ │ │ ├── schema-not-found-error.ts │ │ │ ├── execution-time-exceeded.ts │ │ │ ├── internal-bop-not-found.ts │ │ │ ├── operation-not-found-error.ts │ │ │ ├── constant-type-error.ts │ │ │ └── function-not-found.ts │ │ ├── add-timeout.ts │ │ ├── contexts │ │ │ ├── bop-context.ts │ │ │ └── bop-system-context.ts │ │ ├── variables │ │ │ ├── functions │ │ │ │ ├── decrease-variable.ts │ │ │ │ ├── increase-variable.ts │ │ │ │ └── set-variable.ts │ │ │ └── variables-context.ts │ │ ├── module-resolver.ts │ │ ├── static-info-validation.ts │ │ ├── modules-manager.ts │ │ └── object-manipulator.ts │ ├── meta-function-type.ts │ └── internal-meta-function.ts ├── index.ts ├── bootstrap │ └── collect-strategies │ │ ├── strategies.ts │ │ ├── node-strategies.ts │ │ └── browser-strategies.ts ├── bin │ ├── replace-references.ts │ ├── index.ts │ └── commands.ts ├── entities │ ├── meta-entity.ts │ ├── entity-action.ts │ ├── helpers │ │ ├── validate-meta-file.ts │ │ └── validate-definition.ts │ ├── repository.ts │ ├── singletons │ │ └── logger.ts │ └── broker-support-typings.ts ├── bundler │ └── bundler-types.ts └── broker │ ├── broker-presets.ts │ ├── broker-entity.ts │ └── entity-broker.ts ├── test ├── test-managers.ts ├── helpers │ ├── sleep.ts │ ├── retry.ts │ └── test-throw.ts ├── addons │ ├── test-addon │ │ ├── meta-file.json │ │ └── entrypoint.js │ └── addons.spec.ts ├── doubles │ └── mongo-server.ts ├── schema │ ├── common-schemas │ │ ├── flat-example-schema.ts │ │ ├── deep-example-schema.ts │ │ ├── complex-example-schema.ts │ │ └── multiple-types-schema.ts │ └── random-partial-object.ts ├── factories │ ├── schema-factory.ts │ ├── entity-factory.ts │ ├── schema-format-factory.ts │ └── entity-to-query.ts ├── bops-functions │ ├── bops-engine │ │ └── test-data │ │ │ ├── business-operations │ │ │ ├── envs-bop.ts │ │ │ ├── external-bop.ts │ │ │ ├── prebuilt-bop.ts │ │ │ ├── internal-bop.ts │ │ │ ├── variables-bop.ts │ │ │ ├── schema-bop.ts │ │ │ └── package-bop.ts │ │ │ └── test-system.ts │ └── prebuilt-functions │ │ ├── assertions.spec.ts │ │ ├── boolean.spec.ts │ │ ├── number.spec.ts │ │ └── object.spec.ts ├── configuration │ ├── test-data │ │ ├── schema │ │ │ ├── missing-reference-schema.json │ │ │ ├── deep-object-schema.json │ │ │ └── array-types-schema.json │ │ └── bops │ │ │ ├── no-output.json │ │ │ ├── missing-key.json │ │ │ ├── configuration-loop.json │ │ │ └── duplicate-key.json │ ├── configuration-de-serializer.spec.ts │ └── validations.spec.ts ├── tsconfig.json └── broker │ └── broker.spec.ts ├── .mocharc.json ├── .npmignore ├── tsconfig.json ├── tsconfig.build.json ├── .github ├── ISSUE_TEMPLATE │ ├── general-issue.md │ └── bug_report.md └── FUNDING.yml ├── tsconfig.base.json ├── ROADMAP.md ├── .gitignore ├── package.json └── .eslintrc.cjs /.eslintignore: -------------------------------------------------------------------------------- 1 | .eslintrc.cjs -------------------------------------------------------------------------------- /src/common/types/clouded-object.ts: -------------------------------------------------------------------------------- 1 | export type CloudedObject = Record; 2 | -------------------------------------------------------------------------------- /src/common/logger/logging-utils.ts: -------------------------------------------------------------------------------- 1 | export function fullObject (object : object) : string { 2 | return JSON.stringify(object, undefined, 2); 3 | } 4 | -------------------------------------------------------------------------------- /test/test-managers.ts: -------------------------------------------------------------------------------- 1 | const purgeTestPackages = async () : Promise => { 2 | // TODO Reimplement 3 | }; 4 | 5 | export { purgeTestPackages }; 6 | -------------------------------------------------------------------------------- /src/common/types/json-types.ts: -------------------------------------------------------------------------------- 1 | export type JsonTypes = "string" | "date" | "number" | "boolean"; 2 | export type ExtendedJsonTypes = JsonTypes | "object" | "array" | "any"; 3 | -------------------------------------------------------------------------------- /src/configuration/README.md: -------------------------------------------------------------------------------- 1 | # Meta-System - Configuration 2 | This folder contains types and helpers for validating the configuration provided to run the Meta-System as a whole. -------------------------------------------------------------------------------- /src/configuration/addon-type.ts: -------------------------------------------------------------------------------- 1 | export type Addon = { 2 | version ?: string; 3 | source : string; 4 | identifier : string; 5 | collectStrategy : "npm" | "url" | "file"; 6 | configuration : unknown; 7 | }; 8 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensions": ["ts"], 3 | "spec": ["./test/**/*.spec.*"], 4 | "node-option": [ 5 | "experimental-specifier-resolution=node", 6 | "loader=ts-node/esm" 7 | ], 8 | "timeout": "30000" 9 | } 10 | -------------------------------------------------------------------------------- /src/configuration/schemas/schemas-type.ts: -------------------------------------------------------------------------------- 1 | import { ObjectDefinition } from "@meta-system/object-definition"; 2 | 3 | export interface SchemaType { 4 | name : string; 5 | format : ObjectDefinition; 6 | identifier : string; 7 | } 8 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/non-bops-utils/any-is-nan.ts: -------------------------------------------------------------------------------- 1 | export const anyIsNan = (...values : number[]) : boolean => { 2 | for (const value of values) { 3 | if (Number.isNaN(Number(value))) { return true; } 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /src/* 2 | /external-functions 3 | /node_modules 4 | /test 5 | /test-configs 6 | /test-functions 7 | /runtime 8 | .eslintrc 9 | nodemon.json 10 | tsconfig.json 11 | tsconfig.base.json 12 | tsconfig.build.json 13 | .gitignore 14 | -------------------------------------------------------------------------------- /test/helpers/sleep.ts: -------------------------------------------------------------------------------- 1 | export const sleep = async (durationMs : number) : Promise => { 2 | return new Promise((resolve) => { 3 | console.log(`\nSleeping for ${durationMs}ms...\n`); 4 | setTimeout(resolve, durationMs); 5 | }); 6 | }; 7 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/non-bops-utils/get-decimal-places.ts: -------------------------------------------------------------------------------- 1 | export const getDecimalPlaces = (value : number) : number => { 2 | if(Math.floor(value) === value) return 0; 3 | 4 | return value.toString().split(".")[1].length || 0; 5 | }; 6 | -------------------------------------------------------------------------------- /src/bops-functions/bops-engine/engine-errors/schema-not-found-error.ts: -------------------------------------------------------------------------------- 1 | export class SchemaNotFoundError extends Error { 2 | constructor (schema : string) { 3 | super(`Schema "${schema}" was not found`); 4 | this.name = SchemaNotFoundError.name; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/addons/test-addon/meta-file.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-addon", 3 | "version": "1.0.0", 4 | "entrypoint": "./entrypoint.js", 5 | "configurationFormat": {}, 6 | "permissions": [ 7 | { "entity": "addonsFunctions", "permissions": ["register"] } 8 | ] 9 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { SystemSetup as _SystemSetup } from "./bootstrap/system-setup.js"; 2 | import { ConfigurationType as config } from "./configuration/configuration-type.js"; 3 | 4 | export type ConfigurationType = config; 5 | 6 | export const SystemSetup = _SystemSetup; 7 | -------------------------------------------------------------------------------- /src/bops-functions/bops-engine/engine-errors/execution-time-exceeded.ts: -------------------------------------------------------------------------------- 1 | export class TTLExceededError extends Error { 2 | constructor (timeExecuted : number) { 3 | super(`Time limit exceeded after ${timeExecuted}ms`); 4 | this.name = TTLExceededError.name; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/doubles/mongo-server.ts: -------------------------------------------------------------------------------- 1 | import { MongoMemoryServer } from "mongodb-memory-server"; 2 | 3 | export async function createFakeMongo () : Promise { 4 | const mongoClientServer = await MongoMemoryServer.create(); 5 | const uri = mongoClientServer.getUri(); 6 | 7 | return uri; 8 | }; 9 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/Readme.md: -------------------------------------------------------------------------------- 1 | ## Prebuilt Functions 2 | The folder contains prebuilt BOPs functions, which a user should be able to use in their own BOPs configurations. These functions are some basic types operations that can be chained together to build some more complex behaviors. 3 | -------------------------------------------------------------------------------- /src/configuration/runtime-config/defaults.ts: -------------------------------------------------------------------------------- 1 | export const runtimeDefaults = { 2 | defaultInstallFolder: "runtime", 3 | externalFunctionConfigFileName: "meta-function.json", 4 | externalPackageConfigFileName: "meta-package.json", 5 | externalProtocolConfigFileName: "meta-protocol.json", 6 | }; 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | // TSConfig used by `ts language service` (e.g.: IDEs). 2 | // Also corresponds to the config for linting and testing 3 | { 4 | "extends": "./tsconfig.base.json", 5 | "exclude": [ 6 | "dist/*" 7 | ], 8 | "include": [ 9 | "src/**/*", 10 | "test/**/*" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /src/bootstrap/collect-strategies/strategies.ts: -------------------------------------------------------------------------------- 1 | import { NodeCollectStrategies } from "./node-strategies.js"; 2 | import { BrowserCollectStrategies } from "./browser-strategies.js"; 3 | 4 | export class Strategies { 5 | public static node = NodeCollectStrategies; 6 | public static browser = BrowserCollectStrategies; 7 | } 8 | -------------------------------------------------------------------------------- /src/bops-functions/bops-engine/engine-errors/internal-bop-not-found.ts: -------------------------------------------------------------------------------- 1 | export class InternalBopNotFound extends Error { 2 | constructor (internalBopName : string) { 3 | super(`"${internalBopName}", required as an internal Bop, was not found in the system config`); 4 | this.name = InternalBopNotFound.name; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/common/helpers/parsers.ts: -------------------------------------------------------------------------------- 1 | export function parseInteger (value : string) : number { 2 | const parsedValue = parseInt(value); 3 | if(isNaN(parsedValue)) throw Error(`${value} is not a valid integer`); 4 | if(Number(value) !== parsedValue) console.warn(`${value} will be truncated to an integer`); 5 | return parsedValue; 6 | } 7 | -------------------------------------------------------------------------------- /src/bops-functions/bops-engine/engine-errors/operation-not-found-error.ts: -------------------------------------------------------------------------------- 1 | export class OperationNotFoundError extends Error { 2 | constructor (operation : string, schema : string) { 3 | super(`"${operation}", required for schema ${schema} is not a valid database operation`); 4 | this.name = OperationNotFoundError.name; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | // The TSConfig for building the application. 2 | // Must always exclude "test" and "dist" directories 3 | { 4 | "extends": "./tsconfig.base.json", 5 | "exclude": [ 6 | "test/*", 7 | "dist/*" 8 | ], 9 | "include": [ 10 | "src/*", 11 | "src/bin/index.ts", 12 | "./package.json" 13 | ] 14 | } -------------------------------------------------------------------------------- /src/common/constants.ts: -------------------------------------------------------------------------------- 1 | import { nanoid } from "nanoid"; 2 | import { LogLevelsType } from "./logger/logger-types.js"; 3 | 4 | const runtimeEngineIdentifier = nanoid(); 5 | 6 | export default Object.freeze({ 7 | ENGINE_TTL: 2000, 8 | DEFAULT_LOG_LEVEL: "error" as LogLevelsType, 9 | ENGINE_OWNER: Symbol(runtimeEngineIdentifier), 10 | RUNTIME_ENGINE_IDENTIFIER: runtimeEngineIdentifier, 11 | PERMISSION_OVERRIDE_VALUE: nanoid(), 12 | }); 13 | -------------------------------------------------------------------------------- /src/bops-functions/meta-function-type.ts: -------------------------------------------------------------------------------- 1 | import { ObjectDefinition } from "@meta-system/object-definition"; 2 | 3 | export interface MetaFunction extends FunctionDefinition { 4 | description ?: string; 5 | author ?: string; 6 | version ?: string; 7 | entrypoint : string; 8 | mainFunction : string; 9 | } 10 | 11 | export interface FunctionDefinition { 12 | input : ObjectDefinition; 13 | output : ObjectDefinition; 14 | functionName : string; 15 | } 16 | -------------------------------------------------------------------------------- /test/schema/common-schemas/flat-example-schema.ts: -------------------------------------------------------------------------------- 1 | import { SchemaType } from "../../../src/configuration/schemas/schemas-type.js"; 2 | 3 | export const flatExampleSchema : SchemaType = { 4 | name: "exampleFlatSchema", 5 | format: { 6 | name: { type: "string" }, 7 | age: { type: "number" }, 8 | favoriteFood: { type: "string" }, 9 | eyeColour: { type: "string" }, 10 | height: { type: "number" }, 11 | }, 12 | identifier: "someIdentifier", 13 | }; 14 | -------------------------------------------------------------------------------- /src/common/helpers/import-json-and-parse.ts: -------------------------------------------------------------------------------- 1 | export const importJsonAndParse = async (path : string, relativeHome ?: string) : Promise => { 2 | const { readFileSync } = await import("fs"); 3 | const pathLib = await import("path"); 4 | let usedPath = path; 5 | if (relativeHome) { 6 | usedPath = pathLib.resolve(relativeHome, usedPath); 7 | } 8 | const imported = readFileSync(pathLib.resolve(usedPath)); 9 | 10 | return JSON.parse(imported.toString()); 11 | }; 12 | -------------------------------------------------------------------------------- /src/bops-functions/bops-engine/engine-errors/constant-type-error.ts: -------------------------------------------------------------------------------- 1 | import { BopsConstant } from "../../../configuration/business-operations/business-operations-type.js"; 2 | 3 | export class ConstantTypeError extends Error { 4 | constructor (constant : BopsConstant) { 5 | super(`The constant "${constant.name}" was expected to be a ${constant.type}` + 6 | ` but ${constant.value} type is ${typeof constant.value}`); 7 | this.name = ConstantTypeError.name; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/common/helpers/get-object-property.ts: -------------------------------------------------------------------------------- 1 | export const getObjectProperty = (object : object, path : string) : T | undefined => { 2 | const pathSteps = path.split("."); 3 | let result = undefined; 4 | let currentObjectRef = object; 5 | 6 | for (const step of pathSteps) { 7 | result = currentObjectRef[step] ?? undefined; 8 | 9 | if (result === undefined) return undefined; 10 | currentObjectRef = currentObjectRef[step]; 11 | } 12 | 13 | return result; 14 | }; 15 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/date/date-now.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const dateNowBopsFunction = () : { now : Date } => { 4 | return { now: new Date(Date.now()) }; 5 | }; 6 | 7 | export const dateNowBopsFunctionInformation : InternalMetaFunction = { 8 | functionName: "dateNow", 9 | description: "Gets the current date.", 10 | output: { 11 | now: { type: "date", required: true }, 12 | }, 13 | input: {}, 14 | }; 15 | -------------------------------------------------------------------------------- /src/configuration/diff/configuration-diff-type.ts: -------------------------------------------------------------------------------- 1 | export type EntityType = "schema" | "schemaFunctions" | "internalFunctions" | "businessOperations" | "bopsFunctions" 2 | | "envs" | "addonsFunctions"; 3 | 4 | export type ConfigurationDiff = { 5 | action : "added" | "modified" | "removed"; 6 | actorIdentifier : string; 7 | targetEntityType : EntityType; 8 | targetEntityIdentifier : string; 9 | targetPath : string; 10 | newEntityState : unknown; 11 | targetPathNewValue : unknown; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/configuration/schemas/schema.ts: -------------------------------------------------------------------------------- 1 | import { ObjectDefinition } from "@meta-system/object-definition"; 2 | import { SchemaType } from "./schemas-type.js"; 3 | 4 | export class Schema implements SchemaType { 5 | public readonly name : string; 6 | public readonly identifier : string; 7 | public readonly format : ObjectDefinition; 8 | 9 | public constructor (schema : SchemaType) { 10 | this.name = schema.name; 11 | this.format = schema.format; 12 | this.identifier = schema.identifier; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/schema/common-schemas/deep-example-schema.ts: -------------------------------------------------------------------------------- 1 | import { SchemaType } from "../../../src/configuration/schemas/schemas-type.js"; 2 | 3 | export const deepExampleSchema : SchemaType = { 4 | name: "exampleDeepSchema", 5 | format: { 6 | name: { type: "string" }, 7 | job: { 8 | type: "object", 9 | subtype: { 10 | wage: { type: "number" }, 11 | name: { type: "string" }, 12 | hiredAt: { type: "date" }, 13 | }, 14 | }, 15 | }, 16 | identifier: "someIdentifier", 17 | }; 18 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/non-bops-utils/get-largest-decimal-places.ts: -------------------------------------------------------------------------------- 1 | import { getDecimalPlaces } from "./get-decimal-places.js"; 2 | 3 | export const getGreatestDecimalPlaces = (...numbers : number[]) : number => { 4 | const decimalPlacesList = numbers.map((value) => getDecimalPlaces(value)); 5 | 6 | let greatestDecimalPlaces = 0; 7 | 8 | decimalPlacesList.forEach((decimalPlaces) => { 9 | greatestDecimalPlaces = Math.max(decimalPlaces, greatestDecimalPlaces); 10 | }); 11 | 12 | return greatestDecimalPlaces; 13 | }; 14 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/logic/not.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const notBopsFunction = (input : { A : boolean }) : unknown => { 4 | return ({ result: !input.A }); 5 | }; 6 | 7 | export const notBopsFunctionInformation : InternalMetaFunction = { 8 | functionName: "not", 9 | description: "Inverts the boolean value of A", 10 | input: { 11 | A: { type: "boolean", required: true }, 12 | }, 13 | output: { 14 | result: { type: "boolean", required: true }, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /src/bops-functions/bops-engine/engine-errors/function-not-found.ts: -------------------------------------------------------------------------------- 1 | import { BopsConfigurationEntry } from "../../../configuration/business-operations/business-operations-type.js"; 2 | 3 | export class FunctionNotFoundError extends Error { 4 | constructor (module : BopsConfigurationEntry) { 5 | // eslint-disable-next-line max-len 6 | super(`Function of name "${module.moduleName}" was not found in provided functions! [key: ${module.key}, type: ${module.moduleType}], package: ${module.modulePackage}]`); 7 | this.name = FunctionNotFoundError.name; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/configuration/configuration-type.ts: -------------------------------------------------------------------------------- 1 | import { Addon } from "./addon-type.js"; 2 | import { BusinessOperationType } from "./business-operations/business-operations-type.js"; 3 | import { SchemaType } from "./schemas/schemas-type.js"; 4 | 5 | export interface ConfigurationType { 6 | name ?: string; 7 | version ?: string; 8 | envs ?: EnvironmentVariable[]; 9 | schemas : SchemaType[]; 10 | businessOperations : BusinessOperationType[]; 11 | addons : Addon[]; 12 | } 13 | 14 | export interface EnvironmentVariable { 15 | key : string; 16 | value : string; 17 | } 18 | -------------------------------------------------------------------------------- /src/bin/replace-references.ts: -------------------------------------------------------------------------------- 1 | import { Configuration } from "../configuration/configuration.js"; 2 | import { PathUtils } from "../configuration/path-alias-utils.js"; 3 | 4 | const referenceableProperties : Array = [ 5 | "schemas", 6 | "businessOperations", 7 | "addons", 8 | ]; 9 | 10 | 11 | export async function replaceReferences (input : unknown) : Promise { 12 | for(const property of referenceableProperties) { 13 | if(typeof input[property] === "string") { 14 | input[property] = await PathUtils.getContents(input[property]); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/factories/schema-factory.ts: -------------------------------------------------------------------------------- 1 | import { SchemaType } from "../../src/configuration/schemas/schemas-type.js"; 2 | import { faker } from "@faker-js/faker"; 3 | import { schemaFormatFactory } from "./schema-format-factory.js"; 4 | 5 | export const schemaFactory = (predefined : Partial) : SchemaType => { 6 | 7 | const creationInput : SchemaType = { 8 | name: predefined.name ?? faker.person.jobArea(), 9 | format : predefined.format ?? schemaFormatFactory(), 10 | identifier : predefined.identifier ?? faker.string.alphanumeric(8), 11 | }; 12 | 13 | return creationInput; 14 | }; 15 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/array/length.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const arrayLengthBopsFunction = (input : { array : unknown[] }) : unknown => { 4 | return ({ result: input.array.length }); 5 | }; 6 | 7 | export const arrayLengthBopsFunctionInformation : InternalMetaFunction = { 8 | functionName: "arrayLength", 9 | description: "Gets the length of the list", 10 | input: { 11 | array: { type: "array", subtype: "any", required: true }, 12 | }, 13 | output: { 14 | result: { type: "number", required: true }, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /test/bops-functions/bops-engine/test-data/business-operations/envs-bop.ts: -------------------------------------------------------------------------------- 1 | import { BusinessOperationType } from "../../../../../src/configuration/business-operations/business-operations-type.js"; 2 | 3 | export const envBop : BusinessOperationType = { 4 | identifier: "env-bop", 5 | input: {}, 6 | output: {}, 7 | constants: [], 8 | variables: [], 9 | configuration: [ 10 | { 11 | moduleName: "output", 12 | moduleType: "output", 13 | key: 3, 14 | dependencies: [ 15 | { origin: "env", originPath: "testEnv", targetPath: "output" }, 16 | ], 17 | }, 18 | ], 19 | }; 20 | -------------------------------------------------------------------------------- /test/schema/common-schemas/complex-example-schema.ts: -------------------------------------------------------------------------------- 1 | import { SchemaType } from "../../../src/configuration/schemas/schemas-type.js"; 2 | 3 | export const complexExampleSchema : SchemaType = { 4 | name: "exampleComplexSchema", 5 | format: { 6 | name: { type: "string" }, 7 | hobbies: { 8 | type: "array", 9 | subtype: "string", 10 | }, 11 | acquaintances: { 12 | type: "array", 13 | subtype: { 14 | name: { type: "string" }, 15 | gender: { type: "string" }, 16 | age: { type: "number" }, 17 | }, 18 | }, 19 | }, 20 | identifier: "someIdentifier", 21 | }; 22 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/boolean/bool-to-number.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const boolToNumberBopsFunction = (input : { boolean : boolean }) : unknown => { 4 | return ({ result: input.boolean ? 1 : 0 }); 5 | }; 6 | 7 | export const boolToNumberBopsFunctionInformation : InternalMetaFunction = { 8 | functionName: "boolToNumber", 9 | description: "Converts a boolean to its numerical representation", 10 | input: { 11 | boolean: { type: "boolean", required: true }, 12 | }, 13 | output: { 14 | result: { type: "number", required: true }, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/boolean/bool-to-string.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const boolToStringBopsFunction = (input : { boolean : boolean }) : unknown => { 4 | return ({ result: input.boolean.toString() }); 5 | }; 6 | 7 | export const boolToStringBopsFunctionInformation : InternalMetaFunction = { 8 | functionName: "boolToString", 9 | description: "Converts a boolean to its string representation", 10 | input: { 11 | boolean: { type: "boolean", required: true }, 12 | }, 13 | output: { 14 | result: { type: "string", required: true }, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /src/common/logger/logger-types.ts: -------------------------------------------------------------------------------- 1 | 2 | export enum LogLevels { 3 | fatal, 4 | success, 5 | operation, 6 | error, 7 | warn, 8 | info, 9 | debug 10 | } 11 | 12 | export const logLevelsArray = Object.keys(LogLevels) 13 | .filter(key => !isNaN(Number(LogLevels[key]))) as LogLevelsType[]; 14 | 15 | export type LogLevelsType = keyof typeof LogLevels; 16 | 17 | export type LoggerType = { [Level in LogLevelsType] : (...data : unknown[]) => void; }; 18 | export type StylingFunction = (data : unknown[]) => string 19 | export type Styles = 20 | Partial<{ [Level in LogLevelsType] : StylingFunction }> & 21 | { default : StylingFunction } 22 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/number/random.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | import MersenneTwister from "mersenne-twister"; 3 | 4 | export const randomNumberBopsFunction = () : unknown => { 5 | const random = new MersenneTwister(); 6 | 7 | return ({ result: random.random() }); 8 | }; 9 | 10 | export const randomNumberBopsFunctionInformation : InternalMetaFunction = { 11 | functionName: "randomNumber", 12 | description: "Generates a Pseudo Random number ([0, 1]) using Mersenne-Twister Algorithm", 13 | output: { 14 | result: { type: "number", required: true }, 15 | }, 16 | input :{}, 17 | }; 18 | -------------------------------------------------------------------------------- /src/entities/meta-entity.ts: -------------------------------------------------------------------------------- 1 | import constants from "../common/constants.js"; 2 | 3 | export type EntityValue = T & { 4 | identifier : string 5 | } 6 | 7 | /** handles ownership */ 8 | export class MetaEntity { 9 | public readonly owner : symbol; 10 | public data : T; 11 | 12 | public constructor (owner : symbol | string, entity : T) { 13 | let usedOwner = typeof owner === "string" ? Symbol(owner) : owner; 14 | if (owner === constants.RUNTIME_ENGINE_IDENTIFIER) { 15 | usedOwner = constants.ENGINE_OWNER; 16 | } 17 | 18 | this.owner = usedOwner; 19 | this.data = entity; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/configuration/test-data/schema/missing-reference-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemas": [ 3 | { 4 | "name": "arrayTypesSchemas", 5 | "format": { 6 | "stringArray": { "type": "array", "data": "string" }, 7 | "booleanArray": { "type": "array", "data": "boolean" }, 8 | "dateArray": { "type": "array", "data": "date" }, 9 | "numberArray": { "type": "array", "data": "number"}, 10 | "objectArray": { "type": "array", "data": { 11 | "nestedArrayProperty": { "type": "string" } 12 | } }, 13 | "referencedArray": { "type":"array", "data": "string", "refName": "some" } 14 | } 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /test/helpers/retry.ts: -------------------------------------------------------------------------------- 1 | import { sleep } from "./sleep.js"; 2 | 3 | // eslint-disable-next-line max-lines-per-function 4 | export const retry = async (exec : Function, limit : number) : Promise => { 5 | console.log("Scheduling execution of retryable function - Limit: ", limit); 6 | 7 | let attempt = 1; 8 | let lastError = "NO ERROR PROVIDED"; 9 | while (attempt < limit) { 10 | console.log("Attempt to execute: ", attempt); 11 | 12 | try { 13 | await exec(); 14 | break; 15 | } catch (error) { 16 | await sleep(2000); 17 | attempt ++; 18 | lastError = error; 19 | } 20 | } 21 | 22 | throw lastError; 23 | }; 24 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/logic/and.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const andGateBopsFunction = (input : { A : boolean; B : boolean }) : unknown => { 4 | const bothTrue = input.A && input.B; 5 | 6 | return ({ bothTrue }); 7 | }; 8 | 9 | export const andGateBopsFunctionInformation : InternalMetaFunction = { 10 | functionName: "and", 11 | description: "And gate comparing boolean values for A and B", 12 | input: { 13 | A: { type: "boolean", required: true }, 14 | B: { type: "boolean", required: true }, 15 | }, 16 | output: { 17 | bothTrue: { type: "boolean", required: true }, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/logic/lower-than.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const lowerThanBopsFunction = (input : { A : number; B : number }) : unknown => { 4 | const isLower = input.A < input.B; 5 | 6 | return ({ isLower }); 7 | }; 8 | 9 | export const lowerThanBopsFunctionInformation : InternalMetaFunction = { 10 | functionName: "lowerThan", 11 | description: "compares A to B, returning if A is lower than B", 12 | input: { 13 | A: { type: "number", required: true }, 14 | B: { type: "number", required: true }, 15 | }, 16 | output: { 17 | isLower: { type: "boolean", required: true }, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/string/index-of.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const indexOfStringFunction = (input : { string : string; search : string }) : unknown => { 4 | return ({ index: input.string.indexOf(input.search) }); 5 | }; 6 | 7 | export const indexOfStringFunctionInformation : InternalMetaFunction = { 8 | functionName: "indexOfString", 9 | description: "Gets the index of a substring in the string", 10 | input: { 11 | string: { type: "string", required: true }, 12 | search: { type: "string", required: true }, 13 | }, 14 | output: { 15 | index: { type: "number", required: true }, 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /test/helpers/test-throw.ts: -------------------------------------------------------------------------------- 1 | export const testThrow = (encapsulatedFunction : () => void) : { thrown : boolean; error : Error } => { 2 | let thrown = false; 3 | let error; 4 | 5 | try { 6 | encapsulatedFunction(); 7 | } catch (e) { 8 | thrown = true; 9 | error = e; 10 | } 11 | 12 | return { thrown, error }; 13 | }; 14 | 15 | export const asyncTestThrow = async (encapsulatedFunction : () => Promise) 16 | : Promise<{ thrown : boolean; error : Error }> => { 17 | let thrown = false; 18 | let error; 19 | 20 | await encapsulatedFunction() 21 | .catch((e) => { 22 | thrown = true; 23 | error = e; 24 | }); 25 | 26 | return { thrown, error }; 27 | }; 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/general-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: General Issue 3 | about: For everything else about meta-system and its environment 4 | title: "" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Description 11 | Here you should tell us a bit about what this issue is about. Try to not leave any piece of information behind, as it can improve the chances we understand the problem better. 12 | 13 | ## Solving Suggestion 14 | Under this section, you can try to tell us how you would expect a solution for this specific issue to happen, or just leave it blank if you want help from the community or contributors. 15 | 16 | --------- 17 | Don't forget to add the tags to your issue. 18 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/logic/higher-than.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const higherThanBopsFunction = (input : { A : number; B : number }) : unknown => { 4 | const isHigher = input.A > input.B; 5 | 6 | return ({ isHigher }); 7 | }; 8 | 9 | export const higherThanBopsFunctionInformation : InternalMetaFunction = { 10 | functionName: "higherThan", 11 | description: "compares A to B, returning if A is higher than B", 12 | input: { 13 | A: { type: "number", required: true }, 14 | B: { type: "number", required: true }, 15 | }, 16 | output: { 17 | isHigher: { type: "boolean", required: true }, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/logic/or.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const orGateBopsFunction = (input : { A : boolean; B : boolean }) : unknown => { 4 | const eitherAreTrue = input.A || input.B; 5 | 6 | return ({ eitherIsTrue: eitherAreTrue }); 7 | }; 8 | 9 | export const orGateBopsFunctionInformation : InternalMetaFunction = { 10 | functionName: "or", 11 | description: "OR gate comparing boolean values for A and B", 12 | output: { 13 | eitherIsTrue: { type: "boolean", required: true }, 14 | }, 15 | input: { 16 | A: { type: "boolean", required: true }, 17 | B: { type: "boolean", required: true }, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/array/join.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const arrayJoinBopsFunction = (input : { array : unknown[]; separator ?: string }) : unknown => { 4 | return ({ result: input.array.join(input.separator ?? ",") }); 5 | }; 6 | 7 | export const arrayJoinBopsFunctionInformation : InternalMetaFunction = { 8 | functionName: "arrayJoin", 9 | description: "Joins Items of an array", 10 | input: { 11 | array: { type: "array", subtype: "any", required: true }, 12 | separator: { type: "string", required: false }, 13 | }, 14 | output: { 15 | result: { type: "array", subtype: "any", required: true }, 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /test/configuration/test-data/schema/deep-object-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemas": [ 3 | { 4 | "name": "test1", 5 | "identifier": "test1", 6 | "dbProtocol": "fakeDb", 7 | "format": { 8 | "shallowProperty": { "type": "string" }, 9 | "oneLevelDeepProperty": { "type": "object", "subtype": { 10 | "nestedProperty": { "type": "string" }, 11 | "anotherNestedProperty": { "type": "number" } 12 | } }, 13 | "twoLevelDeepProperty": { "type": "object", "subtype": { 14 | "nestedDeepProperty": { "type": "object", "subtype": { 15 | "reallyDeepNestedProp": { "type": "boolean" } 16 | } } 17 | } } 18 | } 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /test/schema/random-partial-object.ts: -------------------------------------------------------------------------------- 1 | import { CloudedObject } from "../../src/common/types/clouded-object.js"; 2 | import { SchemaType } from "../../src/configuration/schemas/schemas-type.js"; 3 | import { entityFactory } from "../factories/entity-factory.js"; 4 | import { faker } from "@faker-js/faker"; 5 | 6 | export const randomPartialObject = (fromSchema : SchemaType) : CloudedObject => { 7 | const resultObject : CloudedObject = {}; 8 | const computedEntity = entityFactory(fromSchema.format); 9 | 10 | for (const propertyName in computedEntity) { 11 | if (faker.datatype.boolean()) { 12 | resultObject[propertyName] = computedEntity[propertyName]; 13 | } 14 | } 15 | 16 | return resultObject; 17 | }; 18 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/object/to-string.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | import { CloudedObject } from "../../../common/types/clouded-object.js"; 3 | 4 | export const objectToStringBopsFunction = (input : { object : CloudedObject }) : unknown => { 5 | return ({ result: JSON.stringify(input.object) }); 6 | }; 7 | 8 | export const objectToStringBopsFunctionInformation : InternalMetaFunction = { 9 | functionName: "objectToString", 10 | description: "Transforms an object into a JSON-like string", 11 | input: { 12 | object: { type: "cloudedObject", required: true }, 13 | }, 14 | output: { 15 | result: { type: "string", required: true }, 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/assertion/is-nill.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 4 | export const isNillBopsFunction = (input : { value : any }) : unknown => { 5 | const isNill = input.value === undefined || input.value === null; 6 | 7 | return ({ isNill }); 8 | }; 9 | 10 | export const isNillBopsFunctionInformation : InternalMetaFunction = { 11 | functionName: "isNill", 12 | description: "Evaluates true if the value provided is undefined or null.", 13 | input: { 14 | value: { type: "any", required: true }, 15 | }, 16 | output: { 17 | isNill: { type: "boolean", required: true }, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/logic/lower-or-equal-to.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const lowerOrEqualToBopsFunction = (input : { A : number; B : number }) : unknown => { 4 | const isLowerOrEqual = input.A <= input.B; 5 | 6 | 7 | return ({ isLowerOrEqual }); 8 | }; 9 | 10 | export const lowerOrEqualToBopsFunctionInformation : InternalMetaFunction = { 11 | functionName: "lowerOrEqualTo", 12 | description: "compares A to B, returning if A is lower or equal to B", 13 | input: { 14 | A: { type: "number", required: true }, 15 | B: { type: "number", required: true }, 16 | }, 17 | output: { 18 | isLowerOrEqual: { type: "boolean", required: true }, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/logic/higher-or-equal-to.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const higherOrEqualToBopsFunction = (input : { A : number; B : number }) : unknown => { 4 | const isHigherOrEqual = input.A >= input.B; 5 | 6 | return ({ isHigherOrEqual }); 7 | }; 8 | 9 | export const higherOrEqualToBopsFunctionInformation : InternalMetaFunction = { 10 | functionName: "higherOrEqualTo", 11 | description: "compares A to B, returning if A is higher or equal to B", 12 | input: { 13 | A: { type: "number", required: true }, 14 | B: { type: "number", required: true }, 15 | }, 16 | output: { 17 | isHigherOrEqual: { type: "boolean", required: true }, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/string/concat.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const stringConcatFunction = (input : { strings : Record }) 4 | : unknown => { 5 | let result = ""; 6 | Object.values(input.strings).forEach(value => { 7 | result += value.toString(); 8 | }); 9 | return ({ result }); 10 | }; 11 | 12 | export const stringConcatFunctionInformation : InternalMetaFunction = { 13 | functionName: "stringConcat", 14 | description: "Concatenates all given strings in order", 15 | input: { 16 | strings: { type: "object", required: true, subtype: "string" }, 17 | }, 18 | output: { 19 | result: { type: "string", required: true }, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /test/addons/test-addon/entrypoint.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/explicit-function-return-type */ 2 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function 3 | export const boot = () => { 4 | // Empty as it is required by the addons API 5 | }; 6 | 7 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types 8 | export const configure = (broker) => { 9 | const myModuleFunc = ({ arg }) => { 10 | return { type: typeof arg }; 11 | }; 12 | 13 | broker.addonsFunctions.register(myModuleFunc, { 14 | input: { arg: { "type": "any" } }, 15 | output: { type: { "type": "any" } }, 16 | functionName: "myModuleFunc", 17 | }); 18 | 19 | broker.done(); 20 | }; 21 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "NodeNext", 4 | "esModuleInterop": true, 5 | "target": "ES2020", 6 | "lib": ["es6", "ES2017"], 7 | "types": ["node", "mocha"], 8 | "moduleResolution": "NodeNext", 9 | "resolveJsonModule": true, 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "noImplicitAny": false, 13 | "sourceMap": true, 14 | "declaration": true, 15 | "outDir": "dist", 16 | "baseUrl": ".", 17 | "paths": { 18 | "*": [ 19 | "node_modules/*", 20 | "src/*", 21 | "test/*" 22 | ] 23 | } 24 | }, 25 | "exclude": [ 26 | "test/*", 27 | "dist/*" 28 | ], 29 | "inlcude": [ 30 | "src/*" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | // Base TSConfig, used for consistency. 2 | // Other configurations may extend this file with the "extends" parameter. 3 | { 4 | "compilerOptions": { 5 | "module": "ESNext", 6 | "esModuleInterop": true, 7 | "target": "ES2021", 8 | "lib": ["es6", "ES2021"], 9 | "types": ["node", "mocha"], 10 | "moduleResolution": "Bundler", 11 | "resolveJsonModule": true, 12 | "experimentalDecorators": true, 13 | "emitDecoratorMetadata": true, 14 | "noImplicitAny": false, 15 | "sourceMap": true, 16 | "outDir": "dist", 17 | "baseUrl": ".", 18 | "declaration": true, 19 | "paths": { 20 | "*": [ 21 | "node_modules/*", 22 | "src/*", 23 | "test/*" 24 | ] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/flux-control/try-catch.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const tryCatchBopsFunction = async (input : { function : Function }) : Promise => { 4 | try { 5 | return { result: input.function() }; 6 | } catch (error) { 7 | return { error }; 8 | } 9 | }; 10 | 11 | export const tryCatchBopsFunctionInformation : InternalMetaFunction = { 12 | functionName: "tryCatch", 13 | description: "Tries to execute a function, returning the result or error, if present", 14 | input: { 15 | function : { type: "function", required: true }, 16 | }, 17 | output: { 18 | result: { type: "any", required: false }, 19 | error: { type: "any", required: false }, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/number/to-string.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const numberToStringFunction = (input : { number : number }) : unknown => { 4 | if (Number.isNaN(input.number)) { 5 | return ({ errorMessage: "Cannot convert NaN" }); 6 | } 7 | 8 | return ({ result: input.number.toString() }); 9 | }; 10 | 11 | export const numberToStringFunctionInformation : InternalMetaFunction = { 12 | functionName: "numberToString", 13 | description: "Gets the index of a substring in the string", 14 | output: { 15 | result: { type: "number", required: false }, 16 | errorMessage: { type: "string", required: false }, 17 | }, 18 | input: { 19 | number: { type: "number", required: true }, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /src/bundler/bundler-types.ts: -------------------------------------------------------------------------------- 1 | export type FileImportInfo = { 2 | fullString : string, 3 | originFile : string, 4 | parentFile : string, 5 | importedObjects : Array<{ 6 | value : string, 7 | alias ?: string 8 | }>, 9 | defaultImportAlias ?: string, 10 | } 11 | 12 | export type ExportInfo = { 13 | fullString : string, 14 | value : string, 15 | name : string, 16 | isDefault : boolean 17 | } 18 | 19 | export type StaticImportInfo = string 20 | 21 | export type ImportInfo = { 22 | fileImports : Array; 23 | staticImports : Set; 24 | } 25 | 26 | export type ImportStatements = { 27 | ESImports : { 28 | static : Array, 29 | dynamic : Array, 30 | }, 31 | CJSImports : Array, 32 | }; 33 | -------------------------------------------------------------------------------- /test/schema/common-schemas/multiple-types-schema.ts: -------------------------------------------------------------------------------- 1 | import { SchemaType } from "../../../src/configuration/schemas/schemas-type.js"; 2 | 3 | export const multipleTypesSchema : SchemaType = { 4 | name: "exampleFlatSchema", 5 | format: { 6 | name: { type: "string" }, 7 | age: { type: "number" }, 8 | birthDate: { type: "date" }, 9 | isRegistered: { type: "boolean" }, 10 | hobbies: { 11 | type: "array", 12 | subtype: "string", 13 | }, 14 | luckyNumbers: { 15 | type: "array", 16 | subtype: "number", 17 | }, 18 | familyBirthdays: { 19 | type: "array", 20 | subtype: "date", 21 | }, 22 | secretBoolSequence: { 23 | type: "array", 24 | subtype: "boolean", 25 | }, 26 | }, 27 | identifier: "someIdentifier", 28 | }; 29 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/math/absolute.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const absoluteBopsFunction = (input : { input : number }) : unknown => { 4 | const result = Math.abs(input.input); 5 | 6 | if (Number.isNaN(Number(result))) { 7 | return ({ errorMessage: "One of the arguments provided was not a number" }); 8 | } 9 | 10 | return ({ result }); 11 | }; 12 | 13 | export const absoluteFunctionInformation : InternalMetaFunction = { 14 | functionName: "absolute", 15 | description: "Gets the absolute value of a number", 16 | input: { 17 | input: { type: "number", required: true }, 18 | }, 19 | output: { 20 | result: { type: "number", required: false }, 21 | errorMessage: { type: "string", required: false }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: meta-system-engine 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/object/create.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const createObjectBopsFunction = (input : { key ?: string; value ?: unknown }) : unknown => { 4 | const resultObject = {}; 5 | 6 | if (input.key !== undefined) { 7 | resultObject[input.key] = input.value; 8 | } 9 | 10 | return ({ created: resultObject }); 11 | }; 12 | 13 | export const createObjectBopsFunctionInformation : InternalMetaFunction = { 14 | functionName: "createObject", 15 | description: "Creates an object with the given key and value", 16 | input: { 17 | key: { type: "string", required: false }, 18 | value: { type: "any", required: false }, 19 | }, 20 | output: { 21 | created: { type: "cloudedObject", required: true }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /src/entities/entity-action.ts: -------------------------------------------------------------------------------- 1 | import { ObjectDefinition } from "@meta-system/object-definition"; 2 | import { EntityValue } from "./meta-entity.js"; 3 | import { EntityRepository } from "./repository.js"; 4 | 5 | export class EntityAction> { 6 | public interface : { input : ObjectDefinition, output : ObjectDefinition }; 7 | 8 | // eslint-disable-next-line max-params 9 | public constructor ( 10 | public readonly permission : string, 11 | public readonly name : string, 12 | public action : (repo : T) => Function, 13 | public readonly callableInRuntime : boolean = false, 14 | ) {} 15 | 16 | public withInterface (input : ObjectDefinition, output : ObjectDefinition) : void { 17 | this.interface = { 18 | input, output, 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/string/to-number.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const stringToNumberBopsFunction = (input : { string : string }) : unknown => { 4 | const result = Number(input.string); 5 | 6 | if (Number.isNaN(result)) { 7 | return ({ errorMessage: "Given string is not convertible to a number" }); 8 | } 9 | 10 | return ({ result }); 11 | }; 12 | 13 | export const stringToNumberBopsFunctionInformation : InternalMetaFunction = { 14 | functionName: "stringToNumber", 15 | description: "Converts a given string to a Number", 16 | input: { 17 | string: { type: "string", required: true }, 18 | }, 19 | output: { 20 | result: { type: "number", required: false }, 21 | errorMessage: { type: "string", required: false }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /src/broker/broker-presets.ts: -------------------------------------------------------------------------------- 1 | import { EntityValue } from "entities/meta-entity.js"; 2 | import { EntityAction } from "../entities/entity-action.js"; 3 | import { EntityRepository } from "../entities/repository.js"; 4 | import { loggerActions, loggerSingleton } from "../entities/singletons/logger.js"; 5 | 6 | type PresetType = { 7 | repo ?: EntityRepository, 8 | actions : EntityAction>[] 9 | } 10 | 11 | export const brokerPresets : Record = { 12 | "logger": { 13 | repo: new EntityRepository(loggerSingleton), 14 | actions: loggerActions, 15 | }, 16 | "env": { 17 | actions: [], 18 | }, 19 | "schema": { 20 | actions: [], 21 | }, 22 | "businessOperation": { 23 | actions: [], 24 | }, 25 | "addon": { 26 | actions: [], 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/system/execute-with-args.ts: -------------------------------------------------------------------------------- 1 | import { CloudedObject } from "../../../common/types/clouded-object.js"; 2 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 3 | 4 | export const executeWithArgs = async (input : { module : Function; arguments : CloudedObject }) : Promise => { 5 | const moduleOutput = await input.module(input.arguments); 6 | return { moduleOutput }; 7 | }; 8 | 9 | export const executeWithArgsFunctionInformation : InternalMetaFunction = { 10 | functionName: "executeWithArgs", 11 | description: "Gets a function from a functionManager", 12 | input: { 13 | module: { type: "function", required: true }, 14 | arguments: { type: "cloudedObject", required: true }, 15 | }, 16 | output: { 17 | moduleOutput: { type: "any", required: false }, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: For when something goes wrong with MSYS or something in its environment 4 | title: "[BUG] - " 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Bug Description 11 | Explain what happened and why you think the behavior is not correct. 12 | 13 | ## Steps to Reproduce 14 | We need to have clear defined steps on how to reproduce the same behavior you had. Here you should specify them as so: 15 | 1. Type in XXX 16 | 2. Execute Code YYY 17 | 3. [...] 18 | 19 | ## System information 20 | Type in here the essential information about your system. Data like the OS you're running and the CPU are often useful. 21 | 22 | ## Additional information 23 | Here you should describe any information that you think it would help us on understanding the issue a bit better. 24 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/object/values.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | import { CloudedObject } from "../../../common/types/clouded-object.js"; 3 | 4 | export const getObjectValuesBopsFunction = (input : { object : CloudedObject }) : unknown => { 5 | const result = []; 6 | 7 | Object.values(input.object).forEach((value) => { 8 | result.push(value); 9 | }); 10 | 11 | return ({ values: result }); 12 | }; 13 | 14 | export const getObjectValuesBopsFunctionInformation : InternalMetaFunction = { 15 | functionName: "getObjectValues", 16 | description: "Get a list of the values of the given object", 17 | input: { 18 | object: { type: "cloudedObject", required: true }, 19 | }, 20 | output: { 21 | values: { type: "array", subtype: "any", required: true }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/object/keys.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | import { CloudedObject } from "../../../common/types/clouded-object.js"; 3 | 4 | 5 | export const getObjectKeysBopsFunction = (input : { object : CloudedObject }) : unknown => { 6 | const result = []; 7 | 8 | Object.keys(input.object).forEach((keyName) => { 9 | result.push(keyName.toString()); 10 | }); 11 | 12 | return ({ keys: result }); 13 | }; 14 | 15 | export const getObjectKeysBopsFunctionInformation : InternalMetaFunction = { 16 | functionName: "getObjectKeys", 17 | description: "Get a list of the keys of the given object", 18 | input: { 19 | object: { type: "cloudedObject", required: true }, 20 | }, 21 | output: { 22 | keys: { type: "array", subtype: "string", required: true }, 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /src/bops-functions/bops-engine/add-timeout.ts: -------------------------------------------------------------------------------- 1 | import { logger } from "../../common/logger/logger.js"; 2 | import { TTLExceededError } from "./engine-errors/execution-time-exceeded.js"; 3 | 4 | // eslint-disable-next-line max-lines-per-function 5 | export function addTimeout (timeoutMs : number, promise : Function) : Function { 6 | let timeoutHandle : NodeJS.Timeout; 7 | 8 | const result = async (inputs : unknown) : Promise => { 9 | const timeoutPromise = new Promise((_resolve, reject) => { 10 | timeoutHandle = setTimeout(() => reject(new TTLExceededError(timeoutMs)), timeoutMs); 11 | }); 12 | 13 | return Promise.race([ 14 | timeoutPromise, 15 | promise(inputs), 16 | ]).then(promiseResult => { 17 | clearTimeout(timeoutHandle); 18 | return promiseResult; 19 | }); 20 | }; 21 | 22 | return result; 23 | }; 24 | -------------------------------------------------------------------------------- /test/configuration/test-data/bops/no-output.json: -------------------------------------------------------------------------------- 1 | { 2 | "businessOperations": [ 3 | { 4 | "identifier": "carSell", 5 | "input": {}, 6 | "output": {}, 7 | "variables": [], 8 | "constants": [], 9 | "configuration": [ 10 | { 11 | "moduleName": "arrayAt", 12 | "moduleType": "internal", 13 | "key": 2, 14 | "dependencies": [ 15 | { "origin": 3 } 16 | ] 17 | }, 18 | { 19 | "moduleName": "arrayAt", 20 | "moduleType": "internal", 21 | "key": 3, 22 | "dependencies": [ 23 | { "origin": 6 } 24 | ] 25 | }, 26 | { 27 | "moduleName": "arrayAt", 28 | "moduleType": "internal", 29 | "key": 4, 30 | "dependencies": [ 31 | { "origin": 2 } 32 | ] 33 | } 34 | ] 35 | } 36 | ] 37 | } -------------------------------------------------------------------------------- /src/common/meta-file-type.ts: -------------------------------------------------------------------------------- 1 | import { ObjectDefinition } from "@meta-system/object-definition"; 2 | import { EntityPermissions } from "../broker/entity-broker.js"; 3 | 4 | export interface MetaFileType { 5 | name ?: string; 6 | version ?: string; 7 | entrypoint : string; 8 | configurationFormat : ObjectDefinition; 9 | permissions : EntityPermissions[]; 10 | } 11 | 12 | export const metaFileObjectDefinition : ObjectDefinition = { 13 | name: { type: "string", required: false }, 14 | version: { type: "string", required: false }, 15 | entrypoint: { type: "string", required: true }, 16 | configurationFormat: { type: "__%objdef%__", required: true }, 17 | permissions: { type: "array", required: true, subtype: { 18 | permissions: { type: "array", required: true, subtype: "string" }, 19 | entity: { type: "string", required: true }, 20 | } }, 21 | }; 22 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/string/replace.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const stringReplaceFunction = (input : { baseString : string; search : string; replacer : string }) 4 | : unknown => { 5 | let result = input.baseString; 6 | while (result.includes(input.search)) { 7 | result = result.replace(input.search, input.replacer); 8 | } 9 | return ({ result }); 10 | }; 11 | 12 | export const stringReplaceFunctionInformation : InternalMetaFunction = { 13 | functionName: "stringReplace", 14 | description: "Replaces a part of a string", 15 | input: { 16 | baseString: { type: "string", required: true }, 17 | search: { type: "string", required: true }, 18 | replacer: { type: "string", required: true }, 19 | }, 20 | output: { 21 | result: { type: "string", required: true }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/logic/equal.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 3 | export const isEqualToBopsFunction = (input : { A : any; B : any }) : unknown => { 4 | if(typeof input.A === "object" && typeof input.B === "object") { 5 | return { isEqual: JSON.stringify(input.A) === JSON.stringify(input.B) }; 6 | } 7 | 8 | const isEqual = input.A === input.B; 9 | return ({ isEqual }); 10 | }; 11 | 12 | export const isEqualToBopsFunctionInformation : InternalMetaFunction = { 13 | functionName: "equalTo", 14 | description: "compares A to B, returning if A is lower than B", 15 | input: { 16 | A: { type: "any", required: true }, 17 | B: { type: "any", required: true }, 18 | }, 19 | output: { 20 | isEqual: { type: "boolean", required: true }, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/string/char-at.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const charAtBopsFunction = (input : { string : string; index : number }) : unknown => { 4 | const found = input.string[input.index]; 5 | 6 | if (found === undefined) { 7 | return ({ notFoundMessage: "There is no character present at the given index" }); 8 | } 9 | 10 | return ({ found }); 11 | }; 12 | 13 | export const charAtBopsFunctionInformation : InternalMetaFunction = { 14 | functionName: "charAt", 15 | description: "Gets the character in the string at the index given", 16 | input: { 17 | string: { type: "string", required: true }, 18 | index: { type: "number", required: true }, 19 | }, 20 | output: { 21 | found: { type: "string", required: false }, 22 | notFoundMessage: { type: "string", required: false }, 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/array/array-at.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const arrayAtBopsFunction = (input : { array : unknown[]; index : number }) : unknown => { 4 | const found = input.array[input.index]; 5 | 6 | if (found === undefined) { 7 | return ({ notFoundMessage: "There is no item present at the given index" }); 8 | } 9 | 10 | return ({ found }); 11 | }; 12 | 13 | export const arrayAtBopsFunctionInformation : InternalMetaFunction = { 14 | functionName: "arrayAt", 15 | description: "Gets the item in the array at the index given", 16 | input: { 17 | array: { type: "array", subtype: "any", required: true }, 18 | index: { type: "number", required: true }, 19 | }, 20 | output: { 21 | found: { type: "any", required: false }, 22 | notFoundMessage: { type: "string", required: false }, 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /src/entities/helpers/validate-meta-file.ts: -------------------------------------------------------------------------------- 1 | import { validateObject } from "@meta-system/object-definition"; 2 | import { logger } from "../../common/logger/logger.js"; 3 | import { MetaFileType } from "../../common/meta-file-type.js"; 4 | 5 | const metaFileTypeDef = { 6 | name: { type: "string", required: true }, 7 | version: { type: "string", required: false }, 8 | entrypoint: { type: "string", required: true }, 9 | }; 10 | 11 | export function validateMetaFile (file : unknown, identifier : string) : asserts file is MetaFileType { 12 | logger.info(`Validating meta-file for "${identifier}"`); 13 | const validationResult = validateObject(file, metaFileTypeDef); 14 | if(validationResult.errors.length > 0) { 15 | throw Error(`"${identifier}" Does not have a valid Meta-File:\n` + 16 | validationResult.errors.map(error => `${error.path} :: ${error.error}`).join("\n"), 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/object/get-value.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | import { CloudedObject } from "../../../common/types/clouded-object.js"; 3 | 4 | 5 | export const getObjectPropertyValueBopsFunction = (input : { object : CloudedObject; key : string }) : unknown => { 6 | const resultObject = {}; 7 | 8 | Object.assign(resultObject, input.object); 9 | 10 | return ({ value: resultObject[input.key] }); 11 | }; 12 | 13 | export const getObjectPropertyValueBopsFunctionInformation : InternalMetaFunction = { 14 | functionName: "getObjectPropertyValue", 15 | description: "Get a value of an object's propery by one Key", 16 | input: { 17 | object: { type: "cloudedObject", required: true }, 18 | key: { type: "string", required: true }, 19 | }, 20 | output: { 21 | value: { type: "cloudedObject", required: true }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /src/bops-functions/internal-meta-function.ts: -------------------------------------------------------------------------------- 1 | import { MetaFunction } from "./meta-function-type.js"; 2 | 3 | export type InternalMetaFunction = Omit; 4 | 5 | // This section is just to ensure that the interfaces are interchangeable when MetaFunction 6 | // is correcly fulfilled. 7 | const metaFunctionType : MetaFunction = { 8 | functionName: "", 9 | description: "this is a test meta-function", 10 | output: { 11 | result: { type: "number", required: false }, 12 | errorMessage: { type: "string", required: false }, 13 | }, 14 | input: { 15 | numberToRound: { type: "number", required: true }, 16 | precision: { type: "number", required: false }, 17 | }, 18 | entrypoint: "", 19 | mainFunction: "", 20 | }; 21 | 22 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 23 | const convertedMetaFunction : InternalMetaFunction = metaFunctionType; 24 | -------------------------------------------------------------------------------- /test/configuration/test-data/schema/array-types-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemas": [ 3 | { 4 | "name": "arrayTypesSchemas", 5 | "identifier": "test1", 6 | "dbProtocol": "fakeDb", 7 | "format": { 8 | "stringArray": { "type": "array", "subtype": "string" }, 9 | "booleanArray": { "type": "array", "subtype": "boolean" }, 10 | "dateArray": { "type": "array", "subtype": "date" }, 11 | "numberArray": { "type": "array", "subtype": "number"}, 12 | "objectArray": { "type": "array", "subtype": { 13 | "nestedArrayProperty": { "type": "string" } 14 | } }, 15 | "referencedArray": { "type":"array", "subtype": "string", "refName": "other" } 16 | } 17 | }, 18 | { 19 | "name": "other", 20 | "identifier": "other", 21 | "dbProtocol": "fakeDb", 22 | "format": { 23 | "aProperty": { "type": "boolean" } 24 | } 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/string/count.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const countStringFunction = (input : { string : string; search : string }) : unknown => { 4 | let count = 0; 5 | const skipLength = input.search.length; 6 | 7 | for (let i = 0; i <= input.string.length -1; i++) { 8 | const index = input.string.indexOf(input.search, i); 9 | if (index < 0) break; 10 | 11 | count ++; 12 | 13 | i += skipLength + index; 14 | } 15 | 16 | return ({ count }); 17 | }; 18 | 19 | export const countStringFunctionInformation : InternalMetaFunction = { 20 | functionName: "countString", 21 | description: "Gets the amount of times a substring appears in the string", 22 | input: { 23 | string: { type: "string", required: true }, 24 | search: { type: "string", required: true }, 25 | }, 26 | output: { 27 | count: { type: "number", required: true }, 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /src/entities/helpers/validate-definition.ts: -------------------------------------------------------------------------------- 1 | import { ObjectDefinition, validateObject } from "@meta-system/object-definition"; 2 | import { logger } from "../../common/logger/logger.js"; 3 | 4 | const definitionTypeDef = { 5 | functionName : { type: "string", required: true }, 6 | description: { type: "string", required: false }, 7 | input: { type: "cloudedObject", required: true }, 8 | output: { type: "cloudedObject", required: true }, 9 | }; 10 | 11 | export function validateDefinition (definition : unknown, identifier : string) 12 | : asserts definition is ObjectDefinition { 13 | logger.info(`Validating definition for "${identifier}"`); 14 | const validationResult = validateObject(definition, definitionTypeDef); 15 | if(validationResult.errors.length > 0) { 16 | throw Error(`"${identifier}" Does not have a valid definition:\n` + 17 | validationResult.errors.map(error => `${error.path} :: ${error.error}`).join("\n"), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/common/environment-start.ts: -------------------------------------------------------------------------------- 1 | import { Command } from "commander"; 2 | import { environment } from "./execution-env.js"; 3 | import { logger } from "./logger/logger.js"; 4 | import { runtimeDefaults } from "../configuration/runtime-config/defaults.js"; 5 | 6 | export const environmentStart = async ( 7 | fileLocation : string, 8 | withDefaults = true, 9 | command ?: Command, 10 | ) : Promise => { 11 | if (command !== undefined) Object.assign(environment.silent.constants, command.opts()); 12 | const Path = await import("path"); 13 | 14 | await logger.initialize(environment.constants.logLevel); 15 | 16 | environment.constants.configPath = Path.resolve(fileLocation); 17 | environment.constants.configDir = Path.parse(environment.constants.configPath).dir; 18 | 19 | if (withDefaults) { 20 | environment.constants.installDir = Path.resolve( 21 | environment.constants.configDir, 22 | runtimeDefaults.defaultInstallFolder); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /test/bops-functions/prebuilt-functions/assertions.spec.ts: -------------------------------------------------------------------------------- 1 | import { isNillBopsFunction } from "../../../src/bops-functions/prebuilt-functions/assertion/is-nill.js"; 2 | import { expect } from "chai"; 3 | 4 | describe("Assertion BOps Functions", () => { 5 | describe("Is Nill BOps function", () => { 6 | it("Validates string not nill", () => { 7 | const value = "11111"; 8 | 9 | const result = isNillBopsFunction({ value }); 10 | 11 | expect(result).to.be.deep.equal({ isNill: false }); 12 | }); 13 | it("Validates undefined is nill", () => { 14 | const value = undefined; 15 | 16 | const result = isNillBopsFunction({ value }); 17 | 18 | expect(result).to.be.deep.equal({ isNill: true }); 19 | }); 20 | it("Validates null is nill", () => { 21 | const value = null; 22 | 23 | const result = isNillBopsFunction({ value }); 24 | 25 | expect(result).to.be.deep.equal({ isNill: true }); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/object/combine.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | import { CloudedObject } from "../../../common/types/clouded-object.js"; 3 | 4 | 5 | export const combineObjectBopsFunction = (input : { object1 : CloudedObject; object2 : CloudedObject }) : unknown => { 6 | const resultObject = {}; 7 | 8 | Object.assign(resultObject, input.object1); 9 | Object.assign(resultObject, input.object2); 10 | 11 | return ({ combined: resultObject }); 12 | }; 13 | 14 | export const combineObjectBopsFunctionInformation : InternalMetaFunction = { 15 | functionName: "combineObject", 16 | description: "Combine two objects into one, with the latter object overriding conflicting keys", 17 | input: { 18 | object1: { type: "cloudedObject", required: true }, 19 | object2: { type: "cloudedObject", required: true }, 20 | }, 21 | output: { 22 | combined: { type: "cloudedObject", required: true }, 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/math/subtract.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | import { anyIsNan } from "../non-bops-utils/any-is-nan.js"; 3 | import { Decimal } from "decimal.js"; 4 | 5 | export const subtractBopsFunction = (input : { A : number; B : number }) : unknown => { 6 | if (anyIsNan(input.A, input.B)) { 7 | return ({ errorMessage: "One of the arguments provided was not a number" }); 8 | } 9 | 10 | const result = new Decimal(input.A).sub(new Decimal(input.B)); 11 | 12 | return ({ result: result.toNumber() }); 13 | }; 14 | 15 | export const subtractFunctionInformation : InternalMetaFunction = { 16 | functionName: "subtract", 17 | description: "Subtracts B from A", 18 | input: { 19 | A: { type: "number", required: true }, 20 | B: { type: "number", required: true }, 21 | }, 22 | output: { 23 | result: { type: "number", required: false }, 24 | errorMessage: { type: "string", required: false }, 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /src/common/logger/get-system-info.ts: -------------------------------------------------------------------------------- 1 | export async function getSystemInfo () : Promise { 2 | const OS = await import("os"); 3 | const { execSync } = await import("child_process"); 4 | return `System Information: 5 | OS: ${OS.release()} ${OS.type()} ${OS.arch()} 6 | Node Version: ${process.version} 7 | NPM Version: v${execSync("npm -v").toString().trim()} 8 | System RAM: ${Math.round((OS.totalmem()/Math.pow(2,10*3)*10))/10} GB 9 | CPU: ${await parseCPUInfo()}`; 10 | } 11 | 12 | async function parseCPUInfo () : Promise { 13 | const OS = await import("os"); 14 | const cpus = OS.cpus(); 15 | const parsedInfo : { cores : number, model : string }[] = []; 16 | cpus.forEach(cpu => { 17 | const model = parsedInfo.find(info => cpu.model === info.model); 18 | if(model === undefined) parsedInfo.push({ model: cpu.model, cores: 1 }); 19 | else model.cores++; 20 | }); 21 | const infoArray = parsedInfo.map(value => `${value.cores}x ${value.model}`); 22 | return infoArray.join(" | "); 23 | } 24 | -------------------------------------------------------------------------------- /src/configuration/business-operations/business-operation.ts: -------------------------------------------------------------------------------- 1 | import { ObjectDefinition } from "@meta-system/object-definition"; 2 | import { 3 | BopsConfigurationEntry, 4 | BopsConstant, 5 | BopsVariable, 6 | BusinessOperationType, 7 | } from "./business-operations-type.js"; 8 | 9 | export class BusinessOperation implements BusinessOperationType { 10 | public input : ObjectDefinition; 11 | public output : ObjectDefinition; 12 | public constants : BopsConstant[]; 13 | public variables : BopsVariable[]; 14 | public configuration : BopsConfigurationEntry[]; 15 | public ttl ?: number; 16 | public readonly identifier : string; 17 | 18 | public constructor (parameters : BusinessOperationType) { 19 | this.input = parameters.input; 20 | this.output = parameters.output; 21 | this.constants = parameters.constants; 22 | this.configuration = parameters.configuration; 23 | this.variables = parameters.variables; 24 | this.ttl = parameters.ttl; 25 | this.identifier = parameters.identifier; 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /test/bops-functions/bops-engine/test-data/business-operations/external-bop.ts: -------------------------------------------------------------------------------- 1 | import { BusinessOperationType } from "../../../../../src/configuration/business-operations/business-operations-type.js"; 2 | 3 | export const externalBop : BusinessOperationType = { 4 | identifier: "external-bop", 5 | input: { 6 | myName: { type: "string", required: true }, 7 | }, 8 | output: {}, 9 | constants: [], 10 | variables: [], 11 | configuration: [ 12 | { 13 | moduleName: "bops-function-hello-world", 14 | moduleType: "addon", 15 | key: 6, 16 | dependencies: [ 17 | { origin: "inputs", originPath: "myName", targetPath: "nameToGreet" }, 18 | ], 19 | }, 20 | { 21 | moduleName: "output", 22 | moduleType: "output", 23 | key: 3, 24 | dependencies: [ 25 | { origin: 6, originPath: "result.customGreetings", targetPath: "wasGreeted" }, 26 | { origin: 6, originPath: "result.greetingsMade", targetPath: "greetings" }, 27 | ], 28 | }, 29 | ], 30 | }; 31 | -------------------------------------------------------------------------------- /src/common/logger/hook-console-to-file.ts: -------------------------------------------------------------------------------- 1 | import stripAnsi from "strip-ansi"; 2 | 3 | /** 4 | * Creates a new log file in the given dir with the current date. All logs will also 5 | * be written in the file. 6 | * 7 | * @param dirPath The path containing the log files 8 | */ 9 | export async function hookConsoleToFile (dirPath : string) : Promise { 10 | const { createWriteStream, existsSync, mkdirSync } = await import("fs"); 11 | if(!existsSync(dirPath)) mkdirSync(dirPath); 12 | const logFileName = `${dirPath}/${new Date().toISOString()}.log`; 13 | const fileStream = createWriteStream(logFileName); 14 | 15 | const consoleWriteStreams = [process.stderr, process.stdout]; 16 | for(const stream of consoleWriteStreams) { 17 | const oldWrite = stream.write; 18 | stream.write = (string : string, callbackOrEncoding) : boolean => { 19 | oldWrite.apply(process.stdout, [string, callbackOrEncoding]); 20 | fileStream.write(stripAnsi(string), callbackOrEncoding); 21 | return true; 22 | }; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/array/array-map.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const arrayMapBopsFunction = (input : { array : unknown[]; mapper : Function }) : unknown => { 4 | if (typeof input.mapper !== "function") { 5 | return { errorMessage: "No mapper function provided" }; 6 | } 7 | 8 | return { 9 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 10 | result: input.array.map((item, index, array) => input.mapper({ item, index, array }) as any), 11 | }; 12 | }; 13 | 14 | export const arrayMapBopsFunctionInformation : InternalMetaFunction = { 15 | functionName: "arrayMap", 16 | description: "Map the values of an Array into another Array", 17 | input: { 18 | array: { type: "array", subtype: "any", required: true }, 19 | mapper: { type: "function", required: true }, 20 | }, 21 | output: { 22 | result: { type: "array", subtype: "any", required: false }, 23 | errorMessage: { type: "string", required: false }, 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/array/push.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const arrayPushBopsFunction = (input : { 4 | targetArray ?: unknown[]; 5 | newItems ?: Record; 6 | item ?: unknown; 7 | }) : unknown => { 8 | const resultArray = [...(input.targetArray ?? [])]; 9 | const newItemsArray = Object.values(input.newItems ?? {}); 10 | 11 | if (input.item !== undefined) { 12 | resultArray.push(input.item); 13 | } 14 | 15 | return ({ result: [...resultArray, ...newItemsArray ?? [] ] }); 16 | }; 17 | 18 | export const arrayPushBopsFunctionInformation : InternalMetaFunction = { 19 | functionName: "arrayPush", 20 | description: "Pushes items into the array", 21 | input: { 22 | targetArray: { type: "array", subtype: "any", required: true }, 23 | newItems: { type: "cloudedObject", required: false }, 24 | item: { type: "any", required: false }, 25 | }, 26 | output: { 27 | result: { type: "array", subtype: "any", required: true }, 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /test/configuration/test-data/bops/missing-key.json: -------------------------------------------------------------------------------- 1 | { 2 | "businessOperations": [ 3 | { 4 | "identifier": "carSell", 5 | "input": {}, 6 | "output": {}, 7 | "variables": [], 8 | "constants": [], 9 | "configuration": [ 10 | { 11 | "moduleName": "output", 12 | "moduleType": "output", 13 | "key": 1, 14 | "dependencies": [ 15 | { "origin": 2 } 16 | ] 17 | }, 18 | { 19 | "moduleName": "arrayAt", 20 | "moduleType": "internal", 21 | "key": 2, 22 | "dependencies": [ 23 | { "origin": 3 } 24 | ] 25 | }, 26 | { 27 | "moduleName": "arrayAt", 28 | "moduleType": "internal", 29 | "key": 3, 30 | "dependencies": [ 31 | { "origin": 6 } 32 | ] 33 | }, 34 | { 35 | "moduleName": "arrayAt", 36 | "moduleType": "internal", 37 | "key": 4, 38 | "dependencies": [ 39 | { "origin": 2 } 40 | ] 41 | } 42 | ] 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/array/array-filter.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const arrayFilterBopsFunction = (input : { array : unknown[]; filter : Function }) : unknown => { 4 | if (typeof input.filter !== "function") { 5 | return { errorMessage: "No filter function provided" }; 6 | } 7 | 8 | return { 9 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 10 | result: input.array.filter((item, index, array) => input.filter({ item, index, array }) as any), 11 | }; 12 | }; 13 | 14 | export const arrayFilterBopsFunctionInformation : InternalMetaFunction = { 15 | functionName: "arrayFilter", 16 | description: "Filter the values of an Array into another Array", 17 | input: { 18 | array: { type: "array", subtype: "any", required: true }, 19 | filter: { type: "function", required: true }, 20 | }, 21 | output: { 22 | result: { type: "array", subtype: "any", required: false }, 23 | errorMessage: { type: "string", required: false }, 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/math/exponential.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | import { Decimal } from "decimal.js"; 3 | import { anyIsNan } from "../non-bops-utils/any-is-nan.js"; 4 | 5 | export const exponentialBopsFunction = (input : { A : number; B : number}) : unknown => { 6 | if (anyIsNan(input.A, input.B)) { 7 | return ({ errorMessage: "One of the arguments provided was not a number" }); 8 | } 9 | 10 | const A = new Decimal(input.A); 11 | const B = new Decimal(input.B); 12 | 13 | const result = A.pow(B); 14 | 15 | return ({ result: result.toNumber() }); 16 | }; 17 | 18 | export const exponentialFunctionInformation : InternalMetaFunction = { 19 | functionName: "exponential", 20 | description: "Raises A to the power of B", 21 | input: { 22 | A: { type: "number", required: true }, 23 | B: { type: "number", required: true }, 24 | }, 25 | output: { 26 | result: { type: "number", required: false }, 27 | errorMessage: { type: "string", required: false }, 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /test/configuration/test-data/bops/configuration-loop.json: -------------------------------------------------------------------------------- 1 | { 2 | "businessOperations": [ 3 | { 4 | "identifier": "carSell", 5 | "input": {}, 6 | "output": {}, 7 | "variables": [], 8 | "constants": [], 9 | "configuration": [ 10 | { 11 | "moduleName": "output", 12 | "moduleType": "output", 13 | "key": 1, 14 | "dependencies": [ 15 | { "origin": 2 } 16 | ] 17 | }, 18 | { 19 | "moduleName": "arrayAt", 20 | "moduleType": "internal", 21 | "key": 2, 22 | "dependencies": [ 23 | { "origin": 3 } 24 | ] 25 | }, 26 | { 27 | "moduleName": "arrayAt", 28 | "moduleType": "internal", 29 | "key": 3, 30 | "dependencies": [ 31 | { "origin": 4 } 32 | ] 33 | }, 34 | { 35 | "moduleName": "arrayAt", 36 | "moduleType": "internal", 37 | "key": 4, 38 | "dependencies": [ 39 | { "origin": 2 } 40 | ] 41 | } 42 | ] 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /src/configuration/configuration.ts: -------------------------------------------------------------------------------- 1 | import { ConfigurationType } from "../index.js"; 2 | import { Addon } from "./addon-type.js"; 3 | import { BusinessOperation } from "./business-operations/business-operation.js"; 4 | import { EnvironmentVariable } from "./configuration-type.js"; 5 | import { Schema } from "./schemas/schema.js"; 6 | 7 | 8 | export class Configuration implements ConfigurationType { 9 | public readonly name : string; 10 | public readonly version : string; 11 | public readonly envs : EnvironmentVariable[]; 12 | public readonly schemas : Schema[]; 13 | public readonly businessOperations : BusinessOperation[]; 14 | public readonly addons : Addon[]; 15 | 16 | public constructor (input : ConfigurationType) { 17 | this.name = input.name; 18 | this.version = input.version; 19 | this.envs = input.envs; 20 | this.schemas = input.schemas; 21 | this.addons = input.addons; 22 | this.businessOperations = input.businessOperations.map((businessOperationData) => { 23 | return new BusinessOperation(businessOperationData); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/string/template.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const stringTemplateFunction = (input : { template : string; replacers : Record }) 4 | : unknown => { 5 | let result = input.template; 6 | let [head, tail] = [result.indexOf("${"), result.indexOf("}")]; 7 | while ((tail/head) > 1) { 8 | const replacer = result.slice(head+2, tail); 9 | result = result.replace("${" + replacer + "}", input.replacers[replacer] ?? ""); 10 | [head, tail] = [result.indexOf("${"), result.indexOf("}")]; 11 | }; 12 | return ({ result }); 13 | }; 14 | 15 | export const stringTemplateFunctionInformation : InternalMetaFunction = { 16 | functionName: "stringTemplate", 17 | description: "Replaces the values of a string template with the corresponding given values", 18 | input: { 19 | template: { type: "string", required: true }, 20 | replacers: { type: "object", required: true, subtype: "string" }, 21 | }, 22 | output: { 23 | result: { type: "string", required: true }, 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/array/inlcudes.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | import assert from "assert"; 3 | 4 | export const arrayIncludesBopsFunction = (input : { array : unknown[]; searchedItem : unknown }) : unknown => { 5 | if (typeof input.searchedItem === "object") { 6 | const foundItem = input.array.find((value) => { 7 | try { 8 | assert.deepStrictEqual(value, input.searchedItem); 9 | return true; 10 | } catch { 11 | return false; 12 | } 13 | }); 14 | 15 | return ({ result: foundItem !== undefined }); 16 | } 17 | 18 | return ({ result: input.array.includes(input.searchedItem) }); 19 | }; 20 | 21 | export const arrayIncludesBopsFunctionInformation : InternalMetaFunction = { 22 | functionName: "includes", 23 | description: "Verifies if the array contains an Item", 24 | input: { 25 | array: { type: "array", subtype: "any", required: true }, 26 | searchedItem: { type: "any", required: true }, 27 | }, 28 | output: { 29 | result: { type: "boolean", required: true }, 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/array/array-sort.ts: -------------------------------------------------------------------------------- 1 | import clone from "just-clone"; 2 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 3 | 4 | export const arraySortBopsFunction = (input : { array : unknown[]; sorter : Function }) : unknown => { 5 | if (typeof input.sorter !== "function") { 6 | return { errorMessage: "No sorter function provided" }; 7 | } 8 | 9 | const resultArray = clone(input.array); 10 | 11 | return { 12 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 13 | result: resultArray.sort((a, b) => input.sorter({ a, b })), 14 | }; 15 | }; 16 | 17 | export const arraySortBopsFunctionInformation : InternalMetaFunction = { 18 | functionName: "arraySort", 19 | description: "Returns a copy of the input array, sorted by the sorter function", 20 | input: { 21 | array: { type: "array", subtype: "any", required: true }, 22 | sorter: { type: "function", required: true }, 23 | }, 24 | output: { 25 | result: { type: "array", subtype: "any", required: false }, 26 | errorMessage: { type: "string", required: false }, 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/math/square-root.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | import { Decimal } from "decimal.js"; 3 | import { anyIsNan } from "../non-bops-utils/any-is-nan.js"; 4 | 5 | export const squareRootBopsFunction = (input : { A : number }) : unknown => { 6 | if (anyIsNan(input.A)) { 7 | return ({ errorNaN: "One of the arguments provided was not a number" }); 8 | } 9 | 10 | if (input.A < 0) { 11 | return ({ errorNegativeA: "Value must not be a negative number" }); 12 | } 13 | 14 | const A = new Decimal(input.A); 15 | 16 | const result = A.squareRoot(); 17 | 18 | return ({ result: result.toNumber() }); 19 | }; 20 | 21 | export const squareRootFunctionInformation : InternalMetaFunction = { 22 | functionName: "sqrt", 23 | description: "Gets the Square Root of A", 24 | input: { 25 | A: { type: "number", required: true }, 26 | }, 27 | output: { 28 | result: { type: "number", required: false }, 29 | errorNaN: { type: "string", required: false }, 30 | errorNegativeA: { type: "string", required: false }, 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/math/add.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | import { Decimal } from "decimal.js"; 3 | import { anyIsNan } from "../non-bops-utils/any-is-nan.js"; 4 | 5 | export const addBopsFunction = (input : { numbersToAdd : number[] }) : unknown => { 6 | if (anyIsNan(...input.numbersToAdd)) { 7 | return ({ errorMessage: "One of the arguments provided was not a number" }); 8 | } 9 | 10 | const convertedNumbersToAdd = input.numbersToAdd.map((value) => new Decimal(value)); 11 | 12 | let result = new Decimal(0); 13 | convertedNumbersToAdd.forEach((number) => { 14 | result = result.plus(number); 15 | }); 16 | 17 | return ({ result: result.toNumber() }); 18 | }; 19 | 20 | export const addFunctionInformation : InternalMetaFunction = { 21 | functionName: "add", 22 | description: "Adds numbers together", 23 | input: { 24 | numbersToAdd: { type: "array", subtype: "number", required: true }, 25 | }, 26 | output: { 27 | result: { type: "number", required: false }, 28 | errorMessage: { type: "string", required: false }, 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/system/get-system-function.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | // This is just a stub function to be inserted in the manager. The true definition 4 | // of this function is in the FunctionSetup Class 5 | 6 | export const getSystemFunction = ( 7 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 8 | _input : { moduleName : string; modulePackage : string; moduleType : string }, 9 | ) : unknown => { 10 | // eslint-disable-next-line @typescript-eslint/no-empty-function 11 | return () : void => {}; 12 | }; 13 | 14 | export const getSystemFunctionFunctionInformation : InternalMetaFunction = { 15 | functionName: "getSystemFunction", 16 | description: "Gets a function from a functionManager", 17 | input: { 18 | moduleName: { type: "string", required: true }, 19 | modulePackage: { type: "string", required: false }, 20 | moduleType: { type: "string", required: true }, 21 | }, 22 | output: { 23 | callableFunction: { type: "function", required: false }, 24 | found: { type: "boolean", required: true }, 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /src/bin/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { Command } from "commander"; 3 | import { run, testBop } from "./commands.js"; 4 | import { importJsonAndParse } from "../common/helpers/import-json-and-parse.js"; 5 | 6 | const program = new Command("meta-system"); 7 | 8 | const main = async () : Promise => { 9 | let packageFile; 10 | if (process && process.cwd()) { 11 | const libraryPath = process.platform === "win32" ? 12 | new URL(import.meta.url).pathname.replace("/", "") : new URL(import.meta.url).pathname; 13 | packageFile = await importJsonAndParse("../../../package.json", libraryPath); 14 | } 15 | 16 | program 17 | .showSuggestionAfterError(true) 18 | .helpOption("-h, --help", "Displays this help panel") 19 | .version("Currently on version " + packageFile.version, "-v, --version", "Displays the current meta-system version") 20 | .addCommand(run) 21 | .addCommand(testBop); 22 | program.parse(); 23 | 24 | }; 25 | 26 | main().catch((error) => { 27 | console.error("Meta-System exited with an error ", error); 28 | throw error; 29 | }); 30 | // eslint-disable-next-line max-lines-per-function 31 | -------------------------------------------------------------------------------- /src/common/assertions/is-valid-type.ts: -------------------------------------------------------------------------------- 1 | import { ObjectDefinition } from "@meta-system/object-definition"; 2 | 3 | export function isValidType (value : unknown, typeName : string, typeDef ?: ObjectDefinition) : boolean { 4 | if(ComplexTypes[typeName] === undefined) { 5 | return typeof value === typeName; 6 | }; 7 | return validateComplexType[typeName](value, typeDef); 8 | }; 9 | 10 | enum ComplexTypes { 11 | object = "object", 12 | array = "array", 13 | any = "any" 14 | } 15 | 16 | type ValidatorType = { 17 | [expectedType in ComplexTypes] : (value : unknown, typeDef ?: ObjectDefinition) => boolean; 18 | } 19 | 20 | const validateComplexType : ValidatorType = { 21 | "object" : (value, typeDef) => { 22 | if(typeDef) console.log("TypeDef validation has not been implemented yet"); 23 | if(typeof value === "object" && !Array.isArray(value)) return true; 24 | return false; 25 | }, 26 | "array" : (value, typeDef) => { 27 | if(typeDef) console.log("TypeDef validation has not been implemented yet"); 28 | if(Array.isArray(value)) return true; 29 | return false; 30 | }, 31 | "any" : () => true, 32 | }; 33 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/array/remove.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const arrayRemoveBopsFunction = (input : { array : unknown[]; index : number }) : unknown => { 4 | const found = input.array[input.index]; 5 | 6 | if (found === undefined) { 7 | return ({ notFoundMessage: "There is no item present at the given index" }); 8 | } 9 | 10 | const arrayCopy = [...input.array]; 11 | 12 | const removedItem = arrayCopy.splice(input.index, 1)[0]; 13 | 14 | return ({ resultingArray: arrayCopy, removedItem }); 15 | }; 16 | 17 | export const arrayRemoveBopsFunctionInformation : InternalMetaFunction = { 18 | functionName: "arrayRemove", 19 | description: "Removes the item at the given index from the array", 20 | input: { 21 | array: { type: "array", subtype: "any", required: true }, 22 | index: { type: "number", required: true }, 23 | }, 24 | output: { 25 | resultingArray: { type: "array", subtype: "any", required: false }, 26 | removedItem: { type: "any", required: false }, 27 | notFoundMessage: { type: "string", required: false }, 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/array/array-find.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | export const arrayFindBopsFunction = (input : { array : unknown[]; finder : Function }) : unknown => { 4 | let found = false; 5 | 6 | if (typeof input.finder !== "function") { 7 | return { errorMessage: "No finder function provided", found }; 8 | } 9 | 10 | const result = input.array.find((item, index, array) => input.finder({ item, index, array })); 11 | found = !!result; 12 | 13 | return { 14 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 15 | result, 16 | found, 17 | }; 18 | }; 19 | 20 | export const arrayFindBopsFunctionInformation : InternalMetaFunction = { 21 | functionName: "arrayFind", 22 | description: "Find a value in the array", 23 | input: { 24 | array: { type: "array", subtype: "any", required: true }, 25 | finder: { type: "function", required: true }, 26 | }, 27 | output: { 28 | found : { type : "boolean", required: true }, 29 | result: { type: "array", subtype: "any", required: false }, 30 | errorMessage: { type: "string", required: false }, 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/math/divide.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | import { anyIsNan } from "../non-bops-utils/any-is-nan.js"; 3 | import { Decimal } from "decimal.js"; 4 | 5 | export const divideBopsFunction = (input : { A : number; B : number }) : unknown => { 6 | if (input.B === 0) { 7 | return ({ errorDivideByZero: "Cannot divide by zero" }); 8 | } 9 | 10 | if (anyIsNan(input.B, input.A)) { 11 | return ({ errorNaN: "One of the arguments provided was not a number" }); 12 | } 13 | 14 | const A = new Decimal(input.A); 15 | const B = new Decimal(input.B); 16 | const result = A.div(B).toNumber(); 17 | 18 | return ({ result: result }); 19 | }; 20 | 21 | export const divideFunctionInformation : InternalMetaFunction = { 22 | functionName: "divide", 23 | description: "Divides A by B", 24 | input: { 25 | A: { type: "number", required: true }, 26 | B: { type: "number", required: true }, 27 | }, 28 | output: { 29 | result: { type: "number", required: false }, 30 | errorNaN: { type: "string", required: false }, 31 | errorDivideByZero: { type: "string", required: false }, 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/math/multipy.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | import { anyIsNan } from "../non-bops-utils/any-is-nan.js"; 3 | import { Decimal } from "decimal.js"; 4 | 5 | export const multiplyBopsFunction = (input : { numbersToMultiply : number[] }) : unknown => { 6 | if (anyIsNan(...input.numbersToMultiply)) { 7 | return ({ errorMessage: "One of the arguments provided was not a number" }); 8 | } 9 | 10 | let result = new Decimal(1); 11 | const decimalNumbersToMultiply = input.numbersToMultiply.map((value) => new Decimal(value)); 12 | 13 | decimalNumbersToMultiply.forEach((number) => { 14 | result = result.mul(number); 15 | }); 16 | 17 | return ({ result: result.toNumber() }); 18 | }; 19 | 20 | export const multiplyFunctionInformation : InternalMetaFunction = { 21 | functionName: "multiply", 22 | description: "Multiply the list of numbers provided", 23 | input: { 24 | numbersToMultiply: { type: "array", subtype: "number", required: true }, 25 | }, 26 | output: { 27 | result: { type: "number", required: false }, 28 | errorMessage: { type: "string", required: false }, 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /test/bops-functions/bops-engine/test-data/business-operations/prebuilt-bop.ts: -------------------------------------------------------------------------------- 1 | import { BusinessOperationType } from "../../../../../src/configuration/business-operations/business-operations-type.js"; 2 | 3 | export const mapikitProvidedBop : BusinessOperationType = { 4 | identifier: "prebuilt-functions", 5 | input: { 6 | aNumber: { type: "number", required: true }, 7 | }, 8 | output: {}, 9 | constants: [ 10 | { name: "numericThree", type: "number", value: 3 }, 11 | { name: "numericEight", type: "number", value: 8 }, 12 | { name: "targetValue", type: "number", value: 5093 }, 13 | ], 14 | variables: [], 15 | configuration: [ 16 | { 17 | moduleName: "exponential", 18 | moduleType: "internal", 19 | key: 5, 20 | dependencies: [ 21 | { origin: "constants", originPath: "numericThree", targetPath: "A" }, 22 | { origin: "inputs", originPath: "aNumber", targetPath: "B" }, 23 | ], 24 | }, 25 | { 26 | moduleName: "output", 27 | moduleType: "output", 28 | key: 6, 29 | dependencies: [ 30 | { origin: 5, originPath: "result.result", targetPath: "output" }, 31 | ], 32 | }, 33 | ], 34 | }; 35 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/flux-control/if.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | type InputType = { 4 | boolean : boolean; 5 | ifTrue : Function | unknown; 6 | ifFalse : Function | unknown; 7 | } 8 | 9 | async function getValue (valueOrFunction : Function | unknown) : Promise { 10 | if(typeof valueOrFunction === "function") return valueOrFunction(); 11 | return valueOrFunction; 12 | } 13 | 14 | export const ifBopsFunction = async (input : InputType) : Promise => { 15 | const isBooleanTrue = input.boolean; 16 | const outputValue = isBooleanTrue ? await getValue(input.ifTrue) : await getValue(input.ifFalse); 17 | 18 | return ({ outputValue }); 19 | }; 20 | 21 | export const ifBopsFunctionInformation : InternalMetaFunction = { 22 | functionName: "if", 23 | description: "Returns one of two given values based on whether the given boolean is true or false", 24 | input: { 25 | boolean : { type: "boolean", required: true }, 26 | ifTrue : { type: "any", required: true }, 27 | ifFalse : { type: "any", required: false }, 28 | }, 29 | output: { 30 | outputValue: { type: "any", required: true }, 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/number/to-exponential.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | import { Decimal } from "decimal.js"; 3 | 4 | export const toExponentialBopsFunction = (input : { number : number; decimalPlaces ?: number }) : unknown => { 5 | if (Number.isNaN(input.number)) { 6 | return ({ errorMessage: "Cannot make NaN exponential" }); 7 | } 8 | 9 | const decimalPlaces = Number.isNaN(Number(input.decimalPlaces)) ? undefined : input.decimalPlaces; 10 | 11 | if (decimalPlaces !== undefined) { 12 | return ({ result: new Decimal(input.number).toExponential(decimalPlaces, Decimal.ROUND_HALF_UP) }); 13 | } 14 | 15 | return ({ result: new Decimal(input.number).toExponential() }); 16 | }; 17 | 18 | export const toExponentialBopsFunctionInformation : InternalMetaFunction = { 19 | functionName: "toExponential", 20 | description: "Gets the index of a substring in the string", 21 | output: { 22 | result: { type: "number", required: false }, 23 | errorMessage : { type: "string", required: false }, 24 | }, 25 | input: { 26 | string: { type: "string", required: true }, 27 | decimalPlaces: { type: "number", required: false }, 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/math/modulus.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | import { anyIsNan } from "../non-bops-utils/any-is-nan.js"; 3 | import { Decimal } from "decimal.js"; 4 | 5 | export const modulusBopsFunction = (input : { A : number; B : number }) : unknown => { 6 | if (input.B === 0) { 7 | return({ errorDivisionByZero: "Cannot Divide By Zero" }); 8 | } 9 | 10 | if (anyIsNan(input.A, input.B)) { 11 | return ({ errorNotANumber: "One of the arguments provided was not a number" }); 12 | } 13 | 14 | const A = new Decimal(input.A); 15 | const B = new Decimal(input.B); 16 | const result = A.mod(B); 17 | 18 | return ({ result: result.toNumber() }); 19 | }; 20 | 21 | export const modulusFunctionInformation : InternalMetaFunction = { 22 | functionName: "modulus", 23 | description: "Gets the remainder of the division of A by B", 24 | input: { 25 | A: { type: "number", required: true }, 26 | B: { type: "number", required: true }, 27 | }, 28 | output: { 29 | result: { type: "number", required: false }, 30 | errorNotANumber: { type: "string", required: false }, 31 | errorDivisionByZero: { type: "string", required: false }, 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /test/bops-functions/bops-engine/test-data/test-system.ts: -------------------------------------------------------------------------------- 1 | import { mapikitProvidedBop } from "../test-data/business-operations/prebuilt-bop.js"; 2 | import { ConfigurationType } from "../../../../src/configuration/configuration-type.js"; 3 | import { internalBop } from "./business-operations/internal-bop.js"; 4 | import { schemaBop } from "./business-operations/schema-bop.js"; 5 | import { externalBop } from "./business-operations/external-bop.js"; 6 | import { variableBop } from "./business-operations/variables-bop.js"; 7 | import { packageBop } from "./business-operations/package-bop.js"; 8 | import { envBop } from "./business-operations/envs-bop.js"; 9 | 10 | export const testSystem : ConfigurationType = { 11 | name: "test-system", 12 | version: "0.0.1", 13 | envs: [ 14 | { key: "testEnv", value: "test" }, 15 | ], 16 | schemas: [ 17 | { 18 | name: "car", 19 | format: { 20 | model: { type: "string" }, 21 | year: { type: "string" }, 22 | }, 23 | identifier: "3993", 24 | }, 25 | ], 26 | businessOperations: [ 27 | mapikitProvidedBop, 28 | internalBop, 29 | schemaBop, 30 | externalBop, 31 | variableBop, 32 | packageBop, 33 | envBop, 34 | ], 35 | addons: [], 36 | }; 37 | 38 | -------------------------------------------------------------------------------- /test/configuration/test-data/bops/duplicate-key.json: -------------------------------------------------------------------------------- 1 | { 2 | "businessOperations": [ 3 | { 4 | "identifier": "carSell", 5 | "input": {}, 6 | "output": {}, 7 | "variables": [], 8 | "constants": [], 9 | "configuration": [ 10 | { 11 | "moduleName": "output", 12 | "moduleType": "output", 13 | "key": 1, 14 | "dependencies": [ 15 | { "origin": 2 } 16 | ] 17 | }, 18 | { 19 | "moduleName": "arrayAt", 20 | "moduleType": "internal", 21 | "key": 2, 22 | "dependencies": [ 23 | { "origin": 3 } 24 | ] 25 | }, 26 | { 27 | "moduleName": "arrayAt", 28 | "moduleType": "internal", 29 | "key": 3, 30 | "dependencies": [ 31 | { "origin": 4 } 32 | ] 33 | }, 34 | { 35 | "moduleName": "arrayAt", 36 | "moduleType": "internal", 37 | "key": 4, 38 | "dependencies": [ 39 | { "origin": 2 } 40 | ] 41 | }, 42 | { 43 | "moduleName": "arrayAt", 44 | "moduleType": "internal", 45 | "key": 2, 46 | "dependencies": [ 47 | { "origin": 4 } 48 | ] 49 | } 50 | ] 51 | } 52 | ] 53 | } -------------------------------------------------------------------------------- /test/bops-functions/bops-engine/test-data/business-operations/internal-bop.ts: -------------------------------------------------------------------------------- 1 | import { BusinessOperationType } from "../../../../../src/configuration/business-operations/business-operations-type.js"; 2 | 3 | export const internalBop : BusinessOperationType = { 4 | identifier: "internal-bop", 5 | input: {}, 6 | output: {}, 7 | constants: [ 8 | { name: "three", type: "number", value: 3 }, 9 | { name: "eight", type: "number", value: 8 }, 10 | { name: "four", type: "number", value: 4 }, 11 | ], 12 | variables: [], 13 | configuration: [ 14 | { 15 | moduleName: "prebuilt-functions", 16 | moduleType: "bop", 17 | key: 5, 18 | dependencies: [ 19 | { origin: "constants", originPath: "four", targetPath: "aNumber" }, 20 | ], 21 | }, 22 | { 23 | moduleName: "subtract", 24 | moduleType: "internal", 25 | key: 6, 26 | dependencies: [ 27 | { origin: 5, originPath: "result.output", targetPath: "A" }, 28 | { origin: "constants", originPath: "eight", targetPath: "B" }, 29 | ], 30 | }, 31 | { 32 | moduleName: "output", 33 | moduleType: "output", 34 | key: 3, 35 | dependencies: [ 36 | { origin: 6, originPath: "result.result", targetPath: "output" }, 37 | ], 38 | }, 39 | ], 40 | }; 41 | -------------------------------------------------------------------------------- /src/bops-functions/bops-engine/contexts/bop-context.ts: -------------------------------------------------------------------------------- 1 | import { BopsConfigurationEntry } from "../../../configuration/business-operations/business-operations-type.js"; 2 | import { MappedFunctions } from "../modules-manager.js"; 3 | import { ResolvedConstants } from "../static-info-validation.js"; 4 | import { ResolvedVariables } from "../variables/variables-context.js"; 5 | 6 | export class BopContext { 7 | public readonly constants : ResolvedConstants; 8 | public readonly variables : ResolvedVariables; 9 | public readonly config : BopsConfigurationEntry[]; 10 | public readonly resultsCache : Map = new Map(); 11 | public readonly availableFunctions; 12 | 13 | // eslint-disable-next-line max-params 14 | public constructor ( 15 | config : BopsConfigurationEntry[], 16 | variables : ResolvedVariables, 17 | constants : ResolvedConstants, 18 | availableFunctions : MappedFunctions, 19 | ) { 20 | this.constants = Object.freeze(constants); 21 | this.variables = variables; 22 | this.config = config; 23 | this.availableFunctions = availableFunctions; 24 | } 25 | 26 | /** Clones the context, flushing the results */ 27 | public static cloneToNewContext (bopContext : BopContext) : BopContext { 28 | return new BopContext( 29 | bopContext.config, 30 | bopContext.variables, 31 | bopContext.constants, 32 | bopContext.availableFunctions, 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/bops-functions/bops-engine/test-data/business-operations/variables-bop.ts: -------------------------------------------------------------------------------- 1 | import { BusinessOperationType } from "../../../../../src/configuration/business-operations/business-operations-type.js"; 2 | 3 | export const variableBop : BusinessOperationType = { 4 | identifier: "variables", 5 | input: { 6 | aNumber: { type: "number", required: true }, 7 | randomValue: { type: "any", required: true }, 8 | }, 9 | output: {}, 10 | constants: [], 11 | variables: [ 12 | { name: "numberVar", type: "number", initialValue: 15 }, 13 | { name: "anyVar", type: "any" }, 14 | ], 15 | configuration: [ 16 | { 17 | moduleName: "setVariables", 18 | moduleType: "variable", 19 | key: 2, 20 | dependencies: [ 21 | { origin: "inputs", originPath: "aNumber", targetPath: "numberVar" }, 22 | { origin: "inputs", originPath: "randomValue", targetPath: "anyVar" }, 23 | ], 24 | }, 25 | { 26 | moduleName: "output", 27 | moduleType: "output", 28 | key: 1, 29 | dependencies: [ 30 | { origin: "variables", originPath: "numberVar", targetPath: "initialValue" }, 31 | { origin: 2, originPath: "result.setCount", targetPath: "functionOutput" }, 32 | { origin: "variables", originPath: "numberVar", targetPath: "newValue" }, 33 | { origin: "variables", originPath: "anyVar", targetPath: "randomItem" }, 34 | ], 35 | }, 36 | ], 37 | }; 38 | -------------------------------------------------------------------------------- /test/bops-functions/prebuilt-functions/boolean.spec.ts: -------------------------------------------------------------------------------- 1 | import { boolToNumberBopsFunction } from "../../../src/bops-functions/prebuilt-functions/boolean/bool-to-number.js"; 2 | import { boolToStringBopsFunction } from "../../../src/bops-functions/prebuilt-functions/boolean/bool-to-string.js"; 3 | import { expect } from "chai"; 4 | 5 | describe("Boolean BOps functions", () => { 6 | describe("Bool to String", () => { 7 | it("True to 'true'", () => { 8 | const booleanValue = true; 9 | 10 | const result = boolToStringBopsFunction({ boolean: booleanValue }); 11 | 12 | expect(result).to.be.deep.equal({ result: "true" }); 13 | }); 14 | 15 | it("False to 'false'", () => { 16 | const booleanValue = false; 17 | 18 | const result = boolToStringBopsFunction({ boolean: booleanValue }); 19 | 20 | expect(result).to.be.deep.equal({ result: "false" }); 21 | }); 22 | }); 23 | 24 | describe("Bool to Number", () => { 25 | it("True to 1", () => { 26 | const booleanValue = true; 27 | 28 | const result = boolToNumberBopsFunction({ boolean: booleanValue }); 29 | 30 | expect(result).to.be.deep.equal({ result: 1 }); 31 | }); 32 | 33 | it("False to 0", () => { 34 | const booleanValue = false; 35 | 36 | const result = boolToNumberBopsFunction({ boolean: booleanValue }); 37 | 38 | expect(result).to.be.deep.equal({ result: 0 }); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/flux-control/forLoop.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | 3 | // eslint-disable-next-line max-lines-per-function 4 | export const forLoopFunction = async (input : { 5 | quantity : number; 6 | module : Function; 7 | postEach ?: Function; 8 | postAll ?: Function 9 | }) : Promise => { 10 | if(typeof input.quantity !== "number") return { errorMessage: "property \"quantity\" must be a number" }; 11 | 12 | let finalIndex = 0; 13 | 14 | for(let i = 0; i <= input.quantity-1; i++) { 15 | await input.module(); 16 | 17 | if (input.postEach !== undefined) await input.postEach(); 18 | 19 | finalIndex = i; 20 | } 21 | 22 | if (input.postAll !== undefined) await input?.postAll(); 23 | 24 | return ({ lastIndexValue: finalIndex }); 25 | }; 26 | 27 | export const forLoopInformation : InternalMetaFunction = { 28 | functionName: "forLoop", 29 | description: "Executes a module n times and a (optional) secundary module after each execution." + 30 | "Optionally executes a third module once the loop is done", 31 | input: { 32 | quantity: { type: "number", required: true }, 33 | module: { type: "function", required: true }, 34 | postEach: { type: "function", required: false }, 35 | postAll: { type: "function", required: false }, 36 | }, 37 | output: { 38 | lastIndexValue: { type: "number", required: true }, 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /src/bops-functions/bops-engine/variables/functions/decrease-variable.ts: -------------------------------------------------------------------------------- 1 | import { CloudedObject } from "../../../../common/types/clouded-object.js"; 2 | import { InternalMetaFunction } from "../../../internal-meta-function.js"; 3 | import { ResolvedVariables } from "../variables-context.js"; 4 | 5 | export function decreaseVariablesFunction (input : CloudedObject, variables : ResolvedVariables) : unknown { 6 | let updatedCount = 0; 7 | for(const variableName of Object.keys(input)) { 8 | const foundVariable = variables[variableName]; 9 | 10 | if(foundVariable === undefined) { 11 | return { errorMessage: `No variable named "${input.variableName}" was found` }; 12 | } 13 | 14 | if(typeof input[variableName] !== "number" || foundVariable.type !== "number") { 15 | return { errorMessage: `Input value ${input[variableName]} is not a number` }; 16 | } 17 | 18 | (foundVariable.value as number) -= input[variableName] as number; 19 | updatedCount++; 20 | } 21 | return { updatedCount }; 22 | } 23 | 24 | export const decreaseVariableFunctionInformation : InternalMetaFunction = { 25 | functionName: "decreaseVariables", 26 | description: "Decreases all given variables by the given amount", 27 | input: { 28 | "%variableName": { type: "number", required: true }, 29 | }, 30 | output: { 31 | updatedCount: { type: "number", required: false }, 32 | errorMessage: { type: "string", required: false }, 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /src/bops-functions/bops-engine/variables/functions/increase-variable.ts: -------------------------------------------------------------------------------- 1 | import { CloudedObject } from "../../../../common/types/clouded-object.js"; 2 | import { InternalMetaFunction } from "../../../internal-meta-function.js"; 3 | import { ResolvedVariables } from "../variables-context.js"; 4 | 5 | export function increaseVariablesFunction (input : CloudedObject, variables : ResolvedVariables) : unknown { 6 | let updatedCount = 0; 7 | 8 | for(const variableName of Object.keys(input)) { 9 | const foundVariable = variables[variableName]; 10 | 11 | if(foundVariable === undefined) { 12 | return { errorMessage: `No variable named "${variableName}" was found` }; 13 | } 14 | 15 | if(typeof input[variableName] !== "number" || foundVariable.type !== "number") { 16 | return { errorMessage: `Input value ${input[variableName]} is not a number` }; 17 | } 18 | 19 | (foundVariable.value as number) += 1; //input[variableName] as number; 20 | updatedCount++; 21 | } 22 | 23 | return { updatedCount }; 24 | } 25 | 26 | export const increaseVariableFunctionInformation : InternalMetaFunction = { 27 | functionName: "increaseVariables", 28 | description: "Increases all the given variables by the given amount", 29 | input: { 30 | "%variableName": { type: "number", required: true }, 31 | }, 32 | output: { 33 | updatedCount: { type: "number", required: false }, 34 | errorMessage: { type: "string", required: false }, 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/math/round.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | import { anyIsNan } from "../non-bops-utils/any-is-nan.js"; 3 | import { getGreatestDecimalPlaces } from "../non-bops-utils/get-largest-decimal-places.js"; 4 | 5 | export const roundBopsFunction = (input : { input : number; precision : number }) : unknown => { 6 | if (anyIsNan(input.input, input.precision)) { 7 | return ({ errorNaN: "One of the arguments provided was not a number" }); 8 | } 9 | 10 | const decimalPrecision = Math.pow(10, getGreatestDecimalPlaces(input.precision, input.input)); 11 | const precision = Math.abs(input.precision * decimalPrecision); 12 | const toBeRoundedInput = input.input * decimalPrecision; 13 | 14 | const modulus = Math.abs(toBeRoundedInput%precision); 15 | const roundingDifference = Number(modulus >= precision - modulus) * precision - modulus; 16 | 17 | const result = roundingDifference + toBeRoundedInput; 18 | 19 | return ({ result: result/decimalPrecision }); 20 | }; 21 | 22 | export const roundFunctionInformation : InternalMetaFunction = { 23 | functionName: "round", 24 | description: "Rounds Input to a given precision", 25 | input: { 26 | input: { type: "number", required: true }, 27 | precision: { type: "number", required: true }, 28 | }, 29 | output: { 30 | result: { type: "number", required: false }, 31 | errorNaN: { type: "string", required: false }, 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /src/bops-functions/bops-engine/variables/functions/set-variable.ts: -------------------------------------------------------------------------------- 1 | import { isValidType } from "../../../../common/assertions/is-valid-type.js"; 2 | import { CloudedObject } from "../../../../common/types/clouded-object.js"; 3 | import { InternalMetaFunction } from "../../../internal-meta-function.js"; 4 | import { ResolvedVariables } from "../variables-context.js"; 5 | 6 | export function setVariablesFunction (input : CloudedObject, variables : ResolvedVariables) : unknown { 7 | let setCount = 0; 8 | for(const variableName of Object.keys(input)) { 9 | const foundVariable = variables[variableName]; 10 | 11 | if(foundVariable === undefined) { 12 | return { errorMessage: `No variable named "${variableName}" was found` }; 13 | } 14 | 15 | if(!isValidType(input[variableName], foundVariable.type)) { 16 | return { errorMessage: `Type "${typeof input[variableName]}" is not compatible with "${foundVariable.type}"` }; 17 | } 18 | 19 | foundVariable.value = input[variableName]; 20 | setCount ++; 21 | } 22 | 23 | return { setCount }; 24 | } 25 | 26 | export const setVariablesFunctionInformation : InternalMetaFunction = { 27 | functionName: "setVariables", 28 | description: "Sets the variables in targetPath to the new value", 29 | input: { 30 | "%variableName": { type: "any", required: true }, 31 | }, 32 | output: { 33 | newValue: { type: "any", required: false }, 34 | errorMessage: { type: "string", required: false }, 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /src/bops-functions/prebuilt-functions/array/find-index.ts: -------------------------------------------------------------------------------- 1 | import { InternalMetaFunction } from "../../internal-meta-function.js"; 2 | import assert from "assert"; 3 | 4 | // eslint-disable-next-line max-lines-per-function 5 | export const arrayFindIndexBopsFunction = (input : { array : unknown[]; searchedItem : unknown }) : unknown => { 6 | if (typeof input.searchedItem === "object") { 7 | const foundIndex = input.array.findIndex((value) => { 8 | try { 9 | assert.deepStrictEqual(value, input.searchedItem); 10 | return true; 11 | } catch { 12 | return false; 13 | } 14 | }); 15 | 16 | if (foundIndex >= 0) { 17 | return ({ index: foundIndex }); 18 | } 19 | 20 | return ({ notFoundMessage: "No item matchs in the given array" }); 21 | } 22 | 23 | const result = input.array.findIndex((value) => input.searchedItem === value); 24 | 25 | if (result >= 0) { 26 | return ({ index: result }); 27 | } 28 | 29 | return ({ notFoundMessage: "No item matchs in the given array" }); 30 | }; 31 | 32 | export const arrayFindIndexBopsFunctionInformation : InternalMetaFunction = { 33 | functionName: "arrayFindIndex", 34 | description: "Find the index of a given item in the array", 35 | input: { 36 | array: { type: "array", subtype: "any", required: true }, 37 | searchedItem: { type: "any", required: true }, 38 | }, 39 | output: { 40 | index: { type: "number", required: false }, 41 | notFoundMessage: { type: "string", required: false }, 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /src/bops-functions/bops-engine/module-resolver.ts: -------------------------------------------------------------------------------- 1 | import { EntityBroker } from "../../broker/entity-broker.js"; 2 | import { BopsConfigurationEntry } from "../../configuration/business-operations/business-operations-type.js"; 3 | 4 | enum ModuleTypes { 5 | schemaFunctions = "schemaFunction", 6 | internalFunctions = "internal", 7 | addonFunctions = "addon", 8 | bops = "bop" 9 | } 10 | // Internal Bops (embedded) and Bop Outputs are resolved separately 11 | 12 | export type ModuleResolverType = { 13 | [char in ModuleTypes] : (module : BopsConfigurationEntry) => Function; 14 | } 15 | 16 | export class ModuleResolver { 17 | constructor (private readonly systemFunctionsBroker : EntityBroker) { 18 | 19 | } 20 | 21 | public resolve : ModuleResolverType = { 22 | "addon": (module) : Function => { 23 | return this.systemFunctionsBroker.addonsFunctions 24 | .getAddonFunction(module.modulePackage, module.moduleName); 25 | }, 26 | "bop": (module) : Function => { 27 | return this.systemFunctionsBroker.bopFunctions 28 | .getBopFunction(module.moduleName); 29 | }, 30 | "schemaFunction": (module) : Function => { 31 | const schema = module.modulePackage; 32 | const operation = module.moduleName; 33 | 34 | return this.systemFunctionsBroker.schemaFunctions 35 | .getSchemaFunction(operation, schema); 36 | }, 37 | "internal" : (module) : Function => { 38 | const functionName = module.moduleName; 39 | return this.systemFunctionsBroker.internalFunctions 40 | .getFunction(functionName); 41 | }, 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /test/broker/broker.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { FunctionsContext } from "../../src/entities/functions-context.js"; 3 | import { faker } from "@faker-js/faker"; 4 | import constants from "../../src/common/constants.js"; 5 | import { DiffManager } from "../../src/configuration/diff/diff-manager.js"; 6 | const { number } = faker; 7 | 8 | describe("Broker Tests", () => { 9 | const diffManager = new DiffManager(); 10 | 11 | it("Functions context broker", () => { 12 | const factory = new FunctionsContext(diffManager); 13 | const broker = factory.createBroker([], constants.RUNTIME_ENGINE_IDENTIFIER); 14 | 15 | // Would throw if not found 16 | broker["logger"]["fatal"]("NOT AN ERROR, THIS IS A TEST"); 17 | }); 18 | 19 | it("Functions Context Broker - Declaring and retrieving functions", () => { 20 | const factory = new FunctionsContext(diffManager); 21 | const broker = factory.createBroker([{ entity: "schemaFunctions", 22 | permissions: ["set_functions", "get_functions"] }], constants.RUNTIME_ENGINE_IDENTIFIER); 23 | 24 | let myVariable = 0; 25 | const aFunction = (value : number) : void => { myVariable = value; }; 26 | broker.schemaFunctions.setSchemaFunction("mySchema", aFunction, 27 | { functionName: "nn", input: {}, output: {} }, 28 | ); 29 | 30 | const myFunction = broker.schemaFunctions.getSchemaFunction("nn", "mySchema"); 31 | expect(myFunction).to.be.equal(aFunction); 32 | 33 | const newValue = number.int(100); 34 | myFunction(newValue); 35 | expect(myVariable).to.be.equal(newValue); 36 | }); 37 | }); 38 | 39 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Meta-System Roadmap 2 | 3 | ### 0.2 - Gemini :heavy_check_mark: 4 | - :heavy_check_mark: Support to Function Packages; 5 | - :heavy_check_mark: Introduces external protocols; 6 | - :heavy_check_mark: Protocols can provide their BOps functions as well; 7 | - :heavy_check_mark: BETA - Verification of modules dependency property accesses; 8 | 9 | ## 0.3 - Mercury :heavy_check_mark: 10 | - :heavy_check_mark: Meta-System configuration can be split in multiple files, as long as there is a path declared; 11 | - :heavy_check_mark: Introduces DBProtocols - Databases becomes part of the protocol definition; 12 | - :heavy_check_mark: Schemas need to define a protocol Database to connect to; 13 | - :heavy_check_mark: Add StdOut Debugging capabilities to BOps; 14 | 15 | ### 0.4 - Future Release - coming soon 🎉! 16 | - Meta-System can be run on the Browser; 17 | - New Addons architecture - Libraries/Protocols to Meta-System are simpler and easier to write, as well as making an existing one compatible; 18 | - You can write Addons locally and test it with Meta-System without publishing to NPM 19 | - Verification of module dependency property accesses - Becoming a stable feature; 20 | - Streamline CLI usage; 21 | 22 | ### 0.5 - Future Release 23 | - Meta-System as a framework - You can create modules locally and use them directly on your Meta-System BOps; 24 | - Polish Meta-System integration to existing Systems; 25 | - Add eventful debugging capabilities to BOps - Integrating Meta-System to an existing system should give you the ability to listen to what is happening inside the engine. 26 | 27 | ### 1.0 - First Stable Release 28 | ... 29 | -------------------------------------------------------------------------------- /src/configuration/business-operations/business-operations-type.ts: -------------------------------------------------------------------------------- 1 | import { ObjectDefinition } from "@meta-system/object-definition"; 2 | import { ExtendedJsonTypes } from "../../common/types/json-types.js"; 3 | 4 | export interface BusinessOperationType { 5 | ttl ?: number; 6 | input : ObjectDefinition; 7 | output : ObjectDefinition; 8 | constants : BopsConstant[]; 9 | variables : BopsVariable[]; 10 | configuration : BopsConfigurationEntry[]; 11 | identifier : string; 12 | } 13 | 14 | export type ExtendedJsonTypeDict = 15 | T extends "string" ? string : 16 | T extends "number" ? number : 17 | T extends "boolean" ? boolean : 18 | T extends "date" ? Date : 19 | T extends "object" ? object : 20 | T extends "array" ? Array : 21 | T extends "any" ? unknown : never; 22 | 23 | export class BopsConstant { 24 | name : string; 25 | type : ExtendedJsonTypes | "any"; 26 | value : ExtendedJsonTypeDict; 27 | } 28 | 29 | export interface BopsVariable { 30 | name : string; 31 | type : ExtendedJsonTypes | "any"; 32 | initialValue ?: ExtendedJsonTypeDict; 33 | } 34 | 35 | export type ModuleType = "internal" 36 | | "bop" 37 | | "output" 38 | | "variable" 39 | | "addon" 40 | | "schemaFunction"; 41 | 42 | export interface BopsConfigurationEntry { 43 | moduleType : ModuleType; 44 | moduleName : string; 45 | modulePackage ?: string; 46 | key : number; 47 | dependencies : Dependency[]; 48 | } 49 | 50 | export interface Dependency { 51 | origin : string | number; 52 | targetPath ?: string; 53 | originPath ?: string; 54 | } 55 | -------------------------------------------------------------------------------- /test/factories/entity-factory.ts: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | import { ObjectDefinition } from "@meta-system/object-definition"; 3 | const { number, lorem, date, datatype } = faker; 4 | 5 | export const entityFactory = (schemaFormat : ObjectDefinition) : object => { 6 | const entity = {}; 7 | for(const prop in schemaFormat) { 8 | const usedType = getType(schemaFormat[prop]); 9 | entity[prop] = typeCreation[usedType](schemaFormat[prop]["subtype"]); 10 | } 11 | return entity; 12 | }; 13 | 14 | const getType = (def : ObjectDefinition["prop"]) : string => { 15 | if (Array.isArray(def)) { 16 | const usedType = number.int({ min: 0, max: def.length -1 }); 17 | return def[usedType].type; 18 | } 19 | 20 | return def.type; 21 | }; 22 | 23 | const typeCreation = { 24 | string: () : string => lorem.sentence(number.int({ min: 2, max: 5 })), 25 | number: () : number => number.int(), 26 | date: () : Date => date.between({ from: "1500", to: "2500" }), 27 | boolean: () : boolean => datatype.boolean(), 28 | 29 | array: (dataType : string) : Array => { 30 | const array = []; 31 | for (let i = 0; i < number.int({ min: 2, max: 5 }); i++) { 32 | const newItem = typeCreation[typeof dataType === "string" ? dataType : "object"](dataType); 33 | array.push(newItem); 34 | } 35 | return array; 36 | }, 37 | 38 | object: (dataType : ObjectDefinition) : object => { 39 | const object = {}; 40 | for(const prop in dataType) { 41 | object[prop] = typeCreation[getType(dataType[prop])](dataType[prop]["subtype"]); 42 | } 43 | return object; 44 | }, 45 | }; 46 | -------------------------------------------------------------------------------- /src/bops-functions/bops-engine/contexts/bop-system-context.ts: -------------------------------------------------------------------------------- 1 | import { ConfigurationType } from "../../../configuration/configuration-type.js"; 2 | import { BopsVariable } from "../../../configuration/business-operations/business-operations-type.js"; 3 | import { MappedFunctions, ModuleManager } from "../modules-manager.js"; 4 | import { ResolvedConstants, StaticSystemInfo } from "../static-info-validation.js"; 5 | import { VariableContext } from "../variables/variables-context.js"; 6 | 7 | export class BopSystemContext { 8 | public readonly constants : Record; 9 | public readonly variables : Record; 10 | public readonly envs : Record; 11 | public readonly moduleManager : ModuleManager; 12 | public mappedFunctions ?: MappedFunctions; 13 | public readonly config : ConfigurationType; 14 | 15 | public constructor (options : { 16 | ModuleManager : ModuleManager; 17 | SystemConfig : ConfigurationType; 18 | }) { 19 | this.constants = StaticSystemInfo.validateSystemStaticInfo(options.SystemConfig); 20 | this.variables = VariableContext.validateSystemVariables(options.SystemConfig); 21 | this.moduleManager = options.ModuleManager; 22 | this.config = options.SystemConfig; 23 | this.envs = Object.assign({}, ...options.SystemConfig.envs.map(env => ({ [env.key]: env.value }))); 24 | } 25 | 26 | public get hasMappedFunctions () : boolean { 27 | return this.mappedFunctions !== undefined; 28 | } 29 | 30 | public generateMappedFunctions () : void { 31 | this.mappedFunctions = this.moduleManager.resolveSystemModules(this.config); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/factories/schema-format-factory.ts: -------------------------------------------------------------------------------- 1 | import { ObjectDefinition } from "@meta-system/object-definition"; 2 | import { faker } from "@faker-js/faker"; 3 | import { ExtendedJsonTypes, JsonTypes } from "../../src/common/types/json-types.js"; 4 | import { TypeDefinitionDeep } from "@meta-system/object-definition/dist/object-definition-type.js"; 5 | 6 | const basicStrings : JsonTypes[] = ["boolean", "string", "number", "date"]; 7 | const advancedStrings : Exclude[] = ["array", "object"]; 8 | 9 | export const schemaFormatFactory = (maxDepth = 3) : ObjectDefinition => { 10 | const newFormat : ObjectDefinition = {}; 11 | 12 | for (let property = 0; property < 6; property += faker.helpers.arrayElement([1, 2])) { 13 | const type = faker.helpers.arrayElement([...basicStrings, ...advancedStrings]); 14 | if(type === "object" || type === "array") { 15 | if(maxDepth > 0) newFormat[faker.person.jobType()] = typeFactory[type](maxDepth); 16 | } 17 | else newFormat[faker.person.jobType()] = { type }; 18 | } 19 | 20 | return newFormat; 21 | }; 22 | 23 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 24 | const typeFactory : { [type : string] : (maxDepth ?: number) => any } = { 25 | object: (maxDepth : number) : TypeDefinitionDeep => { 26 | return { 27 | type: "object", 28 | subtype: { 29 | ...schemaFormatFactory(maxDepth-1), 30 | }, 31 | }; 32 | }, 33 | 34 | array: (maxDepth : number) : TypeDefinitionDeep => { 35 | return { 36 | type: "array", 37 | subtype: faker.helpers.arrayElement([...basicStrings, typeFactory.object(maxDepth-1).data]), 38 | }; 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /src/entities/repository.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line max-classes-per-file 2 | import clone from "just-clone"; 3 | import { EntityValue, MetaEntity } from "./meta-entity.js"; 4 | 5 | export class RepositoryError extends Error {} 6 | 7 | export class EntityRepository { 8 | public constructor ( 9 | private collectionSingleton : Array>, 10 | ) {} 11 | 12 | public createEntity (entity : MetaEntity) : void { 13 | const identifier = entity.data.identifier; 14 | if (this.getEntity(identifier)) { 15 | throw new RepositoryError(`Cannot create entity: Identifier "${identifier}" already exists.`); 16 | } 17 | 18 | this.collectionSingleton.push(entity); 19 | } 20 | 21 | public updateEntity (entity : MetaEntity) : void { 22 | const itemIndex = this.collectionSingleton.findIndex((ent) => { 23 | return ent.data.identifier === entity.data.identifier; 24 | }); 25 | 26 | if (itemIndex === -1) { 27 | throw new RepositoryError(`Cannot update entity: Identifier "${entity.data.identifier}" does not exist.`); 28 | } 29 | 30 | this.collectionSingleton[itemIndex] = entity; 31 | } 32 | 33 | public readCollection () : Array> { 34 | return this.collectionSingleton.map((value) => { 35 | return new MetaEntity(value.owner, clone(value.data)); 36 | }); 37 | } 38 | 39 | public deleteEntity (entity : MetaEntity) : void { 40 | this.collectionSingleton.filter((ent) => { 41 | ent.data.identifier !== entity.data.identifier; 42 | }); 43 | } 44 | 45 | public getEntity (identifier : string) : MetaEntity { 46 | return this.readCollection().find((ent) => ent.data.identifier === identifier); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/bops-functions/bops-engine/static-info-validation.ts: -------------------------------------------------------------------------------- 1 | 2 | import { isValidType } from "../../common/assertions/is-valid-type.js"; 3 | import { BopsConstant, ExtendedJsonTypeDict } from 4 | "../../configuration/business-operations/business-operations-type.js"; 5 | import { ConfigurationType } from "../../configuration/configuration-type.js"; 6 | import { ConstantTypeError } from "./engine-errors/constant-type-error.js"; 7 | 8 | // TODO: Test 9 | export type ResolvedConstants = Record; 10 | /** 11 | * This class is responsible for gathering and validating all the static, immutable, info; such 12 | * as constants and internalBops 13 | */ 14 | export class StaticSystemInfo { 15 | // TODO: This is definitely not doing all of that above - let's consider renaming 16 | private static validateConstant (constant : BopsConstant) : ExtendedJsonTypeDict { 17 | if(!isValidType(constant.value, constant.type)) throw new ConstantTypeError(constant); 18 | return constant.value; 19 | } 20 | 21 | private static validateConstants (constants : BopsConstant[]) : ResolvedConstants { 22 | const resolvedConstants = {}; 23 | constants.forEach(constant => { 24 | resolvedConstants[constant.name] = this.validateConstant(constant); 25 | }); 26 | return resolvedConstants; 27 | } 28 | 29 | public static validateSystemStaticInfo (systemConfig : ConfigurationType) 30 | : Record { 31 | const bops = systemConfig.businessOperations; 32 | const allSystemConstants : Record = {}; 33 | bops.forEach(bop => { 34 | allSystemConstants[bop.identifier] = this.validateConstants(bop.constants); 35 | }); 36 | return Object.freeze(allSystemConstants); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/factories/entity-to-query.ts: -------------------------------------------------------------------------------- 1 | export const entityToQuery = (entity : object) : string => { 2 | let query = ""; 3 | for(const prop in entity) { 4 | query += typeCreation[typeof entity[prop]](entity[prop], prop); 5 | } 6 | return query.substring(1); 7 | }; 8 | 9 | const typeCreation = { 10 | string: (string : string, propName : string) : string => `&${propName}=${string}`, 11 | number: (number : number, propName : string) : string => `&${propName}=${number}`, 12 | boolean: (bool : boolean, propName : string) : string => `&${propName}=${bool.toString()}`, 13 | object: (object : object, propName ?: string) : string => { 14 | // To javascript, both Arrays and Dates are considered Objects. Because of this, it is 15 | // required to diferentiate between regular Objects, Arrays and Dates. 16 | if(isArrayOfObjects(object)) return arrayOfObjectsToQuery(object as Array, propName); 17 | if(object instanceof Array) return `&${propName}[]=` + object.join(`&${propName}[]=`); 18 | if(object instanceof Date) return `&${propName}=${object.toISOString()}`; 19 | 20 | let query = ""; 21 | for(const prop in object) { 22 | const header = propName ? `${propName}[${prop}]` : prop; 23 | query += `${typeCreation[typeof object[prop]](object[prop], header)}`; 24 | } 25 | return query; 26 | }, 27 | }; 28 | 29 | function isArrayOfObjects (object : object) : boolean { 30 | return object instanceof Array && typeof object[0] === "object"; 31 | } 32 | 33 | function arrayOfObjectsToQuery (array : Array, propName ?: string) : string { 34 | let query = ""; 35 | array.forEach((object, index) => { 36 | query += typeCreation.object(object, `${propName}[${index}]`); 37 | }); 38 | return query; 39 | } 40 | -------------------------------------------------------------------------------- /test/configuration/configuration-de-serializer.spec.ts: -------------------------------------------------------------------------------- 1 | import { DeserializeConfigurationCommand } from "../../src/configuration/de-serialize-configuration.js"; 2 | import { expect } from "chai"; 3 | import { asyncTestThrow } from "../helpers/test-throw.js"; 4 | import { importJsonAndParse } from "../../src/common/helpers/import-json-and-parse.js"; 5 | 6 | describe("Configuration Deserializer", () => { 7 | let configurationExample; 8 | let badConfigurationExample; 9 | 10 | before(async () => { 11 | // eslint-disable-next-line max-len 12 | configurationExample = await importJsonAndParse("./test/configuration/test-data/configuration-example.json"); 13 | // eslint-disable-next-line max-len 14 | badConfigurationExample = await importJsonAndParse("./test/configuration/test-data/configuration/bad-configuration-example.json"); 15 | }); 16 | 17 | // This suite just tests the base type - the Schemas and BOPS tests are written in other suites 18 | it("Successfully deserializes a valid configuration file", async () => { 19 | const command = new DeserializeConfigurationCommand(); 20 | 21 | await command.execute(configurationExample); 22 | expect(command.result).to.not.be.undefined; 23 | expect(command.validation.errors.length).to.be.equal(0); 24 | }); 25 | 26 | it("Fails do deserialize file with bad formatted configuration", async () => { 27 | const command = new DeserializeConfigurationCommand(); 28 | 29 | const execution = async () : Promise => { await command.execute(badConfigurationExample); }; 30 | const result = await asyncTestThrow(execution); 31 | 32 | expect(result.thrown).to.be.true; 33 | console.error(result.error); 34 | expect(result.error.message).to.contain("Config validation failed!"); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/bin/commands.ts: -------------------------------------------------------------------------------- 1 | import { Command, Option } from "commander"; 2 | import constants from "../common/constants.js"; 3 | import { environment } from "../common/execution-env.js"; 4 | import { parseInteger } from "../common/helpers/parsers.js"; 5 | import { logLevelsArray } from "../common/logger/logger-types.js"; 6 | import { testBopFunction, main } from "./functions.js"; 7 | 8 | const testBop = new Command("test-bop") 9 | .argument("", "The config file") 10 | .argument("", "The bop name") 11 | .action(testBopFunction); 12 | 13 | const run = new Command("run") 14 | .description("Runs the given file") 15 | .argument("", "The path to your system configuration json") 16 | .action(main) 17 | .option("-d, --debug", "Logs additional info on the execution of BOps", () => { 18 | environment.silent.constants.logLevel = "debug"; 19 | }) 20 | .option("-L, --create-log-file, --log-file", "Saves logs to a file inside logs folder") 21 | .option("-S, --skip-prop-validation", "Skips validation of properties types") 22 | .option("-D, --dev", "Automatically restarts the system on config file update") 23 | // Currently disabled, will be re-enabled in 0.5 with file splitting support 24 | .addOption( 25 | new Option("-l, --log-level ", "Sets the logging level") 26 | .choices(logLevelsArray) 27 | .default(constants.DEFAULT_LOG_LEVEL)) 28 | .addOption( 29 | new Option("-t, --type-check ", "Type checking level") 30 | .argParser(parseInteger) 31 | .choices(["0", "1", "2", "3", "4"]) 32 | .default(1)) 33 | .addOption( 34 | new Option("-T, --ttl, --time-to-live