First Commit

Yes, that is what I do at 31.12.2021 at 22:39 local time...
This commit is contained in:
K35 2021-12-31 21:38:26 +00:00
commit d5cfdcadc1
21 changed files with 3352 additions and 0 deletions

11
.editorconfig Normal file
View File

@ -0,0 +1,11 @@
root=true
[*]
charset = utf-8
end_of_line = lf
indent_size = 3
indent_style = space
insert_final_newline = true
[*.yml]
indent_size = 2
[*.md]
indent_size = 2

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules/
.yarn/cache
.yarn/install-state.gz
examples/out

File diff suppressed because one or more lines are too long

768
.yarn/releases/yarn-3.1.1.cjs vendored Executable file

File diff suppressed because one or more lines are too long

7
.yarnrc.yml Normal file
View File

@ -0,0 +1,7 @@
nodeLinker: node-modules
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
yarnPath: .yarn/releases/yarn-3.1.1.cjs

33
examples/example.binc Normal file
View File

@ -0,0 +1,33 @@
import "./import.binc";
enum TestEnum {
VAL1,
VAL2,
VAL10 = 10,
VAL11,
VAL12
}
type Test {
atom: TestAtom;
array: TestAtom[];
enumValue: TestEnum;
map: {number, TestAtom};
}
type AddValueRequest {
value1: number;
value2: number;
}
type AddValueResponse {
value: number;
}
service TestService {
AddValuesSingleParam(request: AddValueRequest): AddValueResponse;
AddValuesMultipleParams(value1: number, value2: number): number;
notification OnEvent(param1: string);
}

5
examples/import.binc Normal file
View File

@ -0,0 +1,5 @@
type TestAtom {
val_number: number;
val_boolean: boolean;
val_string: string;
}

54
examples/test.ts Normal file
View File

@ -0,0 +1,54 @@
import { AddValueRequest, AddValueResponse, Logging } from "./out/index"
// Logging.verbose = false;
import * as Client from "./out/index_client"
import * as Server from "./out/index_server"
const client = new Client.ServiceProvider(msg=>{
session.onMessage(msg);
})
const server = new Server.ServiceProvider()
const session = server.getSession((msg) => {
client.onPacket(msg);
})
class TestService extends Server.TestService<undefined> {
async AddValuesSingleParam(request: AddValueRequest, ctx: undefined): Promise<AddValueResponse> {
return {
value: request.value1 + request.value2
}
}
async AddValuesMultipleParams(value1: number, value2: number, ctx: undefined): Promise<number> {
return value1 + value2;
}
OnEvent(param1: string, ctx: undefined): void {
console.log("Received notification", param1);
}
}
server.addService(new TestService())
const test = new Client.TestService(client);
async function run() {
console.log("Testing AddValuesSingleParam")
console.log(await test.AddValuesSingleParam({
value1: 1,
value2: 2
}));
console.log("Testing AddValuesMultipleParams")
console.log(await test.AddValuesMultipleParams(1,2));
console.log("Testing Notification")
test.OnEvent("Hi, this is an event");
}
run();

24
package.json Normal file
View File

@ -0,0 +1,24 @@
{
"name": "@hibas123/SimpleRPC",
"version": "1.0.0",
"main": "lib/index.js",
"license": "MIT",
"packageManager": "yarn@3.1.1",
"scripts": {
"start": "ts-node src/index.ts",
"test": "npm run start -- compile examples/example.binc -o=ts-node:examples/out && ts-node examples/test.ts"
},
"devDependencies": {
"@types/debug": "^4.1.7",
"@types/node": "^17.0.5",
"@types/yargs": "^17.0.8",
"ts-node": "^10.4.0",
"typescript": "^4.5.4"
},
"dependencies": {
"chalk": "4",
"debug": "^4.3.3",
"prettier": "^2.5.1",
"yargs": "^17.3.1"
}
}

74
src/compile.ts Normal file
View File

@ -0,0 +1,74 @@
import * as FS from "fs";
import * as Path from "path";
import {
EnumDefinition,
IR,
ServiceDefinition,
Step,
TypeDefinition,
} from "./ir";
export abstract class CompileTarget {
abstract name: string;
constructor(private outputFolder: string) {
if (!FS.existsSync(outputFolder)) {
FS.mkdirSync(outputFolder, {
recursive: true,
});
}
}
abstract start(): void;
abstract generateType(definition: TypeDefinition): void;
abstract generateEnum(definition: EnumDefinition): void;
abstract generateService(definition: ServiceDefinition): void;
abstract finalize(steps: Step[]): void;
protected writeFile(name: string, content: string | Promise<string>) {
if (content instanceof Promise) {
content.then((res) =>
FS.writeFileSync(Path.join(this.outputFolder, name), res)
);
} else {
FS.writeFileSync(Path.join(this.outputFolder, name), content);
}
}
protected getTemplate(name: string): string {
let path = Path.join(__dirname, "../templates/" + name);
let file = FS.readFileSync(path, "utf-8");
const splitted = file.split("\n");
let res = [];
let ignore = false;
for (const line of splitted) {
if (ignore) {
ignore = false;
} else if (line.trim().startsWith("//@template-ignore")) {
ignore = true;
} else {
res.push(line);
}
}
return res.join("\n");
}
}
export default function compile(ir: IR, target: CompileTarget) {
// Types are verified. They are now ready to be compiled to targets
// setState("Building for target: " + target.name);
ir.forEach((step) => {
const [type, def] = step;
if (type == "type") target.generateType(def as TypeDefinition);
else if (type == "enum") target.generateEnum(def as EnumDefinition);
else if (type == "service")
target.generateService(def as ServiceDefinition);
});
if (target.finalize) target.finalize(ir);
}

62
src/index.ts Normal file
View File

@ -0,0 +1,62 @@
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import startCompile, { Target } from "./process";
import dbg from "debug";
const log = dbg("app");
dbg.disable();
yargs(hideBin(process.argv))
.version("1.0.0")
.command(
"compile <input>",
"Compile source",
(yargs) => {
return yargs
.positional("input", {
describe: "Input file",
type: "string",
demandOption: true,
})
.option("definition", {
type: "string",
describe: "Emit definition json at specified location",
})
.option("output", {
type: "string",
describe: "Output lang and location 'ts:out/' 'c:/test'",
alias: "o",
coerce: (arg: string | string[] | undefined) => {
if (!arg) return [];
if (!Array.isArray(arg)) arg = [arg];
return arg.map((input) => {
const [type, output] = input.split(":", 2);
return {
type,
output,
} as Target;
});
},
array: true,
});
},
(argv) => {
if (argv.verbose) {dbg.enable("app");}
log("Received compile command with args", argv);
startCompile({
input: argv.input,
targets: argv.output as any,
emitDefinitions: argv.definition
})
}
)
.option("verbose", {
alias: "v",
type: "boolean",
describe: "Adds additional outputs",
})
.strictCommands()
.demandCommand()
.parse();

232
src/ir.ts Normal file
View File

