lots of potential issues, but definitely Closes #21

pull/22/head
Valerie 2021-05-22 13:18:01 -04:00
parent 7a12c1adb8
commit 24467f479a
5 changed files with 165 additions and 45 deletions

View File

@ -10,7 +10,7 @@
"scripts": {
"test": "node --enable-source-maps --unhandled-rejections=strict out/run.js test",
"debug": "cross-env DEBUG=vogue:* yarn test",
"debug:watch": "cross-env DEBUG=vogue:* supervisor -w out,test,lib -n exit --exec yarn -- test",
"debug:watch": "cross-env DEBUG=vogue:* supervisor -w out,test/**/*.v,lib -n exit --exec yarn -- test",
"postinstall": "yarn compile && cd test && yarn",
"postcompile:watch": "echo DONE",
"compile": "tsc",

View File

@ -2,7 +2,7 @@ import debug from 'debug';
import _ from 'lodash';
const log = debug('vogue:instance');
import vm from 'vm';
import Module, { Link, Variable } from './Module.js';
import Module, { LinkDescription, Variable } from './Module.js';
import System from './System.js';
import * as uuid from 'uuid';
/**
@ -10,6 +10,8 @@ import * as uuid from 'uuid';
* @typedef {import('./Module.js').default} Module
*/
export type Link = any; // BUT PROXY
export type SerializedInstance = {
type: string,
links: {
@ -25,38 +27,49 @@ export default class Instance {
module: Module;
links = {}
system: System;
context: vm.Context;
context: vm.Context | null = null;
locals = [];
internalFunctions = {};
_link: Instance;
location: string;
_id: string;
initialContext: any = {};
get currentContext(): any {
return this.context ?? this.initialContext;
}
sync() {
this.system.saveInstance(this);
}
createContext(): vm.Context {
if(this.context) return this.context;
const initialContext: any = {};
// system globals!
// TODO turn this into its own vogue module! system.create/instance.create
// TODO request context from system...
initialContext.create = this.system.newInstance.bind(this.system);
initialContext.create = this.system.newLink.bind(this.system);
initialContext.process = process;
for(const name in this.system.staticInstances)
initialContext[name] = this.system.staticInstances[name];
for(const name in this.system.staticLinks) {
log('creating context with static link: ' + name);
initialContext[name] = this.system.staticLinks[name];
}
// local links!
// optional arrays
// TODO maybe make these property accessors to allow for some automation
for(const link of this.module.links.filter((v: Link) => v.array && !v.required))
for(const link of this.module.links.filter((v: LinkDescription) => v.array && !v.required))
initialContext[link.name] = [];
for(const link of this.module.links.filter((v: Link) => !v.array && !v.required))
for(const link of this.module.links.filter((v: LinkDescription) => !v.array && !v.required))
initialContext[link.name] = null;
for(const variable of this.module.variables) {
attachHookedProperty(initialContext, variable.name, null, () => {
this.system.saveInstance(this);
})
}
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];
@ -68,41 +81,70 @@ export default class Instance {
// user defined functions
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);
`;
const injectedScript = `
var ${name} = ${async ? 'async ' : ''}function ${name}(${parameters.join(', ')}) ${code}
${name} = ${name}.bind(this);`.trim();
// log('injecting function...')
// log(injectedScript)
vm.runInContext(injectedScript, context, {});
}
log('context created! ' + Object.keys(context));
// log(context);
return context;
};
constructor(module: Module, location: string, parameters: {[name: string]: any}, system: System) {
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
}
) {
this.module = module;
this.location = location;
this.system = system;
this.context = this.createContext();
this._id = uuid.v4();
// this.context = this.createContext();
this._id = options?.id ?? uuid.v4();
this._link = new Proxy(this, {
get(target: Instance, prop: string, receiver) {
log(`getting ${target.module.name.full}.${prop}: (${target.module.identifiers[prop]}|${typeof target.context[prop]})`);
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);
if(prop === '__link__') return target._id;
if(prop in target.module.functions) {
return target.context[prop];
return target.context?.[prop];
}
throw new Error(DNEText);
}
});
log('created ' + this);
}
restore() {
return this.context.restore?.();
if(this.context === null)
this.context = this.createContext();
return this.context?.restore?.();
}
get link () {
@ -112,9 +154,9 @@ ${name} = ${name}.bind(this);
toSerializableObject(): SerializedInstance {
const obj: any = {};
obj.type = this.module.name.full;
obj.links = Object.fromEntries(this.module.links.map((link: Link): [string, string] => {
obj.links = Object.fromEntries(this.module.links.map((link: LinkDescription): [string, string] => {
const name = link.name;
const linkId = this.context[name]?.__link__;
const linkId = this.context?.[name]?.__link__;
return [name, linkId];
}));
obj.members = Object.fromEntries(
@ -124,7 +166,7 @@ ${name} = ${name}.bind(this);
})
.map((member: Variable): [string, any] => {
const name = member.name;
const value = this.context[name];
const value = this.context?.[name];
return [name, value];
})
);
@ -134,7 +176,7 @@ ${name} = ${name}.bind(this);
}
toString() {
return this.module.name.full + '(' + this._id + ')';
return this.module.name.full + '(' + this._id.substr(0, 4) + ')';
}
}

View File

@ -14,7 +14,7 @@ import { createRequire } from 'module';
import { pathToFileURL } from 'url';
const log = debug('vogue:module');
export type Link = {
export type LinkDescription = {
name: string,
array: boolean,
required: boolean
@ -26,7 +26,7 @@ export type Variable = {
}
export default class Module {
links: Link[] = [];
links: LinkDescription[] = [];
globals = [];
functions: {
[name: string]: {

View File

@ -1,8 +1,8 @@
import Instance from './Instance.js';
import Instance, { Link, SerializedInstance } from './Instance.js';
import _ from 'lodash';
import Module from './Module.js';
import debug from 'debug';
import { writeFileSync } from 'fs';
import { lstatSync, readdirSync, readFileSync, writeFileSync } from 'fs';
import { resolve } from 'path';
import { ensureDirSync } from 'fs-extra';
const log = debug('vogue:system')
@ -16,11 +16,11 @@ type ModuleNamespaceMap = {
type ModuleName = string;
class System {
instances: Instance[] = [];
instances: Map<string, Instance> = new Map();
modules: Module[];
namespace: ModuleNamespaceMap = {};
staticInstances: {
[key: string]: Instance
staticLinks: {
[key: string]: Link
} = {};
rootDir: string;
@ -28,16 +28,87 @@ class System {
this.rootDir = rootDir;
this.modules = modules;
this.createNamespace();
const vault = readdirSync(resolve(this.rootDir, '.system')).map(v => resolve(this.rootDir, '.system', v));
const serializedInstances: SerializedInstance[] = vault.map((v) => JSON.parse(readFileSync(v).toString()));
log('injecting serialized instances...');
for(const serializedInstance of serializedInstances)
this.injectSerializedInstance(serializedInstance);
log('linking serialized instances...');
for(const serializedInstance of serializedInstances)
this.linkSerializedInstance(serializedInstance);
log('restoring static instances...');
for(const [,instance] of this.instances) {
if(!!instance.module.static) {
instance.restore();
}
}
log('restoring boot instances...');
for(const [,instance] of this.instances) {
if(!!instance.module.singleton) {
instance.restore();
}
}
// this.inject(serializedInstance);
if (vault.length !== 0) {
return this;
}
// TODO future workflow notes
// pull jsons into boots
// filter json boots
// create static / singletons into boots
// boot boots!
const bootModules = this.deriveBootModules();
this.createStaticInstances();
log('instantiating boot modules...');
for(const name of bootModules) {
log(' ' + name);
this.newInstance(name);
this.newLink(name);
}
}
linkSerializedInstance(serializedInstance: SerializedInstance): void {
const instance = this.getInstanceById(serializedInstance.id);
for(const name in serializedInstance.links) {
const linkId = serializedInstance.links[name]
const linkedInstance = this.getInstanceById(linkId);
const linkedInstanceLink = linkedInstance.link;
instance.setLink(name, linkedInstanceLink);
}
}
injectSerializedInstance(serializedInstance: SerializedInstance): void {
const instance = new Instance(this.getModule(serializedInstance.type), this.rootDir, {}, this, {
id: serializedInstance.id
});
this.instances.set(instance._id, instance);
for(const name in serializedInstance.members) {
instance.setMember(name, serializedInstance.members[name]);
}
if(instance.module.static) {
log('injected static instance ' + instance.module.static + ': ' + instance.module.name.full);
this.staticLinks[instance.module.static] = instance.link;
}
}
getInstanceById(id: string): Instance {
if(!this.instances.has(id))
throw new Error(`${id} is not a valid instance link id`);
return this.instances.get(id) as Instance;
}
createStaticInstances() {
log('deriving static modules...');
const staticModules = this.modules.filter((module) => {
@ -50,8 +121,8 @@ class System {
log('instantiating static modules...');
for(const module of staticModules) {
log(' ' + module.static + ': ' + module.name.full);
this.staticInstances[module.static] =
this.newInstance(module.name.full, {});
this.staticLinks[module.static] =
this.newLink(module.name.full, {});
}
}
@ -81,16 +152,13 @@ class System {
}
saveInstance(instance: Instance): void {
log('saving ' + instance)
const path = resolve(this.rootDir, '.system');
ensureDirSync(path);
const file = instance._id + '.json';
const filepath = resolve(path, file);
log(filepath);
log('saving ' + instance + '...');
const json = JSON.stringify(instance.toSerializableObject(), null, 2)
log(json);
writeFileSync(filepath, json);
log('synced ' + instance);
}
getModule(name: ModuleName): Module {
@ -100,12 +168,12 @@ class System {
}
createInstance(name: ModuleName, args = {}) {
const instance = new Instance(this.getModule(name), '', args, this);
const instance = new Instance(this.getModule(name), this.rootDir, args, this);
this.saveInstance(instance);
return instance;
}
newInstance(name: ModuleName, args = {}) {
newLink(name: ModuleName, args = {}) {
const instance = this.createInstance(name, args);
const link = instance.link;
instance.restore();
@ -113,4 +181,14 @@ class System {
}
}
export default System;
export default System;
// class SerializedInstanceInjector {
// system: System;
// serializedInstances: SerializedInstance[];
// constructor(serializedInstances: SerializedInstance[], system: System) {
// this.serializedInstances = serializedInstances;
// this.system = system;
// }
// }

Binary file not shown.