Skip to content

Commit

Permalink
#249- only import 'level' when it's actually used (#254)
Browse files Browse the repository at this point in the history
i.e. if a developer provides their own `createLevelDatabase` then we will use that instead of `'level'`

in order to support this for both node and browser, we need to switch to using dynamic `import` (instead of `require`, as that's not defined for node when `"type": "module"`)

most of this commit is changing sync logic to `async` for that purpose (though it also allows for more flexibility when providing a custom `createLevelDatabase`)
  • Loading branch information
dcrousso authored Mar 10, 2023
1 parent beb838f commit 58295c7
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 25 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Decentralized Web Node (DWN) SDK

Code Coverage
![Statements](https://img.shields.io/badge/statements-94.84%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-94.07%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-92.78%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-94.84%25-brightgreen.svg?style=flat)
![Statements](https://img.shields.io/badge/statements-94.86%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-93.59%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-92.82%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-94.86%25-brightgreen.svg?style=flat)

## Introduction

Expand Down
34 changes: 26 additions & 8 deletions src/store/blockstore-level.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@ export class BlockstoreLevel implements Blockstore {
* the {@link https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase IDBDatabase}
* to be opened.
*/
constructor(location: string, config: BlockstoreLevelConfig = {}) {
constructor(config: BlockstoreLevelConfig) {
this.config = {
createLevelDatabase,
...config
};

this.db = this.config.createLevelDatabase<string, Uint8Array>(location, { keyEncoding: 'utf8', valueEncoding: 'binary' });
}

async open(): Promise<void> {
await this.createLevelDatabase();

while (this.db.status === 'opening' || this.db.status === 'closing') {
await sleep(200);
}
Expand All @@ -52,6 +52,10 @@ export class BlockstoreLevel implements Blockstore {
* releases all file handles and locks held by the underlying db.
*/
async close(): Promise<void> {
if (!this.db) {
return;
}

while (this.db.status === 'opening' || this.db.status === 'closing') {
await sleep(200);
}
Expand All @@ -63,23 +67,30 @@ export class BlockstoreLevel implements Blockstore {
return this.db.close();
}

partition(name: string): BlockstoreLevel {
return new BlockstoreLevel('', {
createLevelDatabase: <K, V>(_location: string, options?: LevelDatabaseOptions<K, V>): LevelDatabase<K, V> => {
async partition(name: string): Promise<BlockstoreLevel> {
await this.createLevelDatabase();

return new BlockstoreLevel({
location : '',
createLevelDatabase : async <K, V>(_location: string, options?: LevelDatabaseOptions<K, V>): Promise<LevelDatabase<K, V>> => {
return this.db.sublevel(name, options);
}
});
}

put(key: CID, val: Uint8Array, options?: Options): Promise<void> {
async put(key: CID, val: Uint8Array, options?: Options): Promise<void> {
options?.signal?.throwIfAborted();

await abortOr(options?.signal, this.createLevelDatabase());

return abortOr(options?.signal, this.db.put(key.toString(), val));
}

async get(key: CID, options?: Options): Promise<Uint8Array> {
options?.signal?.throwIfAborted();

await abortOr(options?.signal, this.createLevelDatabase());

try {
const val = await abortOr(options?.signal, this.db.get(key.toString()));
return val;
Expand All @@ -97,9 +108,11 @@ export class BlockstoreLevel implements Blockstore {
return !! await this.get(key, options);
}

delete(key: CID, options?: Options): Promise<void> {
async delete(key: CID, options?: Options): Promise<void> {
options?.signal?.throwIfAborted();

await abortOr(options?.signal, this.createLevelDatabase());

return abortOr(options?.signal, this.db.del(key.toString()));
}

Expand Down Expand Up @@ -127,6 +140,10 @@ export class BlockstoreLevel implements Blockstore {
}
}

private async createLevelDatabase(): Promise<void> {
this.db ??= await this.config.createLevelDatabase<string, Uint8Array>(this.config.location, { keyEncoding: 'utf8', valueEncoding: 'binary' });
}

/**
* deletes all entries
*/
Expand All @@ -148,5 +165,6 @@ export class BlockstoreLevel implements Blockstore {
}

type BlockstoreLevelConfig = {
location: string,
createLevelDatabase?: typeof createLevelDatabase,
};
7 changes: 4 additions & 3 deletions src/store/create-level.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { AbstractDatabaseOptions, AbstractLevel } from 'abstract-level';

import { Level } from 'level';

export type LevelDatabase<K, V> = AbstractLevel<string | Buffer | Uint8Array, K, V>;

export type LevelDatabaseOptions<K, V> = AbstractDatabaseOptions<K, V>;

export function createLevelDatabase<K, V>(location: string, options?: LevelDatabaseOptions<K, V>): LevelDatabase<K, V> {
export async function createLevelDatabase<K, V>(location: string, options?: LevelDatabaseOptions<K, V>): Promise<LevelDatabase<K, V>> {
// Only import `'level'` when it's actually necessary (i.e. only when the default `createLevelDatabase` is used).
// Overriding `createLevelDatabase` will prevent this from happening.
const { Level } = await import('level');
return new Level(location, options);
}
13 changes: 7 additions & 6 deletions src/store/data-store-level.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ export class DataStoreLevel implements DataStore {
...config
};

this.blockstore = new BlockstoreLevel(this.config.blockstoreLocation, {
createLevelDatabase: this.config.createLevelDatabase,
this.blockstore = new BlockstoreLevel({
location : this.config.blockstoreLocation,
createLevelDatabase : this.config.createLevelDatabase,
});
}

Expand All @@ -36,7 +37,7 @@ export class DataStoreLevel implements DataStore {
}

async put(tenant: string, recordId: string, dataStream: Readable): Promise<string> {
const partition = this.blockstore.partition(tenant);
const partition = await this.blockstore.partition(tenant);

const asyncDataBlocks = importer([{ content: dataStream }], partition, { cidVersion: 1 });

Expand All @@ -49,7 +50,7 @@ export class DataStoreLevel implements DataStore {
}

public async get(tenant: string, recordId: string, dataCid: string): Promise<Readable | undefined> {
const partition = this.blockstore.partition(tenant);
const partition = await this.blockstore.partition(tenant);

const cid = CID.parse(dataCid);
const bytes = await partition.get(cid);
Expand Down Expand Up @@ -77,7 +78,7 @@ export class DataStoreLevel implements DataStore {
}

public async has(tenant: string, recordId: string, dataCid: string): Promise<boolean> {
const partition = this.blockstore.partition(tenant);
const partition = await this.blockstore.partition(tenant);

const cid = CID.parse(dataCid);
const rootBlockBytes = await partition.get(cid);
Expand All @@ -86,7 +87,7 @@ export class DataStoreLevel implements DataStore {
}

public async delete(tenant: string, recordId: string, dataCid: string): Promise<void> {
const partition = this.blockstore.partition(tenant);
const partition = await this.blockstore.partition(tenant);

// TODO: Implement data deletion in Records - https://github.com/TBD54566975/dwn-sdk-js/issues/84
const cid = CID.parse(dataCid);
Expand Down
11 changes: 6 additions & 5 deletions src/store/message-store-level.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ export class MessageStoreLevel implements MessageStore {
...config
};

this.blockstore = new BlockstoreLevel(this.config.blockstoreLocation, {
createLevelDatabase: this.config.createLevelDatabase,
this.blockstore = new BlockstoreLevel({
location : this.config.blockstoreLocation,
createLevelDatabase : this.config.createLevelDatabase,
});
}

Expand All @@ -64,7 +65,7 @@ export class MessageStoreLevel implements MessageStore {
async get(tenant: string, cidString: string, options?: Options): Promise<BaseMessage | undefined> {
options?.signal?.throwIfAborted();

const partition = this.blockstore.partition(tenant);
const partition = await abortOr(options?.signal, this.blockstore.partition(tenant));

const cid = CID.parse(cidString);
const bytes = await partition.get(cid, options);
Expand Down Expand Up @@ -106,7 +107,7 @@ export class MessageStoreLevel implements MessageStore {
async delete(tenant: string, cidString: string, options?: Options): Promise<void> {
options?.signal?.throwIfAborted();

const partition = this.blockstore.partition(tenant);
const partition = await abortOr(options?.signal, this.blockstore.partition(tenant));

// TODO: Implement data deletion in Records - https://github.com/TBD54566975/dwn-sdk-js/issues/84
const cid = CID.parse(cidString);
Expand All @@ -124,7 +125,7 @@ export class MessageStoreLevel implements MessageStore {
): Promise<void> {
options?.signal?.throwIfAborted();

const partition = this.blockstore.partition(tenant);
const partition = await abortOr(options?.signal, this.blockstore.partition(tenant));

const encodedMessageBlock = await abortOr(options?.signal, block.encode({ value: message, codec: cbor, hasher: sha256 }));

Expand Down
5 changes: 3 additions & 2 deletions tests/store/message-store.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,15 +175,16 @@ describe('MessageStoreLevel Tests', () => {
it('should be called if provided', async () => {
let called = 0;

new MessageStoreLevel({
const messageStore = new MessageStoreLevel({
blockstoreLocation : 'TEST-BLOCKSTORE',
indexLocation : 'TEST-INDEX',
createLevelDatabase<K, V>(location, options?: LevelDatabaseOptions<K, V>): LevelDatabase<K, V> {
createLevelDatabase<K, V>(location, options?: LevelDatabaseOptions<K, V>): Promise<LevelDatabase<K, V>> {
++called;
expect(location).to.equal('TEST-BLOCKSTORE');
return createLevelDatabase(location, options);
}
});
await messageStore.open();

expect(called).to.equal(1);
});
Expand Down

0 comments on commit 58295c7

Please sign in to comment.