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-
711class Database {
722 #name;
73- #version;
74- #schemas;
75- #instance;
3+ #version = 1 ;
4+ #schemas = null ;
5+ #instance = null ;
766 #active = false ;
77- #stores = new Map ( ) ;
787
79- constructor ( name , version , schemas ) {
8+ constructor ( name , { version, schemas } ) {
809 this . #name = name ;
81- this . #version = version ;
82- this . #schemas = schemas ;
10+ if ( version ) this . #version = version ;
11+ if ( schemas ) this . #schemas = schemas ;
8312 return this . #open( ) ;
8413 }
8514
8615 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 ) => {
9417 const request = indexedDB . open ( this . #name, this . #version) ;
95- request . onupgradeneeded = ( event ) => this . #upgrade( event ) ;
18+ request . onupgradeneeded = ( event ) => this . #upgrade( event . target . result ) ;
9619 request . onsuccess = ( event ) => {
9720 this . #active = true ;
98- resolve ( event . target . result ) ;
21+ this . #instance = event . target . result ;
22+ resolve ( ) ;
9923 } ;
10024 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 ) ;
10228 } ;
10329 } ) ;
30+ return this ;
10431 }
10532
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 ) {
11534 for ( const [ name , schema ] of Object . entries ( this . #schemas) ) {
11635 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+ }
11841 }
11942 }
12043 }
12144
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+ }
12949 return new Promise ( ( resolve , reject ) => {
13050 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 ) ;
13353 const result = operation ( store ) ;
13454 tx . oncomplete = ( ) => resolve ( result ) ;
13555 tx . onerror = ( ) => reject ( tx . error || new Error ( 'Transaction error' ) ) ;
@@ -138,6 +58,82 @@ class Database {
13858 }
13959 } ) ;
14060 }
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+ }
141137}
142138
143- export { Repository , Database } ;
139+ export { Database } ;
0 commit comments