1
- class Repository {
2
- constructor ( store , db , schema ) {
3
- this . store = store ;
4
- this . db = db ;
5
- this . schema = schema ;
6
- }
7
-
8
- insert ( record ) {
9
- const op = ( store ) => store . add ( record ) ;
10
- return this . db . execute ( this . store , 'readwrite' , op ) ;
11
- }
12
-
13
- async select ( { where, limit, offset, order } = { } ) {
14
- const op = ( store ) => {
15
- const results = [ ] ;
16
- let skipped = 0 ;
17
- return new Promise ( ( resolve , reject ) => {
18
- const cursor = store . openCursor ( ) ;
19
- const done = ( ) => resolve ( Repository . #order( results , order ) ) ;
20
- cursor . onerror = ( ) => reject ( cursor . error ) ;
21
- cursor . onsuccess = ( event ) => {
22
- const cursor = event . target . result ;
23
- if ( ! cursor ) return void done ( ) ;
24
- const record = cursor . value ;
25
- if ( ! where || where ( record ) ) {
26
- if ( ! offset || skipped >= offset ) {
27
- results . push ( record ) ;
28
- if ( limit && results . length >= limit ) return void done ( ) ;
29
- } else {
30
- skipped ++ ;
31
- }
32
- }
33
- cursor . continue ( ) ;
34
- } ;
35
- } ) ;
36
- } ;
37
- return this . db . execute ( this . store , 'readonly' , op ) ;
38
- }
39
-
40
- static #order( arr , order ) {
41
- if ( ! order ) return arr ;
42
- const [ field , dir = 'asc' ] = order . split ( ' ' ) ;
43
- const sign = dir === 'desc' ? - 1 : 1 ;
44
- return [ ...arr ] . sort ( ( a , b ) => {
45
- if ( a [ field ] === b [ field ] ) return 0 ;
46
- return a [ field ] > b [ field ] ? sign : - sign ;
47
- } ) ;
48
- }
49
-
50
- get ( { id } ) {
51
- return this . db . execute ( this . store , 'readonly' , ( store ) => {
52
- const req = store . get ( id ) ;
53
- return new Promise ( ( resolve , reject ) => {
54
- req . onerror = ( ) => reject ( req . error || new Error ( `Can't get ${ id } ` ) ) ;
55
- req . onsuccess = ( ) => resolve ( req . result ) ;
56
- } ) ;
57
- } ) ;
58
- }
59
-
60
- update ( record ) {
61
- const op = ( store ) => store . put ( record ) ;
62
- return this . db . execute ( this . store , 'readwrite' , op ) ;
63
- }
64
-
65
- delete ( { id } ) {
66
- const op = ( store ) => store . delete ( id ) ;
67
- return this . db . execute ( this . store , 'readwrite' , op ) ;
68
- }
69
- }
70
-
71
1
class Database {
72
2
#name;
73
- #version;
74
- #schemas;
75
- #instance;
3
+ #version = 1 ;
4
+ #schemas = null ;
5
+ #instance = null ;
76
6
#active = false ;
77
- #stores = new Map ( ) ;
78
7
79
- constructor ( name , version , schemas ) {
8
+ constructor ( name , { version, schemas } ) {
80
9
this . #name = name ;
81
- this . #version = version ;
82
- this . #schemas = schemas ;
10
+ if ( version ) this . #version = version ;
11
+ if ( schemas ) this . #schemas = schemas ;
83
12
return this . #open( ) ;
84
13
}
85
14
86
15
async #open( ) {
87
- await this . #connect( ) ;
88
- await this . #init( ) ;
89
- return this ;
90
- }
91
-
92
- async #connect( ) {
93
- this . #instance = await new Promise ( ( resolve , reject ) => {
16
+ await new Promise ( ( resolve , reject ) => {
94
17
const request = indexedDB . open ( this . #name, this . #version) ;
95
- request . onupgradeneeded = ( event ) => this . #upgrade( event ) ;
18
+ request . onupgradeneeded = ( event ) => this . #upgrade( event . target . result ) ;
96
19
request . onsuccess = ( event ) => {
97
20
this . #active = true ;
98
- resolve ( event . target . result ) ;
21
+ this . #instance = event . target . result ;
22
+ resolve ( ) ;
99
23
} ;
100
24
request . onerror = ( event ) => {
101
- reject ( event . target . error || new Error ( 'IndexedDB open error' ) ) ;
25
+ let { error } = event . target ;
26
+ if ( ! error ) error = new Error ( `IndexedDB: can't open ${ this . #name} ` ) ;
27
+ reject ( error ) ;
102
28
} ;
103
29
} ) ;
30
+ return this ;
104
31
}
105
32
106
- #init( ) {
107
- for ( const [ name , schema ] of Object . entries ( this . #schemas) ) {
108
- const store = new Repository ( name , this , schema ) ;
109
- this . #stores. set ( name , store ) ;
110
- }
111
- }
112
-
113
- #upgrade( event ) {
114
- const db = event . target . result ;
33
+ #upgrade( db ) {
115
34
for ( const [ name , schema ] of Object . entries ( this . #schemas) ) {
116
35
if ( ! db . objectStoreNames . contains ( name ) ) {
117
- db . createObjectStore ( name , schema ) ;
36
+ const store = db . createObjectStore ( name , schema ) ;
37
+ const indexes = schema . indexes || [ ] ;
38
+ for ( const index of Object . entries ( indexes ) ) {
39
+ store . createIndex ( index . name , index . keyPath , index . options ) ;
40
+ }
118
41
}
119
42
}
120
43
}
121
44
122
- getStore ( name ) {
123
- return this . #stores. get ( name ) ;
124
- }
125
-
126
- async execute ( storeName , mode , operation ) {
127
- if ( ! this . #active) throw new Error ( 'Database not connected' ) ;
128
- const db = this . #instance;
45
+ #exec( entity , operation , mode = 'readwrite' ) {
46
+ if ( ! this . #active) {
47
+ return Promise . reject ( new Error ( 'Database not connected' ) ) ;
48
+ }
129
49
return new Promise ( ( resolve , reject ) => {
130
50
try {
131
- const tx = db . transaction ( storeName , mode ) ;
132
- const store = tx . objectStore ( storeName ) ;
51
+ const tx = this . #instance . transaction ( entity , mode ) ;
52
+ const store = tx . objectStore ( entity ) ;
133
53
const result = operation ( store ) ;
134
54
tx . oncomplete = ( ) => resolve ( result ) ;
135
55
tx . onerror = ( ) => reject ( tx . error || new Error ( 'Transaction error' ) ) ;
@@ -138,6 +58,82 @@ class Database {
138
58
}
139
59
} ) ;
140
60
}
61
+
62
+ async insert ( entity , record ) {
63
+ return this . #exec( entity , ( store ) => store . add ( record ) ) ;
64
+ }
65
+
66
+ async update ( entity , record ) {
67
+ return this . #exec( entity , ( store ) => store . put ( record ) ) ;
68
+ }
69
+
70
+ async delete ( entity , { id } ) {
71
+ return this . #exec( entity , ( store ) => store . delete ( id ) ) ;
72
+ }
73
+
74
+ async get ( entity , { id } ) {
75
+ return this . #exec(
76
+ entity ,
77
+ ( store ) => {
78
+ const req = store . get ( id ) ;
79
+ return new Promise ( ( resolve , reject ) => {
80
+ req . onsuccess = ( ) => resolve ( req . result ) ;
81
+ req . onerror = ( ) => reject ( req . error || new Error ( `Can't get ${ id } ` ) ) ;
82
+ } ) ;
83
+ } ,
84
+ 'readonly' ,
85
+ ) ;
86
+ }
87
+
88
+ async select ( entity , { where, limit, offset, order, filter, sort } = { } ) {
89
+ return this . #exec(
90
+ entity ,
91
+ ( store ) => {
92
+ const results = [ ] ;
93
+ let skipped = 0 ;
94
+ return new Promise ( ( resolve , reject ) => {
95
+ const req = store . openCursor ( ) ;
96
+ req . onerror = ( ) => reject ( req . error ) ;
97
+ req . onsuccess = ( event ) => {
98
+ const cursor = event . target . result ;
99
+ if ( ! cursor ) {
100
+ const filtered = filter ? results . filter ( filter ) : results ;
101
+ const sorted = sort
102
+ ? filtered . toSorted ( sort )
103
+ : Database . #order( filtered , order ) ;
104
+ return void resolve ( sorted ) ;
105
+ }
106
+ const record = cursor . value ;
107
+ const match =
108
+ ! where ||
109
+ Object . entries ( where ) . every ( ( [ key , val ] ) => record [ key ] === val ) ;
110
+ if ( match ) {
111
+ if ( ! offset || skipped >= offset ) {
112
+ results . push ( record ) ;
113
+ if ( limit && results . length >= limit ) {
114
+ return void resolve ( results ) ;
115
+ }
116
+ } else {
117
+ skipped ++ ;
118
+ }
119
+ }
120
+ cursor . continue ( ) ;
121
+ } ;
122
+ } ) ;
123
+ } ,
124
+ 'readonly' ,
125
+ ) ;
126
+ }
127
+
128
+ static #order( arr , order ) {
129
+ if ( ! order || typeof order !== 'object' ) return arr ;
130
+ const [ [ field , dir ] ] = Object . entries ( order ) ;
131
+ const sign = dir === 'desc' ? - 1 : 1 ;
132
+ return [ ...arr ] . sort ( ( a , b ) => {
133
+ if ( a [ field ] === b [ field ] ) return 0 ;
134
+ return a [ field ] > b [ field ] ? sign : - sign ;
135
+ } ) ;
136
+ }
141
137
}
142
138
143
- export { Repository , Database } ;
139
+ export { Database } ;
0 commit comments