Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow a Storage implementation to be passed into the LocalStorage adapter #195

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions packages/local-storage/spec/fixtures/in-memory-local-storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
export class InMemoryLocalStorage implements Storage {
private data: Record<string, string> = {};

length: number;
clear(): void {
this.data = {};
}
getItem(key: string): string | null {
return this.data[key];
}
key(index: number): string | null {
return Object.keys(this.data)[index] || null;
}
removeItem(key: string): void {
if (key in this.data) {
delete this.data[key];
}
}
setItem(key: string, value: string): void {
this.data[key] = value;
}
}

export class AsyncInMemoryLocalStorage {
private data: Record<string, string> = {};

length: number;
async clear(): Promise<void> {
this.data = {};
}
async getItem(key: string): Promise<string | null> {
return this.data[key];
}
async key(index: number): Promise<string | null> {
return Object.keys(this.data)[index] || null;
}
async removeItem(key: string): Promise<void> {
if (key in this.data) {
delete this.data[key];
}
}
async setItem(key: string, value: string): Promise<void> {
this.data[key] = value;
}
}
130 changes: 61 additions & 69 deletions packages/local-storage/spec/web/local_storage.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
/* global describe, it, expect */
import { Loki } from "../../../loki/src/loki";
import { LocalStorage } from "../../src/local_storage";
import {
InMemoryLocalStorage,
AsyncInMemoryLocalStorage,
} from "../fixtures/in-memory-local-storage";

