Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New package: @alwatr/storage #167

Merged
merged 13 commits into from
Jul 22, 2022
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,5 @@ package-lock.json
.env
.env.test
test.json

temp
25 changes: 25 additions & 0 deletions demo/storage/big-data-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {random} from '@alwatr/math';
import {AlwatrStorage} from '@alwatr/storage';

import type {DocumentObject} from '@alwatr/storage';

interface User extends DocumentObject {
fname: string;
lname: string;
email: string;
token: string;
}

const db = new AlwatrStorage<User>('junk-data', 'temp');

db.ready.then(() => {
for (let i = 0; i < 10000; i++) {
db.set({
_id: random.string(4, 16),
fname: random.string(4, 16),
lname: random.string(4, 32),
email: random.string(8, 32),
token: random.string(16),
});
}
});
52 changes: 52 additions & 0 deletions demo/storage/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {AlwatrStorage} from '@alwatr/storage';

import type {DocumentObject} from '@alwatr/storage';

interface User extends DocumentObject {
fname: string;
lname: string;
email: string;
token?: string;
}

const db = new AlwatrStorage<User>('user-list', 'temp');

// await db.ready
// or
db.ready.then(() => {
console.log('db loaded and ready to access.');

let ali = db.get('alimd');

if (ali == null) {
console.log('ali not found');
ali = {
_id: 'alimd',
fname: 'Ali',
lname: 'Mihandoost',
email: 'ali@mihandoost.com',
};
} else {
console.log('ali found: %o', ali);
/**
* {
* _id: 'alimd',
* fname: 'Ali',
* lname: 'MM',
* email: 'i@ali.md',
* }
*/

ali.token = Math.random().toString(36).substring(2, 15);
}

db.set(ali);

db.set({
_id: 'fmd',
fname: 'Fatemeh',
lname: 'Mihandoost',
email: 'Fatemeh@mihandoost.com',
token: Math.random().toString(36).substring(2, 15),
});
});
3 changes: 2 additions & 1 deletion demo/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
{"path": "../packages/core/router"},
{"path": "../packages/core/i18n"},
{"path": "../packages/core/math"},
{"path": "../packages/core/element"}
{"path": "../packages/core/element"},
{"path": "../packages/core/storage"},
],
"exclude": ["*.d.ts", "node_modules"]
}
3 changes: 0 additions & 3 deletions packages/core/jatabase/README.md

This file was deleted.

1 change: 0 additions & 1 deletion packages/core/jatabase/src/jatabase.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/core/jatabase/src/type.ts

This file was deleted.

