docs: update README, add some more jsdoc
This commit is contained in:
parent
22a8b4d03f
commit
55a73c3d0a
10 changed files with 155 additions and 30 deletions
|
@ -1,7 +1,6 @@
|
|||
# Disk Storage System
|
||||
# Filesystem Database
|
||||
|
||||
We use the disk instead of a database to reduce complexity. We leave the hard
|
||||
optimization to the filesystem layer.
|
||||
We just write to the disk to reduce complexity.
|
||||
|
||||
## API
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@andyburke/fsdb",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
".": "./fsdb.ts",
|
||||
|
|
107
fsdb.ts
107
fsdb.ts
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* We just write to the disk to reduce complexity.
|
||||
* @module
|
||||
*/
|
||||
|
||||
import * as fs from '@std/fs';
|
||||
import * as path from '@std/path';
|
||||
import by_lurid from './organizers/by_lurid.ts';
|
||||
|
@ -29,7 +34,9 @@ export interface FSDB_INDEXER<T> {
|
|||
lookup(value: string, options?: FSDB_SEARCH_OPTIONS<T>): Promise<string[]>;
|
||||
}
|
||||
|
||||
/** Represents a collection of like items within the database on disk. */
|
||||
/**
|
||||
* Represents a collection of like items within the database on disk.
|
||||
*/
|
||||
export class FSDB_COLLECTION<T extends Record<string, any>> {
|
||||
private config: FSDB_COLLECTION_CONFIG;
|
||||
public INDEX: Record<string, FSDB_INDEXER<any>>;
|
||||
|
@ -93,7 +100,14 @@ export class FSDB_COLLECTION<T extends Record<string, any>> {
|
|||
Deno.writeTextFileSync(collection_info_file_path, collection_info_json);
|
||||
}
|
||||
|
||||
/** Get the "organized" path for the given item within the database. */
|
||||
/**
|
||||
* Get the "organized" path for the given item within the database.
|
||||
*
|
||||
* @param {any} item The item to organize.
|
||||
* @param {string} [id_field] Optional override for id_field.
|
||||
*
|
||||
* @returns {string} An organized, resolved item path.
|
||||
*/
|
||||
public get_organized_item_path(item: any, id_field?: string): string {
|
||||
const id: string = item[id_field ?? this.config.id_field];
|
||||
const path_elements: string[] = this.config.organize(id);
|
||||
|
@ -101,11 +115,24 @@ export class FSDB_COLLECTION<T extends Record<string, any>> {
|
|||
return resolved_item_path;
|
||||
}
|
||||
|
||||
/** Get the "organized" path for the given id within the database. */
|
||||
/**
|
||||
* Get the "organized" path for the given id within the database.
|
||||
*
|
||||
* @param {string} id The id to organize.
|
||||
*
|
||||
* @returns {string} An organized, resolved path for the id.
|
||||
*/
|
||||
public get_organized_id_path(id: string): string {
|
||||
return this.get_organized_item_path({ id }, 'id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the item's directory exists.
|
||||
*
|
||||
* @param {any} item The item to be stored.
|
||||
* @param {string} [id_field] Optional override of the id_field.
|
||||
* @returns {string} An organized, resolved path for the item, with the containing directory created.
|
||||
*/
|
||||
private async ensure_item_path(item: any, id_field?: string): Promise<string> {
|
||||
const organized_item_path: string = this.get_organized_item_path(item, id_field);
|
||||
const organized_item_dir: string = path.dirname(organized_item_path);
|
||||
|
@ -135,7 +162,13 @@ export class FSDB_COLLECTION<T extends Record<string, any>> {
|
|||
}
|
||||
}
|
||||
|
||||
/** Get an item from the collection given its id. */
|
||||
/**
|
||||
* Get an item from the collection.
|
||||
*
|
||||
* @param {string} id The item id to look up.
|
||||
*
|
||||
* @returns {Promise<T | null>} Returns a promise for either the item or `null` if it cannot be found.
|
||||
*/
|
||||
async get(id: string): Promise<T | null> {
|
||||
const item_path: string = this.get_organized_id_path(id);
|
||||
const item_exists: boolean = await fs.exists(item_path);
|
||||
|
@ -155,7 +188,13 @@ export class FSDB_COLLECTION<T extends Record<string, any>> {
|
|||
return item;
|
||||
}
|
||||
|
||||
/** Create an item in the collection. */
|
||||
/**
|
||||
* Create an item in the collection.
|
||||
*
|
||||
* @param {T} item The item to be stored in the collection.
|
||||
*
|
||||
* @returns {Promise<T>} Returns a promise for the item after it has been stored.
|
||||
*/
|
||||
async create(item: T): Promise<T> {
|
||||
const item_path: string = this.get_organized_item_path(item);
|
||||
const item_exists: boolean = await fs.exists(item_path);
|
||||
|
@ -176,7 +215,13 @@ export class FSDB_COLLECTION<T extends Record<string, any>> {
|
|||
return item;
|
||||
}
|
||||
|
||||
/** Update the given item in the collection, requiring the id to be stable. */
|
||||
/**
|
||||
* Update the given item in the collection, requiring the id to be stable.
|
||||
*
|
||||
* @param {T} item The item to be updated in the collection.
|
||||
*
|
||||
* @returns {Promise<T>} Returns a promise for the item after it has been updated.
|
||||
*/
|
||||
async update(item: T): Promise<T> {
|
||||
const item_path: string = this.get_organized_item_path(item);
|
||||
const id: string = item[this.config.id_field];
|
||||
|
@ -199,7 +244,13 @@ export class FSDB_COLLECTION<T extends Record<string, any>> {
|
|||
return item;
|
||||
}
|
||||
|
||||
/** Delete the given item from the collection. */
|
||||
/**
|
||||
* Delete the given item from the collection.
|
||||
*
|
||||
* @param {T} item The item to be updated in the collection.
|
||||
*
|
||||
* @returns {Promise<T>} Returns a promise for the item after it has been deleted or `null` if the item wasn't in the collection.
|
||||
*/
|
||||
async delete(item: T): Promise<T | null> {
|
||||
const item_path = this.get_organized_item_path(item);
|
||||
const item_exists = await fs.exists(item_path);
|
||||
|
@ -241,7 +292,13 @@ export class FSDB_COLLECTION<T extends Record<string, any>> {
|
|||
return item;
|
||||
}
|
||||
|
||||
/** Iterate through the items. */
|
||||
/**
|
||||
* Iterate through all items in the collection.
|
||||
*
|
||||
* @param {FSDB_SEARCH_OPTIONS} options The item to be updated in the collection.
|
||||
*
|
||||
* @returns {Promise<WALK_ENTRY<T>[]>} Returns an array of `WALK_ENTRY` items for items given the input options.
|
||||
*/
|
||||
async all({
|
||||
limit = 100,
|
||||
offset = 0,
|
||||
|
@ -314,16 +371,22 @@ export class FSDB_COLLECTION<T extends Record<string, any>> {
|
|||
return results;
|
||||
}
|
||||
|
||||
/** Use indexes to search for matching items. */
|
||||
async find(criteria: Record<string, any>, input_options?: FSDB_SEARCH_OPTIONS<T>): Promise<WALK_ENTRY<T>[]> {
|
||||
/**
|
||||
* Use indexes to search for matching items.
|
||||
*
|
||||
* @param {Record<string, any>} criteria The criteria an item should match, usually a field like `{ email: 'someone@somewhere.com` }`
|
||||
* @param {FSDB_SEARCH_OPTIONS<T>} options
|
||||
* @returns {Promise<WALK_ENTRY<T>[]>} Returns an array of `WALK_ENTRY` items for items given the input options.
|
||||
*/
|
||||
async find(criteria: Record<string, any>, options?: FSDB_SEARCH_OPTIONS<T>): Promise<WALK_ENTRY<T>[]> {
|
||||
if (Deno.env.get('FSDB_PERF')) performance.mark('fsdb_find_begin');
|
||||
|
||||
const options: FSDB_SEARCH_OPTIONS<T> = {
|
||||
const find_options: FSDB_SEARCH_OPTIONS<T> = {
|
||||
...{
|
||||
limit: 100,
|
||||
offset: 0
|
||||
},
|
||||
...(input_options ?? {})
|
||||
...(options ?? {})
|
||||
};
|
||||
|
||||
const results: WALK_ENTRY<T>[] = [];
|
||||
|
@ -333,12 +396,12 @@ export class FSDB_COLLECTION<T extends Record<string, any>> {
|
|||
const indexer_for_search_key: FSDB_INDEXER<T> | undefined = this.INDEX[search_key];
|
||||
const value: string = criteria[search_key];
|
||||
if (indexer_for_search_key) {
|
||||
item_paths.push(...await indexer_for_search_key.lookup(value, input_options));
|
||||
item_paths.push(...await indexer_for_search_key.lookup(value, find_options));
|
||||
}
|
||||
}
|
||||
|
||||
const limit = options?.limit ?? 100;
|
||||
const offset = options?.offset ?? 0;
|
||||
const limit = find_options.limit ?? 100;
|
||||
const offset = find_options.offset ?? 0;
|
||||
let counter = 0;
|
||||
|
||||
for await (const item_path of item_paths) {
|
||||
|
@ -369,13 +432,19 @@ export class FSDB_COLLECTION<T extends Record<string, any>> {
|
|||
|
||||
this.emit('find', {
|
||||
criteria,
|
||||
options,
|
||||
options: find_options,
|
||||
results
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event listener.
|
||||
*
|
||||
* @param {string} event The event to listen for.
|
||||
* @param {(event_data: any) => void} handler The handler for the event.
|
||||
*/
|
||||
public on(event: string, handler: (event_data: any) => void) {
|
||||
const listeners: ((event: any) => void)[] = this.event_listeners[event] = this.event_listeners[event] ?? [];
|
||||
if (!listeners.includes(handler)) {
|
||||
|
@ -393,6 +462,12 @@ export class FSDB_COLLECTION<T extends Record<string, any>> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an event listener.
|
||||
*
|
||||
* @param {string} event The event that was listened to.
|
||||
* @param {(event_data: any) => void} handler The handler that was registered that should be removed.
|
||||
*/
|
||||
public off(event: string, handler: (event_data: any) => void) {
|
||||
const listeners: ((event: any) => void)[] = this.event_listeners[event] = this.event_listeners[event] ?? [];
|
||||
if (listeners.includes(handler)) {
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* FSDB Indexers
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { FSDB_INDEXER_SYMLINKS } from './indexers/symlinks.ts';
|
||||
|
||||
export { FSDB_INDEXER_SYMLINKS };
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* FSDB Symlink Indexer
|
||||
* @module
|
||||
*/
|
||||
|
||||
import * as fs from '@std/fs';
|
||||
import { FSDB_INDEXER, FSDB_SEARCH_OPTIONS } from '../fsdb.ts';
|
||||
import * as path from '@std/path';
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* FSDB Organizers
|
||||
* @module
|
||||
*/
|
||||
|
||||
import by_character from './organizers/by_character.ts';
|
||||
import by_email from './organizers/by_email.ts';
|
||||
import by_lurid from './organizers/by_lurid.ts';
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
/**
|
||||
* FSDB Organizer: By Character
|
||||
*
|
||||
* Organizes by splitting a string up and using the characters
|
||||
* for folders, eg:
|
||||
*
|
||||
* abcdefg.json => /a/ab/abc/abcdefg.json
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import sanitize from '../utils/sanitize.ts';
|
||||
|
||||
export default function by_character(value: string): string[] {
|
||||
|
|
|
@ -1,3 +1,18 @@
|
|||
/**
|
||||
* FSDB Organizer: By Email
|
||||
*
|
||||
* Organizes by splitting an email into its components, eg:
|
||||
*
|
||||
* com/example.com/soandso@example.com
|
||||
*
|
||||
* A symlinking index based on this organizer might look like:
|
||||
* fsdb root index tld domain email
|
||||
* [ ][ V ][ V ][ V ][ V ]
|
||||
* /path/to/db/root/.indexes/email/com/example.com/soandso@example.com
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import sanitize from '../utils/sanitize.ts';
|
||||
|
||||
const EMAIL_REGEX = /^(?<username>.+)@(?<domain>(?<hostname>.+)\.(?<tld>.+))$/;
|
||||
|
@ -16,9 +31,5 @@ export default function by_email(email: string): string[] {
|
|||
return [];
|
||||
}
|
||||
|
||||
// for example, a symlinking index based on this organizer might look like:
|
||||
// fsdb root index tld domain email
|
||||
// [ ][ V ][ V ][ V ][ V ]
|
||||
// /path/to/db/root/.indexes/email/com/example.com/soandso@example.com
|
||||
return [sanitize(tld), sanitize(domain), sanitize(email), `${sanitize(email)}.json`];
|
||||
}
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
/**
|
||||
* FSDB Organizer: By Lurid
|
||||
*
|
||||
* Organizes by splitting a lurid strategically:
|
||||
*
|
||||
* able-fish-cost-them/able-fish-cost-them-post-many-form/able-fish-cost-them-post-many-form-hope-wife-born.json
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import sanitize from '../utils/sanitize.ts';
|
||||
|
||||
export default function by_lurid(id: string): string[] {
|
||||
// Replace invalid filename characters and leading dots
|
||||
const sanitized_id = sanitize(id);
|
||||
|
||||
// assuming a lurid, eg: able-fish-cost-them-post-many-form-hope-wife-born
|
||||
// ./able-fish-cost-them/able-fish-cost-them-post-many-form/able-fish-cost-them-post-many-form-hope-wife-born.json
|
||||
|
||||
const result: string[] = [
|
||||
sanitized_id.slice(0, 14),
|
||||
sanitized_id.slice(0, 34),
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
/**
|
||||
* FSDB Organizer: By Phone
|
||||
*
|
||||
* Organizes by splitting a phone number into its components:
|
||||
*
|
||||
* Eg: 1-213-555-1234 => 1/213/555/213-555-1234.json
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import sanitize from '../utils/sanitize.ts';
|
||||
|
||||
const PHONE_REGEX =
|
||||
|
@ -22,9 +32,6 @@ export default function by_phone(phone: string): string[] {
|
|||
|
||||
const normalized_number = `${sanitize(area_code)}-${sanitize(central_office_code)}-${sanitize(subscriber_code)}`;
|
||||
|
||||
// for example, a symlinking index based on this organizer might look like:
|
||||
// fsdb root index country_code office_code area_code phone
|
||||
// /path/to/db/root/.indexes/phone/1/213/555/213-555-1234
|
||||
return [
|
||||
sanitize(country_code ?? '1'),
|
||||
sanitize(area_code),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue