summaryrefslogtreecommitdiff
path: root/core/src
diff options
context:
space:
mode:
authorNick Van Doorn <vandoorn.nick@gmail.com>2019-02-09 01:31:39 -0800
committerNick Van Doorn <vandoorn.nick@gmail.com>2019-02-09 01:31:39 -0800
commitcc3c82a0b2816a4e1601d237a130cf7bae953335 (patch)
treedf65babf3a3870de2bc706b43efd815721a83044 /core/src
parent7aa0d4141516cf03e332d70e374f46f35ea09f76 (diff)
Blah
Diffstat (limited to 'core/src')
-rw-r--r--core/src/change-handlers.model.ts6
-rw-r--r--core/src/context.model.ts6
-rw-r--r--core/src/database.model.ts14
-rw-r--r--core/src/database.test.ts197
-rw-r--r--core/src/database.ts114
-rw-r--r--core/src/index.ts6
6 files changed, 172 insertions, 171 deletions
diff --git a/core/src/change-handlers.model.ts b/core/src/change-handlers.model.ts
index 0bddecf..0ef499a 100644
--- a/core/src/change-handlers.model.ts
+++ b/core/src/change-handlers.model.ts
@@ -1,6 +1,6 @@
export interface ChangeHandlers {
[handlerKey: string]: {
- path: string;
- callback: (e: any) => Promise<any>;
- };
+ path: string
+ callback: (e: any) => Promise<any>
+ }
}
diff --git a/core/src/context.model.ts b/core/src/context.model.ts
index 193f152..3551fc8 100644
--- a/core/src/context.model.ts
+++ b/core/src/context.model.ts
@@ -6,13 +6,13 @@ export interface Context {
* but it may be handy to pipe it elsewhere
* later
*/
- logger: (e: any) => void;
+ logger: (e: any) => void
/**
* Location of the database cache file (JSON file)
*/
- cachePath: string;
+ cachePath: string
/**
* Maximum database size in megabytes
*/
- maxDbSizeMB: number;
+ maxDbSizeMB: number
}
diff --git a/core/src/database.model.ts b/core/src/database.model.ts
index a0cd3a5..0ceed6e 100644
--- a/core/src/database.model.ts
+++ b/core/src/database.model.ts
@@ -7,7 +7,7 @@ export interface DatabaseInterface {
* but is required for any form
* of non-volatile storage
*/
- init(): Promise<void>;
+ init(): Promise<void>
/**
* Read data from the "node" described by path
@@ -16,12 +16,12 @@ export interface DatabaseInterface {
* null is returned if there is no data
* at path
*/
- read(path: string): Promise<Object>;
+ read(path: string): Promise<Object>
/**
* Subscribe to all data changes at path
*/
- subscribe(path: string, callback: (e: any) => any): () => any;
+ subscribe(path: string, callback: (e: any) => any): () => any
/**
* Write data to the node described by path
@@ -32,20 +32,20 @@ export interface DatabaseInterface {
*
* An exception is thrown if the write fails
*/
- write(path: string, toWrite: Object): Promise<void>;
+ write(path: string, toWrite: Object): Promise<void>
/**
* Remove data at path
*/
- remove(path: string): Promise<void>;
+ remove(path: string): Promise<void>
/**
* Empty the database
*/
- flush(): Promise<void>;
+ flush(): Promise<void>
/**
* String representation (JSON) of the database
*/
- toString(): Object;
+ toString(): Object
}
diff --git a/core/src/database.test.ts b/core/src/database.test.ts
index 7f81308..7b2be8d 100644
--- a/core/src/database.test.ts
+++ b/core/src/database.test.ts
@@ -1,130 +1,131 @@
-import { Database } from "./database";
-import { NaiveErrorCode } from "../../lib/error.model";
+import { Database } from './database'
+import { NaiveErrorCode } from '../../lib/error.model'
-const generateString = (sizeBytes: number) =>
- Array(Math.ceil(sizeBytes)).fill("a");
+const generateString = (sizeBytes: number): string =>
+ Array(Math.ceil(sizeBytes))
+ .fill('a')
+ .join('')
-const maxDbSizeMB = 0.1;
+const maxDbSizeMB = 0.1
-describe("Database module", () => {
- let db: Database;
+describe('Database module', () => {
+ let db: Database
beforeAll(async () => {
db = new Database({
logger: console.log,
maxDbSizeMB,
cachePath: `${__dirname}/db.json`
- });
- await db.init();
- });
+ })
+ await db.init()
+ })
beforeEach(async () => {
- await db.flush();
- });
-
- // Next 2 tests simply look
- // for expections on operations that
- // should be harmless
- test("it should write data", () => {
- return db.write("/hello/world", {
- formula: "hello world"
- });
- });
-
- test("it should read data", () => {
- return db.read("/hello/world");
- });
-
- test("it should have a string representation", () => {
- expect(db.toString()).not.toHaveLength(0);
- });
-
- test("it should write and then read data", async () => {
- const path = "/hello/world";
+ await db.flush()
+ })
+
+ // Next 2 tests run simple operations
+ // that should _not_ throw exceptions
+ test('it should write data', () => {
+ return db.write('/hello/world', {
+ formula: 'hello world'
+ })
+ })
+
+ test('it should read data', () => {
+ return db.read('/hello/world')
+ })
+
+ test('it should have a string representation', () => {
+ expect(db.toString()).not.toHaveLength(0)
+ })
+
+ test('it should write and then read data', async () => {
+ const path = '/hello/world'
const toWrite = {
- secret: "stuff"
- };
- await db.write(path, toWrite);
- const s = await db.read(path);
- expect(s).toBe(toWrite);
- });
-
- test("it should write and read data from the root", async () => {
+ secret: 'stuff'
+ }
+ await db.write(path, toWrite)
+ const s = await db.read(path)
+ expect(s).toBe(toWrite)
+ })
+
+ test('it should write and read data from the root', async () => {
const toWrite = {
my: {
big: {
- fun: "data"
+ fun: 'data'
}
}
- };
- await db.write("/", toWrite);
- const s = await db.read("/");
- expect(s).toEqual(toWrite);
- });
-
- test("it should remove data", async () => {
- const path = "/this/is/fun";
+ }
+ await db.write('/', toWrite)
+ const s = await db.read('/')
+ expect(s).toEqual(toWrite)
+ })
+
+ test('it should remove data', async () => {
+ const path = '/this/is/fun'
await db.write(path, {
foxNews: {
- stories: ["AOC", "aoc", "aOC", "aOc"]
+ stories: ['AOC', 'aoc', 'aOC', 'aOc']
}
- });
- await db.remove(path);
- const s = await db.read(path);
- expect(s).toBeNull();
- });
+ })
+ await db.remove(path)
+ const s = await db.read(path)
+ expect(s).toBeNull()
+ })
- test("it should remove data at path & leave other data", async () => {
- const path = "/this/is/fun";
+ test('it should remove data at path & leave other data', async () => {
+ const path = '/this/is/fun'
const buzzFeed = {
- stories: ["hack your dogs brain in 4 easy steps"]
- };
+ stories: ['hack your dogs brain in 4 easy steps']
+ }
await db.write(path, {
foxNews: {
- stories: ["AOC", "aoc", "aOC", "aOc"]
+ stories: ['AOC', 'aoc', 'aOC', 'aOc']
},
buzzFeed
- });
- const appendedPath = `${path}/foxNews`;
- await db.remove(appendedPath);
- const buzzFeedOut = await db.read(`${path}/buzzFeed`);
- const foxOut = await db.read(appendedPath);
- expect(buzzFeedOut).toEqual(buzzFeed);
- expect(foxOut).toBeNull();
- });
-
- test("it should be empty after a flush", async () => {
- await db.write("/hello/world", {
- my: "object"
- });
- await db.flush();
- expect(db.toString()).toHaveLength(2); // empty object
- });
-
- test("it should read null on an empty node", async () => {
- const s = await db.read("/any/path/should/work");
- expect(s).toBeNull();
- });
-
- test("it should not change on read", async () => {
- await db.write("/my/data/lives/here", {
+ })
+ const appendedPath = `${path}/foxNews`
+ await db.remove(appendedPath)
+ const buzzFeedOut = await db.read(`${path}/buzzFeed`)
+ const foxOut = await db.read(appendedPath)
+ expect(buzzFeedOut).toEqual(buzzFeed)
+ expect(foxOut).toBeNull()
+ })
+
+ test('it should be empty after a flush', async () => {
+ await db.write('/hello/world', {
+ my: 'object'
+ })
+ await db.flush()
+ expect(db.toString()).toHaveLength(2) // empty object
+ })
+
+ test('it should read null on an empty node', async () => {
+ const s = await db.read('/any/path/should/work')
+ expect(s).toBeNull()
+ })
+
+ test('it should not change on read', async () => {
+ await db.write('/my/data/lives/here', {
whiteHouseDinner: `America's Finest McDouble's`
- });
- const before = db.toString();
- const s = await db.read("/no/data/here/silly");
- expect(s).toBeNull();
- expect(before).toEqual(db.toString());
- });
-
- test("it should throw an error if max size is exceeded", async () => {
+ })
+ const before = db.toString()
+ const s = await db.read('/no/data/here/silly')
+ expect(s).toBeNull()
+ expect(before).toEqual(db.toString())
+ })
+
+ test('it should throw an error if max size is exceeded', async () => {
try {
- await db.write("/big/data", {
+ await db.write('/big/data', {
bigObj: generateString(maxDbSizeMB * 1024 ** 2)
- });
- throw new Error("Did not throw exception");
+ })
+ throw new Error('Did not throw exception')
} catch (e) {
- expect(e.code).toBe(NaiveErrorCode.OUT_OF_SPACE);
+ expect(e.code).toBe(NaiveErrorCode.OUT_OF_SPACE)
}
- });
-});
+ })
+})
diff --git a/core/src/database.ts b/core/src/database.ts
index 3b19f8e..e4083b1 100644
--- a/core/src/database.ts
+++ b/core/src/database.ts
@@ -1,22 +1,22 @@
-import { writeFile, readFile } from "fs";
-import { promisify } from "util";
+import { writeFile, readFile } from 'fs'
+import { promisify } from 'util'
-import { DatabaseInterface } from "./database.model";
-import { Context } from "./context.model";
-import { NaiveError, NaiveErrorCode as e } from "../../lib/error.model";
-import { ChangeHandlers } from "./change-handlers.model";
+import { DatabaseInterface } from './database.model'
+import { Context } from './context.model'
+import { NaiveError, NaiveErrorCode as e } from '../../lib/error.model'
+import { ChangeHandlers } from './change-handlers.model'
-import { last, getKey } from "../../lib/util";
+import { last, getKey } from '../../lib/util'
/**
* Split path using "/" as a delimiter
*/
-const splitPath = (path: string): string[] => path.split("/").filter(k => k);
+const splitPath = (path: string): string[] => path.split('/').filter(k => k)
/**
* Identify if a path is a root node
*/
-const isRootNode = (path: string): boolean => path === "/" || path === "";
+const isRootNode = (path: string): boolean => path === '/' || path === ''
/**
* Check if path1 matches path2,
@@ -26,19 +26,19 @@ const isRootNode = (path: string): boolean => path === "/" || path === "";
*/
const isChildOrMatch = (child: string, parent: string) => {
// console.log(`child: ${child}, parent ${parent}`);
- if (child === parent || parent === "/") return true;
- const parentTokens = parent.split("/").filter((i: string) => i.length);
- return parentTokens.every((t, i) => child.split("/")[i] === t);
-};
+ if (child === parent || parent === '/') return true
+ const parentTokens = parent.split('/').filter((i: string) => i.length)
+ return parentTokens.every((t, i) => child.split('/')[i] === t)
+}
-const write = promisify(writeFile);
-const read = promisify(readFile);
+const write = promisify(writeFile)
+const read = promisify(readFile)
export const DEFAULT_CTX = {
logger: console.log,
cachePath: `${__dirname}/db.json`,
maxDbSizeMB: 6
-};
+}
/**
* Implementation of NoSQL DB that uses paths and objects.
@@ -54,22 +54,22 @@ export class Database implements DatabaseInterface {
/**
* In memory buffer to read/write data
*/
- private buff: any = {};
+ private buff: any = {}
/**
* An array of callback functions that are alerted
* when the database changes (mostly want to use this
* for some type of pubsub functionality on-top)
*/
- private changeHandlers: ChangeHandlers = {};
+ private changeHandlers: ChangeHandlers = {}
constructor(private ctx: Context = DEFAULT_CTX) {}
async init(): Promise<void> {
try {
- const buff = await read(this.ctx.cachePath);
- this.buff = JSON.parse(buff.toString());
+ const buff = await read(this.ctx.cachePath)
+ this.buff = JSON.parse(buff.toString())
} catch (e) {
- this.ctx.logger("Failed to init database, using empty object");
- this.ctx.logger(e);
+ this.ctx.logger('Failed to init database, using empty object')
+ this.ctx.logger(e)
}
}
@@ -78,57 +78,57 @@ export class Database implements DatabaseInterface {
// (which obviously becomes a bad idea at some point)
async read(path: string): Promise<Object> {
// root node case
- if (isRootNode(path)) return this.buff;
- const pathParts = splitPath(path);
- return this.resolve(pathParts);
+ if (isRootNode(path)) return this.buff
+ const pathParts = splitPath(path)
+ return this.resolve(pathParts)
}
async write(path: string, toWrite: any): Promise<void> {
if (isRootNode(path)) {
- this.buff = toWrite;
+ this.buff = toWrite
} else {
- const pathParts = splitPath(path);
- const writeTo = this.resolve(pathParts, false, 1);
- writeTo[last(pathParts)] = toWrite;
+ const pathParts = splitPath(path)
+ const writeTo = this.resolve(pathParts, false, 1)
+ writeTo[last(pathParts)] = toWrite
}
- await this.serialize();
+ await this.serialize()
// alert everyone of our new change
- await this.runChangeHandlers(path, toWrite);
+ await this.runChangeHandlers(path, toWrite)
}
private async runChangeHandlers(path: string, change: any): Promise<void> {
- const handlers = Object.values(this.changeHandlers);
+ const handlers = Object.values(this.changeHandlers)
for (let handler of handlers) {
if (isChildOrMatch(path, handler.path)) {
- await handler.callback(change);
+ await handler.callback(change)
}
}
}
subscribe(path: string, callback: (e: any) => Promise<any>): () => any {
- const key = getKey("subscriber");
+ const key = getKey('subscriber')
this.changeHandlers[key] = {
path,
callback
- };
- return () => this.unsubscribe(key);
+ }
+ return () => this.unsubscribe(key)
}
private unsubscribe(key: string) {
- delete this.changeHandlers[key];
+ delete this.changeHandlers[key]
}
remove(path: string): Promise<void> {
- return this.write(path, null);
+ return this.write(path, null)
}
async flush(): Promise<void> {
- this.buff = {};
- await this.serialize();
+ this.buff = {}
+ await this.serialize()
}
toString() {
- return JSON.stringify(this.buff);
+ return JSON.stringify(this.buff)
}
/**
@@ -148,41 +148,41 @@ export class Database implements DatabaseInterface {
isRead: boolean = true,
level: number = 0
): any {
- const [firstPart] = pathParts;
- if (isRootNode(firstPart)) return this.buff;
- const n = pathParts.length - level;
+ const [firstPart] = pathParts
+ if (isRootNode(firstPart)) return this.buff
+ const n = pathParts.length - level
// TODO avoid having to pull
// this specific case out
if (n === 0) {
if (!this.buff[firstPart]) {
- this.buff[firstPart] = {};
- return this.buff[firstPart];
+ this.buff[firstPart] = {}
+ return this.buff[firstPart]
}
}
// start at the root of our buffer
- let lastNode = this.buff;
- let node;
+ let lastNode = this.buff
+ let node
for (let i = 0; i < n; i++) {
- const part: string = pathParts[i];
+ const part: string = pathParts[i]
// handle null node
if (!lastNode[part]) {
// if we're reading from the object
// we want to stop as soon
// as we hit a null node
if (isRead) {
- return null;
+ return null
}
// but if we're writing and the node is missing,
// we should make it and continue
else {
- lastNode[part] = {};
+ lastNode[part] = {}
}
}
// traverse to the next node
- node = lastNode[part];
- lastNode = node;
+ node = lastNode[part]
+ lastNode = node
}
- return node;
+ return node
}
/**
@@ -195,12 +195,12 @@ export class Database implements DatabaseInterface {
* Throws OUT_OF_SPACE
*/
private serialize(): Promise<void> {
- if (!this.hasSpace()) throw new NaiveError(e.OUT_OF_SPACE);
- return write(this.ctx.cachePath, this.toString());
+ if (!this.hasSpace()) throw new NaiveError(e.OUT_OF_SPACE)
+ return write(this.ctx.cachePath, this.toString())
}
private hasSpace(): boolean {
// convert from MB to B
- return this.toString().length <= this.ctx.maxDbSizeMB * 1024 ** 2;
+ return this.toString().length <= this.ctx.maxDbSizeMB * 1024 ** 2
}
}
diff --git a/core/src/index.ts b/core/src/index.ts
index 0874475..ff4b500 100644
--- a/core/src/index.ts
+++ b/core/src/index.ts
@@ -1,3 +1,3 @@
-export { Database } from "./database";
-export { Context } from "./context.model";
-export { DatabaseInterface } from "./database.model";
+export { Database } from './database'
+export { Context } from './context.model'
+export { DatabaseInterface } from './database.model'