Skip to content

Commit

Permalink
fix(storage): use name as namespace (#21)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: storage package sperate storags by `name` option

## Affected Packages

- `@kvs/env` in Node.js
  - 馃摑 Browser is not affected because it uses IndexedDB
- `@kvs/storage`
- `@kvs/localstorage`
- `@kvs/memorystorage`
- `@kvs/node-localstorage`
- `@kvs/storage-sync`
  • Loading branch information
azu committed Feb 19, 2022
1 parent bfa33d7 commit 2a8d783
Show file tree
Hide file tree
Showing 16 changed files with 1,345 additions and 1,863 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.cache
.parcel-cache
packages/**/module
packages/**/lib
### https://raw.github.com/github/gitignore/d2c1bb2b9c72ead618c9f6a48280ebc7a8e0dff6/Node.gitignore
Expand Down
2 changes: 1 addition & 1 deletion examples/basic/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
<title>kvc - basic</title>
</head>
<body>
<script src="index.ts"></script>
<script type="module" src="./index.ts"></script>
</body>
</html>
4 changes: 2 additions & 2 deletions examples/basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
"start": "parcel serve index.html"
},
"dependencies": {
"@kvs/env": "^1.0.0"
"@kvs/env": "^1.2.0"
},
"devDependencies": {
"parcel": "^1.12.4",
"parcel": "^2.3.1",
"typescript": "^4.2.4"
},
"keywords": [],
Expand Down
30 changes: 28 additions & 2 deletions packages/common-test-case/src/common-test-case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ export const createKVSTestCase = (
});
await kvs.set("key1", "value1");
await kvs.set("key2", "value2");
assert.ok(await kvs.has("key1"));
assert.ok(await kvs.has("key2"));
assert.ok(await kvs.has("key1"), "should have key1");
assert.ok(await kvs.has("key2"), "should have key2");
const results: [string, string][] = [];
for await (const [key, value] of kvs) {
results.push([key, value]);
Expand Down Expand Up @@ -188,6 +188,32 @@ export const createKVSTestCase = (
].sort()
);
});
it("asyncIterator should iterate per storage", async () => {
const aStorage = await kvsStorageConstructor({
name: "a_col",
version: 1
});
const bStorage = await kvsStorageConstructor({
name: "b_col",
version: 1
});
// write a and b
await aStorage.set("key1", "value1");
await bStorage.set("key2", "value2");
const resultsA: [string, string][] = [];
const resultsB: [string, string][] = [];
// iterate
for await (const [key, value] of aStorage) {
resultsA.push([key, value]);
}
for await (const [key, value] of bStorage) {
resultsB.push([key, value]);
}
assert.deepStrictEqual(resultsA.sort(), [["key1", "value1"]].sort());
assert.deepStrictEqual(resultsB.sort(), [["key2", "value2"]].sort());
await bStorage.clear();
await aStorage.clear();
});
}
};
};
2 changes: 1 addition & 1 deletion packages/env/test/node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ const databaseName = "kvs-test";
const kvsTestCase = createKVSTestCase(
(options) =>
kvsEnvStorage({
...options,
name: databaseName,
...options,
debug: true
}),
{
Expand Down
9 changes: 9 additions & 0 deletions packages/indexeddb/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import type { KVS, KVSOptions, StoreNames, StoreValue } from "@kvs/types";
import type { JsonValue } from "@kvs/storage";

function invariant(condition: any, message: string): asserts condition {
if (condition) {
return;
}
throw new Error(message);
}

const debug = {
enabled: false,
log(...args: any[]) {
Expand Down Expand Up @@ -295,6 +302,8 @@ export const kvsIndexedDB = async <Schema extends KVSIndexedSchema>(
options: KvsIndexedDBOptions<Schema>
): Promise<KVSIndexedDB<Schema>> => {
const { name, version, upgrade, ...indexDBOptions } = options;
invariant(typeof name === "string", "name should be string");
invariant(typeof version === "number", "version should be number");
if (indexDBOptions.debug) {
debug.enabled = indexDBOptions.debug;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/indexeddb/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ const databaseName = "kvs-test";
const kvsTestCase = createKVSTestCase(
(options) =>
kvsIndexedDB({
...options,
name: databaseName,
...options,
debug: true
}),
{
Expand Down
2 changes: 1 addition & 1 deletion packages/localstorage/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ const databaseName = "kvs-test";
const kvsTestCase = createKVSTestCase(
(options) =>
kvsLocalStorage({
...options,
name: databaseName,
...options,
debug: true
}),
{
Expand Down
2 changes: 1 addition & 1 deletion packages/memorystorage/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ const databaseName = "kvs-test";
const kvsTestCase = createKVSTestCase(
(options) =>
kvsMemoryStorage({
...options,
name: databaseName,
...options,
debug: true
}),
{
Expand Down
5 changes: 2 additions & 3 deletions packages/node-localstorage/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ const databaseName = "kvs-test";
const kvsTestCase = createKVSTestCase(
(options) =>
kvsLocalStorage({
...options,
name: databaseName,
...options,
debug: true
}),
{
Expand Down Expand Up @@ -52,7 +52,6 @@ const deleteAllDB = async () => {
}
};
describe("@kvs/node-localstorage", () => {
before(deleteAllDB);
afterEach(deleteAllDB);
beforeEach(deleteAllDB);
kvsTestCase.run();
});
84 changes: 59 additions & 25 deletions packages/storage-sync/src/storage.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,41 @@
import type { KVSSync, KVSSyncOptions, StoreNames, StoreValue } from "@kvs/types";
import { JsonValue } from "./JSONValue";

function invariant(condition: any, message: string): asserts condition {
if (condition) {
return;
}
throw new Error(message);
}

export type KVSStorageKey = string;
export const getItem = <Schema extends StorageSchema>(storage: Storage, key: StoreNames<Schema>) => {
const item = storage.getItem(String(key));
const TABLE_KEY_MARKER = ".__.";
export const getItem = <Schema extends StorageSchema>(storage: Storage, tableName: string, key: StoreNames<Schema>) => {
const storageKey = `${tableName}${TABLE_KEY_MARKER}${String(key)}`;
const item = storage.getItem(storageKey);
return item !== null ? JSON.parse(item) : undefined;
};
export const hasItem = <Schema extends StorageSchema>(storage: Storage, key: StoreNames<Schema>) => {
return storage.getItem(String(key)) !== null;
export const hasItem = <Schema extends StorageSchema>(storage: Storage, tableName: string, key: StoreNames<Schema>) => {
const storageKey = `${tableName}${TABLE_KEY_MARKER}${String(key)}`;
return storage.getItem(storageKey) !== null;
};
export const setItem = <Schema extends StorageSchema>(
storage: Storage,
tableName: string,
key: StoreNames<Schema>,
value: StoreValue<Schema, StoreNames<Schema>> | undefined
) => {
// It is difference with IndexedDB implementation.
// This behavior compatible with localStorage.
if (value === undefined) {
return deleteItem(storage, key);
return deleteItem(storage, tableName, key);
}
return storage.setItem(String(key), JSON.stringify(value));
const storageKey = `${tableName}${TABLE_KEY_MARKER}${String(key)}`;
return storage.setItem(storageKey, JSON.stringify(value));
};
export const clearItem = (storage: Storage, kvsVersionKey: string, options: { force: boolean }) => {
export const clearItem = (storage: Storage, tableName: string, kvsVersionKey: string, options: { force: boolean }) => {
// TODO: kvsVersionKey is special type
const currentVersion: number | undefined = getItem<any>(storage, kvsVersionKey);
const currentVersion: number | undefined = getItem<any>(storage, tableName, kvsVersionKey);
// clear all
storage.clear();
// if option.force is true, does not restore metadata.
Expand All @@ -32,12 +44,17 @@ export const clearItem = (storage: Storage, kvsVersionKey: string, options: { fo
}
// set kvs version again
if (currentVersion !== undefined) {
setItem<any>(storage, kvsVersionKey, currentVersion);
setItem<any>(storage, tableName, kvsVersionKey, currentVersion);
}
};
export const deleteItem = <Schema extends StorageSchema>(storage: Storage, key: StoreNames<Schema>): boolean => {
export const deleteItem = <Schema extends StorageSchema>(
storage: Storage,
tableName: string,
key: StoreNames<Schema>
): boolean => {
try {
storage.removeItem(String(key));
const storageKey = `${tableName}${TABLE_KEY_MARKER}${String(key)}`;
storage.removeItem(storageKey);
return true;
} catch {
return false;
Expand All @@ -46,30 +63,39 @@ export const deleteItem = <Schema extends StorageSchema>(storage: Storage, key:

export function* createIterator<Schema extends StorageSchema>(
storage: Storage,
tableName: string,
kvsVersionKey: string
): Iterator<[StoreNames<Schema>, StoreValue<Schema, StoreNames<Schema>>]> {
const tableKeyPrefix = `${tableName}${TABLE_KEY_MARKER}`;
for (let i = 0; i < storage.length; i++) {
const key = storage.key(i) as StoreNames<Schema> | undefined;
if (!key) {
continue;
}
// skip another storage
if (!key.startsWith(tableKeyPrefix)) {
continue;
}
// skip meta key
if (key === kvsVersionKey) {
const keyWithoutPrefix = key.replace(tableKeyPrefix, "");
if (keyWithoutPrefix === kvsVersionKey) {
continue;
}
const value = getItem(storage, key);
yield [key, value];
const value = getItem(storage, tableName, keyWithoutPrefix);
yield [keyWithoutPrefix as StoreNames<Schema>, value];
}
}

const DEFAULT_KVS_VERSION = 1;
const openStorage = ({
storage,
tableName,
version,
kvsVersionKey,
onUpgrade
}: {
storage: Storage;
tableName: string;
version: number;
kvsVersionKey: string;
onUpgrade: ({
Expand All @@ -84,9 +110,9 @@ const openStorage = ({
}) => {
// kvsVersionKey is special type
// first `oldVersion` is `0`
let oldVersion = getItem<any>(storage, kvsVersionKey);
let oldVersion = getItem<any>(storage, tableName, kvsVersionKey);
if (oldVersion === undefined) {
setItem<any>(storage, kvsVersionKey, DEFAULT_KVS_VERSION);
setItem<any>(storage, tableName, kvsVersionKey, DEFAULT_KVS_VERSION);
// first `oldVersion` is `0`
// https://github.com/azu/kvs/issues/8
oldVersion = 0;
Expand All @@ -105,37 +131,39 @@ const openStorage = ({

const createStore = <Schema extends StorageSchema>({
storage,
tableName,
kvsVersionKey
}: {
storage: Storage;
tableName: string;
kvsVersionKey: string;
}): KvsStorageSync<Schema> => {
const store: KvsStorageSync<Schema> = {
get<K extends StoreNames<Schema>>(key: K): StoreValue<Schema, K> | undefined {
return getItem<Schema>(storage, key);
return getItem<Schema>(storage, tableName, key);
},
has(key: StoreNames<Schema>): boolean {
return hasItem<Schema>(storage, key);
return hasItem<Schema>(storage, tableName, key);
},
set<K extends StoreNames<Schema>>(key: K, value: StoreValue<Schema, K> | undefined) {
setItem<Schema>(storage, key, value);
setItem<Schema>(storage, tableName, key, value);
return store;
},
clear(): void {
return clearItem(storage, kvsVersionKey, { force: false });
return clearItem(storage, tableName, kvsVersionKey, { force: false });
},
delete(key: StoreNames<Schema>): boolean {
return deleteItem<Schema>(storage, key);
return deleteItem<Schema>(storage, tableName, key);
},
dropInstance(): void {
return clearItem(storage, kvsVersionKey, { force: true });
return clearItem(storage, tableName, kvsVersionKey, { force: true });
},
close(): void {
// Noop function
return;
},
[Symbol.iterator](): Iterator<[StoreNames<Schema>, StoreValue<Schema, StoreNames<Schema>>]> {
const iterator = createIterator<Schema>(storage, kvsVersionKey);
const iterator = createIterator<Schema>(storage, tableName, kvsVersionKey);
return {
next() {
return iterator.next();
Expand All @@ -145,6 +173,7 @@ const createStore = <Schema extends StorageSchema>({
};
return store;
};

export type StorageSchema = {
[index: string]: JsonValue;
};
Expand All @@ -157,21 +186,26 @@ export const kvsStorageSync = <Schema extends StorageSchema>(
options: KvsStorageSyncOptions<Schema>
): KvsStorageSync<Schema> => {
const { name, version, upgrade, ...kvStorageOptions } = options;
invariant(typeof name === "string", "name should be string");
invariant(name.length > 0, "name should not be empty");
invariant(!name.includes(TABLE_KEY_MARKER), `name can not include ${TABLE_KEY_MARKER}. It is reserved in kvs.`);
invariant(typeof version === "number", `version should be number`);
const kvsVersionKey = kvStorageOptions.kvsVersionKey ?? "__kvs_version__";
const storage = openStorage({
tableName: name,
storage: options.storage,
version: options.version,
onUpgrade: ({ oldVersion, newVersion, storage }) => {
if (!options.upgrade) {
return;
}
return options.upgrade({
kvs: createStore({ storage, kvsVersionKey }),
kvs: createStore({ storage, tableName: name, kvsVersionKey }),
oldVersion,
newVersion
});
},
kvsVersionKey
});
return createStore({ storage, kvsVersionKey });
return createStore({ storage, tableName: name, kvsVersionKey });
};

0 comments on commit 2a8d783

Please sign in to comment.