extract disk related actions to frgid & add tests
parent
a8030aa14e
commit
bc612290dc
|
|
@ -8,6 +8,7 @@
|
||||||
"repository": "https://github.com/marcus13345/frigid.git",
|
"repository": "https://github.com/marcus13345/frigid.git",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^15.12.2",
|
"@types/node": "^15.12.2",
|
||||||
|
"chai": "^4.3.4",
|
||||||
"typescript": "^4.3.2",
|
"typescript": "^4.3.2",
|
||||||
"yarn": "^1.22.10"
|
"yarn": "^1.22.10"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
||||||
|
import Serializable from "./Serializable.js";
|
||||||
|
|
||||||
|
const PERSIST_LOCATION = Symbol('PERSIST_LOCATION');
|
||||||
|
export const RESTORE = Symbol('RESTORE');
|
||||||
|
|
||||||
|
export default class Frigid extends Serializable {
|
||||||
|
static create(filename: string, ...args: any[]) {
|
||||||
|
if(existsSync(filename)) {
|
||||||
|
const instance = this.deserialize(readFileSync(filename));
|
||||||
|
// TS is plain and simply wrong... symbols can be used to index object...
|
||||||
|
// @ts-ignore
|
||||||
|
instance[PERSIST_LOCATION] = filename;
|
||||||
|
instance[RESTORE]();
|
||||||
|
return instance;
|
||||||
|
} else {
|
||||||
|
const instance = new this(...args);
|
||||||
|
// again... TS is wrong...
|
||||||
|
// @ts-ignore
|
||||||
|
instance[PERSIST_LOCATION] = filename;
|
||||||
|
instance[RESTORE]();
|
||||||
|
instance.sync();
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sync() {
|
||||||
|
const data = this.serialize();
|
||||||
|
// this is getting annoying...
|
||||||
|
// @ts-ignore
|
||||||
|
writeFileSync(this[PERSIST_LOCATION], data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
// import { Ubjson } from '@shelacek/ubjson';
|
// import { Ubjson } from '@shelacek/ubjson';
|
||||||
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
||||||
|
import { reverseLookup } from './reverseLookup.js';
|
||||||
|
|
||||||
export default class Serializable {
|
export default class Serializable {
|
||||||
|
|
||||||
|
|
@ -14,26 +15,21 @@ export default class Serializable {
|
||||||
static INSTANCE_DECLARATION = '$$INSTANCE_ID';
|
static INSTANCE_DECLARATION = '$$INSTANCE_ID';
|
||||||
static INSTANCE_REFERENCE = '$$INSTANCE_REF';
|
static INSTANCE_REFERENCE = '$$INSTANCE_REF';
|
||||||
|
|
||||||
// things that need to be stored only at runtime
|
static serializationDependencies(): any[] {
|
||||||
// are keyed with symbols to not interfere with
|
return [];
|
||||||
// user code.
|
}
|
||||||
static PERSIST_LOCATION = Symbol('PERSIST_LOCATION');
|
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
return JSON.stringify(this.toSerializableObject(), null, 2);
|
return JSON.stringify(this.toSerializableObject(), null, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
static serializationDependencies(): any[] {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromJson(str: string) {
|
static fromJson(str: string) {
|
||||||
return this.fromSerializableObject(JSON.parse(str));
|
return this.fromSerializableObject(JSON.parse(str));
|
||||||
}
|
}
|
||||||
|
|
||||||
// thisdoesnt operate recursively, it doesnt need to, because dependency
|
// this doesnt operate recursively, it doesnt need to, because
|
||||||
// resoltion isnt required. we simply declare the dependencies.
|
// dependency resoltion isnt required. we simply declare the
|
||||||
// so we never touch static serializationDependencies!
|
// dependencies. so we never touch serializationDependencies!
|
||||||
toSerializableObject() {
|
toSerializableObject() {
|
||||||
const instances: Map<number, object> = new Map();
|
const instances: Map<number, object> = new Map();
|
||||||
|
|
||||||
|
|
@ -197,57 +193,4 @@ export default class Serializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async restore() {}
|
|
||||||
|
|
||||||
static createFromDisk(filename: string, ...args: any[]) {
|
|
||||||
const filepath = createFilepath(filename);
|
|
||||||
if(existsSync(filepath)) {
|
|
||||||
const instance = this.deserialize(readFileSync(filepath));
|
|
||||||
// TS is plain and simply wrong... symbols can be used to index object...
|
|
||||||
// @ts-ignore
|
|
||||||
instance[Serializable.PERSIST_LOCATION] = filepath;
|
|
||||||
instance?.restore();
|
|
||||||
return instance;
|
|
||||||
} else {
|
|
||||||
const instance = new this(...args);
|
|
||||||
// again... TS is wrong...
|
|
||||||
// @ts-ignore
|
|
||||||
instance[Serializable.PERSIST_LOCATION] = filepath;
|
|
||||||
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}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function reverseLookup<K, V>(map: Map<K, V>, value: V): K {
|
|
||||||
// console.log('searching for', value, 'in', map);
|
|
||||||
for(const [k, v] of map) {
|
|
||||||
if(v === value) {
|
|
||||||
// console.log('found in key', k);
|
|
||||||
return k;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// console.log(value, 'not found')
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
export {default as Serializable} from './Serializable.js';
|
export {default as Serializable} from './Serializable.js';
|
||||||
|
export {default as Frigid, RESTORE} from './Frigid.js'
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
export function reverseLookup<K, V>(map: Map<K, V>, value: V): K {
|
||||||
|
// console.log('searching for', value, 'in', map);
|
||||||
|
for (const [k, v] of map) {
|
||||||
|
if (v === value) {
|
||||||
|
// console.log('found in key', k);
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// console.log(value, 'not found')
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"$$INSTANCE_ID": 0,
|
||||||
|
"foo": "bar",
|
||||||
|
"$$CLASS_NAME": "Test"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
import {Frigid, RESTORE} from '../out/index.js';
|
||||||
|
import { existsSync, readFileSync, unlinkSync } from 'fs';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
|
||||||
|
const trackingData = {
|
||||||
|
constructorCalls: 0,
|
||||||
|
restoreCalls: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
class Test extends Frigid {
|
||||||
|
foo = 'bar';
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
trackingData.constructorCalls ++;
|
||||||
|
}
|
||||||
|
|
||||||
|
[RESTORE]() {
|
||||||
|
trackingData.restoreCalls ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const filepath = 'test.state.json';
|
||||||
|
|
||||||
|
if(existsSync(filepath)) {
|
||||||
|
unlinkSync(filepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
const test = Test.create(filepath)
|
||||||
|
|
||||||
|
expect(test.sync.bind(test)).to.not.throw();
|
||||||
|
|
||||||
|
expect(existsSync(filepath)).to.be.true;
|
||||||
|
expect(readFileSync(filepath).toString()).to.not.be.empty;
|
||||||
|
|
||||||
|
expect(trackingData.constructorCalls).to.equal(1);
|
||||||
|
expect(trackingData.restoreCalls).to.equal(1);
|
||||||
|
|
||||||
|
const retest = Test.create(filepath);
|
||||||
|
|
||||||
|
expect(trackingData.constructorCalls).to.equal(1);
|
||||||
|
expect(trackingData.restoreCalls).to.equal(2);
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { Serializable } from '../out/index.js'
|
||||||
|
|
||||||
|
class Sub extends Serializable {
|
||||||
|
otherData = sharedObject;
|
||||||
|
root;
|
||||||
|
|
||||||
|
static serializationDependencies() {
|
||||||
|
return [Root];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Root extends Serializable{
|
||||||
|
stuff = sharedObject;
|
||||||
|
child;
|
||||||
|
|
||||||
|
static serializationDependencies() {
|
||||||
|
return [Sub];
|
||||||
|
}
|
||||||
|
|
||||||
|
test() {
|
||||||
|
return {
|
||||||
|
circular: this.child.root === this,
|
||||||
|
shared: this.stuff === this.child.otherData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sharedObject = {shared: 'data'}
|
||||||
|
|
||||||
|
const root = new Root();
|
||||||
|
const sub = new Sub();
|
||||||
|
|
||||||
|
root.child = sub;
|
||||||
|
sub.root = root;
|
||||||
|
|
||||||
|
console.clear();
|
||||||
|
console.log('#'.repeat(process.stdout.columns));
|
||||||
|
|
||||||
|
console.log(root);
|
||||||
|
const json = root.toJson();
|
||||||
|
console.log(json);
|
||||||
|
const obj = Root.fromJson(json);
|
||||||
|
console.log(obj);
|
||||||
|
const tests = obj.test();
|
||||||
|
console.log(tests);
|
||||||
|
|
||||||
|
const passing = Object.values(tests).reduce((v, acc) => v && acc, true);
|
||||||
|
if(!passing) {
|
||||||
|
console.log('Some tests failed!');
|
||||||
|
process.exit(1);
|
||||||
|
};
|
||||||
|
|
@ -1,57 +1,4 @@
|
||||||
import { Serializable } from '../out/index.js'
|
import './Serializable.js';
|
||||||
|
import './Frigid.js';
|
||||||
|
|
||||||
class Sub extends Serializable {
|
console.log('All tests passed!');
|
||||||
otherData = sharedObject;
|
|
||||||
root;
|
|
||||||
|
|
||||||
static serializationDependencies() {
|
|
||||||
return [Root];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Root extends Serializable{
|
|
||||||
stuff = sharedObject;
|
|
||||||
child;
|
|
||||||
|
|
||||||
static serializationDependencies() {
|
|
||||||
return [Sub];
|
|
||||||
}
|
|
||||||
|
|
||||||
test() {
|
|
||||||
return {
|
|
||||||
circular: this.child.root === this,
|
|
||||||
shared: this.stuff === this.child.otherData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sharedObject = {shared: 'data'}
|
|
||||||
|
|
||||||
const root = new Root();
|
|
||||||
const sub = new Sub();
|
|
||||||
|
|
||||||
root.child = sub;
|
|
||||||
sub.root = root;
|
|
||||||
|
|
||||||
console.clear();
|
|
||||||
console.log('#'.repeat(process.stdout.columns));
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
console.log(root);
|
|
||||||
const json = root.toJson();
|
|
||||||
console.log(json);
|
|
||||||
const obj = Root.fromJson(json);
|
|
||||||
console.log(obj);
|
|
||||||
const tests = obj.test();
|
|
||||||
console.log(tests);
|
|
||||||
|
|
||||||
const passing = Object.values(tests).reduce((v, acc) => v && acc, true);
|
|
||||||
if(!passing) {
|
|
||||||
console.log('Some tests failed!');
|
|
||||||
process.exit(1);
|
|
||||||
} else {
|
|
||||||
console.log('All tests Passed!');
|
|
||||||
}
|
|
||||||
37
yarn.lock
37
yarn.lock
|
|
@ -6,6 +6,43 @@
|
||||||
version "15.12.2"
|
version "15.12.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-15.12.2.tgz#1f2b42c4be7156ff4a6f914b2fb03d05fa84e38d"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-15.12.2.tgz#1f2b42c4be7156ff4a6f914b2fb03d05fa84e38d"
|
||||||
|
|
||||||
|
assertion-error@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
|
||||||
|
|
||||||
|
chai@^4.3.4:
|
||||||
|
version "4.3.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.4.tgz#b55e655b31e1eac7099be4c08c21964fce2e6c49"
|
||||||
|
dependencies:
|
||||||
|
assertion-error "^1.1.0"
|
||||||
|
check-error "^1.0.2"
|
||||||
|
deep-eql "^3.0.1"
|
||||||
|
get-func-name "^2.0.0"
|
||||||
|
pathval "^1.1.1"
|
||||||
|
type-detect "^4.0.5"
|
||||||
|
|
||||||
|
check-error@^1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
|
||||||
|
|
||||||
|
deep-eql@^3.0.1:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df"
|
||||||
|
dependencies:
|
||||||
|
type-detect "^4.0.0"
|
||||||
|
|
||||||
|
get-func-name@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
|
||||||
|
|
||||||
|
pathval@^1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d"
|
||||||
|
|
||||||
|
type-detect@^4.0.0, type-detect@^4.0.5:
|
||||||
|
version "4.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
|
||||||
|
|
||||||
typescript@^4.3.2:
|
typescript@^4.3.2:
|
||||||
version "4.3.2"
|
version "4.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805"
|
||||||
|
|
|
||||||
Reference in New Issue