xdriver 2.0.4 → 2.0.6

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/lib/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import { IndexdbStatus, KeyRange } from "./const";
2
2
  import Database from "./database";
3
3
  import Connection from "./connection";
4
4
  import Table, { TableMeta } from "./table";
5
- export { IndexdbStatus, KeyRange, Connection, Table };
5
+ import { Logger } from "./com";
6
6
  declare class Driver {
7
7
  private readonly name;
8
8
  private version;
@@ -12,4 +12,5 @@ declare class Driver {
12
12
  private hasChange;
13
13
  connect(version?: number): Promise<Database>;
14
14
  }
15
+ export { IndexdbStatus, KeyRange, Connection, Table, Logger, Driver };
15
16
  export default Driver;
package/lib/index.js CHANGED
@@ -12,7 +12,6 @@ import Database from "./database";
12
12
  import Connection from "./connection";
13
13
  import Table from "./table";
14
14
  import { Logger } from "./com";
15
- export { IndexdbStatus, KeyRange, Connection, Table };
16
15
  const logger = Logger.getLogger("Driver");
17
16
  class Driver {
18
17
  constructor(name, version = 0) {
@@ -137,4 +136,5 @@ class Driver {
137
136
  });
138
137
  }
139
138
  }
139
+ export { IndexdbStatus, KeyRange, Connection, Table, Logger, Driver };
140
140
  export default Driver;
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "xdriver",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
4
4
  "description": "A simple driver for IndexDB",
5
5
  "main": "./lib/index",
6
6
  "files": [
7
- "lib/"
7
+ "lib/", "src"
8
8
  ],
9
9
  "scripts": {
10
10
  "build": "tsc",
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
+ }
@@ -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,150 @@
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
+
8
+ const logger = Logger.getLogger("Driver")
9
+
10
+ class Driver {
11
+ private readonly name: string;
12
+ private version: number;
13
+ private readonly tables: Array<TableMeta> = [];
14
+
15
+ constructor(name: string, version: number = 0) {
16
+ this.name = name;
17
+ this.version = version;
18
+ }
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 {IndexdbStatus, KeyRange, Connection, Table, Logger, Driver};
148
+
149
+
150
+ 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
+ }