memidb work in progress
This commit is contained in:
parent
84c2a0ee93
commit
e0e496b878
138
node_modules/nyc/node_modules/yargs/package.json
generated
vendored
138
node_modules/nyc/node_modules/yargs/package.json
generated
vendored
File diff suppressed because one or more lines are too long
@ -47,6 +47,7 @@
|
|||||||
"pogen": "file:tooling/pogen/",
|
"pogen": "file:tooling/pogen/",
|
||||||
"react": "^15.5.4",
|
"react": "^15.5.4",
|
||||||
"react-dom": "^15.5.4",
|
"react-dom": "^15.5.4",
|
||||||
|
"structured-clone": "^0.2.2",
|
||||||
"through2": "^2.0.1",
|
"through2": "^2.0.1",
|
||||||
"ts-loader": "^2.0.3",
|
"ts-loader": "^2.0.3",
|
||||||
"tslint": "^5.3.2",
|
"tslint": "^5.3.2",
|
||||||
|
114
src/memidb-test.ts
Normal file
114
src/memidb-test.ts
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
This file is part of TALER
|
||||||
|
(C) 2017 Inria and GNUnet e.V.
|
||||||
|
|
||||||
|
TALER is free software; you can redistribute it and/or modify it under the
|
||||||
|
terms of the GNU General Public License as published by the Free Software
|
||||||
|
Foundation; either version 3, or (at your option) any later version.
|
||||||
|
|
||||||
|
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with
|
||||||
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {test} from "ava";
|
||||||
|
import * as memidb from "./memidb";
|
||||||
|
|
||||||
|
test.cb("db open", (t) => {
|
||||||
|
let ncb = 0;
|
||||||
|
const idb = new memidb.MemoryIDBFactory();
|
||||||
|
const req = idb.open("testdb");
|
||||||
|
let called = false;
|
||||||
|
req.onupgradeneeded = (evt) => {
|
||||||
|
ncb += 1;
|
||||||
|
called = true;
|
||||||
|
t.is(req.result, evt.target);
|
||||||
|
t.is(evt.oldVersion, 0);
|
||||||
|
t.is(evt.newVersion, 1);
|
||||||
|
t.truthy(req.result);
|
||||||
|
t.pass();
|
||||||
|
}
|
||||||
|
req.onsuccess = (evt) => {
|
||||||
|
t.is(ncb, 1);
|
||||||
|
t.is(req.result, evt.target);
|
||||||
|
t.truthy(req.result);
|
||||||
|
t.end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test.cb("store creation", (t) => {
|
||||||
|
const idb = new memidb.MemoryIDBFactory();
|
||||||
|
const req = idb.open("testdb");
|
||||||
|
req.onupgradeneeded = (evt) => {
|
||||||
|
const db: IDBDatabase = req.result;
|
||||||
|
|
||||||
|
const store1 = db.createObjectStore("b-store");
|
||||||
|
t.is(store1.name, "b-store");
|
||||||
|
t.deepEqual(Array.from(db.objectStoreNames), ["b-store"]);
|
||||||
|
|
||||||
|
const store2 = db.createObjectStore("a-store");
|
||||||
|
t.is(store2.name, "a-store");
|
||||||
|
t.deepEqual(Array.from(db.objectStoreNames), ["a-store", "b-store"]);
|
||||||
|
|
||||||
|
const store3 = db.createObjectStore("c-store");
|
||||||
|
t.is(store3.name, "c-store");
|
||||||
|
t.deepEqual(Array.from(db.objectStoreNames), ["a-store", "b-store", "c-store"]);
|
||||||
|
t.pass();
|
||||||
|
}
|
||||||
|
req.onsuccess = (evt) => {
|
||||||
|
t.end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test.cb("put and get", (t) => {
|
||||||
|
const idb = new memidb.MemoryIDBFactory();
|
||||||
|
const req = idb.open("testdb");
|
||||||
|
req.onupgradeneeded = (evt) => {
|
||||||
|
const db: IDBDatabase = req.result;
|
||||||
|
const store1 = db.createObjectStore("mystore");
|
||||||
|
store1.put({answer: 42}, "a");
|
||||||
|
}
|
||||||
|
req.onsuccess = (evt) => {
|
||||||
|
t.end()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test("key path evaluation", (t) => {
|
||||||
|
const obj = {
|
||||||
|
a: {
|
||||||
|
b: {
|
||||||
|
c: 42,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
b: "hello",
|
||||||
|
"": "spam",
|
||||||
|
arr: ["foo", "bar"],
|
||||||
|
}
|
||||||
|
t.deepEqual(memidb.evaluateKeyPath(obj, ""), obj);
|
||||||
|
t.deepEqual(memidb.evaluateKeyPath(obj, "a.b.c"), 42);
|
||||||
|
t.deepEqual(memidb.evaluateKeyPath(obj, "a.b"), {c: 42});
|
||||||
|
t.deepEqual(memidb.evaluateKeyPath(obj, "foo"), undefined);
|
||||||
|
t.deepEqual(memidb.evaluateKeyPath(obj, ["a.b.c", "foo"]), undefined);
|
||||||
|
t.deepEqual(memidb.evaluateKeyPath(obj, ["a.b.c", "b"]), [42, "hello"]);
|
||||||
|
t.deepEqual(memidb.evaluateKeyPath(obj, "arr.0"), "foo");
|
||||||
|
t.deepEqual(memidb.evaluateKeyPath(obj, "."), "spam");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("key path evaluation with replacement", (t) => {
|
||||||
|
const obj: any = {
|
||||||
|
a: {
|
||||||
|
b: {
|
||||||
|
c: 42,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
memidb.evaluateKeyPath(obj, "a.b.c", 24);
|
||||||
|
t.is(obj.a.b.c, 24);
|
||||||
|
memidb.evaluateKeyPath(obj, "a.b", 24);
|
||||||
|
t.is(obj.a.b, 24);
|
||||||
|
});
|
352
src/memidb.ts
352
src/memidb.ts
@ -29,20 +29,25 @@
|
|||||||
|
|
||||||
|
|
||||||
const structuredClone = require("structured-clone");
|
const structuredClone = require("structured-clone");
|
||||||
const structuredSerialize = require("structured-clone").serialize;
|
|
||||||
|
|
||||||
|
|
||||||
interface StoredObject {
|
|
||||||
key: any;
|
|
||||||
object: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Store {
|
interface Store {
|
||||||
name: string;
|
name: string;
|
||||||
keyPath: string | string[];
|
keyPath?: string | string[];
|
||||||
keyGenerator: number;
|
keyGenerator: number;
|
||||||
autoIncrement: boolean;
|
autoIncrement: boolean;
|
||||||
objects: { [strKey: string]: StoredObject };
|
objects: { [primaryKey: string]: any };
|
||||||
|
indices: { [indexName: string]: Index };
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Index {
|
||||||
|
multiEntry: boolean;
|
||||||
|
unique: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map the index's key to the primary key.
|
||||||
|
*/
|
||||||
|
map: { [indexKey: string]: string[] };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -80,11 +85,80 @@ class MyDomStringList extends Array<string> implements DOMStringList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function callEventHandler(h: EventListenerOrEventListenerObject, evt: Event, target: any) {
|
class MyKeyRange implements IDBKeyRange {
|
||||||
if ("handleEvent" in h) {
|
static only(value: any): IDBKeyRange {
|
||||||
(h as EventListenerObject).handleEvent(evt);
|
return new MyKeyRange(value, value, false, false);
|
||||||
} else {
|
}
|
||||||
(h as EventListener).call(target, evt);
|
|
||||||
|
static bound(lower: any, upper: any, lowerOpen: boolean = false, upperOpen: boolean = false) {
|
||||||
|
return new MyKeyRange(lower, upper, lowerOpen, upperOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
static lowerBound(lower: any, lowerOpen: boolean = false) {
|
||||||
|
return new MyKeyRange(lower, undefined, lowerOpen, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static upperBound(upper: any, upperOpen: boolean = false) {
|
||||||
|
return new MyKeyRange(undefined, upper, true, upperOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(public lower: any, public upper: any, public lowerOpen: boolean, public upperOpen: boolean) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type guard for an IDBKeyRange.
|
||||||
|
*/
|
||||||
|
export function isKeyRange(obj: any): obj is IDBKeyRange {
|
||||||
|
return (typeof obj === "object" &&
|
||||||
|
"lower" in obj && "upper" in obj &&
|
||||||
|
"lowerOpen" in obj && "upperOpen" in obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class IndexHandle implements IDBIndex {
|
||||||
|
|
||||||
|
_unique: boolean;
|
||||||
|
_multiEntry: boolean;
|
||||||
|
|
||||||
|
get keyPath(): string | string[] {
|
||||||
|
throw Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
get name () {
|
||||||
|
return this.indexName;
|
||||||
|
}
|
||||||
|
|
||||||
|
get unique() {
|
||||||
|
return this._unique;
|
||||||
|
}
|
||||||
|
|
||||||
|
get multiEntry() {
|
||||||
|
return this._multiEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(public objectStore: MyObjectStore, public indexName: string) {
|
||||||
|
}
|
||||||
|
|
||||||
|
count(key?: IDBKeyRange | IDBValidKey): IDBRequest {
|
||||||
|
throw Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key: IDBKeyRange | IDBValidKey): IDBRequest {
|
||||||
|
throw Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
getKey(key: IDBKeyRange | IDBValidKey): IDBRequest {
|
||||||
|
throw Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
openCursor(range?: IDBKeyRange | IDBValidKey, direction?: IDBCursorDirection): IDBRequest {
|
||||||
|
throw Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
openKeyCursor(range?: IDBKeyRange | IDBValidKey, direction?: IDBCursorDirection): IDBRequest {
|
||||||
|
throw Error("not implemented");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,9 +166,10 @@ class MyRequest implements IDBRequest {
|
|||||||
onerror: (this: IDBRequest, ev: Event) => any;
|
onerror: (this: IDBRequest, ev: Event) => any;
|
||||||
|
|
||||||
onsuccess: (this: IDBRequest, ev: Event) => any;
|
onsuccess: (this: IDBRequest, ev: Event) => any;
|
||||||
successHandlers: Array<(this: IDBRequest, ev: Event) => any>;
|
successHandlers: Array<(this: IDBRequest, ev: Event) => any> = [];
|
||||||
|
|
||||||
done: boolean = false;
|
done: boolean = false;
|
||||||
|
_result: any;
|
||||||
|
|
||||||
constructor(public _transaction: Transaction, public runner: () => void) {
|
constructor(public _transaction: Transaction, public runner: () => void) {
|
||||||
}
|
}
|
||||||
@ -113,7 +188,7 @@ class MyRequest implements IDBRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get result(): any {
|
get result(): any {
|
||||||
return null;
|
return this._result;
|
||||||
}
|
}
|
||||||
|
|
||||||
get source() {
|
get source() {
|
||||||
@ -188,17 +263,78 @@ class OpenDBRequest extends MyRequest implements IDBOpenDBRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function follow(x: any, s: string, replacement?: any): any {
|
||||||
|
if (s === "") {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
const ptIdx = s.indexOf(".");
|
||||||
|
if (ptIdx < 0) {
|
||||||
|
const v = x[s];
|
||||||
|
if (replacement !== undefined) {
|
||||||
|
x[s] = replacement;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
} else {
|
||||||
|
const identifier = s.substring(0, ptIdx);
|
||||||
|
const rest = s.substring(ptIdx + 1);
|
||||||
|
return follow(x[identifier], rest, replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function evaluateKeyPath(x: any, path: string | string[], replacement?: any): any {
|
||||||
|
if (typeof path === "string") {
|
||||||
|
return follow(x, path, replacement);
|
||||||
|
} else if (Array.isArray(path)) {
|
||||||
|
const res: any[] = [];
|
||||||
|
for (let s of path) {
|
||||||
|
let c = follow(x, s, replacement);
|
||||||
|
if (c === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
res.push(c);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
} else {
|
||||||
|
throw Error("invalid key path, must be string or array of strings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringifyKey(key: any) {
|
||||||
|
return JSON.stringify(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isValidKey(key: any, memo: any[] = []) {
|
||||||
|
if (typeof key === "string" || typeof key === "number" || key instanceof Date) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (Array.isArray(key)) {
|
||||||
|
for (const element of key) {
|
||||||
|
if (!isValidKey(element, memo.concat([key]))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
class MyObjectStore implements IDBObjectStore {
|
class MyObjectStore implements IDBObjectStore {
|
||||||
|
|
||||||
|
_keyPath: string | string[] | undefined;
|
||||||
|
_autoIncrement: boolean;
|
||||||
|
|
||||||
get indexNames() {
|
get indexNames() {
|
||||||
return new DOMStringList();
|
return new DOMStringList();
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(public transaction: Transaction, public dbName: string, public storeName: string) {
|
constructor(public transaction: Transaction, public storeName: string) {
|
||||||
|
this._keyPath = this.transaction.transactionDbData.stores[this.storeName].keyPath as (string | string[]);
|
||||||
|
this._autoIncrement = this.transaction.transactionDbData.stores[this.storeName].autoIncrement;
|
||||||
}
|
}
|
||||||
|
|
||||||
get keyPath() {
|
get keyPath(): string | string[] {
|
||||||
return this.transaction.db.dbData.stores[this.storeName].keyPath;
|
// TypeScript definitions are wrong here and don't permit a null keyPath
|
||||||
|
return this._keyPath as (string | string[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
get name() {
|
get name() {
|
||||||
@ -206,15 +342,77 @@ class MyObjectStore implements IDBObjectStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get autoIncrement() {
|
get autoIncrement() {
|
||||||
return this.transaction.db.dbData.stores[this.storeName].autoIncrement;
|
return this._autoIncrement;
|
||||||
}
|
}
|
||||||
|
|
||||||
add(value: any, key?: any): IDBRequest {
|
storeImpl(originalValue: any, key: any|undefined, allowExisting: boolean) {
|
||||||
throw Error("not implemented");
|
if (this.transaction.mode === "readonly") {
|
||||||
|
throw Error();
|
||||||
|
}
|
||||||
|
if (!this.transaction.active) {
|
||||||
|
throw Error();
|
||||||
|
}
|
||||||
|
if (!this.transaction.transactionDbData.stores.hasOwnProperty(this.storeName)) {
|
||||||
|
throw Error("object store was deleted");
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = this.transaction.transactionDbData.stores[this.storeName];
|
||||||
|
|
||||||
|
const value = structuredClone(originalValue);
|
||||||
|
|
||||||
|
if (this.keyPath) {
|
||||||
|
// we're dealine with in-line keys
|
||||||
|
if (key) {
|
||||||
|
throw Error("keys not allowed with in-line keys");
|
||||||
|
}
|
||||||
|
key = evaluateKeyPath(value, this.keyPath);
|
||||||
|
if (!key && !this.autoIncrement) {
|
||||||
|
throw Error("key path must evaluate to key for in-line stores without autoIncrement");
|
||||||
|
}
|
||||||
|
if (this.autoIncrement) {
|
||||||
|
if (key && typeof key === "number") {
|
||||||
|
store.keyGenerator = key + 1;
|
||||||
|
} else {
|
||||||
|
key = store.keyGenerator;
|
||||||
|
store.keyGenerator += 1;
|
||||||
|
evaluateKeyPath(value, this.keyPath, key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// we're dealing with out-of-line keys
|
||||||
|
if (!key && !this.autoIncrement) {
|
||||||
|
throw Error("key must be provided for out-of-line stores without autoIncrement");
|
||||||
|
}
|
||||||
|
key = this.transaction.transactionDbData.stores
|
||||||
|
if (this.autoIncrement) {
|
||||||
|
if (key && typeof key === "number") {
|
||||||
|
store.keyGenerator = key + 1;
|
||||||
|
} else {
|
||||||
|
key = store.keyGenerator;
|
||||||
|
store.keyGenerator += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const stringKey = stringifyKey(key);
|
||||||
|
|
||||||
|
if (store.objects.hasOwnProperty(stringKey) && !allowExisting) {
|
||||||
|
throw Error("key already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
store.objects[stringKey] = value;
|
||||||
|
|
||||||
|
const req = new MyRequest(this.transaction, () => {
|
||||||
|
});
|
||||||
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
put(value: any, key?: any): IDBRequest {
|
put(value: any, key?: any): IDBRequest {
|
||||||
throw Error("not implemented");
|
return this.storeImpl(value, key, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
add(value: any, key?: any): IDBRequest {
|
||||||
|
return this.storeImpl(value, key, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(key: any): IDBRequest {
|
delete(key: any): IDBRequest {
|
||||||
@ -242,7 +440,7 @@ class MyObjectStore implements IDBObjectStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
index(indexName: string): IDBIndex {
|
index(indexName: string): IDBIndex {
|
||||||
throw Error("not implemented");
|
return new IndexHandle(this, indexName);
|
||||||
}
|
}
|
||||||
|
|
||||||
openCursor(range?: IDBKeyRange | IDBValidKey, direction?: IDBCursorDirection): IDBRequest {
|
openCursor(range?: IDBKeyRange | IDBValidKey, direction?: IDBCursorDirection): IDBRequest {
|
||||||
@ -257,19 +455,31 @@ class Db implements IDBDatabase {
|
|||||||
onerror: (this: IDBDatabase, ev: Event) => any;
|
onerror: (this: IDBDatabase, ev: Event) => any;
|
||||||
onversionchange: (ev: IDBVersionChangeEvent) => any;
|
onversionchange: (ev: IDBVersionChangeEvent) => any;
|
||||||
|
|
||||||
|
_storeNames: string[] = [];
|
||||||
|
|
||||||
constructor(private _name: string, private _version: number, private factory: MemoryIDBFactory) {
|
constructor(private _name: string, private _version: number, private factory: MemoryIDBFactory) {
|
||||||
|
for (let storeName in this.dbData.stores) {
|
||||||
|
if (this.dbData.stores.hasOwnProperty(storeName)) {
|
||||||
|
this._storeNames.push(storeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._storeNames.sort();
|
||||||
}
|
}
|
||||||
|
|
||||||
get dbData() {
|
get dbData(): Database {
|
||||||
return this.factory.data[this._name];
|
return this.factory.data[this._name];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set dbData(data) {
|
||||||
|
this.factory.data[this._name] = data;
|
||||||
|
}
|
||||||
|
|
||||||
get name() {
|
get name() {
|
||||||
return this._name;
|
return this._name;
|
||||||
}
|
}
|
||||||
|
|
||||||
get objectStoreNames() {
|
get objectStoreNames() {
|
||||||
return new MyDomStringList();
|
return new MyDomStringList(...this._storeNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
get version() {
|
get version() {
|
||||||
@ -284,11 +494,44 @@ class Db implements IDBDatabase {
|
|||||||
if (tx.mode !== "versionchange") {
|
if (tx.mode !== "versionchange") {
|
||||||
throw Error("invalid mode");
|
throw Error("invalid mode");
|
||||||
}
|
}
|
||||||
throw Error("not implemented");
|
|
||||||
|
const td = tx.transactionDbData;
|
||||||
|
if (td.stores[name]) {
|
||||||
|
throw Error("object store already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
td.stores[name] = {
|
||||||
|
autoIncrement: !!(optionalParameters && optionalParameters.autoIncrement),
|
||||||
|
indices: {},
|
||||||
|
keyGenerator: 1,
|
||||||
|
name,
|
||||||
|
objects: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
this._storeNames.push(name);
|
||||||
|
this._storeNames.sort();
|
||||||
|
|
||||||
|
return new MyObjectStore(tx, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteObjectStore(name: string): void {
|
deleteObjectStore(name: string): void {
|
||||||
throw Error("not implemented");
|
let tx = this.factory.getTransaction();
|
||||||
|
if (tx.mode !== "versionchange") {
|
||||||
|
throw Error("invalid mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
const td = tx.transactionDbData;
|
||||||
|
if (td.stores[name]) {
|
||||||
|
throw Error("object store does not exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
const idx = this._storeNames.indexOf(name);
|
||||||
|
if (idx < 0) {
|
||||||
|
throw Error();
|
||||||
|
}
|
||||||
|
this._storeNames.splice(idx, 1);
|
||||||
|
|
||||||
|
delete td.stores[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction(storeNames: string | string[], mode: IDBTransactionMode = "readonly"): IDBTransaction {
|
transaction(storeNames: string | string[], mode: IDBTransactionMode = "readonly"): IDBTransaction {
|
||||||
@ -342,11 +585,30 @@ class Transaction implements IDBTransaction {
|
|||||||
return this._mode;
|
return this._mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get active(): boolean {
|
||||||
|
return this.state === TransactionState.Running || this.state === TransactionState.Created;
|
||||||
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
if (this.state != TransactionState.Created) {
|
if (this.state != TransactionState.Created) {
|
||||||
throw Error();
|
throw Error();
|
||||||
}
|
}
|
||||||
|
this.state = TransactionState.Running;
|
||||||
this._transactionDbData = structuredClone(this.dbHandle.dbData);
|
this._transactionDbData = structuredClone(this.dbHandle.dbData);
|
||||||
|
if (!this._transactionDbData) {
|
||||||
|
throw Error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commit() {
|
||||||
|
if (this.state != TransactionState.Running) {
|
||||||
|
throw Error();
|
||||||
|
}
|
||||||
|
if (!this._transactionDbData) {
|
||||||
|
throw Error();
|
||||||
|
}
|
||||||
|
this.state = TransactionState.Commited;
|
||||||
|
this.dbHandle.dbData = this._transactionDbData;
|
||||||
}
|
}
|
||||||
|
|
||||||
get error(): DOMException {
|
get error(): DOMException {
|
||||||
@ -373,7 +635,7 @@ class Transaction implements IDBTransaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
objectStore(storeName: string): IDBObjectStore {
|
objectStore(storeName: string): IDBObjectStore {
|
||||||
return new MyObjectStore(this, this.dbName, storeName);
|
return new MyObjectStore(this, storeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatchEvent(evt: Event): boolean {
|
dispatchEvent(evt: Event): boolean {
|
||||||
@ -425,8 +687,9 @@ class MyEvent implements Event {
|
|||||||
_timeStamp: number = 0;
|
_timeStamp: number = 0;
|
||||||
_type: string;
|
_type: string;
|
||||||
|
|
||||||
constructor(typeArg: string) {
|
constructor(typeArg: string, target: any) {
|
||||||
this._type = typeArg;
|
this._type = typeArg;
|
||||||
|
this._target = target;
|
||||||
}
|
}
|
||||||
|
|
||||||
get eventPhase() {
|
get eventPhase() {
|
||||||
@ -519,10 +782,10 @@ class MyEvent implements Event {
|
|||||||
class VersionChangeEvent extends MyEvent {
|
class VersionChangeEvent extends MyEvent {
|
||||||
_newVersion: number|null;
|
_newVersion: number|null;
|
||||||
_oldVersion: number;
|
_oldVersion: number;
|
||||||
constructor(oldVersion: number, newVersion?: number) {
|
constructor(oldVersion: number, newVersion: number|null, target: any) {
|
||||||
super("VersionChange");
|
super("VersionChange", target);
|
||||||
this._oldVersion = oldVersion;
|
this._oldVersion = oldVersion;
|
||||||
this._newVersion = newVersion || null;
|
this._newVersion = newVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
get newVersion() {
|
get newVersion() {
|
||||||
@ -535,7 +798,7 @@ class VersionChangeEvent extends MyEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class MemoryIDBFactory implements IDBFactory {
|
export class MemoryIDBFactory implements IDBFactory {
|
||||||
data: Databases = {};
|
data: Databases = {};
|
||||||
|
|
||||||
currentRequest: MyRequest|undefined;
|
currentRequest: MyRequest|undefined;
|
||||||
@ -570,7 +833,7 @@ class MemoryIDBFactory implements IDBFactory {
|
|||||||
// auto-commit the transaction that the
|
// auto-commit the transaction that the
|
||||||
// previous request worked on.
|
// previous request worked on.
|
||||||
let lastTx = prevRequest._transaction;
|
let lastTx = prevRequest._transaction;
|
||||||
this.data[lastTx.dbName] = lastTx.transactionDbData;
|
lastTx.commit();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
alreadyResolved.then(() => {
|
alreadyResolved.then(() => {
|
||||||
@ -604,13 +867,19 @@ class MemoryIDBFactory implements IDBFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let upgradeNeeded = false;
|
let upgradeNeeded = false;
|
||||||
|
let oldVersion: number;
|
||||||
let mydb: Database;
|
let mydb: Database;
|
||||||
if (dbName in this.data) {
|
if (dbName in this.data) {
|
||||||
mydb = this.data[dbName];
|
mydb = this.data[dbName];
|
||||||
|
if (!mydb) {
|
||||||
|
throw Error();
|
||||||
|
}
|
||||||
|
oldVersion = mydb.version;
|
||||||
if (version === undefined || version == mydb.version) {
|
if (version === undefined || version == mydb.version) {
|
||||||
// we can open without upgrading
|
// we can open without upgrading
|
||||||
} else if (version > mydb.version) {
|
} else if (version > mydb.version) {
|
||||||
upgradeNeeded = true;
|
upgradeNeeded = true;
|
||||||
|
mydb.version = version;
|
||||||
} else {
|
} else {
|
||||||
throw Error("version error");
|
throw Error("version error");
|
||||||
}
|
}
|
||||||
@ -621,17 +890,21 @@ class MemoryIDBFactory implements IDBFactory {
|
|||||||
version: (version || 1),
|
version: (version || 1),
|
||||||
};
|
};
|
||||||
upgradeNeeded = true;
|
upgradeNeeded = true;
|
||||||
|
oldVersion = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.data[dbName] = mydb;
|
||||||
|
|
||||||
const db = new Db(dbName, mydb.version, this);
|
const db = new Db(dbName, mydb.version, this);
|
||||||
const tx = new Transaction(dbName, db, "versionchange");
|
const tx = new Transaction(dbName, db, "versionchange");
|
||||||
|
|
||||||
const req = new OpenDBRequest(tx, () => {
|
const req = new OpenDBRequest(tx, () => {
|
||||||
|
req._result = db;
|
||||||
if (upgradeNeeded) {
|
if (upgradeNeeded) {
|
||||||
let versionChangeEvt = new VersionChangeEvent(mydb.version, version);
|
let versionChangeEvt = new VersionChangeEvent(oldVersion, mydb.version, db);
|
||||||
req.callOnupgradeneeded(versionChangeEvt);
|
req.callOnupgradeneeded(versionChangeEvt);
|
||||||
}
|
}
|
||||||
let successEvent = new MyEvent("success");
|
let successEvent = new MyEvent("success", db);
|
||||||
req.callSuccess(successEvent);
|
req.callSuccess(successEvent);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -640,3 +913,10 @@ class MemoryIDBFactory implements IDBFactory {
|
|||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject our IndexedDb implementation in the global namespace,
|
||||||
|
* potentially replacing an existing implementation.
|
||||||
|
*/
|
||||||
|
export function injectGlobals() {
|
||||||
|
}
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
"src/i18n.tsx",
|
"src/i18n.tsx",
|
||||||
"src/i18n/strings.ts",
|
"src/i18n/strings.ts",
|
||||||
"src/logging.ts",
|
"src/logging.ts",
|
||||||
|
"src/memidb-test.ts",
|
||||||
"src/memidb.ts",
|
"src/memidb.ts",
|
||||||
"src/query.ts",
|
"src/query.ts",
|
||||||
"src/timer.ts",
|
"src/timer.ts",
|
||||||
|
@ -4610,6 +4610,10 @@ strip-json-comments@~2.0.1:
|
|||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||||
|
|
||||||
|
structured-clone@^0.2.2:
|
||||||
|
version "0.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/structured-clone/-/structured-clone-0.2.2.tgz#ac92b6be31958a643db30f1335abc6a1b02dfdc2"
|
||||||
|
|
||||||
supports-color@^0.2.0:
|
supports-color@^0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a"
|
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a"
|
||||||
|
Loading…
Reference in New Issue
Block a user