UNPKG

expo-sqlite

Version:

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

148 lines (135 loc) 4.12 kB
// Copyright 2015-present 650 Industries. All rights reserved. import { Deferred } from './Deferred'; import { serialize, deserialize } from './SyncSerializer'; import { type SQLiteWorkerMessageType, type MessageTypeMap, type ResultType, type ResultTypeMap, } from './web.types'; let messageId = 0; const deferredMap = new Map<number, Deferred>(); const PENDING = 1; const RESOLVED = 2; let hasWarnedSync = false; /** * For worker to send result to the main thread. */ export function sendWorkerResult({ id, result, error, syncTrait, }: { id: number; result: ResultType | null; error: Error | null; syncTrait?: { lockBuffer: SharedArrayBuffer; resultBuffer: SharedArrayBuffer; }; }) { if (syncTrait) { const { lockBuffer, resultBuffer } = syncTrait; const lock = new Int32Array(lockBuffer); const resultArray = new Uint8Array(resultBuffer); const resultJson = error != null ? serialize({ error }) : serialize({ result }); const resultBytes = new TextEncoder().encode(resultJson); const length = resultBytes.length; resultArray.set(new Uint32Array([length]), 0); resultArray.set(resultBytes, 4); Atomics.store(lock, 0, RESOLVED); } else { if (result) { self.postMessage({ id, result }); } else { self.postMessage({ id, error }); } } } /** * For main thread to handle worker messages. */ export function workerMessageHandler(event: MessageEvent) { const { id, result, error, isSync } = event.data; if (!isSync) { const deferred = deferredMap.get(id); if (deferred) { if (error) { deferred.reject(new Error(error)); } else { deferred.resolve(result); } deferredMap.delete(id); } } } /** * For main thread to invoke worker function asynchronously. */ export async function invokeWorkerAsync<T extends SQLiteWorkerMessageType & keyof ResultTypeMap>( worker: Worker, type: T, data: MessageTypeMap[T]['data'] ): Promise<ResultTypeMap[T]> { const id = messageId++; const deferred = new Deferred<ResultTypeMap[T]>(); deferredMap.set(id, deferred); worker.postMessage({ type, id, data, isSync: false }); return deferred.getPromise(); } /** * For main thread to invoke worker function synchronously. */ export function invokeWorkerSync<T extends SQLiteWorkerMessageType & keyof ResultTypeMap>( worker: Worker, type: T, data: MessageTypeMap[T]['data'] ): ResultTypeMap[T] { if (__DEV__ && !hasWarnedSync) { console.warn( 'Using synchronous SQLite operations can cause significant performance impact. Consider using async operations instead.' ); hasWarnedSync = true; } const id = messageId++; const lockBuffer = new SharedArrayBuffer(4); const lock = new Int32Array(lockBuffer); const resultBuffer = new SharedArrayBuffer(1024 * 1024); const resultArray = new Uint8Array(resultBuffer); Atomics.store(lock, 0, PENDING); worker.postMessage({ type, id, data, isSync: true, lockBuffer, resultBuffer, }); let i = 0; // @ts-expect-error: Remove this when TypeScript supports Atomics.pause const useAtomicsPause = typeof Atomics.pause === 'function'; while (Atomics.load(lock, 0) === PENDING) { ++i; if (useAtomicsPause) { if (i > 1_000_000) { throw new Error('Sync operation timeout'); } // @ts-expect-error: Remove this when TypeScript supports Atomics.pause Atomics.pause(); } else { // NOTE(kudo): Unfortunate for the busy loop, // because we don't have a way for main thread to yield its execution to other callbacks. if (i > 1000_000_000) { throw new Error('Sync operation timeout'); } } } const length = new Uint32Array(resultArray.buffer, 0, 1)[0]; const resultCopy = new Uint8Array(length); resultCopy.set(new Uint8Array(resultArray.buffer, 4, length)); const resultJson = new TextDecoder().decode(resultCopy); const { result, error } = deserialize<{ result: ResultTypeMap[T]; error?: string }>(resultJson); if (error) throw new Error(error); return result; }