diff --git a/.changeset/tough-donuts-join.md b/.changeset/tough-donuts-join.md new file mode 100644 index 0000000..de824d1 --- /dev/null +++ b/.changeset/tough-donuts-join.md @@ -0,0 +1,5 @@ +--- +'storage-box': minor +--- + +fix: methods are now sync. keyword `await` is no longer needed. diff --git a/README.md b/README.md index 7bda082..1e8e5fb 100644 --- a/README.md +++ b/README.md @@ -28,16 +28,16 @@ import { Client } from 'storage-box'; const client = new Client(); -await client.setex('key', 'value', 2); +client.setex('key', 'value', 2); -console.log(await client.get('key')); // value +console.log(client.get('key')); // value // time to live in milliseconds -console.log(await client.ttl('key', true)); // 2000 +console.log(client.ttl('key', true)); // 2000 // after 3 seconds -setTimeout(async () => { - console.log(await client.get('key')); // undefined +setTimeout(() => { + console.log(client.get('key')); // undefined }, 3e3); ``` diff --git a/docs/driver-browser.md b/docs/driver-browser.md index 1f74e86..38c745b 100644 --- a/docs/driver-browser.md +++ b/docs/driver-browser.md @@ -11,12 +11,12 @@ import { BrowserDriver } from 'storage-box/browser'; const driver = new BrowserDriver('local'); const client = new Client(driver); -await client.setex('key', 'value', 5); +client.setex('key', 'value', 5); -const value = await client.get('key'); +const value = client.get('key'); console.log(value); -setTimeout(async () => { - console.log(await client.get('key')); // undefined +setTimeout(() => { + console.log(client.get('key')); // undefined }, 5000); ``` diff --git a/docs/driver-memory.md b/docs/driver-memory.md index de3ebfd..facb7cf 100644 --- a/docs/driver-memory.md +++ b/docs/driver-memory.md @@ -60,38 +60,38 @@ const c = new Client(); ##### set ```typescript -await client.set('key', 'value'); +client.set('key', 'value'); ``` ##### get ```typescript -const value = await client.get('key'); +const value = client.get('key'); ``` ##### getall ```typescript -const objs = await client.getall(); +const objs = client.getall(); ``` ##### delete ```typescript -await client.del('key'); +client.del('key'); ``` ##### clear ```typescript -await client.clear(); +client.clear(); ``` ##### exists ```typescript -const exists = await client.exists('key'); -const has = await client.has('key'); // has is an alias for exists +const exists = client.exists('key'); +const has = client.has('key'); // has is an alias for exists ``` ##### size @@ -99,19 +99,19 @@ const has = await client.has('key'); // has is an alias for exists Length of the storage ```typescript -const size = await client.size(); +const size = client.size(); ``` ##### keys ```typescript -const keys = await client.keys(); +const keys = client.keys(); ``` ##### values ```typescript -const values = await client.values(); +const values = client.values(); ``` ## Time-based Key Expiration @@ -119,7 +119,7 @@ const values = await client.values(); ##### Setex ```typescript -await client.setex('key', 'value', 10); // 10 seconds +client.setex('key', 'value', 10); // 10 seconds ``` ##### TTL @@ -127,8 +127,8 @@ await client.setex('key', 'value', 10); // 10 seconds Returns the remaining time in seconds ```typescript -const ttl = await client.ttl('key'); -const ttlMs = await client.ttl('key', true); // Returns the remaining time in milliseconds +const ttl = client.ttl('key'); +const ttlMs = client.ttl('key', true); // Returns the remaining time in milliseconds ``` ## Hash Operations @@ -161,7 +161,7 @@ class CuteMap extends HashMap { await this.set(randId, user); } - async initials(): Promise { + async initials(): string[] { const all = await this.getall(); return Object.values(all).map((u) => `${u.first[0]}${u.last[0]}`); } @@ -179,59 +179,59 @@ expect(initials).to.have.members(['MJ', 'PP']); ### hset ```typescript -await client.hset('key', 'field', 'value'); +client.hset('key', 'field', 'value'); ``` ### hget ```typescript -const value = await client.hget('key', 'field'); +const value = client.hget('key', 'field'); ``` ### hgetall ```typescript -const map = await client.hgetall('key'); +const map = client.hgetall('key'); ``` ### hsetex ```typescript -await client.hsetex('key', 'field', 'value', 10); // 10 seconds +client.hsetex('key', 'field', 'value', 10); // 10 seconds ``` ### hkeys ```typescript -const keys = await client.hkeys('key'); +const keys = client.hkeys('key'); ``` ### hvalues ```typescript -const values = await client.hvalues('key'); +const values = client.hvalues('key'); ``` ### hdel ```typescript -await client.hdel('key', 'field'); +client.hdel('key', 'field'); ``` ### hexists ```typescript -const exists = await client.hexists('key', 'field'); +const exists = client.hexists('key', 'field'); ``` ### hsize ```typescript -const size = await client.hsize('key'); +const size = client.hsize('key'); ``` ### hclear ```typescript -await client.hclear('key'); +client.hclear('key'); ``` diff --git a/package.json b/package.json index 7a1ad19..acb6731 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "lint:fix": "eslint --fix .", "format:check": "prettier --check .", "format": "prettier --write .", - "ci:publish": "pnpm build && changeset publish", + "ci:publish": "changeset publish", "prepublishOnly": "pnpm test && pnpm lint && pnpm format:check && pnpm build" }, "packageManager": "pnpm@8.15.8", diff --git a/src/client.test.ts b/src/client.test.ts index 0c9f57e..59ff67a 100644 --- a/src/client.test.ts +++ b/src/client.test.ts @@ -3,66 +3,66 @@ import { expect } from 'chai'; import { Client } from '@/client'; describe('List operations', () => { - const c = new Client(); - beforeEach(async () => { - await c.clear(); + const client = new Client(); + beforeEach(() => { + client.clear(); }); - it('List - Get entry values in list', async () => { - await c.lpush('foo', 'bar'); - await c.lpush('foo', 'foo'); - await c.lpush('foo', 'baz'); - expect(await c.lgetall('foo')).to.have.members(['baz', 'foo', 'bar']); + it('List - Get entry values in list', () => { + client.lpush('foo', 'bar'); + client.lpush('foo', 'foo'); + client.lpush('foo', 'baz'); + expect(client.lgetall('foo')).to.have.members(['baz', 'foo', 'bar']); }); - it('Clear - Reset list to empty state', async () => { - await c.set('foo', 'bar'); - await c.set('bar', 'foo'); - await c.clear(); - expect(await c.keys()).to.be.empty; + it('Clear - Reset list to empty state', () => { + client.set('foo', 'bar'); + client.set('bar', 'foo'); + client.clear(); + expect(client.keys()).to.be.empty; }); - it('List set', async () => { - await c.lpush('foo', 'bar'); - await c.lpush('foo', 'foo'); - await c.lpush('foo', 'baz'); - await c.lset('foo', 1, 'bar'); - expect(await c.lgetall('foo')).to.have.members(['baz', 'bar', 'bar']); + it('List set', () => { + client.lpush('foo', 'bar'); + client.lpush('foo', 'foo'); + client.lpush('foo', 'baz'); + client.lset('foo', 1, 'bar'); + expect(client.lgetall('foo')).to.have.members(['baz', 'bar', 'bar']); }); - it('List get', async () => { - await c.lpush('foo', 'bar'); - await c.lpush('foo', 'foo'); - await c.lpush('foo', 'baz'); - expect(await c.lget('foo', 1)).to.equal('foo'); + it('List get', () => { + client.lpush('foo', 'bar'); + client.lpush('foo', 'foo'); + client.lpush('foo', 'baz'); + expect(client.lget('foo', 1)).to.equal('foo'); }); - it('List range', async () => { - await c.lpush('foo', 'bar'); - await c.lpush('foo', 'foo'); - await c.lpush('foo', 'baz'); - expect(await c.lgetall('foo')).to.have.members(['baz', 'foo', 'bar']); - expect(await c.lrange('foo', 0, 2)).to.have.members(['foo', 'bar']); + it('List range', () => { + client.lpush('foo', 'bar'); + client.lpush('foo', 'foo'); + client.lpush('foo', 'baz'); + expect(client.lgetall('foo')).to.have.members(['baz', 'foo', 'bar']); + expect(client.lrange('foo', 0, 2)).to.have.members(['foo', 'bar']); }); }); describe('Hash operations', () => { - const c = new Client(); - beforeEach(async () => { - await c.clear(); + const client = new Client(); + beforeEach(() => { + client.clear(); }); - it('Hash set and get', async () => { - await c.hset('foo', 'bar', 'baz'); - expect(await c.hget('foo', 'bar')).to.equal('baz'); + it('Hash set and get', () => { + client.hset('foo', 'bar', 'baz'); + expect(client.hget('foo', 'bar')).to.equal('baz'); }); - it('Hash get all', async () => { - await c.hset('foo', 'bar', 'baz'); - await c.hset('foo', 'foo', 'bar'); - await c.hset('foo', 'bar', 'baz'); + it('Hash get all', () => { + client.hset('foo', 'bar', 'baz'); + client.hset('foo', 'foo', 'bar'); + client.hset('foo', 'bar', 'baz'); - const values = await c.hgetall('foo'); + const values = client.hgetall('foo'); const expected = { bar: 'baz', @@ -76,10 +76,10 @@ describe('Hash operations', () => { } }); - it('Hash delete', async () => { - await c.hset('foo', 'bar', 'baz'); - await c.hdel('foo', 'bar'); + it('Hash delete', () => { + client.hset('foo', 'bar', 'baz'); + client.hdel('foo', 'bar'); - expect(await c.hget('foo', 'bar')).to.be.null; + expect(client.hget('foo', 'bar')).to.be.null; }); }); diff --git a/src/client.ts b/src/client.ts index 0caa254..17d7d34 100644 --- a/src/client.ts +++ b/src/client.ts @@ -12,130 +12,89 @@ import type { SerializableList, StorageDriver, StorageOperations, - StorageState, } from '@/typings'; class Client implements StorageOperations { - private readonly _drive: StorageDriver; - - private readonly _ttl: Map = new Map(); - private _state: StorageState = 'stale'; + readonly #drive: StorageDriver; + readonly #ttl: Map = new Map(); constructor(storage?: Driver) { - this._drive = storage || new MemoryDriver(); - this._prepare().finally(); - } - - private async _prepare() { - this._state = 'pending'; - - if (this._drive.prepare) { - await this._drive.prepare(); - } - - await this._load_ttl(); - this._state = 'ready'; + this.#drive = storage || new MemoryDriver(); + this.#load_ttl(); } - private async _preparedDriver() { - if (this._state === 'ready') { - return; - } - - // Wait til driver is ready - return new Promise((resolve) => { - const intervalId = setInterval(() => { - if (this._state === 'stale') { - this._state = 'pending'; - this._prepare().finally(); - } else if (this._state === 'ready') { - clearInterval(intervalId); - resolve(); - } - }, 1); - }); - } - - async getall(): Promise { - await this._preparedDriver(); + getall(): HashRecord { const record: HashRecord = {}; - for (const key of await this._drive.keys()) { - record[key] = await this.get(key); + for (const key of this.#drive.keys()) { + record[key] = this.get(key); } return record; } - async get(key: HashField): Promise { - await this._preparedDriver(); - - const ttl = this._ttl.get(key); + get(key: HashField): Value | null { + const ttl = this.#ttl.get(key); // Return if ttl is not set or not expired if (!ttl || ttl.dat > Date.now()) { - return ((await this._drive.get(key)) as Value) ?? null; + const val = this.#drive.get(key) as Value; + return val ?? null; } if (ttl.type === 'key') { - await this.del(key); + this.del(key); } if (ttl.type === 'list') { // Delete key from TTL list due to TTL mismatch - this._ttl.delete(key); + this.#ttl.delete(key); } return null; } - async set(key: HashKey, value: Serializable | null) { - await this._preparedDriver(); - await this._drive.set(key, value); + set(key: HashKey, value: Serializable | null) { + this.#drive.set(key, value); } - async del(key: HashKey) { - await this._preparedDriver(); - await this._drive.del(key); + del(key: HashKey) { + this.#drive.del(key); } - async exists(key: HashKey) { - await this._preparedDriver(); - return this._drive.exists(key); + exists(key: HashKey) { + return this.#drive.exists(key); } - async has(key: HashKey) { + has(key: HashKey) { return this.exists(key); } - async keys(): Promise { - await this._preparedDriver(); - return (await this._drive.keys()) as Key[]; + keys(): Key[] { + return this.#drive.keys() as Key[]; } - async values() { - await this._preparedDriver(); + values() { const vals: Value[] = []; - for (const key of await this._drive.keys()) { - vals.push((await this.get(key)) as Value); + for (const key of this.#drive.keys()) { + vals.push(this.get(key) as Value); } return vals; } - async clear() { - await this._preparedDriver(); - await this._drive.clear(); + clear() { + this.#drive.clear(); } //// // Hash operations //// - private async _get_hash(key: HashKey) { - if (!(await this._drive.exists(key))) { - await this._drive.set(key, {}); + #get_hash(key: HashKey): HashRecord { + if (!this.#drive.exists(key)) { + this.#drive.set(key, {}); return {}; } - const map = (await this._drive.get(key)) as Record; + const map = this.#drive.get(key) as HashRecord | null; if (!map) { return {}; } @@ -147,71 +106,58 @@ class Client implements StorageOper return map; } - async hget(key: HashKey, field: HashField) { - await this._preparedDriver(); - - const map = await this._get_hash(key); + hget(key: HashKey, field: HashField) { + const map = this.#get_hash(key); return map[field] ?? null; } - async hset(key: HashKey, field: HashField, value: Serializable) { - await this._preparedDriver(); - const map = await this._get_hash(key); + hset(key: HashKey, field: HashField, value: Serializable) { + const map = this.#get_hash(key); map[field] = value; - await this._drive.set(key, map); + this.#drive.set(key, map); } - async hsetex(key: HashKey, field: HashField, value: Serializable, seconds: number) { - await this._preparedDriver(); - const map = await this._get_hash(key); + hsetex(key: HashKey, field: HashField, value: Serializable, seconds: number) { + const map = this.#get_hash(key); map[field] = value; - await this._drive.set(key, map); + this.#drive.set(key, map); const secs = seconds * 1000; const delAt = Date.now() + secs; - await this._create_hdel_timout(key, field, delAt); + this.#create_hdel_timout(key, field, delAt); } - async hkeys(key: HashKey) { - await this._preparedDriver(); - const map = await this._get_hash(key); + hkeys(key: HashKey) { + const map = this.#get_hash(key); return Object.keys(map); } - async hvalues(key: HashKey) { - await this._preparedDriver(); - const map = await this._get_hash(key); + hvalues(key: HashKey) { + const map = this.#get_hash(key); return Object.values(map); } - async hdel(key: HashKey, field: HashField) { - await this._preparedDriver(); - const map = await this._get_hash(key); + hdel(key: HashKey, field: HashField) { + const map = this.#get_hash(key); delete map[field]; - await this._drive.set(key, map); + this.#drive.set(key, map); } - async hexists(key: HashKey, field: HashField) { - await this._preparedDriver(); - const map = await this._get_hash(key); + hexists(key: HashKey, field: HashField) { + const map = this.#get_hash(key); return field in map; } - async hsize(key: HashKey) { - await this._preparedDriver(); - const map = await this._get_hash(key); + hsize(key: HashKey) { + const map = this.#get_hash(key); return Object.keys(map).length; } - async hclear(key: HashKey) { - await this._preparedDriver(); - await this._drive.set(key, {}); + hclear(key: HashKey) { + this.#drive.set(key, {}); } - async hgetall( - key: string - ): Promise> { - await this._preparedDriver(); - const map = await this._get_hash(key); + hgetall(key: string): HashRecord { + const map = this.#get_hash(key); return map as HashRecord; } @@ -249,12 +195,12 @@ class Client implements StorageOper // List operations //// - private async _get_list(key: HashField): Promise { - if (!(await this._drive.exists(key))) { - await this._drive.set(key, []); + #get_list(key: HashField): SerializableList { + if (!this.#drive.exists(key)) { + this.#drive.set(key, []); } - const list = await this._drive.get(key); // Not using this.get() to avoid TTL check + const list = this.#drive.get(key); // Not using this.get() to avoid TTL check if (!list) { return []; } @@ -266,59 +212,59 @@ class Client implements StorageOper return list as SerializableList; } - async lgetall(key: HashKey) { - return this._get_list(key); + lgetall(key: HashKey) { + return this.#get_list(key); } - async lset(key: HashField, index: number, value: Serializable | null) { - const list = await this._get_list(key); + lset(key: HashField, index: number, value: Serializable | null) { + const list = this.#get_list(key); list[index] = value; - await this._drive.set(key, list); + this.#drive.set(key, list); } - async lget(key: HashField, index: number): Promise { - const ttl = this._ttl.get(key); + lget(key: HashField, index: number): HashValue | null { + const ttl = this.#ttl.get(key); if (!ttl || ttl.dat > Date.now()) { - const list = await this._get_list(key); + const list = this.#get_list(key); return list[index] as HashValue; } if (ttl.type === 'list') { - await this.lset(key, ttl.index, null); + this.lset(key, ttl.index, null); } if (ttl.type === 'key') { // TTL mismatch with the key, delete the key from the TTL list - this._ttl.delete(key); + this.#ttl.delete(key); } return null; } - async ldel(key: HashKey, index: number) { - const list = await this._get_list(key); + ldel(key: HashKey, index: number) { + const list = this.#get_list(key); list.splice(index, 1); - await this._drive.set(key, list); + this.#drive.set(key, list); } - async lpush(key: HashKey, value: Serializable): Promise { - const list = await this._get_list(key); + lpush(key: HashKey, value: Serializable): void { + const list = this.#get_list(key); list.push(value); - await this._drive.set(key, list); + this.#drive.set(key, list); } - async lpushex(key: HashKey, value: Serializable, seconds: number): Promise { - const list = await this._get_list(key); + lpushex(key: HashKey, value: Serializable, seconds: number): void { + const list = this.#get_list(key); list.push(value); - await this._drive.set(key, list); + this.#drive.set(key, list); const secs = seconds * 1000; const delAt = Date.now() + secs; - await this._create_ldel_timout(key, list.length - 1, delAt); + this.#create_ldel_timout(key, list.length - 1, delAt); } - async lexists(key: HashKey, value: Serializable): Promise { - const list = await this._get_list(key); + lexists(key: HashKey, value: Serializable): boolean { + const list = this.#get_list(key); return list.includes(value); } @@ -327,18 +273,18 @@ class Client implements StorageOper * * @param key - The key of the list. */ - async lpop(key: HashKey): Promise { - const list = await this._get_list(key); + lpop(key: HashKey): HashValue | null { + const list = this.#get_list(key); return list.pop() ?? null; } - async lsize(key: HashKey) { - const list = await this._get_list(key); + lsize(key: HashKey) { + const list = this.#get_list(key); return list.length; } - async lclear(key: HashKey) { - await this._drive.set(key, []); + lclear(key: HashKey) { + this.#drive.set(key, []); } /** @@ -348,109 +294,107 @@ class Client implements StorageOper * @param start - The index to start the slice at. * @param stop - The index to end the slice at. */ - async lrange(key: HashKey, start: number, stop: number): Promise { - const list = await this._get_list(key); + lrange(key: HashKey, start: number, stop: number): HashValue[] { + const list = this.#get_list(key); return list.slice(start, stop); } - private async _load_ttl() { - const ttlList = (await this._drive.get(TTL_LIST_KEY)) as SerializedTTL[] | undefined; + #load_ttl() { + const ttlList = this.#drive.get(TTL_LIST_KEY) as SerializedTTL[] | undefined; if (!ttlList || !Array.isArray(ttlList)) { // TTL list malformed, clear it - return this._drive.del(TTL_LIST_KEY); + return this.#drive.del(TTL_LIST_KEY); } - await Promise.all( - ttlList.map(async ({ key, dat, ...rest }) => { - // If the key does not exist, remove it from the TTL list - if (!(await this._drive.exists(key))) { - this._ttl.delete(key); - return; - } - - // If the key has already expired, remove it from the TTL list - if (!dat || dat < Date.now()) { - await this._drive.del(key); - this._ttl.delete(key); - return; - } - - this._ttl.set(key, { - dat, - ...rest, - }); - - // Set the timeout for the key - switch (rest.type) { - case 'key': - await this._create_del_timout(key, dat); - break; - case 'list': - await this._create_ldel_timout(key, rest.index, dat); - break; - case 'hash': - await this._create_hdel_timout(key, rest.field, dat); - break; - default: - throw new Error('TTL malformed at key: ' + key); - } - }) - ); + ttlList.map(({ key, dat, ...rest }) => { + // If the key does not exist, remove it from the TTL list + if (!this.#drive.exists(key)) { + this.#ttl.delete(key); + return; + } + + // If the key has already expired, remove it from the TTL list + if (!dat || dat < Date.now()) { + this.#drive.del(key); + this.#ttl.delete(key); + return; + } + + this.#ttl.set(key, { + dat, + ...rest, + }); + + // Set the timeout for the key + switch (rest.type) { + case 'key': + this.#create_del_timout(key, dat); + break; + case 'list': + this.#create_ldel_timout(key, rest.index, dat); + break; + case 'hash': + this.#create_hdel_timout(key, rest.field, dat); + break; + default: + throw new Error('TTL malformed at key: ' + key); + } + }); } - private async _create_del_timout(key: HashField, dat: number) { + #create_del_timout(key: HashField, dat: number) { const timeLeft = dat - Date.now(); setTimeout(() => { - this._drive.del(key); - this._ttl.delete(key); + this.#drive.del(key); + this.#ttl.delete(key); }, timeLeft); - this._ttl.set(key, { + this.#ttl.set(key, { type: 'key', dat, }); - await this._update_ttl_list(); + this.#update_ttl_list(); } - private async _create_ldel_timout(key: HashField, index: number, dat: number) { + #create_ldel_timout(key: HashField, index: number, dat: number) { const timeLeft = dat - Date.now(); setTimeout(() => { this.lset(key, index, null); - this._ttl.delete(key); + this.#ttl.delete(key); }, timeLeft); - this._ttl.set(key, { + this.#ttl.set(key, { type: 'list', index, dat, }); - await this._update_ttl_list(); + this.#update_ttl_list(); } - private async _create_hdel_timout(key: HashField, field: HashField, dat: number) { + #create_hdel_timout(key: HashField, field: HashField, dat: number) { const timeLeft = dat - Date.now(); setTimeout(() => { this.hset(key, field, null); - this._ttl.delete(key); + this.#ttl.delete(key); }, timeLeft); - this._ttl.set(key, { + this.#ttl.set(key, { type: 'hash', field, dat, }); - await this._update_ttl_list(); + this.#update_ttl_list(); } - private async _update_ttl_list() { + #update_ttl_list() { const ttlList: SerializedTTL[] = []; - this._ttl.forEach((ttl, key) => { + this.#ttl.forEach((ttl, key) => { ttlList.push(Object.assign(ttl, { key })); }); - await this.set(TTL_LIST_KEY, ttlList); + this.set(TTL_LIST_KEY, ttlList); } /** @@ -460,11 +404,11 @@ class Client implements StorageOper * @param value * @param seconds */ - async setex(key: HashKey, value: Serializable, seconds: number) { - await this._drive.set(key, value); + setex(key: HashKey, value: Serializable, seconds: number) { + this.#drive.set(key, value); const secs = seconds * 1000; const delAt = Date.now() + secs; - await this._create_del_timout(key, delAt); + this.#create_del_timout(key, delAt); } /** @@ -475,13 +419,13 @@ class Client implements StorageOper * @param value * @param seconds */ - async lsetex(key: HashKey, index: number, value: Serializable, seconds: number) { - const list = await this._get_list(key); + lsetex(key: HashKey, index: number, value: Serializable, seconds: number) { + const list = this.#get_list(key); list[index] = value; - await this._drive.set(key, list); + this.#drive.set(key, list); const secs = seconds * 1000; const delAt = Date.now() + secs; - await this._create_ldel_timout(key, index, delAt); + this.#create_ldel_timout(key, index, delAt); } /** @@ -492,8 +436,8 @@ class Client implements StorageOper * @param key * @param milliseconds If true, returns the remaining time in milliseconds. */ - async ttl(key: HashKey, milliseconds?: boolean) { - const item = this._ttl.get(key); + ttl(key: HashKey, milliseconds?: boolean) { + const item = this.#ttl.get(key); if (!item) { return -1; } diff --git a/src/driver/browser.ts b/src/driver/browser.ts index 12c42af..b1f317c 100644 --- a/src/driver/browser.ts +++ b/src/driver/browser.ts @@ -36,11 +36,11 @@ export default class BrowserDriver { + get(key: Key): Value | null { return this._storage.getItem(key) as Value | null; } - async set(key: Key, value: Value): Promise { + set(key: Key, value: Value): void { // If value was undefined or null we should remove the key if (value === undefined || value === null) { return this.del(key); @@ -49,26 +49,26 @@ export default class BrowserDriver { + del(key: Key): void { this._storage.removeItem(key); } - async exists(key: Key): Promise { + exists(key: Key): boolean { return this._storage.getItem(key) !== null; } - async keys(): Promise { + keys(): Key[] { return Object.keys(this._storage) as Key[]; } - async values(): Promise { + values(): Value[] { return Object.values(this._storage) as Value[]; } /** * Clears the storage. Please be careful with this method. */ - async clear(): Promise { + clear(): void { this._storage.clear(); } } diff --git a/src/driver/fs.test.ts b/src/driver/fs.test.ts index a89fc5e..bed2b85 100644 --- a/src/driver/fs.test.ts +++ b/src/driver/fs.test.ts @@ -18,19 +18,19 @@ describe('Fs-based storage', () => { }); after(async () => { - await promises.unlink(filePath).catch(() => {}); + promises.unlink(filePath).catch(() => {}); }); - it('Set and get', async () => { - await client.set('foo', 'bar'); - await client.set('bar', 'baz'); - expect(await client.get('foo')).to.equal('bar'); + it('Set and get', () => { + client.set('foo', 'bar'); + client.set('bar', 'baz'); + expect(client.get('foo')).to.equal('bar'); }); - it('Delete', async () => { - await client.set('foo', 'bar'); - await client.del('foo'); - expect(await client.get('foo')).to.be.null; + it('Delete', () => { + client.set('foo', 'bar'); + client.del('foo'); + expect(client.get('foo')).to.be.null; }); describe('Time-based', () => { @@ -45,14 +45,14 @@ describe('Fs-based storage', () => { const drive = new FsDriver(filePath); const client = new Client(drive); - await client.setex('foo', 'bar', 2); + client.setex('foo', 'bar', 2); } await sleep(2001); { const drive = new FsDriver(filePath); const client = new Client(drive); - expect(await client.exists('foo')).to.false; + expect(client.exists('foo')).to.false; } }); }); @@ -71,20 +71,20 @@ describe('Fs-based storage', () => { await promises.unlink(filePath).catch(() => {}); }); - it('Set and get', async () => { - await client.set('foo', 'bar'); - await client.set('bar', 'baz'); - expect(await client.get('foo')).to.equal('bar'); + it('Set and get', () => { + client.set('foo', 'bar'); + client.set('bar', 'baz'); + expect(client.get('foo')).to.equal('bar'); }); - it('Delete', async () => { - await client.setex('foo', 'bar', 1); - await client.setex('bar', 'baz', 1); - await client.set('foo', 'bar'); + it('Delete', () => { + client.setex('foo', 'bar', 1); + client.setex('bar', 'baz', 1); + client.set('foo', 'bar'); - await client.del('foo'); + client.del('foo'); - expect(await client.get('foo')).to.be.null; + expect(client.get('foo')).to.be.null; }); }); }); diff --git a/src/driver/fs.ts b/src/driver/fs.ts index a3300e4..825c8d7 100644 --- a/src/driver/fs.ts +++ b/src/driver/fs.ts @@ -1,4 +1,4 @@ -import { promises } from 'node:fs'; +import { mkdirSync, readFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import process from 'node:process'; import debounce, { type DebouncedFunction } from 'debounce'; @@ -20,7 +20,7 @@ export default class FsDriver extends MemoryDriver { private readonly _writer: FileWriter; private readonly _parser: IStorageParser; private readonly _debounceTime: number; - private readonly _bouncyWriteFn: DebouncedFunction<() => Promise>; + private readonly _bouncyWriteFn: DebouncedFunction<() => void>; private readonly _encoding: BufferEncoding; constructor(path: string, opts: FsOptions = {}) { @@ -33,17 +33,15 @@ export default class FsDriver extends MemoryDriver { this._bouncyWriteFn = debounce(this.write, this._debounceTime); this._encoding = opts.encoding || 'utf-8'; this._writer = new FileWriter(this._path, { encoding: this._encoding }); - } - async prepare() { // Try to create a recursive const fileDir = dirname(this._path); - if (!(await access(fileDir))) { - await promises.mkdir(fileDir, { recursive: true }); + if (!access(fileDir)) { + mkdirSync(fileDir, { recursive: true }); } - if (await access(this._path)) { - const rawData = await promises.readFile(this._path, this._encoding); + if (access(this._path)) { + const rawData = readFileSync(this._path, this._encoding); const parser = this._parser; const _storage = rawData === '' ? new Map() : parser.parse(rawData); @@ -57,7 +55,7 @@ export default class FsDriver extends MemoryDriver { }); } - process.on('beforeExit', async () => { + process.on('beforeExit', () => { this._bouncyWriteFn.flush(); }); } @@ -67,18 +65,18 @@ export default class FsDriver extends MemoryDriver { await this._writer.write(data); } - override async set(key: string, value: Serializable): Promise { - await super.set(key, value); + override set(key: string, value: Serializable): void { + super.set(key, value); this._bouncyWriteFn(); } - override async del(key: string): Promise { - await super.del(key); + override del(key: string): void { + super.del(key); this._bouncyWriteFn(); } - override async clear(): Promise { - await super.clear(); + override clear(): void { + super.clear(); this._bouncyWriteFn(); } } diff --git a/src/driver/memory.test.ts b/src/driver/memory.test.ts index 14579ff..d21a071 100644 --- a/src/driver/memory.test.ts +++ b/src/driver/memory.test.ts @@ -6,91 +6,91 @@ import { sleep } from '@/tests/utils'; describe('In-Memory', () => { const client = new Client(); - beforeEach(async () => { - await client.clear(); + beforeEach(() => { + client.clear(); }); - it('Set and get', async () => { - await client.set('foo', 'bar'); - const value = await client.get('foo'); + it('Set and get', () => { + client.set('foo', 'bar'); + const value = client.get('foo'); expect(value).to.equal('bar'); }); - it('Delete', async () => { - await client.set('foo', 'bar'); - await client.del('foo'); + it('Delete', () => { + client.set('foo', 'bar'); + client.del('foo'); - const value = await client.get('foo'); + const value = client.get('foo'); expect(value).to.be.null; }); - it('Exists', async () => { - await client.set('foo', 'bar'); - expect(await client.has('foo')).to.be.true; + it('Exists', () => { + client.set('foo', 'bar'); + expect(client.has('foo')).to.be.true; - await client.del('foo'); - expect(await client.exists('foo')).to.be.false; + client.del('foo'); + expect(client.exists('foo')).to.be.false; }); - it('Keys', async () => { - await client.set('foo', 'bar'); - await client.set('bar', 'foo'); - expect(await client.keys()).to.have.members(['foo', 'bar']); + it('Keys', () => { + client.set('foo', 'bar'); + client.set('bar', 'foo'); + expect(client.keys()).to.have.members(['foo', 'bar']); }); describe('Time-based', () => { const client = new Client(); - beforeEach(async () => { - await client.clear(); + beforeEach(() => { + client.clear(); }); it('Set and get', async () => { - await client.setex('foo', 'bar', 3); - expect(await client.get('foo')).to.equal('bar'); + client.setex('foo', 'bar', 3); + expect(client.get('foo')).to.equal('bar'); await sleep(1000); - expect(await client.get('foo')).to.equal('bar'); + expect(client.get('foo')).to.equal('bar'); await sleep(2000); - expect(await client.get('foo')).to.be.null; + expect(client.get('foo')).to.be.null; }); it('List - set and get', async () => { - await client.lpushex('list', 'bar', 2); - await client.lpush('list', 'foo'); - await client.lpush('list', 'baz'); - await client.lsetex('list', 1, 'bar', 1); + client.lpushex('list', 'bar', 2); + client.lpush('list', 'foo'); + client.lpush('list', 'baz'); + client.lsetex('list', 1, 'bar', 1); - expect(await client.lget('list', 1)).to.equal('bar'); + expect(client.lget('list', 1)).to.equal('bar'); await sleep(1000); - expect(await client.lget('list', 1)).to.be.null; + expect(client.lget('list', 1)).to.be.null; await sleep(1000); - expect(await client.lget('list', 0)).to.be.null; + expect(client.lget('list', 0)).to.be.null; }); it('Hash - Time-based set and get', async () => { - await client.hsetex('foo', 'field', 'bar', 1); - expect(await client.hget('foo', 'field')).to.equal('bar'); + client.hsetex('foo', 'field', 'bar', 1); + expect(client.hget('foo', 'field')).to.equal('bar'); await sleep(1100); - expect(await client.hget('foo', 'field')).to.be.null; + expect(client.hget('foo', 'field')).to.be.null; }); it('should get the key TTL', async () => { - await client.setex('foo', 'bar', 2); - expect(await client.ttl('foo')).to.be.greaterThanOrEqual(0); + client.setex('foo', 'bar', 2); + expect(client.ttl('foo')).to.be.greaterThanOrEqual(0); await sleep(2100); - expect(await client.ttl('foo')).to.equal(-1); + expect(client.ttl('foo')).to.equal(-1); }); it('should get TTL in milliseconds', async () => { - await client.setex('foo', 'bar', 2); - expect(await client.ttl('foo', true)).to.be.greaterThanOrEqual(1000); + client.setex('foo', 'bar', 2); + expect(client.ttl('foo', true)).to.be.greaterThanOrEqual(1000); await sleep(2100); - expect(await client.ttl('foo', true)).to.equal(-1); + expect(client.ttl('foo', true)).to.equal(-1); }); }); }); diff --git a/src/driver/memory.ts b/src/driver/memory.ts index 3315975..2a68c2e 100644 --- a/src/driver/memory.ts +++ b/src/driver/memory.ts @@ -17,38 +17,38 @@ export default class MemoryDriver< } } - async get(key: Key): Promise { - const hasKey = await this.exists(key); + get(key: Key): Value | null { + const hasKey = this.exists(key); if (!hasKey) { return null; } return this._storage[key]; } - async set(key: Key, value: Value) { + set(key: Key, value: Value) { this._storage[key] = value; } - async del(key: Key) { - const hasKey = await this.exists(key); + del(key: Key) { + const hasKey = this.exists(key); if (hasKey) { delete this._storage[key]; } } - async exists(key: Key) { + exists(key: Key) { return key in this._storage; } - async keys(): Promise { + keys(): Key[] { return Object.keys(this._storage) as Key[]; } - async values(): Promise { + values(): Value[] { return Object.values(this._storage); } - async clear() { + clear() { this._storage = {} as HashRecord; } } diff --git a/src/hash-map.test.ts b/src/hash-map.test.ts index 35b909e..6b32e60 100644 --- a/src/hash-map.test.ts +++ b/src/hash-map.test.ts @@ -10,13 +10,13 @@ interface CuteUser extends JsonObject { } class CuteMap extends HashMap { - async addUser(user: CuteUser) { + addUser(user: CuteUser) { const randId = Math.random().toString(36).slice(2); - await this.set(randId, user); + this.set(randId, user); } - async initials(): Promise { - const all = await this.getall(); + initials(): string[] { + const all = this.getall(); return Object.values(all).map((u) => `${u.first[0]}${u.last[0]}`); } } @@ -36,13 +36,13 @@ describe('HashMap', () => { }); describe('CuteMap', () => { - it('get initials', async () => { + it('get initials', () => { const cuties = c.createHashMap('CuteHub', CuteMap); - await cuties.addUser({ first: 'Mary', last: 'Jane' }); - await cuties.addUser({ first: 'Peter', last: 'Parker' }); + cuties.addUser({ first: 'Mary', last: 'Jane' }); + cuties.addUser({ first: 'Peter', last: 'Parker' }); - const initials = await cuties.initials(); + const initials = cuties.initials(); expect(initials).to.have.members(['MJ', 'PP']); }); diff --git a/src/hash-map.ts b/src/hash-map.ts index 13a5fc8..12a18ed 100644 --- a/src/hash-map.ts +++ b/src/hash-map.ts @@ -9,43 +9,43 @@ export class HashMap { + get(key: Key): Value | null { return this._client.hget(this._key, key) as any; } - set(key: Key, value: Value): Promise { + set(key: Key, value: Value): void { return this._client.hset(this._key, key, value); } - setex(key: Key, value: Value, seconds: number): Promise { + setex(key: Key, value: Value, seconds: number): void { return this._client.hsetex(this._key, key, value, seconds); } - del(key: Key): Promise { + del(key: Key): void { return this._client.hdel(this._key, key); } - exists(key: Key): Promise { + exists(key: Key): boolean { return this._client.hexists(this._key, key); } - has(key: Key): Promise { + has(key: Key): boolean { return this._client.hexists(this._key, key); } - keys(): Promise { - return this._client.hkeys(this._key) as Promise; + keys(): Key[] { + return this._client.hkeys(this._key) as Key[]; } - values(): Promise { - return this._client.hvalues(this._key) as Promise; + values(): Value[] { + return this._client.hvalues(this._key) as Value[]; } - clear(): Promise { + clear(): void { return this._client.hclear(this._key); } - getall(): Promise> { + getall(): HashRecord { return this._client.hgetall(this._key); } } diff --git a/src/index.ts b/src/index.ts index 119bc5c..ffc1be5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ export { Client } from './client'; export { HashMap } from './hash-map'; +export { List } from './list'; // ----------- diff --git a/src/list.test.ts b/src/list.test.ts index 58b3e59..e863384 100644 --- a/src/list.test.ts +++ b/src/list.test.ts @@ -7,8 +7,8 @@ import { sleep } from '@/tests/utils'; const c = new Client(); class LuckyNumberList extends List { - async median(): Promise { - const numbers = await this.toArray(); + median(): number { + const numbers = this.toArray(); numbers.sort((a, b) => a - b); @@ -17,24 +17,24 @@ class LuckyNumberList extends List { } describe('List', () => { - it('Baseless', async () => { + it('Baseless', () => { const list = c.createList(); expect(list).to.be.instanceOf(List); - await list.push(1); - await list.push(2); - await list.push(3); + list.push(1); + list.push(2); + list.push(3); - expect(await list.toArray()).to.have.members([1, 2, 3]); + expect(list.toArray()).to.have.members([1, 2, 3]); }); - it('Async iteration', async () => { + it('Iteration', () => { const list = c.createList(); - await list.push(1); - await list.push(2); - await list.push(3); + list.push(1); + list.push(2); + list.push(3); - for await (const item of list) { + for (const item of list) { expect(item).to.be.oneOf([1, 2, 3]); } }); @@ -42,12 +42,12 @@ describe('List', () => { describe('Lucky Numbers', () => { const list = c.createList(LuckyNumberList); - it('get median', async () => { - await list.push(1); - await list.push(2); - await list.push(3); + it('get median', () => { + list.push(1); + list.push(2); + list.push(3); - const median = await list.median(); + const median = list.median(); expect(median).to.equal(2); }); }); @@ -55,17 +55,17 @@ describe('List', () => { describe('Time-based', () => { it('pushex', async () => { const list = c.createList(); - await list.pushex(1, 1); - await list.pushex(2, 1); - await list.pushex(3, 1); + list.pushex(1, 1); + list.pushex(2, 1); + list.pushex(3, 1); - expect(await list.toArray()).to.have.length(3); - expect(await list.toArray()).to.have.members([1, 2, 3]); + expect(list.toArray()).to.have.length(3); + expect(list.toArray()).to.have.members([1, 2, 3]); await sleep(1000); - expect(await list.toArray()).to.have.length(3); - expect(await list.toArray()).to.have.members([null, null, null]); + expect(list.toArray()).to.have.length(3); + expect(list.toArray()).to.have.members([null, null, null]); }); }); }); diff --git a/src/list.ts b/src/list.ts index dea5f96..88e4797 100644 --- a/src/list.ts +++ b/src/list.ts @@ -7,64 +7,64 @@ export class List { private readonly _key: HashKey ) {} - [Symbol.asyncIterator]() { - const pList = this.list(); - return (async function* (): AsyncGenerator { - for (const item of await pList) { + [Symbol.iterator]() { + const list = this.toArray(); + return (function* (): Generator { + for (const item of list) { yield item; } })(); } - set(index: number, value: Value | null): Promise { + set(index: number, value: Value | null): void { return this._client.lset(this._key, index, value); } - setex(index: number, value: Value, seconds: number): Promise { + setex(index: number, value: Value, seconds: number): void { return this._client.lsetex(this._key, index, value, seconds); } - get(index: number): Promise { - return this._client.lget(this._key, index) as Promise; + get(index: number): Value | null { + return this._client.lget(this._key, index) as Value | null; } - del(index: number): Promise { + del(index: number): void { return this._client.ldel(this._key, index); } - push(value: Value): Promise { + push(value: Value): void { return this._client.lpush(this._key, value); } - pushex(value: Value, seconds: number): Promise { + pushex(value: Value, seconds: number): void { return this._client.lpushex(this._key, value, seconds); } - exists(value: Value): Promise { + exists(value: Value): boolean { return this._client.lexists(this._key, value); } - pop(): Promise { - return this._client.lpop(this._key) as Promise; + pop(): Value | null { + return this._client.lpop(this._key) as Value | null; } - size(): Promise { + size(): number { return this._client.lsize(this._key); } - clear(): Promise { + clear(): void { return this._client.lclear(this._key); } - range(start: number, stop: number): Promise { - return this._client.lrange(this._key, start, stop) as Promise; + range(start: number, stop: number): Value[] { + return this._client.lrange(this._key, start, stop) as Value[]; } - list(): Promise { - return this._client.lgetall(this._key) as Promise; + list(): Value[] { + return this._client.lgetall(this._key) as Value[]; } - toArray(): Promise { + toArray(): Value[] { return this.list(); } } diff --git a/src/typings.ts b/src/typings.ts index 78f05d6..db12ef8 100644 --- a/src/typings.ts +++ b/src/typings.ts @@ -1,67 +1,66 @@ export type StorageOperations = KVOperations & HashOperations & ListOperations & { - ttl(key: string): Promise; + ttl(key: string): number; }; export interface KVOperations< Key extends HashField = string, Value extends HashValue = Serializable, > { - get(key: Key): Promise; - set(key: Key, value: Value | null): Promise; - setex(key: Key, value: Value, seconds: number): Promise; - del(key: Key): Promise; - exists(key: Key): Promise; - has(key: Key): Promise; - keys(): Promise; - values(): Promise; - clear(): Promise; - getall(): Promise>; + get(key: Key): Value | null; + set(key: Key, value: Value | null): void; + setex(key: Key, value: Value, seconds: number): void; + del(key: Key): void; + exists(key: Key): boolean; + has(key: Key): boolean; + keys(): Key[]; + values(): Value[]; + clear(): void; + getall(): HashRecord; } /** * Hash operations */ export interface HashOperations { - hget(key: string, field: HashField): Promise; - hset(key: string, field: HashField, value: Serializable): Promise; - hsetex(key: string, field: HashField, value: Serializable, seconds: number): Promise; - hdel(key: string, field: HashField): Promise; - hexists(key: string, field: HashField): Promise; - hsize(key: string): Promise; - hclear(key: string): Promise; - hgetall(key: string): Promise; + hget(key: string, field: HashField): Serializable | null; + hset(key: string, field: HashField, value: Serializable): void; + hsetex(key: string, field: HashField, value: Serializable, seconds: number): void; + hdel(key: string, field: HashField): void; + hexists(key: string, field: HashField): boolean; + hsize(key: string): number; + hclear(key: string): void; + hgetall(key: string): HashRecord; } /** * List operations */ export interface ListOperations { - lset(key: string, index: number, value: Value | null): Promise; - lsetex(key: string, index: number, value: Value, seconds: number): Promise; - lget(key: string, index: number): Promise; - ldel(key: string, index: number): Promise; - lpush(key: string, value: Value): Promise; - lpop(key: string): Promise; - lsize(key: string): Promise; - lclear(key: string): Promise; - lrange(key: string, start: number, stop: number): Promise; - lgetall(key: string): Promise; + lset(key: string, index: number, value: Value | null): void; + lsetex(key: string, index: number, value: Value, seconds: number): void; + lget(key: string, index: number): Value | null; + ldel(key: string, index: number): void; + lpush(key: string, value: Value): void; + lpop(key: string): Value | null; + lsize(key: string): number; + lclear(key: string): void; + lrange(key: string, start: number, stop: number): Value[]; + lgetall(key: string): Value[]; } export interface StorageDriver< Key extends HashField = HashField, Value extends HashValue = HashValue, > { - prepare?(): Promise; - get(key: Key): Promise; - set(key: Key, value: Value): Promise; - del(key: Key): Promise; - exists(key: Key): Promise; - keys(): Promise; - values(): Promise; - clear(): Promise; + get(key: Key): Value | null; + set(key: Key, value: Value): void; + del(key: Key): void; + exists(key: Key): boolean; + keys(): Key[]; + values(): Value[]; + clear(): void; } export interface IStorageParser { @@ -69,8 +68,6 @@ export interface IStorageParser { parse(value: any): Map; } -export type StorageState = 'stale' | 'pending' | 'ready'; - // --------------------- export type HashField = string | number; diff --git a/src/utils/fs-extra.ts b/src/utils/fs-extra.ts index 535942d..5975a6c 100644 --- a/src/utils/fs-extra.ts +++ b/src/utils/fs-extra.ts @@ -1,8 +1,8 @@ -import { promises, type PathLike } from 'node:fs'; +import { accessSync, type PathLike } from 'node:fs'; -export async function access(path: PathLike) { +export function access(path: PathLike) { try { - await promises.access(path); + accessSync(path); return true; } catch { return false;