@ -0,0 +1,232 @@
import type { Parsed, StatementNode } from "./parser";
import dbg from "debug";
const log = dbg("app");
const builtin = ["number", "string", "boolean"];
export class IRError extends Error {
constructor(public statement: StatementNode, message: string) {
super("Error Compiling: " + message);
}
}
export interface TypeFieldDefinition {
name: string;
type: string;
array: boolean;
map?: string;
}
export interface TypeDefinition {
name: string;
depends: string[];
fields: TypeFieldDefinition[];
}
export interface EnumValueDefinition {
name: string;
value: number;
}
export interface EnumDefinition {
name: string;
values: EnumValueDefinition[];
}
export interface ServiceFunctionParamsDefinition {
name: string;
inputs: { type: string; name: string }[];
return: string | undefined;
}
export type ServiceFunctionDefinition = ServiceFunctionParamsDefinition;
export interface ServiceDefinition {
name: string;
depends: string[];
functions: ServiceFunctionDefinition[];
}
export type Step = [
"type" | "enum" | "service",
TypeDefinition | EnumDefinition | ServiceDefinition
];
export type IR = Step[];
export default function get_ir(parsed: Parsed): IR {
log("Generatie IR from parse output");
let defined: string[] = [];
let types: string[] = [];
let enums: string[] = [];
// Verifiy and generating steps
let steps: Step[] = [];
parsed.forEach((statement) => {
log("Working on statement of type %s", statement.type);
if (statement.type == "import")
throw new IRError(
statement,
"Import statements are invalid at this step!"
);
if (statement.type === "type") {
if (defined.indexOf(statement.name) >= 0) {
throw new IRError(
statement,
`Type ${statement.name} already defined!`
);
}
let depends: string[] = [];
const fields = statement.fields.map<TypeFieldDefinition>((field) => {
if (field.type !== "type_field") {
throw new IRError(field, "Invalid statement!");
}
if (defined.indexOf(field.fieldtype) < 0) {
if (builtin.indexOf(field.fieldtype) < 0) {
throw new IRError(
field,
`Type ${field.fieldtype} is not defined!`
);
}
} else {
if (depends.indexOf(field.fieldtype) < 0)
depends.push(field.fieldtype);
}
if (
field.map &&
field.map !== "number" &&
field.map !== "string"
) {
throw new IRError(
field,
`Type ${field.map} is not valid as map key!`
);
}
return {
name: field.name,
type: field.fieldtype,
array: field.array,
map: field.map,
};
});
steps.push([
statement.type,
{
name: statement.name,
depends,
fields,
},
]);
defined.push(statement.name);
types.push(statement.name);
} else if (statement.type === "enum") {
if (defined.indexOf(statement.name) >= 0) {
throw new IRError(
statement,
`Type ${statement.name} already defined!`
);
}
let last = -1;
let values = statement.values.map<EnumValueDefinition>((valueS) => {
let value = last + 1;
if (valueS.value) {
if (valueS.value <= last) {
throw new IRError(
statement,
"Enum value must be larger than the previous one!"
);
} else {
value = valueS.value;
}
}
last = value;
return {
name: valueS.name,
value,
};
});
steps.push([
"enum",
{
name: statement.name,
values,
} as EnumDefinition,
]);
defined.push(statement.name);
enums.push(statement.name);
} else if (statement.type === "service") {
if (defined.indexOf(statement.name) >= 0) {
throw new IRError(
statement,
`Type ${statement.name} already defined!`
);
}
let depends: string[] = [];
let alreadyFoundFunctions = new Set<string>();
let functions = statement.functions.map((fnc) => {
if (alreadyFoundFunctions.has(fnc.name))
throw new IRError(
fnc,
`Function with name ${fnc.name} already defined!`
);
alreadyFoundFunctions.add(fnc.name);
if (fnc.return_type) {
if (defined.indexOf(fnc.return_type) >= 0) {
if (!depends.some((a) => a === fnc.return_type))
depends.push(fnc.return_type);
} else {
if (builtin.indexOf(fnc.return_type) < 0) {
throw new IRError(
fnc,
`Type ${fnc.return_type} is not defined`
);
}
}
}
for (const input of fnc.inputs) {
if (defined.indexOf(input.type) >= 0) {
if (!depends.some((a) => a === input.type))
depends.push(input.type);
} else {
if (builtin.indexOf(input.type) < 0) {
throw new IRError(
fnc,
`Type ${input.type} is not defined`
);
}
}
}
return {
name: fnc.name,
inputs: fnc.inputs,
return: fnc.return_type,
} as ServiceFunctionDefinition;
});
steps.push([
"service",
{
name: statement.name,
depends,
functions,
} as ServiceDefinition,
]);
} else {
throw new IRError(statement, "Invalid statement!");
}
});
return steps;
}

381
src/parser.ts Normal file
View File

