2021-05-20 00:19:25 -04:00
|
|
|
import debug from 'debug';
|
|
|
|
|
import _ from 'lodash';
|
|
|
|
|
const log = debug('vogue:instance');
|
|
|
|
|
import vm from 'vm';
|
2021-05-21 23:31:35 -04:00
|
|
|
import Module, { Link, Variable } from './Module.js';
|
2021-05-20 20:34:50 -04:00
|
|
|
import System from './System.js';
|
2021-05-21 23:31:35 -04:00
|
|
|
import * as uuid from 'uuid';
|
2021-05-20 00:19:25 -04:00
|
|
|
/**
|
|
|
|
|
* @typedef {import('./System.js').default} System
|
|
|
|
|
* @typedef {import('./Module.js').default} Module
|
|
|
|
|
*/
|
|
|
|
|
|
2021-05-21 23:31:35 -04:00
|
|
|
export type SerializedInstance = {
|
|
|
|
|
type: string,
|
|
|
|
|
links: {
|
|
|
|
|
[name: string]: string
|
|
|
|
|
},
|
|
|
|
|
members: {
|
|
|
|
|
[name: string]: any // SO LONG AS ITS SERIALIZABLE ._.*
|
|
|
|
|
},
|
|
|
|
|
id: string
|
|
|
|
|
}
|
2021-05-20 00:19:25 -04:00
|
|
|
|
2021-05-21 23:31:35 -04:00
|
|
|
export default class Instance {
|
2021-05-20 20:34:50 -04:00
|
|
|
module: Module;
|
2021-05-20 00:19:25 -04:00
|
|
|
links = {}
|
2021-05-20 20:34:50 -04:00
|
|
|
system: System;
|
|
|
|
|
context: vm.Context;
|
2021-05-20 00:19:25 -04:00
|
|
|
locals = [];
|
|
|
|
|
internalFunctions = {};
|
2021-05-20 20:34:50 -04:00
|
|
|
_link: Instance;
|
|
|
|
|
location: string;
|
2021-05-21 23:31:35 -04:00
|
|
|
_id: string;
|
2021-05-20 00:19:25 -04:00
|
|
|
|
2021-05-20 20:34:50 -04:00
|
|
|
createContext(): vm.Context {
|
|
|
|
|
if(this.context) return this.context;
|
|
|
|
|
|
2021-05-21 23:31:35 -04:00
|
|
|
const initialContext: any = {};
|
2021-05-20 00:19:25 -04:00
|
|
|
|
|
|
|
|
// system globals!
|
|
|
|
|
// TODO turn this into its own vogue module! system.create/instance.create
|
|
|
|
|
// TODO request context from system...
|
2021-05-21 01:04:38 -04:00
|
|
|
initialContext.create = this.system.newInstance.bind(this.system);
|
|
|
|
|
initialContext.process = process;
|
2021-05-20 20:34:50 -04:00
|
|
|
for(const name in this.system.staticInstances)
|
2021-05-20 00:19:25 -04:00
|
|
|
initialContext[name] = this.system.staticInstances[name];
|
|
|
|
|
|
|
|
|
|
// local links!
|
|
|
|
|
// optional arrays
|
|
|
|
|
// TODO maybe make these property accessors to allow for some automation
|
2021-05-20 20:34:50 -04:00
|
|
|
for(const link of this.module.links.filter((v: Link) => v.array && !v.required))
|
|
|
|
|
initialContext[link.name] = [];
|
|
|
|
|
for(const link of this.module.links.filter((v: Link) => !v.array && !v.required))
|
|
|
|
|
initialContext[link.name] = null;
|
2021-05-21 01:04:38 -04:00
|
|
|
for(const variable of this.module.variables)
|
|
|
|
|
initialContext[variable.name] = null;
|
|
|
|
|
for(const name in this.module.imports)
|
|
|
|
|
initialContext[name] = this.module.imports[name];
|
2021-05-20 20:34:50 -04:00
|
|
|
|
2021-05-21 23:31:35 -04:00
|
|
|
// instance defined functions
|
|
|
|
|
initialContext.sync = this.system.saveInstance.bind(this.system, this);
|
|
|
|
|
|
2021-05-20 20:34:50 -04:00
|
|
|
const context = vm.createContext(initialContext);
|
|
|
|
|
|
2021-05-21 23:31:35 -04:00
|
|
|
// user defined functions
|
2021-05-20 20:34:50 -04:00
|
|
|
for(const name in this.module.functions) {
|
|
|
|
|
const { code, parameters, async } = this.module.functions[name];
|
|
|
|
|
const injectedScript =
|
|
|
|
|
`
|
|
|
|
|
var ${name} = ${async ? 'async' : ''} function ${name}(${parameters.join(', ')}) ${code}
|
2021-05-21 23:31:35 -04:00
|
|
|
${name} = ${name}.bind(this);
|
2021-05-20 20:34:50 -04:00
|
|
|
`;
|
2021-05-21 23:31:35 -04:00
|
|
|
vm.runInContext(injectedScript, context, {});
|
2021-05-20 20:34:50 -04:00
|
|
|
}
|
2021-05-20 00:19:25 -04:00
|
|
|
|
2021-05-20 20:34:50 -04:00
|
|
|
return context;
|
2021-05-20 00:19:25 -04:00
|
|
|
};
|
|
|
|
|
|
2021-05-20 20:34:50 -04:00
|
|
|
constructor(module: Module, location: string, parameters: {[name: string]: any}, system: System) {
|
2021-05-20 00:19:25 -04:00
|
|
|
this.module = module;
|
|
|
|
|
this.location = location;
|
|
|
|
|
this.system = system;
|
2021-05-20 20:34:50 -04:00
|
|
|
this.context = this.createContext();
|
2021-05-21 23:31:35 -04:00
|
|
|
this._id = uuid.v4();
|
2021-05-20 00:19:25 -04:00
|
|
|
|
|
|
|
|
this._link = new Proxy(this, {
|
2021-05-21 01:04:38 -04:00
|
|
|
get(target: Instance, prop: string, receiver) {
|
|
|
|
|
log(`getting ${target.module.name.full}.${prop}: (${target.module.identifiers[prop]}|${typeof target.context[prop]})`);
|
|
|
|
|
const DNEText = `${target.module.name.full}.${prop.toString()} either does not exist, or is not accessible`;
|
|
|
|
|
if(prop === 'restore') throw new Error(DNEText);
|
2021-05-21 23:31:35 -04:00
|
|
|
if(prop === '__link__') return target._id;
|
2021-05-21 01:04:38 -04:00
|
|
|
|
2021-05-20 00:19:25 -04:00
|
|
|
if(prop in target.module.functions) {
|
2021-05-21 01:04:38 -04:00
|
|
|
return target.context[prop];
|
2021-05-20 00:19:25 -04:00
|
|
|
}
|
2021-05-21 01:04:38 -04:00
|
|
|
throw new Error(DNEText);
|
2021-05-20 00:19:25 -04:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-21 01:04:38 -04:00
|
|
|
restore() {
|
|
|
|
|
return this.context.restore?.();
|
2021-05-20 00:19:25 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get link () {
|
|
|
|
|
return this._link;
|
|
|
|
|
}
|
2021-05-21 23:31:35 -04:00
|
|
|
|
|
|
|
|
toSerializableObject(): SerializedInstance {
|
|
|
|
|
const obj: any = {};
|
|
|
|
|
obj.type = this.module.name.full;
|
|
|
|
|
obj.links = Object.fromEntries(this.module.links.map((link: Link): [string, string] => {
|
|
|
|
|
const name = link.name;
|
|
|
|
|
const linkId = this.context[name]?.__link__;
|
|
|
|
|
return [name, linkId];
|
|
|
|
|
}));
|
|
|
|
|
obj.members = Object.fromEntries(
|
|
|
|
|
this.module.variables
|
|
|
|
|
.filter((member: Variable): boolean => {
|
|
|
|
|
return member.persist;
|
|
|
|
|
})
|
|
|
|
|
.map((member: Variable): [string, any] => {
|
|
|
|
|
const name = member.name;
|
|
|
|
|
const value = this.context[name];
|
|
|
|
|
return [name, value];
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
obj.id = this._id;
|
|
|
|
|
|
|
|
|
|
return obj as SerializedInstance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
toString() {
|
|
|
|
|
return this.module.name.full + '(' + this._id + ')';
|
|
|
|
|
}
|
2021-05-20 00:19:25 -04:00
|
|
|
}
|