xdriver 2.0.4 → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/com.ts +68 -0
- package/src/connection.ts +220 -0
- package/src/const.ts +51 -0
- package/src/database.ts +141 -0
- package/src/index.ts +147 -0
- package/src/table.ts +531 -0
package/package.json
CHANGED
package/src/com.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export function _instance(example: any, type: any) {
|
|
2
|
+
let pro = type.prototype;
|
|
3
|
+
example = example.__proto__;
|
|
4
|
+
while (true) {
|
|
5
|
+
if (example === null)
|
|
6
|
+
return false;
|
|
7
|
+
if (pro === example)
|
|
8
|
+
return true;
|
|
9
|
+
example = example.__proto__;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function _copy(src: any, target: any) {
|
|
14
|
+
if (!target) return src;
|
|
15
|
+
for (let prop in src) {
|
|
16
|
+
target[prop] = src[prop];
|
|
17
|
+
}
|
|
18
|
+
return target;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class Logger {
|
|
22
|
+
public static trace: boolean;
|
|
23
|
+
private readonly catalog: string;
|
|
24
|
+
|
|
25
|
+
constructor(catalog: string) {
|
|
26
|
+
this.catalog = catalog;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private print(fn: Function, ...args: any[]) {
|
|
30
|
+
if (Logger.trace) {
|
|
31
|
+
console.trace(...args);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
fn(...args);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private getFormatDate() {
|
|
38
|
+
const date = new Date();
|
|
39
|
+
return `[${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}.${date.getMilliseconds()}]`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
debug(msg: string, ...args: any[]) {
|
|
43
|
+
this.print(console.debug, `%c${this.getFormatDate()} [DEBUG] <${this.catalog}> ${msg}`, "color: gray;", ...args);
|
|
44
|
+
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
warn(msg: string, ...args: any[]) {
|
|
48
|
+
this.print(console.warn, `%c${this.getFormatDate()} [WARN] <${this.catalog}> ${msg}`, "color: #FFCC00;", ...args)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
info(msg: string, ...args: any[]) {
|
|
52
|
+
this.print(console.info, `${this.getFormatDate()} [INFO] <${this.catalog}> ${msg}`, ...args)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
error(msg: string, ...args: any[]) {
|
|
56
|
+
this.print(console.error, `%c${this.getFormatDate()} [ERROR] <${this.catalog}> ${msg}`, "color: #EF4444;", ...args)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
success(msg: string, ...args: any[]) {
|
|
60
|
+
this.print(console.log, `%c${this.getFormatDate()} [SUCCESS] <${this.catalog}> ${msg}`, "color: #10B981;", ...args)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
static getLogger(catalog: string = 'default') {
|
|
64
|
+
return new Logger(catalog);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const logger = Logger.getLogger();
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import {IndexdbStatus, innerDB} from "./const";
|
|
2
|
+
import Table, {Index, ITable, TableMeta} from "./table";
|
|
3
|
+
import {Logger} from './com'
|
|
4
|
+
|
|
5
|
+
interface EventMap {
|
|
6
|
+
process: (this: Connection, status: IndexdbStatus) => void;
|
|
7
|
+
success: (this: Connection, connect: IDBDatabase) => void;
|
|
8
|
+
blocked: (this: Connection, event: IDBVersionChangeEvent) => void;
|
|
9
|
+
change: (connect: IDBDatabase, event: IDBVersionChangeEvent) => void;
|
|
10
|
+
error: (evt: Event) => void;
|
|
11
|
+
close: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type EventKey = keyof EventMap;
|
|
15
|
+
|
|
16
|
+
const logger = Logger.getLogger('Database');
|
|
17
|
+
|
|
18
|
+
export default class Connection {
|
|
19
|
+
name: string;
|
|
20
|
+
version: number;
|
|
21
|
+
public idbDatabase?: IDBDatabase; //数据库连接
|
|
22
|
+
readonly tableMap: Map<string, Table>;
|
|
23
|
+
private eventMap: Partial<EventMap> = {};
|
|
24
|
+
|
|
25
|
+
constructor(database: string = 'default', version: number = 1) {
|
|
26
|
+
this.name = database;
|
|
27
|
+
this.version = version;
|
|
28
|
+
this.tableMap = new Map<string, Table>();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
on<T extends EventKey>(event: T, handle: EventMap[T]) {
|
|
32
|
+
this.eventMap[event] = handle;
|
|
33
|
+
return this
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get connected() {
|
|
37
|
+
return this.idbDatabase != void 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async connect(name?: string, version?: number, upgrade?: (idbDatabase: IDBDatabase, event: IDBVersionChangeEvent) => any): Promise<any> {
|
|
41
|
+
if ((!name || version == void 0) && this.connected) {
|
|
42
|
+
return Promise.resolve(this);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.name = name || this.name;
|
|
46
|
+
this.version = version || this.version;
|
|
47
|
+
logger.debug(`Connect to database ${this.name} ${this.version}`)
|
|
48
|
+
const request: IDBOpenDBRequest = innerDB.open(this.name, version);
|
|
49
|
+
this.eventMap.process?.call(this, IndexdbStatus.CONNECT_ING);
|
|
50
|
+
|
|
51
|
+
request.onblocked = (evt: IDBVersionChangeEvent) => {
|
|
52
|
+
logger.warn(`onblocked change from ${evt.oldVersion} to ${evt.newVersion}`, evt);
|
|
53
|
+
this.eventMap.process?.call(this, IndexdbStatus.CONNECT_CLOSE);
|
|
54
|
+
this.eventMap.blocked?.call(this, evt);
|
|
55
|
+
};
|
|
56
|
+
let returnVal = void 0;
|
|
57
|
+
request.onupgradeneeded = (event: IDBVersionChangeEvent) => {
|
|
58
|
+
logger.info(`Version change from <${event.oldVersion}> to <${event.newVersion}>`, event)
|
|
59
|
+
this.eventMap.process?.call(this, IndexdbStatus.VERSION_CHANGE);
|
|
60
|
+
const target = event.target as IDBOpenDBRequest;
|
|
61
|
+
this.idbDatabase = target.result;
|
|
62
|
+
this.eventMap.change?.call(this, this.idbDatabase, event);
|
|
63
|
+
returnVal = upgrade?.(this.idbDatabase, event);
|
|
64
|
+
};
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
request.onerror = (e: Event) => {
|
|
67
|
+
logger.error('onerror', e);
|
|
68
|
+
this.eventMap.process?.call(this, IndexdbStatus.CONNECT_ERROR);
|
|
69
|
+
this.eventMap.error?.call(this, e)
|
|
70
|
+
reject(e);
|
|
71
|
+
};
|
|
72
|
+
request.onsuccess = (e: Event) => {
|
|
73
|
+
logger.debug(`Connect Success`, e);
|
|
74
|
+
const target = e.target as IDBOpenDBRequest;
|
|
75
|
+
let connect: IDBDatabase = target.result;
|
|
76
|
+
this.idbDatabase = connect;
|
|
77
|
+
let objectStoreNames = connect.objectStoreNames;
|
|
78
|
+
if (objectStoreNames.length) {
|
|
79
|
+
let transaction = connect.transaction(objectStoreNames);
|
|
80
|
+
for (let i = 0; i < objectStoreNames.length; i++) {
|
|
81
|
+
let table = objectStoreNames.item(i);
|
|
82
|
+
if (!table) continue;
|
|
83
|
+
let objectStore = transaction.objectStore(table);
|
|
84
|
+
let {indexNames, keyPath: primaryKey, name, autoIncrement = true} = objectStore;
|
|
85
|
+
let indexes: Array<Index> = [];
|
|
86
|
+
for (let j = 0; indexNames && j < indexNames.length; j++) {
|
|
87
|
+
let indexName = indexNames.item(j);
|
|
88
|
+
if (!indexName) continue;
|
|
89
|
+
let {keyPath, name, multiEntry, unique} = objectStore.index(indexName);
|
|
90
|
+
indexes.push({name, column: keyPath, unique, multiEntry});
|
|
91
|
+
}
|
|
92
|
+
this.tableMap.set(name, new Table({
|
|
93
|
+
name,
|
|
94
|
+
primaryKey: primaryKey == null ? void 0 : primaryKey,
|
|
95
|
+
autoIncrement,
|
|
96
|
+
indexes
|
|
97
|
+
}, this.idbDatabase));
|
|
98
|
+
}
|
|
99
|
+
transaction.commit()
|
|
100
|
+
}
|
|
101
|
+
this.eventMap.process?.call(this, IndexdbStatus.CONNECT_SUCCESS);
|
|
102
|
+
this.eventMap.success?.call(this, this.idbDatabase);
|
|
103
|
+
resolve(returnVal || this);
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
drop() {
|
|
109
|
+
return new Promise<Event>((resolve, reject) => {
|
|
110
|
+
let request = innerDB.deleteDatabase(this.name);
|
|
111
|
+
request.onerror = function (event: Event) {
|
|
112
|
+
reject(event)
|
|
113
|
+
};
|
|
114
|
+
request.onsuccess = function (event: Event) {
|
|
115
|
+
resolve(event)
|
|
116
|
+
};
|
|
117
|
+
this.close();
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
close() {
|
|
122
|
+
if (this.connected) {
|
|
123
|
+
this.eventMap.process?.call(this, IndexdbStatus.CONNECT_CLOSE);
|
|
124
|
+
this.idbDatabase?.close();
|
|
125
|
+
this.eventMap.close?.call(this);
|
|
126
|
+
this.idbDatabase = void 0;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 只能在版本发生变化的时候调用 即在事件change里面调用
|
|
132
|
+
* @param {string} tableName 表名
|
|
133
|
+
* @param meta 表结构信息
|
|
134
|
+
* @param idbDatabase
|
|
135
|
+
*/
|
|
136
|
+
async createTable(tableName: string, meta: Omit<TableMeta, 'name'> & {
|
|
137
|
+
overwrite?: boolean
|
|
138
|
+
} = {}, idbDatabase?: IDBDatabase): Promise<Table> {
|
|
139
|
+
const {overwrite = false} = meta;
|
|
140
|
+
if (!overwrite && this.exists(tableName)) {
|
|
141
|
+
throw new Error(`Table ${tableName} is exists`);
|
|
142
|
+
}
|
|
143
|
+
const metadata: TableMeta = {...meta, name: tableName};
|
|
144
|
+
let {name = '', primaryKey = 'id', autoIncrement = true, indexes = [], rows = []} = metadata;
|
|
145
|
+
const store = (idbDatabase || this.idbDatabase)?.createObjectStore(name, {keyPath: primaryKey, autoIncrement});
|
|
146
|
+
let idxes: Array<Index> = indexes.map(({name, column, unique, multiEntry}) => {
|
|
147
|
+
store?.createIndex(name, column, {unique, multiEntry});
|
|
148
|
+
return {name, column, unique, multiEntry}
|
|
149
|
+
});
|
|
150
|
+
metadata.rows?.forEach(row => store?.add(row));
|
|
151
|
+
let table = new Table({name, primaryKey, autoIncrement, indexes: idxes}, idbDatabase || this.idbDatabase);
|
|
152
|
+
this.tableMap.set(name, table);
|
|
153
|
+
return table;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 批量建表 只能在版本发生变化的时候调用 即在事件change里面调用
|
|
159
|
+
* @param tables 表配置信息
|
|
160
|
+
* @param meta
|
|
161
|
+
* @param idbDatabase
|
|
162
|
+
*/
|
|
163
|
+
async createTables(tables: Array<string | (TableMeta & { overwrite?: boolean })>, meta: Omit<ITable, 'name'> & {
|
|
164
|
+
overwrite?: boolean
|
|
165
|
+
} = {}, idbDatabase: IDBDatabase) {
|
|
166
|
+
return new Promise<Array<Table>>((resolve) => {
|
|
167
|
+
const createTables = async (idbDatabase: IDBDatabase) => {
|
|
168
|
+
let list: Array<Table> = await Promise.all(tables.map(table => {
|
|
169
|
+
if (typeof table === 'string') {
|
|
170
|
+
return this.createTable(table, meta, idbDatabase);
|
|
171
|
+
} else {
|
|
172
|
+
return this.createTable(table.name, {...meta, ...table}, idbDatabase);
|
|
173
|
+
}
|
|
174
|
+
}));
|
|
175
|
+
resolve(list);
|
|
176
|
+
}
|
|
177
|
+
if (idbDatabase) {
|
|
178
|
+
createTables(idbDatabase);
|
|
179
|
+
} else {
|
|
180
|
+
this.connect(this.name, ++this.version, createTables);
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* 只能在版本发生变化的时候调用 即在事件change里面调用
|
|
187
|
+
* @param tableName
|
|
188
|
+
*/
|
|
189
|
+
dropTable(tableName: string) {
|
|
190
|
+
if (this.exists(tableName)) {
|
|
191
|
+
this.idbDatabase?.deleteObjectStore(tableName);
|
|
192
|
+
this.tableMap.delete(tableName);
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
logger.error(`Table ${tableName} is not exists`)
|
|
196
|
+
throw new Error(`Table ${tableName} is not exists`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
*
|
|
201
|
+
* @param tableName
|
|
202
|
+
* @return Table
|
|
203
|
+
*/
|
|
204
|
+
table(tableName: string) {
|
|
205
|
+
const table = this.tableMap.get(tableName);
|
|
206
|
+
if (!table) {
|
|
207
|
+
logger.error(`Table ${tableName} is not exists`)
|
|
208
|
+
throw new Error(`Table ${tableName} is not exists`);
|
|
209
|
+
}
|
|
210
|
+
return table;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
exists(table: string) {
|
|
214
|
+
return this.tableMap.has(table);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
getTables() {
|
|
218
|
+
return [...this.tableMap.values()];
|
|
219
|
+
}
|
|
220
|
+
}
|
package/src/const.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
let win: any = window;
|
|
2
|
+
|
|
3
|
+
export const innerDB: IDBFactory = win.indexedDB || win.mozIndexedDB || win.webkitIndexedDB || win.msIndexedDB;
|
|
4
|
+
|
|
5
|
+
export enum IndexdbStatus {
|
|
6
|
+
CONNECT_ING=0,
|
|
7
|
+
CONNECT_SUCCESS=1,
|
|
8
|
+
CONNECT_ERROR=2,
|
|
9
|
+
CONNECT_CHANGE=3,
|
|
10
|
+
CONNECT_CLOSE=4,
|
|
11
|
+
DATABASE_ERROR=5,
|
|
12
|
+
VERSION_CHANGE=99,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class KeyRange {
|
|
16
|
+
static leq(val: any) {
|
|
17
|
+
return IDBKeyRange.upperBound(val) //All keys ≤ x
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static lt(val: any) {
|
|
21
|
+
return IDBKeyRange.upperBound(val, true) //keys < x
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static geq(val: any) {
|
|
25
|
+
return IDBKeyRange.lowerBound(val)//keys ≥ x
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static gt(val: any) {
|
|
29
|
+
return IDBKeyRange.lowerBound(val, true) //keys > x
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static between(low: any, upper: any) {
|
|
33
|
+
return IDBKeyRange.bound(low, upper)//keys ≥ x && ≤ y
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static gt_lt(low: any, upper: any) {
|
|
37
|
+
return IDBKeyRange.bound(low, upper, true, true)//keys > x &&< y
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static gt_leq(low: any, upper: any) {
|
|
41
|
+
return IDBKeyRange.bound(low, upper, true, false)//keys > x && ≤ y
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static geq_lt(low: any, upper: any) {
|
|
45
|
+
return IDBKeyRange.bound(low, upper, false, true)//keys ≥ x &&< y
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static eq(val: any) {
|
|
49
|
+
return IDBKeyRange.only(val)
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/database.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import Connection from "./connection";
|
|
2
|
+
import {Row, Pagination, RowPacket} from "./table";
|
|
3
|
+
|
|
4
|
+
export default class Database extends Connection {
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 查询数据
|
|
8
|
+
* @param table 表名
|
|
9
|
+
* @param query 查询条件
|
|
10
|
+
* @param count 查询数量
|
|
11
|
+
*/
|
|
12
|
+
query<R extends Row>(table: string, query?: IDBValidKey | IDBKeyRange, count: number = 1000): Promise<RowPacket<R>> {
|
|
13
|
+
return this.table(table).query(query, count)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 查询数据
|
|
18
|
+
* @param table 表名
|
|
19
|
+
* @param key 查询条件
|
|
20
|
+
* @param index 索引名称
|
|
21
|
+
* @param count 查询数量
|
|
22
|
+
*/
|
|
23
|
+
select<R extends Row>(table: string, key?: IDBValidKey | IDBKeyRange, index?: string, count: number = 1000): Promise<RowPacket<R>> {
|
|
24
|
+
return this.table(table).select(key, count, index)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 获取数据
|
|
29
|
+
* @param table 表名
|
|
30
|
+
* @param key 查询条件
|
|
31
|
+
*/
|
|
32
|
+
queryByKey<R extends Row>(table: string, key: IDBValidKey | IDBKeyRange): Promise<RowPacket<R>> {
|
|
33
|
+
return this.table(table).queryByKey(key)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
*
|
|
38
|
+
* @param table
|
|
39
|
+
* @param indexName 索引名称
|
|
40
|
+
* @param key key or keyRange (对应的是索引包含的列的数据)
|
|
41
|
+
* @param count 返回的列数
|
|
42
|
+
*/
|
|
43
|
+
async queryByIndex<R extends Row>(table: string, indexName: string, key?: IDBValidKey | IDBKeyRange, count?: number): Promise<RowPacket<R>> {
|
|
44
|
+
return this.table(table).queryByIndex(indexName, key, count)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 获取数据
|
|
49
|
+
* @param table
|
|
50
|
+
* @param id
|
|
51
|
+
* @param indexName
|
|
52
|
+
*/
|
|
53
|
+
async queryById<R extends Row>(table: string, id: any | Array<any>, indexName?: string): Promise<R> {
|
|
54
|
+
return this.table(table).queryById(id, indexName);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 获取数据
|
|
59
|
+
* @param table 表名
|
|
60
|
+
* @param key 查询条件
|
|
61
|
+
*/
|
|
62
|
+
count(table: string, key: IDBValidKey | IDBKeyRange) {
|
|
63
|
+
return this.table(table).count(key)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 插入数据
|
|
68
|
+
* @param table
|
|
69
|
+
* @param data
|
|
70
|
+
*/
|
|
71
|
+
insert(table: string, data: Array<Record<string, any>>) {
|
|
72
|
+
return this.table(table).insert(data)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 更新数据
|
|
77
|
+
* @param table 表名
|
|
78
|
+
* @param modify 更新内容
|
|
79
|
+
* @param key 条件
|
|
80
|
+
*/
|
|
81
|
+
update(table: string, modify: Row, key?: IDBValidKey | IDBKeyRange | null) {
|
|
82
|
+
return this.table(table).update(modify, key)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 删除数据
|
|
87
|
+
* @param table 表名
|
|
88
|
+
* @param key 索引名称
|
|
89
|
+
*/
|
|
90
|
+
delete(table: string, key: IDBValidKey | IDBKeyRange) {
|
|
91
|
+
return this.table(table).delete(key)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 清空表
|
|
96
|
+
* @param table 表名
|
|
97
|
+
*/
|
|
98
|
+
truncate(table: string) {
|
|
99
|
+
return this.table(table).truncate()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 获取内存分页模型对象
|
|
104
|
+
* @param table
|
|
105
|
+
* @param pageSize
|
|
106
|
+
*/
|
|
107
|
+
getPagination<R extends Row>(table: string, pageSize: number = 10): Promise<Pagination<R>> {
|
|
108
|
+
return this.table(table).getPagination<R>(pageSize, 1)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 扫描数据
|
|
113
|
+
* @param table 表名
|
|
114
|
+
* @param key key or keyRange (对应的是主键的数据)
|
|
115
|
+
* @param direction 游标方向
|
|
116
|
+
* @param indexName 索引名称
|
|
117
|
+
*/
|
|
118
|
+
async scan<R extends Row>(table: string, key?: IDBValidKey | IDBKeyRange, direction: IDBCursorDirection = 'next', indexName?: string): Promise<RowPacket<R>> {
|
|
119
|
+
return this.table(table).scan(key, direction, indexName)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 获取所有数据
|
|
124
|
+
* @param table
|
|
125
|
+
*/
|
|
126
|
+
getAllData<R extends Row>(table: string): Promise<Array<R>> {
|
|
127
|
+
return this.table(table).getAllData();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 分页查询
|
|
132
|
+
* @param table
|
|
133
|
+
* @param pageNo 页码
|
|
134
|
+
* @param pageSize 每页数量
|
|
135
|
+
* @param key (对应的是主键的数据)
|
|
136
|
+
* @param indexName 索引名称
|
|
137
|
+
*/
|
|
138
|
+
async paginate<R extends Row>(table: string, pageNo: number = 1, pageSize: number = 10, key?: IDBValidKey | IDBKeyRange, indexName?: string): Promise<RowPacket<R>> {
|
|
139
|
+
return this.table(table).paginate(pageNo, pageSize, key, indexName)
|
|
140
|
+
}
|
|
141
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import {IndexdbStatus, innerDB, KeyRange} from "./const";
|
|
2
|
+
import Database from "./database";
|
|
3
|
+
import Connection from "./connection";
|
|
4
|
+
import Table, {TableMeta} from "./table";
|
|
5
|
+
import {Logger} from "./com";
|
|
6
|
+
|
|
7
|
+
export { IndexdbStatus, KeyRange, Connection, Table};
|
|
8
|
+
|
|
9
|
+
const logger = Logger.getLogger("Driver")
|
|
10
|
+
|
|
11
|
+
class Driver {
|
|
12
|
+
private readonly name: string;
|
|
13
|
+
private version: number;
|
|
14
|
+
private readonly tables: Array<TableMeta> = [];
|
|
15
|
+
|
|
16
|
+
constructor(name: string, version: number = 0) {
|
|
17
|
+
this.name = name;
|
|
18
|
+
this.version = version;
|
|
19
|
+
}
|
|
20
|
+
defineTables(...tables: Array<TableMeta>): this {
|
|
21
|
+
this.tables.push(...tables);
|
|
22
|
+
logger.debug(`Define tables: ${tables.map(t => t.name).join(', ')}`);
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private async hasChange(database: Database): Promise<boolean> {
|
|
27
|
+
// 获取表列表
|
|
28
|
+
const existTables = database.getTables()
|
|
29
|
+
const defineTables = this.tables;
|
|
30
|
+
if (existTables.length !== defineTables.length) {
|
|
31
|
+
logger.warn(`Table count is changed.`);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
let change = false;
|
|
35
|
+
for (let table of defineTables) {
|
|
36
|
+
if (!existTables.some(t => t.name === table.name)) {
|
|
37
|
+
logger.warn(`Table ${table.name} is added, create it.`);
|
|
38
|
+
change = true;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (change) {
|
|
43
|
+
return change;
|
|
44
|
+
}
|
|
45
|
+
// 对比表是否发生变化
|
|
46
|
+
for (let existTable of existTables) {
|
|
47
|
+
const tableMeta = defineTables.find(t => t.name === existTable.name);
|
|
48
|
+
// 如果定义的表里面找不到此表 表示以删除
|
|
49
|
+
if (!tableMeta) {
|
|
50
|
+
logger.warn(`Table ${existTable.name} is removed, drop it.`);
|
|
51
|
+
change = true;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
let existIndexes = existTable.indexes || [];
|
|
55
|
+
for (let existIndex of existIndexes) {
|
|
56
|
+
const metaIndex = tableMeta.indexes?.find(i => i.name === existIndex.name);
|
|
57
|
+
if (!metaIndex) {
|
|
58
|
+
logger.warn(`Table ${existTable.name} index ${existIndex.name} is removed, drop it.`);
|
|
59
|
+
change = true;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (change) {
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
for (let metaIndex of tableMeta.indexes || []) {
|
|
68
|
+
const index = existIndexes.find(i => i.name === metaIndex.name);
|
|
69
|
+
if (!index) {
|
|
70
|
+
logger.warn(`Table ${existTable.name} index ${metaIndex.name} is added, create it.`);
|
|
71
|
+
change = true;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (change) {
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return change;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async connect(version?: number): Promise<Database> {
|
|
85
|
+
const databases = await innerDB.databases();
|
|
86
|
+
|
|
87
|
+
const existsDB = databases.find(db => db.name === this.name);
|
|
88
|
+
if (existsDB) {
|
|
89
|
+
logger.info(`Database ${this.name} exists`);
|
|
90
|
+
this.version = existsDB.version || this.version;
|
|
91
|
+
}
|
|
92
|
+
if (version) {
|
|
93
|
+
this.version = Math.max(this.version, version);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
logger.debug(`Connect to database ${this.name} ${this.version}`);
|
|
97
|
+
const database = new Database(this.name, this.version);
|
|
98
|
+
database.on('change', (idbDatabase, event) => {
|
|
99
|
+
|
|
100
|
+
const existTables = [...idbDatabase.objectStoreNames];
|
|
101
|
+
if (existTables.length) {
|
|
102
|
+
for (let tableName of existTables) {
|
|
103
|
+
let existTable = this.tables.find(t => t.name === tableName);
|
|
104
|
+
if (!existTable) { // 如果定义中删除
|
|
105
|
+
logger.warn(`Table ${tableName} is removed, drop it.`);
|
|
106
|
+
database.dropTable(tableName);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const transaction = (event.target as IDBOpenDBRequest).transaction;
|
|
110
|
+
if (!transaction) {
|
|
111
|
+
logger.warn(`Transaction not support`);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
let objectStore = transaction.objectStore(tableName);
|
|
115
|
+
if (!objectStore) {
|
|
116
|
+
logger.warn(`Table ${tableName} not exists`);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
let {indexNames} = objectStore;
|
|
120
|
+
// 删除所有索引,重新创建
|
|
121
|
+
for (let indexName of indexNames) {
|
|
122
|
+
objectStore.deleteIndex(indexName);
|
|
123
|
+
}
|
|
124
|
+
for (let index of existTable.indexes || []) {
|
|
125
|
+
objectStore.createIndex(index.name, index.column, {...index});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
for (let table of this.tables) {
|
|
130
|
+
if (existTables.indexOf(table.name) < 0) {
|
|
131
|
+
logger.info(`Table ${table.name} is not exist, create it.`);
|
|
132
|
+
database.createTable(table.name, table);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
await database.connect();
|
|
137
|
+
|
|
138
|
+
if (await this.hasChange(database)) {
|
|
139
|
+
database.close();
|
|
140
|
+
await database.connect(this.name, ++this.version)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return database;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export default Driver;
|
package/src/table.ts
ADDED
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
import {Logger} from "./com";
|
|
2
|
+
|
|
3
|
+
const logger = Logger.getLogger('Table');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const _getObjectStore = (table: Table, mode: IDBTransactionMode = 'readonly') => {
|
|
7
|
+
const trans = table.database?.transaction(table.name, mode);
|
|
8
|
+
if (!trans) {
|
|
9
|
+
throw new Error('database not open');
|
|
10
|
+
}
|
|
11
|
+
trans.oncomplete = (event: any) => {
|
|
12
|
+
logger.debug(`Table ${table.name} transaction success`, event);
|
|
13
|
+
}
|
|
14
|
+
trans.onerror = (event: any) => {
|
|
15
|
+
logger.error(`Table ${table.name} transaction error`, event)
|
|
16
|
+
};
|
|
17
|
+
return trans.objectStore(table.name);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const _request = async <T> (request: IDBRequest<T>): Promise<T> => {
|
|
21
|
+
return new Promise<any>((resolve, reject) => {
|
|
22
|
+
request.onsuccess = (e: any) => {
|
|
23
|
+
resolve(e.target.result)
|
|
24
|
+
};
|
|
25
|
+
request.onerror = (e: any) => {
|
|
26
|
+
reject(e.target.error)
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
export type Index = {
|
|
33
|
+
name: string;
|
|
34
|
+
column: Array<string> | string;
|
|
35
|
+
unique?: boolean;
|
|
36
|
+
multiEntry?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type ITable = {
|
|
40
|
+
name: string;
|
|
41
|
+
primaryKey?: string | string[];
|
|
42
|
+
autoIncrement?: boolean;
|
|
43
|
+
indexes?: Array<Index>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type Row = Record<string, any>;
|
|
47
|
+
|
|
48
|
+
export type Rows = Array<Row>;
|
|
49
|
+
|
|
50
|
+
export type TableMeta = ITable & { rows?: Rows };
|
|
51
|
+
|
|
52
|
+
export class RowPacket<T extends Row> extends Array<T> {
|
|
53
|
+
|
|
54
|
+
constructor(rows: Array<T> = []) {
|
|
55
|
+
super(...rows)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 去重
|
|
60
|
+
* @param column
|
|
61
|
+
*/
|
|
62
|
+
distinct(column?: string | ((row: T) => any)): Array<T> {
|
|
63
|
+
if (!column) {
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
let set = new Set<any>();
|
|
67
|
+
return this.filter(row => {
|
|
68
|
+
let union = null;
|
|
69
|
+
if (column !== void 0) {
|
|
70
|
+
union = typeof column === 'function' ? column(row) : row[column];
|
|
71
|
+
} else {
|
|
72
|
+
union = row;
|
|
73
|
+
}
|
|
74
|
+
if (!set.has(union)) {
|
|
75
|
+
set.add(union);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 数组转map
|
|
84
|
+
*/
|
|
85
|
+
mapping<K, T>(keyFn: (row: T, index: number, array: Array<T>) => K): Map<K, T>;
|
|
86
|
+
mapping<K, V>(keyFn: (row: T, index: number, array: Array<T>) => K,
|
|
87
|
+
valFn: (row: T, index: number, array: Array<T>) => V): Map<K, V>;
|
|
88
|
+
mapping<K, V>(keyFn: (row: T, index: number, array: Array<T>) => K,
|
|
89
|
+
valFn?: (row: T, index: number, array: Array<T>) => V): Map<K, V | T> {
|
|
90
|
+
if (!keyFn) throw new Error('Undefined group by field');
|
|
91
|
+
let mapping = new Map<K, V | T>();
|
|
92
|
+
this.forEach((item, index, array) => {
|
|
93
|
+
let unionKey: K = keyFn.call(this, item, index, array);
|
|
94
|
+
if (mapping.has(unionKey)) {
|
|
95
|
+
throw new Error(`${unionKey} duplicate error`);
|
|
96
|
+
}
|
|
97
|
+
mapping.set(unionKey, valFn ? valFn(item, index, array) : item);
|
|
98
|
+
});
|
|
99
|
+
return mapping;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 按指定key分组
|
|
104
|
+
*/
|
|
105
|
+
group<K, V>(key: keyof T | ((row: T, index: number, array: Array<T>) => K)): Map<K, Array<T>>;
|
|
106
|
+
group<K, V>(key: keyof T | ((row: T, index: number, array: Array<T>) => K),
|
|
107
|
+
valFn: (row: T, index: number, array: Array<T>) => V ): Map<K, Array<V>>;
|
|
108
|
+
group<K, V>(key: keyof T | ((row: T, index: number, array: Array<T>) => K),
|
|
109
|
+
valFn?: (row: T, index: number, array: Array<T>) => V ): Map<K, Array<T | V>> {
|
|
110
|
+
if (!key) throw new Error('Undefined group by field');
|
|
111
|
+
let group = new Map<K, Array<V | T>>();
|
|
112
|
+
this.forEach((item, index, array) => {
|
|
113
|
+
let unionKey: K = typeof key === 'function' ? key.call(this, item, index, array) : item[key];
|
|
114
|
+
let value = valFn ? valFn(item, index, this) : item;
|
|
115
|
+
if (!group.has(unionKey)) {
|
|
116
|
+
group.set(unionKey, [value]);
|
|
117
|
+
} else {
|
|
118
|
+
group.get(unionKey)?.push(value);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
return group;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 找出符合条件的最大行
|
|
126
|
+
* @param keyFn
|
|
127
|
+
*/
|
|
128
|
+
max(keyFn: keyof T | ((row: T, index: number, array: Array<T>) => number)): {
|
|
129
|
+
value: number;
|
|
130
|
+
rows: Array<T>
|
|
131
|
+
} {
|
|
132
|
+
let max = 0;
|
|
133
|
+
let maxRows: Array<T> = [];
|
|
134
|
+
this.forEach((item, index, array) => {
|
|
135
|
+
let unionKey: number = typeof keyFn === 'function' ? keyFn.call(this, item, index, array) : item[keyFn];
|
|
136
|
+
if (unionKey > max) {
|
|
137
|
+
max = unionKey;
|
|
138
|
+
maxRows = [item];
|
|
139
|
+
} else if (unionKey === max) {
|
|
140
|
+
maxRows.push(item);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
return {
|
|
144
|
+
value: max,
|
|
145
|
+
rows: maxRows
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 筛选数组最小值
|
|
151
|
+
*/
|
|
152
|
+
min(keyFn: keyof T | ((row: T, index: number, array: Array<T>) => number)): {
|
|
153
|
+
value: number;
|
|
154
|
+
rows: Array<T>
|
|
155
|
+
} {
|
|
156
|
+
let min = 0;
|
|
157
|
+
let minRows: Array<T> = [];
|
|
158
|
+
this.forEach((item, index, array) => {
|
|
159
|
+
let unionKey: number = typeof keyFn === 'function' ? keyFn.call(this, item, index, array) : item[keyFn];
|
|
160
|
+
if (unionKey < min) {
|
|
161
|
+
min = unionKey;
|
|
162
|
+
minRows = [item];
|
|
163
|
+
} else if (unionKey === min) {
|
|
164
|
+
minRows.push(item);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
return {
|
|
168
|
+
value: min,
|
|
169
|
+
rows: minRows
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* 求和
|
|
175
|
+
* @param keyFn
|
|
176
|
+
*/
|
|
177
|
+
sum(keyFn: keyof T | ((row: T, index: number, array: Array<T>) => number)): number {
|
|
178
|
+
return this.reduce((sum, item, index, array) => {
|
|
179
|
+
return sum + (typeof keyFn === 'function' ? keyFn.call(this, item, index, array) : item[keyFn]);
|
|
180
|
+
}, 0)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* 获取平均值
|
|
185
|
+
* @param keyFn
|
|
186
|
+
*/
|
|
187
|
+
average(keyFn: keyof T | ((row: T, index: number, array: Array<T>) => number)): number {
|
|
188
|
+
return this.sum(keyFn) / this.length;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* 分页模型 包含所有数据,通过分页索引和分页大小进行数据截取
|
|
195
|
+
*/
|
|
196
|
+
export class Pagination<T extends Row> {
|
|
197
|
+
/**
|
|
198
|
+
* 数据源
|
|
199
|
+
* @private
|
|
200
|
+
*/
|
|
201
|
+
private readonly _data: Array<T>;
|
|
202
|
+
/**
|
|
203
|
+
* 分页大小
|
|
204
|
+
* @private
|
|
205
|
+
*/
|
|
206
|
+
private readonly _pageSize: number;
|
|
207
|
+
/**
|
|
208
|
+
* 当前页索引 默认1
|
|
209
|
+
* @private
|
|
210
|
+
*/
|
|
211
|
+
private _pageIndex: number;
|
|
212
|
+
/**
|
|
213
|
+
* 总数据量
|
|
214
|
+
* @private
|
|
215
|
+
*/
|
|
216
|
+
private readonly _total: number;
|
|
217
|
+
|
|
218
|
+
constructor(data: Array<T>, pageSize: number = 10, pageIndex: number = 1) {
|
|
219
|
+
this._data = data;
|
|
220
|
+
this._pageSize = pageSize < 1 ? 10 : pageSize;
|
|
221
|
+
this._pageIndex = pageIndex < 0 ? 1 : pageIndex;
|
|
222
|
+
this._total = data.length;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* 总页数
|
|
227
|
+
*/
|
|
228
|
+
get totalPage(): number {
|
|
229
|
+
return Math.ceil(this._total / this._pageSize);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* 总数据量
|
|
234
|
+
*/
|
|
235
|
+
get total(): number {
|
|
236
|
+
return this._total;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* 当前页索引
|
|
240
|
+
*/
|
|
241
|
+
get pageIndex(): number {
|
|
242
|
+
return this._pageIndex;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* 当前页大小
|
|
246
|
+
*/
|
|
247
|
+
get pageSize(): number {
|
|
248
|
+
return this._pageSize;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* 是否有下一页
|
|
252
|
+
*/
|
|
253
|
+
get hasNext(): boolean {
|
|
254
|
+
return this._pageIndex * this._pageSize < this._total;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* 是否有上一页
|
|
259
|
+
*/
|
|
260
|
+
get hasPrev(): boolean {
|
|
261
|
+
return this._pageIndex > 1;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* 当前页数据
|
|
266
|
+
*/
|
|
267
|
+
get data(): Array<any> {
|
|
268
|
+
let start = (this._pageIndex - 1) * this._pageSize;
|
|
269
|
+
let end = start + this._pageSize;
|
|
270
|
+
return this._data.slice(start, end);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* 上一页
|
|
274
|
+
*/
|
|
275
|
+
prev(): boolean {
|
|
276
|
+
if (this.hasPrev) {
|
|
277
|
+
this._pageIndex--;
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* 下一页
|
|
284
|
+
*/
|
|
285
|
+
next(): boolean {
|
|
286
|
+
if (this.hasNext) {
|
|
287
|
+
this._pageIndex++;
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export default class Table implements ITable{
|
|
295
|
+
readonly name : string;
|
|
296
|
+
readonly primaryKey: string | string[] = "id";
|
|
297
|
+
readonly autoIncrement: boolean = false;
|
|
298
|
+
readonly indexes: Array<Index> = [];
|
|
299
|
+
readonly database?: IDBDatabase;
|
|
300
|
+
/**
|
|
301
|
+
*
|
|
302
|
+
* @param table
|
|
303
|
+
* @param {Database} database 数据库对象 @see Database
|
|
304
|
+
*/
|
|
305
|
+
constructor(table: ITable | string, database?: IDBDatabase) {
|
|
306
|
+
let tableMeta: TableMeta;
|
|
307
|
+
if (typeof table === 'string') {
|
|
308
|
+
tableMeta = {name: table}
|
|
309
|
+
} else {
|
|
310
|
+
tableMeta = {...table};
|
|
311
|
+
}
|
|
312
|
+
const {name = '', primaryKey = 'id', autoIncrement = true, indexes = []} = tableMeta;
|
|
313
|
+
this.name = name;
|
|
314
|
+
this.primaryKey = primaryKey;
|
|
315
|
+
this.autoIncrement = autoIncrement;
|
|
316
|
+
this.database = database;
|
|
317
|
+
this.indexes = (indexes || []).map(idx => {
|
|
318
|
+
return {
|
|
319
|
+
unique: false,
|
|
320
|
+
multiEntry: false,
|
|
321
|
+
...idx
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
get meta(): TableMeta {
|
|
327
|
+
return {
|
|
328
|
+
name: this.name,
|
|
329
|
+
primaryKey: this.primaryKey,
|
|
330
|
+
autoIncrement: this.autoIncrement,
|
|
331
|
+
indexes: this.indexes
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
*
|
|
337
|
+
* 插入数据 对象或者数据数组 如果数据中包含主键,则会查找是否存在,如果存在则会报错
|
|
338
|
+
* @param data 数据
|
|
339
|
+
*/
|
|
340
|
+
async insert(data: Row | Rows): Promise<Array<IDBValidKey>> {
|
|
341
|
+
const store = _getObjectStore(this, 'readwrite');
|
|
342
|
+
let results: Array<any> = [];
|
|
343
|
+
let rows: Rows = Array.isArray(data) ? data : [data];
|
|
344
|
+
for (let row of rows) {
|
|
345
|
+
let result = await _request(store.add(row));
|
|
346
|
+
results.push(result);
|
|
347
|
+
}
|
|
348
|
+
return results
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* 更新modify 如果对象中有key会自动从对象中提取key 如果没有key会变成新增数据
|
|
353
|
+
* @param modify 更新内容
|
|
354
|
+
* @param key 条件
|
|
355
|
+
*/
|
|
356
|
+
async update(modify: Row, key?: IDBValidKey | IDBKeyRange | null): Promise<IDBValidKey> {
|
|
357
|
+
const store = _getObjectStore(this, 'readwrite');
|
|
358
|
+
return _request(store.put(modify, key as IDBValidKey));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* 根据主键值删除
|
|
363
|
+
* @param key
|
|
364
|
+
*/
|
|
365
|
+
async delete(key: IDBValidKey | IDBKeyRange) {
|
|
366
|
+
if (!key) {
|
|
367
|
+
return Promise.reject('key is required');
|
|
368
|
+
}
|
|
369
|
+
let objectStore = _getObjectStore(this, 'readwrite');
|
|
370
|
+
let rows = await _request(objectStore.getAll(key));
|
|
371
|
+
await _request(objectStore.delete(key));
|
|
372
|
+
return rows;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* 清空表
|
|
377
|
+
*/
|
|
378
|
+
truncate (): Promise<void> {
|
|
379
|
+
return _request(_getObjectStore(this, 'readwrite').clear())
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* 根据索引名称删除索引
|
|
384
|
+
* @param name
|
|
385
|
+
*/
|
|
386
|
+
dropIndex (name:string) {
|
|
387
|
+
return _getObjectStore(this, 'readwrite').deleteIndex(name)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* 获取指定范围内的主键列表
|
|
392
|
+
* @param key @see{@link IDBKeyRange | IDBValidKey}
|
|
393
|
+
* @param count 获取数量
|
|
394
|
+
*/
|
|
395
|
+
keys (key?: IDBValidKey | IDBKeyRange | null, count?: number) {
|
|
396
|
+
return _request(_getObjectStore(this).getAllKeys(key, count));
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* 统计数量
|
|
401
|
+
* @param key
|
|
402
|
+
*/
|
|
403
|
+
count(key?: IDBValidKey | IDBKeyRange) {
|
|
404
|
+
return _request(_getObjectStore(this).count(key))
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
*
|
|
409
|
+
* @param key
|
|
410
|
+
* @param count
|
|
411
|
+
*/
|
|
412
|
+
async query<R extends Row>(key?: IDBValidKey | IDBKeyRange, count?: number): Promise<RowPacket<R>> {
|
|
413
|
+
let rows = await _request(_getObjectStore(this).getAll(key, count));
|
|
414
|
+
return new RowPacket<R>(rows);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
*
|
|
419
|
+
* @param indexName 索引名称
|
|
420
|
+
* @param key key or keyRange (对应的是索引包含的列的数据)
|
|
421
|
+
* @param count 返回的列数
|
|
422
|
+
*/
|
|
423
|
+
async queryByIndex<R extends Row>(indexName: string, key?: IDBValidKey | IDBKeyRange, count?: number): Promise<RowPacket<R>> {
|
|
424
|
+
const store = _getObjectStore(this);
|
|
425
|
+
const index = store.index(indexName);
|
|
426
|
+
let rows: Array<any> = await _request(index.getAll(key || null, count));
|
|
427
|
+
return new RowPacket<R>(rows);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
*
|
|
432
|
+
* @param key 数据条件 如果是联合索引则需要指定每个索引列的值,以数组方式
|
|
433
|
+
* @param count 数量
|
|
434
|
+
* @param indexName 索引名称
|
|
435
|
+
*/
|
|
436
|
+
select<R extends Row>(key?: IDBValidKey | IDBKeyRange, count?: number, indexName?: string): Promise<RowPacket<R>> {
|
|
437
|
+
if (!indexName) {
|
|
438
|
+
return this.query(key, count);
|
|
439
|
+
}
|
|
440
|
+
return this.queryByIndex(indexName, key, count);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* @param key (对应的是主键的数据)
|
|
445
|
+
*/
|
|
446
|
+
async queryByKey<R extends Row>(key: IDBValidKey | IDBKeyRange): Promise<RowPacket<R>> {
|
|
447
|
+
let rows = await _request(_getObjectStore(this).get(key))
|
|
448
|
+
return new RowPacket<R>(rows);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* 分页查询
|
|
453
|
+
* @param pageNo 页码
|
|
454
|
+
* @param pageSize 每页数量
|
|
455
|
+
* @param key (对应的是主键的数据)
|
|
456
|
+
* @param indexName 索引名称
|
|
457
|
+
*/
|
|
458
|
+
async paginate<R extends Row>(pageNo: number = 1, pageSize: number = 10, key?: IDBValidKey | IDBKeyRange, indexName?: string): Promise<RowPacket<R>> {
|
|
459
|
+
const store = _getObjectStore(this);
|
|
460
|
+
let request: IDBRequest<any[]>;
|
|
461
|
+
let count = pageSize * pageNo;
|
|
462
|
+
if (indexName) {
|
|
463
|
+
const index = store.index(indexName);
|
|
464
|
+
request = index.getAll(key, count);
|
|
465
|
+
} else {
|
|
466
|
+
request = store.getAll(key, count);
|
|
467
|
+
}
|
|
468
|
+
let rows: Array<any> = await _request(request);
|
|
469
|
+
return new RowPacket(rows.slice(pageSize * (pageNo - 1), pageSize * pageNo));
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* 根据主键查询 如果是联合主键 则id是数组
|
|
474
|
+
* @param id id名称
|
|
475
|
+
* @param indexName 索引名称
|
|
476
|
+
*/
|
|
477
|
+
async queryById<R extends Row>(id: any | Array<any>, indexName?: string): Promise<R> {
|
|
478
|
+
const [result] = await this.select(IDBKeyRange.only(id), 1, indexName);
|
|
479
|
+
return result as R;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* 通过游标查询
|
|
484
|
+
* @param key
|
|
485
|
+
* @param direction
|
|
486
|
+
* @param indexName
|
|
487
|
+
*/
|
|
488
|
+
async scan<R extends Row>(key?: IDBValidKey | IDBKeyRange, direction: IDBCursorDirection = 'next', indexName?: string): Promise<RowPacket<R>> {
|
|
489
|
+
const store = _getObjectStore(this)
|
|
490
|
+
let request: IDBRequest<IDBCursorWithValue | null>
|
|
491
|
+
if (!indexName) {
|
|
492
|
+
request = store.openCursor(key, direction);
|
|
493
|
+
} else {
|
|
494
|
+
request = store.index(indexName).openCursor(key, direction);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return new Promise((resolve, reject) => {
|
|
498
|
+
let packets: RowPacket<R> = new RowPacket<R>([]);
|
|
499
|
+
request.onsuccess = function (event: Event) {
|
|
500
|
+
const req = event.target as IDBRequest<IDBCursorWithValue>;
|
|
501
|
+
const cursor = req.result
|
|
502
|
+
if (cursor) {
|
|
503
|
+
packets.push(cursor.value)
|
|
504
|
+
cursor.continue()
|
|
505
|
+
} else {
|
|
506
|
+
resolve(packets);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
request.onerror = function (event: Event) {
|
|
511
|
+
reject(event)
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* 获取所有数据
|
|
518
|
+
*/
|
|
519
|
+
getAllData<R extends Row>(): Promise<Array<R>> {
|
|
520
|
+
const store = _getObjectStore(this);
|
|
521
|
+
return _request(store.getAll())
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* 获取一个内存分页模型
|
|
526
|
+
*/
|
|
527
|
+
async getPagination<R extends Row>(pageSize: number = 10, pageIndex: number = 1): Promise<Pagination<R>> {
|
|
528
|
+
const rows = await this.getAllData<R>();
|
|
529
|
+
return new Pagination<R>(rows, pageSize, pageIndex)
|
|
530
|
+
}
|
|
531
|
+
}
|