@ -0,0 +1,381 @@
import type { Token } from "./tokenizer";
export interface DefinitionNode {
type: string;
location: {
file: string;
idx: number;
};
}
export interface ImportStatement extends DefinitionNode {
type: "import";
path: string;
}
export interface TypeFieldStatement extends DefinitionNode {
type: "type_field";
name: string;
fieldtype: string;
array: boolean;
map?: string;
}
export interface EnumValueStatement extends DefinitionNode {
type: "enum_value";
name: string;
value?: number;
}
export interface EnumStatement extends DefinitionNode {
type: "enum";
name: string;
values: EnumValueStatement[];
}
export interface TypeStatement extends DefinitionNode {
type: "type";
name: string;
fields: TypeFieldStatement[];
}
export interface IServiceFunctionInput {
name: string;
type: string;
}
export interface ServiceFunctionStatement extends DefinitionNode {
type: "service_function";
inputs: IServiceFunctionInput[];
name: string;
return_type: string | undefined; // Makes it a notification
}
export interface ServiceStatement extends DefinitionNode {
type: "service";
name: string;
functions: ServiceFunctionStatement[];
}
export type RootStatementNode =
| ImportStatement
| TypeStatement
| ServiceStatement
| EnumStatement;
export type StatementNode =
| RootStatementNode
| TypeFieldStatement
| ServiceFunctionStatement
| EnumValueStatement;
export type Parsed = RootStatementNode[];
export class ParserError extends Error {
token: Token;
constructor(message: string, token: Token) {
super(message);
this.token = token;
}
}
export default function parse(tokens: Token[], file: string): Parsed {
const tokenIterator = tokens[Symbol.iterator]();
let currentToken: Token = tokenIterator.next().value;
let nextToken: Token = tokenIterator.next().value;
const eatToken = (value?: string) => {
if (value && value !== currentToken.value) {
throw new ParserError(
`Unexpected token value, expected '${value}', received '${currentToken.value}'`,
currentToken
);
}
let idx = currentToken.startIdx;
currentToken = nextToken;
nextToken = tokenIterator.next().value;
return idx;
};
const eatText = (): [string, number] => {
checkTypes("text");
let val = currentToken.value;
let idx = currentToken.startIdx;
eatToken();
return [val, idx];
};
const eatNumber = (): number => {
checkTypes("number");
let val = Number(currentToken.value);
if (Number.isNaN(val)) {
throw new ParserError(
`Value cannot be parsed as number! ${currentToken.value}`,
currentToken
);
}
eatToken();
return val;
};
const checkTypes = (...types: string[]) => {
if (types.indexOf(currentToken.type) < 0) {
throw new ParserError(
`Unexpected token value, expected ${types.join(" | ")}, received '${
currentToken.value
}'`,
currentToken
);
}
};
// const parseUnionField = (): UnionFieldStatement => {
// let idx = currentToken.startIdx;
// let name = currentToken.value;
// eatToken();
// eatToken(":");
// let [type] = eatText();
// eatToken("=");
// let label = eatNumber();
// eatToken(";");
// return {
// type: "union_field",
// name,
// label,
// fieldtype: type,
// location: { file, idx },
// };
// };
const parseTypeField = (): TypeFieldStatement => {
const idx = currentToken.startIdx;
let name = currentToken.value;
eatToken();
eatToken(":");
let array = false;
let type: string;
let mapKey: string | undefined = undefined;
if (currentToken.type === "curly_open") {
eatToken("{");
[mapKey] = eatText();
eatToken(",");
[type] = eatText();
eatToken("}");
} else {
[type] = eatText();
if (currentToken.type === "array") {
array = true;
eatToken("[]");
}
}
eatToken(";");
return {
type: "type_field",
name,
fieldtype: type,
array,
map: mapKey,
location: { file, idx },
};
};
const parseTypeStatement = (): TypeStatement => {
const idx = eatToken("type");
let [name] = eatText();
eatToken("{");
let fields: TypeFieldStatement[] = [];
while (currentToken.type === "text" || currentToken.type === "keyword") {
//Keywords can also be field names
fields.push(parseTypeField());
}
eatToken("}");
return {
type: "type",
name,
fields,
location: { file, idx },
};
};
// const parseUnionStatement = (): UnionStatement => {
// const idx = eatToken("union");
// let [name] = eatText();
// eatToken("{");
// let fields: UnionFieldStatement[] = [];
// while (currentToken.type === "text") {
// fields.push(parseUnionField());
// }
// eatToken("}");
// return {
// type: "union",
// name,
// fields,
// location: { file, idx },
// };
// };
const parseImportStatement = (): ImportStatement => {
const idx = eatToken("import");
checkTypes("text", "string");
let path = currentToken.value;
if (currentToken.type === "string") {
path = path.substring(1, path.length - 1);
}
eatToken();
eatToken(";");
return {
type: "import",
path,
location: { file, idx },
};
};
const parseEnumValue = (): EnumValueStatement => {
let [name, idx] = eatText();
let value = undefined;
if (currentToken.type === "equals") {
eatToken("=");
value = eatNumber();
}
return {
type: "enum_value",
name,
value,
location: { file, idx },
};
};
const parseEnumStatement = (): EnumStatement => {
let idx = eatToken("enum");
let [name] = eatText();
eatToken("{");
let values: EnumValueStatement[] = [];
let next = currentToken.type === "text";
while (next) {
values.push(parseEnumValue());
if (currentToken.type === "comma") {
eatToken(",");
next = true;
} else {
next = false;
}
}
eatToken("}");
return {
type: "enum",
name: name,
values: values,
location: { file, idx },
};
};
const parseServiceFunction = (
notification?: boolean
): ServiceFunctionStatement => {
const [name, idx] = eatText();
eatToken("(");
let input_streaming: string | undefined = undefined;
let inputs = [];
if (currentToken.value !== ")") {
while (true) {
const [name] = eatText();
eatToken(":");
const [type] = eatText();
inputs.push({ name, type });
if (currentToken.value !== ",") break;
eatToken(",");
}
}
eatToken(")");
let return_type = undefined;
if (!notification) {
eatToken(":");
return_type = eatText()[0];
}
eatToken(";");
return {
type: "service_function",
name,
location: {
file,
idx,
},
inputs,
return_type,
};
};
const parseServiceStatement = (): ServiceStatement => {
let idx = eatToken("service");
let [name] = eatText();
eatToken("{");
let functions: ServiceFunctionStatement[] = [];
while (currentToken.type === "text") {
let notification = false;
if (currentToken.value == "notification") {
eatText();
notification = true;
}
functions.push(parseServiceFunction(notification));
}
eatToken("}");
return {
type: "service",
name: name,
functions,
location: { file, idx },
};
};
const parseStatement = () => {
if (currentToken.type === "keyword") {
switch (currentToken.value) {
case "type":
return parseTypeStatement();
// case "union":
// return parseUnionStatement();
case "import":
return parseImportStatement();
case "enum":
return parseEnumStatement();
case "service":
return parseServiceStatement();
default:
throw new ParserError(
`Unknown keyword ${currentToken.value}`,
currentToken
);
}
} else {
throw new ParserError(
`Invalid statement! ${currentToken.value}`,
currentToken
);
}
};
const nodes: RootStatementNode[] = [];
while (currentToken) {
nodes.push(parseStatement());
}
return nodes;
}

154
src/process.ts Normal file
View File

@ -0,0 +1,154 @@
import dbg from "debug";
import * as FS from "fs";
import Color from "chalk";
import * as Path from "path";
import tokenize, { TokenizerError } from "./tokenizer";
import parse, { Parsed, ParserError } from "./parser";
import get_ir, { IR } from "./ir";
import compile, { CompileTarget } from "./compile";
import { ESMTypescriptTarget, NodeJSTypescriptTarget } from "./targets/typescript";
const log = dbg("app");
const Targets = new Map<string,typeof CompileTarget>();
Targets.set("ts-esm", ESMTypescriptTarget)
Targets.set("ts-node", NodeJSTypescriptTarget)
function indexToLineAndCol(src: string, index: number) {
let line = 1;
let col = 1;
for (let i = 0; i < index; i++) {
if (src.charAt(i) === "\n") {
line++;
col = 1;
} else {
col++;
}
}
return { line, col };
}
const fileCache = new Map<string, string>();
function getFile(name: string) {
if (fileCache.has(name)) return fileCache.get(name);
else {
try {
const data = FS.readFileSync(name, "utf-8");
fileCache.set(name, data);
return data;
} catch (err) {
printError(new Error(`Cannot open file ${name};`), null, 0);
}
}
return undefined;
}
function printError(err: Error, file: string | null, idx: number) {
let loc = { line: 0, col: 0 };
if (file != null) {
const data = getFile(file);
if (data) loc = indexToLineAndCol(data, idx);
}
console.error(`${Color.red("ERROR: at")} ${file}:${loc.line}:${loc.col}`);
console.error(" ", err.message);
log(err.stack);
}
export type Target = {
type: string;
output: string;
};
export type CompileOptions = {
input: string;
targets: Target[];
emitDefinitions?: string;
};
type ProcessContext = {
options: CompileOptions;
processedFiles: Set<string>;
};
function processFile(ctx: ProcessContext, file: string): Parsed | null {
file = Path.resolve(file);
if (ctx.processedFiles.has(file)) {
log("Skipping file %s since it has already be processed", file);
return null;
}
log("Processing file %s", file);
const content = getFile(file);
if (!content) throw new Error("Could not open file " + file);
try {
log("Tokenizing %s", file);
const tokens = tokenize(content);
log("Parsing %s", file);
const parsed = parse(tokens, file);
log("Resolving imports of %s", file);
let resolved = parsed
.map((statement) => {
if (statement.type == "import") {
const base = Path.dirname(file);
const resolved = Path.resolve(Path.join(base, statement.path));
return processFile(ctx, resolved);
} else {
return statement;
}
})
.filter((e) => !!e)
.flat(1) as Parsed;
return resolved;
} catch (err) {
if (err instanceof TokenizerError) {
printError(err, file, err.index);
} else if (err instanceof ParserError) {
printError(err, file, err.token.startIdx);
} else {
throw err;
}
return null;
}
}
export default function startCompile(options: CompileOptions) {
const ctx = {
options,
processedFiles: new Set(),
} as ProcessContext;
let ir: IR | undefined = undefined;
if(options.input.endsWith(".json")) {
ir = JSON.parse(FS.readFileSync(options.input, "utf-8"));
} else {
const parsed = processFile(ctx, options.input);
// console.log(([...parsed].pop() as any).functions)
if(!parsed)
throw new Error("Error compiling: Parse output is undefined!");
ir = get_ir(parsed);
}
if(!ir)
throw new Error("Error compiling: Cannot get IR");
if(options.emitDefinitions) {
FS.writeFileSync(options.emitDefinitions, JSON.stringify(ir));
}
options.targets.forEach(target => {
const tg = Targets.get(target.type) as any
if(!tg) {
console.log(Color.red("ERROR:"), "Target not supported!");
return;
}
compile(ir, new tg(target.output)); //TODO: implement
})
}

426
src/targets/typescript.ts Normal file
View File

