This repository has been archived on 2023-11-14. You can view files and clone it, but cannot push or open issues/pull-requests.
vogue/src/Instance.ts

196 lines
5.3 KiB
TypeScript
Raw Normal View History

2021-05-20 00:19:25 -04:00
import debug from 'debug';
import _ from 'lodash';
const log = debug('vogue:instance');
import vm from 'vm';
import Module, { LinkDescription, 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
*/
export type Link = any; // BUT PROXY
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 | null = null;
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;
initialContext: any = {};
get currentContext(): any {
return this.context ?? this.initialContext;
}
sync() {
this.system.saveInstance(this);
}
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...
initialContext.create = this.system.newLink.bind(this.system);
initialContext.process = process;
for(const name in this.system.staticLinks) {
log('creating context with static link: ' + name);
initialContext[name] = this.system.staticLinks[name];
}
2021-05-20 00:19:25 -04:00
// local links!
// optional arrays
// TODO maybe make these property accessors to allow for some automation
for(const link of this.module.links.filter((v: LinkDescription) => v.array && !v.required))
2021-05-20 20:34:50 -04:00
initialContext[link.name] = [];
for(const link of this.module.links.filter((v: LinkDescription) => !v.array && !v.required))
2021-05-20 20:34:50 -04:00
initialContext[link.name] = null;
for(const variable of this.module.variables)
attachHookedProperty(initialContext, variable.name, null, this.sync.bind(this))
for(const name in this.initialContext)
initialContext[name] = this.initialContext[name]
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}
${name} = ${name}.bind(this);`.trim();
// log('injecting function...')
// log(injectedScript)
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
log('context created! ' + Object.keys(context));
// log(context);
2021-05-20 20:34:50 -04:00
return context;
2021-05-20 00:19:25 -04:00
};
setMember(name: string, value: any) {
log('setMember: ' + this.toString() + '.' + name + ' => ' + value);
this.currentContext[name] = value;
}
setLink(name: string, value: Link) {
log('setLink: ' + this.toString() + '.' + name + ' => ' + value.__link__);
this.currentContext[name] = value;
}
constructor(
module: Module,
location: string,
parameters: {[name: string]: any},
system: System,
options?: {
id?: string
}
) {
2021-05-20 00:19:25 -04:00
this.module = module;
this.location = location;
this.system = system;
// this.context = this.createContext();
this._id = options?.id ?? uuid.v4();
2021-05-20 00:19:25 -04:00
this._link = new Proxy(this, {
get(target: Instance, prop: string, receiver) {
log(`getting ${target.module.name.full}.${prop.toString()}: (${target.module.identifiers[prop]}|${typeof target.context?.[prop]})`);
if(target.context === null)
target.restore();
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-20 00:19:25 -04:00
if(prop in target.module.functions) {
return target.context?.[prop];
2021-05-20 00:19:25 -04:00
}
throw new Error(DNEText);
2021-05-20 00:19:25 -04:00
}
});
log('created ' + this);
2021-05-20 00:19:25 -04:00
}
restore() {
if(this.context === null)
this.context = this.createContext();
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: LinkDescription): [string, string] => {
2021-05-21 23:31:35 -04:00
const name = link.name;
const linkId = this.context?.[name]?.__link__;
2021-05-21 23:31:35 -04:00
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];
2021-05-21 23:31:35 -04:00
return [name, value];
})
);
obj.id = this._id;
return obj as SerializedInstance;
}
toString() {
return this.module.name.full + '(' + this._id.substr(0, 4) + ')';
2021-05-21 23:31:35 -04:00
}
}
function attachHookedProperty(target: any, name: string, initialValue: any, changedHook: () => void) {
const propId = uuid.v4();
target[propId] = initialValue;
// TODO if its an object, replace it with a dead simple proxy? for detecting internal changes...
Object.defineProperty(target, name, {
get() {
return target[propId];
},
set(value) {
target[propId] = value;
changedHook();
}
})
2021-05-20 00:19:25 -04:00
}