describe("testing local storage", function () {
const STORAGES = {
"window.localStorage": window.localStorage,
InMemoryLocalStorage: new InMemoryLocalStorage(),
AsyncInMemoryLocalStorage: new AsyncInMemoryLocalStorage(),
};

describe("testing local storage", function () {
interface Name {
name: string;
}
Expand All @@ -16,80 +25,63 @@ describe("testing local storage", function () {
LocalStorage.deregister();
});

it("LokiLocalStorage", function (done) {
const db = new Loki("myTestApp");
const adapter = {adapter: new LocalStorage()};
db.initializePersistence(adapter)
.then(() => {
db.addCollection<Name>("myColl").insert({name: "Hello World"});
return db.saveDatabase();
})
.then(() => {
const db2 = new Loki("myTestApp");
return db2.initializePersistence()
.then(() => {
return db2.loadDatabase();
}).then(() => {
expect(db2.getCollection<Name>("myColl").find()[0].name).toEqual("Hello World");
});
})
.then(() => {
for (const [storageName, storage] of Object.entries(STORAGES)) {
describe(`Testing with ${storageName}`, () => {
const adapter = new LocalStorage(storage);

it("should isolate different databases, collections and objects", async function () {
const db = new Loki("myTestApp");
await db.initializePersistence({ adapter });
db.addCollection<Name>("myColl").insert({ name: "Hello World" });
await db.saveDatabase();

const db2 = new Loki("myTestApp");
return db2.initializePersistence({persistenceMethod: "local-storage"})
.then(() => {
return db2.loadDatabase();
}).then(() => {
expect(db2.getCollection<Name>("myColl").find()[0].name).toEqual("Hello World");
});
})
.then(() => {
await db2.initializePersistence({ adapter });
await db2.loadDatabase();
expect(db2.getCollection<Name>("myColl").find()[0].name).toEqual(
"Hello World"
);

await db2.initializePersistence({
persistenceMethod: "local-storage",
adapter,
});
await db2.loadDatabase();
expect(db2.getCollection<Name>("myColl").find()[0].name).toEqual(
"Hello World"
);

const db3 = new Loki("other");
return db3.initializePersistence()
.then(() => {
return db3.loadDatabase();
}).then(() => {
expect(false).toEqual(true);
}, () => {
expect(true).toEqual(true);
});
})
.then(() => {
return db.deleteDatabase();
})
.then(() => {
return db.loadDatabase()
.then(() => {
expect(db.getCollection<Name>("myColl").find()[0].name).toEqual("Hello World");
expect(false).toEqual(true);
done();
}, () => {
expect(true).toEqual(true);
done();
});
await db3.initializePersistence({ adapter });
await expect(async () => {
await db3.loadDatabase();
}).not.toThrow();

await db.deleteDatabase();
await expect(async () => {
await db.loadDatabase();
}).not.toThrow();
});
});

it("auto save and auto load", async function () {
const db = new Loki("myTestApp2");

await db.initializePersistence({
autosave: true,
autoload: true,
adapter,
});

it("auto save and auto load", function (done) {
const db = new Loki("myTestApp2");
db.addCollection<Name>("myColl").insert({ name: "Hello World" });
await db.close();

db.initializePersistence({autosave: true, autoload: true})
.then(() => {
db.addCollection<Name>("myColl").insert({name: "Hello World"});
return db.close();
})
.then(() => {
const db2 = new Loki("myTestApp2");
return db2.initializePersistence({autosave: true, autoload: true})
.then(() => {
expect(db2.getCollection<Name>("myColl").find()[0].name).toEqual("Hello World");
});
})
.catch(e => {
fail(e);
})
.then(() => {
done();

await db2.initializePersistence({ autosave: true, autoload: true });
expect(db2.getCollection<Name>("myColl").find()[0].name).toEqual(
"Hello World"
);
});
});
});
}
});
5 changes: 3 additions & 2 deletions packages/local-storage/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { LocalStorage } from "./local_storage";
export * from "./types";

export {LocalStorage};
import { LocalStorage } from "./local_storage";
export { LocalStorage };
export default LocalStorage;
22 changes: 16 additions & 6 deletions packages/local-storage/src/local_storage.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { PLUGINS } from "../../common/plugin";
import { StorageAdapter } from "../../common/types";
import { Storage } from "./types";

/**
* A loki persistence adapter which persists to web browser's local storage object
* @constructor LocalStorageAdapter
*/
export class LocalStorage implements StorageAdapter {
private storage: Storage;

/**
* Registers the local storage as plugin.
*/
Expand All @@ -20,13 +23,20 @@ export class LocalStorage implements StorageAdapter {
delete PLUGINS["LocalStorage"];
}

/**
* @param {Storage} [constructor=window.localStorage] - Application name context can be used to distinguish subdomains, "loki" by default
*/
constructor(storage: Storage = window.localStorage) {
this.storage = storage;
}

/**
* loadDatabase() - Load data from localstorage
* @param {string} dbname - the name of the database to load
* @returns {Promise} a Promise that resolves after the database was loaded
*/
loadDatabase(dbname: string) {
return Promise.resolve(localStorage.getItem(dbname));
async loadDatabase(dbname: string) {
return this.storage.getItem(dbname);
}

/**
Expand All @@ -35,8 +45,8 @@ export class LocalStorage implements StorageAdapter {
* @param {string} dbname - the filename of the database to load
* @returns {Promise} a Promise that resolves after the database was saved
*/
saveDatabase(dbname: string, dbstring: string) {
return Promise.resolve(localStorage.setItem(dbname, dbstring));
async saveDatabase(dbname: string, dbstring: string) {
return this.storage.setItem(dbname, dbstring);
}

/**
Expand All @@ -45,8 +55,8 @@ export class LocalStorage implements StorageAdapter {
* @param {string} dbname - the filename of the database to delete
* @returns {Promise} a Promise that resolves after the database was deleted
*/
deleteDatabase(dbname: string) {
return Promise.resolve(localStorage.removeItem(dbname));
async deleteDatabase(dbname: string) {
return this.storage.removeItem(dbname);
}
}

Expand Down
10 changes: 10 additions & 0 deletions packages/local-storage/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export type SyncOrAsync<T> = T | Promise<T>;

export interface Storage {
readonly length: number;
clear(): SyncOrAsync<void>;
getItem(key: string): SyncOrAsync<string | null>;
key(index: number): SyncOrAsync<string | null>;
removeItem(key: string): SyncOrAsync<void>;
setItem(key: string, value: string): SyncOrAsync<void>;
}