@ -0,0 +1,426 @@
import {
TypeDefinition,
ServiceDefinition,
EnumDefinition,
TypeFieldDefinition,
Step,
} from "../ir";
import { CompileTarget, } from "../compile";
type lineAppender = (ind: number, line: string | string[]) => void;
const conversion = {
boolean: "boolean",
number: "number",
string: "string",
};
function toJSType(type: string): string {
return (conversion as any)[type] || type;
}
export class TypescriptTarget extends CompileTarget {
name = "Typescript";
flavour: "esm" | "node" = "node";
start() {}
private generateImport(imports: string, path: string) {
return `import ${imports} from "${
path + (this.flavour === "esm" ? ".ts" : "")
}";\n`;
}
private generateImports(
a: lineAppender,
def: TypeDefinition | ServiceDefinition
) {
a(
0,
def.depends.map((dep) =>
this.generateImport(
`${dep}, { verify_${dep} }`,
"./" + dep
)
)
);
}
private getFileName(typename: string) {
return typename + ".ts";
}
private writeFormattedFile(file: string, code: string) {
this.writeFile(file, code);
//TODO: Add Prettier back
// const formatted = format(code, {
// parser: "typescript",
// tabWidth: 3,
// });
// this.writeFile(file, formatted);
}
generateType(def: TypeDefinition) {
let lines: string[] = [];
const a: lineAppender = (i, t) => {
if (!Array.isArray(t)) {
t = [t];
}
t.forEach((l) => lines.push(" ".repeat(i) + l.trim()));
};
this.generateImports(a, def);
a(0, `export default class ${def.name} {`);
a(
1,
def.fields.map((field) => {
let type = "";
if (field.array) {
type = toJSType(field.type) + "[]";
} else if (field.map) {
type = `Map<${toJSType(field.map)}, ${toJSType(field.type)}>`;
} else {
type = toJSType(field.type);
}
return `${field.name}?: ${type}; `;
})
);
a(0, ``);
a(1, `constructor(init?:Partial<${def.name}>){`);
a(2, `if(init){`)
def.fields.forEach(field=>{
a(3, `if(init["${field.name}"])`)
a(4, `this.${field.name} = init["${field.name}"];`)
})
a(2, `}`);
a(1, `}`);
a(0, ``);
a(0, ``);
a(1, `static verify(data: ${def.name}){`);
a(2, `return verify_${def.name}(data);`);
a(1, `}`)
a(0, "}");
a(0, `export function verify_${def.name}(data: ${def.name}): boolean {`);
{
def.fields.forEach((field) => {
a(
1,
`if(data["${field.name}"] !== null && data["${field.name}"] !== undefined ) {`
);
const ap: lineAppender = (i, l) => a(i + 2, l);
const verifyType = ( )=>{};
a(2, "// TODO: Implement")
//TODO: Build verification
// if (field.array) {
// a(2, `if(!Array.isArray(data["${field.name}"])) return false`);
// a(2, `if(!(data["${field.name}"].some(e=>))) return false`)
// serializeArray(field, `data["${field.name}"]`, ap);
// } else if (field.map) {
// serializeMap(field, `data["${field.name}"]`, ap);
// } else {
// serializeType(field, `data["${field.name}"]`, true, ap);
// }
a(1, "}");
a(0, ``);
});
a(1, `return true`);
}
a(0, `}`);
this.writeFormattedFile(this.getFileName(def.name), lines.join("\n"));
}
generateEnum(def: EnumDefinition) {
let lines: string[] = [];
const a: lineAppender = (i, t) => {
if (!Array.isArray(t)) {
t = [t];
}
t.forEach((l) => lines.push(" ".repeat(i) + l.trim()));
};
a(0, `enum ${def.name} {`);
for (const value of def.values) {
a(1, `${value.name}=${value.value},`);
}
a(0, `}`);
a(0, `export default ${def.name}`);
a(
0,
`export function verify_${def.name} (data: ${def.name}): boolean {`
);
a(1, `return ${def.name}[data] != undefined`);
a(0, "}");
this.writeFormattedFile(this.getFileName(def.name), lines.join("\n"));
}
generateServiceClient(def: ServiceDefinition) {
let lines: string[] = [];
const a: lineAppender = (i, t) => {
if (!Array.isArray(t)) {
t = [t];
}
t.forEach((l) => lines.push(" ".repeat(i) + l.trim()));
};
this.generateImports(a, def);
a(0, `export type {`);
def.depends.forEach((dep) => {
a(1, `${dep},`);
});
a(0, `}`);
this.writeFile(
"service_client.ts",
this.generateImport(
"{ RequestObject, ResponseObject, ErrorCodes, Logging }",
"./service_base"
) +
"\n\n" +
this.getTemplate("ts_service_client.ts")
);
a(
0,
this.generateImport(
"{ Service, ServiceProvider, getRandomID }",
"./service_client"
)
);
a(0, `export class ${def.name} extends Service {`);
a(1, `constructor(provider: ServiceProvider){`);
a(2, `super(provider, "${def.name}");`);
a(1, `}`);
for(const fnc of def.functions) {
const params = fnc.inputs.map(e=>`${e.name}: ${toJSType(e.type)}`).join(",");
//TODO: Prio 1 : Verify response!
//TODO: Prio 2 : Add optional parameters to this and the declaration file
//TODO: Prio 3 : Maybe verify params? But the server will to this regardless so... Maybe not?`
if(!fnc.return) {
a(1, `${fnc.name}(${params}): void {`);
a(2, `this._provider.sendMessage({`);
a(3, `jsonrpc: "2.0",`);
a(3, `method: "${def.name}.${fnc.name}",`);
a(3, `params: [...arguments]`);
a(2, `});`);
a(1, `}`);
} else {
const retType = fnc.return ? toJSType(fnc.return) : "void";
a(1, `${fnc.name}(${params}): Promise<${retType}> {`);
a(2, `return new Promise<${retType}>((ok, err) => {`)
a(3, `this._provider.sendMessage({`);
a(4, `jsonrpc: "2.0",`);
a(4, `id: getRandomID(16),`);
a(4, `method: "${def.name}.${fnc.name}",`);
a(4, `params: [...arguments]`);
a(3, `}, {`);
a(4, `ok, err`);
a(3, `});`);
a(2, `});`);
a(1, `}`);
}
a(0, ``);
}
a(0, `}`);
this.writeFormattedFile(
this.getFileName(def.name + "_client"),
lines.join("\n")
);
}
generateServiceServer(def: ServiceDefinition) {
let lines: string[] = [];
const a: lineAppender = (i, t) => {
if (!Array.isArray(t)) {
t = [t];
}
t.forEach((l) => lines.push(" ".repeat(i) + l.trim()));
};
this.writeFile(
"service_server.ts",
this.generateImport(
"{ RequestObject, ResponseObject, ErrorCodes, Logging }",
"./service_base"
) +
"\n\n" +
this.getTemplate("ts_service_server.ts")
);
this.generateImports(a, def);
a(0, `export type {`);
def.depends.forEach((dep) => {
a(1, `${dep},`);
});
a(0, `}`);
a(
0,
this.generateImport(
"{ Service }",
"./service_server"
)
);
a(0, `export abstract class ${def.name}<T> extends Service<T> {`);
a(1, `public name = "${def.name}";`);
a(1, `constructor(){`);
a(2, `super();`);
for(const fnc of def.functions) {
a(2, `this.functions.add("${fnc.name}")`);
}
a(1, `}`)
a(0, ``);
for(const fnc of def.functions) {
const params = [...fnc.inputs.map(e=>`${e.name}: ${toJSType(e.type)}`), `ctx: T`].join(", ");
const retVal = fnc.return ? `Promise<${toJSType(fnc.return)}>` : `void`;
a(1, `abstract ${fnc.name}(${params}): ${retVal};`)
// a(0, ``);
a(1, `_${fnc.name}(params: any[] | any, ctx: T): ${retVal} {`);
a(2, `let p: any[] = [];`);
a(2, `if(Array.isArray(params)){`);
//TODO: Verify params!
a(3, `p = params;`);
a(2, `} else {`);
for(const param of fnc.inputs) {
a(3, `p.push(params["${param.name}"])`);
}
a(2, `}`);
a(2, `p.push(ctx);`) //TODO: Either this or [...p, ctx] but idk
a(2, `return this.${fnc.name}.call(this, ...p);`);
a(1, `}`)
a(0, ``);
}
a(0, `}`)
this.writeFormattedFile(
this.getFileName(def.name + "_server"),
lines.join("\n")
);
}
generateService(def: ServiceDefinition) {
this.writeFile("service_base.ts", this.getTemplate("ts_service_base.ts"));
this.generateServiceClient(def);
this.generateServiceServer(def);
}
finalize(steps: Step[]) {
let linesClient: string[] = [];
let linesServer: string[] = [];
const ac: lineAppender = (i, t) => {
if (!Array.isArray(t)) {
t = [t];
}
t.forEach((l) => linesClient.push(" ".repeat(i) + l.trim()));
};
const as: lineAppender = (i, t) => {
if (!Array.isArray(t)) {
t = [t];
}
t.forEach((l) => linesServer.push(" ".repeat(i) + l.trim()));
};
let lines:string[] = [];
const a: lineAppender = (i, t) => {
if (!Array.isArray(t)) {
t = [t];
}
t.forEach((l) => lines.push(" ".repeat(i) + l.trim()));
};
let hasService = false;
steps.forEach(([type, def]) => {
switch(type) {
case "type":
a(0, this.generateImport(`${def.name}, { verify_${def.name} }`, "./" + def.name))
a(0, `export { verify_${def.name} }`)
a(0, `export type { ${def.name} }`);
a(0,``);
break;
case "enum":
a(0, this.generateImport(`${def.name}, { verify_${def.name} }`, "./" + def.name))
a(0, `export { ${def.name}, verify_${def.name} }`)
a(0,``);
break;
case "service":
let ext = this.flavour == "esm" ? ".ts" : "";
if(!hasService) {
hasService = true;
ac(0, `export * from "./service_client${ext}"`);
ac(0,``);
as(0, `export * from "./service_server${ext}"`);
as(0,``);
a(0, `export * as Client from "./index_client${ext}"`);
a(0, `export * as Server from "./index_server${ext}"`);
a(0, `export { Logging } from "./service_base${ext}"`);
a(0,``)
//TODO: Export service globals
}
ac(0, `export { ${def.name} } from "./${def.name}_client${ext}"`);
as(0, `export { ${def.name} } from "./${def.name}_server${ext}"`);
ac(0,``);
as(0,``);
break;
}
})
this.writeFormattedFile(
this.getFileName("index"),
lines.join("\n")
);
this.writeFormattedFile(
this.getFileName("index_client"),
linesClient.join("\n")
);
this.writeFormattedFile(
this.getFileName("index_server"),
linesServer.join("\n")
);
}
}
export class ESMTypescriptTarget extends TypescriptTarget {
name = "ts-esm";
flavour: "esm" = "esm";
}
export class NodeJSTypescriptTarget extends TypescriptTarget {
name = "ts-node";
flavour: "node" = "node";
}

