Skip to content

Commit

Permalink
fix: 🐛 fix: Fixed scope-related logic in the LevelDB storage
Browse files Browse the repository at this point in the history
  • Loading branch information
kostysh committed Jan 17, 2024
1 parent a32a742 commit 0afaff8
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 24 deletions.
2 changes: 1 addition & 1 deletion packages/storage/src/abstract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export abstract class Storage {
abstract delete(key: string): Promise<boolean>;
abstract entries<CustomValueType = unknown>():
| IterableIterator<[string, CustomValueType]>
| Promise<[[string, CustomValueType]]>;
| Promise<[string, CustomValueType][]>;
}

export interface GenericStorageOptions {
Expand Down
113 changes: 90 additions & 23 deletions packages/storage/src/level.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import {
StorageInitializerFunction,
} from './abstract.js';
import { ClassicLevel } from 'classic-level';
import { parse, stringify } from 'superjson';
import { stringify, parse } from 'superjson';
import { IEncoding } from 'level-transcoder';
import { Buffer } from 'buffer';
import { createLogger } from '@windingtree/sdk-logger';

const logger = createLogger('LocalStorage');
const logger = createLogger('LevelDBStorage');

const superJsonEncoding: IEncoding<
string | string[],
Expand All @@ -26,11 +26,23 @@ export interface LevelStorageOptions extends GenericStorageOptions {
path?: string;
}

/**
* LevelDB based key-value storage implementation
*
* @class LevelDBStorage
* @extends {Storage}
*/
export class LevelDBStorage extends Storage {
protected db: ClassicLevel<string, string | string[]>;
/** Key for storing ids included in the scope */
scopeIdsKey?: string;

/**
* Creates an instance of LevelDBStorage.
*
* @param {LevelStorageOptions} [options]
* @memberof LevelDBStorage
*/
constructor(options?: LevelStorageOptions) {
super();

Expand All @@ -57,7 +69,7 @@ export class LevelDBStorage extends Storage {
return new Set();
}

const scope = (await this.get(this.scopeIdsKey)) as Iterable<string>;
const scope = (await this.get<string[]>(this.scopeIdsKey)) ?? [];
return new Set(scope);
}

Expand Down Expand Up @@ -93,68 +105,123 @@ export class LevelDBStorage extends Storage {
ids.delete(id);
await this.saveScopeIds(ids);
} catch (error) {
logger.error('addScopeId', error);
logger.error('deleteScopeId', error);
}
}

/**
* Deletes the key from the storage
*
* @param {string} key
* @returns {Promise<boolean>}
* @memberof LevelDBStorage
*/
async delete(key: string): Promise<boolean> {
try {
await this.db.del(key);
const isDeleted = (await this.db.get(key)) === null;
const keyExists = await this.db
.get(key)
.then(() => true)
.catch(() => false);

if (isDeleted) {
await this.deleteScopeId(key);
if (!keyExists) {
return false;
}

return isDeleted;
await this.db.del(key);
await this.deleteScopeId(key);
return true;
} catch (e) {
logger.error('delete', e);
return false;
}
}

entries<ValueType = unknown>(): Promise<[[string, ValueType]]> {
return this.db.iterator().all() as Promise<[[string, ValueType]]>;
/**
* Returns all entries in the storage as an array of [key, value] pairs
*
* @template ValueType
* @returns {Promise<[string, ValueType][]>}
* @memberof LevelDBStorage
*/
async entries<ValueType>(): Promise<[string, ValueType][]> {
const scopeEnabled = Boolean(this.scopeIdsKey);
const ids = await this.getScopeIds();
const entries: [string, ValueType][] = [];

for await (const [key, value] of this.db.iterator<string, ValueType>({})) {
if (scopeEnabled && this.scopeIdsKey && !ids.has(key)) {
continue;
}

entries.push([key, value]);
}

return entries;
}

/**
* Retrieves the value associated with the given key
*
* @template ValueType
* @param {string} key
* @returns {Promise<ValueType | undefined>}
* @memberof LevelDBStorage
*/
async get<ValueType>(key: string): Promise<ValueType | undefined> {
try {
return (await this.db.get(key)) as ValueType;
} catch (e) {
logger.error(e);
logger.error('get', e);
return;
}
}

protected a = 0;

/**
* Sets the value for the given key in the storage
*
* @template ValueType
* @param {string} key
* @param {ValueType} value
* @memberof LevelDBStorage
*/
async set<ValueType>(key: string, value: ValueType): Promise<void> {
await this.db.put(key, value as string | string[]);
await this.addScopeId(key);
}

/**
* Clears all data from the storage
*
* @memberof LevelDBStorage
*/
async reset() {
await this.db.clear();
}

/**
* Opens the LevelDB storage
*
* @memberof LevelDBStorage
*/
async open() {
await this.db.open();
}

/**
* Returns the LevelDB instance
*
* @readonly
* @memberof LevelDBStorage
*/
get instance() {
return this.db;
}
}

export interface LevelStorageOptions extends GenericStorageOptions {
scope?: string;
}

export const createInitializer: StorageInitializerFunction<
LevelDBStorage,
LevelStorageOptions
> =
(options) =>
// eslint-disable-next-line @typescript-eslint/require-await
async (): Promise<LevelDBStorage> => {
return new LevelDBStorage(options);
};
> = (options) => async (): Promise<LevelDBStorage> => {
return new LevelDBStorage(options);
};

0 comments on commit 0afaff8

Please sign in to comment.