UNPKG

expo-sqlite

Version:

Provides access to a database using SQLite (https://www.sqlite.org/). The database is persisted across restarts of your app.

452 lines (415 loc) 13.2 kB
// Copyright 2015-present 650 Industries. All rights reserved. import { registerWebModule, NativeModule } from 'expo'; import { invokeWorkerAsync, invokeWorkerSync, workerMessageHandler } from './WorkerChannel'; import { type SQLiteOpenOptions } from '../src/NativeDatabase'; import { type Changeset } from '../src/NativeSession'; import { type SQLiteBindBlobParams, type SQLiteBindPrimitiveParams, type SQLiteColumnNames, type SQLiteColumnValues, type SQLiteRunResult, } from '../src/NativeStatement'; let worker: Worker | null = null; let nextNativeDatabaseId = 0; let nextNativeStatementId = 0; let nextNativeSessionId = 0; function getWorker(): Worker { if (!worker) { worker = new Worker(new URL('./worker', window.location.href)); worker.addEventListener('message', (event) => { if (event.data.type === 'onDatabaseChange') { // @ts-expect-error EventEmitter type for NativeModule is not inferred correctly on web. SQLiteModuleInstance.emit(event.data.type, event.data.data); return; } workerMessageHandler(event); }); } return worker; } class NativeDatabase { public readonly id: number; constructor( public readonly databasePath: string, public readonly options?: SQLiteOpenOptions, private serializedData?: Uint8Array ) { this.id = nextNativeDatabaseId++; } async initAsync(): Promise<void> { await invokeWorkerAsync(getWorker(), 'open', { nativeDatabaseId: this.id, databasePath: this.databasePath, options: this.options ?? {}, serializedData: this.serializedData, }); } initSync(): void { invokeWorkerSync(getWorker(), 'open', { nativeDatabaseId: this.id, databasePath: this.databasePath, options: this.options ?? {}, serializedData: this.serializedData, }); } async isInTransactionAsync(): Promise<boolean> { return await invokeWorkerAsync(getWorker(), 'isInTransaction', { nativeDatabaseId: this.id, }); } isInTransactionSync(): boolean { return invokeWorkerSync(getWorker(), 'isInTransaction', { nativeDatabaseId: this.id, }); } async closeAsync(): Promise<void> { await invokeWorkerAsync(getWorker(), 'close', { nativeDatabaseId: this.id, }); } closeSync(): void { invokeWorkerSync(getWorker(), 'close', { nativeDatabaseId: this.id, }); } async execAsync(source: string): Promise<void> { await invokeWorkerAsync(getWorker(), 'exec', { nativeDatabaseId: this.id, source, }); } execSync(source: string): void { invokeWorkerSync(getWorker(), 'exec', { nativeDatabaseId: this.id, source, }); } async serializeAsync(schemaName: string): Promise<Uint8Array> { return await invokeWorkerAsync(getWorker(), 'serialize', { nativeDatabaseId: this.id, schemaName, }); } serializeSync(schemaName: string): Uint8Array { return invokeWorkerSync(getWorker(), 'serialize', { nativeDatabaseId: this.id, schemaName, }); } async prepareAsync(nativeStatement: NativeStatement, source: string): Promise<void> { await invokeWorkerAsync(getWorker(), 'prepare', { nativeDatabaseId: this.id, nativeStatementId: nativeStatement.id, source, }); } prepareSync(nativeStatement: NativeStatement, source: string): void { invokeWorkerSync(getWorker(), 'prepare', { nativeDatabaseId: this.id, nativeStatementId: nativeStatement.id, source, }); } async createSessionAsync(nativeSession: NativeSession, dbName: string): Promise<void> { await invokeWorkerAsync(getWorker(), 'sessionCreate', { nativeDatabaseId: this.id, nativeSessionId: nativeSession.id, dbName, }); } createSessionSync(nativeSession: NativeSession, dbName: string): void { invokeWorkerSync(getWorker(), 'sessionCreate', { nativeDatabaseId: this.id, nativeSessionId: nativeSession.id, dbName, }); } } class NativeStatement { public readonly id: number; constructor() { this.id = nextNativeStatementId++; } async runAsync( database: NativeDatabase, bindParams: SQLiteBindPrimitiveParams, bindBlobParams: SQLiteBindBlobParams, shouldPassAsArray: boolean ): Promise<SQLiteRunResult & { firstRowValues: SQLiteColumnValues }> { if (this.id == null) { throw new Error('Statement not prepared'); } return await invokeWorkerAsync(getWorker(), 'run', { nativeDatabaseId: database.id, nativeStatementId: this.id, bindParams, bindBlobParams, shouldPassAsArray, }); } runSync( database: NativeDatabase, bindParams: SQLiteBindPrimitiveParams, bindBlobParams: SQLiteBindBlobParams, shouldPassAsArray: boolean ): SQLiteRunResult & { firstRowValues: SQLiteColumnValues } { if (this.id == null) { throw new Error('Statement not prepared'); } return invokeWorkerSync(getWorker(), 'run', { nativeDatabaseId: database.id, nativeStatementId: this.id, bindParams, bindBlobParams, shouldPassAsArray, }); } async stepAsync(database: NativeDatabase): Promise<SQLiteColumnValues | null> { if (this.id == null) { throw new Error('Statement not prepared'); } return await invokeWorkerAsync(getWorker(), 'step', { nativeDatabaseId: database.id, nativeStatementId: this.id, }); } stepSync(database: NativeDatabase): SQLiteColumnValues | null { if (this.id == null) { throw new Error('Statement not prepared'); } return invokeWorkerSync(getWorker(), 'step', { nativeDatabaseId: database.id, nativeStatementId: this.id, }); } async getAllAsync(database: NativeDatabase): Promise<SQLiteColumnValues[]> { if (this.id == null) { throw new Error('Statement not prepared'); } return await invokeWorkerAsync(getWorker(), 'getAll', { nativeDatabaseId: database.id, nativeStatementId: this.id, }); } getAllSync(database: NativeDatabase): SQLiteColumnValues[] { if (this.id == null) { throw new Error('Statement not prepared'); } return invokeWorkerSync(getWorker(), 'getAll', { nativeDatabaseId: database.id, nativeStatementId: this.id, }); } async resetAsync(database: NativeDatabase): Promise<void> { if (this.id == null) { throw new Error('Statement not prepared'); } await invokeWorkerAsync(getWorker(), 'reset', { nativeDatabaseId: database.id, nativeStatementId: this.id, }); } resetSync(database: NativeDatabase): void { if (this.id == null) { throw new Error('Statement not prepared'); } invokeWorkerSync(getWorker(), 'reset', { nativeDatabaseId: database.id, nativeStatementId: this.id, }); } async getColumnNamesAsync(): Promise<SQLiteColumnNames> { if (this.id == null) { throw new Error('Statement not prepared'); } return await invokeWorkerAsync(getWorker(), 'getColumnNames', { nativeStatementId: this.id, }); } getColumnNamesSync(): SQLiteColumnNames { if (this.id == null) { throw new Error('Statement not prepared'); } return invokeWorkerSync(getWorker(), 'getColumnNames', { nativeStatementId: this.id, }); } async finalizeAsync(database: NativeDatabase): Promise<void> { if (this.id == null) { throw new Error('Statement not prepared'); } await invokeWorkerAsync(getWorker(), 'finalize', { nativeDatabaseId: database.id, nativeStatementId: this.id, }); } finalizeSync(database: NativeDatabase): void { if (this.id == null) { throw new Error('Statement not prepared'); } invokeWorkerSync(getWorker(), 'finalize', { nativeDatabaseId: database.id, nativeStatementId: this.id, }); } } export class NativeSession { public readonly id: number; constructor() { this.id = nextNativeSessionId++; } async attachAsync(database: NativeDatabase, table: string | null): Promise<void> { await invokeWorkerAsync(getWorker(), 'sessionAttach', { nativeDatabaseId: database.id, nativeSessionId: this.id, table, }); } attachSync(database: NativeDatabase, table: string | null): void { invokeWorkerSync(getWorker(), 'sessionAttach', { nativeDatabaseId: database.id, nativeSessionId: this.id, table, }); } async enableAsync(database: NativeDatabase, enabled: boolean): Promise<void> { await invokeWorkerAsync(getWorker(), 'sessionEnable', { nativeDatabaseId: database.id, nativeSessionId: this.id, enabled, }); } enableSync(database: NativeDatabase, enabled: boolean): void { invokeWorkerSync(getWorker(), 'sessionEnable', { nativeDatabaseId: database.id, nativeSessionId: this.id, enabled, }); } async closeAsync(database: NativeDatabase): Promise<void> { await invokeWorkerAsync(getWorker(), 'sessionClose', { nativeDatabaseId: database.id, nativeSessionId: this.id, }); } closeSync(database: NativeDatabase): void { invokeWorkerSync(getWorker(), 'sessionClose', { nativeDatabaseId: database.id, nativeSessionId: this.id, }); } async createChangesetAsync(database: NativeDatabase): Promise<Changeset> { return await invokeWorkerAsync(getWorker(), 'sessionCreateChangeset', { nativeDatabaseId: database.id, nativeSessionId: this.id, }); } createChangesetSync(database: NativeDatabase): Changeset { return invokeWorkerSync(getWorker(), 'sessionCreateChangeset', { nativeDatabaseId: database.id, nativeSessionId: this.id, }); } async createInvertedChangesetAsync(database: NativeDatabase): Promise<Changeset> { return await invokeWorkerAsync(getWorker(), 'sessionCreateInvertedChangeset', { nativeDatabaseId: database.id, nativeSessionId: this.id, }); } createInvertedChangesetSync(database: NativeDatabase): Changeset { return invokeWorkerSync(getWorker(), 'sessionCreateInvertedChangeset', { nativeDatabaseId: database.id, nativeSessionId: this.id, }); } async applyChangesetAsync(database: NativeDatabase, changeset: Changeset): Promise<void> { await invokeWorkerAsync(getWorker(), 'sessionApplyChangeset', { nativeDatabaseId: database.id, nativeSessionId: this.id, changeset, }); } applyChangesetSync(database: NativeDatabase, changeset: Changeset): void { invokeWorkerSync(getWorker(), 'sessionApplyChangeset', { nativeDatabaseId: database.id, nativeSessionId: this.id, changeset, }); } async invertChangesetAsync(database: NativeDatabase, changeset: Changeset): Promise<Changeset> { return await invokeWorkerAsync(getWorker(), 'sessionInvertChangeset', { nativeDatabaseId: database.id, nativeSessionId: this.id, changeset, }); } invertChangesetSync(database: NativeDatabase, changeset: Changeset): Changeset { return invokeWorkerSync(getWorker(), 'sessionInvertChangeset', { nativeDatabaseId: database.id, nativeSessionId: this.id, changeset, }); } } export class SQLiteModule extends NativeModule { readonly defaultDatabaseDirectory = '.'; async deleteDatabaseAsync(databasePath: string): Promise<void> { await invokeWorkerAsync(getWorker(), 'deleteDatabase', { databasePath, }); } deleteDatabaseSync(databasePath: string): void { invokeWorkerSync(getWorker(), 'deleteDatabase', { databasePath, }); } async ensureDatabasePathExistsAsync(databasePath: string): Promise<void> { // No-op for web } ensureDatabasePathExistsSync(databasePath: string): void { // No-op for web } async backupDatabaseAsync( destDatabase: NativeDatabase, destDatabaseName: string, sourceDatabase: NativeDatabase, sourceDatabaseName: string ): Promise<void> { await invokeWorkerAsync(getWorker(), 'backupDatabase', { destNativeDatabaseId: destDatabase.id, destDatabaseName, sourceNativeDatabaseId: sourceDatabase.id, sourceDatabaseName, }); } backupDatabaseSync( destDatabase: NativeDatabase, destDatabaseName: string, sourceDatabase: NativeDatabase, sourceDatabaseName: string ): void { invokeWorkerSync(getWorker(), 'backupDatabase', { destNativeDatabaseId: destDatabase.id, destDatabaseName, sourceNativeDatabaseId: sourceDatabase.id, sourceDatabaseName, }); } async importAssetDatabaseAsync( databasePath: string, assetDatabasePath: string, forceOverwrite: boolean ): Promise<void> { await invokeWorkerAsync(getWorker(), 'importAssetDatabase', { databasePath, assetDatabasePath, forceOverwrite, }); } readonly NativeDatabase: typeof NativeDatabase = NativeDatabase; readonly NativeStatement: typeof NativeStatement = NativeStatement; readonly NativeSession: typeof NativeSession = NativeSession; } const SQLiteModuleInstance = registerWebModule(SQLiteModule, 'SQLiteModule'); export default SQLiteModuleInstance;