93
src/tokenizer.ts Normal file
View File

@ -0,0 +1,93 @@
export type TokenTypes =
| "space"
| "comment"
| "string"
| "keyword"
| "colon"
| "semicolon"
| "comma"
| "equals"
| "curly_open"
| "curly_close"
| "bracket_open"
| "bracket_close"
| "array"
| "questionmark"
| "number"
| "text";
export type Token = {
type: TokenTypes;
value: string;
startIdx: number;
endIdx: number;
};
type Matcher = (input: string, index: number) => undefined | Token;
export class TokenizerError extends Error {
index: number;
constructor(message: string, index: number) {
super(message);
this.index = index;
}
}
function regexMatcher(regex: string | RegExp, type: TokenTypes): Matcher {
if (typeof regex === "string") regex = new RegExp(regex);
return (input: string, index: number) => {
let matches = input.substring(index).match(regex as RegExp);
if (!matches || matches.length <= 0) return undefined;
return {
type,
value: matches[0],
startIdx: index,
endIdx: index + matches[0].length,
} as Token;
};
}
const matcher = [
regexMatcher(/^\s+/, "space"),
regexMatcher(/^(\/\*)(.|\s)*?(\*\/)/g, "comment"),
regexMatcher(/^\/\/.+/, "comment"),
regexMatcher(/^#.+/, "comment"),
regexMatcher(/^".*?"/, "string"),
// regexMatcher(/(?<=^")(.*?)(?=")/, "string"),
regexMatcher(/^(type|enum|import|service)\b/, "keyword"),
regexMatcher(/^\:/, "colon"),
regexMatcher(/^\;/, "semicolon"),
regexMatcher(/^\,/, "comma"),
regexMatcher(/^\=/, "equals"),
regexMatcher(/^{/, "curly_open"),
regexMatcher(/^}/, "curly_close"),
regexMatcher(/^\(/, "bracket_open"),
regexMatcher(/^\)/, "bracket_close"),
regexMatcher(/^\[\]/, "array"),
regexMatcher(/^\?/, "questionmark"),
regexMatcher(/^[\.0-9]+/, "number"),
regexMatcher(/^[a-zA-Z_]([a-zA-Z0-9_]?)+/, "text"),
];
export default function tokenize(input: string) {
let index = 0;
let tokens: Token[] = [];
while (index < input.length) {
const matches = matcher.map((m) => m(input, index)).filter((e) => !!e);
let match = matches[0];
if (match) {
if (match.type !== "space" && match.type !== "comment") {
tokens.push(match);
}
index += match.value.length;
} else {
throw new TokenizerError(
`Unexpected token '${input.substring(index, index + 1)}'`,
index
);
}
}
return tokens;
}

View File

@ -0,0 +1,30 @@
export const Logging = {
verbose: false,
log(...args: any[]) {
if(Logging.verbose) {
console.log(...args)
}
}
}
export enum ErrorCodes {
ParseError=-32700,
InvalidRequest=-32700,
MethodNotFound=-32700,
InvalidParams=-32700,
InternalError=-32700,
}
export interface RequestObject {
jsonrpc: "2.0";
method: string;
params?: any[] | { [key: string]: any };
id?: string | null;
}
export interface ResponseObject {
jsonrpc: "2.0";
result?: any;
error?: { code: ErrorCodes, message:string, data?: any}
id: string;
}

View File

@ -0,0 +1,93 @@
//@template-ignore
import { RequestObject, ResponseObject, ErrorCodes, Logging } from "./ts_service_base";
export type IMessageCallback = (data: any) => void;
export type ResponseListener = {
ok: (response:any)=>void;
err: (error: Error)=>void;
}
export class Service {
public _name: string = null as any;
constructor(protected _provider: ServiceProvider, name: string) {
this._name = name;
this._provider.services.set(name, this);
}
}
export class ServiceProvider {
services = new Map<string, Service>();
requests = new Map<string, ResponseListener>();
constructor(private sendPacket: IMessageCallback) {}
onPacket(msg: RequestObject | ResponseObject) {
Logging.log("CLIENT: Received message:", msg);
if("method" in msg) {
if(msg.id){
Logging.log("CLIENT: Determined type is Request");
// Request, which are not supported by client, so ignore
return;
} else {
Logging.log("CLIENT: Determined type is Notification");
//Notification. Send to Notification handler
//TODO: Implement
}
} else {
Logging.log("CLIENT: Determined type is Response");
// Response
let resListener = this.requests.get(msg.id);
if(!resListener) return; // Ignore wrong responses
if(msg.error) {
resListener.err(new Error(msg.error.message));
} else {
resListener.ok(msg.result);
}
}
}
sendMessage(msg: RequestObject, res?: ResponseListener) {
Logging.log("CLIENT: Sending Messgage", msg);
if(msg.id) {
this.requests.set(msg.id, res);
}
this.sendPacket(msg)
}
}
declare var require: any;
export const getRandomBytes = (
typeof self !== "undefined" && (self.crypto || (self as any).msCrypto)
? function () {
// Browsers
var crypto = self.crypto || (self as any).msCrypto;
var QUOTA = 65536;
return function (n: number) {
var a = new Uint8Array(n);
for (var i = 0; i < n; i += QUOTA) {
crypto.getRandomValues(
a.subarray(i, i + Math.min(n - i, QUOTA))
);
}
return a;
};
}
: function () {
// Node
return require("crypto").randomBytes;
}
)() as (cnt: number) => Uint8Array;
export const getRandomID = (length: number) => {
return btoa(String.fromCharCode.apply(null, getRandomBytes(length) as any));
};

View File

@ -0,0 +1,120 @@
//@template-ignore
import { RequestObject, ResponseObject, ErrorCodes, Logging } from "./ts_service_base";
export class Service<T> {
public name: string = null as any;
public functions = new Set<string>();
constructor() {}
}
type ISendMessageCB = (data: any, catchedErr?: Error) => void;
export class ServiceProvider<T = any> {
services = new Map<string, Service<T>>();
addService(service: Service<T>) {
this.services.set(service.name, service);
Logging.log("SERVER: Adding Service to provider:", service.name);
Logging.log("SERVER: Service provides:", [...service.functions.keys()])
}
getSession(send: ISendMessageCB, ctx?: Partial<T>): Session<T> {
return new Session(this, send, ctx);
}
}
class Session<T> {
ctx: Partial<T>;
constructor(
private provider: ServiceProvider,
private _send: ISendMessageCB,
ctx?: Partial<T>
) {
this.ctx = ctx || {};
}
send(data: any, catchedErr?:Error) {
Logging.log("SERVER: Sending Message", data)
this._send(data, catchedErr);
}
async onMessage(data: RequestObject) {
Logging.log("SERVER: Received Message", data);
try {
if (!data.method) {
if (data.id) {
this.send({
jsonrpc: "2.0",
id: data.id,
error: {
code: ErrorCodes.InvalidRequest,
message: "No method defined!",
},
} as ResponseObject);
}
return;
}
const [srvName, fncName] = data.method.split(".");
Logging.log("SERVER: Message for", srvName, fncName);
const service = this.provider.services.get(srvName);
if (!service) {
Logging.log("SERVER: Did not find Service");
if (data.id) {
this.send({
jsonrpc: "2.0",
id: data.id,
error: {
code: ErrorCodes.MethodNotFound,
message: "Service not found!",
},
} as ResponseObject);
}
return;
}
const fnc = service.functions.has(fncName);
if (!fnc) {
Logging.log("SERVER: Did not find Function");
if (data.id) {
this.send({
jsonrpc: "2.0",
id: data.id,
error: {
code: ErrorCodes.MethodNotFound,
message: "Function not found!",
},
} as ResponseObject);
}
return;
}
let result = await (service as any)["_" + fncName](data.params, this.ctx);
if(data.id) { //Request
this.send({
jsonrpc: "2.0",
id: data.id,
result: result,
} as ResponseObject);
} //else Notification and response is ignored
} catch (err) {
if (data.id) {
this.send(
{
jsonrpc: "2.0",
id: data.id,
error: {
code: ErrorCodes.InternalError,
message: err.message,
},
} as ResponseObject,
err
);
}
//TODO: Think about else case
}
}
}

14
tsconfig.json Normal file
View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "CommonJS",
"moduleResolution": "node",
"outDir": "examples/out",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true
},
"include": [
"src/"
]
}