11 changes: 11 additions & 0 deletions packages/core/math/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,17 @@ Generate a random float between min and max.
console.log(random.float(1, 10)); // somewhere between 1 and 10
```

### `string: (min: number, max?: number): string`

Generate a random string with random length.
The string will contain only characters from the characters list.
The length of the string will be between min and max (max included).
If max not specified, the length will be set to min.

```js
console.log(random.string(6)); // something like 'Aab1V2'
```

### `step(min: number, max: number, step: number): number`

Generate a random integer between min and max with a step.
Expand Down
26 changes: 25 additions & 1 deletion packages/core/math/src/math.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ export const transformToRange = (x: number, options: TransformRangeOptions): num
return y;
};

const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;

export const random = {
/**
* Returns a float random number between 0 and 1 (1 Not included).
Expand All @@ -81,7 +84,7 @@ export const random = {
},

/**
* Generate a random integer between min and max.
* Generate a random integer number between min and max (max included).
*
* Example:
*
Expand All @@ -92,6 +95,7 @@ export const random = {
integer: (min: number, max: number): number => Math.floor(random.float(min, max + 1)),

/**
* Generate a random float number between min and max (max not included).
*
* Example:
*
Expand All @@ -101,6 +105,26 @@ export const random = {
*/
float: (min: number, max: number): number => random.value * (max - min) + min,

/**
* Generate a random string with random length.
* The string will contain only characters from the characters list.
* The length of the string will be between min and max (max included).
* If max not specified, the length will be set to min.
*
* Example:
*
*```js
* console.log(random.string(6)); // something like 'Aab1V2'
* ```
*/
string: (min: number, max?: number): string => {
let result = '';
for (let i = max != null ? random.integer(min, max) : min; i > 0; i--) {
result += characters.charAt(Math.floor(random.value * charactersLength));
}
return result;
},

/**
* Generate a random integer between min and max with a step.
*
Expand Down
2 changes: 1 addition & 1 deletion packages/core/micro-server/src/micro-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ alwatrRegisteredList.push({
});

export class AlwatrMicroServer {
protected logger = createLogger(`micro-server:${this.port}`);
protected logger = createLogger(`alwatr-micro-server:${this.port}`);
protected server = createServer(this.handleRequest);

constructor(protected port: number, autoListen = true) {
Expand Down
3 changes: 3 additions & 0 deletions packages/core/storage/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# @alwatr/storage

Elegant micro in-memory json-like storage with disk backed, Faster NoSQL Database written in tiny TypeScript ES module.
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
{
"name": "@alwatr/jatabase",
"name": "@alwatr/storage",
"version": "0.0.0",
"description": "Elegant powerful micro in-memory Database with JSON disk backed, written in tiny TypeScript ES module.",
"description": "Elegant micro in-memory json-like storage with disk backed, Faster NoSQL Database written in tiny TypeScript ES module.",
"keywords": [
"database",
"storage",
"json",
"nosql",
"no-sql",
"data",
"data-storage",
"file",
"typescript",
"esm",
"alwatr"
],
"main": "jatabase.js",
"main": "storage.js",
"type": "module",
"types": "jatabase.d.ts",
"types": "storage.d.ts",
"author": "S. Ali Mihandoost <ali.mihandoost@gmail.com> (https://ali.mihandoost.com)",
"license": "MIT",
"files": [
Expand All @@ -25,9 +29,9 @@
"repository": {
"type": "git",
"url": "https://github.com/AliMD/alwatr",
"directory": "package/jatabase"
"directory": "package/storage"
},
"homepage": "https://github.com/AliMD/alwatr/tree/main/package/jatabase#readme",
"homepage": "https://github.com/AliMD/alwatr/tree/main/package/storage#readme",
"bugs": {
"url": "https://github.com/AliMD/alwatr/issues"
},
Expand Down
123 changes: 123 additions & 0 deletions packages/core/storage/src/storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import {existsSync} from 'fs';

import {alwatrRegisteredList, createLogger} from '@alwatr/logger';

import {readJsonFile, writeJsonFile} from './util.js';

import type {DocumentObject, DocumentListStorage} from './type.js';
import type {Logger} from '@alwatr/logger/type.js';

export * from './type.js';

alwatrRegisteredList.push({
name: '@alwatr/storage',
version: '{{ALWATR_VERSION}}',
});

/**
* Elegant powerful micro in-memory document Database with disk backed.
*
* @example
* import {AlwatrStorage} from '@alwatr/storage';
* const db = new AlwatrStorage<User>('user-list');
* await db.ready
* const user = db.get('my-user-id', true);
*/
export class AlwatrStorage<DocumentType extends DocumentObject> {
isReady = false;
readonly ready: Promise<void>;
readonly name: string;

protected _logger: Logger;
protected _storage: DocumentListStorage<DocumentType> = {};
protected _storagePath: string;

constructor(name: string, pathPrefix = 'data') {
this._logger = createLogger(`alwatr-storage:${name}`);
this.name = name;
this._storagePath = `${pathPrefix}/${name}.json`;
this.ready = this._init();
}

private async _init(): Promise<void> {
this._logger.logMethod('_init');
if (existsSync(this._storagePath)) {
this._storage = await readJsonFile<DocumentListStorage<DocumentType>>(this._storagePath);
} else {
this._storage = {};
}
this.isReady = true;
this._logger.logProperty('isReady', this.isReady);
}

/**
* Get a document object by id.
*
* @param documentId The id of the document object.
* @param fastInstance by default it will return a copy of the document.
* if you set fastInstance to true, it will return the original document.
* This is dangerous but much faster and you should use it only if you know what you are doing.
*/
get(documentId: string, fastInstance?: boolean): DocumentType | null {
this._logger.logMethodArgs('get', documentId);
const documentObject = this._storage[documentId];
if (documentObject == null) {
return null;
} else if (fastInstance) {
return documentObject;
} else {
return JSON.parse(JSON.stringify(documentObject));
}
}

/**
* Insert/update a document object in the storage.
*
* @param documentObject The document object to insert/update contain `_id`.
* @param fastInstance by default it will make a copy of the document before set.
* if you set fastInstance to true, it will set the original document.
* This is dangerous but much faster and you should use it only if you know what you are doing.
*/
set(documentObject: DocumentType, fastInstance?: boolean): void {
this._logger.logMethodArgs('set', documentObject._id);

// update meta
const oldData = this._storage[documentObject._id];
documentObject._updated = Date.now();
documentObject._created = oldData?._created ?? documentObject._updated;
documentObject._rev = (oldData?._rev ?? 0) + 1;

if (fastInstance !== true) {
documentObject = JSON.parse(JSON.stringify(documentObject));
}

this._storage._last = documentObject._id;
this._storage[documentObject._id] = documentObject;
this.save();
}

/**
* Remove a document object from the storage.
*/
remove(documentId: string): void {
this._logger.logMethodArgs('remove', documentId);
delete this._storage[documentId];
}

private _saveTimer?: NodeJS.Timeout | number;
/**
* Save the storage to disk.
*/
save(): void {
this._logger.logMethod('save.request');
if (this._saveTimer != null) {
return;
}
this._saveTimer = setTimeout(() => {
this._logger.logMethod('save.action');
clearTimeout(this._saveTimer);
delete this._saveTimer;
writeJsonFile(this._storagePath, this._storage);
}, 100);
}
}
11 changes: 11 additions & 0 deletions packages/core/storage/src/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type JSON = Record<string, unknown>;

export interface DocumentObject {
_id: string;
_rev?: number;
_created?: number;
_updated?: number;
}

export type DocumentListStorage<DocType extends DocumentObject> =
Record<string, DocType | undefined> & {_last?: string};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {existsSync, promises as fs} from 'fs';
import {resolve, dirname} from 'path';

import type {JSON} from './type';
import type {JSON} from './type.js';

// @TODO: add debug log

Expand All @@ -26,7 +26,7 @@ export async function readJsonFile<T extends JSON>(path: string): Promise<T> {
}

try {
return JSON.parse(fileContent);
return JSON.parse(fileContent) as T;
} catch (err) {
throw new Error('invalid_json');
}
Expand All @@ -50,7 +50,7 @@ export async function writeJsonFile<T extends JSON>(path: string, dataObject: T)

let jsonContent;
try {
jsonContent = JSON.stringify(dataObject, undefined, 2);
jsonContent = JSON.stringify(dataObject);
} catch (err) {
throw new Error('stringify_failed');
}
Expand Down
Loading