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": { "scripts": {
"test": "node --enable-source-maps --unhandled-rejections=strict out/run.js test", "test": "node --enable-source-maps --unhandled-rejections=strict out/run.js test",
"debug": "cross-env DEBUG=vogue:* yarn 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", "postinstall": "yarn compile && cd test && yarn",
"postcompile:watch": "echo DONE", "postcompile:watch": "echo DONE",
"compile": "tsc", "compile": "tsc",

View File

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

View File

@ -1,8 +1,8 @@
import Instance from './Instance.js'; import Instance, { Link, SerializedInstance } from './Instance.js';
import _ from 'lodash'; import _ from 'lodash';
import Module from './Module.js'; import Module from './Module.js';
import debug from 'debug'; import debug from 'debug';
import { writeFileSync } from 'fs'; import { lstatSync, readdirSync, readFileSync, writeFileSync } from 'fs';
import { resolve } from 'path'; import { resolve } from 'path';
import { ensureDirSync } from 'fs-extra'; import { ensureDirSync } from 'fs-extra';
const log = debug('vogue:system') const log = debug('vogue:system')
@ -16,11 +16,11 @@ type ModuleNamespaceMap = {
type ModuleName = string; type ModuleName = string;
class System { class System {
instances: Instance[] = []; instances: Map<string, Instance> = new Map();
modules: Module[]; modules: Module[];
namespace: ModuleNamespaceMap = {}; namespace: ModuleNamespaceMap = {};
staticInstances: { staticLinks: {
[key: string]: Instance [key: string]: Link
} = {}; } = {};
rootDir: string; rootDir: string;
@ -28,16 +28,87 @@ class System {
this.rootDir = rootDir; this.rootDir = rootDir;
this.modules = modules; this.modules = modules;
this.createNamespace(); 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(); const bootModules = this.deriveBootModules();
this.createStaticInstances(); this.createStaticInstances();
log('instantiating boot modules...'); log('instantiating boot modules...');
for(const name of bootModules) { for(const name of bootModules) {
log(' ' + name); 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() { createStaticInstances() {
log('deriving static modules...'); log('deriving static modules...');
const staticModules = this.modules.filter((module) => { const staticModules = this.modules.filter((module) => {
@ -50,8 +121,8 @@ class System {
log('instantiating static modules...'); log('instantiating static modules...');
for(const module of staticModules) { for(const module of staticModules) {
log(' ' + module.static + ': ' + module.name.full); log(' ' + module.static + ': ' + module.name.full);
this.staticInstances[module.static] = this.staticLinks[module.static] =
this.newInstance(module.name.full, {}); this.newLink(module.name.full, {});
} }
} }
@ -81,16 +152,13 @@ class System {
} }
saveInstance(instance: Instance): void { saveInstance(instance: Instance): void {
log('saving ' + instance)
const path = resolve(this.rootDir, '.system'); const path = resolve(this.rootDir, '.system');
ensureDirSync(path); ensureDirSync(path);
const file = instance._id + '.json'; const file = instance._id + '.json';
const filepath = resolve(path, file); const filepath = resolve(path, file);
log(filepath); log('saving ' + instance + '...');
const json = JSON.stringify(instance.toSerializableObject(), null, 2) const json = JSON.stringify(instance.toSerializableObject(), null, 2)
log(json);
writeFileSync(filepath, json); writeFileSync(filepath, json);
log('synced ' + instance);
} }
getModule(name: ModuleName): Module { getModule(name: ModuleName): Module {
@ -100,12 +168,12 @@ class System {
} }
createInstance(name: ModuleName, args = {}) { 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); this.saveInstance(instance);
return instance; return instance;
} }
newInstance(name: ModuleName, args = {}) { newLink(name: ModuleName, args = {}) {
const instance = this.createInstance(name, args); const instance = this.createInstance(name, args);
const link = instance.link; const link = instance.link;
instance.restore(); instance.restore();
@ -114,3 +182,13 @@ 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.