404
yarn.lock Normal file
View File

@ -0,0 +1,404 @@
# This file is generated by running "yarn install" inside your project.
# Manual changes might be lost - proceed with caution!
__metadata:
version: 5
cacheKey: 8
"@cspotcode/source-map-consumer@npm:0.8.0":
version: 0.8.0
resolution: "@cspotcode/source-map-consumer@npm:0.8.0"
checksum: c0c16ca3d2f58898f1bd74c4f41a189dbcc202e642e60e489cbcc2e52419c4e89bdead02c886a12fb13ea37798ede9e562b2321df997ebc210ae9bd881561b4e
languageName: node
linkType: hard
"@cspotcode/source-map-support@npm:0.7.0":
version: 0.7.0
resolution: "@cspotcode/source-map-support@npm:0.7.0"
dependencies:
"@cspotcode/source-map-consumer": 0.8.0
checksum: 9faddda7757cd778b5fd6812137b2cc265810043680d6399acc20441668fafcdc874053be9dccd0d9110087287bfad27eb3bf342f72bceca9aa9059f5d0c4be8
languageName: node
linkType: hard
"@hibas123/SimpleRPC@workspace:.":
version: 0.0.0-use.local
resolution: "@hibas123/SimpleRPC@workspace:."
dependencies:
"@types/debug": ^4.1.7
"@types/node": ^17.0.5
"@types/yargs": ^17.0.8
chalk: 4
debug: ^4.3.3
prettier: ^2.5.1
ts-node: ^10.4.0
typescript: ^4.5.4
yargs: ^17.3.1
languageName: unknown
linkType: soft
"@tsconfig/node10@npm:^1.0.7":
version: 1.0.8
resolution: "@tsconfig/node10@npm:1.0.8"
checksum: b8d5fffbc6b17ef64ef74f7fdbccee02a809a063ade785c3648dae59406bc207f70ea2c4296f92749b33019fa36a5ae716e42e49cc7f1bbf0fd147be0d6b970a
languageName: node
linkType: hard
"@tsconfig/node12@npm:^1.0.7":
version: 1.0.9
resolution: "@tsconfig/node12@npm:1.0.9"
checksum: a01b2400ab3582b86b589c6d31dcd0c0656f333adecde85d6d7d4086adb059808b82692380bb169546d189bf771ae21d02544a75b57bd6da4a5dd95f8567bec9
languageName: node
linkType: hard
"@tsconfig/node14@npm:^1.0.0":
version: 1.0.1
resolution: "@tsconfig/node14@npm:1.0.1"
checksum: 976345e896c0f059867f94f8d0f6ddb8b1844fb62bf36b727de8a9a68f024857e5db97ed51d3325e23e0616a5e48c034ff51a8d595b3fe7e955f3587540489be
languageName: node
linkType: hard
"@tsconfig/node16@npm:^1.0.2":
version: 1.0.2
resolution: "@tsconfig/node16@npm:1.0.2"
checksum: ca94d3639714672bbfd55f03521d3f56bb6a25479bd425da81faf21f13e1e9d15f40f97377dedbbf477a5841c5b0c8f4cd1b391f33553d750b9202c54c2c07aa
languageName: node
linkType: hard
"@types/debug@npm:^4.1.7":
version: 4.1.7
resolution: "@types/debug@npm:4.1.7"
dependencies:
"@types/ms": "*"
checksum: 0a7b89d8ed72526858f0b61c6fd81f477853e8c4415bb97f48b1b5545248d2ae389931680b94b393b993a7cfe893537a200647d93defe6d87159b96812305adc
languageName: node
linkType: hard
"@types/ms@npm:*":
version: 0.7.31
resolution: "@types/ms@npm:0.7.31"
checksum: daadd354aedde024cce6f5aa873fefe7b71b22cd0e28632a69e8b677aeb48ae8caa1c60e5919bb781df040d116b01cb4316335167a3fc0ef6a63fa3614c0f6da
languageName: node
linkType: hard
"@types/node@npm:^17.0.5":
version: 17.0.5
resolution: "@types/node@npm:17.0.5"
checksum: 105535e78722515c26cfdc1b0cbf1b19f55fe53b814e2e90d8b1e653bc63136d4760c7efc102eca111c6d124a291e37d60d761d569a3f4afb3fba05bad5d9ade
languageName: node
linkType: hard
"@types/yargs-parser@npm:*":
version: 20.2.1
resolution: "@types/yargs-parser@npm:20.2.1"
checksum: 1d039e64494a7a61ddd278349a3dc60b19f99ff0517425696e796f794e4252452b9d62178e69755ad03f439f9dc0c8c3d7b3a1201b3a24e134bac1a09fa11eaa
languageName: node
linkType: hard
"@types/yargs@npm:^17.0.8":
version: 17.0.8
resolution: "@types/yargs@npm:17.0.8"
dependencies:
"@types/yargs-parser": "*"
checksum: 63d06700ffbed745f00d7994eb92416649c8a3ead22f26446979d383f3af52fa9400bb185268f3a44a2348749098ffe33a8185ca676b77bc3206c63b8b73fd01
languageName: node
linkType: hard
"acorn-walk@npm:^8.1.1":
version: 8.2.0
resolution: "acorn-walk@npm:8.2.0"
checksum: 1715e76c01dd7b2d4ca472f9c58968516a4899378a63ad5b6c2d668bba8da21a71976c14ec5f5b75f887b6317c4ae0b897ab141c831d741dc76024d8745f1ad1
languageName: node
linkType: hard
"acorn@npm:^8.4.1":
version: 8.7.0
resolution: "acorn@npm:8.7.0"
bin:
acorn: bin/acorn
checksum: e0f79409d68923fbf1aa6d4166f3eedc47955320d25c89a20cc822e6ba7c48c5963d5bc657bc242d68f7a4ac9faf96eef033e8f73656da6c640d4219935fdfd0
languageName: node
linkType: hard
"ansi-regex@npm:^5.0.1":
version: 5.0.1
resolution: "ansi-regex@npm:5.0.1"
checksum: 2aa4bb54caf2d622f1afdad09441695af2a83aa3fe8b8afa581d205e57ed4261c183c4d3877cee25794443fde5876417d859c108078ab788d6af7e4fe52eb66b
languageName: node
linkType: hard
"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0":
version: 4.3.0
resolution: "ansi-styles@npm:4.3.0"
dependencies:
color-convert: ^2.0.1
checksum: 513b44c3b2105dd14cc42a19271e80f386466c4be574bccf60b627432f9198571ebf4ab1e4c3ba17347658f4ee1711c163d574248c0c1cdc2d5917a0ad582ec4
languageName: node
linkType: hard
"arg@npm:^4.1.0":
version: 4.1.3
resolution: "arg@npm:4.1.3"
checksum: 544af8dd3f60546d3e4aff084d451b96961d2267d668670199692f8d054f0415d86fc5497d0e641e91546f0aa920e7c29e5250e99fc89f5552a34b5d93b77f43
languageName: node
linkType: hard
"chalk@npm:4":
version: 4.1.2
resolution: "chalk@npm:4.1.2"
dependencies:
ansi-styles: ^4.1.0
supports-color: ^7.1.0
checksum: fe75c9d5c76a7a98d45495b91b2172fa3b7a09e0cc9370e5c8feb1c567b85c4288e2b3fded7cfdd7359ac28d6b3844feb8b82b8686842e93d23c827c417e83fc
languageName: node
linkType: hard
"cliui@npm:^7.0.2":
version: 7.0.4
resolution: "cliui@npm:7.0.4"
dependencies:
string-width: ^4.2.0
strip-ansi: ^6.0.0
wrap-ansi: ^7.0.0
checksum: ce2e8f578a4813806788ac399b9e866297740eecd4ad1823c27fd344d78b22c5f8597d548adbcc46f0573e43e21e751f39446c5a5e804a12aace402b7a315d7f
languageName: node
linkType: hard
"color-convert@npm:^2.0.1":
version: 2.0.1
resolution: "color-convert@npm:2.0.1"
dependencies:
color-name: ~1.1.4
checksum: 79e6bdb9fd479a205c71d89574fccfb22bd9053bd98c6c4d870d65c132e5e904e6034978e55b43d69fcaa7433af2016ee203ce76eeba9cfa554b373e7f7db336
languageName: node
linkType: hard
"color-name@npm:~1.1.4":
version: 1.1.4
resolution: "color-name@npm:1.1.4"
checksum: b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610
languageName: node
linkType: hard
"create-require@npm:^1.1.0":
version: 1.1.1
resolution: "create-require@npm:1.1.1"
checksum: a9a1503d4390d8b59ad86f4607de7870b39cad43d929813599a23714831e81c520bddf61bcdd1f8e30f05fd3a2b71ae8538e946eb2786dc65c2bbc520f692eff
languageName: node
linkType: hard
"debug@npm:^4.3.3":
version: 4.3.3
resolution: "debug@npm:4.3.3"
dependencies:
ms: 2.1.2
peerDependenciesMeta:
supports-color:
optional: true
checksum: 14472d56fe4a94dbcfaa6dbed2dd3849f1d72ba78104a1a328047bb564643ca49df0224c3a17fa63533fd11dd3d4c8636cd861191232a2c6735af00cc2d4de16
languageName: node
linkType: hard
"diff@npm:^4.0.1":
version: 4.0.2
resolution: "diff@npm:4.0.2"
checksum: f2c09b0ce4e6b301c221addd83bf3f454c0bc00caa3dd837cf6c127d6edf7223aa2bbe3b688feea110b7f262adbfc845b757c44c8a9f8c0c5b15d8fa9ce9d20d
languageName: node
linkType: hard
"emoji-regex@npm:^8.0.0":
version: 8.0.0
resolution: "emoji-regex@npm:8.0.0"
checksum: d4c5c39d5a9868b5fa152f00cada8a936868fd3367f33f71be515ecee4c803132d11b31a6222b2571b1e5f7e13890156a94880345594d0ce7e3c9895f560f192
languageName: node
linkType: hard
"escalade@npm:^3.1.1":
version: 3.1.1
resolution: "escalade@npm:3.1.1"
checksum: a3e2a99f07acb74b3ad4989c48ca0c3140f69f923e56d0cba0526240ee470b91010f9d39001f2a4a313841d237ede70a729e92125191ba5d21e74b106800b133
languageName: node
linkType: hard
"get-caller-file@npm:^2.0.5":
version: 2.0.5
resolution: "get-caller-file@npm:2.0.5"
checksum: b9769a836d2a98c3ee734a88ba712e62703f1df31b94b784762c433c27a386dd6029ff55c2a920c392e33657d80191edbf18c61487e198844844516f843496b9
languageName: node
linkType: hard
"has-flag@npm:^4.0.0":
version: 4.0.0
resolution: "has-flag@npm:4.0.0"
checksum: 261a1357037ead75e338156b1f9452c016a37dcd3283a972a30d9e4a87441ba372c8b81f818cd0fbcd9c0354b4ae7e18b9e1afa1971164aef6d18c2b6095a8ad
languageName: node
linkType: hard
"is-fullwidth-code-point@npm:^3.0.0":
version: 3.0.0
resolution: "is-fullwidth-code-point@npm:3.0.0"
checksum: 44a30c29457c7fb8f00297bce733f0a64cd22eca270f83e58c105e0d015e45c019491a4ab2faef91ab51d4738c670daff901c799f6a700e27f7314029e99e348
languageName: node
linkType: hard
"make-error@npm:^1.1.1":
version: 1.3.6
resolution: "make-error@npm:1.3.6"
checksum: b86e5e0e25f7f777b77fabd8e2cbf15737972869d852a22b7e73c17623928fccb826d8e46b9951501d3f20e51ad74ba8c59ed584f610526a48f8ccf88aaec402
languageName: node
linkType: hard
"ms@npm:2.1.2":
version: 2.1.2
resolution: "ms@npm:2.1.2"
checksum: 673cdb2c3133eb050c745908d8ce632ed2c02d85640e2edb3ace856a2266a813b30c613569bf3354fdf4ea7d1a1494add3bfa95e2713baa27d0c2c71fc44f58f
languageName: node
linkType: hard
"prettier@npm:^2.5.1":
version: 2.5.1
resolution: "prettier@npm:2.5.1"
bin:
prettier: bin-prettier.js
checksum: 21b9408476ea1c544b0e45d51ceb94a84789ff92095abb710942d780c862d0daebdb29972d47f6b4d0f7ebbfb0ffbf56cc2cfa3e3e9d1cca54864af185b15b66
languageName: node
linkType: hard
"require-directory@npm:^2.1.1":
version: 2.1.1
resolution: "require-directory@npm:2.1.1"
checksum: fb47e70bf0001fdeabdc0429d431863e9475e7e43ea5f94ad86503d918423c1543361cc5166d713eaa7029dd7a3d34775af04764bebff99ef413111a5af18c80
languageName: node
linkType: hard
"string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3":
version: 4.2.3
resolution: "string-width@npm:4.2.3"
dependencies:
emoji-regex: ^8.0.0
is-fullwidth-code-point: ^3.0.0
strip-ansi: ^6.0.1
checksum: e52c10dc3fbfcd6c3a15f159f54a90024241d0f149cf8aed2982a2d801d2e64df0bf1dc351cf8e95c3319323f9f220c16e740b06faecd53e2462df1d2b5443fb
languageName: node
linkType: hard
"strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1":
version: 6.0.1
resolution: "strip-ansi@npm:6.0.1"
dependencies:
ansi-regex: ^5.0.1
checksum: f3cd25890aef3ba6e1a74e20896c21a46f482e93df4a06567cebf2b57edabb15133f1f94e57434e0a958d61186087b1008e89c94875d019910a213181a14fc8c
languageName: node
linkType: hard
"supports-color@npm:^7.1.0":
version: 7.2.0
resolution: "supports-color@npm:7.2.0"
dependencies:
has-flag: ^4.0.0
checksum: 3dda818de06ebbe5b9653e07842d9479f3555ebc77e9a0280caf5a14fb877ffee9ed57007c3b78f5a6324b8dbeec648d9e97a24e2ed9fdb81ddc69ea07100f4a
languageName: node
linkType: hard
"ts-node@npm:^10.4.0":
version: 10.4.0
resolution: "ts-node@npm:10.4.0"
dependencies:
"@cspotcode/source-map-support": 0.7.0
"@tsconfig/node10": ^1.0.7
"@tsconfig/node12": ^1.0.7
"@tsconfig/node14": ^1.0.0
"@tsconfig/node16": ^1.0.2
acorn: ^8.4.1
acorn-walk: ^8.1.1
arg: ^4.1.0
create-require: ^1.1.0
diff: ^4.0.1
make-error: ^1.1.1
yn: 3.1.1
peerDependencies:
"@swc/core": ">=1.2.50"
"@swc/wasm": ">=1.2.50"
"@types/node": "*"
typescript: ">=2.7"
peerDependenciesMeta:
"@swc/core":
optional: true
"@swc/wasm":
optional: true
bin:
ts-node: dist/bin.js
ts-node-cwd: dist/bin-cwd.js
ts-node-script: dist/bin-script.js
ts-node-transpile-only: dist/bin-transpile.js
ts-script: dist/bin-script-deprecated.js
checksum: 3933ac0a937d33c45e04a6750fcdd3e765eb2897d1da1307cd97ac52af093bcfb632ec0453a75000a65c8b5b7bdb32b1077050a186dcc556e62657cb592e6d49
languageName: node
linkType: hard
"typescript@npm:^4.5.4":
version: 4.5.4
resolution: "typescript@npm:4.5.4"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 59f3243f9cd6fe3161e6150ff6bf795fc843b4234a655dbd938a310515e0d61afd1ac942799e7415e4334255e41c2c49b7dd5d9fd38a17acd25a6779ca7e0961
languageName: node
linkType: hard
"typescript@patch:typescript@^4.5.4#~builtin<compat/typescript>":
version: 4.5.4
resolution: "typescript@patch:typescript@npm%3A4.5.4#~builtin<compat/typescript>::version=4.5.4&hash=493e53"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 2e488dde7d2c4a2fa2e79cf2470600f8ce81bc0563c276b72df8ff412d74456ae532ba824650ae936ce207440c79720ddcfaa25e3cb4477572b8534fa4e34d49
languageName: node
linkType: hard
"wrap-ansi@npm:^7.0.0":
version: 7.0.0
resolution: "wrap-ansi@npm:7.0.0"
dependencies:
ansi-styles: ^4.0.0
string-width: ^4.1.0
strip-ansi: ^6.0.0
checksum: a790b846fd4505de962ba728a21aaeda189b8ee1c7568ca5e817d85930e06ef8d1689d49dbf0e881e8ef84436af3a88bc49115c2e2788d841ff1b8b5b51a608b
languageName: node
linkType: hard
"y18n@npm:^5.0.5":
version: 5.0.8
resolution: "y18n@npm:5.0.8"
checksum: 54f0fb95621ee60898a38c572c515659e51cc9d9f787fb109cef6fde4befbe1c4602dc999d30110feee37456ad0f1660fa2edcfde6a9a740f86a290999550d30
languageName: node
linkType: hard
"yargs-parser@npm:^21.0.0":
version: 21.0.0
resolution: "yargs-parser@npm:21.0.0"
checksum: 1e205fca1cb7a36a1585e2b94a64e641c12741b53627d338e12747f4dca3c3610cdd9bb235040621120548dd74c3ef03a8168d52a1eabfedccbe4a62462b6731
languageName: node
linkType: hard
"yargs@npm:^17.3.1":
version: 17.3.1
resolution: "yargs@npm:17.3.1"
dependencies:
cliui: ^7.0.2
escalade: ^3.1.1
get-caller-file: ^2.0.5
require-directory: ^2.1.1
string-width: ^4.2.3
y18n: ^5.0.5
yargs-parser: ^21.0.0
checksum: 64fc2e32c56739f1d14d2d24acd17a6944c3c8e3e3558f09fc1953ac112e868cc16013d282886b9d5be22187f8b9ed4f60741a6b1011f595ce2718805a656852
languageName: node
linkType: hard
"yn@npm:3.1.1":
version: 3.1.1
resolution: "yn@npm:3.1.1"
checksum: 2c487b0e149e746ef48cda9f8bad10fc83693cd69d7f9dcd8be4214e985de33a29c9e24f3c0d6bcf2288427040a8947406ab27f7af67ee9456e6b84854f02dd6
languageName: node
linkType: hard