commit 2eb7268903ade72892e15bcc946dc057143fdd51 Author: Valerie Date: Wed Jun 9 21:25:37 2021 -0400 frigid! diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..884a4d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +out diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..c44b806 --- /dev/null +++ b/.npmignore @@ -0,0 +1,5 @@ +src +.gitignore +node_modules +tsconfig.json +yarn.lock \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..7b4e530 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "frigid", + "version": "1.0.0", + "main": "out/index.js", + "license": "MIT", + "type": "module", + "repository": "", + "devDependencies": { + "typescript": "^4.3.2", + "@types/node": "^15.12.2" + }, + "scripts": { + "build": "tsc" + } +} diff --git a/src/Serializable.ts b/src/Serializable.ts new file mode 100644 index 0000000..a60bcc9 --- /dev/null +++ b/src/Serializable.ts @@ -0,0 +1,197 @@ +// import { Ubjson } from '@shelacek/ubjson'; +import { existsSync, readFileSync, writeFileSync } from 'fs'; + +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: any): any => { + const clone: any = {}; + 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: any) { + 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: any): any => { + const clone: any = {}; + 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/index.ts b/src/index.ts new file mode 100644 index 0000000..655f98a --- /dev/null +++ b/src/index.ts @@ -0,0 +1 @@ +export {default as Serializable} from './Serializable'; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a8f8e54 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ES2020", + "moduleResolution": "Node", + "outDir": "out" + }, + "include": [ + "src/**/*.ts" + ] +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..c278594 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,11 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/node@^15.12.2": + version "15.12.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-15.12.2.tgz#1f2b42c4be7156ff4a6f914b2fb03d05fa84e38d" + +typescript@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805"