From 6fb5c30b1dc7888b34f0d805476d23fa581208dc Mon Sep 17 00:00:00 2001 From: Bronwen Date: Fri, 21 May 2021 23:31:35 -0400 Subject: [PATCH] systems save to disk. Closes #3 --- package.json | 4 + src/Instance.ts | 62 +++++++++++--- src/KV.ts | 3 - src/Serializable.ts | 198 -------------------------------------------- src/System.ts | 26 ++++-- src/extensions.ts | 5 ++ src/run.ts | 7 +- test/counter.v | 1 + test/main.v | 2 + yarn.lock | 50 +++++++++++ 10 files changed, 136 insertions(+), 222 deletions(-) delete mode 100644 src/KV.ts delete mode 100644 src/Serializable.ts diff --git a/package.json b/package.json index 3eb38b9..7fae617 100644 --- a/package.json +++ b/package.json @@ -18,12 +18,15 @@ }, "dependencies": { "@types/debug": "^4.1.5", + "@types/fs-extra": "^9.0.11", "@types/lodash": "^4.14.169", "@types/nearley": "^2.11.1", "@types/node": "^15.3.0", "@types/uglify-js": "^3.13.0", + "@types/uuid": "^8.3.0", "cross-env": "^7.0.3", "debug": "^4.3.1", + "fs-extra": "^10.0.0", "lodash": "^4.17.21", "moo": "^0.5.1", "nearley": "^2.20.1", @@ -31,6 +34,7 @@ "supervisor": "^0.12.0", "typescript": "^4.2.4", "uglify-js": "^3.13.5", + "uuid": "^8.3.2", "yarn": "^1.22.10" } } diff --git a/src/Instance.ts b/src/Instance.ts index b9b5cb4..ff84599 100644 --- a/src/Instance.ts +++ b/src/Instance.ts @@ -1,20 +1,27 @@ - -import Serializable from './Serializable.js'; -import minify from './minify.js'; import debug from 'debug'; import _ from 'lodash'; const log = debug('vogue:instance'); import vm from 'vm'; -import Module, { Link } from './Module.js'; +import Module, { Link, Variable } from './Module.js'; import System from './System.js'; -import { KV } from './KV.js'; +import * as uuid from 'uuid'; /** * @typedef {import('./System.js').default} System * @typedef {import('./Module.js').default} Module */ +export type SerializedInstance = { + type: string, + links: { + [name: string]: string + }, + members: { + [name: string]: any // SO LONG AS ITS SERIALIZABLE ._.* + }, + id: string +} -export default class Instance extends Serializable { +export default class Instance { module: Module; links = {} system: System; @@ -23,11 +30,12 @@ export default class Instance extends Serializable { internalFunctions = {}; _link: Instance; location: string; + _id: string; createContext(): vm.Context { if(this.context) return this.context; - const initialContext: KV = {}; + const initialContext: any = {}; // system globals! // TODO turn this into its own vogue module! system.create/instance.create @@ -49,34 +57,38 @@ export default class Instance extends Serializable { for(const name in this.module.imports) initialContext[name] = this.module.imports[name]; + // instance defined functions + initialContext.sync = this.system.saveInstance.bind(this.system, this); + const context = vm.createContext(initialContext); + // 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); `; - vm.runInContext(injectedScript, context, { - - }); + vm.runInContext(injectedScript, context, {}); } return context; }; constructor(module: Module, location: string, parameters: {[name: string]: any}, system: System) { - super(); this.module = module; this.location = location; this.system = system; this.context = this.createContext(); + this._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]})`); 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]; @@ -93,4 +105,32 @@ var ${name} = ${async ? 'async' : ''} function ${name}(${parameters.join(', ')}) get link () { return this._link; } + + 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 + ')'; + } } \ No newline at end of file diff --git a/src/KV.ts b/src/KV.ts deleted file mode 100644 index 795fc4d..0000000 --- a/src/KV.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type KV = { - [key: string]: any -}; \ No newline at end of file diff --git a/src/Serializable.ts b/src/Serializable.ts deleted file mode 100644 index c410650..0000000 --- a/src/Serializable.ts +++ /dev/null @@ -1,198 +0,0 @@ -// import { Ubjson } from '@shelacek/ubjson'; -import { existsSync, readFileSync, writeFileSync } from 'fs'; -import { KV } from './KV'; - -export default class Serializable { - - constructor(...args: any[]) {} - - // things that need to be stored only in cold - // storage are keyed with a special prefix - static CLASS_REFERENCE = '$$CLASS_NAME'; - - // things that need to be stored only at runtime - // are keyed with symbols to not interfere with - // user code. - static PERSIST_LOCATION = Symbol('PERSIST_LOCATION'); - - start() {} - - // toUbj() { - // return Ubjson.encode(this.toSerializableObject()); - // } - - // static fromUbj(buffer) { - // return this.fromSerializableObject(Ubjson.decode(buffer)); - // } - - toJson() { - return JSON.stringify(this.toSerializableObject(), null, 2); - } - - static serializationDependencies(): any[] { - return []; - } - - static fromJson(str: string) { - return this.fromSerializableObject(JSON.parse(str)); - } - - toSerializableObject() { - - const transformValue = (val: any): any => { - if(Array.isArray(val)) { - return transformArray(val); - } else if (val === null || val === undefined) { - return val; - } else if(typeof val === 'object') { - return transformObject(val); - } else { - return val; - } - } - - const transformObject = (obj: KV): KV => { - const clone: KV = {}; - for(const prop of Object.keys(obj)) { - if(prop.startsWith('_')) continue; - - clone[prop] = transformValue(obj[prop]); - } - if(obj instanceof Serializable) { - clone[Serializable.CLASS_REFERENCE] = obj.constructor.name; - } - return clone; - } - - const transformArray = (arr: any[]): any[] => { - const clone = []; - for(const item of arr) { - clone.push(transformValue(item)); - } - return clone; - } - - return transformObject(this); - } - - static fromSerializableObject(obj: KV) { - if(obj[Serializable.CLASS_REFERENCE] !== this.name) return null; - - const transformValue = (val: any): any => { - if(Array.isArray(val)) { - return transformArray(val); - } else if(val === null || val === undefined) { - return val; - } else if(typeof val === 'object') { - if(Serializable.CLASS_REFERENCE in val) { - const classes = this.serializationDependencies(); - const matchingClasses = classes.filter((classObject) => { - classObject.name === val[Serializable.CLASS_REFERENCE] - }); - if(matchingClasses.length === 1) { - return matchingClasses[0].fromSerializableObject(val); - } else { - return transformObject(val); - } - } - return transformObject(val); - } else { - return val; - } - } - - const transformObject = (obj: KV): KV => { - const clone: KV = {}; - for(const prop of Object.keys(obj)) { - if(prop.startsWith('_')) continue; - - clone[prop] = transformValue(obj[prop]); - } - return clone; - } - - const transformArray = (arr: any[]): any[] => { - const clone = []; - for(const item of arr) { - clone.push(transformValue(item)); - } - return clone; - } - - const clone = transformObject(obj); - if(Serializable.CLASS_REFERENCE in obj) - clone.__proto__ = this.prototype; - - clone.restore(); - - return clone; - } - - serialize({ - encoding = 'json' - } = {}) { - - switch(encoding) { - case 'json': return this.toJson(); - case 'ubjson': - // case 'ubj': return this.toUbj(); - default: { - throw new TypeError('Unknown encoding: ' + encoding); - } - } - - } - - static deserialize(obj: any, { - encoding = 'json' - } = {}) { - - switch(encoding) { - case 'json': return this.fromJson(obj); - case 'ubjson': - // case 'ubj': return this.fromUbj(obj); - default: { - throw new TypeError('Unknown encoding: ' + encoding); - } - } - } - - async restore() {} - - static createFromDisk(filename: string, ...args: any[]) { - if(existsSync(filename)) { - const instance = this.deserialize(readFileSync(createFilepath(filename))); - // TS is plain and simply wrong... symbols can be used to index object... - // @ts-ignore - instance[Serializable.PERSIST_LOCATION] = createFilepath(filename); - instance?.restore(); - return instance; - } else { - const instance = new this(...args); - // again... TS is wrong... - // @ts-ignore - instance[Serializable.PERSIST_LOCATION] = createFilepath(filename); - instance?.updateDisk(); - return instance; - } - } - - updateDisk(filepath?: string) { - // if it hasnt yet been written to disk... - // this can happen if the contrustor - // was called outside of createFromDisk - if(filepath) { - // see above... TS7053 is just _wrong_. incorrect. thats not how JS works. - // @ts-ignore - this[Serializable.PERSIST_LOCATION] = createFilepath(filepath); - } - const data = this.serialize(); - // this is getting annoying... - // @ts-ignore - writeFileSync(this[Serializable.PERSIST_LOCATION], data); - } -} - -function createFilepath(path: string) { - return `data/${path}`; -} \ No newline at end of file diff --git a/src/System.ts b/src/System.ts index 0d51240..79e85d6 100644 --- a/src/System.ts +++ b/src/System.ts @@ -1,8 +1,10 @@ import Instance from './Instance.js'; -import Serializable from './Serializable.js'; import _ from 'lodash'; import Module from './Module.js'; import debug from 'debug'; +import { writeFileSync } from 'fs'; +import { resolve } from 'path'; +import { ensureDirSync } from 'fs-extra'; const log = debug('vogue:system') const {get, set} = _; @@ -13,7 +15,7 @@ type ModuleNamespaceMap = { type ModuleName = string; -class System extends Serializable { +class System { instances: Instance[] = []; modules: Module[]; namespace: ModuleNamespaceMap = {}; @@ -23,12 +25,11 @@ class System extends Serializable { rootDir: string; constructor(modules: Module[], rootDir: string) { - super(); + this.rootDir = rootDir; this.modules = modules; this.createNamespace(); const bootModules = this.deriveBootModules(); this.createStaticInstances(); - this.rootDir = rootDir; log('instantiating boot modules...'); for(const name of bootModules) { @@ -79,6 +80,19 @@ class System extends Serializable { }, {}); } + 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); + const json = JSON.stringify(instance.toSerializableObject(), null, 2) + log(json); + writeFileSync(filepath, json); + log('synced ' + instance); + } + getModule(name: ModuleName): Module { const module = get(this.namespace, name); if(module instanceof Module) return module; @@ -86,7 +100,9 @@ class System extends Serializable { } createInstance(name: ModuleName, args = {}) { - return new Instance(this.getModule(name), '', args, this); + const instance = new Instance(this.getModule(name), '', args, this); + this.saveInstance(instance); + return instance; } newInstance(name: ModuleName, args = {}) { diff --git a/src/extensions.ts b/src/extensions.ts index e0bb37d..06b0441 100644 --- a/src/extensions.ts +++ b/src/extensions.ts @@ -4,3 +4,8 @@ Object.defineProperty(Array.prototype, 'empty', { return this.length === 0; } }); + + +process.on('unhandledRejection', (reason: Error, p) => { + console.log(reason.stack ?? reason.name + '\n\nStack trace unavailable...'); +}); \ No newline at end of file diff --git a/src/run.ts b/src/run.ts index ed37cc7..5de9ebb 100644 --- a/src/run.ts +++ b/src/run.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env node +#!/usr/bin/env node --enable-source-maps --unhandled-rejections=strict import debug from 'debug'; const log = debug('vogue:cli'); const systemLocation = resolve(process.argv[2]); @@ -11,14 +11,11 @@ import Module from './Module.js'; import System from './System.js'; import './extensions.js'; import { fileURLToPath } from 'url'; -// globals inside grammar context -import minify from './minify'; const { get, set } = _; const standardLibrary = resolve(fileURLToPath(dirname(import.meta.url)), '..', 'lib', 'vogue'); (async () => { - // TODO simplify this line gaddam const ignoreDeps = (path: string) => parse(path).name !== 'node_modules'; const files = [ @@ -35,7 +32,7 @@ const standardLibrary = resolve(fileURLToPath(dirname(import.meta.url)), '..', ' const modules = await Promise.all(fullpaths.map(loc => Module.create(loc, systemLocation))); const sys = new System(modules, systemLocation); -})() +})(); function walkdirSync(root: string, filter: ((path: string) => boolean) = () => true): string[] { log('reading', root, '...'); diff --git a/test/counter.v b/test/counter.v index 3e04bf3..f241772 100644 --- a/test/counter.v +++ b/test/counter.v @@ -2,6 +2,7 @@ member count; increment() { count ++; + sync(); } getCount() { diff --git a/test/main.v b/test/main.v index a901312..cd334cf 100644 --- a/test/main.v +++ b/test/main.v @@ -19,4 +19,6 @@ async restore { // window.setScene() // await counter.render(); + + sync(); } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 3bfd784..4231fa1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6,6 +6,13 @@ version "4.1.5" resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz" +"@types/fs-extra@^9.0.11": + version "9.0.11" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.11.tgz#8cc99e103499eab9f347dbc6ca4e99fb8d2c2b87" + integrity sha512-mZsifGG4QeQ7hlkhO56u7zt/ycBgGxSVsFI/6lGTU34VtwkiqrrSDgw0+ygs8kFGWcXnFQWMrzF2h7TtDFNixA== + dependencies: + "@types/node" "*" + "@types/lodash@^4.14.169": version "4.14.169" resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.169.tgz" @@ -14,6 +21,11 @@ version "2.11.1" resolved "https://registry.npmjs.org/@types/nearley/-/nearley-2.11.1.tgz" +"@types/node@*": + version "15.6.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-15.6.0.tgz#f0ddca5a61e52627c9dcb771a6039d44694597bc" + integrity sha512-gCYSfQpy+LYhOFTKAeE8BkyGqaxmlFxe+n4DKM6DR0wzw/HISUE/hAmkC/KT8Sw5PCJblqg062b3z9gucv3k0A== + "@types/node@^15.3.0": version "15.3.0" resolved "https://registry.npmjs.org/@types/node/-/node-15.3.0.tgz" @@ -24,6 +36,11 @@ dependencies: source-map "^0.6.1" +"@types/uuid@^8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f" + integrity sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ== + async@0.2.10: version "0.2.10" resolved "https://registry.npmjs.org/async/-/async-0.2.10.tgz" @@ -62,6 +79,20 @@ discontinuous-range@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz" +fs-extra@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1" + integrity sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.6" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + immediate@~3.0.5: version "3.0.6" resolved "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz" @@ -70,6 +101,15 @@ isexe@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + lie@3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz" @@ -172,6 +212,16 @@ underscore@~1.4.4: version "1.4.4" resolved "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz" +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + which@^2.0.1: version "2.0.2" resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz"