Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP #33

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft

WIP #33

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 202 additions & 0 deletions bin/repl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
#!/usr/bin/env node
import path from 'path'
import vm from "node:vm";
import fs from "node:fs";
const repl = require('node:repl');

const { parse, each } = require("abstract-syntax-tree")
import { attribute, Digraph, Subgraph, Node, Edge, toDot } from 'ts-graphviz';
import { toFile } from 'ts-graphviz/adapter';
const execSync = require('child_process').execSync;

import opentelemetry from '@opentelemetry/api';
import { NodeSDK } from '@opentelemetry/sdk-node';
import { ConsoleSpanExporter } from '@opentelemetry/sdk-trace-node';



import { Runner, ValuesMem } from "../lib/resumable"
import inspector from "node:inspector";

const tsNode = require('ts-node')

const argv: string[] = process.argv.slice(2)

let connectorPath: string = path.resolve(process.cwd(), argv[0])

const loadConnector = async (connectorPath: string) => {
let c = require(connectorPath)
const connector = c.connector
const connectorCustomizer = c.connectorCustomizer
Object.keys(require.cache)
.filter((key: string) => !key.includes('node_modules'))
.forEach((key: string) => delete require.cache[key])

return {
connector: typeof connector === 'function' ? await connector() : connector,
connectorCustomizer: typeof connectorCustomizer === 'function' ? await connectorCustomizer() : connectorCustomizer
}
}

class X {
get(name: Function, args: any) {
console.log(`=> ${name.name}(${JSON.stringify(args)})`)
}

inspect(obj: any) {
console.log(obj)
}

store(type: string, value: any) {
}
}

const gets = (ast: any): Array<string> => {
const out = new Array();
each(ast, "CallExpression", (node: any) => {
//console.log(JSON.stringify(node, null, 2))
const functionName = node.callee.property.name
if (functionName == "get") {
var resourceName = ""
if (node.arguments[0].type == "MemberExpression") {
resourceName = node.arguments[0].property.name
} else {
resourceName = node.arguments[0].name
}
out.push(resourceName)
}
})
return out
}

