UNPKG

@react-native-firebase/app

Version:

A well tested, feature rich Firebase implementation for React Native, supporting iOS & Android. Individual module support for Admob, Analytics, Auth, Crash Reporting, Cloud Firestore, Database, Dynamic Links, Functions, Messaging (FCM), Remote Config, Sto

504 lines (490 loc) 15.9 kB
import FDBKeyRange from './FDBKeyRange.js'; import FDBObjectStore from './FDBObjectStore.js'; import cmp from './lib/cmp.js'; import { DataError, InvalidAccessError, InvalidStateError, ReadOnlyError, TransactionInactiveError, } from './lib/errors.js'; import extractKey from './lib/extractKey.js'; import valueToKey from './lib/valueToKey.js'; const getEffectiveObjectStore = cursor => { if (cursor.source instanceof FDBObjectStore) { return cursor.source; } return cursor.source.objectStore; }; // This takes a key range, a list of lower bounds, and a list of upper bounds and combines them all into a single key // range. It does not handle gt/gte distinctions, because it doesn't really matter much anyway, since for next/prev // cursor iteration it'd also have to look at values to be precise, which would be complicated. This should get us 99% // of the way there. const makeKeyRange = (range, lowers, uppers) => { // Start with bounds from range let lower = range !== undefined ? range.lower : undefined; let upper = range !== undefined ? range.upper : undefined; // Augment with values from lowers and uppers for (const lowerTemp of lowers) { if (lowerTemp === undefined) { continue; } if (lower === undefined || cmp(lower, lowerTemp) === 1) { lower = lowerTemp; } } for (const upperTemp of uppers) { if (upperTemp === undefined) { continue; } if (upper === undefined || cmp(upper, upperTemp) === -1) { upper = upperTemp; } } if (lower !== undefined && upper !== undefined) { return FDBKeyRange.bound(lower, upper); } if (lower !== undefined) { return FDBKeyRange.lowerBound(lower); } if (upper !== undefined) { return FDBKeyRange.upperBound(upper); } }; // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#cursor class FDBCursor { _gotValue = false; _position = undefined; // Key of previously returned record _objectStorePosition = undefined; _keyOnly = false; _key = undefined; _primaryKey = undefined; constructor(source, range, direction = 'next', request, keyOnly = false) { this._range = range; this._source = source; this._direction = direction; this._request = request; this._keyOnly = keyOnly; } // Read only properties get source() { return this._source; } set source(val) { /* For babel */ } get request() { return this._request; } set request(val) { /* For babel */ } get direction() { return this._direction; } set direction(val) { /* For babel */ } get key() { return this._key; } set key(val) { /* For babel */ } get primaryKey() { return this._primaryKey; } set primaryKey(val) { /* For babel */ } // https://w3c.github.io/IndexedDB/#iterate-a-cursor _iterate(key, primaryKey) { const sourceIsObjectStore = this.source instanceof FDBObjectStore; // Can't use sourceIsObjectStore because TypeScript const records = this.source instanceof FDBObjectStore ? this.source._rawObjectStore.records : this.source._rawIndex.records; let foundRecord; if (this.direction === 'next') { const range = makeKeyRange(this._range, [key, this._position], []); for (const record of records.values(range)) { const cmpResultKey = key !== undefined ? cmp(record.key, key) : undefined; const cmpResultPosition = this._position !== undefined ? cmp(record.key, this._position) : undefined; if (key !== undefined) { if (cmpResultKey === -1) { continue; } } if (primaryKey !== undefined) { if (cmpResultKey === -1) { continue; } const cmpResultPrimaryKey = cmp(record.value, primaryKey); if (cmpResultKey === 0 && cmpResultPrimaryKey === -1) { continue; } } if (this._position !== undefined && sourceIsObjectStore) { if (cmpResultPosition !== 1) { continue; } } if (this._position !== undefined && !sourceIsObjectStore) { if (cmpResultPosition === -1) { continue; } if (cmpResultPosition === 0 && cmp(record.value, this._objectStorePosition) !== 1) { continue; } } if (this._range !== undefined) { if (!this._range.includes(record.key)) { continue; } } foundRecord = record; break; } } else if (this.direction === 'nextunique') { // This could be done without iterating, if the range was defined slightly better (to handle gt/gte cases). // But the performance difference should be small, and that wouldn't work anyway for directions where the // value needs to be used (like next and prev). const range = makeKeyRange(this._range, [key, this._position], []); for (const record of records.values(range)) { if (key !== undefined) { if (cmp(record.key, key) === -1) { continue; } } if (this._position !== undefined) { if (cmp(record.key, this._position) !== 1) { continue; } } if (this._range !== undefined) { if (!this._range.includes(record.key)) { continue; } } foundRecord = record; break; } } else if (this.direction === 'prev') { const range = makeKeyRange(this._range, [], [key, this._position]); for (const record of records.values(range, 'prev')) { const cmpResultKey = key !== undefined ? cmp(record.key, key) : undefined; const cmpResultPosition = this._position !== undefined ? cmp(record.key, this._position) : undefined; if (key !== undefined) { if (cmpResultKey === 1) { continue; } } if (primaryKey !== undefined) { if (cmpResultKey === 1) { continue; } const cmpResultPrimaryKey = cmp(record.value, primaryKey); if (cmpResultKey === 0 && cmpResultPrimaryKey === 1) { continue; } } if (this._position !== undefined && sourceIsObjectStore) { if (cmpResultPosition !== -1) { continue; } } if (this._position !== undefined && !sourceIsObjectStore) { if (cmpResultPosition === 1) { continue; } if (cmpResultPosition === 0 && cmp(record.value, this._objectStorePosition) !== -1) { continue; } } if (this._range !== undefined) { if (!this._range.includes(record.key)) { continue; } } foundRecord = record; break; } } else if (this.direction === 'prevunique') { let tempRecord; const range = makeKeyRange(this._range, [], [key, this._position]); for (const record of records.values(range, 'prev')) { if (key !== undefined) { if (cmp(record.key, key) === 1) { continue; } } if (this._position !== undefined) { if (cmp(record.key, this._position) !== -1) { continue; } } if (this._range !== undefined) { if (!this._range.includes(record.key)) { continue; } } tempRecord = record; break; } if (tempRecord) { foundRecord = records.get(tempRecord.key); } } let result; if (!foundRecord) { this._key = undefined; if (!sourceIsObjectStore) { this._objectStorePosition = undefined; } // "this instanceof FDBCursorWithValue" would be better and not require (this as any), but causes runtime // error due to circular dependency. if (!this._keyOnly && this.toString() === '[object IDBCursorWithValue]') { this.value = undefined; } result = null; } else { this._position = foundRecord.key; if (!sourceIsObjectStore) { this._objectStorePosition = foundRecord.value; } this._key = foundRecord.key; if (sourceIsObjectStore) { this._primaryKey = structuredClone(foundRecord.key); if (!this._keyOnly && this.toString() === '[object IDBCursorWithValue]') { this.value = structuredClone(foundRecord.value); } } else { this._primaryKey = structuredClone(foundRecord.value); if (!this._keyOnly && this.toString() === '[object IDBCursorWithValue]') { if (this.source instanceof FDBObjectStore) { // Can't use sourceIsObjectStore because TypeScript throw new Error('This should never happen'); } const value = this.source.objectStore._rawObjectStore.getValue(foundRecord.value); this.value = structuredClone(value); } } this._gotValue = true; // eslint-disable-next-line @typescript-eslint/no-this-alias result = this; } return result; } // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBCursor-update-IDBRequest-any-value update(value) { if (value === undefined) { throw new TypeError(); } const effectiveObjectStore = getEffectiveObjectStore(this); const effectiveKey = Object.hasOwn(this.source, '_rawIndex') ? this.primaryKey : this._position; const transaction = effectiveObjectStore.transaction; if (transaction._state !== 'active') { throw new TransactionInactiveError(); } if (transaction.mode === 'readonly') { throw new ReadOnlyError(); } if (effectiveObjectStore._rawObjectStore.deleted) { throw new InvalidStateError(); } if (!(this.source instanceof FDBObjectStore) && this.source._rawIndex.deleted) { throw new InvalidStateError(); } if (!this._gotValue || !Object.hasOwn(this, 'value')) { throw new InvalidStateError(); } const clone = structuredClone(value); if (effectiveObjectStore.keyPath !== null) { let tempKey; try { tempKey = extractKey(effectiveObjectStore.keyPath, clone); } catch (_) { /* Handled immediately below */ } if (cmp(tempKey, effectiveKey) !== 0) { throw new DataError(); } } const record = { key: effectiveKey, value: clone, }; return transaction._execRequestAsync({ operation: effectiveObjectStore._rawObjectStore.storeRecord.bind( effectiveObjectStore._rawObjectStore, record, false, transaction._rollbackLog, ), source: this, }); } // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBCursor-advance-void-unsigned-long-count advance(count) { if (!Number.isInteger(count) || count <= 0) { throw new TypeError(); } const effectiveObjectStore = getEffectiveObjectStore(this); const transaction = effectiveObjectStore.transaction; if (transaction._state !== 'active') { throw new TransactionInactiveError(); } if (effectiveObjectStore._rawObjectStore.deleted) { throw new InvalidStateError(); } if (!(this.source instanceof FDBObjectStore) && this.source._rawIndex.deleted) { throw new InvalidStateError(); } if (!this._gotValue) { throw new InvalidStateError(); } if (this._request) { this._request.readyState = 'pending'; } transaction._execRequestAsync({ operation: () => { let result; for (let i = 0; i < count; i++) { result = this._iterate(); // Not sure why this is needed if (!result) { break; } } return result; }, request: this._request, source: this.source, }); this._gotValue = false; } // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBCursor-continue-void-any-key continue(key) { const effectiveObjectStore = getEffectiveObjectStore(this); const transaction = effectiveObjectStore.transaction; if (transaction._state !== 'active') { throw new TransactionInactiveError(); } if (effectiveObjectStore._rawObjectStore.deleted) { throw new InvalidStateError(); } if (!(this.source instanceof FDBObjectStore) && this.source._rawIndex.deleted) { throw new InvalidStateError(); } if (!this._gotValue) { throw new InvalidStateError(); } if (key !== undefined) { key = valueToKey(key); const cmpResult = cmp(key, this._position); if ( (cmpResult <= 0 && (this.direction === 'next' || this.direction === 'nextunique')) || (cmpResult >= 0 && (this.direction === 'prev' || this.direction === 'prevunique')) ) { throw new DataError(); } } if (this._request) { this._request.readyState = 'pending'; } transaction._execRequestAsync({ operation: this._iterate.bind(this, key), request: this._request, source: this.source, }); this._gotValue = false; } // hthttps://w3c.github.io/IndexedDB/#dom-idbcursor-continueprimarykey continuePrimaryKey(key, primaryKey) { const effectiveObjectStore = getEffectiveObjectStore(this); const transaction = effectiveObjectStore.transaction; if (transaction._state !== 'active') { throw new TransactionInactiveError(); } if (effectiveObjectStore._rawObjectStore.deleted) { throw new InvalidStateError(); } if (!(this.source instanceof FDBObjectStore) && this.source._rawIndex.deleted) { throw new InvalidStateError(); } if ( this.source instanceof FDBObjectStore || (this.direction !== 'next' && this.direction !== 'prev') ) { throw new InvalidAccessError(); } if (!this._gotValue) { throw new InvalidStateError(); } // Not sure about this if (key === undefined || primaryKey === undefined) { throw new DataError(); } key = valueToKey(key); const cmpResult = cmp(key, this._position); if ( (cmpResult === -1 && this.direction === 'next') || (cmpResult === 1 && this.direction === 'prev') ) { throw new DataError(); } const cmpResult2 = cmp(primaryKey, this._objectStorePosition); if (cmpResult === 0) { if ( (cmpResult2 <= 0 && this.direction === 'next') || (cmpResult2 >= 0 && this.direction === 'prev') ) { throw new DataError(); } } if (this._request) { this._request.readyState = 'pending'; } transaction._execRequestAsync({ operation: this._iterate.bind(this, key, primaryKey), request: this._request, source: this.source, }); this._gotValue = false; } delete() { const effectiveObjectStore = getEffectiveObjectStore(this); const effectiveKey = Object.hasOwn(this.source, '_rawIndex') ? this.primaryKey : this._position; const transaction = effectiveObjectStore.transaction; if (transaction._state !== 'active') { throw new TransactionInactiveError(); } if (transaction.mode === 'readonly') { throw new ReadOnlyError(); } if (effectiveObjectStore._rawObjectStore.deleted) { throw new InvalidStateError(); } if (!(this.source instanceof FDBObjectStore) && this.source._rawIndex.deleted) { throw new InvalidStateError(); } if (!this._gotValue || !Object.hasOwn(this, 'value')) { throw new InvalidStateError(); } return transaction._execRequestAsync({ operation: effectiveObjectStore._rawObjectStore.deleteRecord.bind( effectiveObjectStore._rawObjectStore, effectiveKey, transaction._rollbackLog, ), source: this, }); } toString() { return '[object IDBCursor]'; } } export default FDBCursor;