@@ -13,16 +13,21 @@ import {
1313import { SSRDBAdapter } from '../SSRDBAdapter.js' ;
1414import { vfsRequiresDedicatedWorkers , WASQLiteVFS } from './vfs.js' ;
1515import { MultiDatabaseServer } from '../../../worker/db/MultiDatabaseServer.js' ;
16- import { ClientOptions , DatabaseClient , OpenWorkerConnection } from './DatabaseClient.js' ;
16+ import { DatabaseClient , OpenWorkerConnection } from './DatabaseClient.js' ;
1717import { generateTabCloseSignal } from '../../../shared/tab_close_signal.js' ;
18- import { AsyncDbAdapter } from '../AsyncWebAdapter.js' ;
18+ import { AsyncDbAdapter , PoolConnection } from '../AsyncWebAdapter.js' ;
1919
2020export interface WASQLiteOpenFactoryOptions extends WebSQLOpenFactoryOptions {
2121 vfs ?: WASQLiteVFS ;
2222}
2323
2424export interface ResolvedWASQLiteOpenFactoryOptions extends ResolvedWebSQLOpenOptions {
2525 vfs : WASQLiteVFS ;
26+
27+ /**
28+ * Whether this is a read-only connection opened for the `OPFSWriteAheadVFS` file system.
29+ */
30+ isReadOnly : boolean ;
2631}
2732
2833export interface WorkerDBOpenerOptions extends ResolvedWASQLiteOpenFactoryOptions {
@@ -82,7 +87,7 @@ export class WASQLiteOpenFactory implements SQLOpenFactory {
8287 return this . openAdapter ( ) ;
8388 }
8489
85- async openConnection ( ) : Promise < DatabaseClient > {
90+ async openConnection ( ) : Promise < PoolConnection > {
8691 const { enableMultiTabs, useWebWorker } = this . resolvedFlags ;
8792 const {
8893 vfs = WASQLiteVFS . IDBBatchAtomicVFS ,
@@ -95,70 +100,97 @@ export class WASQLiteOpenFactory implements SQLOpenFactory {
95100 this . logger . warn ( 'Multiple tabs are not enabled in this browser' ) ;
96101 }
97102
98- const resolvedOptions : ResolvedWASQLiteOpenFactoryOptions = {
103+ const resolveOptions = ( isReadOnly : boolean ) : ResolvedWASQLiteOpenFactoryOptions => ( {
99104 dbFilename : this . options . dbFilename ,
100105 dbLocation : this . options . dbLocation ,
101106 debugMode : this . options . debugMode ,
102107 vfs,
103108 temporaryStorage,
104109 cacheSizeKb,
105110 flags : this . resolvedFlags ,
106- encryptionKey : encryptionKey
107- } ;
111+ encryptionKey : encryptionKey ,
112+ isReadOnly
113+ } ) ;
108114
109- let clientOptions : ClientOptions ;
115+ let client : DatabaseClient ;
116+ let additionalReader : DatabaseClient | undefined ;
110117 let requiresPersistentTriggers = vfsRequiresDedicatedWorkers ( vfs ) ;
111118
112119 if ( useWebWorker ) {
113120 const optionsDbWorker = this . options . worker ;
114121
115- const workerPort =
116- typeof optionsDbWorker == 'function'
117- ? resolveWorkerDatabasePortFactory ( ( ) =>
118- optionsDbWorker ( {
119- ...this . options ,
120- temporaryStorage,
121- cacheSizeKb,
122- flags : this . resolvedFlags ,
123- encryptionKey
124- } )
125- )
126- : openWorkerDatabasePort ( this . options . dbFilename , enableMultiTabs , optionsDbWorker , this . waOptions . vfs ) ;
127-
128- const source = Comlink . wrap < OpenWorkerConnection > ( workerPort ) ;
129- const closeSignal = new AbortController ( ) ;
130- const connection = await source . connect ( {
131- ...resolvedOptions ,
132- logLevel : this . logger . getLevel ( ) ,
133- lockName : await generateTabCloseSignal ( closeSignal . signal )
134- } ) ;
135- clientOptions = {
136- connection,
137- source,
138- // This tab owns the worker, so we're guaranteed to outlive it.
139- remoteCanCloseUnexpectedly : false ,
140- onClose : ( ) => {
141- closeSignal . abort ( ) ;
142- if ( workerPort instanceof Worker ) {
143- workerPort . terminate ( ) ;
144- } else {
145- workerPort . close ( ) ;
122+ const openDatabaseWorker = async (
123+ resolvedOptions : ResolvedWASQLiteOpenFactoryOptions
124+ ) : Promise < DatabaseClient > => {
125+ const workerPort =
126+ typeof optionsDbWorker == 'function'
127+ ? resolveWorkerDatabasePortFactory ( ( ) =>
128+ optionsDbWorker ( {
129+ ...this . options ,
130+ temporaryStorage,
131+ cacheSizeKb,
132+ flags : this . resolvedFlags ,
133+ encryptionKey
134+ } )
135+ )
136+ : openWorkerDatabasePort ( this . options . dbFilename , enableMultiTabs , optionsDbWorker , this . waOptions . vfs ) ;
137+
138+ const source = Comlink . wrap < OpenWorkerConnection > ( workerPort ) ;
139+ const closeSignal = new AbortController ( ) ;
140+ const connection = await source . connect ( {
141+ ...resolvedOptions ,
142+ logLevel : this . logger . getLevel ( ) ,
143+ lockName : await generateTabCloseSignal ( closeSignal . signal )
144+ } ) ;
145+ const clientOptions = {
146+ connection,
147+ source,
148+ // This tab owns the worker, so we're guaranteed to outlive it.
149+ remoteCanCloseUnexpectedly : false ,
150+ onClose : ( ) => {
151+ closeSignal . abort ( ) ;
152+ if ( workerPort instanceof Worker ) {
153+ workerPort . terminate ( ) ;
154+ } else {
155+ workerPort . close ( ) ;
156+ }
146157 }
147- }
158+ } ;
159+
160+ return new DatabaseClient ( clientOptions , {
161+ ...resolvedOptions ,
162+ requiresPersistentTriggers
163+ } ) ;
148164 } ;
165+
166+ client = await openDatabaseWorker ( resolveOptions ( false ) ) ;
167+
168+ if ( vfs == WASQLiteVFS . OPFSWriteAheadVFS ) {
169+ // This VFS supports concurrent reads, so we can open additional workers to host read-only connections for
170+ // concurrent reads / writes. To avoid excessive resource usage, we currently add one additional reader per
171+ // tab. In the future, we might revisit this to use a growable pool of readers.
172+ additionalReader = await openDatabaseWorker ( resolveOptions ( true ) ) ;
173+ }
149174 } else {
150175 // Don't use a web worker. Instead, open the MultiDatabaseServer a worker would use locally.
151176 const localServer = new MultiDatabaseServer ( this . logger ) ;
152177 requiresPersistentTriggers = true ;
153178
179+ const resolvedOptions = resolveOptions ( false ) ;
154180 const connection = await localServer . openConnectionLocally ( resolvedOptions ) ;
155- clientOptions = { connection, source : null , remoteCanCloseUnexpectedly : false } ;
181+ client = new DatabaseClient (
182+ { connection, source : null , remoteCanCloseUnexpectedly : false } ,
183+ {
184+ ...resolvedOptions ,
185+ requiresPersistentTriggers
186+ }
187+ ) ;
156188 }
157189
158- return new DatabaseClient ( clientOptions , {
159- ... resolvedOptions ,
160- requiresPersistentTriggers
161- } ) ;
190+ return {
191+ writer : client ,
192+ additionalReader
193+ } ;
162194 }
163195}
164196
0 commit comments