(async () => {
inspector.open()
const sdk = new NodeSDK({
//traceExporter: new ConsoleSpanExporter(),
});

sdk.start();

const c = await loadConnector(connectorPath)
console.log(c.connector._resources)

const config = JSON.parse(fs.readFileSync("config.json").toString())
console.log(config)

const client = await c.connector._resources["client"](config)

const x = new X()
Object.assign(x, client);
//x.client = client

const r = repl.start('> ')
r.context.c = c
r.context.x = x
Object.assign(r.context, c.connector._resources);
Object.assign(r.context, c.connector._resumable_ops);

const go = async (resource: Function, args: any) => {
await resource(x, args)
}
r.context.go = go

const collect = async (resource: Function, args: any, outPath?: string): Promise<any> => {
// TODO track elapsed time and memory usage?
const tracer = opentelemetry.trace.getTracer(
'instrumentation-scope-name',
'instrumentation-scope-version',
);
const r = new Runner({
resources: c.connector._resources,
}, client, { logGets: true, tracer });
await r.run([[resource, args]]);
console.log(r.values);
if (outPath !== null && outPath !== undefined) {
fs.writeFileSync(outPath, JSON.stringify(r.values))
}
console.log(JSON.stringify(r.summary))

return r.values
}
r.context.collect = collect

r.defineCommand('viz',
{
action: async function(resourceName: string) {
const resource = c.connector._resources[resourceName];
const edges = new Array();
const nodes = new Map<string,Node>();

const G = new Digraph();
const A = new Subgraph('A');

const addEdges = (name: string) => {
const node = new Node(name);
nodes.set(name, node);
A.addNode(node);
const ast = parse((c.connector._resources[name] as Function).toString())
console.log(gets(ast))
for (const dest of gets(ast)) {
edges.push([name, dest]);
if (!nodes.has(dest)) {
addEdges(dest);
}
}
}

addEdges(resourceName)
console.log(edges)

for (const edge of edges) {
A.addEdge(new Edge([nodes.get(edge[0])!!, nodes.get(edge[1])!!]))
}

G.addSubgraph(A)
const dot = toDot(G)
console.log(dot)

await toFile(dot, './result.svg', { format: 'svg' });
execSync(`open file:///${process.cwd()}/result.svg`)

this.displayPrompt()
}
}
)


r.defineCommand('resources',
{
action: function() {
for (const [name,f] of Object.entries(c.connector._resources)) {
// TODO check type signature
if (name == "client") {
continue
}
const ast = parse((f as Function).toString())
//console.log(JSON.stringify(ast))
//console.log((f as Function).toString())
console.log(name)
each(ast, "CallExpression", (node: any) => {
//console.log(JSON.stringify(node, null, 2))
const functionName = node.callee.property.name
if (functionName == "get") {
//const resourceName = node.arguments[0].property.name
var resourceName = ""
if (node.arguments[0].type == "MemberExpression") {
resourceName = node.arguments[0].property.name
} else {
resourceName = node.arguments[0].name
}
console.log(` ➡ "${resourceName}"`)
} else if (functionName == "store") {
const resourceName = node.arguments[0].value
console.log(` 💾 "${resourceName}"`)
}
})

}
this.displayPrompt();
}
}
)
})()
43 changes: 41 additions & 2 deletions lib/connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,13 @@ export class Connector {
public static readonly SDK_VERSION = SDK_VERSION
private readonly _sdkVersion = Connector.SDK_VERSION
private readonly _handlers: Map<string, CommandHandler>
private _resources: any
private _resumable_ops: any
private readonly _resumable: Map<string, boolean>

constructor() {
this._handlers = new Map<string, CommandHandler>()
this._resumable = new Map<string, boolean>()
this.command(StandardCommand.StdSpecRead, StdSpecReadDefaultHandler)
}

Expand Down Expand Up @@ -102,6 +106,10 @@ export class Connector {
return this.command(StandardCommand.StdAccountList, handler)
}

stdAccountListResumable(handler: StdAccountListHandler): this {
return this.resumableCommand(StandardCommand.StdAccountList, handler)
}

/**
* Add a handler for 'std:account:read' command
* @param handler handler
Expand Down Expand Up @@ -169,6 +177,27 @@ export class Connector {
return this
}

/**
* Add a handler for a command of specified type
* @param type command type
* @param handler handler
*/
resumableCommand(type: string, handler: CommandHandler): this {
this._handlers.set(type, handler)
this._resumable.set(type, true)
return this
}

registerResources(resources: any): this {
this._resources = resources
return this
}

registerResumable(resumable: any): this {
this._resumable_ops = resumable
return this
}

/**
* Execute the handler for given command type
*
Expand All @@ -186,10 +215,20 @@ export class Connector {
throw new Error(`unsupported command: ${type}`)
}

// TODO sloppy
const contextNew = {
...context,
resources: this._resources
}

await contextState.run(context, async () => {
// If customizer does not exist, we just run the command handler itself.
if (!customizer) {
return handler(context, input, new ResponseStream<any>(res))
//if this._resumable.get(type) {
// return handler(context, input, new ResponseStream<any>(res))
//} else {
return handler(contextNew, input, new ResponseStream<any>(res))
//}
}

// If before handler exists, run the before handler and updates the command input
Expand Down Expand Up @@ -244,4 +283,4 @@ export class Connector {
*/
export const createConnector = (): Connector => {
return new Connector()
}
}
1 change: 1 addition & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from './connector-handler'
export * from './connector-customizer-handler'
export * from './response'
export * from './logger'
export * from './resumable'
14 changes: 14 additions & 0 deletions lib/repl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

export const loadConnector = async (connectorPath: string) => {
let c = require(connectorPath)
const connector = c.connector
const connectorCustomizer = c.connectorCustomizer
Object.keys(require.cache)
.filter((key: string) => !key.includes('node_modules'))
.forEach((key: string) => delete require.cache[key])

return {
connector: typeof connector === 'function' ? await connector() : connector,
connectorCustomizer: typeof connectorCustomizer === 'function' ? await connectorCustomizer() : connectorCustomizer
}
}
Loading